Skip to content
This repository was archived by the owner on Nov 20, 2024. It is now read-only.

move cli main into lib/ #1114

Merged
merged 1 commit into from
Aug 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 2 additions & 178 deletions bin/linter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,184 +3,8 @@
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:io';

import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/lint/config.dart';
import 'package:analyzer/src/lint/io.dart';
import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/lint/registry.dart';
import 'package:args/args.dart';
import 'package:linter/src/analyzer.dart';
import 'package:linter/src/formatter.dart';
import 'package:linter/src/rules.dart';
import 'package:linter/src/cli.dart' as cli;

Future main(List<String> args) async {
await runLinter(args, new LinterOptions()..previewDart2 = true);
}

const processFileFailedExitCode = 65;
const unableToProcessExitCode = 64;

String getRoot(List<String> paths) =>
paths.length == 1 && new Directory(paths[0]).existsSync() ? paths[0] : null;

bool 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
''');
}

Future runLinter(List<String> args, LinterOptions initialLintOptions) async {
// Force the rule registry to be populated.
registerLintRules();

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('benchmark', negatable: false, help: 'Show lint benchmarks.')
..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.')
..addMultiOption('rules',
help: 'A list of lint rules to run. For example: '
'avoid_as,annotate_overrides')
..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 = Registry.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'];
var benchmark = options['benchmark'];
if (stats || benchmark) {
lintOptions.enableTiming = true;
}

lintOptions
..packageConfigPath = packageConfigFile
..resourceProvider = PhysicalResourceProvider.INSTANCE;

List<File> filesToLint = [];
for (var path in options.rest) {
filesToLint.addAll(collectFiles(path));
}

if (benchmark) {
await writeBenchmarks(outSink, filesToLint, lintOptions);
return;
}

final linter = new DartLinter(lintOptions);

try {
final timer = new Stopwatch()..start();
List<AnalysisErrorInfo> errors = await lintFiles(linter, filesToLint);
timer.stop();

var commonRoot = getRoot(options.rest);
new ReportFormatter(errors, lintOptions.filter, outSink,
elapsedMs: timer.elapsedMilliseconds,
fileCount: linter.numSourcesAnalyzed,
fileRoot: commonRoot,
showStatistics: stats,
machineOutput: options['machine'],
quiet: options['quiet'])
..write();
// ignore: avoid_catches_without_on_clauses
} catch (err, stack) {
errorSink.writeln('''An error occurred while linting
Please report it at: github.com/dart-lang/linter/issues
$err
$stack''');
}
await cli.run(args);
}
187 changes: 187 additions & 0 deletions lib/src/cli.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// Copyright (c) 2018, 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:async';
import 'dart:io';

import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/lint/config.dart';
import 'package:analyzer/src/lint/io.dart';
import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/lint/registry.dart';
import 'package:args/args.dart';
import 'package:linter/src/analyzer.dart';
import 'package:linter/src/formatter.dart';
import 'package:linter/src/rules.dart';

/// Start linting from the command-line.
Future run(List<String> args) async {
await runLinter(args, new LinterOptions()..previewDart2 = true);
}

const processFileFailedExitCode = 65;
const unableToProcessExitCode = 64;

String getRoot(List<String> paths) =>
paths.length == 1 && new Directory(paths[0]).existsSync() ? paths[0] : null;

bool 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
''');
}

Future runLinter(List<String> args, LinterOptions initialLintOptions) async {
// Force the rule registry to be populated.
registerLintRules();

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('benchmark', negatable: false, help: 'Show lint benchmarks.')
..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.')
..addMultiOption('rules',
help: 'A list of lint rules to run. For example: '
'avoid_as,annotate_overrides')
..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 = Registry.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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pro tip: return the exit code from this method – set it in bin. Makes testing MUCH easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point. That said, I think these cases are pretty well covered in our integration tests. Are you making a general point or do you see cases we're missing?

return;
}

var stats = options['stats'];
var benchmark = options['benchmark'];
if (stats || benchmark) {
lintOptions.enableTiming = true;
}

lintOptions
..packageConfigPath = packageConfigFile
..resourceProvider = PhysicalResourceProvider.INSTANCE;

List<File> filesToLint = [];
for (var path in options.rest) {
filesToLint.addAll(collectFiles(path));
}

if (benchmark) {
await writeBenchmarks(outSink, filesToLint, lintOptions);
return;
}

final linter = new DartLinter(lintOptions);

try {
final timer = new Stopwatch()..start();
List<AnalysisErrorInfo> errors = await lintFiles(linter, filesToLint);
timer.stop();

var commonRoot = getRoot(options.rest);
new ReportFormatter(errors, lintOptions.filter, outSink,
elapsedMs: timer.elapsedMilliseconds,
fileCount: linter.numSourcesAnalyzed,
fileRoot: commonRoot,
showStatistics: stats,
machineOutput: options['machine'],
quiet: options['quiet'])
..write();
// ignore: avoid_catches_without_on_clauses
} catch (err, stack) {
errorSink.writeln('''An error occurred while linting
Please report it at: github.com/dart-lang/linter/issues
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this catch to bin – makes testing easier

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...make sure to set the exit code to an error!

$err
$stack''');
}
}
Loading