blob: 6809d5b00f1b741b2cd244eb16b2f6895542ed2f [file] [log] [blame] [edit]
// Copyright (c) 2025, 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';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/src/clients/build_resolvers/build_resolvers.dart';
import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/scanner/scanner.dart';
import 'package:analyzer/src/lint/registry.dart' as linter;
import 'package:linter/src/rules.dart' as linter;
void main(List<String> args) {
String? dirPath;
bool silent = false;
bool lints = true;
bool warnings = true;
bool comments = true;
bool dumpPerformanceNumbers = false;
// Hardcoded for now, but could be interesting to try them one at a time and
// see what the cost of each individual lint is.
Set<String> wantedLints = {
"collection_methods_unrelated_type",
"curly_braces_in_flow_control_structures",
"depend_on_referenced_packages",
"prefer_adjacent_string_concatenation",
"unawaited_futures",
"avoid_void_async",
"recursive_getters",
"avoid_empty_else",
"empty_statements",
"valid_regexps",
"lines_longer_than_80_chars",
"unrelated_type_equality_checks",
"annotate_overrides",
"always_declare_return_types",
};
for (String arg in args) {
if (arg == "--silent") {
silent = true;
} else if (arg == "--no-lints") {
lints = false;
} else if (arg == "--no-warnings") {
warnings = false;
} else if (arg == "--no-comments") {
comments = false;
} else if (arg == "--dump-performance") {
dumpPerformanceNumbers = true;
} else if (!arg.startsWith("--") && dirPath == null) {
dirPath = arg;
} else {
throw "Unknown argument: $arg";
}
}
if (dirPath == null) throw "Needs a directory to work on.";
if (dirPath.endsWith("/")) dirPath = dirPath.substring(0, dirPath.length - 1);
Directory dir = Directory(dirPath);
List<String> filesToAnalyze = [];
for (FileSystemEntity entity in dir.listSync(
recursive: true,
followLinks: false,
)) {
if (entity is File && entity.path.endsWith('.dart')) {
filesToAnalyze.add(entity.path);
}
}
if (lints) {
linter.registerLintRules();
}
// ignore: invalid_use_of_visible_for_testing_member
Scanner.preserveCommentsDefaultForTesting = comments;
// Creating the Scheduler here, and not starting it, means it's not going to
// get started. It's good for stability :).
// To make sure the 'start' method is overwritten below.
PerformanceLog performanceLog = PerformanceLog(null);
AnalysisDriverScheduler scheduler = BypassedAnalysisDriverScheduler(
performanceLog,
);
AnalysisContextCollectionImpl collection = AnalysisContextCollectionImpl(
includedPaths: [dir.path],
scheduler: scheduler,
updateAnalysisOptions4: ({required AnalysisOptionsImpl analysisOptions}) {
analysisOptions.warning = warnings;
analysisOptions.lint = lints;
if (lints) {
int added = 0;
for (var rule in linter.Registry.ruleRegistry.rules) {
if (wantedLints.contains(rule.name)) {
analysisOptions.lintRules.add(rule);
added++;
}
}
if (!silent) print("Enabled $added lints");
}
},
);
DriverBasedAnalysisContext context = collection.contexts[0];
AnalysisDriver analysisDriver = context.driver;
List<String> libPaths = [];
analysisDriver.scheduler.accumulatedPerformance.run('getFileForPath', (_) {
for (var path in filesToAnalyze) {
var file = analysisDriver.fsState.getFileForPath(path);
var kind = file.kind;
if (kind is LibraryFileKind) {
libPaths.add(path);
}
}
});
for (var path in libPaths) {
// ignore: invalid_use_of_visible_for_testing_member
var library = analysisDriver.analyzeFileForTesting(path);
if (library == null) {
throw 'Got null for $path';
} else {
for (var unit in library.units) {
for (var diagnostic in unit.diagnostics) {
if (diagnostic.diagnosticCode.type == DiagnosticType.TODO) continue;
if (!silent) print(diagnostic);
}
}
}
}
if (dumpPerformanceNumbers) {
var buffer = StringBuffer();
analysisDriver.scheduler.accumulatedPerformance.write(buffer: buffer);
print(buffer);
}
}
class BypassedAnalysisDriverScheduler extends AnalysisDriverScheduler {
BypassedAnalysisDriverScheduler(super.logger);
@override
void start() {
throw 'This scheduler should not be started.';
}
}