linter: inline LintDriver code into TestLinter
Change-Id: I1eb821f2e0365204bd415b630fb6a7e5a7ba47a7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/444926 Commit-Queue: Brian Wilkerson <brianwilkerson@google.com> Auto-Submit: Samuel Rawlins <srawlins@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
committed by
Commit Queue
parent
e1619544a2
commit
23a63f2542
@@ -20,7 +20,6 @@ import 'package:path/path.dart' as path;
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
import 'lint_sets.dart';
|
||||
import 'linter_options.dart';
|
||||
import 'test_linter.dart';
|
||||
|
||||
/// Benchmarks lint rules.
|
||||
@@ -62,16 +61,20 @@ Iterable<File> collectFiles(String entityPath) {
|
||||
return files;
|
||||
}
|
||||
|
||||
Future<void> lintFiles(TestLinter linter, List<File> filesToLint) async {
|
||||
Future<void> lintFiles(
|
||||
List<File> filesToLint, {
|
||||
required List<AbstractAnalysisRule> rules,
|
||||
required String? dartSdkPath,
|
||||
}) async {
|
||||
// Setup an error watcher to track whether an error was logged to stderr so
|
||||
// we can set the exit code accordingly.
|
||||
var errorWatcher = _ErrorWatchingSink(errorSink);
|
||||
errorSink = errorWatcher;
|
||||
var errors = await linter.lintFiles(filesToLint);
|
||||
var diagnostics = await TestLinter(rules, dartSdkPath).lintFiles(filesToLint);
|
||||
if (errorWatcher.encounteredError) {
|
||||
exitCode = loggedAnalyzerErrorExitCode;
|
||||
} else if (errors.isNotEmpty) {
|
||||
exitCode = _maxSeverity(errors);
|
||||
} else if (diagnostics.isNotEmpty) {
|
||||
exitCode = _maxSeverity(diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +119,7 @@ Future<void> runLinter(List<String> args) async {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options['help'] as bool) {
|
||||
if (options.flag('help')) {
|
||||
printUsage(parser, outSink);
|
||||
return;
|
||||
}
|
||||
@@ -132,25 +135,21 @@ Future<void> runLinter(List<String> args) async {
|
||||
return;
|
||||
}
|
||||
|
||||
var configFile = options['config'];
|
||||
var ruleNames = options['rules'];
|
||||
var customSdk = options.option('dart-sdk');
|
||||
var configFile = options.option('config');
|
||||
var ruleNames = options.multiOption('rules');
|
||||
var dartSdkPath = options.option('dart-sdk');
|
||||
|
||||
LinterOptions linterOptions;
|
||||
if (configFile is String) {
|
||||
var optionsContent = readFile(configFile);
|
||||
List<AbstractAnalysisRule> rules;
|
||||
if (configFile != null) {
|
||||
var optionsContent = File(configFile).readAsStringSync();
|
||||
var options = loadYamlNode(optionsContent) as YamlMap;
|
||||
var ruleConfigs = parseLinterSection(options)!.values;
|
||||
var enabledRules = Registry.ruleRegistry.where(
|
||||
(rule) => !ruleConfigs.any((rc) => rc.disables(rule.name)),
|
||||
);
|
||||
|
||||
linterOptions = LinterOptions(
|
||||
enabledRules: enabledRules,
|
||||
dartSdkPath: customSdk,
|
||||
);
|
||||
} else if (ruleNames is Iterable<String> && ruleNames.isNotEmpty) {
|
||||
var rules = <AbstractAnalysisRule>[];
|
||||
rules =
|
||||
Registry.ruleRegistry
|
||||
.where((rule) => !ruleConfigs.any((rc) => rc.disables(rule.name)))
|
||||
.toList();
|
||||
} else if (ruleNames.isNotEmpty) {
|
||||
rules = <AbstractAnalysisRule>[];
|
||||
for (var ruleName in ruleNames) {
|
||||
var rule = Registry.ruleRegistry[ruleName];
|
||||
if (rule == null) {
|
||||
@@ -159,9 +158,8 @@ Future<void> runLinter(List<String> args) async {
|
||||
}
|
||||
rules.add(rule);
|
||||
}
|
||||
linterOptions = LinterOptions(enabledRules: rules, dartSdkPath: customSdk);
|
||||
} else {
|
||||
linterOptions = LinterOptions(dartSdkPath: customSdk);
|
||||
rules = Registry.ruleRegistry.toList();
|
||||
}
|
||||
|
||||
var filesToLint = [
|
||||
@@ -171,17 +169,23 @@ Future<void> runLinter(List<String> args) async {
|
||||
).map((file) => file.path.toAbsoluteNormalizedPath()).map(File.new),
|
||||
];
|
||||
|
||||
await writeBenchmarks(outSink, filesToLint, linterOptions);
|
||||
await writeBenchmarks(
|
||||
outSink,
|
||||
filesToLint,
|
||||
rules: rules,
|
||||
dartSdkPath: dartSdkPath,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> writeBenchmarks(
|
||||
StringSink out,
|
||||
List<File> filesToLint,
|
||||
LinterOptions linterOptions,
|
||||
) async {
|
||||
List<File> filesToLint, {
|
||||
required List<AbstractAnalysisRule> rules,
|
||||
required String? dartSdkPath,
|
||||
}) async {
|
||||
var timings = <String, int>{};
|
||||
for (var i = 0; i < benchmarkRuns; ++i) {
|
||||
await lintFiles(TestLinter(linterOptions), filesToLint);
|
||||
await lintFiles(filesToLint, rules: rules, dartSdkPath: dartSdkPath);
|
||||
analysisRuleTimers.timers.forEach((n, t) {
|
||||
var timing = t.elapsedMilliseconds;
|
||||
var previous = timings[n];
|
||||
@@ -195,18 +199,13 @@ Future<void> writeBenchmarks(
|
||||
|
||||
var stats =
|
||||
timings.keys.map((t) {
|
||||
var sets = <String>[];
|
||||
if (coreRuleset.contains(t)) {
|
||||
sets.add('core');
|
||||
}
|
||||
if (recommendedRuleset.contains(t)) {
|
||||
sets.add('recommended');
|
||||
}
|
||||
if (flutterRuleset.contains(t)) {
|
||||
sets.add('flutter');
|
||||
}
|
||||
var rulesets = [
|
||||
if (coreRuleset.contains(t)) 'core',
|
||||
if (recommendedRuleset.contains(t)) 'recommended',
|
||||
if (flutterRuleset.contains(t)) 'flutter',
|
||||
];
|
||||
|
||||
var details = sets.isEmpty ? '' : " [${sets.join(', ')}]";
|
||||
var details = rulesets.isEmpty ? '' : " [${rulesets.join(', ')}]";
|
||||
return Stat('$t$details', timings[t] ?? 0);
|
||||
}).toList();
|
||||
out.writeTimings(stats, 0);
|
||||
@@ -260,6 +259,7 @@ extension on String {
|
||||
path.split(this).any((part) => part.startsWith('.'));
|
||||
|
||||
/// Whether this path is a Dart file or a Pubspec file.
|
||||
// TODO(srawlins): This should include analysis options files as well.
|
||||
bool get isLintable =>
|
||||
endsWith('.dart') || path.basename(this) == file_paths.pubspecYaml;
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
// 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/dart/analysis/results.dart';
|
||||
import 'package:analyzer/diagnostic/diagnostic.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/src/dart/analysis/analysis_context_collection.dart';
|
||||
import 'package:analyzer/src/generated/engine.dart' show AnalysisEngine;
|
||||
import 'package:analyzer/src/lint/io.dart';
|
||||
|
||||
import 'linter_options.dart';
|
||||
|
||||
class LintDriver {
|
||||
/// The files which have been analyzed so far. This is used to compute the
|
||||
/// total number of files analyzed for statistics.
|
||||
final Set<String> _filesAnalyzed = {};
|
||||
|
||||
final LinterOptions _options;
|
||||
|
||||
LintDriver(this._options);
|
||||
|
||||
ResourceProvider get _resourceProvider =>
|
||||
file_system.PhysicalResourceProvider.INSTANCE;
|
||||
|
||||
Future<List<Diagnostic>> analyze(Iterable<io.File> files) async {
|
||||
AnalysisEngine.instance.instrumentationService = _StdInstrumentation();
|
||||
|
||||
var filesPaths =
|
||||
files.map((file) => _absoluteNormalizedPath(file.path)).toList();
|
||||
|
||||
var contextCollection = AnalysisContextCollectionImpl(
|
||||
resourceProvider: _resourceProvider,
|
||||
sdkPath: _options.dartSdkPath,
|
||||
includedPaths: filesPaths,
|
||||
updateAnalysisOptions3: ({required analysisOptions, required sdk}) {
|
||||
analysisOptions.lint = true;
|
||||
analysisOptions.warning = false;
|
||||
analysisOptions.lintRules = _options.enabledRules.toList(
|
||||
growable: false,
|
||||
);
|
||||
},
|
||||
enableLintRuleTiming: true,
|
||||
);
|
||||
|
||||
_filesAnalyzed.addAll(filesPaths);
|
||||
|
||||
var result = <Diagnostic>[];
|
||||
for (var path in _filesAnalyzed) {
|
||||
var analysisSession = contextCollection.contextFor(path).currentSession;
|
||||
var errorsResult = await analysisSession.getErrors(path);
|
||||
if (errorsResult is ErrorsResult) {
|
||||
result.addAll(errorsResult.diagnostics);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
String _absoluteNormalizedPath(String path) => _resourceProvider.pathContext
|
||||
.normalize(_resourceProvider.pathContext.absolute(path));
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// 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 'package:analyzer/analysis_rule/analysis_rule.dart';
|
||||
import 'package:analyzer/src/lint/registry.dart';
|
||||
|
||||
class LinterOptions {
|
||||
final Iterable<AbstractAnalysisRule> enabledRules;
|
||||
|
||||
/// The path to the Dart SDK.
|
||||
final String? dartSdkPath;
|
||||
|
||||
LinterOptions({
|
||||
Iterable<AbstractAnalysisRule>? enabledRules,
|
||||
this.dartSdkPath,
|
||||
}) : enabledRules = enabledRules ?? Registry.ruleRegistry;
|
||||
}
|
||||
@@ -2,44 +2,41 @@
|
||||
// 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';
|
||||
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/source/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/io.dart';
|
||||
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 'lint_driver.dart';
|
||||
import 'linter_options.dart';
|
||||
|
||||
Source _createSource(Uri uri) {
|
||||
var filePath = uri.toFilePath();
|
||||
var file = file_system.PhysicalResourceProvider.INSTANCE.getFile(filePath);
|
||||
return FileSource(file, uri);
|
||||
}
|
||||
|
||||
class TestLinter implements DiagnosticListener {
|
||||
final errors = <Diagnostic>[];
|
||||
|
||||
final LinterOptions options;
|
||||
TestLinter(this.options);
|
||||
final List<AbstractAnalysisRule> _rules;
|
||||
|
||||
path.Context get _pathContext =>
|
||||
file_system.PhysicalResourceProvider.INSTANCE.pathContext;
|
||||
final String? _dartSdkPath;
|
||||
|
||||
Future<List<Diagnostic>> lintFiles(List<File> files) async {
|
||||
var lintDriver = LintDriver(options);
|
||||
var errors = await lintDriver.analyze(
|
||||
files.where((f) => f.path.endsWith('.dart')),
|
||||
);
|
||||
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: _pathContext.normalize(file.absolute.path),
|
||||
sourcePath: _resourceProvider.pathContext.normalize(file.absolute.path),
|
||||
);
|
||||
}
|
||||
return errors;
|
||||
@@ -48,22 +45,57 @@ class TestLinter implements DiagnosticListener {
|
||||
@override
|
||||
void onDiagnostic(Diagnostic error) => errors.add(error);
|
||||
|
||||
/// Returns whether this [entry] is a pubspec file.
|
||||
bool _isPubspecFile(FileSystemEntity entry) =>
|
||||
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 options.enabledRules) {
|
||||
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 = _createSource(sourceUrl);
|
||||
var source = FileSource(
|
||||
_resourceProvider.getFile(sourceUrl.toFilePath()),
|
||||
sourceUrl,
|
||||
);
|
||||
rule.reporter = DiagnosticReporter(this, source);
|
||||
}
|
||||
try {
|
||||
@@ -75,3 +107,33 @@ class TestLinter implements DiagnosticListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user