blob: bf0a50a07f6b90a7e222dbae3ca4bc50c7029b66 [file] [log] [blame]
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:io' as io;
import 'package:analyzer/analysis_rule/analysis_rule.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart' as file_system;
import 'package:analyzer/instrumentation/instrumentation.dart';
import 'package:analyzer/source/file_source.dart';
import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/src/generated/engine.dart' show AnalysisEngine;
import 'package:analyzer/src/lint/pub.dart';
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
import 'package:path/path.dart' as path;
import 'util/io.dart';
class TestLinter implements DiagnosticListener {
final errors = <Diagnostic>[];
final List<AbstractAnalysisRule> _rules;
final String? _dartSdkPath;
TestLinter(this._rules, this._dartSdkPath);
ResourceProvider get _resourceProvider =>
file_system.PhysicalResourceProvider.INSTANCE;
Future<List<Diagnostic>> lintFiles(List<io.File> files) async {
var errors = await _analyze(files.where((f) => f.path.endsWith('.dart')));
for (var file in files.where(_isPubspecFile)) {
_lintPubspecSource(
contents: file.readAsStringSync(),
sourcePath: _resourceProvider.pathContext.normalize(file.absolute.path),
);
}
return errors;
}
@override
void onDiagnostic(Diagnostic error) => errors.add(error);
String _absoluteNormalizedPath(String path) => _resourceProvider.pathContext
.normalize(_resourceProvider.pathContext.absolute(path));
Future<List<Diagnostic>> _analyze(Iterable<io.File> files) async {
AnalysisEngine.instance.instrumentationService = _StdInstrumentation();
var filePaths =
files.map((file) => _absoluteNormalizedPath(file.path)).toList();
var contextCollection = AnalysisContextCollectionImpl(
resourceProvider: _resourceProvider,
sdkPath: _dartSdkPath,
includedPaths: filePaths,
updateAnalysisOptions3: ({required analysisOptions, required sdk}) {
analysisOptions.lint = true;
analysisOptions.warning = false;
analysisOptions.lintRules = _rules;
},
enableLintRuleTiming: true,
);
var result = <Diagnostic>[];
for (var path in filePaths) {
var analysisSession = contextCollection.contextFor(path).currentSession;
var errorsResult = await analysisSession.getErrors(path);
if (errorsResult is ErrorsResult) {
result.addAll(errorsResult.diagnostics);
}
}
return result;
}
/// Whether this [entry] is a pubspec file.
bool _isPubspecFile(io.FileSystemEntity entry) =>
path.basename(entry.path) == file_paths.pubspecYaml;
void _lintPubspecSource({required String contents, String? sourcePath}) {
var sourceUrl = sourcePath == null ? null : path.toUri(sourcePath);
var spec = Pubspec.parse(contents, sourceUrl: sourceUrl);
for (var rule in _rules) {
var visitor = rule.pubspecVisitor;
if (visitor != null) {
// Analyzer sets reporters; if this file is not being analyzed,
// we need to set one ourselves. (Needless to say, when pubspec
// processing gets pushed down, this hack can go away.)
if (sourceUrl != null) {
var source = FileSource(
_resourceProvider.getFile(sourceUrl.toFilePath()),
sourceUrl,
);
rule.reporter = DiagnosticReporter(this, source);
}
try {
spec.accept(visitor);
} on Exception catch (_) {
// TODO(srawlins): Report the exception somewhere?
}
}
}
}
}
/// Prints logging information comments to the [outSink] and error messages to
/// [errorSink].
class _StdInstrumentation extends NoopInstrumentationService {
@override
void logError(String message, [Object? exception]) {
errorSink.writeln(message);
if (exception != null) {
errorSink.writeln(exception);
}
}
@override
void logException(
exception, [
StackTrace? stackTrace,
List<InstrumentationServiceAttachment>? attachments,
]) {
errorSink.writeln(exception);
errorSink.writeln(stackTrace);
}
@override
void logInfo(String message, [Object? exception]) {
outSink.writeln(message);
if (exception != null) {
outSink.writeln(exception);
}
}
}