diff --git a/bin/linter.dart b/bin/linter.dart index 52082ffb5..da111447c 100644 --- a/bin/linter.dart +++ b/bin/linter.dart @@ -2,10 +2,12 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:async'; import 'dart:io'; import 'dart:math' as math; import 'package:analyzer/error/error.dart'; +import 'package:analyzer/file_system/physical_file_system.dart'; import 'package:analyzer/src/generated/engine.dart'; import 'package:analyzer/src/lint/config.dart'; import 'package:analyzer/src/lint/io.dart'; @@ -16,8 +18,8 @@ import 'package:linter/src/analyzer.dart'; import 'package:linter/src/formatter.dart'; import 'package:linter/src/rules.dart'; -void main(List args) { - runLinter(args, new LinterOptions()); +Future main(List args) async { + await runLinter(args, new LinterOptions()); } const processFileFailedExitCode = 65; @@ -27,7 +29,7 @@ const unableToProcessExitCode = 64; String getRoot(List paths) => paths.length == 1 && new Directory(paths[0]).existsSync() ? paths[0] : null; -isLinterErrorCode(int code) => +bool isLinterErrorCode(int code) => code == unableToProcessExitCode || code == processFileFailedExitCode; void printUsage(ArgParser parser, IOSink out, [String error]) { @@ -44,7 +46,7 @@ For more information, see https://github.com/dart-lang/linter '''); } -void runLinter(List args, LinterOptions initialLintOptions) { +Future runLinter(List args, LinterOptions initialLintOptions) async { // Force the rule registry to be populated. registerLintRules(); @@ -150,7 +152,7 @@ void runLinter(List args, LinterOptions initialLintOptions) { lintOptions ..packageConfigPath = packageConfigFile - ..visitTransitiveClosure = options['visit-transitive-closure']; + ..resourceProvider = PhysicalResourceProvider.INSTANCE; List filesToLint = []; for (var path in options.rest) { @@ -166,7 +168,7 @@ void runLinter(List args, LinterOptions initialLintOptions) { try { final timer = new Stopwatch()..start(); - List errors = linter.lintFiles(filesToLint); + List errors = await linter.lintFiles(filesToLint); timer.stop(); if (errors.length > 0) { diff --git a/pubspec.yaml b/pubspec.yaml index c72f36f9e..f373b4e3e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ homepage: https://github.com/dart-lang/linter environment: sdk: '>=1.12.0 <2.0.0-dev.infinity' dependencies: - analyzer: ^0.30.0 + analyzer: ^0.31.0-alpha.0 args: '>=0.12.1 <0.14.0' glob: ^1.0.3 meta: ^1.0.2 @@ -20,5 +20,5 @@ dev_dependencies: matcher: ^0.12.0 mockito: ^1.0.0 path: '>=0.9.0 <2.0.0' - test: ^0.12.0 + test: ^0.12.24+1 unscripted: ^0.6.2 diff --git a/test/_data/directives_ordering/export_directives_after_import_directives/bad.dart b/test/_data/directives_ordering/export_directives_after_import_directives/bad.dart index 76fa4174e..124c56ee0 100644 --- a/test/_data/directives_ordering/export_directives_after_import_directives/bad.dart +++ b/test/_data/directives_ordering/export_directives_after_import_directives/bad.dart @@ -1,6 +1,7 @@ // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +library dummy_lib; import 'dummy.dart'; diff --git a/test/engine_test.dart b/test/engine_test.dart index 6268ae851..b3bb66d43 100644 --- a/test/engine_test.dart +++ b/test/engine_test.dart @@ -2,6 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:async'; import 'dart:io'; import 'package:analyzer/dart/ast/ast.dart' show AstNode, AstVisitor; @@ -26,7 +27,7 @@ main() { } /// Linter engine tests -void defineLinterEngineTests() { +Future defineLinterEngineTests() { group('engine', () { group('reporter', () { _test(String label, String expected, report(PrintingReporter r)) { @@ -147,39 +148,39 @@ void defineLinterEngineTests() { exitCode = 0; errorSink = stderr; }); - test('smoke', () { + test('smoke', () async { FileSystemEntity firstRuleTest = new Directory(ruleDir).listSync().firstWhere((f) => isDartFile(f)); - dartlint.main([firstRuleTest.path]); + await dartlint.main([firstRuleTest.path]); expect(dartlint.isLinterErrorCode(exitCode), isFalse); }); - test('no args', () { - dartlint.main([]); + test('no args', () async { + await dartlint.main([]); expect(exitCode, equals(dartlint.unableToProcessExitCode)); }); - test('help', () { - dartlint.main(['-h']); + test('help', () async { + await dartlint.main(['-h']); // Help shouldn't generate an error code expect(dartlint.isLinterErrorCode(exitCode), isFalse); }); - test('unknown arg', () { - dartlint.main(['-XXXXX']); + test('unknown arg', () async { + await dartlint.main(['-XXXXX']); expect(exitCode, equals(dartlint.unableToProcessExitCode)); }); - test('custom sdk path', () { + test('custom sdk path', () async { // Smoke test to ensure a custom sdk path doesn't sink the ship FileSystemEntity firstRuleTest = new Directory(ruleDir).listSync().firstWhere((f) => isDartFile(f)); var sdk = getSdkPath(); - dartlint.main(['--dart-sdk', sdk, firstRuleTest.path]); + await dartlint.main(['--dart-sdk', sdk, firstRuleTest.path]); expect(dartlint.isLinterErrorCode(exitCode), isFalse); }); - test('custom package root', () { + test('custom package root', () async { // Smoke test to ensure a custom package root doesn't sink the ship FileSystemEntity firstRuleTest = new Directory(ruleDir).listSync().firstWhere((f) => isDartFile(f)); var packageDir = new Directory('.').path; - dartlint.main(['--package-root', packageDir, firstRuleTest.path]); + await dartlint.main(['--package-root', packageDir, firstRuleTest.path]); expect(dartlint.isLinterErrorCode(exitCode), isFalse); }); }); diff --git a/test/integration_test.dart b/test/integration_test.dart index 10dce291f..a0191a369 100644 --- a/test/integration_test.dart +++ b/test/integration_test.dart @@ -34,8 +34,8 @@ defineTests() { exitCode = 0; }); group('config', () { - test('excludes', () { - dartlint + test('excludes', () async { + await dartlint .main(['test/_data/p2', '-c', 'test/_data/p2/lintconfig.yaml']); expect(exitCode, 1); expect( @@ -43,15 +43,15 @@ defineTests() { stringContainsInOrder( ['4 files analyzed, 1 issue found (2 filtered), in'])); }); - test('overrrides', () { - dartlint + test('overrrides', () async { + await dartlint .main(['test/_data/p2', '-c', 'test/_data/p2/lintconfig2.yaml']); expect(exitCode, 0); expect(collectingOut.trim(), stringContainsInOrder(['4 files analyzed, 0 issues found, in'])); }); - test('default', () { - dartlint.main(['test/_data/p2']); + test('default', () async { + await dartlint.main(['test/_data/p2']); expect(exitCode, 1); expect(collectingOut.trim(), stringContainsInOrder(['4 files analyzed, 3 issues found, in'])); @@ -66,8 +66,8 @@ defineTests() { collectingOut.buffer.clear(); outSink = currentOut; }); - test('bad pubspec', () { - dartlint.main(['test/_data/p3', 'test/_data/p3/_pubpspec.yaml']); + test('bad pubspec', () async { + await dartlint.main(['test/_data/p3', 'test/_data/p3/_pubpspec.yaml']); expect(collectingOut.trim(), startsWith('1 file analyzed, 0 issues found, in')); }); @@ -80,10 +80,10 @@ defineTests() { collectingOut.buffer.clear(); outSink = currentOut; }); - test('no warnings due to bad canonicalization', () { + test('no warnings due to bad canonicalization', () async { var packagesFilePath = new File('test/_data/p4/_packages').absolute.path; - dartlint.runLinter(['--packages', packagesFilePath, 'test/_data/p4'], + await dartlint.runLinter(['--packages', packagesFilePath, 'test/_data/p4'], new LinterOptions([])); expect(collectingOut.trim(), startsWith('3 files analyzed, 0 issues found, in')); @@ -103,9 +103,9 @@ defineTests() { exitCode = 0; }); group('.packages', () { - test('basic', () { + test('basic', () async { // Requires .packages to analyze cleanly. - dartlint + await dartlint .main(['test/_data/p5', '--packages', 'test/_data/p5/_packages']); // Should have 0 issues. expect(exitCode, 0); @@ -127,8 +127,8 @@ defineTests() { }); // https://github.com/dart-lang/linter/issues/246 - test('overrides across libraries', () { - dartlint.main( + test('overrides across libraries', () async { + await dartlint.main( ['test/_data/overridden_fields', '--rules', 'overridden_fields']); expect(exitCode, 1); expect( @@ -151,9 +151,9 @@ defineTests() { exitCode = 0; }); - test('close sinks', () { + test('close sinks', () async { var packagesFilePath = new File('.packages').absolute.path; - dartlint.main([ + await dartlint.main([ '--packages', packagesFilePath, 'test/_data/close_sinks', @@ -183,8 +183,8 @@ defineTests() { exitCode = 0; }); - test('cancel subscriptions', () { - dartlint.main([ + test('cancel subscriptions', () async { + await dartlint.main([ 'test/_data/cancel_subscriptions', '--rules=cancel_subscriptions' ]); @@ -212,9 +212,9 @@ defineTests() { exitCode = 0; }); - test('dart_directives_go_first', () { + test('dart_directives_go_first', () async { var packagesFilePath = new File('.packages').absolute.path; - dartlint.main([ + await dartlint.main([ '--packages', packagesFilePath, 'test/_data/directives_ordering/dart_directives_go_first', @@ -236,9 +236,9 @@ defineTests() { ])); }); - test('package_directives_before_relative', () { + test('package_directives_before_relative', () async { var packagesFilePath = new File('.packages').absolute.path; - dartlint.main([ + await dartlint.main([ '--packages', packagesFilePath, 'test/_data/directives_ordering/package_directives_before_relative', @@ -260,9 +260,9 @@ defineTests() { ])); }); - test('third_party_package_directives_before_own', () { + test('third_party_package_directives_before_own', () async { var packagesFilePath = new File('.packages').absolute.path; - dartlint.main([ + await dartlint.main([ '--packages', packagesFilePath, 'test/_data/directives_ordering/third_party_package_directives_before_own', @@ -284,15 +284,14 @@ defineTests() { ])); }); - test('export_directives_after_import_directives', () { + test('export_directives_after_import_directives', () async { var packagesFilePath = new File('.packages').absolute.path; - dartlint.main([ + await dartlint.main([ '--packages', packagesFilePath, 'test/_data/directives_ordering/export_directives_after_import_directives', '--rules=directives_ordering' ]); - expect(exitCode, 1); expect( collectingOut.trim(), stringContainsInOrder([ @@ -302,11 +301,12 @@ defineTests() { "export 'dummy2.dart'; // LINT", '5 files analyzed, 2 issues found, in' ])); + expect(exitCode, 1); }); - test('sort_directive_sections_alphabetically', () { + test('sort_directive_sections_alphabetically', () async { var packagesFilePath = new File('.packages').absolute.path; - dartlint.main([ + await dartlint.main([ '--packages', packagesFilePath, 'test/_data/directives_ordering/sort_directive_sections_alphabetically', @@ -344,9 +344,9 @@ defineTests() { ])); }); - test('lint_one_node_no_more_than_once', () { + test('lint_one_node_no_more_than_once', () async { var packagesFilePath = new File('.packages').absolute.path; - dartlint.main([ + await dartlint.main([ '--packages', packagesFilePath, 'test/_data/directives_ordering/lint_one_node_no_more_than_once', @@ -376,8 +376,8 @@ defineTests() { exitCode = 0; }); - test('only throw errors', () { - dartlint.main( + test('only throw errors', () async { + await dartlint.main( ['test/_data/only_throw_errors', '--rules=only_throw_errors']); expect(exitCode, 1); expect( @@ -406,8 +406,8 @@ defineTests() { exitCode = 0; }); - test('only throw errors', () { - dartlint.runLinter([ + test('only throw errors', () async { + await dartlint.runLinter([ 'test/_data/always_require_non_null_named_parameters', '--rules=always_require_non_null_named_parameters' ], new LinterOptions()..enableAssertInitializer = true); @@ -432,8 +432,8 @@ defineTests() { exitCode = 0; }); - test('only throw errors', () { - dartlint.runLinter([ + test('only throw errors', () async { + await dartlint.runLinter([ 'test/_data/prefer_asserts_in_initializer_lists', '--rules=prefer_asserts_in_initializer_lists' ], new LinterOptions()..enableAssertInitializer = true); @@ -458,8 +458,8 @@ defineTests() { exitCode = 0; }); - test('only throw errors', () { - dartlint.runLinter([ + test('only throw errors', () async { + await dartlint.runLinter([ 'test/_data/prefer_const_constructors_in_immutables', '--rules=prefer_const_constructors_in_immutables' ], new LinterOptions()..enableAssertInitializer = true); diff --git a/test/mock_sdk.dart b/test/mock_sdk.dart index 4b2e61e37..35ae3680d 100644 --- a/test/mock_sdk.dart +++ b/test/mock_sdk.dart @@ -13,6 +13,7 @@ import 'package:analyzer/src/generated/sdk.dart' show DartSdk, SdkLibrary; import 'package:analyzer/src/generated/source.dart' show DartUriResolver, Source, SourceFactory; import 'package:analyzer/src/summary/idl.dart'; +import 'package:analyzer/src/summary/summary_file_builder.dart'; /// Mock SDK for testing purposes. class MockSdk implements DartSdk { @@ -268,13 +269,15 @@ class HtmlElement {} LIB_HTML, ]; - final resource.MemoryResourceProvider provider = - new resource.MemoryResourceProvider(); + /// The cached linked bundle of the SDK. + PackageBundle _bundle; + + final resource.MemoryResourceProvider provider; /// The [AnalysisContextImpl] which is used for all of the sources. AnalysisContextImpl _analysisContext; - MockSdk() { + MockSdk(this.provider) { LIBRARIES.forEach((SdkLibrary library) { if (library is _MockSdkLibrary) { provider.newFile(library.path, library.content); @@ -301,6 +304,32 @@ class HtmlElement {} return _analysisContext; } + @override + PackageBundle getLinkedBundle() { + if (_bundle == null) { + resource.File summaryFile = + provider.getFile(provider.convertPath('/lib/_internal/spec.sum')); + List bytes; + if (summaryFile.exists) { + bytes = summaryFile.readAsBytesSync(); + } else { + bytes = _computeLinkedBundleBytes(); + } + _bundle = new PackageBundle.fromBuffer(bytes); + } + return _bundle; + } + + /// Compute the bytes of the linked bundle associated with this SDK. + List _computeLinkedBundleBytes() { + List librarySources = sdkLibraries + .map((SdkLibrary library) => mapDartUri(library.shortName)) + .toList(); + return new SummaryBuilder( + librarySources, context, context.analysisOptions.strongMode) + .build(); + } + @override List get sdkLibraries => LIBRARIES; @@ -310,38 +339,38 @@ class HtmlElement {} UnimplementedError get unimplemented => new UnimplementedError(); @override - List get uris { - List uris = []; - for (SdkLibrary library in LIBRARIES) { - uris.add(library.shortName); - } - return uris; - } + List get uris => + sdkLibraries.map((SdkLibrary library) => library.shortName).toList(); + @override Source fromFileUri(Uri uri) { - String filePath = uri.path; + + String filePath = provider.pathContext.fromUri(uri); + String libPath = '/lib'; - if (!filePath.startsWith('$libPath/')) { + if (!filePath.startsWith(provider.convertPath('$libPath/'))) { return null; } - for (SdkLibrary library in LIBRARIES) { - String libraryPath = library.path; - if (filePath.replaceAll('\\', '/') == libraryPath) { + for (SdkLibrary library in sdkLibraries) { + String libraryPath = provider.convertPath(library.path); + if (filePath == libraryPath) { try { - resource.File file = provider.getResource(uri.path); + resource.File file = provider.getResource(filePath); Uri dartUri = Uri.parse(library.shortName); return file.createSource(dartUri); } catch (exception) { return null; } } - if (filePath.startsWith('$libraryPath/')) { - String pathInLibrary = filePath.substring(libraryPath.length + 1); - String path = '${library.shortName}/$pathInLibrary'; + String libraryRootPath = provider.pathContext.dirname(libraryPath) + + provider.pathContext.separator; + if (filePath.startsWith(libraryRootPath)) { + String pathInLibrary = filePath.substring(libraryRootPath.length); + String uriStr = '${library.shortName}/$pathInLibrary'; try { - resource.File file = provider.getResource(uri.path); - Uri dartUri = new Uri(scheme: 'dart', path: path); + resource.File file = provider.getResource(filePath); + Uri dartUri = Uri.parse(uriStr); return file.createSource(dartUri); } catch (exception) { return null; @@ -351,9 +380,6 @@ class HtmlElement {} return null; } - @override - PackageBundle getLinkedBundle() => null; - @override SdkLibrary getSdkLibrary(String dartUri) { // getSdkLibrary() is only used to determine whether a library is internal @@ -377,7 +403,7 @@ class HtmlElement {} String path = uriToPath[dartUri]; if (path != null) { - resource.File file = provider.getResource(path); + resource.File file = provider.getResource(provider.convertPath(path)); Uri uri = new Uri(scheme: 'dart', path: dartUri.substring(5)); return file.createSource(uri); } diff --git a/test/rule_test.dart b/test/rule_test.dart index e9ddfc8d8..7e2336e8f 100644 --- a/test/rule_test.dart +++ b/test/rule_test.dart @@ -2,14 +2,19 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:async'; import 'dart:io'; import 'package:analyzer/error/error.dart'; +import 'package:analyzer/file_system/file_system.dart' as file_system; +import 'package:analyzer/file_system/memory_file_system.dart'; +import 'package:analyzer/file_system/physical_file_system.dart'; import 'package:analyzer/src/generated/engine.dart'; import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/lint/io.dart'; import 'package:analyzer/src/lint/linter.dart'; import 'package:analyzer/src/lint/registry.dart'; +import 'package:analyzer/src/util/absolute_path.dart'; import 'package:linter/src/analyzer.dart'; import 'package:linter/src/ast.dart'; import 'package:linter/src/formatter.dart'; @@ -61,8 +66,7 @@ defineRuleUnitTests() { [ Uri.parse('package:foo/src/bar.dart'), Uri.parse('package:foo/src/baz/bar.dart') - ] - ..forEach((uri) { + ]..forEach((uri) { test(uri.toString(), () { expect(isPackage(uri), isTrue); }); @@ -71,8 +75,7 @@ defineRuleUnitTests() { Uri.parse('foo/bar.dart'), Uri.parse('src/bar.dart'), Uri.parse('dart:async') - ] - ..forEach((uri) { + ]..forEach((uri) { test(uri.toString(), () { expect(isPackage(uri), isFalse); }); @@ -98,8 +101,7 @@ defineRuleUnitTests() { [ Uri.parse('package:foo/src/bar.dart'), Uri.parse('package:foo/src/baz/bar.dart') - ] - ..forEach((uri) { + ]..forEach((uri) { test(uri.toString(), () { expect(isImplementation(uri), isTrue); }); @@ -387,7 +389,7 @@ testEachInt(Iterable values, bool f(int s), Matcher m) { testRule(String ruleName, File file, {bool debug: false}) { registerLintRules(); - test('$ruleName', () { + test('$ruleName', () async { if (!file.existsSync()) { throw new Exception('No rule found defined at: ${file.path}'); } @@ -410,13 +412,19 @@ testRule(String ruleName, File file, {bool debug: false}) { return; } + MemoryResourceProvider memoryResourceProvider = + new MemoryResourceProvider(); + TestResourceProvider resourceProvider = + new TestResourceProvider(memoryResourceProvider); + LinterOptions options = new LinterOptions([rule]) - ..mockSdk = new MockSdk() + ..mockSdk = new MockSdk(memoryResourceProvider) + ..resourceProvider = resourceProvider ..packageRootPath = '.'; DartLinter driver = new DartLinter(options); - Iterable lints = driver.lintFiles([file]); + Iterable lints = await driver.lintFiles([file]); List actual = []; lints.forEach((AnalysisErrorInfo info) { @@ -445,6 +453,57 @@ testRule(String ruleName, File file, {bool debug: false}) { }); } +class TestResourceProvider implements file_system.ResourceProvider { + MemoryResourceProvider memoryResourceProvider; + + TestResourceProvider(this.memoryResourceProvider); + + PhysicalResourceProvider get physicalResourceProvider => + PhysicalResourceProvider.INSTANCE; + + @override + AbsolutePathContext get absolutePathContext => + memoryResourceProvider.absolutePathContext; + + @override + file_system.File getFile(String path) { + file_system.File file = memoryResourceProvider.getFile(path); + return file.exists ? file : physicalResourceProvider.getFile(path); + } + + @override + file_system.Folder getFolder(String path) { + file_system.Folder folder = memoryResourceProvider.getFolder(path); + return folder.exists ? folder : physicalResourceProvider.getFolder(path); + } + + @override + Future> getModificationTimes(List sources) { + //If this gets tripped, we know to implement it! :) + throw new StateError('Unexpected call to getModificationTimes'); + } + + @override + file_system.Resource getResource(String path) { + file_system.Resource resource = memoryResourceProvider.getResource(path); + return resource.exists + ? resource + : physicalResourceProvider.getResource(path); + } + + @override + file_system.Folder getStateLocation(String pluginId) { + file_system.Folder folder = + memoryResourceProvider.getStateLocation(pluginId); + return folder.exists + ? folder + : physicalResourceProvider.getStateLocation(pluginId); + } + + @override + p.Context get pathContext => memoryResourceProvider.pathContext; +} + class Annotation { final int column; final int length;