├── test ├── data │ ├── empty_options.yaml │ ├── file_with_error.dart │ ├── no_lints_project │ │ ├── .analysis_options │ │ └── test_file.dart │ ├── library_and_parts │ │ ├── part1.dart │ │ ├── part2.dart │ │ └── lib.dart │ ├── test_options.yaml │ ├── file_with_hint.dart │ ├── file_with_warning.dart │ ├── package_with_sdk_extension │ │ └── lib │ │ │ ├── _sdkext │ │ │ └── foo.dart │ ├── linter_project │ │ ├── .analysis_options │ │ └── test_file.dart │ ├── packages_file │ │ ├── .packages │ │ └── sdk_ext_user.dart │ ├── bad_plugin_options.yaml │ ├── options_tests_project │ │ ├── .analysis_options │ │ └── test_file.dart │ ├── plugin_options.yaml │ ├── test_file.dart │ ├── no_packages_file │ │ └── sdk_ext_user.dart │ ├── super_mixin_example.dart │ └── strong_example.dart ├── all.dart ├── mocks.dart ├── strong_mode_test.dart ├── sdk_ext_test.dart ├── error_test.dart ├── utils.dart ├── super_mixin_test.dart ├── reporter_test.dart ├── plugin_manager_test.dart ├── options_test.dart └── driver_test.dart ├── .test_config ├── .analysis_options ├── .travis.yml ├── codereview.settings ├── .gitignore ├── AUTHORS ├── CHANGELOG.md ├── README.md ├── bin └── analyzer.dart ├── pubspec.yaml ├── appveyor.yml ├── tool └── travis.sh ├── CONTRIBUTOR.md ├── LICENSE └── lib └── src ├── plugin └── plugin_manager.dart ├── error_formatter.dart ├── bootloader.dart ├── analyzer_impl.dart ├── options.dart └── driver.dart /test/data/empty_options.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.test_config: -------------------------------------------------------------------------------- 1 | { 2 | "test_package": true 3 | } 4 | -------------------------------------------------------------------------------- /test/data/file_with_error.dart: -------------------------------------------------------------------------------- 1 | 2 | error 3 | 4 | -------------------------------------------------------------------------------- /test/data/no_lints_project/.analysis_options: -------------------------------------------------------------------------------- 1 | # empty 2 | -------------------------------------------------------------------------------- /test/data/library_and_parts/part1.dart: -------------------------------------------------------------------------------- 1 | part of example; 2 | -------------------------------------------------------------------------------- /test/data/library_and_parts/part2.dart: -------------------------------------------------------------------------------- 1 | part of nothing; 2 | -------------------------------------------------------------------------------- /test/data/test_options.yaml: -------------------------------------------------------------------------------- 1 | test_plugin: 2 | foo: bar 3 | -------------------------------------------------------------------------------- /.analysis_options: -------------------------------------------------------------------------------- 1 | analyzer: 2 | exclude: 3 | - test/data/* 4 | -------------------------------------------------------------------------------- /test/data/file_with_hint.dart: -------------------------------------------------------------------------------- 1 | main() { 2 | int unused; 3 | } 4 | -------------------------------------------------------------------------------- /test/data/file_with_warning.dart: -------------------------------------------------------------------------------- 1 | main() { 2 | undefined(); 3 | } 4 | -------------------------------------------------------------------------------- /test/data/library_and_parts/lib.dart: -------------------------------------------------------------------------------- 1 | library example; 2 | 3 | part 'part1.dart'; 4 | -------------------------------------------------------------------------------- /test/data/package_with_sdk_extension/lib/_sdkext: -------------------------------------------------------------------------------- 1 | { 2 | "dart:foo": "foo.dart" 3 | } 4 | -------------------------------------------------------------------------------- /test/data/linter_project/.analysis_options: -------------------------------------------------------------------------------- 1 | linter: 2 | rules: 3 | - camel_case_types 4 | -------------------------------------------------------------------------------- /test/data/packages_file/.packages: -------------------------------------------------------------------------------- 1 | package_with_sdk_extension:../package_with_sdk_extension/lib/ 2 | -------------------------------------------------------------------------------- /test/data/bad_plugin_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | plugins: 3 | - lists 4 | - are 5 | - not 6 | - supported 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | dart: dev 3 | script: ./tool/travis.sh 4 | sudo: false 5 | cache: 6 | directories: 7 | - $HOME/.pub-cache 8 | -------------------------------------------------------------------------------- /test/data/options_tests_project/.analysis_options: -------------------------------------------------------------------------------- 1 | analyzer: 2 | errors: 3 | unused_local_variable: ignore 4 | language: 5 | enableSuperMixins: true 6 | -------------------------------------------------------------------------------- /codereview.settings: -------------------------------------------------------------------------------- 1 | CODE_REVIEW_SERVER: https://codereview.chromium.org/ 2 | VIEW_VC: https://github.com/dart-lang/analyzer_cli/commit/ 3 | CC_LIST: reviews@dartlang.org 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .buildlog 2 | .DS_Store 3 | .idea 4 | /.packages 5 | .project 6 | .pub/ 7 | .settings/ 8 | analyzer_cli.iml 9 | build/ 10 | packages 11 | pubspec.lock 12 | -------------------------------------------------------------------------------- /test/data/plugin_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | plugins: 3 | my_plugin1: 4 | version: any 5 | library_uri: 'package:my_plugin/my_plugin.dart' 6 | class_name: MyPlugin 7 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Google Inc. 7 | 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.1.1 4 | 5 | - Removed outmoded `--url-mapping` command line option. 6 | 7 | ## 1.1.0 8 | 9 | - New `--lints` command line option. 10 | 11 | ## 0.0. 12 | 13 | 14 | ## 0.0.1 15 | 16 | - Initial version 17 | -------------------------------------------------------------------------------- /test/data/package_with_sdk_extension/lib/foo.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | void bar() {} 6 | -------------------------------------------------------------------------------- /test/data/test_file.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library analyzer_cli.test.data.test_file; 6 | -------------------------------------------------------------------------------- /test/data/linter_project/test_file.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library analyzer_cli.test.data.linter_project.test_file; 6 | 7 | class a {} 8 | -------------------------------------------------------------------------------- /test/data/no_lints_project/test_file.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library analyzer_cli.test.data.no_lints_project.test_file; 6 | 7 | class a {} 8 | -------------------------------------------------------------------------------- /test/data/options_tests_project/test_file.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library analyzer_cli.test.data.options_test_project.test_file; 6 | 7 | class a {} 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **We've moved!** 2 | 3 | The `analyzer_cli` package has moved into the Dart SDK repo: 4 | https://github.com/dart-lang/sdk/tree/master/pkg/analyzer_cli. 5 | 6 | For background on this move, see [issue 24731](https://github.com/dart-lang/sdk/issues/24731). Going forward, please use the [Dart SDK issue tracker](https://github.com/dart-lang/sdk/issues/new). 7 | 8 | Thanks! 9 | -------------------------------------------------------------------------------- /test/data/packages_file/sdk_ext_user.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:foo'; 6 | 7 | main() { 8 | // import of 'dart:foo' should resolve to a file which defines bar(). 9 | bar(); 10 | } 11 | -------------------------------------------------------------------------------- /test/data/no_packages_file/sdk_ext_user.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:foo'; 6 | 7 | main() { 8 | // import of 'dart:foo' should resolve to a file which defines bar(). 9 | bar(); 10 | } 11 | -------------------------------------------------------------------------------- /bin/analyzer.dart: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dart 2 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 3 | // for details. All rights reserved. Use of this source code is governed by a 4 | // BSD-style license that can be found in the LICENSE file. 5 | 6 | import 'package:analyzer_cli/src/driver.dart'; 7 | 8 | /// The entry point for the analyzer. 9 | void main(List args) { 10 | var starter = new Driver(); 11 | starter.start(args); 12 | } 13 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: analyzer_cli 2 | version: 1.1.2 3 | author: Dart Team 4 | description: Command line interface for the Dart Analyzer. 5 | homepage: https://github.com/dart-lang/analyzer_cli 6 | environment: 7 | sdk: '>=1.12.0 <2.0.0' 8 | dependencies: 9 | analyzer: ^0.26.1+17 10 | args: ^0.13.0 11 | cli_util: ^0.0.1 12 | linter: ^0.1.3+4 13 | package_config: ^0.1.1 14 | plugin: ^0.1.0 15 | yaml: ^2.1.2 16 | dev_dependencies: 17 | mockito: ^0.8.2 18 | test: ^0.12.0 19 | -------------------------------------------------------------------------------- /test/data/super_mixin_example.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// This produces errors normally, but --supermixin disables them. 6 | class Test extends Object with C { 7 | void foo() {} 8 | } 9 | 10 | abstract class B { 11 | void foo(); 12 | } 13 | 14 | abstract class C extends B { 15 | void bar() { 16 | super.foo(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/data/strong_example.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// This produces an error with --strong enabled, but not otherwise. 6 | class MyIterable extends Iterable { 7 | // Error: invalid override 8 | Iterator get iterator => [1, 2, 3].iterator; 9 | } 10 | 11 | main() { 12 | var i = new MyIterable().iterator..moveNext(); 13 | print(i.current); 14 | 15 | // Error: type check failed 16 | List list = [1, 2, 3]; 17 | print(list); 18 | } 19 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | # for details. All rights reserved. Use of this source code is governed by a 3 | # BSD-style license that can be found in the LICENSE file. 4 | 5 | install: 6 | # TODO: Switch back to chocolatey once 1.12.0 is available. 7 | # - choco install -y dart-sdk -version 1.11.1 8 | - ps: wget https://gsdview.appspot.com/dart-archive/channels/dev/raw/latest/sdk/dartsdk-windows-x64-release.zip -OutFile dart-sdk.zip 9 | - cmd: echo "Unzipping dart-sdk..." 10 | - cmd: 7z x dart-sdk.zip -o"C:\tools" -y > nul 11 | - set PATH=%PATH%;C:\tools\dart-sdk\bin 12 | - set PATH=%PATH%;%APPDATA%\Pub\Cache\bin 13 | - pub get 14 | 15 | build: off 16 | 17 | test_script: 18 | # - pub run test -j1 19 | - dart test\all.dart 20 | -------------------------------------------------------------------------------- /test/all.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'driver_test.dart' as driver; 6 | import 'error_test.dart' as error; 7 | import 'options_test.dart' as options; 8 | import 'plugin_manager_test.dart' as plugin_manager; 9 | import 'reporter_test.dart' as reporter; 10 | import 'sdk_ext_test.dart' as sdk_ext; 11 | import 'strong_mode_test.dart' as strong_mode; 12 | import 'super_mixin_test.dart' as super_mixin; 13 | 14 | main() { 15 | driver.main(); 16 | error.main(); 17 | options.main(); 18 | plugin_manager.main(); 19 | reporter.main(); 20 | sdk_ext.main(); 21 | strong_mode.main(); 22 | super_mixin.main(); 23 | } 24 | -------------------------------------------------------------------------------- /tool/travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 4 | # for details. All rights reserved. Use of this source code is governed by a 5 | # BSD-style license that can be found in the LICENSE file. 6 | 7 | # Fast fail the script on failures. 8 | set -e 9 | 10 | # Verify that the libraries are error free. 11 | dartanalyzer --fatal-warnings \ 12 | bin/analyzer.dart \ 13 | lib/src/analyzer_impl.dart \ 14 | test/all.dart 15 | 16 | # Run the tests. 17 | # Note: the "-j1" is necessary because some tests temporarily change the 18 | # working directory, and the working directory state is shared across isolates. 19 | pub run test -j1 20 | 21 | # Install dart_coveralls; gather and send coverage data. 22 | if [ "$COVERALLS_TOKEN" ]; then 23 | pub global activate dart_coveralls 24 | pub global run dart_coveralls report \ 25 | --exclude-test-files \ 26 | --log-level warning \ 27 | test/all.dart 28 | fi 29 | -------------------------------------------------------------------------------- /CONTRIBUTOR.md: -------------------------------------------------------------------------------- 1 | # Contribute to dartanalyzer 2 | 3 | To _use_ the dartanalyzer command-line tool, see the 4 | [user docs](https://github.com/dart-lang/analyzer_cli/blob/master/README.md#analyzer_cli). 5 | This page contains information relevant for contributors to dartanalyzer. 6 | 7 | ## Can I help? 8 | 9 | Yes! 10 | 11 | Start by [using the tool](README.md) and filing issues and requests. 12 | 13 | See the interface for the Dart `analyzer` library 14 | [package](https://pub.dartlang.org/packages/analyzer). 15 | 16 | If you want to contribute, check out the 17 | [issue tracker](https://github.com/dart-lang/analyzer_cli/issues). 18 | If you want to add a new feature that's not yet in the issue tracker, 19 | start by opening an issue. Thanks! 20 | 21 | [![Build Status](https://travis-ci.org/dart-lang/analyzer_cli.svg)](https://travis-ci.org/dart-lang/analyzer_cli) 22 | [![Build status](https://ci.appveyor.com/api/projects/status/48jv262mnbohjb9m?svg=true)](https://ci.appveyor.com/project/sethladd/analyzer-cli) 23 | [![Coverage Status](https://coveralls.io/repos/dart-lang/analyzer_cli/badge.svg)](https://coveralls.io/r/dart-lang/analyzer_cli) 24 | 25 | ## Features and bugs 26 | 27 | Please file feature requests and bugs at the [issue tracker][tracker]. 28 | 29 | [tracker]: https://github.com/dart-lang/analyzer_cli/issues 30 | 31 | ## License 32 | 33 | Please see the [dartanalyzer license](https://github.com/dart-lang/analyzer_cli/blob/master/LICENSE). 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015, the Dart project authors. All rights reserved. 2 | Redistribution and use in source and binary forms, with or without 3 | modification, are permitted provided that the following conditions are 4 | met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | -------------------------------------------------------------------------------- /test/mocks.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library analyzer_cli.test.mocks; 6 | 7 | import 'package:analyzer/analyzer.dart'; 8 | import 'package:analyzer/src/generated/engine.dart'; 9 | import 'package:analyzer/src/generated/source.dart'; 10 | import 'package:analyzer_cli/src/options.dart'; 11 | import 'package:mockito/mockito.dart'; 12 | 13 | 14 | class MockAnalysisError extends Mock implements AnalysisError { 15 | @override 16 | noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); 17 | } 18 | 19 | class MockAnalysisErrorInfo extends Mock implements AnalysisErrorInfo { 20 | @override 21 | noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); 22 | } 23 | 24 | class MockCommandLineOptions extends Mock implements CommandLineOptions { 25 | @override 26 | noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); 27 | } 28 | 29 | class MockErrorCode extends Mock implements ErrorCode { 30 | @override 31 | noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); 32 | } 33 | 34 | class MockErrorType extends Mock implements ErrorType { 35 | @override 36 | noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); 37 | } 38 | 39 | class MockLineInfo extends Mock implements LineInfo { 40 | @override 41 | noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); 42 | } 43 | 44 | class MockLineInfo_Location extends Mock implements LineInfo_Location { 45 | @override 46 | noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); 47 | } 48 | 49 | class MockSource extends Mock implements Source { 50 | @override 51 | noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); 52 | } 53 | -------------------------------------------------------------------------------- /test/strong_mode_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | library analyzer_cli.test.strong_mode; 7 | 8 | import 'dart:io'; 9 | 10 | import 'package:analyzer_cli/src/driver.dart' show Driver, errorSink, outSink; 11 | import 'package:path/path.dart' as path; 12 | import 'package:test/test.dart'; 13 | 14 | import 'driver_test.dart'; 15 | import 'utils.dart'; 16 | 17 | /// End-to-end test for --strong checking. 18 | /// 19 | /// Most strong mode tests are in Analyzer, but this verifies the option is 20 | /// working and producing extra errors as expected. 21 | /// 22 | /// Generally we don't want a lot of cases here as it requires spinning up a 23 | /// full analysis context. 24 | void main() { 25 | group('--strong', () { 26 | StringSink savedOutSink, savedErrorSink; 27 | int savedExitCode; 28 | setUp(() { 29 | savedOutSink = outSink; 30 | savedErrorSink = errorSink; 31 | savedExitCode = exitCode; 32 | outSink = new StringBuffer(); 33 | errorSink = new StringBuffer(); 34 | }); 35 | tearDown(() { 36 | outSink = savedOutSink; 37 | errorSink = savedErrorSink; 38 | exitCode = savedExitCode; 39 | }); 40 | 41 | test('produces stricter errors', () async { 42 | var testPath = path.join(testDirectory, 'data/strong_example.dart'); 43 | new Driver().start(['--options', emptyOptionsFile, '--strong', testPath]); 44 | 45 | expect(exitCode, 3); 46 | var stdout = outSink.toString(); 47 | expect(stdout, contains('[error] Invalid override')); 48 | expect(stdout, contains('[error] Type check failed')); 49 | expect(stdout, contains('2 errors found.')); 50 | expect(errorSink.toString(), ''); 51 | }); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /test/sdk_ext_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | 7 | /// Test that sdk extensions are properly detected in various scenarios. 8 | library analyzer_cli.test.sdk_ext; 9 | 10 | import 'dart:io'; 11 | 12 | import 'package:analyzer_cli/src/driver.dart' show Driver, errorSink, outSink; 13 | import 'package:path/path.dart' as path; 14 | import 'package:test/test.dart'; 15 | 16 | import 'utils.dart'; 17 | 18 | main() { 19 | group('Sdk extensions', () { 20 | StringSink savedOutSink, savedErrorSink; 21 | int savedExitCode; 22 | Directory savedCurrentDirectory; 23 | setUp(() { 24 | savedOutSink = outSink; 25 | savedErrorSink = errorSink; 26 | savedExitCode = exitCode; 27 | outSink = new StringBuffer(); 28 | errorSink = new StringBuffer(); 29 | savedCurrentDirectory = Directory.current; 30 | }); 31 | tearDown(() { 32 | outSink = savedOutSink; 33 | errorSink = savedErrorSink; 34 | exitCode = savedExitCode; 35 | Directory.current = savedCurrentDirectory; 36 | }); 37 | 38 | test('--packages option supplied', () async { 39 | var testDir = path.join(testDirectory, 'data', 'no_packages_file'); 40 | Directory.current = new Directory(testDir); 41 | var packagesPath = path.join('..', 'packages_file', '.packages'); 42 | new Driver().start(['--packages', packagesPath, 'sdk_ext_user.dart']); 43 | 44 | expect(exitCode, 0); 45 | }); 46 | 47 | test('.packages file present', () async { 48 | var testDir = path.join(testDirectory, 'data', 'packages_file'); 49 | Directory.current = new Directory(testDir); 50 | new Driver().start(['sdk_ext_user.dart']); 51 | 52 | expect(exitCode, 0); 53 | }); 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /test/error_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | 7 | library analyzer_cli.test.error; 8 | 9 | import 'package:test/test.dart'; 10 | 11 | import 'utils.dart'; 12 | 13 | void main() { 14 | group('error', () { 15 | test("a valid Dart file doesn't throw any errors", () { 16 | expect(errorsForFile('void main() => print("Hello, world!");'), isNull); 17 | }); 18 | 19 | test("an empty Dart file doesn't throw any errors", () { 20 | expect(errorsForFile(''), isNull); 21 | }); 22 | 23 | test("an error on the first line", () { 24 | expect(errorsForFile('void foo;\n'), equals( 25 | "Error in test.dart: Variables cannot have a type of 'void'\n")); 26 | }); 27 | 28 | test("an error on the last line", () { 29 | expect(errorsForFile('\nvoid foo;'), equals( 30 | "Error in test.dart: Variables cannot have a type of 'void'\n")); 31 | }); 32 | 33 | test("an error in the middle", () { 34 | expect(errorsForFile('\nvoid foo;\n'), equals( 35 | "Error in test.dart: Variables cannot have a type of 'void'\n")); 36 | }); 37 | 38 | var veryLongString = new List.filled(107, ' ').join(''); 39 | 40 | test("an error at the end of a very long line", () { 41 | expect(errorsForFile('$veryLongString void foo;'), equals( 42 | "Error in test.dart: Variables cannot have a type of 'void'\n")); 43 | }); 44 | 45 | test("an error at the beginning of a very long line", () { 46 | expect(errorsForFile('void foo; $veryLongString'), equals( 47 | "Error in test.dart: Variables cannot have a type of 'void'\n")); 48 | }); 49 | 50 | test("an error in the middle of a very long line", () { 51 | expect(errorsForFile('$veryLongString void foo;$veryLongString'), equals( 52 | "Error in test.dart: Variables cannot have a type of 'void'\n")); 53 | }); 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /test/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library analyzer_cli.test.utils; 6 | 7 | import 'dart:io'; 8 | import 'dart:mirrors'; 9 | 10 | import 'package:analyzer/analyzer.dart'; 11 | import 'package:path/path.dart' as pathos; 12 | 13 | /// Returns the string representation of the [AnalyzerErrorGroup] thrown when 14 | /// parsing [contents] as a Dart file. If [contents] doesn't throw any errors, 15 | /// this will return null. 16 | /// 17 | /// This replaces the filename in the error string with its basename, since the 18 | /// full path will vary from machine to machine. It also replaces the exception 19 | /// message with "..." to decouple these tests from the specific exception 20 | /// messages. 21 | String errorsForFile(String contents) { 22 | return withTempDir((temp) { 23 | var path = pathos.join(temp, 'test.dart'); 24 | new File(path).writeAsStringSync(contents); 25 | try { 26 | parseDartFile(path); 27 | } on AnalyzerErrorGroup catch (e) { 28 | return e.toString().replaceAllMapped( 29 | new RegExp(r"^(Error on line \d+ of )((?:[A-Z]+:)?[^:]+): .*$", 30 | multiLine: true), 31 | (match) => match[1] + pathos.basename(match[2]) + ': ...'); 32 | } 33 | return null; 34 | }); 35 | } 36 | 37 | /// Creates a temporary directory and passes its path to [fn]. Once [fn] 38 | /// completes, the temporary directory and all its contents will be deleted. 39 | /// 40 | /// Returns the return value of [fn]. 41 | dynamic withTempDir(fn(String path)) { 42 | var tempDir = Directory.systemTemp.createTempSync('analyzer_').path; 43 | try { 44 | return fn(tempDir); 45 | } finally { 46 | new Directory(tempDir).deleteSync(recursive: true); 47 | } 48 | } 49 | 50 | /// Gets the test directory in a way that works with 51 | /// package:test and package:unittest. 52 | /// See for more info. 53 | final String testDirectory = pathos.dirname( 54 | pathos.fromUri((reflectClass(_TestUtils).owner as LibraryMirror).uri)); 55 | 56 | class _TestUtils {} 57 | -------------------------------------------------------------------------------- /test/super_mixin_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | library analyzer_cli.test.super_mixin; 7 | 8 | import 'dart:io'; 9 | 10 | import 'package:analyzer_cli/src/driver.dart' show Driver, errorSink, outSink; 11 | import 'package:path/path.dart' as path; 12 | import 'package:test/test.dart'; 13 | 14 | import 'utils.dart'; 15 | 16 | /// End-to-end test for --supermixins. 17 | /// 18 | /// Most super mixin tests are in Analyzer, but this verifies the option is 19 | /// working and producing extra errors as expected. 20 | /// 21 | /// Generally we don't want a lot of cases here as it requires spinning up a 22 | /// full analysis context. 23 | void main() { 24 | group('--supermixins', () { 25 | StringSink savedOutSink, savedErrorSink; 26 | int savedExitCode; 27 | setUp(() { 28 | savedOutSink = outSink; 29 | savedErrorSink = errorSink; 30 | savedExitCode = exitCode; 31 | outSink = new StringBuffer(); 32 | errorSink = new StringBuffer(); 33 | }); 34 | tearDown(() { 35 | outSink = savedOutSink; 36 | errorSink = savedErrorSink; 37 | exitCode = savedExitCode; 38 | }); 39 | 40 | test('produces errors when option absent', () async { 41 | var testPath = path.join(testDirectory, 'data/super_mixin_example.dart'); 42 | new Driver().start([testPath]); 43 | 44 | expect(exitCode, 3); 45 | var stdout = outSink.toString(); 46 | expect( 47 | stdout, 48 | contains( 49 | "[error] The class 'C' cannot be used as a mixin because it extends a class other than Object")); 50 | expect( 51 | stdout, 52 | contains( 53 | "[error] The class 'C' cannot be used as a mixin because it references 'super'")); 54 | expect(stdout, contains('2 errors found.')); 55 | expect(errorSink.toString(), ''); 56 | }); 57 | 58 | test('produces no errors when option present', () async { 59 | var testPath = path.join(testDirectory, 'data/super_mixin_example.dart'); 60 | new Driver().start(['--supermixin', testPath]); 61 | 62 | expect(exitCode, 0); 63 | var stdout = outSink.toString(); 64 | expect(stdout, contains('No issues found')); 65 | }); 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /test/reporter_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | library analyzer_cli.test.formatter; 7 | 8 | import 'package:analyzer/analyzer.dart'; 9 | import 'package:analyzer_cli/src/error_formatter.dart'; 10 | import 'package:mockito/mockito.dart'; 11 | import 'package:test/test.dart' hide ErrorFormatter; 12 | 13 | import 'mocks.dart'; 14 | 15 | main() { 16 | group('reporter', () { 17 | var out = new StringBuffer(); 18 | 19 | tearDown(() => out.clear()); 20 | 21 | // Options 22 | var options = new MockCommandLineOptions(); 23 | when(options.disableHints).thenReturn(true); 24 | when(options.enableTypeChecks).thenReturn(true); 25 | when(options.machineFormat).thenReturn(false); 26 | when(options.hintsAreFatal).thenReturn(false); 27 | 28 | var reporter = new ErrorFormatter(out, options); 29 | 30 | test('error', () { 31 | var error = mockError(ErrorType.SYNTACTIC_ERROR, ErrorSeverity.ERROR); 32 | reporter.formatErrors([error]); 33 | 34 | expect(out.toString(), 35 | equals('''[error] MSG (/foo/bar/baz.dart, line 3, col 3) 36 | 1 error found. 37 | ''')); 38 | }); 39 | 40 | test('hint', () { 41 | var error = mockError(ErrorType.HINT, ErrorSeverity.INFO); 42 | reporter.formatErrors([error]); 43 | 44 | expect(out.toString(), 45 | equals('''[hint] MSG (/foo/bar/baz.dart, line 3, col 3) 46 | 1 hint found. 47 | ''')); 48 | }); 49 | }); 50 | } 51 | 52 | MockAnalysisErrorInfo mockError(ErrorType type, ErrorSeverity severity) { 53 | // ErrorInfo 54 | var info = new MockAnalysisErrorInfo(); 55 | var error = new MockAnalysisError(); 56 | var lineInfo = new MockLineInfo(); 57 | var location = new MockLineInfo_Location(); 58 | when(location.columnNumber).thenReturn(3); 59 | when(location.lineNumber).thenReturn(3); 60 | when(lineInfo.getLocation(any)).thenReturn(location); 61 | when(info.lineInfo).thenReturn(lineInfo); 62 | 63 | // Details 64 | var code = new MockErrorCode(); 65 | when(code.type).thenReturn(type); 66 | when(code.errorSeverity).thenReturn(severity); 67 | when(code.name).thenReturn('mock_code'); 68 | when(error.errorCode).thenReturn(code); 69 | when(error.message).thenReturn('MSG'); 70 | var source = new MockSource(); 71 | when(source.fullName).thenReturn('/foo/bar/baz.dart'); 72 | when(error.source).thenReturn(source); 73 | when(info.errors).thenReturn([error]); 74 | 75 | return info; 76 | } 77 | -------------------------------------------------------------------------------- /test/plugin_manager_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | library analyzer_cli.test.plugin_manager_test; 7 | 8 | import 'package:analyzer/src/plugin/plugin_configuration.dart'; 9 | import 'package:analyzer_cli/src/plugin/plugin_manager.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | main() { 13 | group('plugin manager tests', () { 14 | test('combine plugin info', () { 15 | PluginInfo localInfo = new PluginInfo(name: 'my_plugin'); 16 | PluginInfo manifestInfo = new PluginInfo( 17 | className: 'MyPlugin', libraryUri: 'my_plugin/my_plugin.dart'); 18 | 19 | PluginInfo merged = combine(localInfo, manifestInfo); 20 | expect(merged.name, equals('my_plugin')); 21 | expect(merged.className, equals('MyPlugin')); 22 | expect(merged.libraryUri, equals('my_plugin/my_plugin.dart')); 23 | }); 24 | 25 | test('find manifest', () { 26 | const manifestSrc = ''' 27 | library_uri: 'my_plugin/my_plugin.dart' 28 | '''; 29 | var packageMap = {'my_plugin': new Uri.file('my_plugin')}; 30 | 31 | PluginManager pm = 32 | new PluginManager(packageMap, 'analyzer', (Uri uri) => manifestSrc); 33 | 34 | PluginManifest manifest = pm.findManifest('my_plugin'); 35 | expect(manifest, isNotNull); 36 | expect(manifest.plugin.libraryUri, equals('my_plugin/my_plugin.dart')); 37 | }); 38 | 39 | final plugin1Uri = new Uri.file('my_plugin1'); 40 | final plugin2Uri = new Uri.file('my_plugin2'); 41 | final plugin3Uri = new Uri.file('my_plugin3'); 42 | 43 | const serverPluginManifest = ''' 44 | library_uri: 'my_plugin2/my_plugin2.dart' 45 | contributes_to: analysis_server 46 | '''; 47 | const analyzerPluginManifest = ''' 48 | library_uri: 'my_plugin3/my_plugin3.dart' 49 | contributes_to: analyzer 50 | '''; 51 | 52 | var packageMap = { 53 | 'my_plugin': plugin1Uri, 54 | 'my_plugin2': plugin2Uri, 55 | 'my_plugin3': plugin3Uri 56 | }; 57 | 58 | var manifestReader = (Uri uri) { 59 | if (uri == plugin2Uri) return serverPluginManifest; 60 | if (uri == plugin3Uri) return analyzerPluginManifest; 61 | return null; 62 | }; 63 | 64 | test('get plugin details', () { 65 | PluginManager pm = 66 | new PluginManager(packageMap, 'analysis_server', manifestReader); 67 | 68 | PluginInfo notFound = new PluginInfo(name: 'my_plugin1'); 69 | PluginInfo applicable = new PluginInfo(name: 'my_plugin2'); 70 | PluginInfo notApplicable = new PluginInfo(name: 'my_plugin3'); 71 | 72 | PluginConfig localConfig = 73 | new PluginConfig([notFound, applicable, notApplicable]); 74 | 75 | Iterable details = pm.getPluginDetails(localConfig); 76 | expect(details, hasLength(3)); 77 | 78 | List plugins = sortByName(details); 79 | 80 | expect(plugins[0].plugin.name, equals('my_plugin1')); 81 | expect(plugins[0].status, equals(PluginStatus.NotFound)); 82 | expect(plugins[1].plugin.name, equals('my_plugin2')); 83 | expect( 84 | plugins[1].plugin.libraryUri, equals('my_plugin2/my_plugin2.dart')); 85 | expect(plugins[1].status, equals(PluginStatus.Applicable)); 86 | expect(plugins[2].plugin.name, equals('my_plugin3')); 87 | expect(plugins[2].status, equals(PluginStatus.NotApplicable)); 88 | }); 89 | }); 90 | } 91 | 92 | List sortByName(Iterable details) => 93 | details.toList() 94 | ..sort((p1, p2) => p1.plugin.name.compareTo(p2.plugin.name)); 95 | -------------------------------------------------------------------------------- /lib/src/plugin/plugin_manager.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library analyzer_cli.src.plugin.plugin_manager; 6 | 7 | import 'dart:io'; 8 | 9 | import 'package:analyzer/src/plugin/plugin_configuration.dart'; 10 | import 'package:path/path.dart' as path; 11 | 12 | const _manifestFileName = 'plugins.yaml'; 13 | 14 | /// Given a local configuration (as defined in `.analysis_options`) 15 | /// and information from a plugin manifest, return plugin info 16 | /// appropriate for configuring this plugin. 17 | PluginInfo combine(PluginInfo localConfig, PluginInfo manifestInfo) { 18 | return new PluginInfo( 19 | name: localConfig.name, 20 | version: manifestInfo.version, 21 | className: manifestInfo.className, 22 | libraryUri: manifestInfo.libraryUri); 23 | } 24 | 25 | /// Call-back to allow for the injection of manifest readers that do not need 26 | /// to go to disk (for testing purposes). 27 | typedef String ManifestReader(Uri uri); 28 | 29 | /// Wraps a [plugin] info object elaborated with any configuration information 30 | /// extracted from an associated manifest and [status]. 31 | class PluginDetails { 32 | /// Plugin status. 33 | final PluginStatus status; 34 | 35 | /// Plugin info. 36 | final PluginInfo plugin; 37 | 38 | /// Wrap a [plugin] with [status] info. 39 | PluginDetails(this.plugin) : status = PluginStatus.Applicable; 40 | PluginDetails.notApplicable(this.plugin) 41 | : status = PluginStatus.NotApplicable; 42 | PluginDetails.notFound(this.plugin) : status = PluginStatus.NotFound; 43 | } 44 | 45 | /// Manages plugin information derived from plugin manifests. 46 | class PluginManager { 47 | /// Mapping from package name to package location. 48 | final Map _packageMap; 49 | 50 | /// The package naming the app to host plugins. 51 | final String hostPackage; 52 | 53 | /// Function to perform the reading of manifest URIs. (For testing.) 54 | ManifestReader _manifestReader; 55 | 56 | /// Create a plugin manager with backing package map information. 57 | PluginManager(this._packageMap, this.hostPackage, 58 | [ManifestReader manifestReader]) { 59 | _manifestReader = 60 | manifestReader != null ? manifestReader : _findAndReadManifestAtUri; 61 | } 62 | 63 | /// Find a plugin manifest describing the given [pluginPackage]. 64 | PluginManifest findManifest(String pluginPackage) { 65 | Uri uri = _packageMap[pluginPackage]; 66 | String contents = _manifestReader(uri); 67 | if (contents == null) { 68 | return null; 69 | } 70 | return parsePluginManifestString(contents); 71 | } 72 | 73 | /// Return [PluginDetails] derived from associated plugin manifests 74 | /// corresponding to plugins specified in the given [config]. 75 | Iterable getPluginDetails(PluginConfig config) => 76 | config.plugins.map((PluginInfo localConfig) { 77 | PluginManifest manifest = findManifest(localConfig.name); 78 | return _getDetails(localConfig, manifest); 79 | }); 80 | 81 | String _findAndReadManifestAtUri(Uri uri) { 82 | File manifestFile = _findManifest(uri); 83 | return manifestFile?.readAsStringSync(); 84 | } 85 | 86 | File _findManifest(Uri uri) { 87 | if (uri == null) { 88 | return null; 89 | } 90 | 91 | Directory directory = new Directory.fromUri(uri); 92 | File file = new File(path.join(directory.path, _manifestFileName)); 93 | 94 | return file.existsSync() ? file : null; 95 | } 96 | 97 | PluginDetails _getDetails(PluginInfo localConfig, PluginManifest manifest) { 98 | if (manifest == null) { 99 | return new PluginDetails.notFound(localConfig); 100 | } 101 | if (!manifest.contributesTo.contains(hostPackage)) { 102 | return new PluginDetails.notApplicable(localConfig); 103 | } 104 | 105 | return new PluginDetails(combine(localConfig, manifest.plugin)); 106 | } 107 | } 108 | 109 | /// Describes plugin status. 110 | enum PluginStatus { Applicable, NotApplicable, NotFound } 111 | -------------------------------------------------------------------------------- /lib/src/error_formatter.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library analyzer_cli.src.error_formatter; 6 | 7 | import 'package:analyzer/src/generated/engine.dart'; 8 | import 'package:analyzer/src/generated/error.dart'; 9 | import 'package:analyzer/src/generated/source.dart'; 10 | import 'package:analyzer_cli/src/analyzer_impl.dart'; 11 | import 'package:analyzer_cli/src/options.dart'; 12 | 13 | /// Allows any [AnalysisError]. 14 | bool _anyError(AnalysisError error) => true; 15 | 16 | /// Returns `true` if [AnalysisError] should be printed. 17 | typedef bool _ErrorFilter(AnalysisError error); 18 | 19 | /// Helper for formatting [AnalysisError]s. 20 | /// The two format options are a user consumable format and a machine consumable format. 21 | class ErrorFormatter { 22 | final StringSink out; 23 | final CommandLineOptions options; 24 | final _ErrorFilter errorFilter; 25 | 26 | ErrorFormatter(this.out, this.options, [this.errorFilter = _anyError]); 27 | 28 | void formatError( 29 | Map errorToLine, AnalysisError error) { 30 | Source source = error.source; 31 | LineInfo_Location location = errorToLine[error].getLocation(error.offset); 32 | int length = error.length; 33 | ErrorSeverity severity = 34 | AnalyzerImpl.computeSeverity(error, options); 35 | if (options.machineFormat) { 36 | if (severity == ErrorSeverity.WARNING && options.warningsAreFatal) { 37 | severity = ErrorSeverity.ERROR; 38 | } 39 | out.write(severity); 40 | out.write('|'); 41 | out.write(error.errorCode.type); 42 | out.write('|'); 43 | out.write(error.errorCode.name); 44 | out.write('|'); 45 | out.write(escapePipe(source.fullName)); 46 | out.write('|'); 47 | out.write(location.lineNumber); 48 | out.write('|'); 49 | out.write(location.columnNumber); 50 | out.write('|'); 51 | out.write(length); 52 | out.write('|'); 53 | out.write(escapePipe(error.message)); 54 | } else { 55 | String errorType = severity.displayName; 56 | if (error.errorCode.type == ErrorType.HINT || 57 | error.errorCode.type == ErrorType.LINT) { 58 | errorType = error.errorCode.type.displayName; 59 | } 60 | // [warning] 'foo' is not a... (/Users/.../tmp/foo.dart, line 1, col 2) 61 | out.write('[$errorType] ${error.message} '); 62 | out.write('(${source.fullName}'); 63 | out.write(', line ${location.lineNumber}, col ${location.columnNumber})'); 64 | } 65 | out.writeln(); 66 | } 67 | 68 | void formatErrors(List errorInfos) { 69 | var errors = new List(); 70 | var errorToLine = new Map(); 71 | for (AnalysisErrorInfo errorInfo in errorInfos) { 72 | for (AnalysisError error in errorInfo.errors) { 73 | if (errorFilter(error)) { 74 | errors.add(error); 75 | errorToLine[error] = errorInfo.lineInfo; 76 | } 77 | } 78 | } 79 | // Sort errors. 80 | errors.sort((AnalysisError error1, AnalysisError error2) { 81 | // Severity. 82 | ErrorSeverity severity1 = 83 | AnalyzerImpl.computeSeverity(error1, options); 84 | ErrorSeverity severity2 = 85 | AnalyzerImpl.computeSeverity(error2, options); 86 | int compare = severity2.compareTo(severity1); 87 | if (compare != 0) { 88 | return compare; 89 | } 90 | // Path. 91 | compare = Comparable.compare(error1.source.fullName.toLowerCase(), 92 | error2.source.fullName.toLowerCase()); 93 | if (compare != 0) { 94 | return compare; 95 | } 96 | // Offset. 97 | return error1.offset - error2.offset; 98 | }); 99 | // Format errors. 100 | int errorCount = 0; 101 | int warnCount = 0; 102 | int hintCount = 0; 103 | int lintCount = 0; 104 | for (AnalysisError error in errors) { 105 | ErrorSeverity severity = 106 | AnalyzerImpl.computeSeverity(error, options); 107 | if (severity == ErrorSeverity.ERROR) { 108 | errorCount++; 109 | } else if (severity == ErrorSeverity.WARNING) { 110 | if (options.warningsAreFatal) { 111 | errorCount++; 112 | } else { 113 | warnCount++; 114 | } 115 | } else if (error.errorCode.type == ErrorType.HINT) { 116 | hintCount++; 117 | } else if (error.errorCode.type == ErrorType.LINT) { 118 | lintCount++; 119 | } 120 | formatError(errorToLine, error); 121 | } 122 | // Print statistics. 123 | if (!options.machineFormat) { 124 | var hasErrors = errorCount != 0; 125 | var hasWarns = warnCount != 0; 126 | var hasHints = hintCount != 0; 127 | var hasLints = lintCount != 0; 128 | bool hasContent = false; 129 | if (hasErrors) { 130 | out.write(errorCount); 131 | out.write(' '); 132 | out.write(pluralize("error", errorCount)); 133 | hasContent = true; 134 | } 135 | if (hasWarns) { 136 | if (hasContent) { 137 | if (!hasHints && !hasLints) { 138 | out.write(' and '); 139 | } else { 140 | out.write(", "); 141 | } 142 | } 143 | out.write(warnCount); 144 | out.write(' '); 145 | out.write(pluralize("warning", warnCount)); 146 | hasContent = true; 147 | } 148 | if (hasHints) { 149 | if (hasContent) { 150 | if (!hasLints) { 151 | out.write(' and '); 152 | } else { 153 | out.write(", "); 154 | } 155 | } 156 | out.write(hintCount); 157 | out.write(' '); 158 | out.write(pluralize("hint", hintCount)); 159 | hasContent = true; 160 | } 161 | if (hasLints) { 162 | if (hasContent) { 163 | out.write(" and "); 164 | } 165 | out.write(lintCount); 166 | out.write(' '); 167 | out.write(pluralize("lint", lintCount)); 168 | hasContent = true; 169 | } 170 | if (hasContent) { 171 | out.writeln(" found."); 172 | } else { 173 | out.writeln("No issues found"); 174 | } 175 | } 176 | } 177 | 178 | static String escapePipe(String input) { 179 | var result = new StringBuffer(); 180 | for (var c in input.codeUnits) { 181 | if (c == '\\' || c == '|') { 182 | result.write('\\'); 183 | } 184 | result.writeCharCode(c); 185 | } 186 | return result.toString(); 187 | } 188 | 189 | static String pluralize(String word, int count) { 190 | if (count == 1) { 191 | return word; 192 | } else { 193 | return word + "s"; 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /lib/src/bootloader.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library analyzer_cli.src.bootloader; 6 | 7 | import 'dart:async'; 8 | import 'dart:isolate'; 9 | 10 | import 'package:analyzer/file_system/physical_file_system.dart'; 11 | import 'package:analyzer/source/analysis_options_provider.dart'; 12 | import 'package:analyzer/src/context/context.dart'; 13 | import 'package:analyzer/src/generated/engine.dart' as engine; 14 | import 'package:analyzer/src/plugin/plugin_configuration.dart'; 15 | import 'package:analyzer_cli/src/driver.dart'; 16 | import 'package:analyzer_cli/src/options.dart'; 17 | import 'package:source_span/source_span.dart'; 18 | import 'package:yaml/src/yaml_node.dart'; 19 | 20 | const _analyzerPackageName = 'analyzer'; 21 | 22 | /// Return non-null if there is a validation issue with this plugin. 23 | String validate(PluginInfo plugin) { 24 | var missing = []; 25 | if (plugin.className == null) { 26 | missing.add('class name'); 27 | } 28 | if (plugin.libraryUri == null) { 29 | missing.add('library uri'); 30 | } 31 | if (missing.isEmpty) { 32 | // All good. 33 | return null; 34 | } 35 | return 'Plugin ${plugin.name} skipped, config missing: ${missing.join(", ")}'; 36 | } 37 | 38 | List _validate(Iterable plugins) { 39 | List validated = []; 40 | plugins.forEach((PluginInfo plugin) { 41 | String validation = validate(plugin); 42 | if (validation != null) { 43 | errorSink.writeln(validation); 44 | } else { 45 | validated.add(plugin); 46 | } 47 | }); 48 | return validated; 49 | } 50 | 51 | /// Source code assembler. 52 | class Assembler { 53 | /// Plugins to configure. 54 | final Iterable plugins; 55 | 56 | /// Create an assembler for the given plugin [config]. 57 | Assembler(this.plugins); 58 | 59 | /// A string enumerating required package `import`s. 60 | String get enumerateImports => 61 | plugins.map((PluginInfo p) => "import '${p.libraryUri}';").join('\n'); 62 | 63 | /// A string listing initialized plugin instances. 64 | String get pluginList => 65 | plugins.map((PluginInfo p) => 'new ${p.className}()').join(', '); 66 | 67 | /// Create a file containing a `main()` suitable for loading in spawned 68 | /// isolate. 69 | String createMain() => _generateMain(); 70 | 71 | String _generateMain() => """ 72 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 73 | // for details. All rights reserved. Use of this source code is governed by a 74 | // BSD-style license that can be found in the LICENSE file. 75 | 76 | // This code was auto-generated, is not intended to be edited, and is subject to 77 | // significant change. Please see the README file for more information. 78 | 79 | import 'package:analyzer_cli/src/driver.dart'; 80 | 81 | $enumerateImports 82 | 83 | void main(List args) { 84 | var starter = new Driver(); 85 | starter.userDefinedPlugins = [$pluginList]; 86 | starter.start(args); 87 | } 88 | """; 89 | } 90 | 91 | /// Given environment information extracted from command-line `args`, creates a 92 | /// a loadable analyzer "image". 93 | class BootLoader { 94 | /// Emits an error message to [errorSink] if plugin config can't be read. 95 | static final ErrorHandler _pluginConfigErrorHandler = (Exception e) { 96 | String details; 97 | if (e is PluginConfigFormatException) { 98 | details = e.message; 99 | var node = e.yamlNode; 100 | if (node is YamlNode) { 101 | SourceLocation location = node.span.start; 102 | details += ' (line ${location.line}, column ${location.column})'; 103 | } 104 | } else { 105 | details = e.toString(); 106 | } 107 | 108 | errorSink.writeln('Plugin configuration skipped: $details'); 109 | }; 110 | 111 | /// Reads plugin config info from `.analysis_options`. 112 | PluginConfigOptionsProcessor _pluginOptionsProcessor = 113 | new PluginConfigOptionsProcessor(_pluginConfigErrorHandler); 114 | 115 | /// Create a loadable analyzer image configured with plugins derived from 116 | /// the given analyzer command-line `args`. 117 | Image createImage(List args) { 118 | // Parse commandline options. 119 | CommandLineOptions options = CommandLineOptions.parse(args); 120 | 121 | // Process analysis options file (and notify all interested parties). 122 | _processAnalysisOptions(options); 123 | 124 | // TODO(pquitslund): Pass in .packages info 125 | return new Image(_pluginOptionsProcessor.config, 126 | args: args, packageRootPath: options.packageRootPath); 127 | } 128 | 129 | void _processAnalysisOptions(CommandLineOptions options) { 130 | // Determine options file path. 131 | var filePath = options.analysisOptionsFile ?? 132 | engine.AnalysisEngine.ANALYSIS_OPTIONS_FILE; 133 | try { 134 | var file = PhysicalResourceProvider.INSTANCE.getFile(filePath); 135 | AnalysisOptionsProvider analysisOptionsProvider = 136 | new AnalysisOptionsProvider(); 137 | Map options = 138 | analysisOptionsProvider.getOptionsFromFile(file); 139 | //TODO(pq): thread in proper context. 140 | var temporaryContext = new AnalysisContextImpl(); 141 | _pluginOptionsProcessor.optionsProcessed(temporaryContext, options); 142 | } on Exception catch (e) { 143 | _pluginOptionsProcessor.onError(e); 144 | } 145 | } 146 | } 147 | 148 | /// A loadable "image" of a a configured analyzer instance. 149 | class Image { 150 | /// (Optional) package root path. 151 | final String packageRootPath; 152 | 153 | /// (Optional) package map. 154 | final Map packages; 155 | 156 | /// (Optional) args to be passed on to the loaded main. 157 | final List args; 158 | 159 | /// Plugin configuration. 160 | final PluginConfig config; 161 | 162 | /// Create an image with the given [config] and optionally [packages], 163 | /// [packageRootPath], and command line [args]. 164 | Image(this.config, {this.packages, this.packageRootPath, this.args}); 165 | 166 | /// Load this image. 167 | /// 168 | /// Loading an image consists in assembling an analyzer `main()`, configured 169 | /// to include the appropriate analyzer plugins as specified in 170 | /// `.analyzer_options` which is then run in a spawned isolate. 171 | Future load() { 172 | List plugins = _validate(config.plugins); 173 | String mainSource = new Assembler(plugins).createMain(); 174 | 175 | Completer completer = new Completer(); 176 | ReceivePort exitListener = new ReceivePort(); 177 | exitListener.listen((data) { 178 | completer.complete(); 179 | exitListener.close(); 180 | }); 181 | 182 | Uri uri = 183 | Uri.parse('data:application/dart;charset=utf-8,${Uri.encodeComponent( 184 | mainSource)}'); 185 | 186 | // TODO(pquitslund): update once .packages are supported. 187 | String packageRoot = 188 | packageRootPath != null ? packageRootPath : './packages'; 189 | Uri packageUri = new Uri.file(packageRoot); 190 | 191 | Isolate.spawnUri(uri, args, null /* msg */, 192 | packageRoot: packageUri, onExit: exitListener.sendPort); 193 | 194 | return completer.future; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /test/options_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | library analyzer_cli.test.options; 7 | 8 | import 'package:analyzer_cli/src/options.dart'; 9 | import 'package:args/args.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | main() { 13 | group('CommandLineOptions', () { 14 | group('parse', () { 15 | test('defaults', () { 16 | CommandLineOptions options = 17 | CommandLineOptions.parse(['--dart-sdk', '.', 'foo.dart']); 18 | expect(options, isNotNull); 19 | expect(options.dartSdkPath, isNotNull); 20 | expect(options.disableHints, isFalse); 21 | expect(options.lints, isFalse); 22 | expect(options.displayVersion, isFalse); 23 | expect(options.enableStrictCallChecks, isFalse); 24 | expect(options.enableSuperMixins, isFalse); 25 | expect(options.enableTypeChecks, isFalse); 26 | expect(options.hintsAreFatal, isFalse); 27 | expect(options.ignoreUnrecognizedFlags, isFalse); 28 | expect(options.log, isFalse); 29 | expect(options.machineFormat, isFalse); 30 | expect(options.packageRootPath, isNull); 31 | expect(options.shouldBatch, isFalse); 32 | expect(options.showPackageWarnings, isFalse); 33 | expect(options.showSdkWarnings, isFalse); 34 | expect(options.sourceFiles, equals(['foo.dart'])); 35 | expect(options.warningsAreFatal, isFalse); 36 | expect(options.strongMode, isFalse); 37 | }); 38 | 39 | test('batch', () { 40 | CommandLineOptions options = 41 | CommandLineOptions.parse(['--dart-sdk', '.', '--batch']); 42 | expect(options.shouldBatch, isTrue); 43 | }); 44 | 45 | test('defined variables', () { 46 | CommandLineOptions options = CommandLineOptions 47 | .parse(['--dart-sdk', '.', '-Dfoo=bar', 'foo.dart']); 48 | expect(options.definedVariables['foo'], equals('bar')); 49 | expect(options.definedVariables['bar'], isNull); 50 | }); 51 | 52 | test('enable strict call checks', () { 53 | CommandLineOptions options = CommandLineOptions.parse( 54 | ['--dart-sdk', '.', '--enable-strict-call-checks', 'foo.dart']); 55 | expect(options.enableStrictCallChecks, isTrue); 56 | }); 57 | 58 | test('enable super mixins', () { 59 | CommandLineOptions options = CommandLineOptions 60 | .parse(['--dart-sdk', '.', '--supermixin', 'foo.dart']); 61 | expect(options.enableSuperMixins, isTrue); 62 | }); 63 | 64 | test('enable type checks', () { 65 | CommandLineOptions options = CommandLineOptions 66 | .parse(['--dart-sdk', '.', '--enable_type_checks', 'foo.dart']); 67 | expect(options.enableTypeChecks, isTrue); 68 | }); 69 | 70 | test('hintsAreFatal', () { 71 | CommandLineOptions options = CommandLineOptions 72 | .parse(['--dart-sdk', '.', '--fatal-hints', 'foo.dart']); 73 | expect(options.hintsAreFatal, isTrue); 74 | }); 75 | 76 | test('log', () { 77 | CommandLineOptions options = 78 | CommandLineOptions.parse(['--dart-sdk', '.', '--log', 'foo.dart']); 79 | expect(options.log, isTrue); 80 | }); 81 | 82 | test('machine format', () { 83 | CommandLineOptions options = CommandLineOptions 84 | .parse(['--dart-sdk', '.', '--format=machine', 'foo.dart']); 85 | expect(options.machineFormat, isTrue); 86 | }); 87 | 88 | test('no-hints', () { 89 | CommandLineOptions options = CommandLineOptions 90 | .parse(['--dart-sdk', '.', '--no-hints', 'foo.dart']); 91 | expect(options.disableHints, isTrue); 92 | }); 93 | 94 | test('options', () { 95 | CommandLineOptions options = CommandLineOptions.parse( 96 | ['--dart-sdk', '.', '--options', 'options.yaml', 'foo.dart']); 97 | expect(options.analysisOptionsFile, equals('options.yaml')); 98 | }); 99 | 100 | test('lints', () { 101 | CommandLineOptions options = CommandLineOptions 102 | .parse(['--dart-sdk', '.', '--lints', 'foo.dart']); 103 | expect(options.lints, isTrue); 104 | }); 105 | 106 | test('package root', () { 107 | CommandLineOptions options = CommandLineOptions 108 | .parse(['--dart-sdk', '.', '-p', 'bar', 'foo.dart']); 109 | expect(options.packageRootPath, equals('bar')); 110 | }); 111 | 112 | test('package warnings', () { 113 | CommandLineOptions options = CommandLineOptions 114 | .parse(['--dart-sdk', '.', '--package-warnings', 'foo.dart']); 115 | expect(options.showPackageWarnings, isTrue); 116 | }); 117 | 118 | test('sdk warnings', () { 119 | CommandLineOptions options = CommandLineOptions 120 | .parse(['--dart-sdk', '.', '--warnings', 'foo.dart']); 121 | expect(options.showSdkWarnings, isTrue); 122 | }); 123 | 124 | test('sourceFiles', () { 125 | CommandLineOptions options = CommandLineOptions.parse( 126 | ['--dart-sdk', '.', '--log', 'foo.dart', 'foo2.dart', 'foo3.dart']); 127 | expect(options.sourceFiles, 128 | equals(['foo.dart', 'foo2.dart', 'foo3.dart'])); 129 | }); 130 | 131 | test('warningsAreFatal', () { 132 | CommandLineOptions options = CommandLineOptions 133 | .parse(['--dart-sdk', '.', '--fatal-warnings', 'foo.dart']); 134 | expect(options.warningsAreFatal, isTrue); 135 | }); 136 | 137 | test('notice unrecognized flags', () { 138 | expect( 139 | () => new CommandLineParser() 140 | .parse(['--bar', '--baz', 'foo.dart'], {}), 141 | throwsA(new isInstanceOf())); 142 | }); 143 | 144 | test('ignore unrecognized flags', () { 145 | CommandLineOptions options = CommandLineOptions.parse([ 146 | '--ignore-unrecognized-flags', 147 | '--bar', 148 | '--baz', 149 | '--dart-sdk', 150 | '.', 151 | 'foo.dart' 152 | ]); 153 | expect(options, isNotNull); 154 | expect(options.sourceFiles, equals(['foo.dart'])); 155 | }); 156 | 157 | test('ignore unrecognized options', () { 158 | CommandLineParser parser = 159 | new CommandLineParser(alwaysIgnoreUnrecognized: true); 160 | parser.addOption('optionA'); 161 | parser.addFlag('flagA'); 162 | ArgResults argResults = 163 | parser.parse(['--optionA=1', '--optionB=2', '--flagA'], {}); 164 | expect(argResults['optionA'], '1'); 165 | expect(argResults['flagA'], isTrue); 166 | }); 167 | 168 | test('strong mode', () { 169 | CommandLineOptions options = CommandLineOptions 170 | .parse(['--strong', 'foo.dart']); 171 | expect(options.strongMode, isTrue); 172 | }); 173 | 174 | test("can't specify package and package-root", () { 175 | var failureMessage; 176 | CommandLineOptions.parse( 177 | ['--package-root', '.', '--packages', '.', 'foo.dart'], 178 | (msg) => failureMessage = msg); 179 | expect(failureMessage, 180 | equals("Cannot specify both '--package-root' and '--packages.")); 181 | }); 182 | 183 | test("bad SDK dir", () { 184 | var failureMessage; 185 | CommandLineOptions.parse( 186 | ['--dart-sdk', '&&&&&', 'foo.dart'], (msg) => failureMessage = msg); 187 | expect(failureMessage, equals('Invalid Dart SDK path: &&&&&')); 188 | }); 189 | }); 190 | }); 191 | } 192 | -------------------------------------------------------------------------------- /lib/src/analyzer_impl.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library analyzer_cli.src.analyzer_impl; 6 | 7 | import 'dart:collection'; 8 | import 'dart:io'; 9 | 10 | import 'package:analyzer/src/generated/element.dart'; 11 | import 'package:analyzer/src/generated/engine.dart'; 12 | import 'package:analyzer/src/generated/error.dart'; 13 | import 'package:analyzer/src/generated/java_engine.dart'; 14 | import 'package:analyzer/src/generated/java_io.dart'; 15 | import 'package:analyzer/src/generated/sdk_io.dart'; 16 | import 'package:analyzer/src/generated/source.dart'; 17 | import 'package:analyzer/src/generated/source_io.dart'; 18 | import 'package:analyzer/src/generated/utilities_general.dart'; 19 | import 'package:analyzer_cli/src/driver.dart'; 20 | import 'package:analyzer_cli/src/error_formatter.dart'; 21 | import 'package:analyzer_cli/src/options.dart'; 22 | 23 | DirectoryBasedDartSdk sdk; 24 | 25 | /// The maximum number of sources for which AST structures should be kept in the cache. 26 | const int _maxCacheSize = 512; 27 | 28 | int currentTimeMillis() => new DateTime.now().millisecondsSinceEpoch; 29 | 30 | /// Analyzes single library [File]. 31 | class AnalyzerImpl { 32 | final CommandLineOptions options; 33 | final int startTime; 34 | 35 | final AnalysisContext context; 36 | final Source librarySource; 37 | 38 | /// All [Source]s references by the analyzed library. 39 | final Set sources = new Set(); 40 | 41 | /// All [AnalysisErrorInfo]s in the analyzed library. 42 | final List errorInfos = new List(); 43 | 44 | /// [HashMap] between sources and analysis error infos. 45 | final HashMap sourceErrorsMap = 46 | new HashMap(); 47 | 48 | /// If the file specified on the command line is part of a package, the name 49 | /// of that package. Otherwise `null`. This allows us to analyze the file 50 | /// specified on the command line as though it is reached via a "package:" 51 | /// URI, but avoid suppressing its output in the event that the user has not 52 | /// specified the "--package-warnings" option. 53 | String _selfPackageName; 54 | 55 | AnalyzerImpl(this.context, this.librarySource, this.options, this.startTime); 56 | 57 | /// Returns the maximal [ErrorSeverity] of the recorded errors. 58 | ErrorSeverity get maxErrorSeverity { 59 | var status = ErrorSeverity.NONE; 60 | for (AnalysisErrorInfo errorInfo in errorInfos) { 61 | for (AnalysisError error in errorInfo.errors) { 62 | if (!_isDesiredError(error)) { 63 | continue; 64 | } 65 | var severity = computeSeverity(error, options); 66 | status = status.max(severity); 67 | } 68 | } 69 | return status; 70 | } 71 | 72 | void addCompilationUnitSource(CompilationUnitElement unit, 73 | Set libraries, Set units) { 74 | if (unit == null || units.contains(unit)) { 75 | return; 76 | } 77 | units.add(unit); 78 | sources.add(unit.source); 79 | } 80 | 81 | void addLibrarySources(LibraryElement library, Set libraries, 82 | Set units) { 83 | if (library == null || !libraries.add(library)) { 84 | return; 85 | } 86 | // Maybe skip library. 87 | { 88 | UriKind uriKind = library.source.uriKind; 89 | // Optionally skip package: libraries. 90 | if (!options.showPackageWarnings && _isOtherPackage(library.source.uri)) { 91 | return; 92 | } 93 | // Optionally skip SDK libraries. 94 | if (!options.showSdkWarnings && uriKind == UriKind.DART_URI) { 95 | return; 96 | } 97 | } 98 | // Add compilation units. 99 | addCompilationUnitSource(library.definingCompilationUnit, libraries, units); 100 | for (CompilationUnitElement child in library.parts) { 101 | addCompilationUnitSource(child, libraries, units); 102 | } 103 | // Add referenced libraries. 104 | for (LibraryElement child in library.importedLibraries) { 105 | addLibrarySources(child, libraries, units); 106 | } 107 | for (LibraryElement child in library.exportedLibraries) { 108 | addLibrarySources(child, libraries, units); 109 | } 110 | } 111 | 112 | /// Treats the [sourcePath] as the top level library and analyzes it using a 113 | /// synchronous algorithm over the analysis engine. If [printMode] is `0`, 114 | /// then no error or performance information is printed. If [printMode] is `1`, 115 | /// then both will be printed. If [printMode] is `2`, then only performance 116 | /// information will be printed, and it will be marked as being for a cold VM. 117 | ErrorSeverity analyzeSync({int printMode: 1}) { 118 | setupForAnalysis(); 119 | return _analyzeSync(printMode); 120 | } 121 | 122 | /// Fills [errorInfos] using [sources]. 123 | void prepareErrors() { 124 | for (Source source in sources) { 125 | context.computeErrors(source); 126 | 127 | errorInfos.add(context.getErrors(source)); 128 | } 129 | } 130 | 131 | /// Fills [sources]. 132 | void prepareSources(LibraryElement library) { 133 | var units = new Set(); 134 | var libraries = new Set(); 135 | addLibrarySources(library, libraries, units); 136 | } 137 | 138 | /// Setup local fields such as the analysis context for analysis. 139 | void setupForAnalysis() { 140 | sources.clear(); 141 | errorInfos.clear(); 142 | Uri libraryUri = librarySource.uri; 143 | if (libraryUri.scheme == 'package' && libraryUri.pathSegments.length > 0) { 144 | _selfPackageName = libraryUri.pathSegments[0]; 145 | } 146 | } 147 | 148 | /// The sync version of analysis. 149 | ErrorSeverity _analyzeSync(int printMode) { 150 | // Don't try to analyze parts. 151 | if (context.computeKindOf(librarySource) == SourceKind.PART) { 152 | stderr.writeln("Only libraries can be analyzed."); 153 | stderr.writeln( 154 | "${librarySource.fullName} is a part and can not be analyzed."); 155 | return ErrorSeverity.ERROR; 156 | } 157 | // Resolve library. 158 | var libraryElement = context.computeLibraryElement(librarySource); 159 | // Prepare source and errors. 160 | prepareSources(libraryElement); 161 | prepareErrors(); 162 | 163 | // Print errors and performance numbers. 164 | if (printMode == 1) { 165 | _printErrorsAndPerf(); 166 | } else if (printMode == 2) { 167 | _printColdPerf(); 168 | } 169 | 170 | // Compute max severity and set exitCode. 171 | ErrorSeverity status = maxErrorSeverity; 172 | if (status == ErrorSeverity.WARNING && options.warningsAreFatal) { 173 | status = ErrorSeverity.ERROR; 174 | } 175 | return status; 176 | } 177 | 178 | bool _isDesiredError(AnalysisError error) { 179 | if (error.errorCode.type == ErrorType.TODO) { 180 | return false; 181 | } 182 | if (computeSeverity(error, options) == ErrorSeverity.INFO && 183 | options.disableHints) { 184 | return false; 185 | } 186 | return true; 187 | } 188 | 189 | /// Determine whether the given URI refers to a package other than the package 190 | /// being analyzed. 191 | bool _isOtherPackage(Uri uri) { 192 | if (uri.scheme != 'package') { 193 | return false; 194 | } 195 | if (_selfPackageName != null && 196 | uri.pathSegments.length > 0 && 197 | uri.pathSegments[0] == _selfPackageName) { 198 | return false; 199 | } 200 | return true; 201 | } 202 | 203 | _printColdPerf() { 204 | // Print cold VM performance numbers. 205 | int totalTime = currentTimeMillis() - startTime; 206 | int otherTime = totalTime; 207 | for (PerformanceTag tag in PerformanceTag.all) { 208 | if (tag != PerformanceTag.UNKNOWN) { 209 | int tagTime = tag.elapsedMs; 210 | outSink.writeln('${tag.label}-cold:$tagTime'); 211 | otherTime -= tagTime; 212 | } 213 | } 214 | outSink.writeln('other-cold:$otherTime'); 215 | outSink.writeln("total-cold:$totalTime"); 216 | } 217 | 218 | _printErrorsAndPerf() { 219 | // The following is a hack. We currently print out to stderr to ensure that 220 | // when in batch mode we print to stderr, this is because the prints from 221 | // batch are made to stderr. The reason that options.shouldBatch isn't used 222 | // is because when the argument flags are constructed in BatchRunner and 223 | // passed in from batch mode which removes the batch flag to prevent the 224 | // "cannot have the batch flag and source file" error message. 225 | StringSink sink = options.machineFormat ? errorSink : outSink; 226 | 227 | // Print errors. 228 | ErrorFormatter formatter = 229 | new ErrorFormatter(sink, options, _isDesiredError); 230 | formatter.formatErrors(errorInfos); 231 | } 232 | 233 | /// Compute the severity of the error; however: 234 | /// * if [options.enableTypeChecks] is false, then de-escalate checked-mode 235 | /// compile time errors to a severity of [ErrorSeverity.INFO]. 236 | /// * if [options.hintsAreFatal] is true, escalate hints to errors. 237 | static ErrorSeverity computeSeverity( 238 | AnalysisError error, CommandLineOptions options) { 239 | if (!options.enableTypeChecks && 240 | error.errorCode.type == ErrorType.CHECKED_MODE_COMPILE_TIME_ERROR) { 241 | return ErrorSeverity.INFO; 242 | } 243 | if (options.hintsAreFatal && error.errorCode is HintCode) { 244 | return ErrorSeverity.ERROR; 245 | } 246 | return error.errorCode.errorSeverity; 247 | } 248 | 249 | /// Return the corresponding package directory or `null` if none is found. 250 | static JavaFile getPackageDirectoryFor(JavaFile sourceFile) { 251 | // We are going to ask parent file, so get absolute path. 252 | sourceFile = sourceFile.getAbsoluteFile(); 253 | // Look in the containing directories. 254 | JavaFile dir = sourceFile.getParentFile(); 255 | while (dir != null) { 256 | JavaFile packagesDir = new JavaFile.relative(dir, "packages"); 257 | if (packagesDir.exists()) { 258 | return packagesDir; 259 | } 260 | dir = dir.getParentFile(); 261 | } 262 | // Not found. 263 | return null; 264 | } 265 | } 266 | 267 | /// This [Logger] prints out information comments to [outSink] and error messages 268 | /// to [errorSink]. 269 | class StdLogger extends Logger { 270 | StdLogger(); 271 | 272 | @override 273 | void logError(String message, [CaughtException exception]) { 274 | errorSink.writeln(message); 275 | if (exception != null) { 276 | errorSink.writeln(exception); 277 | } 278 | } 279 | 280 | @override 281 | void logError2(String message, Object exception) { 282 | errorSink.writeln(message); 283 | if (exception != null) { 284 | errorSink.writeln(exception.toString()); 285 | } 286 | } 287 | 288 | @override 289 | void logInformation(String message, [CaughtException exception]) { 290 | outSink.writeln(message); 291 | if (exception != null) { 292 | outSink.writeln(exception); 293 | } 294 | } 295 | 296 | @override 297 | void logInformation2(String message, Object exception) { 298 | outSink.writeln(message); 299 | if (exception != null) { 300 | outSink.writeln(exception.toString()); 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /test/driver_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn("vm") 6 | library analyzer_cli.test.driver; 7 | 8 | import 'dart:io'; 9 | 10 | import 'package:analyzer/plugin/options.dart'; 11 | import 'package:analyzer/source/analysis_options_provider.dart'; 12 | import 'package:analyzer/src/generated/engine.dart'; 13 | import 'package:analyzer/src/generated/error.dart'; 14 | import 'package:analyzer/src/generated/source.dart'; 15 | import 'package:analyzer/src/plugin/plugin_configuration.dart'; 16 | import 'package:analyzer/src/services/lint.dart'; 17 | import 'package:analyzer_cli/src/bootloader.dart'; 18 | import 'package:analyzer_cli/src/driver.dart'; 19 | import 'package:analyzer_cli/src/options.dart'; 20 | import 'package:path/path.dart' as path; 21 | import 'package:plugin/plugin.dart'; 22 | import 'package:test/test.dart'; 23 | import 'package:yaml/src/yaml_node.dart'; 24 | 25 | main() { 26 | group('Driver', () { 27 | StringSink savedOutSink, savedErrorSink; 28 | int savedExitCode; 29 | setUp(() { 30 | savedOutSink = outSink; 31 | savedErrorSink = errorSink; 32 | savedExitCode = exitCode; 33 | outSink = new StringBuffer(); 34 | errorSink = new StringBuffer(); 35 | }); 36 | tearDown(() { 37 | outSink = savedOutSink; 38 | errorSink = savedErrorSink; 39 | exitCode = savedExitCode; 40 | }); 41 | 42 | group('options', () { 43 | test('custom processor', () { 44 | Driver driver = new Driver(); 45 | TestProcessor processor = new TestProcessor(); 46 | driver.userDefinedPlugins = [new TestPlugin(processor)]; 47 | driver.start([ 48 | '--options', 49 | 'test/data/test_options.yaml', 50 | 'test/data/test_file.dart' 51 | ]); 52 | expect(processor.options['test_plugin'], isNotNull); 53 | expect(processor.exception, isNull); 54 | }); 55 | }); 56 | 57 | group('exit codes', () { 58 | StringSink savedOutSink, savedErrorSink; 59 | int savedExitCode; 60 | ExitHandler savedExitHandler; 61 | setUp(() { 62 | savedOutSink = outSink; 63 | savedErrorSink = errorSink; 64 | savedExitCode = exitCode; 65 | savedExitHandler = exitHandler; 66 | exitHandler = (code) => exitCode = code; 67 | outSink = new StringBuffer(); 68 | errorSink = new StringBuffer(); 69 | }); 70 | tearDown(() { 71 | outSink = savedOutSink; 72 | errorSink = savedErrorSink; 73 | exitCode = savedExitCode; 74 | exitHandler = savedExitHandler; 75 | }); 76 | 77 | test('fatal hints', () { 78 | drive('test/data/file_with_hint.dart', args: ['--fatal-hints']); 79 | expect(exitCode, 3); 80 | }); 81 | 82 | test('not fatal hints', () { 83 | drive('test/data/file_with_hint.dart'); 84 | expect(exitCode, 0); 85 | }); 86 | 87 | test('fatal errors', () { 88 | drive('test/data/file_with_error.dart'); 89 | expect(exitCode, 3); 90 | }); 91 | 92 | test('not fatal warnings', () { 93 | drive('test/data/file_with_warning.dart'); 94 | expect(exitCode, 0); 95 | }); 96 | 97 | test('fatal warnings', () { 98 | drive('test/data/file_with_warning.dart', args: ['--fatal-warnings']); 99 | expect(exitCode, 3); 100 | }); 101 | 102 | test('missing options file', () { 103 | drive('test/data/test_file.dart', options: 'test/data/NO_OPTIONS_HERE'); 104 | expect(exitCode, 3); 105 | }); 106 | 107 | test('missing dart file', () { 108 | drive('test/data/NO_DART_FILE_HERE.dart'); 109 | expect(exitCode, 3); 110 | }); 111 | 112 | test('part file', () { 113 | drive('test/data/library_and_parts/part2.dart'); 114 | expect(exitCode, 3); 115 | }); 116 | 117 | test('non-dangling part file', () { 118 | Driver driver = new Driver(); 119 | driver.start([ 120 | 'test/data/library_and_parts/lib.dart', 121 | 'test/data/library_and_parts/part1.dart', 122 | ]); 123 | expect(exitCode, 0); 124 | }); 125 | 126 | test('extra part file', () { 127 | Driver driver = new Driver(); 128 | driver.start([ 129 | 'test/data/library_and_parts/lib.dart', 130 | 'test/data/library_and_parts/part1.dart', 131 | 'test/data/library_and_parts/part2.dart', 132 | ]); 133 | expect(exitCode, 3); 134 | }); 135 | }); 136 | 137 | group('linter', () { 138 | group('lints in options', () { 139 | StringSink savedOutSink; 140 | Driver driver; 141 | 142 | setUp(() { 143 | savedOutSink = outSink; 144 | outSink = new StringBuffer(); 145 | 146 | driver = new Driver(); 147 | driver.start([ 148 | '--options', 149 | 'test/data/linter_project/.analysis_options', 150 | '--lints', 151 | 'test/data/linter_project/test_file.dart' 152 | ]); 153 | }); 154 | tearDown(() { 155 | outSink = savedOutSink; 156 | }); 157 | 158 | test('gets analysis options', () { 159 | /// Lints should be enabled. 160 | expect(driver.context.analysisOptions.lint, isTrue); 161 | 162 | /// The .analysis_options file only specifies 'camel_case_types'. 163 | var lintNames = getLints(driver.context).map((r) => r.name); 164 | expect(lintNames, orderedEquals(['camel_case_types'])); 165 | }); 166 | 167 | test('generates lints', () { 168 | expect(outSink.toString(), 169 | contains('[lint] Name types using UpperCamelCase.')); 170 | }); 171 | }); 172 | 173 | group('default lints', () { 174 | StringSink savedOutSink; 175 | Driver driver; 176 | 177 | setUp(() { 178 | savedOutSink = outSink; 179 | outSink = new StringBuffer(); 180 | 181 | driver = new Driver(); 182 | driver.start([ 183 | '--lints', 184 | 'test/data/linter_project/test_file.dart', 185 | '--options', 186 | 'test/data/linter_project/.analysis_options' 187 | ]); 188 | }); 189 | tearDown(() { 190 | outSink = savedOutSink; 191 | }); 192 | 193 | test('gets default lints', () { 194 | /// Lints should be enabled. 195 | expect(driver.context.analysisOptions.lint, isTrue); 196 | 197 | /// Default list should include camel_case_types. 198 | var lintNames = getLints(driver.context).map((r) => r.name); 199 | expect(lintNames, contains('camel_case_types')); 200 | }); 201 | 202 | test('generates lints', () { 203 | expect(outSink.toString(), 204 | contains('[lint] Name types using UpperCamelCase.')); 205 | }); 206 | }); 207 | 208 | group('no `--lints` flag (none in options)', () { 209 | StringSink savedOutSink; 210 | Driver driver; 211 | 212 | setUp(() { 213 | savedOutSink = outSink; 214 | outSink = new StringBuffer(); 215 | 216 | driver = new Driver(); 217 | driver.start([ 218 | 'test/data/no_lints_project/test_file.dart', 219 | '--options', 220 | 'test/data/no_lints_project/.analysis_options' 221 | ]); 222 | }); 223 | tearDown(() { 224 | outSink = savedOutSink; 225 | }); 226 | 227 | test('lints disabled', () { 228 | expect(driver.context.analysisOptions.lint, isFalse); 229 | }); 230 | 231 | test('no registered lints', () { 232 | expect(getLints(driver.context), isEmpty); 233 | }); 234 | 235 | test('no generated warnings', () { 236 | expect(outSink.toString(), contains('No issues found')); 237 | }); 238 | }); 239 | }); 240 | 241 | test('containsLintRuleEntry', () { 242 | Map options; 243 | options = parseOptions(''' 244 | linter: 245 | rules: 246 | - foo 247 | '''); 248 | expect(containsLintRuleEntry(options), true); 249 | options = parseOptions(''' 250 | '''); 251 | expect(containsLintRuleEntry(options), false); 252 | options = parseOptions(''' 253 | linter: 254 | rules: 255 | # - foo 256 | '''); 257 | expect(containsLintRuleEntry(options), true); 258 | options = parseOptions(''' 259 | linter: 260 | # rules: 261 | # - foo 262 | '''); 263 | expect(containsLintRuleEntry(options), false); 264 | }); 265 | 266 | group('options processing', () { 267 | group('error filters', () { 268 | StringSink savedOutSink; 269 | Driver driver; 270 | 271 | setUp(() { 272 | savedOutSink = outSink; 273 | outSink = new StringBuffer(); 274 | 275 | driver = new Driver(); 276 | driver.start([ 277 | 'test/data/options_tests_project/test_file.dart', 278 | '--options', 279 | 'test/data/options_tests_project/.analysis_options' 280 | ]); 281 | }); 282 | tearDown(() { 283 | outSink = savedOutSink; 284 | }); 285 | 286 | test('filters', () { 287 | var filters = 288 | driver.context.getConfigurationData(CONFIGURED_ERROR_FILTERS); 289 | expect(filters, hasLength(1)); 290 | 291 | var unused_error = new AnalysisError( 292 | new TestSource(), 0, 1, HintCode.UNUSED_LOCAL_VARIABLE, [ 293 | ['x'] 294 | ]); 295 | expect(filters.any((filter) => filter(unused_error)), isTrue); 296 | }); 297 | 298 | test('language config', () { 299 | expect(driver.context.analysisOptions.enableSuperMixins, isTrue); 300 | }); 301 | }); 302 | }); 303 | 304 | group('in temp directory', () { 305 | StringSink savedOutSink, savedErrorSink; 306 | int savedExitCode; 307 | Directory savedCurrentDirectory; 308 | Directory tempDir; 309 | setUp(() { 310 | savedOutSink = outSink; 311 | savedErrorSink = errorSink; 312 | savedExitCode = exitCode; 313 | outSink = new StringBuffer(); 314 | errorSink = new StringBuffer(); 315 | savedCurrentDirectory = Directory.current; 316 | tempDir = Directory.systemTemp.createTempSync('analyzer_'); 317 | }); 318 | tearDown(() { 319 | outSink = savedOutSink; 320 | errorSink = savedErrorSink; 321 | exitCode = savedExitCode; 322 | Directory.current = savedCurrentDirectory; 323 | tempDir.deleteSync(recursive: true); 324 | }); 325 | 326 | test('packages folder', () { 327 | Directory.current = tempDir; 328 | new File(path.join(tempDir.path, 'test.dart')).writeAsStringSync(''' 329 | import 'package:foo/bar.dart'; 330 | main() { 331 | baz(); 332 | } 333 | '''); 334 | Directory packagesDir = 335 | new Directory(path.join(tempDir.path, 'packages')); 336 | packagesDir.createSync(); 337 | Directory fooDir = new Directory(path.join(packagesDir.path, 'foo')); 338 | fooDir.createSync(); 339 | new File(path.join(fooDir.path, 'bar.dart')).writeAsStringSync(''' 340 | void baz() {} 341 | '''); 342 | new Driver().start(['test.dart']); 343 | expect(exitCode, 0); 344 | }); 345 | 346 | test('no package resolution', () { 347 | Directory.current = tempDir; 348 | new File(path.join(tempDir.path, 'test.dart')).writeAsStringSync(''' 349 | import 'package:path/path.dart'; 350 | main() {} 351 | '''); 352 | new Driver().start(['test.dart']); 353 | expect(exitCode, 3); 354 | String stdout = outSink.toString(); 355 | expect(stdout, contains('[error] Target of URI does not exist')); 356 | expect(stdout, contains('1 error found.')); 357 | expect(errorSink.toString(), ''); 358 | }); 359 | 360 | test('bad package root', () { 361 | new Driver().start(['--package-root', 'does/not/exist', 'test.dart']); 362 | String stdout = outSink.toString(); 363 | expect(exitCode, 3); 364 | expect( 365 | stdout, 366 | contains( 367 | 'Package root directory (does/not/exist) does not exist.')); 368 | }); 369 | }); 370 | }); 371 | group('Bootloader', () { 372 | group('plugin processing', () { 373 | StringSink savedErrorSink; 374 | setUp(() { 375 | savedErrorSink = errorSink; 376 | errorSink = new StringBuffer(); 377 | }); 378 | tearDown(() { 379 | errorSink = savedErrorSink; 380 | }); 381 | test('bad format', () { 382 | BootLoader loader = new BootLoader(); 383 | loader.createImage([ 384 | '--options', 385 | 'test/data/bad_plugin_options.yaml', 386 | 'test/data/test_file.dart' 387 | ]); 388 | expect( 389 | errorSink.toString(), 390 | equals('Plugin configuration skipped: Unrecognized plugin config ' 391 | 'format, expected `YamlMap`, got `YamlList` ' 392 | '(line 2, column 4)\n')); 393 | }); 394 | test('plugin config', () { 395 | BootLoader loader = new BootLoader(); 396 | Image image = loader.createImage([ 397 | '--options', 398 | 'test/data/plugin_options.yaml', 399 | 'test/data/test_file.dart' 400 | ]); 401 | var plugins = image.config.plugins; 402 | expect(plugins, hasLength(1)); 403 | expect(plugins.first.name, equals('my_plugin1')); 404 | }); 405 | group('plugin validation', () { 406 | test('requires class name', () { 407 | expect( 408 | validate(new PluginInfo( 409 | name: 'test_plugin', libraryUri: 'my_package/foo.dart')), 410 | isNotNull); 411 | }); 412 | test('requires library URI', () { 413 | expect( 414 | validate( 415 | new PluginInfo(name: 'test_plugin', className: 'MyPlugin')), 416 | isNotNull); 417 | }); 418 | test('check', () { 419 | expect( 420 | validate(new PluginInfo( 421 | name: 'test_plugin', 422 | className: 'MyPlugin', 423 | libraryUri: 'my_package/foo.dart')), 424 | isNull); 425 | }); 426 | }); 427 | }); 428 | }); 429 | } 430 | 431 | const emptyOptionsFile = 'test/data/empty_options.yaml'; 432 | 433 | /// Start a driver for the given [source], optionally providing additional 434 | /// [args] and an [options] file path. The value of [options] defaults to 435 | /// an empty options file to avoid unwanted configuration from an otherwise 436 | /// discovered options file. 437 | void drive(String source, 438 | {String options: emptyOptionsFile, 439 | List args: const []}) => 440 | new Driver().start(['--options', options, source]..addAll(args)); 441 | 442 | Map parseOptions(String src) => 443 | new AnalysisOptionsProvider().getOptionsFromString(src); 444 | 445 | class TestPlugin extends Plugin { 446 | TestProcessor processor; 447 | TestPlugin(this.processor); 448 | 449 | @override 450 | String get uniqueIdentifier => 'test_plugin.core'; 451 | 452 | @override 453 | void registerExtensionPoints(RegisterExtensionPoint register) { 454 | // None 455 | } 456 | 457 | @override 458 | void registerExtensions(RegisterExtension register) { 459 | register(OPTIONS_PROCESSOR_EXTENSION_POINT_ID, processor); 460 | } 461 | } 462 | 463 | class TestProcessor extends OptionsProcessor { 464 | Map options; 465 | Exception exception; 466 | 467 | @override 468 | void onError(Exception exception) { 469 | this.exception = exception; 470 | } 471 | 472 | @override 473 | void optionsProcessed( 474 | AnalysisContext context, Map options) { 475 | this.options = options; 476 | } 477 | } 478 | 479 | class TestSource implements Source { 480 | TestSource(); 481 | 482 | @override 483 | noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); 484 | } 485 | -------------------------------------------------------------------------------- /lib/src/options.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library analyzer_cli.src.options; 6 | 7 | import 'dart:io'; 8 | 9 | import 'package:analyzer_cli/src/driver.dart'; 10 | import 'package:args/args.dart'; 11 | import 'package:cli_util/cli_util.dart' show getSdkDir; 12 | 13 | const _binaryName = 'dartanalyzer'; 14 | 15 | /// Shared exit handler. 16 | /// 17 | /// *Visible for testing.* 18 | ExitHandler exitHandler = exit; 19 | 20 | /// Print the given message to stderr and exit with the given [exitCode] 21 | void printAndFail(String message, {int exitCode: 15}) { 22 | errorSink.writeln(message); 23 | exitHandler(exitCode); 24 | } 25 | 26 | /// Exit handler. 27 | /// 28 | /// *Visible for testing.* 29 | typedef void ExitHandler(int code); 30 | 31 | /// Analyzer commandline configuration options. 32 | class CommandLineOptions { 33 | /// The path to an analysis options file 34 | final String analysisOptionsFile; 35 | 36 | /// The path to the dart SDK 37 | String dartSdkPath; 38 | 39 | /// A table mapping the names of defined variables to their values. 40 | final Map definedVariables; 41 | 42 | /// Whether to report hints 43 | final bool disableHints; 44 | 45 | /// Whether to display version information 46 | final bool displayVersion; 47 | 48 | /// Whether to enable null-aware operators (DEP 9). 49 | final bool enableNullAwareOperators; 50 | 51 | /// Whether to strictly follow the specification when generating warnings on 52 | /// "call" methods (fixes dartbug.com/21938). 53 | final bool enableStrictCallChecks; 54 | 55 | /// Whether to relax restrictions on mixins (DEP 34). 56 | final bool enableSuperMixins; 57 | 58 | /// Whether to treat type mismatches found during constant evaluation as 59 | /// errors. 60 | final bool enableTypeChecks; 61 | 62 | /// Whether to treat hints as fatal 63 | final bool hintsAreFatal; 64 | 65 | /// Whether to ignore unrecognized flags 66 | final bool ignoreUnrecognizedFlags; 67 | 68 | /// Whether to report lints 69 | final bool lints; 70 | 71 | /// Whether to log additional analysis messages and exceptions 72 | final bool log; 73 | 74 | /// Whether to use machine format for error display 75 | final bool machineFormat; 76 | 77 | /// The path to the package root 78 | final String packageRootPath; 79 | 80 | /// The path to a `.packages` configuration file 81 | final String packageConfigPath; 82 | 83 | /// Batch mode (for unit testing) 84 | final bool shouldBatch; 85 | 86 | /// Whether to show package: warnings 87 | final bool showPackageWarnings; 88 | 89 | /// Whether to show SDK warnings 90 | final bool showSdkWarnings; 91 | 92 | /// The source files to analyze 93 | final List sourceFiles; 94 | 95 | /// Whether to treat warnings as fatal 96 | final bool warningsAreFatal; 97 | 98 | /// Whether to use strong static checking. 99 | final bool strongMode; 100 | 101 | /// Initialize options from the given parsed [args]. 102 | CommandLineOptions._fromArgs( 103 | ArgResults args, Map definedVariables) 104 | : dartSdkPath = args['dart-sdk'], 105 | this.definedVariables = definedVariables, 106 | analysisOptionsFile = args['options'], 107 | disableHints = args['no-hints'], 108 | displayVersion = args['version'], 109 | enableNullAwareOperators = args['enable-null-aware-operators'], 110 | enableStrictCallChecks = args['enable-strict-call-checks'], 111 | enableSuperMixins = args['supermixin'], 112 | enableTypeChecks = args['enable_type_checks'], 113 | hintsAreFatal = args['fatal-hints'], 114 | ignoreUnrecognizedFlags = args['ignore-unrecognized-flags'], 115 | lints = args['lints'], 116 | log = args['log'], 117 | machineFormat = args['machine'] || args['format'] == 'machine', 118 | packageConfigPath = args['packages'], 119 | packageRootPath = args['package-root'], 120 | shouldBatch = args['batch'], 121 | showPackageWarnings = 122 | args['show-package-warnings'] || args['package-warnings'], 123 | showSdkWarnings = args['show-sdk-warnings'] || args['warnings'], 124 | sourceFiles = args.rest, 125 | warningsAreFatal = args['fatal-warnings'], 126 | strongMode = args['strong']; 127 | 128 | /// Parse [args] into [CommandLineOptions] describing the specified 129 | /// analyzer options. In case of a format error, calls [printAndFail], which 130 | /// by default prints an error message to stderr and exits. 131 | static CommandLineOptions parse(List args, 132 | [printAndFail = printAndFail]) { 133 | CommandLineOptions options = _parse(args); 134 | // Check SDK. 135 | { 136 | // Infer if unspecified. 137 | if (options.dartSdkPath == null) { 138 | Directory sdkDir = getSdkDir(args); 139 | if (sdkDir != null) { 140 | options.dartSdkPath = sdkDir.path; 141 | } 142 | } 143 | 144 | var sdkPath = options.dartSdkPath; 145 | 146 | // Check that SDK is specified. 147 | if (sdkPath == null) { 148 | printAndFail('No Dart SDK found.'); 149 | } 150 | // Check that SDK is existing directory. 151 | if (!(new Directory(sdkPath)).existsSync()) { 152 | printAndFail('Invalid Dart SDK path: $sdkPath'); 153 | } 154 | } 155 | 156 | // Check package config. 157 | { 158 | if (options.packageRootPath != null && 159 | options.packageConfigPath != null) { 160 | printAndFail("Cannot specify both '--package-root' and '--packages."); 161 | } 162 | } 163 | 164 | // OK. Report deprecated options. 165 | if (options.enableNullAwareOperators) { 166 | stderr.writeln( 167 | "Info: Option '--enable-null-aware-operators' is no longer needed. Null aware operators are supported by default."); 168 | } 169 | 170 | return options; 171 | } 172 | 173 | static String _getVersion() { 174 | try { 175 | // This is relative to bin/snapshot, so ../.. 176 | String versionPath = 177 | Platform.script.resolve('../../version').toFilePath(); 178 | File versionFile = new File(versionPath); 179 | return versionFile.readAsStringSync().trim(); 180 | } catch (_) { 181 | // This happens when the script is not running in the context of an SDK. 182 | return ""; 183 | } 184 | } 185 | 186 | static CommandLineOptions _parse(List args) { 187 | args = args.expand((String arg) => arg.split('=')).toList(); 188 | var parser = new CommandLineParser() 189 | ..addFlag('batch', 190 | abbr: 'b', 191 | help: 'Read commands from standard input (for testing).', 192 | defaultsTo: false, 193 | negatable: false) 194 | ..addOption('dart-sdk', help: 'The path to the Dart SDK.') 195 | ..addOption('packages', 196 | help: 197 | 'Path to the package resolution configuration file, which supplies a mapping of package names to paths. This option cannot be used with --package-root.') 198 | ..addOption('package-root', 199 | abbr: 'p', 200 | help: 201 | 'Path to a package root directory (deprecated). This option cannot be used with --packages.') 202 | ..addOption('options', help: 'Path to an analysis options file.') 203 | ..addOption('format', 204 | help: 'Specifies the format in which errors are displayed.') 205 | ..addFlag('machine', 206 | help: 'Print errors in a format suitable for parsing (deprecated).', 207 | defaultsTo: false, 208 | negatable: false) 209 | ..addFlag('version', 210 | help: 'Print the analyzer version.', 211 | defaultsTo: false, 212 | negatable: false) 213 | ..addFlag('lints', 214 | help: 'Show lint results.', defaultsTo: false, negatable: false) 215 | ..addFlag('no-hints', 216 | help: 'Do not show hint results.', 217 | defaultsTo: false, 218 | negatable: false) 219 | ..addFlag('ignore-unrecognized-flags', 220 | help: 'Ignore unrecognized command line flags.', 221 | defaultsTo: false, 222 | negatable: false) 223 | ..addFlag('fatal-hints', 224 | help: 'Treat hints as fatal.', defaultsTo: false, negatable: false) 225 | ..addFlag('fatal-warnings', 226 | help: 'Treat non-type warnings as fatal.', 227 | defaultsTo: false, 228 | negatable: false) 229 | ..addFlag('package-warnings', 230 | help: 'Show warnings from package: imports.', 231 | defaultsTo: false, 232 | negatable: false) 233 | ..addFlag('show-package-warnings', 234 | help: 'Show warnings from package: imports (deprecated).', 235 | defaultsTo: false, 236 | negatable: false) 237 | ..addFlag('warnings', 238 | help: 'Show warnings from SDK imports.', 239 | defaultsTo: false, 240 | negatable: false) 241 | ..addFlag('show-sdk-warnings', 242 | help: 'Show warnings from SDK imports (deprecated).', 243 | defaultsTo: false, 244 | negatable: false) 245 | ..addFlag('help', 246 | abbr: 'h', 247 | help: 'Display this help message.', 248 | defaultsTo: false, 249 | negatable: false) 250 | ..addOption('url-mapping', 251 | help: '--url-mapping=libraryUri,/path/to/library.dart directs the ' 252 | 'analyzer to use "library.dart" as the source for an import ' 253 | 'of "libraryUri".', 254 | allowMultiple: true, 255 | splitCommas: false) 256 | // 257 | // Hidden flags. 258 | // 259 | ..addFlag('enable-async', 260 | help: 'Enable support for the proposed async feature.', 261 | defaultsTo: false, 262 | negatable: false, 263 | hide: true) 264 | ..addFlag('enable-enum', 265 | help: 'Enable support for the proposed enum feature.', 266 | defaultsTo: false, 267 | negatable: false, 268 | hide: true) 269 | ..addFlag('enable-null-aware-operators', 270 | help: 'Enable support for null-aware operators (DEP 9).', 271 | defaultsTo: false, 272 | negatable: false, 273 | hide: true) 274 | ..addFlag('enable-strict-call-checks', 275 | help: 'Fix issue 21938.', 276 | defaultsTo: false, 277 | negatable: false, 278 | hide: true) 279 | ..addFlag('enable-new-task-model', 280 | help: 'Ennable new task model.', 281 | defaultsTo: false, 282 | negatable: false, 283 | hide: true) 284 | ..addFlag('supermixin', 285 | help: 'Relax restrictions on mixins (DEP 34).', 286 | defaultsTo: false, 287 | negatable: false, 288 | hide: true) 289 | ..addFlag('log', 290 | help: 'Log additional messages and exceptions.', 291 | defaultsTo: false, 292 | negatable: false, 293 | hide: true) 294 | ..addFlag('enable_type_checks', 295 | help: 'Check types in constant evaluation.', 296 | defaultsTo: false, 297 | negatable: false, 298 | hide: true) 299 | ..addFlag('strong', 300 | help: 'Enable strong static checks (https://goo.gl/DqcBsw)'); 301 | 302 | try { 303 | // TODO(scheglov) https://code.google.com/p/dart/issues/detail?id=11061 304 | args = 305 | args.map((String arg) => arg == '-batch' ? '--batch' : arg).toList(); 306 | Map definedVariables = {}; 307 | var results = parser.parse(args, definedVariables); 308 | // Help requests. 309 | if (results['help']) { 310 | _showUsage(parser); 311 | exit(0); 312 | } 313 | // Batch mode and input files. 314 | if (results['batch']) { 315 | if (results.rest.isNotEmpty) { 316 | stderr.writeln('No source files expected in the batch mode.'); 317 | _showUsage(parser); 318 | exit(15); 319 | } 320 | } else if (results['version']) { 321 | print('$_binaryName version ${_getVersion()}'); 322 | exit(0); 323 | } else { 324 | if (results.rest.isEmpty) { 325 | _showUsage(parser); 326 | exit(15); 327 | } 328 | } 329 | return new CommandLineOptions._fromArgs(results, definedVariables); 330 | } on FormatException catch (e) { 331 | stderr.writeln(e.message); 332 | _showUsage(parser); 333 | exit(15); 334 | } 335 | } 336 | 337 | static _showUsage(parser) { 338 | stderr 339 | .writeln('Usage: $_binaryName [options...] '); 340 | stderr.writeln(parser.getUsage()); 341 | stderr.writeln(''); 342 | stderr.writeln( 343 | 'For more information, see http://www.dartlang.org/tools/analyzer.'); 344 | } 345 | } 346 | 347 | /// Commandline argument parser. 348 | /// 349 | /// TODO(pquitslund): when the args package supports ignoring unrecognized 350 | /// options/flags, this class can be replaced with a simple [ArgParser] 351 | /// instance. 352 | class CommandLineParser { 353 | final List _knownFlags; 354 | final bool _alwaysIgnoreUnrecognized; 355 | final ArgParser _parser; 356 | 357 | /// Creates a new command line parser. 358 | CommandLineParser({bool alwaysIgnoreUnrecognized: false}) 359 | : _knownFlags = [], 360 | _alwaysIgnoreUnrecognized = alwaysIgnoreUnrecognized, 361 | _parser = new ArgParser(allowTrailingOptions: true); 362 | 363 | ArgParser get parser => _parser; 364 | 365 | /// Defines a flag. 366 | /// See [ArgParser.addFlag()]. 367 | void addFlag(String name, 368 | {String abbr, 369 | String help, 370 | bool defaultsTo: false, 371 | bool negatable: true, 372 | void callback(bool value), 373 | bool hide: false}) { 374 | _knownFlags.add(name); 375 | _parser.addFlag(name, 376 | abbr: abbr, 377 | help: help, 378 | defaultsTo: defaultsTo, 379 | negatable: negatable, 380 | callback: callback, 381 | hide: hide); 382 | } 383 | 384 | /// Defines a value-taking option. 385 | /// See [ArgParser.addOption()]. 386 | void addOption(String name, 387 | {String abbr, 388 | String help, 389 | List allowed, 390 | Map allowedHelp, 391 | String defaultsTo, 392 | void callback(value), 393 | bool allowMultiple: false, 394 | bool splitCommas}) { 395 | _knownFlags.add(name); 396 | _parser.addOption(name, 397 | abbr: abbr, 398 | help: help, 399 | allowed: allowed, 400 | allowedHelp: allowedHelp, 401 | defaultsTo: defaultsTo, 402 | callback: callback, 403 | allowMultiple: allowMultiple, 404 | splitCommas: splitCommas); 405 | } 406 | 407 | /// Generates a string displaying usage information for the defined options. 408 | /// See [ArgParser.usage]. 409 | String getUsage() => _parser.usage; 410 | 411 | /// Parses [args], a list of command-line arguments, matches them against the 412 | /// flags and options defined by this parser, and returns the result. The 413 | /// values of any defined variables are captured in the given map. 414 | /// See [ArgParser]. 415 | ArgResults parse(List args, Map definedVariables) => 416 | _parser.parse( 417 | _filterUnknowns(parseDefinedVariables(args, definedVariables))); 418 | 419 | List parseDefinedVariables( 420 | List args, Map definedVariables) { 421 | int count = args.length; 422 | List remainingArgs = []; 423 | for (int i = 0; i < count; i++) { 424 | String arg = args[i]; 425 | if (arg == '--') { 426 | while (i < count) { 427 | remainingArgs.add(args[i++]); 428 | } 429 | } else if (arg.startsWith("-D")) { 430 | definedVariables[arg.substring(2)] = args[++i]; 431 | } else { 432 | remainingArgs.add(arg); 433 | } 434 | } 435 | return remainingArgs; 436 | } 437 | 438 | List _filterUnknowns(List args) { 439 | // Only filter args if the ignore flag is specified, or if 440 | // _alwaysIgnoreUnrecognized was set to true. 441 | if (_alwaysIgnoreUnrecognized || 442 | args.contains('--ignore-unrecognized-flags')) { 443 | //TODO(pquitslund): replace w/ the following once library skew issues are 444 | // sorted out 445 | //return args.where((arg) => !arg.startsWith('--') || 446 | // _knownFlags.contains(arg.substring(2))); 447 | 448 | // Filter all unrecognized flags and options. 449 | List filtered = []; 450 | for (int i = 0; i < args.length; ++i) { 451 | String arg = args[i]; 452 | if (arg.startsWith('--') && arg.length > 2) { 453 | String option = arg.substring(2); 454 | // strip the last '=value' 455 | int equalsOffset = option.lastIndexOf('='); 456 | if (equalsOffset != -1) { 457 | option = option.substring(0, equalsOffset); 458 | } 459 | // Check the option 460 | if (!_knownFlags.contains(option)) { 461 | //"eat" params by advancing to the next flag/option 462 | i = _getNextFlagIndex(args, i); 463 | } else { 464 | filtered.add(arg); 465 | } 466 | } else { 467 | filtered.add(arg); 468 | } 469 | } 470 | 471 | return filtered; 472 | } else { 473 | return args; 474 | } 475 | } 476 | 477 | int _getNextFlagIndex(args, i) { 478 | for (; i < args.length; ++i) { 479 | if (args[i].startsWith('--')) { 480 | return i; 481 | } 482 | } 483 | return i; 484 | } 485 | } 486 | -------------------------------------------------------------------------------- /lib/src/driver.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library analyzer_cli.src.driver; 6 | 7 | import 'dart:async'; 8 | import 'dart:convert'; 9 | import 'dart:io'; 10 | 11 | import 'package:analyzer/file_system/file_system.dart' as fileSystem; 12 | import 'package:analyzer/file_system/physical_file_system.dart'; 13 | import 'package:analyzer/plugin/options.dart'; 14 | import 'package:analyzer/source/analysis_options_provider.dart'; 15 | import 'package:analyzer/source/package_map_provider.dart'; 16 | import 'package:analyzer/source/package_map_resolver.dart'; 17 | import 'package:analyzer/source/pub_package_map_provider.dart'; 18 | import 'package:analyzer/source/sdk_ext.dart'; 19 | import 'package:analyzer/src/generated/constant.dart'; 20 | import 'package:analyzer/src/generated/engine.dart'; 21 | import 'package:analyzer/src/generated/error.dart'; 22 | import 'package:analyzer/src/generated/interner.dart'; 23 | import 'package:analyzer/src/generated/java_engine.dart'; 24 | import 'package:analyzer/src/generated/java_io.dart'; 25 | import 'package:analyzer/src/generated/sdk_io.dart'; 26 | import 'package:analyzer/src/generated/source.dart'; 27 | import 'package:analyzer/src/generated/source_io.dart'; 28 | import 'package:analyzer/src/services/lint.dart'; 29 | import 'package:analyzer/src/task/options.dart'; 30 | import 'package:analyzer_cli/src/analyzer_impl.dart'; 31 | import 'package:analyzer_cli/src/options.dart'; 32 | import 'package:linter/src/plugin/linter_plugin.dart'; 33 | import 'package:package_config/discovery.dart' as pkgDiscovery; 34 | import 'package:package_config/packages.dart' show Packages; 35 | import 'package:package_config/packages_file.dart' as pkgfile show parse; 36 | import 'package:package_config/src/packages_impl.dart' show MapPackages; 37 | import 'package:path/path.dart' as path; 38 | import 'package:plugin/plugin.dart'; 39 | import 'package:yaml/yaml.dart'; 40 | 41 | /// The maximum number of sources for which AST structures should be kept in the 42 | /// cache. 43 | const int _maxCacheSize = 512; 44 | 45 | /// Shared IO sink for standard error reporting. 46 | /// 47 | /// *Visible for testing.* 48 | StringSink errorSink = stderr; 49 | 50 | /// Shared IO sink for standard out reporting. 51 | /// 52 | /// *Visible for testing.* 53 | StringSink outSink = stdout; 54 | 55 | /// Test this option map to see if it specifies lint rules. 56 | bool containsLintRuleEntry(Map options) { 57 | var linterNode = options['linter']; 58 | return linterNode is YamlMap && linterNode.containsKey('rules'); 59 | } 60 | 61 | typedef ErrorSeverity _BatchRunnerHandler(List args); 62 | 63 | class Driver { 64 | /// The plugins that are defined outside the `analyzer_cli` package. 65 | List _userDefinedPlugins = []; 66 | 67 | /// Indicates whether the analyzer is running in batch mode. 68 | bool _isBatch; 69 | 70 | /// The context that was most recently created by a call to [_analyzeAll], or 71 | /// `null` if [_analyzeAll] hasn't been called yet. 72 | AnalysisContext _context; 73 | 74 | /// If [_context] is not `null`, the [CommandLineOptions] that guided its 75 | /// creation. 76 | CommandLineOptions _previousOptions; 77 | 78 | /// This Driver's current analysis context. 79 | /// 80 | /// *Visible for testing.* 81 | AnalysisContext get context => _context; 82 | 83 | /// Set the [plugins] that are defined outside the `analyzer_cli` package. 84 | void set userDefinedPlugins(List plugins) { 85 | _userDefinedPlugins = plugins == null ? [] : plugins; 86 | } 87 | 88 | /// Use the given command-line [args] to start this analysis driver. 89 | void start(List args) { 90 | StringUtilities.INTERNER = new MappedInterner(); 91 | 92 | _processPlugins(); 93 | 94 | // Parse commandline options. 95 | CommandLineOptions options = CommandLineOptions.parse(args); 96 | 97 | // Cache options of interest to inform analysis. 98 | _setupEnv(options); 99 | 100 | // Do analysis. 101 | if (_isBatch) { 102 | _BatchRunner.runAsBatch(args, (List args) { 103 | CommandLineOptions options = CommandLineOptions.parse(args); 104 | return _analyzeAll(options); 105 | }); 106 | } else { 107 | ErrorSeverity severity = _analyzeAll(options); 108 | // In case of error propagate exit code. 109 | if (severity == ErrorSeverity.ERROR) { 110 | exitCode = severity.ordinal; 111 | } 112 | } 113 | } 114 | 115 | /// Perform analysis according to the given [options]. 116 | ErrorSeverity _analyzeAll(CommandLineOptions options) { 117 | if (!options.machineFormat) { 118 | outSink.writeln("Analyzing ${options.sourceFiles}..."); 119 | } 120 | 121 | // Create a context, or re-use the previous one. 122 | try { 123 | _createAnalysisContext(options); 124 | } on _DriverError catch (error) { 125 | outSink.writeln(error.msg); 126 | return ErrorSeverity.ERROR; 127 | } 128 | 129 | // Add all the files to be analyzed en masse to the context. Skip any 130 | // files that were added earlier (whether explicitly or implicitly) to 131 | // avoid causing those files to be unnecessarily re-read. 132 | Set knownSources = _context.sources.toSet(); 133 | List sourcesToAnalyze = []; 134 | ChangeSet changeSet = new ChangeSet(); 135 | for (String sourcePath in options.sourceFiles) { 136 | sourcePath = sourcePath.trim(); 137 | // Check that file exists. 138 | if (!new File(sourcePath).existsSync()) { 139 | errorSink.writeln('File not found: $sourcePath'); 140 | exitCode = ErrorSeverity.ERROR.ordinal; 141 | //Fail fast; don't analyze more files 142 | return ErrorSeverity.ERROR; 143 | } 144 | // Check that file is Dart file. 145 | if (!AnalysisEngine.isDartFileName(sourcePath)) { 146 | errorSink.writeln('$sourcePath is not a Dart file'); 147 | exitCode = ErrorSeverity.ERROR.ordinal; 148 | // Fail fast; don't analyze more files. 149 | return ErrorSeverity.ERROR; 150 | } 151 | Source source = _computeLibrarySource(sourcePath); 152 | if (!knownSources.contains(source)) { 153 | changeSet.addedSource(source); 154 | } 155 | sourcesToAnalyze.add(source); 156 | } 157 | _context.applyChanges(changeSet); 158 | 159 | // Analyze the libraries. 160 | ErrorSeverity allResult = ErrorSeverity.NONE; 161 | var libUris = []; 162 | var parts = []; 163 | for (Source source in sourcesToAnalyze) { 164 | if (context.computeKindOf(source) == SourceKind.PART) { 165 | parts.add(source); 166 | continue; 167 | } 168 | ErrorSeverity status = _runAnalyzer(source, options); 169 | allResult = allResult.max(status); 170 | libUris.add(source.uri); 171 | } 172 | 173 | // Check that each part has a corresponding source in the input list. 174 | for (Source part in parts) { 175 | bool found = false; 176 | for (var lib in context.getLibrariesContaining(part)) { 177 | if (libUris.contains(lib.uri)) { 178 | found = true; 179 | } 180 | } 181 | if (!found) { 182 | errorSink.writeln("${part.fullName} is a part and cannot be analyzed."); 183 | errorSink.writeln("Please pass in a library that contains this part."); 184 | exitCode = ErrorSeverity.ERROR.ordinal; 185 | allResult = allResult.max(ErrorSeverity.ERROR); 186 | } 187 | } 188 | 189 | return allResult; 190 | } 191 | 192 | /// Determine whether the context created during a previous call to 193 | /// [_analyzeAll] can be re-used in order to analyze using [options]. 194 | bool _canContextBeReused(CommandLineOptions options) { 195 | // TODO(paulberry): add a command-line option that disables context re-use. 196 | if (_context == null) { 197 | return false; 198 | } 199 | if (options.packageRootPath != _previousOptions.packageRootPath) { 200 | return false; 201 | } 202 | if (options.packageConfigPath != _previousOptions.packageConfigPath) { 203 | return false; 204 | } 205 | if (!_equalMaps( 206 | options.definedVariables, _previousOptions.definedVariables)) { 207 | return false; 208 | } 209 | if (options.log != _previousOptions.log) { 210 | return false; 211 | } 212 | if (options.disableHints != _previousOptions.disableHints) { 213 | return false; 214 | } 215 | if (options.enableStrictCallChecks != 216 | _previousOptions.enableStrictCallChecks) { 217 | return false; 218 | } 219 | if (options.showPackageWarnings != _previousOptions.showPackageWarnings) { 220 | return false; 221 | } 222 | if (options.showSdkWarnings != _previousOptions.showSdkWarnings) { 223 | return false; 224 | } 225 | if (options.lints != _previousOptions.lints) { 226 | return false; 227 | } 228 | if (options.strongMode != _previousOptions.strongMode) { 229 | return false; 230 | } 231 | if (options.enableSuperMixins != _previousOptions.enableSuperMixins) { 232 | return false; 233 | } 234 | return true; 235 | } 236 | 237 | /// Decide on the appropriate policy for which files need to be fully parsed 238 | /// and which files need to be diet parsed, based on [options], and return an 239 | /// [AnalyzeFunctionBodiesPredicate] that implements this policy. 240 | AnalyzeFunctionBodiesPredicate _chooseDietParsingPolicy( 241 | CommandLineOptions options) { 242 | if (_isBatch) { 243 | // As analyzer is currently implemented, once a file has been diet 244 | // parsed, it can't easily be un-diet parsed without creating a brand new 245 | // context and losing caching. In batch mode, we can't predict which 246 | // files we'll need to generate errors and warnings for in the future, so 247 | // we can't safely diet parse anything. 248 | return (Source source) => true; 249 | } 250 | 251 | // Determine the set of packages requiring a full parse. Use null to 252 | // represent the case where all packages require a full parse. 253 | Set packagesRequiringFullParse; 254 | if (options.showPackageWarnings) { 255 | // We are showing warnings from all packages so all packages require a 256 | // full parse. 257 | packagesRequiringFullParse = null; 258 | } else { 259 | // We aren't showing warnings for dependent packages, but we may still 260 | // need to show warnings for "self" packages, so we need to do a full 261 | // parse in any package containing files mentioned on the command line. 262 | // TODO(paulberry): implement this. As a temporary workaround, we're 263 | // fully parsing all packages. 264 | packagesRequiringFullParse = null; 265 | } 266 | return (Source source) { 267 | if (source.uri.scheme == 'dart') { 268 | return options.showSdkWarnings; 269 | } else if (source.uri.scheme == 'package') { 270 | if (packagesRequiringFullParse == null) { 271 | return true; 272 | } else if (source.uri.pathSegments.length == 0) { 273 | // We should never see a URI like this, but fully parse it to be 274 | // safe. 275 | return true; 276 | } else { 277 | return packagesRequiringFullParse 278 | .contains(source.uri.pathSegments[0]); 279 | } 280 | } else { 281 | return true; 282 | } 283 | }; 284 | } 285 | 286 | /// Decide on the appropriate method for resolving URIs based on the given 287 | /// [options] and [customUrlMappings] settings, and return a 288 | /// [SourceFactory] that has been configured accordingly. 289 | SourceFactory _chooseUriResolutionPolicy(CommandLineOptions options) { 290 | Packages packages; 291 | Map> packageMap; 292 | UriResolver packageUriResolver; 293 | 294 | // Process options, caching package resolution details. 295 | if (options.packageConfigPath != null) { 296 | String packageConfigPath = options.packageConfigPath; 297 | Uri fileUri = new Uri.file(packageConfigPath); 298 | try { 299 | File configFile = new File.fromUri(fileUri).absolute; 300 | List bytes = configFile.readAsBytesSync(); 301 | Map map = pkgfile.parse(bytes, configFile.uri); 302 | packages = new MapPackages(map); 303 | packageMap = _getPackageMap(packages); 304 | } catch (e) { 305 | printAndFail( 306 | 'Unable to read package config data from $packageConfigPath: $e'); 307 | } 308 | } else if (options.packageRootPath != null) { 309 | packageMap = _PackageRootPackageMapBuilder 310 | .buildPackageMap(options.packageRootPath); 311 | 312 | JavaFile packageDirectory = new JavaFile(options.packageRootPath); 313 | packageUriResolver = new PackageUriResolver([packageDirectory]); 314 | } else { 315 | fileSystem.Resource cwd = 316 | PhysicalResourceProvider.INSTANCE.getResource('.'); 317 | 318 | // Look for .packages. 319 | packages = _discoverPackagespec(new Uri.directory(cwd.path)); 320 | 321 | if (packages != null) { 322 | packageMap = _getPackageMap(packages); 323 | } else { 324 | // Fall back to pub list-package-dirs. 325 | 326 | PubPackageMapProvider pubPackageMapProvider = 327 | new PubPackageMapProvider(PhysicalResourceProvider.INSTANCE, sdk); 328 | PackageMapInfo packageMapInfo = 329 | pubPackageMapProvider.computePackageMap(cwd); 330 | packageMap = packageMapInfo.packageMap; 331 | 332 | // Only create a packageUriResolver if pub list-package-dirs succeeded. 333 | // If it failed, that's not a problem; it simply means we have no way 334 | // to resolve packages. 335 | if (packageMapInfo.packageMap != null) { 336 | packageUriResolver = new PackageMapUriResolver( 337 | PhysicalResourceProvider.INSTANCE, packageMap); 338 | } 339 | } 340 | } 341 | 342 | // Now, build our resolver list. 343 | 344 | // 'dart:' URIs come first. 345 | List resolvers = [new DartUriResolver(sdk)]; 346 | 347 | // Next SdkExts. 348 | if (packageMap != null) { 349 | resolvers.add(new SdkExtUriResolver(packageMap)); 350 | } 351 | 352 | // Then package URIs. 353 | if (packageUriResolver != null) { 354 | resolvers.add(packageUriResolver); 355 | } 356 | 357 | // Finally files. 358 | resolvers.add(new FileUriResolver()); 359 | 360 | return new SourceFactory(resolvers, packages); 361 | } 362 | 363 | /// Convert the given [sourcePath] (which may be relative to the current 364 | /// working directory) to a [Source] object that can be fed to the analysis 365 | /// context. 366 | Source _computeLibrarySource(String sourcePath) { 367 | sourcePath = _normalizeSourcePath(sourcePath); 368 | JavaFile sourceFile = new JavaFile(sourcePath); 369 | Source source = sdk.fromFileUri(sourceFile.toURI()); 370 | if (source != null) { 371 | return source; 372 | } 373 | source = new FileBasedSource(sourceFile, sourceFile.toURI()); 374 | Uri uri = _context.sourceFactory.restoreUri(source); 375 | if (uri == null) { 376 | return source; 377 | } 378 | return new FileBasedSource(sourceFile, uri); 379 | } 380 | 381 | /// Create an analysis context that is prepared to analyze sources according 382 | /// to the given [options], and store it in [_context]. 383 | void _createAnalysisContext(CommandLineOptions options) { 384 | if (_canContextBeReused(options)) { 385 | return; 386 | } 387 | _previousOptions = options; 388 | // Choose a package resolution policy and a diet parsing policy based on 389 | // the command-line options. 390 | SourceFactory sourceFactory = _chooseUriResolutionPolicy(options); 391 | AnalyzeFunctionBodiesPredicate dietParsingPolicy = 392 | _chooseDietParsingPolicy(options); 393 | // Create a context using these policies. 394 | AnalysisContext context = AnalysisEngine.instance.createAnalysisContext(); 395 | 396 | context.sourceFactory = sourceFactory; 397 | 398 | Map definedVariables = options.definedVariables; 399 | if (!definedVariables.isEmpty) { 400 | DeclaredVariables declaredVariables = context.declaredVariables; 401 | definedVariables.forEach((String variableName, String value) { 402 | declaredVariables.define(variableName, value); 403 | }); 404 | } 405 | 406 | if (options.log) { 407 | AnalysisEngine.instance.logger = new StdLogger(); 408 | } 409 | 410 | // Set context options. 411 | AnalysisOptionsImpl contextOptions = new AnalysisOptionsImpl(); 412 | contextOptions.cacheSize = _maxCacheSize; 413 | contextOptions.hint = !options.disableHints; 414 | contextOptions.enableStrictCallChecks = options.enableStrictCallChecks; 415 | contextOptions.enableSuperMixins = options.enableSuperMixins; 416 | contextOptions.analyzeFunctionBodiesPredicate = dietParsingPolicy; 417 | contextOptions.generateImplicitErrors = options.showPackageWarnings; 418 | contextOptions.generateSdkErrors = options.showSdkWarnings; 419 | contextOptions.lint = options.lints; 420 | contextOptions.strongMode = options.strongMode; 421 | context.analysisOptions = contextOptions; 422 | _context = context; 423 | 424 | // Process analysis options file (and notify all interested parties). 425 | _processAnalysisOptions(options, context); 426 | } 427 | 428 | /// Return discovered packagespec, or `null` if none is found. 429 | Packages _discoverPackagespec(Uri root) { 430 | try { 431 | Packages packages = pkgDiscovery.findPackagesFromFile(root); 432 | if (packages != Packages.noPackages) { 433 | return packages; 434 | } 435 | } catch (_) { 436 | // Ignore and fall through to null. 437 | } 438 | 439 | return null; 440 | } 441 | 442 | fileSystem.File _getOptionsFile(CommandLineOptions options) { 443 | fileSystem.File file; 444 | String filePath = options.analysisOptionsFile; 445 | if (filePath != null) { 446 | file = PhysicalResourceProvider.INSTANCE.getFile(filePath); 447 | if (!file.exists) { 448 | printAndFail('Options file not found: $filePath', 449 | exitCode: ErrorSeverity.ERROR.ordinal); 450 | } 451 | } else { 452 | filePath = AnalysisEngine.ANALYSIS_OPTIONS_FILE; 453 | file = PhysicalResourceProvider.INSTANCE.getFile(filePath); 454 | } 455 | return file; 456 | } 457 | 458 | Map> _getPackageMap(Packages packages) { 459 | if (packages == null) { 460 | return null; 461 | } 462 | 463 | Map> folderMap = 464 | new Map>(); 465 | packages.asMap().forEach((String packagePath, Uri uri) { 466 | folderMap[packagePath] = [ 467 | PhysicalResourceProvider.INSTANCE.getFolder(path.fromUri(uri)) 468 | ]; 469 | }); 470 | return folderMap; 471 | } 472 | 473 | void _processAnalysisOptions( 474 | CommandLineOptions options, AnalysisContext context) { 475 | fileSystem.File file = _getOptionsFile(options); 476 | List optionsProcessors = 477 | AnalysisEngine.instance.optionsPlugin.optionsProcessors; 478 | try { 479 | AnalysisOptionsProvider analysisOptionsProvider = 480 | new AnalysisOptionsProvider(); 481 | Map optionMap = 482 | analysisOptionsProvider.getOptionsFromFile(file); 483 | optionsProcessors.forEach( 484 | (OptionsProcessor p) => p.optionsProcessed(context, optionMap)); 485 | 486 | // Fill in lint rule defaults in case lints are enabled and rules are 487 | // not specified in an options file. 488 | if (options.lints && !containsLintRuleEntry(optionMap)) { 489 | setLints(context, linterPlugin.contributedRules); 490 | } 491 | 492 | // Ask engine to further process options. 493 | if (optionMap != null) { 494 | configureContextOptions(context, optionMap); 495 | } 496 | } on Exception catch (e) { 497 | optionsProcessors.forEach((OptionsProcessor p) => p.onError(e)); 498 | } 499 | } 500 | 501 | void _processPlugins() { 502 | List plugins = []; 503 | plugins.add(linterPlugin); 504 | plugins.addAll(_userDefinedPlugins); 505 | AnalysisEngine.instance.userDefinedPlugins = plugins; 506 | 507 | // This ensures that AE extension manager processes plugins. 508 | AnalysisEngine.instance.taskManager; 509 | } 510 | 511 | /// Analyze a single source. 512 | ErrorSeverity _runAnalyzer(Source source, CommandLineOptions options) { 513 | int startTime = currentTimeMillis(); 514 | AnalyzerImpl analyzer = 515 | new AnalyzerImpl(_context, source, options, startTime); 516 | var errorSeverity = analyzer.analyzeSync(); 517 | if (errorSeverity == ErrorSeverity.ERROR) { 518 | exitCode = errorSeverity.ordinal; 519 | } 520 | if (options.warningsAreFatal && errorSeverity == ErrorSeverity.WARNING) { 521 | exitCode = errorSeverity.ordinal; 522 | } 523 | return errorSeverity; 524 | } 525 | 526 | void _setupEnv(CommandLineOptions options) { 527 | // In batch mode, SDK is specified on the main command line rather than in 528 | // the command lines sent to stdin. So process it before deciding whether 529 | // to activate batch mode. 530 | if (sdk == null) { 531 | sdk = new DirectoryBasedDartSdk(new JavaFile(options.dartSdkPath)); 532 | } 533 | _isBatch = options.shouldBatch; 534 | } 535 | 536 | /// Perform a deep comparison of two string maps. 537 | static bool _equalMaps(Map m1, Map m2) { 538 | if (m1.length != m2.length) { 539 | return false; 540 | } 541 | for (String key in m1.keys) { 542 | if (!m2.containsKey(key) || m1[key] != m2[key]) { 543 | return false; 544 | } 545 | } 546 | return true; 547 | } 548 | 549 | /// Convert [sourcePath] into an absolute path. 550 | static String _normalizeSourcePath(String sourcePath) => 551 | path.normalize(new File(sourcePath).absolute.path); 552 | } 553 | 554 | /// Provides a framework to read command line options from stdin and feed them 555 | /// to a callback. 556 | class _BatchRunner { 557 | /// Run the tool in 'batch' mode, receiving command lines through stdin and 558 | /// returning pass/fail status through stdout. This feature is intended for 559 | /// use in unit testing. 560 | static void runAsBatch(List sharedArgs, _BatchRunnerHandler handler) { 561 | outSink.writeln('>>> BATCH START'); 562 | Stopwatch stopwatch = new Stopwatch(); 563 | stopwatch.start(); 564 | int testsFailed = 0; 565 | int totalTests = 0; 566 | ErrorSeverity batchResult = ErrorSeverity.NONE; 567 | // Read line from stdin. 568 | Stream cmdLine = 569 | stdin.transform(UTF8.decoder).transform(new LineSplitter()); 570 | cmdLine.listen((String line) { 571 | // Maybe finish. 572 | if (line.isEmpty) { 573 | var time = stopwatch.elapsedMilliseconds; 574 | outSink.writeln( 575 | '>>> BATCH END (${totalTests - testsFailed}/$totalTests) ${time}ms'); 576 | exitCode = batchResult.ordinal; 577 | } 578 | // Prepare aruments. 579 | var args; 580 | { 581 | var lineArgs = line.split(new RegExp('\\s+')); 582 | args = new List(); 583 | args.addAll(sharedArgs); 584 | args.addAll(lineArgs); 585 | args.remove('-b'); 586 | args.remove('--batch'); 587 | } 588 | // Analyze single set of arguments. 589 | try { 590 | totalTests++; 591 | ErrorSeverity result = handler(args); 592 | bool resultPass = result != ErrorSeverity.ERROR; 593 | if (!resultPass) { 594 | testsFailed++; 595 | } 596 | batchResult = batchResult.max(result); 597 | // Write stderr end token and flush. 598 | errorSink.writeln('>>> EOF STDERR'); 599 | String resultPassString = resultPass ? 'PASS' : 'FAIL'; 600 | outSink.writeln( 601 | '>>> TEST $resultPassString ${stopwatch.elapsedMilliseconds}ms'); 602 | } catch (e, stackTrace) { 603 | errorSink.writeln(e); 604 | errorSink.writeln(stackTrace); 605 | errorSink.writeln('>>> EOF STDERR'); 606 | outSink.writeln('>>> TEST CRASH'); 607 | } 608 | }); 609 | } 610 | } 611 | 612 | class _DriverError implements Exception { 613 | String msg; 614 | _DriverError(this.msg); 615 | } 616 | 617 | /// [SdkExtUriResolver] needs a Map from package name to folder. In the case 618 | /// that the analyzer is invoked with a --package-root option, we need to 619 | /// manually create this mapping. Given [packageRootPath], 620 | /// [_PackageRootPackageMapBuilder] creates a simple mapping from package name 621 | /// to full path on disk (resolving any symbolic links). 622 | class _PackageRootPackageMapBuilder { 623 | static Map> buildPackageMap( 624 | String packageRootPath) { 625 | var packageRoot = new Directory(packageRootPath); 626 | if (!packageRoot.existsSync()) { 627 | throw new _DriverError( 628 | 'Package root directory ($packageRootPath) does not exist.'); 629 | } 630 | var packages = packageRoot.listSync(followLinks: false); 631 | var result = new Map>(); 632 | for (var package in packages) { 633 | var packageName = path.basename(package.path); 634 | var realPath = package.resolveSymbolicLinksSync(); 635 | result[packageName] = [ 636 | PhysicalResourceProvider.INSTANCE.getFolder(realPath) 637 | ]; 638 | } 639 | return result; 640 | } 641 | } 642 | --------------------------------------------------------------------------------