From eef267931ae46cd74e8aa5a7a3f3858518a63c65 Mon Sep 17 00:00:00 2001 From: pq Date: Tue, 7 Aug 2018 10:31:13 -0700 Subject: [PATCH] move cli main into `lib/` --- bin/linter.dart | 180 +---------------------------------- lib/src/cli.dart | 187 +++++++++++++++++++++++++++++++++++++ test/engine_test.dart | 26 +++--- test/integration_test.dart | 65 +++++++------ 4 files changed, 234 insertions(+), 224 deletions(-) create mode 100644 lib/src/cli.dart diff --git a/bin/linter.dart b/bin/linter.dart index 4e0396f10..821363219 100644 --- a/bin/linter.dart +++ b/bin/linter.dart @@ -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 args) async { - await runLinter(args, new LinterOptions()..previewDart2 = true); -} - -const processFileFailedExitCode = 65; -const unableToProcessExitCode = 64; - -String getRoot(List 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 -${parser.usage} - -For more information, see https://github.com/dart-lang/linter -'''); -} - -Future runLinter(List 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 = []; - 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 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 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); } diff --git a/lib/src/cli.dart b/lib/src/cli.dart new file mode 100644 index 000000000..938b35a57 --- /dev/null +++ b/lib/src/cli.dart @@ -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 args) async { + await runLinter(args, new LinterOptions()..previewDart2 = true); +} + +const processFileFailedExitCode = 65; +const unableToProcessExitCode = 64; + +String getRoot(List 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 +${parser.usage} + +For more information, see https://github.com/dart-lang/linter +'''); +} + +Future runLinter(List 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 = []; + 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 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 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'''); + } +} diff --git a/test/engine_test.dart b/test/engine_test.dart index 291a5b09b..d235ae718 100644 --- a/test/engine_test.dart +++ b/test/engine_test.dart @@ -14,10 +14,10 @@ import 'package:analyzer/src/lint/linter.dart' hide CamelCaseString; import 'package:analyzer/src/lint/pub.dart'; import 'package:analyzer/src/string_source.dart' show StringSource; import 'package:cli_util/cli_util.dart' show getSdkPath; +import 'package:linter/src/cli.dart' as cli; import 'package:linter/src/utils.dart'; import 'package:test/test.dart'; -import '../bin/linter.dart' as dartlint; import 'mocks.dart'; import 'rule_test.dart' show ruleDir; @@ -146,37 +146,37 @@ void defineLinterEngineTests() { test('smoke', () async { FileSystemEntity firstRuleTest = new Directory(ruleDir).listSync().firstWhere(isDartFile); - await dartlint.main([firstRuleTest.path]); - expect(dartlint.isLinterErrorCode(exitCode), isFalse); + await cli.run([firstRuleTest.path]); + expect(cli.isLinterErrorCode(exitCode), isFalse); }); test('no args', () async { - await dartlint.main([]); - expect(exitCode, dartlint.unableToProcessExitCode); + await cli.run([]); + expect(exitCode, cli.unableToProcessExitCode); }); test('help', () async { - await dartlint.main(['-h']); + await cli.run(['-h']); // Help shouldn't generate an error code - expect(dartlint.isLinterErrorCode(exitCode), isFalse); + expect(cli.isLinterErrorCode(exitCode), isFalse); }); test('unknown arg', () async { - await dartlint.main(['-XXXXX']); - expect(exitCode, dartlint.unableToProcessExitCode); + await cli.run(['-XXXXX']); + expect(exitCode, cli.unableToProcessExitCode); }); test('custom sdk path', () async { // Smoke test to ensure a custom sdk path doesn't sink the ship FileSystemEntity firstRuleTest = new Directory(ruleDir).listSync().firstWhere(isDartFile); var sdk = getSdkPath(); - await dartlint.main(['--dart-sdk', sdk, firstRuleTest.path]); - expect(dartlint.isLinterErrorCode(exitCode), isFalse); + await cli.run(['--dart-sdk', sdk, firstRuleTest.path]); + expect(cli.isLinterErrorCode(exitCode), isFalse); }); test('custom package root', () async { // Smoke test to ensure a custom package root doesn't sink the ship FileSystemEntity firstRuleTest = new Directory(ruleDir).listSync().firstWhere(isDartFile); var packageDir = new Directory('.').path; - await dartlint.main(['--package-root', packageDir, firstRuleTest.path]); - expect(dartlint.isLinterErrorCode(exitCode), isFalse); + await cli.run(['--package-root', packageDir, firstRuleTest.path]); + expect(cli.isLinterErrorCode(exitCode), isFalse); }); }); diff --git a/test/integration_test.dart b/test/integration_test.dart index e3c3dd284..555c8a464 100644 --- a/test/integration_test.dart +++ b/test/integration_test.dart @@ -7,11 +7,11 @@ import 'dart:io'; import 'package:analyzer/src/lint/io.dart'; import 'package:analyzer/src/lint/linter.dart'; import 'package:linter/src/analyzer.dart'; +import 'package:linter/src/cli.dart' as cli; import 'package:linter/src/rules.dart'; import 'package:test/test.dart'; import 'package:yaml/yaml.dart'; -import '../bin/linter.dart' as dartlint; import 'mocks.dart'; main() { @@ -34,8 +34,8 @@ defineTests() { }); group('config', () { test('excludes', () async { - await dartlint - .main(['test/_data/p2', '-c', 'test/_data/p2/lintconfig.yaml']); + await cli + .run(['test/_data/p2', '-c', 'test/_data/p2/lintconfig.yaml']); expect(exitCode, 1); expect( collectingOut.trim(), @@ -43,14 +43,14 @@ defineTests() { ['4 files analyzed, 1 issue found (2 filtered), in'])); }); test('overrrides', () async { - await dartlint - .main(['test/_data/p2', '-c', 'test/_data/p2/lintconfig2.yaml']); + await cli + .run(['test/_data/p2', '-c', 'test/_data/p2/lintconfig2.yaml']); expect(exitCode, 0); expect(collectingOut.trim(), stringContainsInOrder(['4 files analyzed, 0 issues found, in'])); }); test('default', () async { - await dartlint.main(['test/_data/p2']); + await cli.run(['test/_data/p2']); expect(exitCode, 1); expect(collectingOut.trim(), stringContainsInOrder(['4 files analyzed, 3 issues found, in'])); @@ -66,7 +66,7 @@ defineTests() { outSink = currentOut; }); test('bad pubspec', () async { - await dartlint.main(['test/_data/p3', 'test/_data/p3/_pubpspec.yaml']); + await cli.run(['test/_data/p3', 'test/_data/p3/_pubpspec.yaml']); expect(collectingOut.trim(), startsWith('1 file analyzed, 0 issues found, in')); }); @@ -82,8 +82,7 @@ defineTests() { test('no warnings due to bad canonicalization', () async { var packagesFilePath = new File('test/_data/p4/_packages').absolute.path; - await dartlint.runLinter( - ['--packages', packagesFilePath, 'test/_data/p4'], + await cli.runLinter(['--packages', packagesFilePath, 'test/_data/p4'], new LinterOptions([])..previewDart2 = true); expect(collectingOut.trim(), startsWith('3 files analyzed, 0 issues found, in')); @@ -105,8 +104,8 @@ defineTests() { group('.packages', () { test('basic', () async { // Requires .packages to analyze cleanly. - await dartlint - .main(['test/_data/p5', '--packages', 'test/_data/p5/_packages']); + await cli + .run(['test/_data/p5', '--packages', 'test/_data/p5/_packages']); // Should have 0 issues. expect(exitCode, 0); }); @@ -128,7 +127,7 @@ defineTests() { // https://github.com/dart-lang/linter/issues/246 test('overrides across libraries', () async { - await dartlint.main( + await cli.run( ['test/_data/overridden_fields', '--rules', 'overridden_fields']); expect(exitCode, 1); expect( @@ -153,7 +152,7 @@ defineTests() { test('close sinks', () async { var packagesFilePath = new File('.packages').absolute.path; - await dartlint.main([ + await cli.run([ '--packages', packagesFilePath, 'test/_data/close_sinks', @@ -184,7 +183,7 @@ defineTests() { }); test('cancel subscriptions', () async { - await dartlint.main([ + await cli.run([ 'test/_data/cancel_subscriptions', '--rules=cancel_subscriptions' ]); @@ -214,7 +213,7 @@ defineTests() { test('dart_directives_go_first', () async { var packagesFilePath = new File('.packages').absolute.path; - await dartlint.main([ + await cli.run([ '--packages', packagesFilePath, 'test/_data/directives_ordering/dart_directives_go_first', @@ -238,7 +237,7 @@ defineTests() { test('package_directives_before_relative', () async { var packagesFilePath = new File('.packages').absolute.path; - await dartlint.main([ + await cli.run([ '--packages', packagesFilePath, 'test/_data/directives_ordering/package_directives_before_relative', @@ -262,7 +261,7 @@ defineTests() { test('third_party_package_directives_before_own', () async { var packagesFilePath = new File('.packages').absolute.path; - await dartlint.main([ + await cli.run([ '--packages', packagesFilePath, 'test/_data/directives_ordering/third_party_package_directives_before_own', @@ -286,7 +285,7 @@ defineTests() { test('export_directives_after_import_directives', () async { var packagesFilePath = new File('.packages').absolute.path; - await dartlint.main([ + await cli.run([ '--packages', packagesFilePath, 'test/_data/directives_ordering/export_directives_after_import_directives', @@ -306,7 +305,7 @@ defineTests() { test('sort_directive_sections_alphabetically', () async { var packagesFilePath = new File('.packages').absolute.path; - await dartlint.main([ + await cli.run([ '--packages', packagesFilePath, 'test/_data/directives_ordering/sort_directive_sections_alphabetically', @@ -346,7 +345,7 @@ defineTests() { test('lint_one_node_no_more_than_once', () async { var packagesFilePath = new File('.packages').absolute.path; - await dartlint.main([ + await cli.run([ '--packages', packagesFilePath, 'test/_data/directives_ordering/lint_one_node_no_more_than_once', @@ -377,7 +376,7 @@ defineTests() { }); test('on bad file names', () async { - await dartlint.main(['test/_data/file_names', '--rules=file_names']); + await cli.run(['test/_data/file_names', '--rules=file_names']); expect(exitCode, 1); expect( collectingOut.trim(), @@ -401,7 +400,7 @@ defineTests() { }); test('only throw errors', () async { - await dartlint.main([ + await cli.run([ 'test/_data/lines_longer_than_80_chars', '--rules=lines_longer_than_80_chars' ]); @@ -432,8 +431,8 @@ defineTests() { }); test('only throw errors', () async { - await dartlint.main( - ['test/_data/only_throw_errors', '--rules=only_throw_errors']); + await cli + .run(['test/_data/only_throw_errors', '--rules=only_throw_errors']); expect(exitCode, 1); expect( collectingOut.trim(), @@ -462,7 +461,7 @@ defineTests() { }); test('only throw errors', () async { - await dartlint.runLinter([ + await cli.runLinter([ 'test/_data/always_require_non_null_named_parameters', '--rules=always_require_non_null_named_parameters' ], new LinterOptions()..previewDart2 = true); @@ -488,7 +487,7 @@ defineTests() { }); test('only throw errors', () async { - await dartlint.runLinter([ + await cli.runLinter([ 'test/_data/prefer_asserts_in_initializer_lists', '--rules=prefer_asserts_in_initializer_lists' ], new LinterOptions()..previewDart2 = true); @@ -514,7 +513,7 @@ defineTests() { }); test('only throw errors', () async { - await dartlint.runLinter([ + await cli.runLinter([ 'test/_data/prefer_const_constructors_in_immutables', '--rules=prefer_const_constructors_in_immutables' ], new LinterOptions()..previewDart2 = true); @@ -540,7 +539,7 @@ defineTests() { }); test('avoid relative lib imports', () async { - await dartlint.runLinter([ + await cli.runLinter([ 'test/_data/avoid_relative_lib_imports', '--rules=avoid_relative_lib_imports', '--packages', @@ -571,7 +570,7 @@ defineTests() { test('lint lib/ sources and non-lib/ sources', () async { var packagesFilePath = new File('.packages').absolute.path; - await dartlint.main([ + await cli.run([ '--packages', packagesFilePath, 'test/_data/public_member_api_docs', @@ -618,7 +617,7 @@ defineTests() { }); test('lint lib/ sources and non-lib/ sources', () async { - await dartlint.main([ + await cli.run([ '--packages', 'test/_data/avoid_renaming_method_parameters/_packages', 'test/_data/avoid_renaming_method_parameters', @@ -655,7 +654,7 @@ defineTests() { }); test('handles parts', () async { - await dartlint.main([ + await cli.run([ 'test/_data/avoid_private_typedef_functions/lib.dart', 'test/_data/avoid_private_typedef_functions/part.dart', '--rules=avoid_private_typedef_functions' @@ -688,7 +687,7 @@ defineTests() { }); test('avoid keyword to create instances', () async { - await dartlint.runLinter( + await cli.runLinter( ['test/_data/unnecessary_const', '--rules=unnecessary_const'], new LinterOptions()..previewDart2 = true); expect(exitCode, 1); @@ -721,7 +720,7 @@ defineTests() { }); test('avoid keyword to create instances', () async { - await dartlint.runLinter( + await cli.runLinter( ['test/_data/unnecessary_new', '--rules=unnecessary_new'], new LinterOptions()..previewDart2 = true); expect(exitCode, 1);