blob: 5d81ba5fc48ba2ac118b75624db35729904c9cbb [file] [log] [blame]
// Copyright (c) 2015, 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 'dart:math' as math;
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/error.dart';
import 'package:args/args.dart';
import 'package:linter/src/config.dart';
import 'package:linter/src/formatter.dart';
import 'package:linter/src/io.dart';
import 'package:linter/src/linter.dart';
import 'package:linter/src/rules.dart';
void main(List<String> args) {
runLinter(args, new LinterOptions());
}
const processFileFailedExitCode = 65;
const unableToProcessExitCode = 64;
String getRoot(List<String> paths) =>
paths.length == 1 && new Directory(paths[0]).existsSync() ? paths[0] : null;
isLinterErrorCode(int code) =>
code == unableToProcessExitCode || code == processFileFailedExitCode;
void printUsage(ArgParser parser, IOSink out, [String error]) {
var message = "Lints Dart source files and pubspecs.";
if (error != null) {
message = error;
}
out.writeln('''$message
Usage: linter <file>
${parser.usage}
For more information, see https://github.com/dart-lang/linter
''');
}
void runLinter(List<String> args, LinterOptions initialLintOptions) {
var parser = new ArgParser(allowTrailingOptions: true);
parser
..addFlag("help",
abbr: "h", negatable: false, help: "Show usage information.")
..addFlag("stats",
abbr: "s", negatable: false, help: "Show lint statistics.")
..addFlag('visit-transitive-closure',
help: 'Visit the transitive closure of imported/exported libraries.')
..addFlag('quiet', abbr: 'q', help: "Don't show individual lint errors.")
..addFlag('machine',
help: 'Print results in a format suitable for parsing.',
defaultsTo: false,
negatable: false)
..addFlag('strong', help: 'Use strong-mode analyzer.')
..addOption('config', abbr: 'c', help: 'Use configuration from this file.')
..addOption('dart-sdk', help: 'Custom path to a Dart SDK.')
..addOption('rules',
help: 'A list of lint rules to run. For example: '
'avoid_as,annotate_overrides',
allowMultiple: true)
..addOption('packages',
help: 'Path to the package resolution configuration file, which\n'
'supplies a mapping of package names to paths. This option\n'
'cannot be used with --package-root.')
..addOption('package-root',
abbr: 'p', help: 'Custom package root. (Discouraged.)');
ArgResults options;
try {
options = parser.parse(args);
} on FormatException catch (err) {
printUsage(parser, errorSink, err.message);
exitCode = unableToProcessExitCode;
return;
}
if (options["help"]) {
printUsage(parser, outSink);
return;
}
if (options.rest.isEmpty) {
printUsage(parser, errorSink,
"Please provide at least one file or directory to lint.");
exitCode = unableToProcessExitCode;
return;
}
var lintOptions = initialLintOptions;
var configFile = options["config"];
if (configFile != null) {
var config = new LintConfig.parse(readFile(configFile));
lintOptions.configure(config);
}
var lints = options['rules'];
if (lints != null && !lints.isEmpty) {
var rules = <LintRule>[];
for (var lint in lints) {
var rule = ruleRegistry[lint];
if (rule == null) {
errorSink.write('Unrecognized lint rule: $lint');
exit(unableToProcessExitCode);
}
rules.add(rule);
}
lintOptions.enabledLints = rules;
}
var customSdk = options['dart-sdk'];
if (customSdk != null) {
lintOptions.dartSdkPath = customSdk;
}
var strongMode = options['strong'];
if (strongMode != null) lintOptions.strongMode = strongMode;
var customPackageRoot = options['package-root'];
if (customPackageRoot != null) {
lintOptions.packageRootPath = customPackageRoot;
}
var packageConfigFile = options['packages'];
if (customPackageRoot != null && packageConfigFile != null) {
errorSink.write("Cannot specify both '--package-root' and '--packages.");
exitCode = unableToProcessExitCode;
return;
}
var stats = options['stats'];
if (stats == true) {
lintOptions.enableTiming = true;
}
lintOptions.packageConfigPath = packageConfigFile;
lintOptions.visitTransitiveClosure = options['visit-transitive-closure'];
var linter = new DartLinter(lintOptions);
List<File> filesToLint = [];
for (var path in options.rest) {
filesToLint.addAll(collectFiles(path));
}
try {
Stopwatch timer = new Stopwatch();
timer.start();
List<AnalysisErrorInfo> errors = linter.lintFiles(filesToLint);
timer.stop();
if (errors.length > 0) {
exitCode = _maxSeverity(errors, lintOptions.filter);
}
var commonRoot = getRoot(options.rest);
ReportFormatter reporter = new ReportFormatter(
errors, lintOptions.filter, outSink,
elapsedMs: timer.elapsedMilliseconds,
fileCount: linter.numSourcesAnalyzed,
fileRoot: commonRoot,
showStatistics: stats,
machineOutput: options['machine'],
quiet: options['quiet']);
reporter.write();
} catch (err, stack) {
errorSink.writeln('''An error occurred while linting
Please report it at: github.com/dart-lang/linter/issues
$err
$stack''');
}
}
Iterable<AnalysisError> _filtered(
List<AnalysisError> errors, LintFilter filter) =>
(filter == null)
? errors
: errors.where((AnalysisError e) => !filter.filter(e));
int _maxSeverity(List<AnalysisErrorInfo> errors, LintFilter filter) {
int max = 0;
for (AnalysisErrorInfo info in errors) {
_filtered(info.errors, filter).forEach((AnalysisError e) {
max = math.max(max, e.errorCode.errorSeverity.ordinal);
});
}
return max;
}