From 178dbdf2c35f64ad60a8edfc451e80a836542087 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Tue, 13 Jan 2015 16:05:14 -0800 Subject: [PATCH 0001/2942] Initial commit. --- README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000000..e69de29bb2d From c1d0b8d6d7fb0aeba98a515548da79568a8a4802 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Tue, 13 Jan 2015 16:13:36 -0800 Subject: [PATCH 0002/2942] LICENSE, AUTHORS, boilerplate --- .gitignore | 4 ++++ AUTHORS | 10 ++++++++++ LICENSE | 27 +++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 LICENSE diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..7de946ea35f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +.pub +packages +build diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000000..e5a94c05b6c --- /dev/null +++ b/AUTHORS @@ -0,0 +1,10 @@ +# Names should be added to this file with this pattern: +# +# For individuals: +# Name +# +# For organizations: +# Organization +# +Google Inc. <*@google.com> + diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..3c80eb5ecaa --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright 2015, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + From 05deae810c4c5a5dcd6a2458504429051bf65507 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Tue, 13 Jan 2015 17:08:29 -0800 Subject: [PATCH 0003/2942] Code review settings --- codereview.settings | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 codereview.settings diff --git a/codereview.settings b/codereview.settings new file mode 100644 index 00000000000..771c0263a4f --- /dev/null +++ b/codereview.settings @@ -0,0 +1,3 @@ +CODE_REVIEW_SERVER: https://chromiumcodereview.appspot.com/ +VIEW_VC: https://github.com/dart-lang/dart_lint/commit/ +CC_LIST: reviews@dartlang.org From 0ce4230508ce5dc672b1fd1e7ef460768f2924b2 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Thu, 15 Jan 2015 13:09:33 -0800 Subject: [PATCH 0004/2942] Basic code review settings. Code review settings BUG= R=rnystrom@google.com Review URL: https://chromiumcodereview.appspot.com//851823004 --- codereview.settings | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 codereview.settings diff --git a/codereview.settings b/codereview.settings new file mode 100644 index 00000000000..771c0263a4f --- /dev/null +++ b/codereview.settings @@ -0,0 +1,3 @@ +CODE_REVIEW_SERVER: https://chromiumcodereview.appspot.com/ +VIEW_VC: https://github.com/dart-lang/dart_lint/commit/ +CC_LIST: reviews@dartlang.org From 9a57deada4919c78e7371030ce862dc1945c5e78 Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Thu, 29 Jan 2015 13:27:28 -0800 Subject: [PATCH 0005/2942] Create .travis.yaml --- .travis.yaml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .travis.yaml diff --git a/.travis.yaml b/.travis.yaml new file mode 100644 index 00000000000..9c3a39f9762 --- /dev/null +++ b/.travis.yaml @@ -0,0 +1,2 @@ +language: dart +script: ./tool/travis.sh From ab73b09bd17265a2be952b192e7153caffb67a40 Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Thu, 29 Jan 2015 13:33:32 -0800 Subject: [PATCH 0006/2942] Create travis.sh --- tool/travis.sh | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tool/travis.sh diff --git a/tool/travis.sh b/tool/travis.sh new file mode 100644 index 00000000000..35dcb8f71c7 --- /dev/null +++ b/tool/travis.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +# for details. All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +# Fast fail the script on failures. +set -e + +# Verify that the libraries are error free. +#dartanalyzer --fatal-warnings \ +# bin/dartpad_server.dart \ +# lib/dartpad_server.dart \ +# test/all.dart + +# Run the tests. +#dart test/all.dart + +# Install dart_coveralls; gather and send coverage data. +if [ "$REPO_TOKEN" ]; then + pub global activate dart_coveralls + pub global run dart_coveralls report \ + --token $REPO_TOKEN \ + --retry 2 \ + --exclude-test-files \ + test/all.dart +fi From 9fbb72fcbdb8f2cc5822a2e861b3e2d185ff9331 Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Thu, 29 Jan 2015 13:37:50 -0800 Subject: [PATCH 0007/2942] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index e69de29bb2d..f49c9689f0e 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,3 @@ +# dart_lint + +[![Build Status](https://travis-ci.org/dart-lang/dart_lint.svg)](https://travis-ci.org/dart-lang/dart_lint) From f3704edb77c2de6ad2975d3c3c8948e2b7f77d54 Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Thu, 29 Jan 2015 13:39:33 -0800 Subject: [PATCH 0008/2942] Rename .travis.yaml to .travis.yml --- .travis.yaml => .travis.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .travis.yaml => .travis.yml (100%) diff --git a/.travis.yaml b/.travis.yml similarity index 100% rename from .travis.yaml rename to .travis.yml From c20e8a07108d86d82aa9f92d2426622d94c2d229 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Thu, 29 Jan 2015 13:46:21 -0800 Subject: [PATCH 0009/2942] Added exec bit to travis.sh. --- tool/travis.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 tool/travis.sh diff --git a/tool/travis.sh b/tool/travis.sh old mode 100644 new mode 100755 From 39f9e6452c05c4dc064a55b0c79036185ca1824f Mon Sep 17 00:00:00 2001 From: pquitslund Date: Mon, 2 Feb 2015 09:51:15 -0800 Subject: [PATCH 0010/2942] Analysis driver WIP. --- lib/src/analysis.dart | 628 ++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 10 + 2 files changed, 638 insertions(+) create mode 100644 lib/src/analysis.dart create mode 100644 pubspec.yaml diff --git a/lib/src/analysis.dart b/lib/src/analysis.dart new file mode 100644 index 00000000000..71ca9b775dd --- /dev/null +++ b/lib/src/analysis.dart @@ -0,0 +1,628 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library analysis; + +import 'dart:async'; +import 'dart:collection'; +import 'dart:io'; + +import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/file_system/file_system.dart' show Folder; +import 'package:analyzer/file_system/physical_file_system.dart'; +import 'package:analyzer/options.dart'; +import 'package:analyzer/source/package_map_provider.dart'; +import 'package:analyzer/source/package_map_resolver.dart'; +import 'package:analyzer/source/pub_package_map_provider.dart'; +import 'package:analyzer/src/analyzer_impl.dart'; +import 'package:analyzer/src/error_formatter.dart'; +import 'package:analyzer/src/generated/constant.dart'; +import 'package:analyzer/src/generated/element.dart'; +import 'package:analyzer/src/generated/engine.dart'; +import 'package:analyzer/src/generated/java_core.dart' show JavaSystem; +import 'package:analyzer/src/generated/java_io.dart'; +import 'package:analyzer/src/generated/sdk.dart'; +import 'package:analyzer/src/generated/sdk_io.dart'; +import 'package:analyzer/src/generated/source.dart'; +import 'package:analyzer/src/generated/source_io.dart'; +import 'package:grinder/grinder.dart' as grinder; + + +/// A refactored fork of [AnalyzerImpl] with an eye towards easing +/// use and extension. +/// +/// Still very much a WIP. +class AnalysisDriver { + + /// The maximum number of sources for which AST structures should be kept + /// in the cache. + // TODO: move to DriverOptions + static const _MAX_CACHE_SIZE = 512; + + final DriverOptions _options; + + final int _startTime; + AnalysisContext _context; + + Source _librarySource; + SourceFactory _sourceFactory; + /// All [Source]s referenced by the analyzed library. + final Set _sources = new Set(); + + /// All [AnalysisErrorInfo]s in the analyzed library. + final List errorInfos = new List(); + + /// [HashMap] between sources and analysis error infos. + final HashMap sourceErrorsMap = + new HashMap(); + + AnalysisDriver.forFile(File file, DriverOptions options, [DartSdk dartSdk]) + : this.forPath(file.absolute.path, options, dartSdk); + + AnalysisDriver.forPath(String sourcePath, DriverOptions options, + [DartSdk dartSdk]) + : this.forSource( + _createSource(sourcePath, dartSdk, options), + options, + dartSdk); + + AnalysisDriver.forSource(this._librarySource, this._options, + [DartSdk dartSdk]) + : _startTime = _currentTimeInMillis() { + //TODO: is already called when redirected from forPath + _setupSdk(dartSdk, _options); + } + + /// Returns the maximal [ErrorSeverity] of the recorded errors. + ErrorSeverity get maxErrorSeverity { + var status = ErrorSeverity.NONE; + for (AnalysisErrorInfo errorInfo in errorInfos) { + for (AnalysisError error in errorInfo.errors) { + if (!isDesiredError(error)) { + continue; + } + var severity = _computeSeverity(error, _options.enableTypeChecks); + status = status.max(severity); + } + } + return status; + } + + /// Default implementation contributes a package URI and package map + /// resolvers (in addition to the predefined dart URI and file URI resolvers). + /// Override to specialize. + void addResolvers(List resolvers) { + if (_options.packageRootPath != null) { + JavaFile packageDirectory = new JavaFile(_options.packageRootPath); + resolvers.add(new PackageUriResolver([packageDirectory])); + } else { + PubPackageMapProvider pubPackageMapProvider = + new PubPackageMapProvider(PhysicalResourceProvider.INSTANCE, sdk); + PackageMapInfo packageMapInfo = pubPackageMapProvider.computePackageMap( + PhysicalResourceProvider.INSTANCE.getResource('.')); + Map> packageMap = packageMapInfo.packageMap; + if (packageMap != null) { + resolvers.add( + new PackageMapUriResolver(PhysicalResourceProvider.INSTANCE, packageMap)); + } + } + } + + /// Treats the [sourcePath] as the top level library and analyzes it using an + /// asynchronous algorithm over the analysis engine. + void analyzeAsync() { + _setupForAnalysis(); + _analyzeAsync(); + } + + /// Treats the [sourcePath] as the top level library and analyzes it using a + /// synchronous algorithm over the analysis engine. If [printMode] is `0`, + + /// then no error or performance information is printed. If [printMode] is `1`, + /// then both will be printed. If [printMode] is `2`, then only performance + /// information will be printed, and it will be marked as being for a cold VM. + ErrorSeverity analyzeSync({int printMode: 1}) { + _setupForAnalysis(); + return _analyzeSync(printMode); + } + + /// By default creates a logger that reports to standard out and error. + Logger createLogger() => new StdLogger(_options.log); + + bool isDesiredError(AnalysisError error) { + if (error.errorCode.type == ErrorType.TODO) { + return false; + } + if (_computeSeverity(error, _options.enableTypeChecks) == + ErrorSeverity.INFO && + _options.disableHints) { + return false; + } + return true; + } + + void _addCompilationUnitSource(CompilationUnitElement unit, + Set libraries, Set units) { + if (unit == null || units.contains(unit)) { + return; + } + units.add(unit); + _sources.add(unit.source); + } + + void _addLibrarySources(LibraryElement library, Set libraries, + Set units) { + if (library == null || !libraries.add(library)) { + return; + } + // may be skip library + { + UriKind uriKind = library.source.uriKind; + // Optionally skip package: libraries. + if (!_options.showPackageWarnings && uriKind == UriKind.PACKAGE_URI) { + return; + } + // Optionally skip SDK libraries. + if (!_options.showSdkWarnings && uriKind == UriKind.DART_URI) { + return; + } + } + // add compilation units + _addCompilationUnitSource( + library.definingCompilationUnit, + libraries, + units); + for (CompilationUnitElement child in library.parts) { + _addCompilationUnitSource(child, libraries, units); + } + // add referenced libraries + for (LibraryElement child in library.importedLibraries) { + _addLibrarySources(child, libraries, units); + } + for (LibraryElement child in library.exportedLibraries) { + _addLibrarySources(child, libraries, units); + } + } + + /// The async version of the analysis + void _analyzeAsync() { + new Future(_context.performAnalysisTask).then((AnalysisResult result) { + List notices = result.changeNotices; + if (result.hasMoreWork) { + // There is more work, record the set of sources, and then call self + // again to perform next task + for (ChangeNotice notice in notices) { + _sources.add(notice.source); + sourceErrorsMap[notice.source] = notice; + } + return _analyzeAsync(); + } + + // There are not any more tasks, set error code and print performance + // numbers. + // + // prepare errors + sourceErrorsMap.forEach((k, v) { + errorInfos.add(sourceErrorsMap[k]); + }); + + // print errors and performance numbers + _printErrorsAndPerf(); + + // compute max severity and set exitCode + ErrorSeverity status = maxErrorSeverity; + if (status == ErrorSeverity.WARNING && _options.warningsAreFatal) { + status = ErrorSeverity.ERROR; + } + exitCode = status.ordinal; + }).catchError((ex, st) { + AnalysisEngine.instance.logger.logError("$ex\n$st"); + }); + } + + /// The sync version of analysis. + ErrorSeverity _analyzeSync(int printMode) { + // don't try to analyze parts + if (_context.computeKindOf(_librarySource) == SourceKind.PART) { + print("Only libraries can be analyzed."); + print("${_librarySource.shortName} is a part and can not be analyzed."); + return ErrorSeverity.ERROR; + } + // resolve library + var libraryElement = _context.computeLibraryElement(_librarySource); + // prepare source and errors + _prepareSources(libraryElement); + _prepareErrors(); + + // print errors and performance numbers + if (printMode == 1) { + _printErrorsAndPerf(); + } else if (printMode == 2) { + _printColdPerf(); + } + + // compute max severity and set exitCode + ErrorSeverity status = maxErrorSeverity; + if (status == ErrorSeverity.WARNING && _options.warningsAreFatal) { + status = ErrorSeverity.ERROR; + } + return status; + } + + List _getResolvers() { + List resolvers = [ + new DartUriResolver(sdk), + new FileUriResolver()]; + addResolvers(resolvers); + return resolvers; + } + + void _prepareAnalysisContext(Source source) { + var resolvers = _getResolvers(); + _sourceFactory = new SourceFactory(resolvers); + _context = AnalysisEngine.instance.createAnalysisContext(); + _context.sourceFactory = _sourceFactory; + Map definedVariables = _options.definedVariables; + if (!definedVariables.isEmpty) { + DeclaredVariables declaredVariables = _context.declaredVariables; + definedVariables.forEach((String variableName, String value) { + declaredVariables.define(variableName, value); + }); + } + + AnalysisEngine.instance.logger = createLogger(); + + // set options for context + AnalysisOptionsImpl contextOptions = new AnalysisOptionsImpl(); + _setOptions(contextOptions); + _context.analysisOptions = contextOptions; + + // Create and add a ChangeSet + ChangeSet changeSet = new ChangeSet(); + changeSet.addedSource(source); + _context.applyChanges(changeSet); + } + + /// Fills [errorInfos] using [_sources]. + void _prepareErrors() { + for (Source source in _sources) { + _context.computeErrors(source); + var sourceErrors = _context.getErrors(source); + errorInfos.add(sourceErrors); + } + } + + /// Fills [_sources]. + void _prepareSources(LibraryElement library) { + var units = new Set(); + var libraries = new Set(); + _addLibrarySources(library, libraries, units); + } + + _printColdPerf() { + // print cold VM performance numbers + int totalTime = JavaSystem.currentTimeMillis() - _startTime; + int ioTime = PerformanceStatistics.io.result; + int scanTime = PerformanceStatistics.scan.result; + int parseTime = PerformanceStatistics.parse.result; + int resolveTime = PerformanceStatistics.resolve.result; + int errorsTime = PerformanceStatistics.errors.result; + int hintsTime = PerformanceStatistics.hints.result; + stdout.writeln("io-cold:$ioTime"); + stdout.writeln("scan-cold:$scanTime"); + stdout.writeln("parse-cold:$parseTime"); + stdout.writeln("resolve-cold:$resolveTime"); + stdout.writeln("errors-cold:$errorsTime"); + stdout.writeln("hints-cold:$hintsTime"); + stdout.writeln("other-cold:${totalTime + - (ioTime + scanTime + parseTime + resolveTime + errorsTime + hintsTime)}"); + stdout.writeln("total-cold:$totalTime"); + } + + _printErrorsAndPerf() { + // The following is a hack. We currently print out to stderr to ensure that + // when in batch mode we print to stderr, this is because the prints from + // batch are made to stderr. The reason that options.shouldBatch isn't used + // is because when the argument flags are constructed in BatchRunner and + // passed in from batch mode which removes the batch flag to prevent the + // "cannot have the batch flag and source file" error message. + IOSink sink = _options.machineFormat ? stderr : stdout; + + // print errors + ErrorFormatter formatter = + new ErrorFormatter(sink, new _OptionsWrapper(_options), isDesiredError); + formatter.formatErrors(errorInfos); + + // print performance numbers + if (_options.perf || _options.warmPerf) { + int totalTime = JavaSystem.currentTimeMillis() - _startTime; + int ioTime = PerformanceStatistics.io.result; + int scanTime = PerformanceStatistics.scan.result; + int parseTime = PerformanceStatistics.parse.result; + int resolveTime = PerformanceStatistics.resolve.result; + int errorsTime = PerformanceStatistics.errors.result; + int hintsTime = PerformanceStatistics.hints.result; + stdout.writeln("io:$ioTime"); + stdout.writeln("scan:$scanTime"); + stdout.writeln("parse:$parseTime"); + stdout.writeln("resolve:$resolveTime"); + stdout.writeln("errors:$errorsTime"); + stdout.writeln("hints:$hintsTime"); + stdout.writeln("other:${totalTime + - (ioTime + scanTime + parseTime + resolveTime + errorsTime + hintsTime)}"); + stdout.writeln("total:$totalTime"); + } + } + + void _setOptions(AnalysisOptionsImpl analysisOptions) { + analysisOptions.cacheSize = _MAX_CACHE_SIZE; + analysisOptions.hint = !_options.disableHints; + analysisOptions.lint = _options.enableLints; + } + + /// Setup local fields such as the analysis context for analysis. + void _setupForAnalysis() { + _sources.clear(); + errorInfos.clear(); + _prepareAnalysisContext(_librarySource); + } + + /// Compute the severity of the error; however, if + /// enableTypeChecks] is false, then de-escalate checked-mode compile time + /// errors to a severity of [ErrorSeverity.INFO]. + static ErrorSeverity _computeSeverity(AnalysisError error, + bool enableTypeChecks) { + if (!enableTypeChecks && + error.errorCode.type == ErrorType.CHECKED_MODE_COMPILE_TIME_ERROR) { + return ErrorSeverity.INFO; + } + return error.errorCode.errorSeverity; + } + + static Source _createSource(String sourcePath, DartSdk dartSdk, + DriverOptions options) { + _setupSdk(dartSdk, options); + JavaFile sourceFile = new JavaFile(_normalizeSourcePath(sourcePath)); + Uri uri = _getUri(sourceFile); + return new FileBasedSource.con2(uri, sourceFile); + } + + static int _currentTimeInMillis() => + new DateTime.now().millisecondsSinceEpoch; + + static JavaFile _getPackageDirectoryFor(JavaFile sourceFile) { + // we are going to ask parent file, so get absolute path + sourceFile = sourceFile.getAbsoluteFile(); + // look in the containing directories + JavaFile dir = sourceFile.getParentFile(); + while (dir != null) { + JavaFile packagesDir = new JavaFile.relative(dir, "packages"); + if (packagesDir.exists()) { + return packagesDir; + } + dir = dir.getParentFile(); + } + // not found + return null; + } + + /// Returns the [Uri] for the given input file. + /// Usually it is a `file:` [Uri], but if [file] is located in the `lib` + /// directory of the [sdk], then returns a `dart:` [Uri]. + static Uri _getUri(JavaFile file) { + // may be file in SDK + { + Source source = sdk.fromFileUri(file.toURI()); + if (source != null) { + return source.uri; + } + } + // some generic file + return file.toURI(); + } + + /// Convert [sourcePath] into an absolute path. + static String _normalizeSourcePath(String sourcePath) => + new File(sourcePath).absolute.path; + + static void _setupSdk(DartSdk dartSdk, DriverOptions options) { + if (dartSdk != null) { + if (dartSdk is DirectoryBasedDartSdk) { + sdk = dartSdk; + } else { + sdk = new SdkWrapper(dartSdk); + } + } else if (options.dartSdkPath != null) { + sdk = new DirectoryBasedDartSdk(new JavaFile(options.dartSdkPath)); + } else if (sdk == null) { + // In case no SDK has been specified, fall back to inferring it + // TODO: pass args to grinder + Directory sdkDir = grinder.getSdkDir(); + sdk = new DirectoryBasedDartSdk(new JavaFile(sdkDir.path)); + } + } + +} + + +class DriverOptions { + + /// The path to the dart SDK. + String dartSdkPath; + + /// A table mapping the names of defined variables to their values. + Map definedVariables = {}; + + /// Whether to display version information. + bool displayVersion; + + /// Whether to report hints. + bool disableHints = false; + + /// Whether to enable lints. + bool enableLints = false; + + /// Whether to treat type mismatches found during constant evaluation as + /// errors. + bool enableTypeChecks = false; + + /// Whether to ignore unrecognized flags. + bool ignoreUnrecognizedFlags; + + /// Whether to log additional analysis messages and exceptions. + bool log = false; + + /// Whether to use machine format for error display. + bool machineFormat = false; + + /// The path to the package root. + String packageRootPath; + + /// Whether to show performance statistics. + bool perf = false; + + /// Whether to show package: warnings. + bool showPackageWarnings = false; + + /// Whether to show SDK warnings. + bool showSdkWarnings = false; + + /// Whether to show both cold and hot performance statistics. + bool warmPerf = false; + + /// Whether to treat warnings as fatal. + bool warningsAreFatal = false; +} + + +class SdkWrapper implements DirectoryBasedDartSdk { + + DartSdk dartSdk; + + SdkWrapper(this.dartSdk); + + @override + AnalysisContext get context => dartSdk.context; + + @override + JavaFile get dart2JsExecutable => null; + + @override + JavaFile get dartFmtExecutable => null; + + @override + String get dartiumBinaryName => null; + + @override + JavaFile get dartiumExecutable => null; + + @override + JavaFile get dartiumWorkingDirectory => null; + + @override + JavaFile get directory => null; + + @override + JavaFile get docDirectory => null; + + @override + bool get hasDocumentation => false; + + @override + bool get isDartiumInstalled => null; + + @override + JavaFile get libraryDirectory => null; + + @override + JavaFile get pubExecutable => null; + + @override + List get sdkLibraries => dartSdk.sdkLibraries; + + @override + String get sdkVersion => dartSdk.sdkVersion; + + @override + List get uris => dartSdk.uris; + + @override + String get vmBinaryName => null; + + @override + JavaFile get vmExecutable => null; + + @override + Source fromFileUri(Uri uri) => dartSdk.fromFileUri(uri); + + @override + JavaFile getDartiumWorkingDirectory(JavaFile installDir) => null; + + @override + JavaFile getDocFileFor(String libraryName) => null; + + @override + SdkLibrary getSdkLibrary(String dartUri) => null; + + @override + LibraryMap initialLibraryMap(bool useDart2jsPaths) => null; + + @override + Source mapDartUri(String dartUri) => dartSdk.mapDartUri(dartUri); +} + +class _OptionsWrapper implements CommandLineOptions { + + final DriverOptions driverOptions; + + _OptionsWrapper(this.driverOptions); + + @override + String get dartSdkPath => null; + + @override + Map get definedVariables => null; + + @override + bool get disableHints => false; + + @override + bool get displayVersion => driverOptions.displayVersion; + + @override + bool get enableTypeChecks => false; + + @override + bool get ignoreUnrecognizedFlags => driverOptions.ignoreUnrecognizedFlags; + + @override + bool get log => driverOptions.log; + + @override + bool get machineFormat => driverOptions.machineFormat; + + @override + String get packageRootPath => driverOptions.packageRootPath; + + @override + bool get perf => false; // driverOptions.perf; + + @override + bool get shouldBatch => false; + + @override + bool get showPackageWarnings => driverOptions.showPackageWarnings; + + @override + bool get showSdkWarnings => driverOptions.showSdkWarnings; + + @override + List get sourceFiles => []; + + @override + bool get warmPerf => false; // driverOptions.warmPerf; + + @override + bool get warningsAreFatal => false; +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000000..09dadbd844b --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,10 @@ +name: dart_lint +version: 0.0.1 +author: Dart Team +description: Style linter for Dart. +homepage: https://github.com/dart-lang/dart_lint +dependencies: + analyzer: '0.23.0-dev.16' + grinder: '>=0.6.0 <0.7.0' +dev_dependencies: + unittest: '>=0.11.0 <0.12.0' \ No newline at end of file From 7a398b9e31de1227b5c10e3855182507a51cd93b Mon Sep 17 00:00:00 2001 From: pquitslund Date: Mon, 2 Feb 2015 09:58:54 -0800 Subject: [PATCH 0011/2942] Basic lint driver jumping off point. --- lib/src/linter.dart | 187 ++++++++++++++++++++++++++++++++++++++++++++ lib/src/rules.dart | 66 ++++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 lib/src/linter.dart create mode 100644 lib/src/rules.dart diff --git a/lib/src/linter.dart b/lib/src/linter.dart new file mode 100644 index 00000000000..e3782f94c92 --- /dev/null +++ b/lib/src/linter.dart @@ -0,0 +1,187 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library linter_impl; + +import 'dart:io'; + +import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/src/generated/engine.dart'; +import 'package:analyzer/src/generated/java_engine.dart'; +import 'package:analyzer/src/generated/parser.dart'; +import 'package:analyzer/src/services/lint.dart'; +import 'package:dart_lint/src/analysis.dart'; +import 'package:dart_lint/src/rules.dart'; + + +/// Dart source linter. +abstract class DartLinter { + + /// Creates a new linter for dart source. + factory DartLinter([LinterOptions options]) => new SourceLinter(options); + + disableRule(String ruleName); + + enableRule(String ruleName); + + Iterable lintFile(File sourceFile); + + Iterable lintPath(String sourcePath); + + registerRule(String ruleName, Linter lintRule); +} + +class LintDriver extends AnalysisDriver { + + static LinterOptions get standardOptions => new LinterOptions(); + + factory LintDriver.forFile(File file, Iterable linters, + [LinterOptions options]) { + _registerLinters(linters); + return new LintDriver._forFile( + file, + options != null ? options : standardOptions); + } + + factory LintDriver.forPath(String path, Iterable linters, + [LinterOptions options]) { + _registerLinters(linters); + return new LintDriver._forPath( + path, + options != null ? options : standardOptions); + } + + LintDriver._forFile(File file, [LinterOptions options]) + : super.forFile(file, options); + + LintDriver._forPath(String path, [LinterOptions options]) + : super.forPath(path, options); + + bool isDesiredError(AnalysisError error) => + error.errorCode.type == ErrorType.LINT; + + Iterable lint() { + analyzeSync(); + return errorInfos; + } + + static void _registerLinters(Iterable linters) { + LintGenerator.LINTERS.clear(); + linters.forEach((l) => LintGenerator.LINTERS.add(l)); + } +} + +/// Thrown when an error occurs in linting. +class LinterException implements Exception { + + /// A message describing the error. + final String message; + + /// Creates a new LinterException with an optional error [message]. + const LinterException([this.message = 'LinterException']); + + LinterException.forError(List errors) + : message = _createMessage(errors); + + String toString() => '$message'; + + //TODO: revisit + static String _createMessage(errors) { + var errorCode = errors[0].errorCode; + var phase = errorCode is ParserErrorCode ? 'parsing' : 'scanning'; + return 'An error occured while $phase (${errorCode.name}).'; + } +} + +/// Linter options. +class LinterOptions extends DriverOptions { + bool get enableLints => true; +} + +abstract class Reporter { + void exception(LinterException exception); + void warn(String message); +} + +/// Linter implementation +class SourceLinter implements DartLinter, AnalysisErrorListener { + + final errors = []; + LinterOptions options; + final Reporter reporter; + RuleRegistry registry; + + SourceLinter(this.options, [this.reporter = const StdIoReporter(), + this.registry]) { + if (options == null) { + options = new LinterOptions(); + } + if (registry == null) { + registry = new RuleRegistry(reporter); + } + } + + @override + disableRule(String ruleName) { + registry.disable(ruleName); + } + + @override + enableRule(String ruleName) { + registry.enable(ruleName); + } + + @override + Iterable lintFile(File sourceFile) => + new LintDriver.forFile(sourceFile, registry.enabledLints, options).lint(); + + @override + Iterable lintPath(String sourcePath) => + new LintDriver.forPath(sourcePath, registry.enabledLints, options).lint(); + + @override + onError(AnalysisError error) { + errors.add(error); + } + + @override + registerRule(String ruleName, Linter lintRule) { + registry.registerLinter(ruleName, lintRule); + } +} + +class StdIoReporter implements Reporter, Logger { + + const StdIoReporter(); + + @override + void exception(LinterException exception) { + print('EXCEPTION: $exception'); + } + + @override + void logError(String message, [CaughtException exception]) { + print('ERROR: $message'); + } + + @override + void logError2(String message, Object exception) { + print('ERROR: $message'); + } + + @override + void logInformation(String message, [CaughtException exception]) { + print('INFO: $message'); + } + + @override + void logInformation2(String message, Object exception) { + print('INFO: $message'); + } + + @override + void warn(String message) { + print('WARN: $message'); + } +} diff --git a/lib/src/rules.dart b/lib/src/rules.dart new file mode 100644 index 00000000000..9e2f0c59d14 --- /dev/null +++ b/lib/src/rules.dart @@ -0,0 +1,66 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library rules; + +import 'package:analyzer/src/services/lint.dart'; +import 'package:dart_lint/src/linter.dart'; +//import 'package:dart_lint/src/rules/super_goes_last.dart'; + +/// Map of contributed lint rules. +final Map _rules = { + //'super_goes_last': new SuperGoesLastLint() +}; + +class Rule { + + /// Whether this rule is enabled in the current rule set. + bool enabled; + /// A uniquely identifying name for this rule. + final String ruleName; + /// The associated linter. + final Linter linter; + + Rule(this.ruleName, this.linter, {this.enabled: false}); +} + +class RuleRegistry { + + final Reporter reporter; + final Map _ruleMap = {}; + + RuleRegistry(this.reporter) { + // Register rules + _rules.forEach((n, l) => registerLinter(n, l)); + } + + /// An empty registry for testing + RuleRegistry.empty(this.reporter); + + Iterable get enabledLints => + _ruleMap.values.where((Rule r) => r.enabled).map((Rule r) => r.linter); + + void disable(String ruleName) { + if (_ruleMap[ruleName] == null) { + reporter.warn("No rule registered to '$ruleName', cannot disable"); + } else { + _ruleMap[ruleName].enabled = false; + } + } + + void enable(String ruleName) { + if (_ruleMap[ruleName] == null) { + reporter.warn("No rule registered to '$ruleName', cannot enable"); + } else { + _ruleMap[ruleName].enabled = true; + } + } + + void registerLinter(String name, Linter linter) { + if (_ruleMap[name] != null) { + reporter.warn("Multiple linter rules registered to name '$name'"); + } + _ruleMap[name] = new Rule(name, linter); + } +} From 271638478156fe205fb5fade8fff261cb86ad176 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Mon, 2 Feb 2015 10:12:16 -0800 Subject: [PATCH 0012/2942] Migrate cache size setting to driver options. --- lib/src/analysis.dart | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/src/analysis.dart b/lib/src/analysis.dart index 71ca9b775dd..c2157d9d904 100644 --- a/lib/src/analysis.dart +++ b/lib/src/analysis.dart @@ -35,11 +35,6 @@ import 'package:grinder/grinder.dart' as grinder; /// Still very much a WIP. class AnalysisDriver { - /// The maximum number of sources for which AST structures should be kept - /// in the cache. - // TODO: move to DriverOptions - static const _MAX_CACHE_SIZE = 512; - final DriverOptions _options; final int _startTime; @@ -356,7 +351,7 @@ class AnalysisDriver { } void _setOptions(AnalysisOptionsImpl analysisOptions) { - analysisOptions.cacheSize = _MAX_CACHE_SIZE; + analysisOptions.cacheSize = _options.cacheSize; analysisOptions.hint = !_options.disableHints; analysisOptions.lint = _options.enableLints; } @@ -448,6 +443,10 @@ class AnalysisDriver { class DriverOptions { + /// The maximum number of sources for which AST structures should be kept + /// in the cache. The default is 512. + int cacheSize = 512; + /// The path to the dart SDK. String dartSdkPath; From c35a4be1d57b20f99774b056c5d76739d4c76e94 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Mon, 2 Feb 2015 10:57:07 -0800 Subject: [PATCH 0013/2942] Lint tests kickstart. --- test/lint_test.dart | 212 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 test/lint_test.dart diff --git a/test/lint_test.dart b/test/lint_test.dart new file mode 100644 index 00000000000..3113bc9551b --- /dev/null +++ b/test/lint_test.dart @@ -0,0 +1,212 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library dart_lint.test.lint_test; + +import 'dart:io'; + +import 'package:analyzer/src/generated/ast.dart'; +import 'package:analyzer/src/generated/engine.dart'; +import 'package:analyzer/src/generated/error.dart'; +import 'package:analyzer/src/generated/source_io.dart'; +import 'package:analyzer/src/services/lint.dart'; +import 'package:dart_lint/src/linter.dart'; +import 'package:dart_lint/src/rules.dart'; +import 'package:path/path.dart' as p; +import 'package:unittest/compact_vm_config.dart'; +import 'package:unittest/unittest.dart'; + + +const ruleDir = 'test/rules'; + + +main() { + // Tidy up the unittest output. + filterStacks = true; + formatStacks = true; + useCompactVMConfiguration(); + + defineSanityTests(); + defineLinterEngineTests(); + //defineRuleTests(); +} + + +void defineLinterEngineTests() { + group('linter engine tests', () { + group('registry', () { + test('duplicate rules', () { + var registry = new MockRegistry(); + registry + ..registerLinter('r1', new MockLinter()) + ..registerLinter('r1', new MockLinter()) + ..expectWarnings(["Multiple linter rules registered to name 'r1'"]); + }); + test('empty to start', () { + var registry = new MockRegistry(); + expect(registry.enabledLints, isEmpty); + }); + test('new entries disabled by default', () { + var registry = new MockRegistry(); + registry.registerLinter('my_first_lint', new MockLinter()); + expect(registry.enabledLints, isEmpty); + }); + test('enablement', () { + var registry = new MockRegistry(); + var linter = new MockLinter(); + registry.registerLinter('my_first_lint', linter); + registry.enable('my_first_lint'); + expect(registry.enabledLints, contains(linter)); + }); + test('enablement - unregistered', () { + var registry = new MockRegistry(); + registry.enable('unknown_rule'); + registry.expectWarnings( + ["No rule registered to 'unknown_rule', cannot enable"]); + }); + test('disablement', () { + var registry = new MockRegistry(); + var linter = new MockLinter(); + registry.registerLinter('my_first_lint', linter); + registry.disable('my_first_lint'); + expect(registry.enabledLints, isEmpty); + }); + test('disablement - unregistered', () { + var registry = new MockRegistry(); + registry.disable('unknown_rule'); + registry.expectWarnings( + ["No rule registered to 'unknown_rule', cannot disable"]); + }); + }); + }); +} + + +// Test framework sanity +void defineRuleTests() { + + //TODO: if ruleDir cannot be found print message to set CWD to project root + + print("Running tests in '$ruleDir'..."); + + for (var entry in new Directory(ruleDir).listSync()) { + if (entry is! File || !entry.path.endsWith('.dart')) continue; + var ruleName = p.basenameWithoutExtension(entry.path); + print("Testing rule '$ruleName'"); + testRule(ruleName, entry); + } +} + +// Linter engine tests +void defineSanityTests() { + group('test framework tests', () { + test('annotation extraction', () { + expect(extractAnnotation('int x; // LINT'), isNotNull); + expect(extractAnnotation('int x; //LINT'), isNotNull); + expect(extractAnnotation('int x; // OK'), isNull); + expect(extractAnnotation('int x;'), isNull); + expect( + extractAnnotation('dynamic x; // LINT dynamic is bad').message, + equals('dynamic is bad')); + }); + }); +} + +// Rule tests +Annotation extractAnnotation(String line) { + int index = line.indexOf(new RegExp(r'//[ ]?LINT')); + if (index > -1) { + int msgIndex = line.substring(index).indexOf('T') + 1; + String msg = null; + if (msgIndex < line.length) { + msg = line.substring(index + msgIndex).trim(); + } + return new Annotation.forLint(msg); + } + return null; +} + +void testRule(String ruleName, File file) { + + var expected = []; + + int lineNumber = 0; + for (var line in file.readAsLinesSync()) { + var annotation = extractAnnotation(line); + if (annotation != null) { + annotation.lineNumber = lineNumber; + expected.add(annotation); + } + ++lineNumber; + } + + DartLinter driver = new DartLinter(); + driver.enableRule(ruleName); + + Iterable lints = driver.lintFile(file); + + List actual = []; + lints.forEach((AnalysisErrorInfo info) { + info.errors.forEach((AnalysisError error) { + actual.add(new Annotation.forError(error, info.lineInfo)); + }); + }); + + print(lints); + + expect(actual, unorderedEquals(expected)); + +} + +class Annotation { + final String message; + final ErrorType type; + int lineNumber; + + Annotation(this.message, this.type); + + Annotation.forError(AnalysisError error, LineInfo lineInfo) + : this(error.message, error.errorCode.type); + + Annotation.forLint([String message]) : this(message, ErrorType.LINT); + + String toString() => '[$type]: "$message" (line: $lineNumber)'; + + static Iterable fromErrors(AnalysisErrorInfo error) { + List annotations = []; + error.errors.forEach( + (e) => annotations.add(new Annotation.forError(e, error.lineInfo))); + return annotations; + } +} + + +class MockLinter extends Linter { + @override + AstVisitor getVisitor() => null; +} + +class MockRegistry extends RuleRegistry { + MockRegistry() : super(new MockReporter()); + + expectWarnings(List warnings) { + expect((reporter as MockReporter).warnings, unorderedEquals(warnings)); + } +} + +class MockReporter extends Reporter { + + var exceptions = []; + var warnings = []; + + @override + void exception(LinterException exception) { + exceptions.add(exception); + } + + @override + void warn(String message) { + warnings.add(message); + } +} From ffe9f50f6ade729a142d754765665cafc70247f5 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Mon, 2 Feb 2015 11:00:16 -0800 Subject: [PATCH 0014/2942] Git ignore updates. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 7de946ea35f..bcff5be24bb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .pub packages build +.project +pubspec.lock \ No newline at end of file From 4a9cbd001387d4fb139606eb73e756746a6d10f5 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Mon, 2 Feb 2015 11:12:15 -0800 Subject: [PATCH 0015/2942] Entry point. --- lib/dart_lint.dart | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 lib/dart_lint.dart diff --git a/lib/dart_lint.dart b/lib/dart_lint.dart new file mode 100644 index 00000000000..718951b7d87 --- /dev/null +++ b/lib/dart_lint.dart @@ -0,0 +1,8 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library dart_lint; + +export 'package:dart_lint/src/linter.dart' show DartLinter, LinterOptions, + LinterException; From db26110a41c5cba497cfd22063888449223b295e Mon Sep 17 00:00:00 2001 From: pquitslund Date: Mon, 2 Feb 2015 11:15:19 -0800 Subject: [PATCH 0016/2942] Travis test config. --- test/all.dart | 17 +++++++++++++++++ test/lint_test.dart | 8 -------- tool/travis.sh | 9 ++++----- 3 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 test/all.dart diff --git a/test/all.dart b/test/all.dart new file mode 100644 index 00000000000..f76f309070b --- /dev/null +++ b/test/all.dart @@ -0,0 +1,17 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:unittest/compact_vm_config.dart'; +import 'package:unittest/unittest.dart'; + +import 'lint_test.dart' as lint_test; + +main() { + // Tidy up the unittest output. + filterStacks = true; + formatStacks = true; + useCompactVMConfiguration(); + + lint_test.main(); +} diff --git a/test/lint_test.dart b/test/lint_test.dart index 3113bc9551b..4dc17bdb06a 100644 --- a/test/lint_test.dart +++ b/test/lint_test.dart @@ -14,25 +14,17 @@ import 'package:analyzer/src/services/lint.dart'; import 'package:dart_lint/src/linter.dart'; import 'package:dart_lint/src/rules.dart'; import 'package:path/path.dart' as p; -import 'package:unittest/compact_vm_config.dart'; import 'package:unittest/unittest.dart'; const ruleDir = 'test/rules'; - main() { - // Tidy up the unittest output. - filterStacks = true; - formatStacks = true; - useCompactVMConfiguration(); - defineSanityTests(); defineLinterEngineTests(); //defineRuleTests(); } - void defineLinterEngineTests() { group('linter engine tests', () { group('registry', () { diff --git a/tool/travis.sh b/tool/travis.sh index 35dcb8f71c7..7e6f7ed01e7 100755 --- a/tool/travis.sh +++ b/tool/travis.sh @@ -8,13 +8,12 @@ set -e # Verify that the libraries are error free. -#dartanalyzer --fatal-warnings \ -# bin/dartpad_server.dart \ -# lib/dartpad_server.dart \ -# test/all.dart +dartanalyzer --fatal-warnings \ + lib/dart_lint.dart \ + test/all.dart # Run the tests. -#dart test/all.dart +dart test/all.dart # Install dart_coveralls; gather and send coverage data. if [ "$REPO_TOKEN" ]; then From ea67595149ed63c216c87dcd6e9e6cefdf070068 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Mon, 2 Feb 2015 14:03:24 -0800 Subject: [PATCH 0017/2942] Coveralls config. --- README.md | 1 + tool/travis.sh | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f49c9689f0e..6401ab80cce 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ # dart_lint [![Build Status](https://travis-ci.org/dart-lang/dart_lint.svg)](https://travis-ci.org/dart-lang/dart_lint) +[![Coverage Status](https://coveralls.io/repos/dart-lang/dart_lint/badge.svg)](https://coveralls.io/r/dart-lang/dart_lint) \ No newline at end of file diff --git a/tool/travis.sh b/tool/travis.sh index 7e6f7ed01e7..11cabfe6c71 100755 --- a/tool/travis.sh +++ b/tool/travis.sh @@ -16,10 +16,10 @@ dartanalyzer --fatal-warnings \ dart test/all.dart # Install dart_coveralls; gather and send coverage data. -if [ "$REPO_TOKEN" ]; then +if [ "$COVERALLS_TOKEN" ]; then pub global activate dart_coveralls pub global run dart_coveralls report \ - --token $REPO_TOKEN \ + --token $COVERALLS_TOKEN \ --retry 2 \ --exclude-test-files \ test/all.dart From 008d993f150a98900ecc3f161131e9b5280cbf64 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Mon, 2 Feb 2015 14:10:08 -0800 Subject: [PATCH 0018/2942] Verbose tests FTW --- test/all.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/all.dart b/test/all.dart index f76f309070b..8d8549fa2a5 100644 --- a/test/all.dart +++ b/test/all.dart @@ -2,7 +2,6 @@ // 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 'package:unittest/compact_vm_config.dart'; import 'package:unittest/unittest.dart'; import 'lint_test.dart' as lint_test; @@ -11,7 +10,7 @@ main() { // Tidy up the unittest output. filterStacks = true; formatStacks = true; - useCompactVMConfiguration(); +// useCompactVMConfiguration(); lint_test.main(); } From c4bad1eea894fd2946ce183bf5151bfd12904a21 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Mon, 2 Feb 2015 16:45:16 -0800 Subject: [PATCH 0019/2942] Formatter run. --- lib/src/analysis.dart | 43 +++++++++++++++---------------------------- lib/src/linter.dart | 14 ++++---------- lib/src/rules.dart | 1 - test/all.dart | 2 +- test/lint_test.dart | 15 ++++----------- 5 files changed, 24 insertions(+), 51 deletions(-) diff --git a/lib/src/analysis.dart b/lib/src/analysis.dart index c2157d9d904..c51c57fbc60 100644 --- a/lib/src/analysis.dart +++ b/lib/src/analysis.dart @@ -28,13 +28,11 @@ import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/generated/source_io.dart'; import 'package:grinder/grinder.dart' as grinder; - /// A refactored fork of [AnalyzerImpl] with an eye towards easing /// use and extension. /// /// Still very much a WIP. class AnalysisDriver { - final DriverOptions _options; final int _startTime; @@ -56,15 +54,11 @@ class AnalysisDriver { : this.forPath(file.absolute.path, options, dartSdk); AnalysisDriver.forPath(String sourcePath, DriverOptions options, - [DartSdk dartSdk]) - : this.forSource( - _createSource(sourcePath, dartSdk, options), - options, - dartSdk); + [DartSdk dartSdk]) : this.forSource( + _createSource(sourcePath, dartSdk, options), options, dartSdk); AnalysisDriver.forSource(this._librarySource, this._options, - [DartSdk dartSdk]) - : _startTime = _currentTimeInMillis() { + [DartSdk dartSdk]) : _startTime = _currentTimeInMillis() { //TODO: is already called when redirected from forPath _setupSdk(dartSdk, _options); } @@ -98,8 +92,8 @@ class AnalysisDriver { PhysicalResourceProvider.INSTANCE.getResource('.')); Map> packageMap = packageMapInfo.packageMap; if (packageMap != null) { - resolvers.add( - new PackageMapUriResolver(PhysicalResourceProvider.INSTANCE, packageMap)); + resolvers.add(new PackageMapUriResolver( + PhysicalResourceProvider.INSTANCE, packageMap)); } } } @@ -114,7 +108,7 @@ class AnalysisDriver { /// Treats the [sourcePath] as the top level library and analyzes it using a /// synchronous algorithm over the analysis engine. If [printMode] is `0`, - /// then no error or performance information is printed. If [printMode] is `1`, + /// then no error or performance information is printed. If [printMode] is `1`, /// then both will be printed. If [printMode] is `2`, then only performance /// information will be printed, and it will be marked as being for a cold VM. ErrorSeverity analyzeSync({int printMode: 1}) { @@ -130,8 +124,7 @@ class AnalysisDriver { return false; } if (_computeSeverity(error, _options.enableTypeChecks) == - ErrorSeverity.INFO && - _options.disableHints) { + ErrorSeverity.INFO && _options.disableHints) { return false; } return true; @@ -165,9 +158,7 @@ class AnalysisDriver { } // add compilation units _addCompilationUnitSource( - library.definingCompilationUnit, - libraries, - units); + library.definingCompilationUnit, libraries, units); for (CompilationUnitElement child in library.parts) { _addCompilationUnitSource(child, libraries, units); } @@ -247,8 +238,9 @@ class AnalysisDriver { List _getResolvers() { List resolvers = [ - new DartUriResolver(sdk), - new FileUriResolver()]; + new DartUriResolver(sdk), + new FileUriResolver() + ]; addResolvers(resolvers); return resolvers; } @@ -366,8 +358,8 @@ class AnalysisDriver { /// Compute the severity of the error; however, if /// enableTypeChecks] is false, then de-escalate checked-mode compile time /// errors to a severity of [ErrorSeverity.INFO]. - static ErrorSeverity _computeSeverity(AnalysisError error, - bool enableTypeChecks) { + static ErrorSeverity _computeSeverity( + AnalysisError error, bool enableTypeChecks) { if (!enableTypeChecks && error.errorCode.type == ErrorType.CHECKED_MODE_COMPILE_TIME_ERROR) { return ErrorSeverity.INFO; @@ -375,8 +367,8 @@ class AnalysisDriver { return error.errorCode.errorSeverity; } - static Source _createSource(String sourcePath, DartSdk dartSdk, - DriverOptions options) { + static Source _createSource( + String sourcePath, DartSdk dartSdk, DriverOptions options) { _setupSdk(dartSdk, options); JavaFile sourceFile = new JavaFile(_normalizeSourcePath(sourcePath)); Uri uri = _getUri(sourceFile); @@ -437,10 +429,8 @@ class AnalysisDriver { sdk = new DirectoryBasedDartSdk(new JavaFile(sdkDir.path)); } } - } - class DriverOptions { /// The maximum number of sources for which AST structures should be kept @@ -494,9 +484,7 @@ class DriverOptions { bool warningsAreFatal = false; } - class SdkWrapper implements DirectoryBasedDartSdk { - DartSdk dartSdk; SdkWrapper(this.dartSdk); @@ -572,7 +560,6 @@ class SdkWrapper implements DirectoryBasedDartSdk { } class _OptionsWrapper implements CommandLineOptions { - final DriverOptions driverOptions; _OptionsWrapper(this.driverOptions); diff --git a/lib/src/linter.dart b/lib/src/linter.dart index e3782f94c92..f8389fab7f4 100644 --- a/lib/src/linter.dart +++ b/lib/src/linter.dart @@ -14,7 +14,6 @@ import 'package:analyzer/src/services/lint.dart'; import 'package:dart_lint/src/analysis.dart'; import 'package:dart_lint/src/rules.dart'; - /// Dart source linter. abstract class DartLinter { @@ -33,23 +32,20 @@ abstract class DartLinter { } class LintDriver extends AnalysisDriver { - static LinterOptions get standardOptions => new LinterOptions(); factory LintDriver.forFile(File file, Iterable linters, [LinterOptions options]) { _registerLinters(linters); return new LintDriver._forFile( - file, - options != null ? options : standardOptions); + file, options != null ? options : standardOptions); } factory LintDriver.forPath(String path, Iterable linters, [LinterOptions options]) { _registerLinters(linters); return new LintDriver._forPath( - path, - options != null ? options : standardOptions); + path, options != null ? options : standardOptions); } LintDriver._forFile(File file, [LinterOptions options]) @@ -106,14 +102,13 @@ abstract class Reporter { /// Linter implementation class SourceLinter implements DartLinter, AnalysisErrorListener { - final errors = []; LinterOptions options; final Reporter reporter; RuleRegistry registry; - SourceLinter(this.options, [this.reporter = const StdIoReporter(), - this.registry]) { + SourceLinter(this.options, + [this.reporter = const StdIoReporter(), this.registry]) { if (options == null) { options = new LinterOptions(); } @@ -152,7 +147,6 @@ class SourceLinter implements DartLinter, AnalysisErrorListener { } class StdIoReporter implements Reporter, Logger { - const StdIoReporter(); @override diff --git a/lib/src/rules.dart b/lib/src/rules.dart index 9e2f0c59d14..c762ece591a 100644 --- a/lib/src/rules.dart +++ b/lib/src/rules.dart @@ -26,7 +26,6 @@ class Rule { } class RuleRegistry { - final Reporter reporter; final Map _ruleMap = {}; diff --git a/test/all.dart b/test/all.dart index 8d8549fa2a5..ede81fef3cd 100644 --- a/test/all.dart +++ b/test/all.dart @@ -10,7 +10,7 @@ main() { // Tidy up the unittest output. filterStacks = true; formatStacks = true; -// useCompactVMConfiguration(); + // useCompactVMConfiguration(); lint_test.main(); } diff --git a/test/lint_test.dart b/test/lint_test.dart index 4dc17bdb06a..50388dcb80a 100644 --- a/test/lint_test.dart +++ b/test/lint_test.dart @@ -16,7 +16,6 @@ import 'package:dart_lint/src/rules.dart'; import 'package:path/path.dart' as p; import 'package:unittest/unittest.dart'; - const ruleDir = 'test/rules'; main() { @@ -31,9 +30,9 @@ void defineLinterEngineTests() { test('duplicate rules', () { var registry = new MockRegistry(); registry - ..registerLinter('r1', new MockLinter()) - ..registerLinter('r1', new MockLinter()) - ..expectWarnings(["Multiple linter rules registered to name 'r1'"]); + ..registerLinter('r1', new MockLinter()) + ..registerLinter('r1', new MockLinter()) + ..expectWarnings(["Multiple linter rules registered to name 'r1'"]); }); test('empty to start', () { var registry = new MockRegistry(); @@ -74,7 +73,6 @@ void defineLinterEngineTests() { }); } - // Test framework sanity void defineRuleTests() { @@ -98,8 +96,7 @@ void defineSanityTests() { expect(extractAnnotation('int x; //LINT'), isNotNull); expect(extractAnnotation('int x; // OK'), isNull); expect(extractAnnotation('int x;'), isNull); - expect( - extractAnnotation('dynamic x; // LINT dynamic is bad').message, + expect(extractAnnotation('dynamic x; // LINT dynamic is bad').message, equals('dynamic is bad')); }); }); @@ -120,7 +117,6 @@ Annotation extractAnnotation(String line) { } void testRule(String ruleName, File file) { - var expected = []; int lineNumber = 0; @@ -148,7 +144,6 @@ void testRule(String ruleName, File file) { print(lints); expect(actual, unorderedEquals(expected)); - } class Annotation { @@ -173,7 +168,6 @@ class Annotation { } } - class MockLinter extends Linter { @override AstVisitor getVisitor() => null; @@ -188,7 +182,6 @@ class MockRegistry extends RuleRegistry { } class MockReporter extends Reporter { - var exceptions = []; var warnings = []; From 0ec5087a4cfdb3c94afba569e4dc2e750a47689e Mon Sep 17 00:00:00 2001 From: pquitslund Date: Mon, 2 Feb 2015 17:03:25 -0800 Subject: [PATCH 0020/2942] Source linter tests. --- lib/src/linter.dart | 2 +- test/lint_test.dart | 40 +++++++++++++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/lib/src/linter.dart b/lib/src/linter.dart index f8389fab7f4..09f718dc4d5 100644 --- a/lib/src/linter.dart +++ b/lib/src/linter.dart @@ -108,7 +108,7 @@ class SourceLinter implements DartLinter, AnalysisErrorListener { RuleRegistry registry; SourceLinter(this.options, - [this.reporter = const StdIoReporter(), this.registry]) { + {this.reporter: const StdIoReporter(), this.registry}) { if (options == null) { options = new LinterOptions(); } diff --git a/test/lint_test.dart b/test/lint_test.dart index 50388dcb80a..5d6f4dc615c 100644 --- a/test/lint_test.dart +++ b/test/lint_test.dart @@ -18,12 +18,7 @@ import 'package:unittest/unittest.dart'; const ruleDir = 'test/rules'; -main() { - defineSanityTests(); - defineLinterEngineTests(); - //defineRuleTests(); -} - +/// Linter engine tests void defineLinterEngineTests() { group('linter engine tests', () { group('registry', () { @@ -70,10 +65,30 @@ void defineLinterEngineTests() { ["No rule registered to 'unknown_rule', cannot disable"]); }); }); + group('source linter', () { + test('enable rule', () { + var registry = new MockRegistry(); + var lint = new MockLinter(); + registry.registerLinter('my_first_lint', lint); + var linter = new SourceLinter(null, registry: registry); + linter.enableRule('my_first_lint'); + expect(linter.registry.enabledLints, unorderedEquals([lint])); + }); + test('disable rule', () { + var registry = new MockRegistry(); + var lint = new MockLinter(); + registry.registerLinter('my_first_lint', lint); + var linter = new SourceLinter(null, registry: registry); + linter.enableRule('my_first_lint'); + expect(linter.registry.enabledLints, unorderedEquals([lint])); + linter.disableRule('my_first_lint'); + expect(linter.registry.enabledLints, isEmpty); + }); + }); }); } -// Test framework sanity +/// Rule tests void defineRuleTests() { //TODO: if ruleDir cannot be found print message to set CWD to project root @@ -88,7 +103,7 @@ void defineRuleTests() { } } -// Linter engine tests +/// Test framework sanity void defineSanityTests() { group('test framework tests', () { test('annotation extraction', () { @@ -102,7 +117,6 @@ void defineSanityTests() { }); } -// Rule tests Annotation extractAnnotation(String line) { int index = line.indexOf(new RegExp(r'//[ ]?LINT')); if (index > -1) { @@ -116,6 +130,14 @@ Annotation extractAnnotation(String line) { return null; } +main() { + groupSep = ' | '; + + defineSanityTests(); + defineLinterEngineTests(); + //defineRuleTests(); +} + void testRule(String ruleName, File file) { var expected = []; From c9090bc4df986e1f70452f1a856dc156d07b40f8 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Mon, 2 Feb 2015 17:04:57 -0800 Subject: [PATCH 0021/2942] More ignores. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bcff5be24bb..f48e3c9a80a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ packages build .project +.settings pubspec.lock \ No newline at end of file From c6a6da0226d88db1bd4c727c2e0fcbf4500b228f Mon Sep 17 00:00:00 2001 From: pquitslund Date: Tue, 3 Feb 2015 15:33:19 -0800 Subject: [PATCH 0022/2942] Tweaks and tests. --- lib/src/linter.dart | 35 +++++++++++-- test/lint_test.dart | 116 +++++++++++++++++++++++++++++++------------- 2 files changed, 113 insertions(+), 38 deletions(-) diff --git a/lib/src/linter.dart b/lib/src/linter.dart index 09f718dc4d5..16caa204a1d 100644 --- a/lib/src/linter.dart +++ b/lib/src/linter.dart @@ -10,7 +10,9 @@ import 'package:analyzer/analyzer.dart'; import 'package:analyzer/src/generated/engine.dart'; import 'package:analyzer/src/generated/java_engine.dart'; import 'package:analyzer/src/generated/parser.dart'; +import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/services/lint.dart'; +import 'package:analyzer/src/string_source.dart'; import 'package:dart_lint/src/analysis.dart'; import 'package:dart_lint/src/rules.dart'; @@ -18,7 +20,8 @@ import 'package:dart_lint/src/rules.dart'; abstract class DartLinter { /// Creates a new linter for dart source. - factory DartLinter([LinterOptions options]) => new SourceLinter(options); + factory DartLinter([LinterOptions options]) => + new SourceLinter(options: options); disableRule(String ruleName); @@ -26,6 +29,9 @@ abstract class DartLinter { Iterable lintFile(File sourceFile); + Iterable lintLibrarySource( + {String libraryName, String libraryContents}); + Iterable lintPath(String sourcePath); registerRule(String ruleName, Linter lintRule); @@ -48,12 +54,24 @@ class LintDriver extends AnalysisDriver { path, options != null ? options : standardOptions); } + factory LintDriver.forSource( + String libraryName, String librarySource, Iterable linters, + [LinterOptions options]) { + _registerLinters(linters); + return new LintDriver._forSource(libraryName, librarySource, + options != null ? options : standardOptions); + } + LintDriver._forFile(File file, [LinterOptions options]) : super.forFile(file, options); LintDriver._forPath(String path, [LinterOptions options]) : super.forPath(path, options); + LintDriver._forSource(String libraryName, String librarySource, + [LinterOptions options]) + : super.forSource(new _StringSource(librarySource, libraryName), options); + bool isDesiredError(AnalysisError error) => error.errorCode.type == ErrorType.LINT; @@ -107,8 +125,8 @@ class SourceLinter implements DartLinter, AnalysisErrorListener { final Reporter reporter; RuleRegistry registry; - SourceLinter(this.options, - {this.reporter: const StdIoReporter(), this.registry}) { + SourceLinter( + {this.options, this.reporter: const StdIoReporter(), this.registry}) { if (options == null) { options = new LinterOptions(); } @@ -131,6 +149,11 @@ class SourceLinter implements DartLinter, AnalysisErrorListener { Iterable lintFile(File sourceFile) => new LintDriver.forFile(sourceFile, registry.enabledLints, options).lint(); + @override + Iterable lintLibrarySource( + {String libraryName, String libraryContents}) => new LintDriver.forSource( + libraryName, libraryContents, registry.enabledLints, options).lint(); + @override Iterable lintPath(String sourcePath) => new LintDriver.forPath(sourcePath, registry.enabledLints, options).lint(); @@ -179,3 +202,9 @@ class StdIoReporter implements Reporter, Logger { print('WARN: $message'); } } + +class _StringSource extends StringSource { + _StringSource(String contents, String fullName) : super(contents, fullName); + + UriKind get uriKind => UriKind.FILE_URI; +} diff --git a/test/lint_test.dart b/test/lint_test.dart index 5d6f4dc615c..1a5b555e1ec 100644 --- a/test/lint_test.dart +++ b/test/lint_test.dart @@ -20,7 +20,7 @@ const ruleDir = 'test/rules'; /// Linter engine tests void defineLinterEngineTests() { - group('linter engine tests', () { + group('engine', () { group('registry', () { test('duplicate rules', () { var registry = new MockRegistry(); @@ -65,12 +65,13 @@ void defineLinterEngineTests() { ["No rule registered to 'unknown_rule', cannot disable"]); }); }); + group('source linter', () { test('enable rule', () { var registry = new MockRegistry(); var lint = new MockLinter(); registry.registerLinter('my_first_lint', lint); - var linter = new SourceLinter(null, registry: registry); + var linter = new SourceLinter(registry: registry); linter.enableRule('my_first_lint'); expect(linter.registry.enabledLints, unorderedEquals([lint])); }); @@ -78,13 +79,24 @@ void defineLinterEngineTests() { var registry = new MockRegistry(); var lint = new MockLinter(); registry.registerLinter('my_first_lint', lint); - var linter = new SourceLinter(null, registry: registry); + var linter = new SourceLinter(registry: registry); linter.enableRule('my_first_lint'); expect(linter.registry.enabledLints, unorderedEquals([lint])); linter.disableRule('my_first_lint'); expect(linter.registry.enabledLints, isEmpty); }); }); + + group('lint driver', () { + test('basic', () { + bool visited; + var r = new MockRegistry([new MockLinter((n) => visited = true)]); + new SourceLinter(registry: r).lintLibrarySource( + libraryName: 'testLibrary', + libraryContents: 'library testLibrary;'); + expect(visited, isTrue); + }); + }); }); } @@ -92,20 +104,18 @@ void defineLinterEngineTests() { void defineRuleTests() { //TODO: if ruleDir cannot be found print message to set CWD to project root - - print("Running tests in '$ruleDir'..."); - - for (var entry in new Directory(ruleDir).listSync()) { - if (entry is! File || !entry.path.endsWith('.dart')) continue; - var ruleName = p.basenameWithoutExtension(entry.path); - print("Testing rule '$ruleName'"); - testRule(ruleName, entry); - } + group('rule', () { + for (var entry in new Directory(ruleDir).listSync()) { + if (entry is! File || !entry.path.endsWith('.dart')) continue; + var ruleName = p.basenameWithoutExtension(entry.path); + testRule(ruleName, entry); + } + }); } /// Test framework sanity void defineSanityTests() { - group('test framework tests', () { + group('test framework', () { test('annotation extraction', () { expect(extractAnnotation('int x; // LINT'), isNotNull); expect(extractAnnotation('int x; //LINT'), isNotNull); @@ -139,35 +149,41 @@ main() { } void testRule(String ruleName, File file) { - var expected = []; + test('$ruleName', () { + var expected = []; - int lineNumber = 0; - for (var line in file.readAsLinesSync()) { - var annotation = extractAnnotation(line); - if (annotation != null) { - annotation.lineNumber = lineNumber; - expected.add(annotation); + int lineNumber = 0; + for (var line in file.readAsLinesSync()) { + var annotation = extractAnnotation(line); + if (annotation != null) { + annotation.lineNumber = lineNumber; + expected.add(annotation); + } + ++lineNumber; } - ++lineNumber; - } - DartLinter driver = new DartLinter(); - driver.enableRule(ruleName); + DartLinter driver = new DartLinter(); + driver.enableRule(ruleName); - Iterable lints = driver.lintFile(file); + Iterable lints = driver.lintFile(file); - List actual = []; - lints.forEach((AnalysisErrorInfo info) { - info.errors.forEach((AnalysisError error) { - actual.add(new Annotation.forError(error, info.lineInfo)); + List actual = []; + lints.forEach((AnalysisErrorInfo info) { + info.errors.forEach((AnalysisError error) { + actual.add(new Annotation.forError(error, info.lineInfo)); + }); }); + + print(lints); + + expect(actual, unorderedEquals(expected)); }); - - print(lints); - - expect(actual, unorderedEquals(expected)); } +typedef nodeVisitor(AstNode node); + +typedef AstVisitor VisitorCallback(); + class Annotation { final String message; final ErrorType type; @@ -191,18 +207,36 @@ class Annotation { } class MockLinter extends Linter { + VisitorCallback visitorCallback; + + MockLinter([nodeVisitor v]) { + visitorCallback = () => new MockVisitor(v); + } + @override - AstVisitor getVisitor() => null; + AstVisitor getVisitor() => visitorCallback(); } class MockRegistry extends RuleRegistry { - MockRegistry() : super(new MockReporter()); + MockRegistry([List lints]) : super(new MockReporter()) { + if (lints != null) { + for (int i = 0; i < lints.length; ++i) { + registerLinter('_linter_$i', lints[i]); + enable('_linter_$i'); + } + } + } expectWarnings(List warnings) { expect((reporter as MockReporter).warnings, unorderedEquals(warnings)); } } +//class MockLinter extends Linter { +// @override +// AstVisitor getVisitor() => null; +//} + class MockReporter extends Reporter { var exceptions = []; var warnings = []; @@ -217,3 +251,15 @@ class MockReporter extends Reporter { warnings.add(message); } } + +class MockVisitor extends GeneralizingAstVisitor { + final nodeVisitor; + + MockVisitor(this.nodeVisitor); + + visitNode(AstNode node) { + if (nodeVisitor != null) { + nodeVisitor(node); + } + } +} From 468da691c07dc6a989b6130ca0c63495ddac8ec3 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Tue, 3 Feb 2015 20:41:11 -0800 Subject: [PATCH 0023/2942] Driver refactoring. --- lib/src/analysis.dart | 35 ++++++++++++----- lib/src/linter.dart | 91 ++++++++++++------------------------------- 2 files changed, 51 insertions(+), 75 deletions(-) diff --git a/lib/src/analysis.dart b/lib/src/analysis.dart index c51c57fbc60..38d7ee2ac3d 100644 --- a/lib/src/analysis.dart +++ b/lib/src/analysis.dart @@ -28,6 +28,9 @@ import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/generated/source_io.dart'; import 'package:grinder/grinder.dart' as grinder; +/// Returns `true` if [AnalysisError] should be printed. +typedef bool ErrorFilter(AnalysisError error); + /// A refactored fork of [AnalyzerImpl] with an eye towards easing /// use and extension. /// @@ -78,6 +81,10 @@ class AnalysisDriver { return status; } + ErrorFilter get _errorFilter => _options.errorFilter != null + ? _options.errorFilter + : _defaultErrorFilter(); + /// Default implementation contributes a package URI and package map /// resolvers (in addition to the predefined dart URI and file URI resolvers). /// Override to specialize. @@ -119,17 +126,13 @@ class AnalysisDriver { /// By default creates a logger that reports to standard out and error. Logger createLogger() => new StdLogger(_options.log); - bool isDesiredError(AnalysisError error) { - if (error.errorCode.type == ErrorType.TODO) { - return false; - } - if (_computeSeverity(error, _options.enableTypeChecks) == - ErrorSeverity.INFO && _options.disableHints) { - return false; - } - return true; + Iterable getErrors() { + analyzeSync(); + return errorInfos; } + bool isDesiredError(AnalysisError error) => _errorFilter(error); + void _addCompilationUnitSource(CompilationUnitElement unit, Set libraries, Set units) { if (unit == null || units.contains(unit)) { @@ -236,6 +239,17 @@ class AnalysisDriver { return status; } + ErrorFilter _defaultErrorFilter() => (AnalysisError error) { + if (error.errorCode.type == ErrorType.TODO) { + return false; + } + if (_computeSeverity(error, _options.enableTypeChecks) == + ErrorSeverity.INFO && _options.disableHints) { + return false; + } + return true; + }; + List _getResolvers() { List resolvers = [ new DartUriResolver(sdk), @@ -456,6 +470,9 @@ class DriverOptions { /// errors. bool enableTypeChecks = false; + /// Custom error filtering. + ErrorFilter errorFilter; + /// Whether to ignore unrecognized flags. bool ignoreUnrecognizedFlags; diff --git a/lib/src/linter.dart b/lib/src/linter.dart index 16caa204a1d..d5e4f2de0e5 100644 --- a/lib/src/linter.dart +++ b/lib/src/linter.dart @@ -16,10 +16,17 @@ import 'package:analyzer/src/string_source.dart'; import 'package:dart_lint/src/analysis.dart'; import 'package:dart_lint/src/rules.dart'; +void _registerLinters(Iterable linters) { + LintGenerator.LINTERS.clear(); + linters.forEach((l) => LintGenerator.LINTERS.add(l)); +} + +typedef AnalysisDriver _DriverFactory(); + /// Dart source linter. abstract class DartLinter { - /// Creates a new linter for dart source. + /// Creates a new linter. factory DartLinter([LinterOptions options]) => new SourceLinter(options: options); @@ -37,55 +44,6 @@ abstract class DartLinter { registerRule(String ruleName, Linter lintRule); } -class LintDriver extends AnalysisDriver { - static LinterOptions get standardOptions => new LinterOptions(); - - factory LintDriver.forFile(File file, Iterable linters, - [LinterOptions options]) { - _registerLinters(linters); - return new LintDriver._forFile( - file, options != null ? options : standardOptions); - } - - factory LintDriver.forPath(String path, Iterable linters, - [LinterOptions options]) { - _registerLinters(linters); - return new LintDriver._forPath( - path, options != null ? options : standardOptions); - } - - factory LintDriver.forSource( - String libraryName, String librarySource, Iterable linters, - [LinterOptions options]) { - _registerLinters(linters); - return new LintDriver._forSource(libraryName, librarySource, - options != null ? options : standardOptions); - } - - LintDriver._forFile(File file, [LinterOptions options]) - : super.forFile(file, options); - - LintDriver._forPath(String path, [LinterOptions options]) - : super.forPath(path, options); - - LintDriver._forSource(String libraryName, String librarySource, - [LinterOptions options]) - : super.forSource(new _StringSource(librarySource, libraryName), options); - - bool isDesiredError(AnalysisError error) => - error.errorCode.type == ErrorType.LINT; - - Iterable lint() { - analyzeSync(); - return errorInfos; - } - - static void _registerLinters(Iterable linters) { - LintGenerator.LINTERS.clear(); - linters.forEach((l) => LintGenerator.LINTERS.add(l)); - } -} - /// Thrown when an error occurs in linting. class LinterException implements Exception { @@ -110,7 +68,9 @@ class LinterException implements Exception { /// Linter options. class LinterOptions extends DriverOptions { - bool get enableLints => true; + final bool enableLints = true; + final ErrorFilter errorFilter = + (AnalysisError error) => error.errorCode.type == ErrorType.LINT; } abstract class Reporter { @@ -136,36 +96,35 @@ class SourceLinter implements DartLinter, AnalysisErrorListener { } @override - disableRule(String ruleName) { - registry.disable(ruleName); - } + disableRule(String ruleName) => registry.disable(ruleName); @override - enableRule(String ruleName) { - registry.enable(ruleName); - } + enableRule(String ruleName) => registry.enable(ruleName); @override Iterable lintFile(File sourceFile) => - new LintDriver.forFile(sourceFile, registry.enabledLints, options).lint(); + _registerAndRun(() => new AnalysisDriver.forFile(sourceFile, options)); @override Iterable lintLibrarySource( - {String libraryName, String libraryContents}) => new LintDriver.forSource( - libraryName, libraryContents, registry.enabledLints, options).lint(); + {String libraryName, String libraryContents}) => _registerAndRun( + () => new AnalysisDriver.forSource( + new _StringSource(libraryContents, libraryName), options)); @override Iterable lintPath(String sourcePath) => - new LintDriver.forPath(sourcePath, registry.enabledLints, options).lint(); + _registerAndRun(() => new AnalysisDriver.forPath(sourcePath, options)); @override - onError(AnalysisError error) { - errors.add(error); - } + onError(AnalysisError error) => errors.add(error); @override - registerRule(String ruleName, Linter lintRule) { - registry.registerLinter(ruleName, lintRule); + registerRule(String ruleName, Linter lintRule) => + registry.registerLinter(ruleName, lintRule); + + Iterable _registerAndRun(_DriverFactory createDriver) { + _registerLinters(registry.enabledLints); + return createDriver().getErrors(); } } From 964e6bd5ee4d8c0dce4fb95aa381b51d71d4fec3 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Wed, 4 Feb 2015 08:44:23 -0800 Subject: [PATCH 0024/2942] More tests. --- lib/src/linter.dart | 79 ++++++++++++++++++++++++--------------------- test/lint_test.dart | 32 ++++++++++++++++++ 2 files changed, 74 insertions(+), 37 deletions(-) diff --git a/lib/src/linter.dart b/lib/src/linter.dart index d5e4f2de0e5..8cb96d122f7 100644 --- a/lib/src/linter.dart +++ b/lib/src/linter.dart @@ -21,6 +21,8 @@ void _registerLinters(Iterable linters) { linters.forEach((l) => LintGenerator.LINTERS.add(l)); } +typedef Printer(String msg); + typedef AnalysisDriver _DriverFactory(); /// Dart source linter. @@ -51,12 +53,13 @@ class LinterException implements Exception { final String message; /// Creates a new LinterException with an optional error [message]. - const LinterException([this.message = 'LinterException']); + const LinterException([this.message]); LinterException.forError(List errors) : message = _createMessage(errors); - String toString() => '$message'; + String toString() => + message == null ? "LinterException" : "LinterException: $message"; //TODO: revisit static String _createMessage(errors) { @@ -73,6 +76,42 @@ class LinterOptions extends DriverOptions { (AnalysisError error) => error.errorCode.type == ErrorType.LINT; } +class PrintingReporter implements Reporter, Logger { + final Printer _print; + + const PrintingReporter([this._print = print]); + + @override + void exception(LinterException exception) { + _print('EXCEPTION: $exception'); + } + + @override + void logError(String message, [CaughtException exception]) { + _print('ERROR: $message'); + } + + @override + void logError2(String message, Object exception) { + _print('ERROR: $message'); + } + + @override + void logInformation(String message, [CaughtException exception]) { + _print('INFO: $message'); + } + + @override + void logInformation2(String message, Object exception) { + _print('INFO: $message'); + } + + @override + void warn(String message) { + _print('WARN: $message'); + } +} + abstract class Reporter { void exception(LinterException exception); void warn(String message); @@ -86,7 +125,7 @@ class SourceLinter implements DartLinter, AnalysisErrorListener { RuleRegistry registry; SourceLinter( - {this.options, this.reporter: const StdIoReporter(), this.registry}) { + {this.options, this.reporter: const PrintingReporter(), this.registry}) { if (options == null) { options = new LinterOptions(); } @@ -128,40 +167,6 @@ class SourceLinter implements DartLinter, AnalysisErrorListener { } } -class StdIoReporter implements Reporter, Logger { - const StdIoReporter(); - - @override - void exception(LinterException exception) { - print('EXCEPTION: $exception'); - } - - @override - void logError(String message, [CaughtException exception]) { - print('ERROR: $message'); - } - - @override - void logError2(String message, Object exception) { - print('ERROR: $message'); - } - - @override - void logInformation(String message, [CaughtException exception]) { - print('INFO: $message'); - } - - @override - void logInformation2(String message, Object exception) { - print('INFO: $message'); - } - - @override - void warn(String message) { - print('WARN: $message'); - } -} - class _StringSource extends StringSource { _StringSource(String contents, String fullName) : super(contents, fullName); diff --git a/test/lint_test.dart b/test/lint_test.dart index 1a5b555e1ec..5020c173c98 100644 --- a/test/lint_test.dart +++ b/test/lint_test.dart @@ -66,6 +66,38 @@ void defineLinterEngineTests() { }); }); + group('reporter', () { + _test(String label, String expected, report(PrintingReporter r)) { + test(label, () { + String msg; + PrintingReporter reporter = new PrintingReporter((m) => msg = m); + report(reporter); + expect(msg, expected); + }); + } + + _test('exception', 'EXCEPTION: LinterException: foo', + (r) => r.exception(new LinterException('foo'))); + _test('logError', 'ERROR: foo', (r) => r.logError('foo')); + _test('logError2', 'ERROR: foo', + (r) => r.logError2('foo', new Exception())); + _test('logInformation', 'INFO: foo', (r) => r.logInformation('foo')); + _test('logInformation2', 'INFO: foo', + (r) => r.logInformation2('foo', new Exception())); + _test('warn', 'WARN: foo', (r) => r.warn('foo')); + }); + + group('exceptions', () { + test('message', () { + expect(const LinterException('foo').message, equals('foo')); + }); + test('toString', () { + expect(const LinterException().toString(), equals('LinterException')); + expect(const LinterException('foo').toString(), + equals('LinterException: foo')); + }); + }); + group('source linter', () { test('enable rule', () { var registry = new MockRegistry(); From 6778a25fedda278f985508f27fb1238b0cf46a02 Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Wed, 4 Feb 2015 09:09:16 -0800 Subject: [PATCH 0025/2942] Update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6401ab80cce..5c46ce35b6a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,12 @@ # dart_lint +A Dart style linter. + [![Build Status](https://travis-ci.org/dart-lang/dart_lint.svg)](https://travis-ci.org/dart-lang/dart_lint) -[![Coverage Status](https://coveralls.io/repos/dart-lang/dart_lint/badge.svg)](https://coveralls.io/r/dart-lang/dart_lint) \ No newline at end of file +[![Coverage Status](https://coveralls.io/repos/dart-lang/dart_lint/badge.svg)](https://coveralls.io/r/dart-lang/dart_lint) + +## Features and bugs + +Please file feature requests and bugs at the [issue tracker][tracker]. + +[tracker]: https://github.com/dart-lang/cli_util/issues From e47f72e8e14ec64017320cefd9548e25c97616ee Mon Sep 17 00:00:00 2001 From: pquitslund Date: Wed, 4 Feb 2015 14:42:07 -0800 Subject: [PATCH 0026/2942] Tests and sample lint. --- lib/src/linter.dart | 49 +++----- lib/src/rules.dart | 9 +- .../unnecessary_brace_in_string_interp.dart | 37 ++++++ test/lint_test.dart | 118 +++++++++++------- 4 files changed, 132 insertions(+), 81 deletions(-) create mode 100644 lib/src/rules/unnecessary_brace_in_string_interp.dart diff --git a/lib/src/linter.dart b/lib/src/linter.dart index 8cb96d122f7..bc7c4a3c161 100644 --- a/lib/src/linter.dart +++ b/lib/src/linter.dart @@ -14,27 +14,29 @@ import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/services/lint.dart'; import 'package:analyzer/src/string_source.dart'; import 'package:dart_lint/src/analysis.dart'; -import 'package:dart_lint/src/rules.dart'; void _registerLinters(Iterable linters) { - LintGenerator.LINTERS.clear(); - linters.forEach((l) => LintGenerator.LINTERS.add(l)); + if (linters != null) { + LintGenerator.LINTERS.clear(); + linters.forEach((l) => LintGenerator.LINTERS.add(l)); + } } typedef Printer(String msg); +/// Describes a set of enabled rules. +typedef Iterable RuleSet(); + typedef AnalysisDriver _DriverFactory(); /// Dart source linter. abstract class DartLinter { /// Creates a new linter. - factory DartLinter([LinterOptions options]) => - new SourceLinter(options: options); + factory DartLinter([LinterOptions options]) => new SourceLinter(options); - disableRule(String ruleName); - - enableRule(String ruleName); + factory DartLinter.forRules(RuleSet ruleSet) => + new DartLinter(new LinterOptions(ruleSet)); Iterable lintFile(File sourceFile); @@ -42,8 +44,6 @@ abstract class DartLinter { {String libraryName, String libraryContents}); Iterable lintPath(String sourcePath); - - registerRule(String ruleName, Linter lintRule); } /// Thrown when an error occurs in linting. @@ -71,9 +71,12 @@ class LinterException implements Exception { /// Linter options. class LinterOptions extends DriverOptions { + final RuleSet _enabledLints; final bool enableLints = true; final ErrorFilter errorFilter = (AnalysisError error) => error.errorCode.type == ErrorType.LINT; + LinterOptions(this._enabledLints); + Iterable get enabledLints => _enabledLints(); } class PrintingReporter implements Reporter, Logger { @@ -120,25 +123,9 @@ abstract class Reporter { /// Linter implementation class SourceLinter implements DartLinter, AnalysisErrorListener { final errors = []; - LinterOptions options; + final LinterOptions options; final Reporter reporter; - RuleRegistry registry; - - SourceLinter( - {this.options, this.reporter: const PrintingReporter(), this.registry}) { - if (options == null) { - options = new LinterOptions(); - } - if (registry == null) { - registry = new RuleRegistry(reporter); - } - } - - @override - disableRule(String ruleName) => registry.disable(ruleName); - - @override - enableRule(String ruleName) => registry.enable(ruleName); + SourceLinter(this.options, {this.reporter: const PrintingReporter()}); @override Iterable lintFile(File sourceFile) => @@ -157,12 +144,8 @@ class SourceLinter implements DartLinter, AnalysisErrorListener { @override onError(AnalysisError error) => errors.add(error); - @override - registerRule(String ruleName, Linter lintRule) => - registry.registerLinter(ruleName, lintRule); - Iterable _registerAndRun(_DriverFactory createDriver) { - _registerLinters(registry.enabledLints); + _registerLinters(options.enabledLints); return createDriver().getErrors(); } } diff --git a/lib/src/rules.dart b/lib/src/rules.dart index c762ece591a..9fc787c4975 100644 --- a/lib/src/rules.dart +++ b/lib/src/rules.dart @@ -6,11 +6,12 @@ library rules; import 'package:analyzer/src/services/lint.dart'; import 'package:dart_lint/src/linter.dart'; -//import 'package:dart_lint/src/rules/super_goes_last.dart'; +import 'package:dart_lint/src/rules/unnecessary_brace_in_string_interp.dart'; /// Map of contributed lint rules. -final Map _rules = { +final Map ruleMap = { //'super_goes_last': new SuperGoesLastLint() + 'unnecessary_brace_in_string_interp': new UnnecessaryBraceInStringInterp() }; class Rule { @@ -29,9 +30,9 @@ class RuleRegistry { final Reporter reporter; final Map _ruleMap = {}; - RuleRegistry(this.reporter) { + RuleRegistry([this.reporter = const PrintingReporter()]) { // Register rules - _rules.forEach((n, l) => registerLinter(n, l)); + ruleMap.forEach((n, l) => registerLinter(n, l)); } /// An empty registry for testing diff --git a/lib/src/rules/unnecessary_brace_in_string_interp.dart b/lib/src/rules/unnecessary_brace_in_string_interp.dart new file mode 100644 index 00000000000..fa8e9ee9a86 --- /dev/null +++ b/lib/src/rules/unnecessary_brace_in_string_interp.dart @@ -0,0 +1,37 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library unnecessary_brace_in_string_interp; + +import 'package:analyzer/src/generated/ast.dart'; +import 'package:analyzer/src/generated/error.dart'; +import 'package:analyzer/src/services/lint.dart'; + +const msg = ''' +Interpolated simple identifiers (not followed by an alphanumeric string) do +not need braces. +'''; + +const name = 'UnnecessaryBraceInStringInterp'; + +class UnnecessaryBraceInStringInterp extends Linter { + @override + AstVisitor getVisitor() => new Visitor(reporter); +} + +class Visitor extends SimpleAstVisitor { + ErrorReporter reporter; + Visitor(this.reporter); + + @override + visitStringInterpolation(StringInterpolation node) { + var expressions = node.elements.where((e) => e is InterpolationExpression); + for (InterpolationExpression expression in expressions) { + if (expression.expression is SimpleIdentifier && + expression.rightBracket != null) { + reporter.reportErrorForNode(new LintCode(name, msg), expression, []); + } + } + } +} diff --git a/test/lint_test.dart b/test/lint_test.dart index 5020c173c98..ad23a2797a9 100644 --- a/test/lint_test.dart +++ b/test/lint_test.dart @@ -98,32 +98,12 @@ void defineLinterEngineTests() { }); }); - group('source linter', () { - test('enable rule', () { - var registry = new MockRegistry(); - var lint = new MockLinter(); - registry.registerLinter('my_first_lint', lint); - var linter = new SourceLinter(registry: registry); - linter.enableRule('my_first_lint'); - expect(linter.registry.enabledLints, unorderedEquals([lint])); - }); - test('disable rule', () { - var registry = new MockRegistry(); - var lint = new MockLinter(); - registry.registerLinter('my_first_lint', lint); - var linter = new SourceLinter(registry: registry); - linter.enableRule('my_first_lint'); - expect(linter.registry.enabledLints, unorderedEquals([lint])); - linter.disableRule('my_first_lint'); - expect(linter.registry.enabledLints, isEmpty); - }); - }); - group('lint driver', () { test('basic', () { bool visited; - var r = new MockRegistry([new MockLinter((n) => visited = true)]); - new SourceLinter(registry: r).lintLibrarySource( + var options = + new LinterOptions(() => [new MockLinter((n) => visited = true)]); + new SourceLinter(options).lintLibrarySource( libraryName: 'testLibrary', libraryContents: 'library testLibrary;'); expect(visited, isTrue); @@ -148,13 +128,35 @@ void defineRuleTests() { /// Test framework sanity void defineSanityTests() { group('test framework', () { - test('annotation extraction', () { - expect(extractAnnotation('int x; // LINT'), isNotNull); - expect(extractAnnotation('int x; //LINT'), isNotNull); - expect(extractAnnotation('int x; // OK'), isNull); - expect(extractAnnotation('int x;'), isNull); - expect(extractAnnotation('dynamic x; // LINT dynamic is bad').message, - equals('dynamic is bad')); + group('annotation', () { + test('extraction', () { + expect(extractAnnotation('int x; // LINT'), isNotNull); + expect(extractAnnotation('int x; //LINT'), isNotNull); + expect(extractAnnotation('int x; // OK'), isNull); + expect(extractAnnotation('int x;'), isNull); + expect(extractAnnotation('dynamic x; // LINT dynamic is bad').message, + equals('dynamic is bad')); + expect(extractAnnotation('dynamic x; //LINT').message, isNull); + expect(extractAnnotation('dynamic x; //LINT ').message, isNull); + }); + }); + test('equality', () { + expect( + new Annotation('Actual message (to be ignored)', ErrorType.LINT, 1), + matchesAnnotation(null, ErrorType.LINT, 1)); + expect(new Annotation('Message', ErrorType.LINT, 1), + matchesAnnotation('Message', ErrorType.LINT, 1)); + }); + test('inequality', () { + expect(() => expect(new Annotation('Message', ErrorType.LINT, 1), + matchesAnnotation('Message', ErrorType.HINT, 1)), + throwsA(new isInstanceOf())); + expect(() => expect(new Annotation('Message', ErrorType.LINT, 1), + matchesAnnotation('Message2', ErrorType.LINT, 1)), + throwsA(new isInstanceOf())); + expect(() => expect(new Annotation('Message', ErrorType.LINT, 1), + matchesAnnotation('Message', ErrorType.LINT, 2)), + throwsA(new isInstanceOf())); }); }); } @@ -166,6 +168,9 @@ Annotation extractAnnotation(String line) { String msg = null; if (msgIndex < line.length) { msg = line.substring(index + msgIndex).trim(); + if (msg.length == 0) { + msg = null; + } } return new Annotation.forLint(msg); } @@ -177,25 +182,29 @@ main() { defineSanityTests(); defineLinterEngineTests(); - //defineRuleTests(); + defineRuleTests(); } +AnnotationMatcher matchesAnnotation( + String message, ErrorType type, int lineNumber) => + new AnnotationMatcher(new Annotation(message, type, lineNumber)); + void testRule(String ruleName, File file) { test('$ruleName', () { - var expected = []; + var expected = []; - int lineNumber = 0; + int lineNumber = 1; for (var line in file.readAsLinesSync()) { var annotation = extractAnnotation(line); if (annotation != null) { annotation.lineNumber = lineNumber; - expected.add(annotation); + expected.add(new AnnotationMatcher(annotation)); } ++lineNumber; } - DartLinter driver = new DartLinter(); - driver.enableRule(ruleName); + DartLinter driver = new DartLinter.forRules( + () => [ruleMap[ruleName]].where((rule) => rule != null)); Iterable lints = driver.lintFile(file); @@ -205,10 +214,7 @@ void testRule(String ruleName, File file) { actual.add(new Annotation.forError(error, info.lineInfo)); }); }); - - print(lints); - - expect(actual, unorderedEquals(expected)); + expect(actual, unorderedMatches(expected)); }); } @@ -221,12 +227,13 @@ class Annotation { final ErrorType type; int lineNumber; - Annotation(this.message, this.type); + Annotation(this.message, this.type, this.lineNumber); - Annotation.forError(AnalysisError error, LineInfo lineInfo) - : this(error.message, error.errorCode.type); + Annotation.forError(AnalysisError error, LineInfo lineInfo) : this( + error.message, error.errorCode.type, + lineInfo.getLocation(error.offset).lineNumber); - Annotation.forLint([String message]) : this(message, ErrorType.LINT); + Annotation.forLint([String message]) : this(message, ErrorType.LINT, null); String toString() => '[$type]: "$message" (line: $lineNumber)'; @@ -238,6 +245,29 @@ class Annotation { } } +class AnnotationMatcher extends Matcher { + final Annotation _expected; + AnnotationMatcher(this._expected); + + Description describe(Description description) => + description.addDescriptionOf(_expected); + + bool matches(item, Map matchState) { + return item is Annotation && _matches(item as Annotation); + } + + bool _matches(Annotation other) { + // Only test messages if they're specified in the expectation + if (_expected.message != null) { + if (_expected.message != other.message) { + return false; + } + } + return _expected.type == other.type && + _expected.lineNumber == other.lineNumber; + } +} + class MockLinter extends Linter { VisitorCallback visitorCallback; From 00f00e1a7974903e20fcae8f397ba94ae954886d Mon Sep 17 00:00:00 2001 From: pquitslund Date: Wed, 4 Feb 2015 15:14:47 -0800 Subject: [PATCH 0027/2942] Test data discovery spelunking. --- test/lint_test.dart | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/test/lint_test.dart b/test/lint_test.dart index ad23a2797a9..7c46c116034 100644 --- a/test/lint_test.dart +++ b/test/lint_test.dart @@ -16,7 +16,22 @@ import 'package:dart_lint/src/rules.dart'; import 'package:path/path.dart' as p; import 'package:unittest/unittest.dart'; -const ruleDir = 'test/rules'; +Directory get ruleDir { + + print('CWD: ${Directory.current.path}'); + print('CWD: ${Directory.current.absolute.path}'); + + Directory dir = new Directory('test/rules'); + if (dir.existsSync()) { + return dir; + } + + Directory testDir = new File(Platform.script.path).parent; + Directory rulesDir = new Directory(testDir.path + '/rules'); + + print('trying... ${rulesDir.absolute.path}'); + return rulesDir; +} /// Linter engine tests void defineLinterEngineTests() { @@ -117,7 +132,7 @@ void defineRuleTests() { //TODO: if ruleDir cannot be found print message to set CWD to project root group('rule', () { - for (var entry in new Directory(ruleDir).listSync()) { + for (var entry in ruleDir.listSync()) { if (entry is! File || !entry.path.endsWith('.dart')) continue; var ruleName = p.basenameWithoutExtension(entry.path); testRule(ruleName, entry); From fae2a2e34b37dfec12bd364aa823717594d1a4ad Mon Sep 17 00:00:00 2001 From: pquitslund Date: Wed, 4 Feb 2015 15:20:08 -0800 Subject: [PATCH 0028/2942] Missed test data file. --- test/rules/unnecessary_brace_in_string_interp.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 test/rules/unnecessary_brace_in_string_interp.dart diff --git a/test/rules/unnecessary_brace_in_string_interp.dart b/test/rules/unnecessary_brace_in_string_interp.dart new file mode 100644 index 00000000000..4dbd01470ba --- /dev/null +++ b/test/rules/unnecessary_brace_in_string_interp.dart @@ -0,0 +1,10 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +main(args) { + print('hello'); + print('hello $args'); + print('hello ${args}'); //LINT + print('hello ${args.length}'); +} From 42881a908e8675f3395f4d79fd999ea608f095f5 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Wed, 4 Feb 2015 15:27:38 -0800 Subject: [PATCH 0029/2942] Backed out directory debugging printlns. --- test/lint_test.dart | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/test/lint_test.dart b/test/lint_test.dart index 7c46c116034..82f36930d2d 100644 --- a/test/lint_test.dart +++ b/test/lint_test.dart @@ -16,22 +16,7 @@ import 'package:dart_lint/src/rules.dart'; import 'package:path/path.dart' as p; import 'package:unittest/unittest.dart'; -Directory get ruleDir { - - print('CWD: ${Directory.current.path}'); - print('CWD: ${Directory.current.absolute.path}'); - - Directory dir = new Directory('test/rules'); - if (dir.existsSync()) { - return dir; - } - - Directory testDir = new File(Platform.script.path).parent; - Directory rulesDir = new Directory(testDir.path + '/rules'); - - print('trying... ${rulesDir.absolute.path}'); - return rulesDir; -} +const String ruleDir = 'test/rules'; /// Linter engine tests void defineLinterEngineTests() { @@ -132,7 +117,7 @@ void defineRuleTests() { //TODO: if ruleDir cannot be found print message to set CWD to project root group('rule', () { - for (var entry in ruleDir.listSync()) { + for (var entry in new Directory(ruleDir).listSync()) { if (entry is! File || !entry.path.endsWith('.dart')) continue; var ruleName = p.basenameWithoutExtension(entry.path); testRule(ruleName, entry); From 81d642b4e7075e1db0ce79d2a926db872f1ca9e2 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Wed, 4 Feb 2015 17:06:52 -0800 Subject: [PATCH 0030/2942] Fix for alphanumerics following braces. --- .../unnecessary_brace_in_string_interp.dart | 23 ++++++++++++------- .../unnecessary_brace_in_string_interp.dart | 3 +++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/src/rules/unnecessary_brace_in_string_interp.dart b/lib/src/rules/unnecessary_brace_in_string_interp.dart index fa8e9ee9a86..49ac7e851dc 100644 --- a/lib/src/rules/unnecessary_brace_in_string_interp.dart +++ b/lib/src/rules/unnecessary_brace_in_string_interp.dart @@ -6,21 +6,26 @@ library unnecessary_brace_in_string_interp; import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/error.dart'; +import 'package:analyzer/src/generated/scanner.dart'; import 'package:analyzer/src/services/lint.dart'; -const msg = ''' -Interpolated simple identifiers (not followed by an alphanumeric string) do -not need braces. -'''; +final RegExp alphaNumeric = new RegExp(r'^[a-zA-Z0-9]'); + +const msg = + '''Interpolated simple identifiers (not followed by an alphanumeric string) do +not need braces.'''; const name = 'UnnecessaryBraceInStringInterp'; +bool isAlphaNumeric(Token token) => + token is StringToken && token.lexeme.startsWith(alphaNumeric); + class UnnecessaryBraceInStringInterp extends Linter { @override AstVisitor getVisitor() => new Visitor(reporter); } -class Visitor extends SimpleAstVisitor { +class Visitor extends SimpleAstVisitor { ErrorReporter reporter; Visitor(this.reporter); @@ -28,9 +33,11 @@ class Visitor extends SimpleAstVisitor { visitStringInterpolation(StringInterpolation node) { var expressions = node.elements.where((e) => e is InterpolationExpression); for (InterpolationExpression expression in expressions) { - if (expression.expression is SimpleIdentifier && - expression.rightBracket != null) { - reporter.reportErrorForNode(new LintCode(name, msg), expression, []); + if (expression.expression is SimpleIdentifier) { + Token bracket = expression.rightBracket; + if (bracket != null && !isAlphaNumeric(bracket.next)) { + reporter.reportErrorForNode(new LintCode(name, msg), expression, []); + } } } } diff --git a/test/rules/unnecessary_brace_in_string_interp.dart b/test/rules/unnecessary_brace_in_string_interp.dart index 4dbd01470ba..c5701339b6d 100644 --- a/test/rules/unnecessary_brace_in_string_interp.dart +++ b/test/rules/unnecessary_brace_in_string_interp.dart @@ -5,6 +5,9 @@ main(args) { print('hello'); print('hello $args'); + print('hello $args!'); + print('hello ${args}1'); print('hello ${args}'); //LINT + print('hello ${args}!'); //LINT print('hello ${args.length}'); } From e7c35b3b75334d28c59c20fb4dd63f2bcb8aac33 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Wed, 4 Feb 2015 19:44:08 -0800 Subject: [PATCH 0031/2942] Fix for alphanumerics following braces. BUG=3 R=brianwilkerson@google.com, scheglov@google.com Review URL: https://chromiumcodereview.appspot.com//900093002 --- .../unnecessary_brace_in_string_interp.dart | 23 ++++++++++++------- .../unnecessary_brace_in_string_interp.dart | 3 +++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/src/rules/unnecessary_brace_in_string_interp.dart b/lib/src/rules/unnecessary_brace_in_string_interp.dart index fa8e9ee9a86..49ac7e851dc 100644 --- a/lib/src/rules/unnecessary_brace_in_string_interp.dart +++ b/lib/src/rules/unnecessary_brace_in_string_interp.dart @@ -6,21 +6,26 @@ library unnecessary_brace_in_string_interp; import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/error.dart'; +import 'package:analyzer/src/generated/scanner.dart'; import 'package:analyzer/src/services/lint.dart'; -const msg = ''' -Interpolated simple identifiers (not followed by an alphanumeric string) do -not need braces. -'''; +final RegExp alphaNumeric = new RegExp(r'^[a-zA-Z0-9]'); + +const msg = + '''Interpolated simple identifiers (not followed by an alphanumeric string) do +not need braces.'''; const name = 'UnnecessaryBraceInStringInterp'; +bool isAlphaNumeric(Token token) => + token is StringToken && token.lexeme.startsWith(alphaNumeric); + class UnnecessaryBraceInStringInterp extends Linter { @override AstVisitor getVisitor() => new Visitor(reporter); } -class Visitor extends SimpleAstVisitor { +class Visitor extends SimpleAstVisitor { ErrorReporter reporter; Visitor(this.reporter); @@ -28,9 +33,11 @@ class Visitor extends SimpleAstVisitor { visitStringInterpolation(StringInterpolation node) { var expressions = node.elements.where((e) => e is InterpolationExpression); for (InterpolationExpression expression in expressions) { - if (expression.expression is SimpleIdentifier && - expression.rightBracket != null) { - reporter.reportErrorForNode(new LintCode(name, msg), expression, []); + if (expression.expression is SimpleIdentifier) { + Token bracket = expression.rightBracket; + if (bracket != null && !isAlphaNumeric(bracket.next)) { + reporter.reportErrorForNode(new LintCode(name, msg), expression, []); + } } } } diff --git a/test/rules/unnecessary_brace_in_string_interp.dart b/test/rules/unnecessary_brace_in_string_interp.dart index 4dbd01470ba..c5701339b6d 100644 --- a/test/rules/unnecessary_brace_in_string_interp.dart +++ b/test/rules/unnecessary_brace_in_string_interp.dart @@ -5,6 +5,9 @@ main(args) { print('hello'); print('hello $args'); + print('hello $args!'); + print('hello ${args}1'); print('hello ${args}'); //LINT + print('hello ${args}!'); //LINT print('hello ${args.length}'); } From 06566a741b4b0c385451653d02be34439fb07150 Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Wed, 4 Feb 2015 19:47:23 -0800 Subject: [PATCH 0032/2942] Create CONTRIBUTING.md --- CONTRIBUTING.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..6f5e0ea67d9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +Want to contribute? Great! First, read this page (including the small print at +the end). + +### Before you contribute +Before we can use your code, you must sign the +[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) +(CLA), which you can do online. The CLA is necessary mainly because you own the +copyright to your changes, even after your contribution becomes part of our +codebase, so we need your permission to use and distribute your code. We also +need to be sure of various other things—for instance that you'll tell us if you +know that your code infringes on other people's patents. You don't have to sign +the CLA until after you've submitted your code for review and a member has +approved it, but you must do it before we can put your code into our codebase. + +Before you start working on a larger contribution, you should get in touch with +us first through the issue tracker with your idea so that we can help out and +possibly guide you. Coordinating up front makes it much easier to avoid +frustration later on. + +### Code reviews +All submissions, including submissions by project members, require review. + +### File headers +All files in the project must start with the following header. + + // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file + // for details. All rights reserved. Use of this source code is governed by a + // BSD-style license that can be found in the LICENSE file. + +### The small print +Contributions made by corporations are covered by a different agreement than the +one above, the +[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate). From fdf81619e4cc6bd8377994e32f6053eb59e27a9e Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Wed, 4 Feb 2015 19:50:56 -0800 Subject: [PATCH 0033/2942] Update README.md Added contribution blip. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 5c46ce35b6a..be75e15d635 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,8 @@ A Dart style linter. Please file feature requests and bugs at the [issue tracker][tracker]. [tracker]: https://github.com/dart-lang/cli_util/issues + +### Contributing + +Contributions welcome! Please read the +[contribution guidelines](CONTRIBUTING.md). From bbbe13a1bdb2e6c3603be2a456fe28f583675ec5 Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Wed, 4 Feb 2015 21:07:18 -0800 Subject: [PATCH 0034/2942] Update README.md --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index be75e15d635..7e761a64283 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,14 @@ A Dart style linter. [![Build Status](https://travis-ci.org/dart-lang/dart_lint.svg)](https://travis-ci.org/dart-lang/dart_lint) [![Coverage Status](https://coveralls.io/repos/dart-lang/dart_lint/badge.svg)](https://coveralls.io/r/dart-lang/dart_lint) +## Contributing + +Contributions welcome! Please read the +[contribution guidelines](CONTRIBUTING.md). + ## Features and bugs Please file feature requests and bugs at the [issue tracker][tracker]. [tracker]: https://github.com/dart-lang/cli_util/issues -### Contributing - -Contributions welcome! Please read the -[contribution guidelines](CONTRIBUTING.md). From c634d66163cd90440ff365848392346b492fcc43 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Thu, 5 Feb 2015 15:00:45 -0800 Subject: [PATCH 0035/2942] Dep change: grinder=>cli_util. --- lib/src/analysis.dart | 6 +++--- pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/analysis.dart b/lib/src/analysis.dart index 38d7ee2ac3d..45d1235d355 100644 --- a/lib/src/analysis.dart +++ b/lib/src/analysis.dart @@ -26,7 +26,7 @@ import 'package:analyzer/src/generated/sdk.dart'; import 'package:analyzer/src/generated/sdk_io.dart'; import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/generated/source_io.dart'; -import 'package:grinder/grinder.dart' as grinder; +import 'package:cli_util/cli_util.dart' as cli_util; /// Returns `true` if [AnalysisError] should be printed. typedef bool ErrorFilter(AnalysisError error); @@ -438,8 +438,8 @@ class AnalysisDriver { sdk = new DirectoryBasedDartSdk(new JavaFile(options.dartSdkPath)); } else if (sdk == null) { // In case no SDK has been specified, fall back to inferring it - // TODO: pass args to grinder - Directory sdkDir = grinder.getSdkDir(); + // TODO: pass args to cli_util + Directory sdkDir = cli_util.getSdkDir(); sdk = new DirectoryBasedDartSdk(new JavaFile(sdkDir.path)); } } diff --git a/pubspec.yaml b/pubspec.yaml index 09dadbd844b..fefaf91ab5f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,6 +5,6 @@ description: Style linter for Dart. homepage: https://github.com/dart-lang/dart_lint dependencies: analyzer: '0.23.0-dev.16' - grinder: '>=0.6.0 <0.7.0' + cli_util: '>=0.0.1 <0.1.0' dev_dependencies: unittest: '>=0.11.0 <0.12.0' \ No newline at end of file From f02fd8649f0697ddb17fd3932ba74c631a75f3df Mon Sep 17 00:00:00 2001 From: pquitslund Date: Thu, 5 Feb 2015 16:11:14 -0800 Subject: [PATCH 0036/2942] Dead code cleanup. --- lib/src/analysis.dart | 101 ++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 57 deletions(-) diff --git a/lib/src/analysis.dart b/lib/src/analysis.dart index 45d1235d355..72021be421a 100644 --- a/lib/src/analysis.dart +++ b/lib/src/analysis.dart @@ -20,7 +20,6 @@ import 'package:analyzer/src/error_formatter.dart'; import 'package:analyzer/src/generated/constant.dart'; import 'package:analyzer/src/generated/element.dart'; import 'package:analyzer/src/generated/engine.dart'; -import 'package:analyzer/src/generated/java_core.dart' show JavaSystem; import 'package:analyzer/src/generated/java_io.dart'; import 'package:analyzer/src/generated/sdk.dart'; import 'package:analyzer/src/generated/sdk_io.dart'; @@ -228,7 +227,7 @@ class AnalysisDriver { if (printMode == 1) { _printErrorsAndPerf(); } else if (printMode == 2) { - _printColdPerf(); + //_printColdPerf(); } // compute max severity and set exitCode @@ -301,26 +300,6 @@ class AnalysisDriver { _addLibrarySources(library, libraries, units); } - _printColdPerf() { - // print cold VM performance numbers - int totalTime = JavaSystem.currentTimeMillis() - _startTime; - int ioTime = PerformanceStatistics.io.result; - int scanTime = PerformanceStatistics.scan.result; - int parseTime = PerformanceStatistics.parse.result; - int resolveTime = PerformanceStatistics.resolve.result; - int errorsTime = PerformanceStatistics.errors.result; - int hintsTime = PerformanceStatistics.hints.result; - stdout.writeln("io-cold:$ioTime"); - stdout.writeln("scan-cold:$scanTime"); - stdout.writeln("parse-cold:$parseTime"); - stdout.writeln("resolve-cold:$resolveTime"); - stdout.writeln("errors-cold:$errorsTime"); - stdout.writeln("hints-cold:$hintsTime"); - stdout.writeln("other-cold:${totalTime - - (ioTime + scanTime + parseTime + resolveTime + errorsTime + hintsTime)}"); - stdout.writeln("total-cold:$totalTime"); - } - _printErrorsAndPerf() { // The following is a hack. We currently print out to stderr to ensure that // when in batch mode we print to stderr, this is because the prints from @@ -335,27 +314,51 @@ class AnalysisDriver { new ErrorFormatter(sink, new _OptionsWrapper(_options), isDesiredError); formatter.formatErrors(errorInfos); - // print performance numbers - if (_options.perf || _options.warmPerf) { - int totalTime = JavaSystem.currentTimeMillis() - _startTime; - int ioTime = PerformanceStatistics.io.result; - int scanTime = PerformanceStatistics.scan.result; - int parseTime = PerformanceStatistics.parse.result; - int resolveTime = PerformanceStatistics.resolve.result; - int errorsTime = PerformanceStatistics.errors.result; - int hintsTime = PerformanceStatistics.hints.result; - stdout.writeln("io:$ioTime"); - stdout.writeln("scan:$scanTime"); - stdout.writeln("parse:$parseTime"); - stdout.writeln("resolve:$resolveTime"); - stdout.writeln("errors:$errorsTime"); - stdout.writeln("hints:$hintsTime"); - stdout.writeln("other:${totalTime - - (ioTime + scanTime + parseTime + resolveTime + errorsTime + hintsTime)}"); - stdout.writeln("total:$totalTime"); - } + //_printWarmPerf(); } +// _printColdPerf() { +// // print cold VM performance numbers +// int totalTime = JavaSystem.currentTimeMillis() - _startTime; +// int ioTime = PerformanceStatistics.io.result; +// int scanTime = PerformanceStatistics.scan.result; +// int parseTime = PerformanceStatistics.parse.result; +// int resolveTime = PerformanceStatistics.resolve.result; +// int errorsTime = PerformanceStatistics.errors.result; +// int hintsTime = PerformanceStatistics.hints.result; +// stdout.writeln("io-cold:$ioTime"); +// stdout.writeln("scan-cold:$scanTime"); +// stdout.writeln("parse-cold:$parseTime"); +// stdout.writeln("resolve-cold:$resolveTime"); +// stdout.writeln("errors-cold:$errorsTime"); +// stdout.writeln("hints-cold:$hintsTime"); +// stdout.writeln("other-cold:${totalTime +// - (ioTime + scanTime + parseTime + resolveTime + errorsTime + hintsTime)}"); +// stdout.writeln("total-cold:$totalTime"); +// } + +// _printWarmPerf() { +// // print performance numbers +// if (_options.perf || _options.warmPerf) { +// int totalTime = JavaSystem.currentTimeMillis() - _startTime; +// int ioTime = PerformanceStatistics.io.result; +// int scanTime = PerformanceStatistics.scan.result; +// int parseTime = PerformanceStatistics.parse.result; +// int resolveTime = PerformanceStatistics.resolve.result; +// int errorsTime = PerformanceStatistics.errors.result; +// int hintsTime = PerformanceStatistics.hints.result; +// stdout.writeln("io:$ioTime"); +// stdout.writeln("scan:$scanTime"); +// stdout.writeln("parse:$parseTime"); +// stdout.writeln("resolve:$resolveTime"); +// stdout.writeln("errors:$errorsTime"); +// stdout.writeln("hints:$hintsTime"); +// stdout.writeln("other:${totalTime +// - (ioTime + scanTime + parseTime + resolveTime + errorsTime + hintsTime)}"); +// stdout.writeln("total:$totalTime"); +// } +// } + void _setOptions(AnalysisOptionsImpl analysisOptions) { analysisOptions.cacheSize = _options.cacheSize; analysisOptions.hint = !_options.disableHints; @@ -392,22 +395,6 @@ class AnalysisDriver { static int _currentTimeInMillis() => new DateTime.now().millisecondsSinceEpoch; - static JavaFile _getPackageDirectoryFor(JavaFile sourceFile) { - // we are going to ask parent file, so get absolute path - sourceFile = sourceFile.getAbsoluteFile(); - // look in the containing directories - JavaFile dir = sourceFile.getParentFile(); - while (dir != null) { - JavaFile packagesDir = new JavaFile.relative(dir, "packages"); - if (packagesDir.exists()) { - return packagesDir; - } - dir = dir.getParentFile(); - } - // not found - return null; - } - /// Returns the [Uri] for the given input file. /// Usually it is a `file:` [Uri], but if [file] is located in the `lib` /// directory of the [sdk], then returns a `dart:` [Uri]. From aa09134901d38fe16313f91455da544b3ae373a6 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Thu, 5 Feb 2015 16:57:43 -0800 Subject: [PATCH 0037/2942] Dead code cleanup. --- lib/src/linter.dart | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/src/linter.dart b/lib/src/linter.dart index bc7c4a3c161..5774cc0b5ba 100644 --- a/lib/src/linter.dart +++ b/lib/src/linter.dart @@ -9,7 +9,6 @@ import 'dart:io'; import 'package:analyzer/analyzer.dart'; import 'package:analyzer/src/generated/engine.dart'; import 'package:analyzer/src/generated/java_engine.dart'; -import 'package:analyzer/src/generated/parser.dart'; import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/services/lint.dart'; import 'package:analyzer/src/string_source.dart'; @@ -55,18 +54,8 @@ class LinterException implements Exception { /// Creates a new LinterException with an optional error [message]. const LinterException([this.message]); - LinterException.forError(List errors) - : message = _createMessage(errors); - String toString() => message == null ? "LinterException" : "LinterException: $message"; - - //TODO: revisit - static String _createMessage(errors) { - var errorCode = errors[0].errorCode; - var phase = errorCode is ParserErrorCode ? 'parsing' : 'scanning'; - return 'An error occured while $phase (${errorCode.name}).'; - } } /// Linter options. From f3306daff8b1ebb89dfde8970e28d4a0233489b2 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Thu, 5 Feb 2015 20:03:09 -0800 Subject: [PATCH 0038/2942] More unused code pruning. --- lib/src/analysis.dart | 44 ------------------------------------------- 1 file changed, 44 deletions(-) diff --git a/lib/src/analysis.dart b/lib/src/analysis.dart index 72021be421a..52ef6769da1 100644 --- a/lib/src/analysis.dart +++ b/lib/src/analysis.dart @@ -4,7 +4,6 @@ library analysis; -import 'dart:async'; import 'dart:collection'; import 'dart:io'; @@ -104,13 +103,6 @@ class AnalysisDriver { } } - /// Treats the [sourcePath] as the top level library and analyzes it using an - /// asynchronous algorithm over the analysis engine. - void analyzeAsync() { - _setupForAnalysis(); - _analyzeAsync(); - } - /// Treats the [sourcePath] as the top level library and analyzes it using a /// synchronous algorithm over the analysis engine. If [printMode] is `0`, @@ -173,42 +165,6 @@ class AnalysisDriver { } } - /// The async version of the analysis - void _analyzeAsync() { - new Future(_context.performAnalysisTask).then((AnalysisResult result) { - List notices = result.changeNotices; - if (result.hasMoreWork) { - // There is more work, record the set of sources, and then call self - // again to perform next task - for (ChangeNotice notice in notices) { - _sources.add(notice.source); - sourceErrorsMap[notice.source] = notice; - } - return _analyzeAsync(); - } - - // There are not any more tasks, set error code and print performance - // numbers. - // - // prepare errors - sourceErrorsMap.forEach((k, v) { - errorInfos.add(sourceErrorsMap[k]); - }); - - // print errors and performance numbers - _printErrorsAndPerf(); - - // compute max severity and set exitCode - ErrorSeverity status = maxErrorSeverity; - if (status == ErrorSeverity.WARNING && _options.warningsAreFatal) { - status = ErrorSeverity.ERROR; - } - exitCode = status.ordinal; - }).catchError((ex, st) { - AnalysisEngine.instance.logger.logError("$ex\n$st"); - }); - } - /// The sync version of analysis. ErrorSeverity _analyzeSync(int printMode) { // don't try to analyze parts From 9b932555c9cf7e8d57c45741b8008a25963526aa Mon Sep 17 00:00:00 2001 From: pquitslund Date: Fri, 6 Feb 2015 14:44:06 -0800 Subject: [PATCH 0039/2942] Simple command line driver. --- lib/src/io.dart | 43 +++++++++++++++++ lib/src/linter.dart | 23 ++++++--- pubspec.yaml | 2 +- test/lint_test.dart | 115 ++++++++++++++++++++++++++++++++------------ 4 files changed, 143 insertions(+), 40 deletions(-) create mode 100644 lib/src/io.dart diff --git a/lib/src/io.dart b/lib/src/io.dart new file mode 100644 index 00000000000..e1cc1e1746c --- /dev/null +++ b/lib/src/io.dart @@ -0,0 +1,43 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library io; + +import 'dart:io'; + +import 'package:dart_lint/src/linter.dart'; + + +/// Runs the linter on [file], skipping links and files not ending in the +/// '.dart' extension. +/// +/// Returns `true` if successful or `false` if an error occurred. +bool processFile(File file) { + var path = file.path; + + if (file is Link) { + print('Skipping link $path'); + return false; + } + + if (!path.endsWith('.dart')) { + print('Skipping $path (unsupported extenstion)'); + return false; + } + + DartLinter linter = new DartLinter(); + + try { + linter.lintFile(file); + return true; + } catch (err, stack) { + stderr.writeln('''An error occurred while linting $path + Please report it at: github.com/dart-lang/dart_lint/issues +$err +$stack'''); + } + return false; +} + +bool isDartFile(FileSystemEntity entry) => entry.path.endsWith('.dart'); \ No newline at end of file diff --git a/lib/src/linter.dart b/lib/src/linter.dart index 5774cc0b5ba..78da2c27f35 100644 --- a/lib/src/linter.dart +++ b/lib/src/linter.dart @@ -13,6 +13,7 @@ import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/services/lint.dart'; import 'package:analyzer/src/string_source.dart'; import 'package:dart_lint/src/analysis.dart'; +import 'package:dart_lint/src/rules.dart'; void _registerLinters(Iterable linters) { if (linters != null) { @@ -28,6 +29,7 @@ typedef Iterable RuleSet(); typedef AnalysisDriver _DriverFactory(); + /// Dart source linter. abstract class DartLinter { @@ -39,8 +41,8 @@ abstract class DartLinter { Iterable lintFile(File sourceFile); - Iterable lintLibrarySource( - {String libraryName, String libraryContents}); + Iterable lintLibrarySource({String libraryName, + String libraryContents}); Iterable lintPath(String sourcePath); } @@ -114,17 +116,21 @@ class SourceLinter implements DartLinter, AnalysisErrorListener { final errors = []; final LinterOptions options; final Reporter reporter; - SourceLinter(this.options, {this.reporter: const PrintingReporter()}); + SourceLinter(LinterOptions options, {this.reporter: const PrintingReporter()}) + : this.options = options != null ? options : _defaultOptions(); @override Iterable lintFile(File sourceFile) => _registerAndRun(() => new AnalysisDriver.forFile(sourceFile, options)); @override - Iterable lintLibrarySource( - {String libraryName, String libraryContents}) => _registerAndRun( - () => new AnalysisDriver.forSource( - new _StringSource(libraryContents, libraryName), options)); + Iterable lintLibrarySource({String libraryName, + String libraryContents}) => + _registerAndRun( + () => + new AnalysisDriver.forSource( + new _StringSource(libraryContents, libraryName), + options)); @override Iterable lintPath(String sourcePath) => @@ -137,6 +143,9 @@ class SourceLinter implements DartLinter, AnalysisErrorListener { _registerLinters(options.enabledLints); return createDriver().getErrors(); } + + static LinterOptions _defaultOptions() => + new LinterOptions(() => ruleMap.values); } class _StringSource extends StringSource { diff --git a/pubspec.yaml b/pubspec.yaml index fefaf91ab5f..8485f3f0b94 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ author: Dart Team description: Style linter for Dart. homepage: https://github.com/dart-lang/dart_lint dependencies: - analyzer: '0.23.0-dev.16' + analyzer: '0.23.0' cli_util: '>=0.0.1 <0.1.0' dev_dependencies: unittest: '>=0.11.0 <0.12.0' \ No newline at end of file diff --git a/test/lint_test.dart b/test/lint_test.dart index 82f36930d2d..925e0eca3fe 100644 --- a/test/lint_test.dart +++ b/test/lint_test.dart @@ -9,13 +9,17 @@ import 'dart:io'; import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/engine.dart'; import 'package:analyzer/src/generated/error.dart'; -import 'package:analyzer/src/generated/source_io.dart'; +import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/services/lint.dart'; +import 'package:dart_lint/src/io.dart'; import 'package:dart_lint/src/linter.dart'; import 'package:dart_lint/src/rules.dart'; import 'package:path/path.dart' as p; import 'package:unittest/unittest.dart'; +import '../bin/lint.dart' as dartlint; + + const String ruleDir = 'test/rules'; /// Linter engine tests @@ -25,9 +29,9 @@ void defineLinterEngineTests() { test('duplicate rules', () { var registry = new MockRegistry(); registry - ..registerLinter('r1', new MockLinter()) - ..registerLinter('r1', new MockLinter()) - ..expectWarnings(["Multiple linter rules registered to name 'r1'"]); + ..registerLinter('r1', new MockLinter()) + ..registerLinter('r1', new MockLinter()) + ..expectWarnings(["Multiple linter rules registered to name 'r1'"]); }); test('empty to start', () { var registry = new MockRegistry(); @@ -76,13 +80,19 @@ void defineLinterEngineTests() { }); } - _test('exception', 'EXCEPTION: LinterException: foo', + _test( + 'exception', + 'EXCEPTION: LinterException: foo', (r) => r.exception(new LinterException('foo'))); _test('logError', 'ERROR: foo', (r) => r.logError('foo')); - _test('logError2', 'ERROR: foo', + _test( + 'logError2', + 'ERROR: foo', (r) => r.logError2('foo', new Exception())); _test('logInformation', 'INFO: foo', (r) => r.logInformation('foo')); - _test('logInformation2', 'INFO: foo', + _test( + 'logInformation2', + 'INFO: foo', (r) => r.logInformation2('foo', new Exception())); _test('warn', 'WARN: foo', (r) => r.warn('foo')); }); @@ -93,7 +103,8 @@ void defineLinterEngineTests() { }); test('toString', () { expect(const LinterException().toString(), equals('LinterException')); - expect(const LinterException('foo').toString(), + expect( + const LinterException('foo').toString(), equals('LinterException: foo')); }); }); @@ -103,12 +114,44 @@ void defineLinterEngineTests() { bool visited; var options = new LinterOptions(() => [new MockLinter((n) => visited = true)]); - new SourceLinter(options).lintLibrarySource( - libraryName: 'testLibrary', - libraryContents: 'library testLibrary;'); + new SourceLinter( + options).lintLibrarySource( + libraryName: 'testLibrary', + libraryContents: 'library testLibrary;'); expect(visited, isTrue); }); }); + + group('main', () { + setUp(() => exitCode = 0); + tearDown(() => exitCode = 0); + test('smoke', () { + FileSystemEntity firstRuleTest = + new Directory(ruleDir).listSync().firstWhere((f) => isDartFile(f)); + dartlint.main([firstRuleTest.path]); + expect(dartlint.isLinterErrorCode(exitCode), isFalse); + }); + test('no args', () { + dartlint.main([]); + expect(exitCode, equals(dartlint.unableToProcessExitCode)); + }); + test('help', () { + dartlint.main(['-h']); + // Help shouldn't generate an error code + expect(dartlint.isLinterErrorCode(exitCode), isFalse); + }); + test('unknown arg', () { + dartlint.main(['-XXXXX']); + expect(exitCode, equals(dartlint.unableToProcessExitCode)); + }); + test('bad path', () { + var badPath = new Directory(ruleDir).path + '/___NonExistent.dart'; + dartlint.main([badPath]); + expect(exitCode, equals(dartlint.unableToProcessExitCode)); + }); + }); + + }); } @@ -118,7 +161,7 @@ void defineRuleTests() { //TODO: if ruleDir cannot be found print message to set CWD to project root group('rule', () { for (var entry in new Directory(ruleDir).listSync()) { - if (entry is! File || !entry.path.endsWith('.dart')) continue; + if (entry is! File || !isDartFile(entry)) continue; var ruleName = p.basenameWithoutExtension(entry.path); testRule(ruleName, entry); } @@ -134,7 +177,8 @@ void defineSanityTests() { expect(extractAnnotation('int x; //LINT'), isNotNull); expect(extractAnnotation('int x; // OK'), isNull); expect(extractAnnotation('int x;'), isNull); - expect(extractAnnotation('dynamic x; // LINT dynamic is bad').message, + expect( + extractAnnotation('dynamic x; // LINT dynamic is bad').message, equals('dynamic is bad')); expect(extractAnnotation('dynamic x; //LINT').message, isNull); expect(extractAnnotation('dynamic x; //LINT ').message, isNull); @@ -144,18 +188,28 @@ void defineSanityTests() { expect( new Annotation('Actual message (to be ignored)', ErrorType.LINT, 1), matchesAnnotation(null, ErrorType.LINT, 1)); - expect(new Annotation('Message', ErrorType.LINT, 1), + expect( + new Annotation('Message', ErrorType.LINT, 1), matchesAnnotation('Message', ErrorType.LINT, 1)); }); test('inequality', () { - expect(() => expect(new Annotation('Message', ErrorType.LINT, 1), - matchesAnnotation('Message', ErrorType.HINT, 1)), + expect( + () => + expect( + new Annotation('Message', ErrorType.LINT, 1), + matchesAnnotation('Message', ErrorType.HINT, 1)), throwsA(new isInstanceOf())); - expect(() => expect(new Annotation('Message', ErrorType.LINT, 1), - matchesAnnotation('Message2', ErrorType.LINT, 1)), + expect( + () => + expect( + new Annotation('Message', ErrorType.LINT, 1), + matchesAnnotation('Message2', ErrorType.LINT, 1)), throwsA(new isInstanceOf())); - expect(() => expect(new Annotation('Message', ErrorType.LINT, 1), - matchesAnnotation('Message', ErrorType.LINT, 2)), + expect( + () => + expect( + new Annotation('Message', ErrorType.LINT, 1), + matchesAnnotation('Message', ErrorType.LINT, 2)), throwsA(new isInstanceOf())); }); }); @@ -185,9 +239,9 @@ main() { defineRuleTests(); } -AnnotationMatcher matchesAnnotation( - String message, ErrorType type, int lineNumber) => - new AnnotationMatcher(new Annotation(message, type, lineNumber)); +AnnotationMatcher matchesAnnotation(String message, ErrorType type, + int lineNumber) => + new AnnotationMatcher(new Annotation(message, type, lineNumber)); void testRule(String ruleName, File file) { test('$ruleName', () { @@ -218,10 +272,10 @@ void testRule(String ruleName, File file) { }); } -typedef nodeVisitor(AstNode node); - typedef AstVisitor VisitorCallback(); +typedef nodeVisitor(AstNode node); + class Annotation { final String message; final ErrorType type; @@ -229,8 +283,10 @@ class Annotation { Annotation(this.message, this.type, this.lineNumber); - Annotation.forError(AnalysisError error, LineInfo lineInfo) : this( - error.message, error.errorCode.type, + Annotation.forError(AnalysisError error, LineInfo lineInfo) + : this( + error.message, + error.errorCode.type, lineInfo.getLocation(error.offset).lineNumber); Annotation.forLint([String message]) : this(message, ErrorType.LINT, null); @@ -294,11 +350,6 @@ class MockRegistry extends RuleRegistry { } } -//class MockLinter extends Linter { -// @override -// AstVisitor getVisitor() => null; -//} - class MockReporter extends Reporter { var exceptions = []; var warnings = []; From bf69cf264028e427f233b401fa92eb8d49cdf836 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Fri, 6 Feb 2015 14:49:42 -0800 Subject: [PATCH 0040/2942] Simple command line driver. --- bin/lint.dart | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 bin/lint.dart diff --git a/bin/lint.dart b/bin/lint.dart new file mode 100644 index 00000000000..bc377b7a9b2 --- /dev/null +++ b/bin/lint.dart @@ -0,0 +1,74 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:dart_lint/src/io.dart'; + + +const processFileFailedExitCode = 65; +const unableToProcessExitCode = 64; + +isLinterErrorCode(int code) => + code == unableToProcessExitCode || code == processFileFailedExitCode; + + +void main(List args) { + var parser = new ArgParser(allowTrailingOptions: true); + + parser.addFlag( + "help", + abbr: "h", + negatable: false, + help: "Shows usage information."); + + var options; + try { + options = parser.parse(args); + } on FormatException catch (err) { + printUsage(parser, err.message); + exitCode = unableToProcessExitCode; + return; + } + + if (options["help"]) { + printUsage(parser); + return; + } + + if (options.rest.isEmpty) { + printUsage(parser, "Please provide at least one library file to lint."); + exitCode = unableToProcessExitCode; + return; + } + + for (var path in options.rest) { + var file = new File(path); + if (file.existsSync()) { + print("Linting $path..."); + if (!processFile(file)) { + exitCode = processFileFailedExitCode; + } + } else { + stderr.writeln('No file found at "$path".'); + exitCode = unableToProcessExitCode; + } + } +} + +void printUsage(ArgParser parser, [String error]) { + + var message = "Lints Dart source files."; + if (error != null) { + message = error; + } + + stdout.write('''$message +Usage: dartlint +${parser.usage} + +For more information, see https://github.com/dart-lang/dart_lint +'''); +} From 5284ffe80b07de6e789b8fecac7e12c016e84a05 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Fri, 6 Feb 2015 15:42:34 -0800 Subject: [PATCH 0041/2942] Super goes last Lint. Fix dart-lang/linter#13. --- lib/src/rules.dart | 3 ++- lib/src/rules/super_goes_last.dart | 37 ++++++++++++++++++++++++++++++ test/rules/super_goes_last.dart | 22 ++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 lib/src/rules/super_goes_last.dart create mode 100644 test/rules/super_goes_last.dart diff --git a/lib/src/rules.dart b/lib/src/rules.dart index 9fc787c4975..e1e004351c7 100644 --- a/lib/src/rules.dart +++ b/lib/src/rules.dart @@ -6,11 +6,12 @@ library rules; import 'package:analyzer/src/services/lint.dart'; import 'package:dart_lint/src/linter.dart'; +import 'package:dart_lint/src/rules/super_goes_last.dart'; import 'package:dart_lint/src/rules/unnecessary_brace_in_string_interp.dart'; /// Map of contributed lint rules. final Map ruleMap = { - //'super_goes_last': new SuperGoesLastLint() + 'super_goes_last': new SuperGoesLast(), 'unnecessary_brace_in_string_interp': new UnnecessaryBraceInStringInterp() }; diff --git a/lib/src/rules/super_goes_last.dart b/lib/src/rules/super_goes_last.dart new file mode 100644 index 00000000000..39fba5fcc8e --- /dev/null +++ b/lib/src/rules/super_goes_last.dart @@ -0,0 +1,37 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library super_goes_last; + +import 'package:analyzer/src/generated/ast.dart'; +import 'package:analyzer/src/generated/error.dart'; +import 'package:analyzer/src/services/lint.dart'; + +const msg = + 'DO place the super() call last in a constructor initialization list.'; + +const name = 'SuperGoesLast'; + +class SuperGoesLast extends Linter { + @override + AstVisitor getVisitor() => new Visitor(reporter); +} + +class Visitor extends SimpleAstVisitor { + ErrorReporter reporter; + + Visitor(this.reporter); + + @override + visitConstructorDeclaration(ConstructorDeclaration node) { + var last = node.initializers.length - 1; + + for (int i = 0; i <= last; ++i) { + ConstructorInitializer init = node.initializers[i]; + if (init is SuperConstructorInvocation && i != last) { + reporter.reportErrorForNode(new LintCode(name, msg), init, []); + } + } + } +} diff --git a/test/rules/super_goes_last.dart b/test/rules/super_goes_last.dart new file mode 100644 index 00000000000..6f8959e2241 --- /dev/null +++ b/test/rules/super_goes_last.dart @@ -0,0 +1,22 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +class A { + int a; + A(this.a); +} + +class B extends A { + int _b; + B(int a) + : _b = a + 1, + super(a); // OK +} + +class C extends A { + int _c; + C(int a) + : super(a), // LINT + _c = a + 1; +} From e2eb887e7aad13e31f3616735802810f91817ac6 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Sat, 7 Feb 2015 09:49:02 -0800 Subject: [PATCH 0042/2942] Msg fix for style guide consistency. --- lib/src/rules/unnecessary_brace_in_string_interp.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/src/rules/unnecessary_brace_in_string_interp.dart b/lib/src/rules/unnecessary_brace_in_string_interp.dart index 49ac7e851dc..ac72eea7bbe 100644 --- a/lib/src/rules/unnecessary_brace_in_string_interp.dart +++ b/lib/src/rules/unnecessary_brace_in_string_interp.dart @@ -11,9 +11,7 @@ import 'package:analyzer/src/services/lint.dart'; final RegExp alphaNumeric = new RegExp(r'^[a-zA-Z0-9]'); -const msg = - '''Interpolated simple identifiers (not followed by an alphanumeric string) do -not need braces.'''; +const msg ='AVOID bracketed interpolation of simple identifiers'; const name = 'UnnecessaryBraceInStringInterp'; From b96be3a59334b5ea0e1de769adc28037999192a6 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Sat, 7 Feb 2015 10:02:08 -0800 Subject: [PATCH 0043/2942] Lint for braces in empty constructor bodies. Fix dart-lang/linter#12. --- lib/src/rules.dart | 2 ++ lib/src/rules/empty_constructor_bodies.dart | 34 +++++++++++++++++++++ test/rules/empty_constructor_bodies.dart | 22 +++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 lib/src/rules/empty_constructor_bodies.dart create mode 100644 test/rules/empty_constructor_bodies.dart diff --git a/lib/src/rules.dart b/lib/src/rules.dart index e1e004351c7..cc9094bd03a 100644 --- a/lib/src/rules.dart +++ b/lib/src/rules.dart @@ -6,11 +6,13 @@ library rules; import 'package:analyzer/src/services/lint.dart'; import 'package:dart_lint/src/linter.dart'; +import 'package:dart_lint/src/rules/empty_constructor_bodies.dart'; import 'package:dart_lint/src/rules/super_goes_last.dart'; import 'package:dart_lint/src/rules/unnecessary_brace_in_string_interp.dart'; /// Map of contributed lint rules. final Map ruleMap = { + 'empty_constructor_bodies' : new EmptyConstructorBodies(), 'super_goes_last': new SuperGoesLast(), 'unnecessary_brace_in_string_interp': new UnnecessaryBraceInStringInterp() }; diff --git a/lib/src/rules/empty_constructor_bodies.dart b/lib/src/rules/empty_constructor_bodies.dart new file mode 100644 index 00000000000..50ab0e0a35d --- /dev/null +++ b/lib/src/rules/empty_constructor_bodies.dart @@ -0,0 +1,34 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library empty_constructor_bodies; + +import 'package:analyzer/src/generated/ast.dart'; +import 'package:analyzer/src/generated/error.dart'; +import 'package:analyzer/src/services/lint.dart'; + +const msg = 'DO use ; instead of {} for empty constructor bodies.'; + +const name = 'EmptyConstructorBodies'; + +class EmptyConstructorBodies extends Linter { + @override + AstVisitor getVisitor() => new Visitor(reporter); +} + +class Visitor extends SimpleAstVisitor { + ErrorReporter reporter; + + Visitor(this.reporter); + + @override + visitConstructorDeclaration(ConstructorDeclaration node) { + if (node.body is BlockFunctionBody) { + Block block = (node.body as BlockFunctionBody).block; + if (block.statements.length == 0) { + reporter.reportErrorForNode(new LintCode(name, msg), block, []); + } + } + } +} diff --git a/test/rules/empty_constructor_bodies.dart b/test/rules/empty_constructor_bodies.dart new file mode 100644 index 00000000000..ce26d7ce85c --- /dev/null +++ b/test/rules/empty_constructor_bodies.dart @@ -0,0 +1,22 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +class A { + int a; + A(this.a); +} + +class B { + B() {} //LINT +} + +class C { + C(); +} + +class D { + D() { + print('hi'); + } +} From 8c6adb2e987d50f2dec100a19d420896501f7871 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Sun, 8 Feb 2015 12:44:09 -0800 Subject: [PATCH 0044/2942] Added LintRule base class. --- lib/src/linter.dart | 101 ++++++++++++++++-- lib/src/rules.dart | 4 +- lib/src/rules/empty_constructor_bodies.dart | 4 +- lib/src/rules/super_goes_last.dart | 4 +- .../unnecessary_brace_in_string_interp.dart | 4 +- test/lint_test.dart | 100 ++++++++--------- 6 files changed, 150 insertions(+), 67 deletions(-) diff --git a/lib/src/linter.dart b/lib/src/linter.dart index 78da2c27f35..14e1df25b46 100644 --- a/lib/src/linter.dart +++ b/lib/src/linter.dart @@ -15,6 +15,15 @@ import 'package:analyzer/src/string_source.dart'; import 'package:dart_lint/src/analysis.dart'; import 'package:dart_lint/src/rules.dart'; +final _camelCaseMatcher = new RegExp('[A-Z][a-z]*'); + +final _camelCaseTester = new RegExp('([A-Z]+[a-z0-9]+)+'); + +String _humanize(String camelCase) => + _camelCaseMatcher.allMatches(camelCase).map((m) => m.group(0)).join(' '); + +bool _isCamelCase(String name) => _camelCaseTester.hasMatch(name); + void _registerLinters(Iterable linters) { if (linters != null) { LintGenerator.LINTERS.clear(); @@ -25,10 +34,21 @@ void _registerLinters(Iterable linters) { typedef Printer(String msg); /// Describes a set of enabled rules. -typedef Iterable RuleSet(); +typedef Iterable RuleSet(); typedef AnalysisDriver _DriverFactory(); +/// Describes a String in valid camel case format. +class CamelCaseString { + final String value; + CamelCaseString(this.value) { + if (!_isCamelCase(value)) { + throw new ArgumentError('$value is not CamelCase'); + } + } + + String get humanized => _humanize(value); +} /// Dart source linter. abstract class DartLinter { @@ -41,12 +61,64 @@ abstract class DartLinter { Iterable lintFile(File sourceFile); - Iterable lintLibrarySource({String libraryName, - String libraryContents}); + Iterable lintLibrarySource( + {String libraryName, String libraryContents}); Iterable lintPath(String sourcePath); } +class Group { + + /// Defined rule groups. + static final Group STYLE_GUIDE = new Group._('Style Guide'); + + final String name; + final bool custom; + factory Group(String name) { + switch (name) { + case 'Styleguide': + case 'Style Guide': + return STYLE_GUIDE; + default: + return new Group._(name, custom: true); + } + } + + Group._(this.name, {this.custom: false}); +} +class Kind { + + /// Defined rule kinds. + static final Kind DO = new Kind._('Do'); + static final Kind DONT = new Kind._("Don't"); + static final Kind PREFER = new Kind._('Prefer'); + static final Kind AVOID = new Kind._('Avoid'); + static final Kind CONSIDER = new Kind._('Consider'); + + final String name; + final bool custom; + factory Kind(String name) { + var label = name.toUpperCase(); + switch (label) { + case 'DO': + return DO; + case 'DONT': + case "DON'T": + return DONT; + case 'PREFER': + return PREFER; + case 'AVOID': + return AVOID; + case 'CONSIDER': + return CONSIDER; + default: + return new Kind._(label, custom: true); + } + } + + Kind._(this.name, {this.custom: false}); +} + /// Thrown when an error occurs in linting. class LinterException implements Exception { @@ -70,6 +142,18 @@ class LinterOptions extends DriverOptions { Iterable get enabledLints => _enabledLints(); } +/// Describes a lint rule. +abstract class LintRule extends Linter { + /// Lint description (in markdown format). + String description; + /// Lint group (for example, 'Style Guide') + Group group; + /// Lint kind (DO|DON'T|PREFER|AVOID|CONSIDER) + Kind kind; + /// Lint name. + CamelCaseString name; +} + class PrintingReporter implements Reporter, Logger { final Printer _print; @@ -124,13 +208,10 @@ class SourceLinter implements DartLinter, AnalysisErrorListener { _registerAndRun(() => new AnalysisDriver.forFile(sourceFile, options)); @override - Iterable lintLibrarySource({String libraryName, - String libraryContents}) => - _registerAndRun( - () => - new AnalysisDriver.forSource( - new _StringSource(libraryContents, libraryName), - options)); + Iterable lintLibrarySource( + {String libraryName, String libraryContents}) => _registerAndRun( + () => new AnalysisDriver.forSource( + new _StringSource(libraryContents, libraryName), options)); @override Iterable lintPath(String sourcePath) => diff --git a/lib/src/rules.dart b/lib/src/rules.dart index cc9094bd03a..a477f04889f 100644 --- a/lib/src/rules.dart +++ b/lib/src/rules.dart @@ -11,8 +11,8 @@ import 'package:dart_lint/src/rules/super_goes_last.dart'; import 'package:dart_lint/src/rules/unnecessary_brace_in_string_interp.dart'; /// Map of contributed lint rules. -final Map ruleMap = { - 'empty_constructor_bodies' : new EmptyConstructorBodies(), +final Map ruleMap = { + 'empty_constructor_bodies': new EmptyConstructorBodies(), 'super_goes_last': new SuperGoesLast(), 'unnecessary_brace_in_string_interp': new UnnecessaryBraceInStringInterp() }; diff --git a/lib/src/rules/empty_constructor_bodies.dart b/lib/src/rules/empty_constructor_bodies.dart index 50ab0e0a35d..8f073e56154 100644 --- a/lib/src/rules/empty_constructor_bodies.dart +++ b/lib/src/rules/empty_constructor_bodies.dart @@ -6,13 +6,13 @@ library empty_constructor_bodies; import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/error.dart'; -import 'package:analyzer/src/services/lint.dart'; +import 'package:dart_lint/src/linter.dart'; const msg = 'DO use ; instead of {} for empty constructor bodies.'; const name = 'EmptyConstructorBodies'; -class EmptyConstructorBodies extends Linter { +class EmptyConstructorBodies extends LintRule { @override AstVisitor getVisitor() => new Visitor(reporter); } diff --git a/lib/src/rules/super_goes_last.dart b/lib/src/rules/super_goes_last.dart index 39fba5fcc8e..611360e48a8 100644 --- a/lib/src/rules/super_goes_last.dart +++ b/lib/src/rules/super_goes_last.dart @@ -6,14 +6,14 @@ library super_goes_last; import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/error.dart'; -import 'package:analyzer/src/services/lint.dart'; +import 'package:dart_lint/src/linter.dart'; const msg = 'DO place the super() call last in a constructor initialization list.'; const name = 'SuperGoesLast'; -class SuperGoesLast extends Linter { +class SuperGoesLast extends LintRule { @override AstVisitor getVisitor() => new Visitor(reporter); } diff --git a/lib/src/rules/unnecessary_brace_in_string_interp.dart b/lib/src/rules/unnecessary_brace_in_string_interp.dart index ac72eea7bbe..65fc7880134 100644 --- a/lib/src/rules/unnecessary_brace_in_string_interp.dart +++ b/lib/src/rules/unnecessary_brace_in_string_interp.dart @@ -7,7 +7,7 @@ library unnecessary_brace_in_string_interp; import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/error.dart'; import 'package:analyzer/src/generated/scanner.dart'; -import 'package:analyzer/src/services/lint.dart'; +import 'package:dart_lint/src/linter.dart'; final RegExp alphaNumeric = new RegExp(r'^[a-zA-Z0-9]'); @@ -18,7 +18,7 @@ const name = 'UnnecessaryBraceInStringInterp'; bool isAlphaNumeric(Token token) => token is StringToken && token.lexeme.startsWith(alphaNumeric); -class UnnecessaryBraceInStringInterp extends Linter { +class UnnecessaryBraceInStringInterp extends LintRule { @override AstVisitor getVisitor() => new Visitor(reporter); } diff --git a/test/lint_test.dart b/test/lint_test.dart index 925e0eca3fe..91cf759dc24 100644 --- a/test/lint_test.dart +++ b/test/lint_test.dart @@ -19,7 +19,6 @@ import 'package:unittest/unittest.dart'; import '../bin/lint.dart' as dartlint; - const String ruleDir = 'test/rules'; /// Linter engine tests @@ -29,9 +28,9 @@ void defineLinterEngineTests() { test('duplicate rules', () { var registry = new MockRegistry(); registry - ..registerLinter('r1', new MockLinter()) - ..registerLinter('r1', new MockLinter()) - ..expectWarnings(["Multiple linter rules registered to name 'r1'"]); + ..registerLinter('r1', new MockLinter()) + ..registerLinter('r1', new MockLinter()) + ..expectWarnings(["Multiple linter rules registered to name 'r1'"]); }); test('empty to start', () { var registry = new MockRegistry(); @@ -80,19 +79,13 @@ void defineLinterEngineTests() { }); } - _test( - 'exception', - 'EXCEPTION: LinterException: foo', + _test('exception', 'EXCEPTION: LinterException: foo', (r) => r.exception(new LinterException('foo'))); _test('logError', 'ERROR: foo', (r) => r.logError('foo')); - _test( - 'logError2', - 'ERROR: foo', + _test('logError2', 'ERROR: foo', (r) => r.logError2('foo', new Exception())); _test('logInformation', 'INFO: foo', (r) => r.logInformation('foo')); - _test( - 'logInformation2', - 'INFO: foo', + _test('logInformation2', 'INFO: foo', (r) => r.logInformation2('foo', new Exception())); _test('warn', 'WARN: foo', (r) => r.warn('foo')); }); @@ -103,21 +96,45 @@ void defineLinterEngineTests() { }); test('toString', () { expect(const LinterException().toString(), equals('LinterException')); - expect( - const LinterException('foo').toString(), + expect(const LinterException('foo').toString(), equals('LinterException: foo')); }); }); + group('camel case', () { + test('humanize', () { + expect(new CamelCaseString('FooBar').humanized, equals('Foo Bar')); + expect(new CamelCaseString('Foo').humanized, equals('Foo')); + }); + test('validation', () { + expect(() => new CamelCaseString('foo'), + throwsA(new isInstanceOf())); + }); + }); + + group('groups', () { + test('factory', () { + expect(new Group('Style Guide').custom, isFalse); + expect(new Group('Styleguide').custom, isFalse); + expect(new Group('Kustom').custom, isTrue); + }); + }); + + group('kinds', () { + test('factory', () { + var kinds = ['DO', 'DONT', "DON'T", 'AVOID', 'PREFER']; + kinds.map((n) => new Kind(n)).forEach((k) => expect(k.custom, isFalse)); + }); + }); + group('lint driver', () { test('basic', () { bool visited; var options = new LinterOptions(() => [new MockLinter((n) => visited = true)]); - new SourceLinter( - options).lintLibrarySource( - libraryName: 'testLibrary', - libraryContents: 'library testLibrary;'); + new SourceLinter(options).lintLibrarySource( + libraryName: 'testLibrary', + libraryContents: 'library testLibrary;'); expect(visited, isTrue); }); }); @@ -150,8 +167,6 @@ void defineLinterEngineTests() { expect(exitCode, equals(dartlint.unableToProcessExitCode)); }); }); - - }); } @@ -177,8 +192,7 @@ void defineSanityTests() { expect(extractAnnotation('int x; //LINT'), isNotNull); expect(extractAnnotation('int x; // OK'), isNull); expect(extractAnnotation('int x;'), isNull); - expect( - extractAnnotation('dynamic x; // LINT dynamic is bad').message, + expect(extractAnnotation('dynamic x; // LINT dynamic is bad').message, equals('dynamic is bad')); expect(extractAnnotation('dynamic x; //LINT').message, isNull); expect(extractAnnotation('dynamic x; //LINT ').message, isNull); @@ -188,28 +202,18 @@ void defineSanityTests() { expect( new Annotation('Actual message (to be ignored)', ErrorType.LINT, 1), matchesAnnotation(null, ErrorType.LINT, 1)); - expect( - new Annotation('Message', ErrorType.LINT, 1), + expect(new Annotation('Message', ErrorType.LINT, 1), matchesAnnotation('Message', ErrorType.LINT, 1)); }); test('inequality', () { - expect( - () => - expect( - new Annotation('Message', ErrorType.LINT, 1), - matchesAnnotation('Message', ErrorType.HINT, 1)), + expect(() => expect(new Annotation('Message', ErrorType.LINT, 1), + matchesAnnotation('Message', ErrorType.HINT, 1)), throwsA(new isInstanceOf())); - expect( - () => - expect( - new Annotation('Message', ErrorType.LINT, 1), - matchesAnnotation('Message2', ErrorType.LINT, 1)), + expect(() => expect(new Annotation('Message', ErrorType.LINT, 1), + matchesAnnotation('Message2', ErrorType.LINT, 1)), throwsA(new isInstanceOf())); - expect( - () => - expect( - new Annotation('Message', ErrorType.LINT, 1), - matchesAnnotation('Message', ErrorType.LINT, 2)), + expect(() => expect(new Annotation('Message', ErrorType.LINT, 1), + matchesAnnotation('Message', ErrorType.LINT, 2)), throwsA(new isInstanceOf())); }); }); @@ -239,9 +243,9 @@ main() { defineRuleTests(); } -AnnotationMatcher matchesAnnotation(String message, ErrorType type, - int lineNumber) => - new AnnotationMatcher(new Annotation(message, type, lineNumber)); +AnnotationMatcher matchesAnnotation( + String message, ErrorType type, int lineNumber) => + new AnnotationMatcher(new Annotation(message, type, lineNumber)); void testRule(String ruleName, File file) { test('$ruleName', () { @@ -272,10 +276,10 @@ void testRule(String ruleName, File file) { }); } -typedef AstVisitor VisitorCallback(); - typedef nodeVisitor(AstNode node); +typedef AstVisitor VisitorCallback(); + class Annotation { final String message; final ErrorType type; @@ -283,10 +287,8 @@ class Annotation { Annotation(this.message, this.type, this.lineNumber); - Annotation.forError(AnalysisError error, LineInfo lineInfo) - : this( - error.message, - error.errorCode.type, + Annotation.forError(AnalysisError error, LineInfo lineInfo) : this( + error.message, error.errorCode.type, lineInfo.getLocation(error.offset).lineNumber); Annotation.forLint([String message]) : this(message, ErrorType.LINT, null); From 035cc30ec44a7f1821892feb92d66120681eda51 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Sun, 8 Feb 2015 12:51:04 -0800 Subject: [PATCH 0045/2942] Missed test case. --- test/lint_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lint_test.dart b/test/lint_test.dart index 91cf759dc24..4a5bf7190c2 100644 --- a/test/lint_test.dart +++ b/test/lint_test.dart @@ -122,7 +122,7 @@ void defineLinterEngineTests() { group('kinds', () { test('factory', () { - var kinds = ['DO', 'DONT', "DON'T", 'AVOID', 'PREFER']; + var kinds = ['DO', 'DONT', "DON'T", 'AVOID', 'PREFER', 'CONSIDER']; kinds.map((n) => new Kind(n)).forEach((k) => expect(k.custom, isFalse)); }); }); From fec65b05c39e7e55f0b2ad3eccadcc0f5b1b6457 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Sun, 8 Feb 2015 20:07:38 -0800 Subject: [PATCH 0046/2942] LintRule and registry rethink. --- lib/src/linter.dart | 31 ++++--- lib/src/rules.dart | 52 ----------- lib/src/rules/empty_constructor_bodies.dart | 23 +++-- lib/src/rules/super_goes_last.dart | 20 +++-- .../unnecessary_brace_in_string_interp.dart | 19 ++-- pubspec.yaml | 1 + test/lint_test.dart | 86 ++----------------- 7 files changed, 69 insertions(+), 163 deletions(-) diff --git a/lib/src/linter.dart b/lib/src/linter.dart index 14e1df25b46..c1953db52b9 100644 --- a/lib/src/linter.dart +++ b/lib/src/linter.dart @@ -61,8 +61,8 @@ abstract class DartLinter { Iterable lintFile(File sourceFile); - Iterable lintLibrarySource( - {String libraryName, String libraryContents}); + Iterable lintLibrarySource({String libraryName, + String libraryContents}); Iterable lintPath(String sourcePath); } @@ -144,14 +144,20 @@ class LinterOptions extends DriverOptions { /// Describes a lint rule. abstract class LintRule extends Linter { - /// Lint description (in markdown format). - String description; + + /// Longer description (in markdown format). + final String details; + /// Short description + final String description; /// Lint group (for example, 'Style Guide') - Group group; + final Group group; /// Lint kind (DO|DON'T|PREFER|AVOID|CONSIDER) - Kind kind; + final Kind kind; /// Lint name. - CamelCaseString name; + final CamelCaseString name; + + LintRule({String name, this.group, this.kind, this.description, this.details}) + : name = new CamelCaseString(name); } class PrintingReporter implements Reporter, Logger { @@ -208,10 +214,13 @@ class SourceLinter implements DartLinter, AnalysisErrorListener { _registerAndRun(() => new AnalysisDriver.forFile(sourceFile, options)); @override - Iterable lintLibrarySource( - {String libraryName, String libraryContents}) => _registerAndRun( - () => new AnalysisDriver.forSource( - new _StringSource(libraryContents, libraryName), options)); + Iterable lintLibrarySource({String libraryName, + String libraryContents}) => + _registerAndRun( + () => + new AnalysisDriver.forSource( + new _StringSource(libraryContents, libraryName), + options)); @override Iterable lintPath(String sourcePath) => diff --git a/lib/src/rules.dart b/lib/src/rules.dart index a477f04889f..359bd278305 100644 --- a/lib/src/rules.dart +++ b/lib/src/rules.dart @@ -4,7 +4,6 @@ library rules; -import 'package:analyzer/src/services/lint.dart'; import 'package:dart_lint/src/linter.dart'; import 'package:dart_lint/src/rules/empty_constructor_bodies.dart'; import 'package:dart_lint/src/rules/super_goes_last.dart'; @@ -16,54 +15,3 @@ final Map ruleMap = { 'super_goes_last': new SuperGoesLast(), 'unnecessary_brace_in_string_interp': new UnnecessaryBraceInStringInterp() }; - -class Rule { - - /// Whether this rule is enabled in the current rule set. - bool enabled; - /// A uniquely identifying name for this rule. - final String ruleName; - /// The associated linter. - final Linter linter; - - Rule(this.ruleName, this.linter, {this.enabled: false}); -} - -class RuleRegistry { - final Reporter reporter; - final Map _ruleMap = {}; - - RuleRegistry([this.reporter = const PrintingReporter()]) { - // Register rules - ruleMap.forEach((n, l) => registerLinter(n, l)); - } - - /// An empty registry for testing - RuleRegistry.empty(this.reporter); - - Iterable get enabledLints => - _ruleMap.values.where((Rule r) => r.enabled).map((Rule r) => r.linter); - - void disable(String ruleName) { - if (_ruleMap[ruleName] == null) { - reporter.warn("No rule registered to '$ruleName', cannot disable"); - } else { - _ruleMap[ruleName].enabled = false; - } - } - - void enable(String ruleName) { - if (_ruleMap[ruleName] == null) { - reporter.warn("No rule registered to '$ruleName', cannot enable"); - } else { - _ruleMap[ruleName].enabled = true; - } - } - - void registerLinter(String name, Linter linter) { - if (_ruleMap[name] != null) { - reporter.warn("Multiple linter rules registered to name '$name'"); - } - _ruleMap[name] = new Rule(name, linter); - } -} diff --git a/lib/src/rules/empty_constructor_bodies.dart b/lib/src/rules/empty_constructor_bodies.dart index 8f073e56154..ded6f070b82 100644 --- a/lib/src/rules/empty_constructor_bodies.dart +++ b/lib/src/rules/empty_constructor_bodies.dart @@ -8,26 +8,35 @@ import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/error.dart'; import 'package:dart_lint/src/linter.dart'; -const msg = 'DO use ; instead of {} for empty constructor bodies.'; - -const name = 'EmptyConstructorBodies'; +const desc = 'DO use ; instead of {} for empty constructor bodies.'; class EmptyConstructorBodies extends LintRule { + + EmptyConstructorBodies() + : super( + name: 'EmptyConstructorBodies', + description: desc, + group: Group.STYLE_GUIDE, + kind: Kind.DO); + @override - AstVisitor getVisitor() => new Visitor(reporter); + AstVisitor getVisitor() => new Visitor(this); } class Visitor extends SimpleAstVisitor { - ErrorReporter reporter; + LintRule rule; - Visitor(this.reporter); + Visitor(this.rule); @override visitConstructorDeclaration(ConstructorDeclaration node) { if (node.body is BlockFunctionBody) { Block block = (node.body as BlockFunctionBody).block; if (block.statements.length == 0) { - reporter.reportErrorForNode(new LintCode(name, msg), block, []); + rule.reporter.reportErrorForNode( + new LintCode(rule.name.value, rule.description), + block, + []); } } } diff --git a/lib/src/rules/super_goes_last.dart b/lib/src/rules/super_goes_last.dart index 611360e48a8..58b3c11b8b6 100644 --- a/lib/src/rules/super_goes_last.dart +++ b/lib/src/rules/super_goes_last.dart @@ -8,20 +8,26 @@ import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/error.dart'; import 'package:dart_lint/src/linter.dart'; -const msg = +const desc = 'DO place the super() call last in a constructor initialization list.'; -const name = 'SuperGoesLast'; - class SuperGoesLast extends LintRule { + + SuperGoesLast() + : super( + name: 'SuperGoesLast', + description: desc, + group: Group.STYLE_GUIDE, + kind: Kind.DO); + @override - AstVisitor getVisitor() => new Visitor(reporter); + AstVisitor getVisitor() => new Visitor(this); } class Visitor extends SimpleAstVisitor { - ErrorReporter reporter; + LintRule rule; - Visitor(this.reporter); + Visitor(this.rule); @override visitConstructorDeclaration(ConstructorDeclaration node) { @@ -30,7 +36,7 @@ class Visitor extends SimpleAstVisitor { for (int i = 0; i <= last; ++i) { ConstructorInitializer init = node.initializers[i]; if (init is SuperConstructorInvocation && i != last) { - reporter.reportErrorForNode(new LintCode(name, msg), init, []); + rule.reporter.reportErrorForNode(new LintCode(rule.name.value, rule.description), init, []); } } } diff --git a/lib/src/rules/unnecessary_brace_in_string_interp.dart b/lib/src/rules/unnecessary_brace_in_string_interp.dart index 65fc7880134..4fd550ada47 100644 --- a/lib/src/rules/unnecessary_brace_in_string_interp.dart +++ b/lib/src/rules/unnecessary_brace_in_string_interp.dart @@ -11,21 +11,25 @@ import 'package:dart_lint/src/linter.dart'; final RegExp alphaNumeric = new RegExp(r'^[a-zA-Z0-9]'); -const msg ='AVOID bracketed interpolation of simple identifiers'; - -const name = 'UnnecessaryBraceInStringInterp'; +const desc = 'AVOID bracketed interpolation of simple identifiers'; bool isAlphaNumeric(Token token) => token is StringToken && token.lexeme.startsWith(alphaNumeric); class UnnecessaryBraceInStringInterp extends LintRule { + UnnecessaryBraceInStringInterp() : super( + name: 'UnnecessaryBraceInStringInterp', + description: desc, + group: Group.STYLE_GUIDE, + kind: Kind.AVOID); + @override - AstVisitor getVisitor() => new Visitor(reporter); + AstVisitor getVisitor() => new Visitor(this); } class Visitor extends SimpleAstVisitor { - ErrorReporter reporter; - Visitor(this.reporter); + LintRule rule; + Visitor(this.rule); @override visitStringInterpolation(StringInterpolation node) { @@ -34,7 +38,8 @@ class Visitor extends SimpleAstVisitor { if (expression.expression is SimpleIdentifier) { Token bracket = expression.rightBracket; if (bracket != null && !isAlphaNumeric(bracket.next)) { - reporter.reportErrorForNode(new LintCode(name, msg), expression, []); + rule.reporter.reportErrorForNode( + new LintCode(rule.name.value, rule.description), expression, []); } } } diff --git a/pubspec.yaml b/pubspec.yaml index 8485f3f0b94..c98a0014337 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,4 +7,5 @@ dependencies: analyzer: '0.23.0' cli_util: '>=0.0.1 <0.1.0' dev_dependencies: + markdown: '>=0.7.1+2 <0.8.0' unittest: '>=0.11.0 <0.12.0' \ No newline at end of file diff --git a/test/lint_test.dart b/test/lint_test.dart index 4a5bf7190c2..138e1914489 100644 --- a/test/lint_test.dart +++ b/test/lint_test.dart @@ -10,7 +10,6 @@ import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/engine.dart'; import 'package:analyzer/src/generated/error.dart'; import 'package:analyzer/src/generated/source.dart'; -import 'package:analyzer/src/services/lint.dart'; import 'package:dart_lint/src/io.dart'; import 'package:dart_lint/src/linter.dart'; import 'package:dart_lint/src/rules.dart'; @@ -24,51 +23,6 @@ const String ruleDir = 'test/rules'; /// Linter engine tests void defineLinterEngineTests() { group('engine', () { - group('registry', () { - test('duplicate rules', () { - var registry = new MockRegistry(); - registry - ..registerLinter('r1', new MockLinter()) - ..registerLinter('r1', new MockLinter()) - ..expectWarnings(["Multiple linter rules registered to name 'r1'"]); - }); - test('empty to start', () { - var registry = new MockRegistry(); - expect(registry.enabledLints, isEmpty); - }); - test('new entries disabled by default', () { - var registry = new MockRegistry(); - registry.registerLinter('my_first_lint', new MockLinter()); - expect(registry.enabledLints, isEmpty); - }); - test('enablement', () { - var registry = new MockRegistry(); - var linter = new MockLinter(); - registry.registerLinter('my_first_lint', linter); - registry.enable('my_first_lint'); - expect(registry.enabledLints, contains(linter)); - }); - test('enablement - unregistered', () { - var registry = new MockRegistry(); - registry.enable('unknown_rule'); - registry.expectWarnings( - ["No rule registered to 'unknown_rule', cannot enable"]); - }); - test('disablement', () { - var registry = new MockRegistry(); - var linter = new MockLinter(); - registry.registerLinter('my_first_lint', linter); - registry.disable('my_first_lint'); - expect(registry.enabledLints, isEmpty); - }); - test('disablement - unregistered', () { - var registry = new MockRegistry(); - registry.disable('unknown_rule'); - registry.expectWarnings( - ["No rule registered to 'unknown_rule', cannot disable"]); - }); - }); - group('reporter', () { _test(String label, String expected, report(PrintingReporter r)) { test(label, () { @@ -78,7 +32,6 @@ void defineLinterEngineTests() { expect(msg, expected); }); } - _test('exception', 'EXCEPTION: LinterException: foo', (r) => r.exception(new LinterException('foo'))); _test('logError', 'ERROR: foo', (r) => r.logError('foo')); @@ -326,10 +279,15 @@ class AnnotationMatcher extends Matcher { } } -class MockLinter extends Linter { +class MockLinter extends LintRule { VisitorCallback visitorCallback; - MockLinter([nodeVisitor v]) { + MockLinter([nodeVisitor v]) : super( + name: 'MockLint', + group: Group.STYLE_GUIDE, + kind: Kind.AVOID, + description: 'Desc', + details: 'And so on...') { visitorCallback = () => new MockVisitor(v); } @@ -337,36 +295,6 @@ class MockLinter extends Linter { AstVisitor getVisitor() => visitorCallback(); } -class MockRegistry extends RuleRegistry { - MockRegistry([List lints]) : super(new MockReporter()) { - if (lints != null) { - for (int i = 0; i < lints.length; ++i) { - registerLinter('_linter_$i', lints[i]); - enable('_linter_$i'); - } - } - } - - expectWarnings(List warnings) { - expect((reporter as MockReporter).warnings, unorderedEquals(warnings)); - } -} - -class MockReporter extends Reporter { - var exceptions = []; - var warnings = []; - - @override - void exception(LinterException exception) { - exceptions.add(exception); - } - - @override - void warn(String message) { - warnings.add(message); - } -} - class MockVisitor extends GeneralizingAstVisitor { final nodeVisitor; From 13bbae4495205cc11e8f02cb697a738c71d6a9ee Mon Sep 17 00:00:00 2001 From: pquitslund Date: Sun, 8 Feb 2015 21:09:19 -0800 Subject: [PATCH 0047/2942] Doc generator rough mix. --- lib/src/linter.dart | 25 ++-- lib/src/rules/empty_constructor_bodies.dart | 33 ++++- lib/src/rules/super_goes_last.dart | 45 ++++++- .../unnecessary_brace_in_string_interp.dart | 19 ++- tool/doc.dart | 116 ++++++++++++++++++ 5 files changed, 213 insertions(+), 25 deletions(-) create mode 100644 tool/doc.dart diff --git a/lib/src/linter.dart b/lib/src/linter.dart index c1953db52b9..7ff2ca54091 100644 --- a/lib/src/linter.dart +++ b/lib/src/linter.dart @@ -61,8 +61,8 @@ abstract class DartLinter { Iterable lintFile(File sourceFile); - Iterable lintLibrarySource({String libraryName, - String libraryContents}); + Iterable lintLibrarySource( + {String libraryName, String libraryContents}); Iterable lintPath(String sourcePath); } @@ -145,9 +145,10 @@ class LinterOptions extends DriverOptions { /// Describes a lint rule. abstract class LintRule extends Linter { - /// Longer description (in markdown format). + /// Description (in markdown format) suitable for display in a detailed lint + /// description. final String details; - /// Short description + /// Short description suitable for display in console output. final String description; /// Lint group (for example, 'Style Guide') final Group group; @@ -158,6 +159,11 @@ abstract class LintRule extends Linter { LintRule({String name, this.group, this.kind, this.description, this.details}) : name = new CamelCaseString(name); + + void reportLint(AstNode node) { + reporter.reportErrorForNode( + new LintCode(name.value, description), node, []); + } } class PrintingReporter implements Reporter, Logger { @@ -214,13 +220,10 @@ class SourceLinter implements DartLinter, AnalysisErrorListener { _registerAndRun(() => new AnalysisDriver.forFile(sourceFile, options)); @override - Iterable lintLibrarySource({String libraryName, - String libraryContents}) => - _registerAndRun( - () => - new AnalysisDriver.forSource( - new _StringSource(libraryContents, libraryName), - options)); + Iterable lintLibrarySource( + {String libraryName, String libraryContents}) => _registerAndRun( + () => new AnalysisDriver.forSource( + new _StringSource(libraryContents, libraryName), options)); @override Iterable lintPath(String sourcePath) => diff --git a/lib/src/rules/empty_constructor_bodies.dart b/lib/src/rules/empty_constructor_bodies.dart index ded6f070b82..e31630bfac0 100644 --- a/lib/src/rules/empty_constructor_bodies.dart +++ b/lib/src/rules/empty_constructor_bodies.dart @@ -5,17 +5,43 @@ library empty_constructor_bodies; import 'package:analyzer/src/generated/ast.dart'; -import 'package:analyzer/src/generated/error.dart'; import 'package:dart_lint/src/linter.dart'; const desc = 'DO use ; instead of {} for empty constructor bodies.'; +const details = +''' +From the [style guide] (https://www.dartlang.org/articles/style-guide/): + +**DO** use ; instead of {} for empty constructor bodies. + +In Dart, a constructor with an empty body can be terminated with just a +semicolon. This is required for const constructors. For consistency and +brevity, other constructors should also do this. + +**GOOD:** +``` +class Point { + int x, y; + Point(this.x, this.y); +} +``` +**BAD:** +``` +class Point { + int x, y; + Point(this.x, this.y) {} +} +``` +'''; + class EmptyConstructorBodies extends LintRule { EmptyConstructorBodies() : super( name: 'EmptyConstructorBodies', description: desc, + details: details, group: Group.STYLE_GUIDE, kind: Kind.DO); @@ -33,10 +59,7 @@ class Visitor extends SimpleAstVisitor { if (node.body is BlockFunctionBody) { Block block = (node.body as BlockFunctionBody).block; if (block.statements.length == 0) { - rule.reporter.reportErrorForNode( - new LintCode(rule.name.value, rule.description), - block, - []); + rule.reportLint(block); } } } diff --git a/lib/src/rules/super_goes_last.dart b/lib/src/rules/super_goes_last.dart index 58b3c11b8b6..0bc03148ad8 100644 --- a/lib/src/rules/super_goes_last.dart +++ b/lib/src/rules/super_goes_last.dart @@ -5,18 +5,51 @@ library super_goes_last; import 'package:analyzer/src/generated/ast.dart'; -import 'package:analyzer/src/generated/error.dart'; import 'package:dart_lint/src/linter.dart'; const desc = 'DO place the super() call last in a constructor initialization list.'; -class SuperGoesLast extends LintRule { +const details = ''' +From the [style guide] (https://www.dartlang.org/articles/style-guide/): - SuperGoesLast() - : super( +**DO** place the ``super()`` call last in a constructor initialization list. + +Field initializers are evaluated in the order that they appear in the +constructor initialization list. If you place a ``super()`` call in the +middle of an initializer list, the superclass's initializers will be evaluated +right then before evaluating the rest of the subclass's initializers. + +What it doesn't mean is that the superclass's constructor body will be executed +then. That always happens after all initializers are run regardless of where +``super()`` appears. It's vanishingly rare that the order of initializers +matters, so the placement of ``super()`` in the list almost never matters +either. + +Getting in the habit of placing it last improves consistency, visually +reinforces when the superclass's constructor body is run, and may help +performance. + +**GOOD:** +``` +View(Style style, List children) + : _children = children, + super(style) { +``` + +**BAD:** +``` +View(Style style, List children) + : super(style), + _children = children { +``` +'''; + +class SuperGoesLast extends LintRule { + SuperGoesLast() : super( name: 'SuperGoesLast', description: desc, + details: details, group: Group.STYLE_GUIDE, kind: Kind.DO); @@ -34,9 +67,9 @@ class Visitor extends SimpleAstVisitor { var last = node.initializers.length - 1; for (int i = 0; i <= last; ++i) { - ConstructorInitializer init = node.initializers[i]; + var init = node.initializers[i]; if (init is SuperConstructorInvocation && i != last) { - rule.reporter.reportErrorForNode(new LintCode(rule.name.value, rule.description), init, []); + rule.reportLint(init); } } } diff --git a/lib/src/rules/unnecessary_brace_in_string_interp.dart b/lib/src/rules/unnecessary_brace_in_string_interp.dart index 4fd550ada47..8c6412e34de 100644 --- a/lib/src/rules/unnecessary_brace_in_string_interp.dart +++ b/lib/src/rules/unnecessary_brace_in_string_interp.dart @@ -5,7 +5,6 @@ library unnecessary_brace_in_string_interp; import 'package:analyzer/src/generated/ast.dart'; -import 'package:analyzer/src/generated/error.dart'; import 'package:analyzer/src/generated/scanner.dart'; import 'package:dart_lint/src/linter.dart'; @@ -13,6 +12,20 @@ final RegExp alphaNumeric = new RegExp(r'^[a-zA-Z0-9]'); const desc = 'AVOID bracketed interpolation of simple identifiers'; +const details = r''' +AVOID bracketed interpolation of simple identifiers. + +**GOOD:** +```dart +print("Hi, $name!"); +``` + +**BAD:** +```dart +print("Hi, ${name}!"); +``` +'''; + bool isAlphaNumeric(Token token) => token is StringToken && token.lexeme.startsWith(alphaNumeric); @@ -20,6 +33,7 @@ class UnnecessaryBraceInStringInterp extends LintRule { UnnecessaryBraceInStringInterp() : super( name: 'UnnecessaryBraceInStringInterp', description: desc, + details: details, group: Group.STYLE_GUIDE, kind: Kind.AVOID); @@ -38,8 +52,7 @@ class Visitor extends SimpleAstVisitor { if (expression.expression is SimpleIdentifier) { Token bracket = expression.rightBracket; if (bracket != null && !isAlphaNumeric(bracket.next)) { - rule.reporter.reportErrorForNode( - new LintCode(rule.name.value, rule.description), expression, []); + rule.reportLint(expression); } } } diff --git a/tool/doc.dart b/tool/doc.dart new file mode 100644 index 00000000000..66d2e2fdb18 --- /dev/null +++ b/tool/doc.dart @@ -0,0 +1,116 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library docgen; + +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:dart_lint/src/linter.dart'; +import 'package:dart_lint/src/rules.dart'; +import 'package:markdown/markdown.dart'; + + +/// Generates lint rule docs for publishing to http://dart-lang.github.io/ +void main([args]) { + var parser = new ArgParser(allowTrailingOptions: true); + + parser.addOption('out', abbr: 'o', help: 'Specifies output directory.'); + + var options; + try { + options = parser.parse(args); + } on FormatException catch (err) { + printUsage(parser, err.message); + return; + } + + var outDir = options['out']; + + if (outDir != null) { + Directory d = new Directory(outDir); + if (!d.existsSync()) { + print("Directory '${d.path}' does not exist"); + return; + } + } + + ruleMap.values.forEach((l) => new Generator(l).generate(outDir)); +} + +void printUsage(ArgParser parser, [String error]) { + var message = 'Generates lint docs.'; + if (error != null) { + message = error; + } + + stdout.write('''$message +Usage: doc +${parser.usage} +'''); +} + +class Generator { + LintRule rule; + Generator(this.rule); + + String get details => rule.details != null ? rule.details : ''; + String get group => rule.group.name; + String get humanReadableName => rule.name.humanized; + String get kind => rule.kind.name; + String get name => rule.name.value; + + generate([String filePath]) { + var generated = _generate(); + if (filePath != null) { + var outPath = '$filePath/$name.html'; + print('Writing to $outPath'); + new File(outPath).writeAsStringSync(generated); + } else { + print(generated); + } + } + + String _generate() => ''' + + + + + + $name + + + + + + + +
+
+

$humanReadableName

+

Group: $group

+

Kind: $kind

+

View the Project on GitHub dart-lang/dart_lint

+ +
+
+ + ${markdownToHtml(details)} + +
+
+ + + + +'''; +} From 56251e5e74124bc0cb7c6ef4cf2769adcb5348c3 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Sun, 8 Feb 2015 21:16:00 -0800 Subject: [PATCH 0048/2942] Analyzer dep bump. --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index c98a0014337..990dd0409f2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ author: Dart Team description: Style linter for Dart. homepage: https://github.com/dart-lang/dart_lint dependencies: - analyzer: '0.23.0' + analyzer: '0.24.0-dev.1' cli_util: '>=0.0.1 <0.1.0' dev_dependencies: markdown: '>=0.7.1+2 <0.8.0' From 84df540c988721de494742fc8f8cc00c803a6def Mon Sep 17 00:00:00 2001 From: pquitslund Date: Sun, 8 Feb 2015 21:34:23 -0800 Subject: [PATCH 0049/2942] IO tests. --- lib/src/io.dart | 4 ++-- test/lint_test.dart | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/src/io.dart b/lib/src/io.dart index e1cc1e1746c..44c8c171c63 100644 --- a/lib/src/io.dart +++ b/lib/src/io.dart @@ -13,7 +13,7 @@ import 'package:dart_lint/src/linter.dart'; /// '.dart' extension. /// /// Returns `true` if successful or `false` if an error occurred. -bool processFile(File file) { +bool processFile(FileSystemEntity file) { var path = file.path; if (file is Link) { @@ -21,7 +21,7 @@ bool processFile(File file) { return false; } - if (!path.endsWith('.dart')) { + if (!isDartFile(file)) { print('Skipping $path (unsupported extenstion)'); return false; } diff --git a/test/lint_test.dart b/test/lint_test.dart index 138e1914489..928d3f613cd 100644 --- a/test/lint_test.dart +++ b/test/lint_test.dart @@ -120,6 +120,17 @@ void defineLinterEngineTests() { expect(exitCode, equals(dartlint.unableToProcessExitCode)); }); }); + + group('io', () { + test('link', () { + Link l = new Link('bogus'); + expect(processFile(l), isFalse); + }); + test('bad extension', () { + File f = new File('bogus.txt'); + expect(processFile(f), isFalse); + }); + }); }); } From 89e1c3b93daee4c936bff0d48f2191e042e6f89e Mon Sep 17 00:00:00 2001 From: pquitslund Date: Mon, 9 Feb 2015 11:45:25 -0800 Subject: [PATCH 0050/2942] Docgen updates. --- lib/src/linter.dart | 150 ++++++++++++++---- lib/src/rules.dart | 4 + .../unnecessary_brace_in_string_interp.dart | 3 +- test/lint_test.dart | 52 ++++++ tool/doc.dart | 126 ++++++++++++++- 5 files changed, 300 insertions(+), 35 deletions(-) diff --git a/lib/src/linter.dart b/lib/src/linter.dart index 7ff2ca54091..0672b77bc85 100644 --- a/lib/src/linter.dart +++ b/lib/src/linter.dart @@ -48,6 +48,8 @@ class CamelCaseString { } String get humanized => _humanize(value); + + String toString() => value; } /// Dart source linter. @@ -70,34 +72,77 @@ abstract class DartLinter { class Group { /// Defined rule groups. - static final Group STYLE_GUIDE = new Group._('Style Guide'); + static const Group STYLE_GUIDE = const Group._('Style Guide', + link: const Hyperlink('See the Style Guide', + 'https://www.dartlang.org/articles/style-guide/')); final String name; final bool custom; - factory Group(String name) { + final String description; + final Hyperlink link; + + factory Group(String name, {String description, Hyperlink link}) { switch (name) { case 'Styleguide': case 'Style Guide': return STYLE_GUIDE; default: - return new Group._(name, custom: true); + return new Group._(name, + custom: true, description: description, link: link); } } - Group._(this.name, {this.custom: false}); + const Group._(this.name, {this.custom: false, this.description, this.link}); } -class Kind { + +class Hyperlink { + final String label; + final String href; + final bool bold; + const Hyperlink(this.label, this.href, {this.bold: false}); + String get html => '${_emph(label)}'; + String _emph(msg) => bold ? '$msg' : msg; +} + +class Kind implements Comparable { /// Defined rule kinds. - static final Kind DO = new Kind._('Do'); - static final Kind DONT = new Kind._("Don't"); - static final Kind PREFER = new Kind._('Prefer'); - static final Kind AVOID = new Kind._('Avoid'); - static final Kind CONSIDER = new Kind._('Consider'); + static const Kind DO = const Kind._('Do', ordinal: 0, description: ''' +**DO** guidelines describe practices that should always be followed. +There will almost never be a valid reason to stray from them. +'''); + static const Kind DONT = const Kind._("Don't", ordinal: 1, description: ''' +**DON'T** guidelines are the converse: things that are almost never a good idea. +You'll note there are few of these here. Guidelines like these in other +languages help to avoid the pitfalls that appear over time. Dart is new enough +that we can just fix those pitfalls directly instead of putting up ropes around +them. +'''); + static const Kind PREFER = const Kind._('Prefer', ordinal: 2, description: ''' +**PREFER** guidelines are practices that you should follow. However, there +may be circumstances where it makes sense to do otherwise. Just make sure you +understand the full implications of ignoring the guideline when you do. +'''); + static const Kind AVOID = const Kind._('Avoid', ordinal: 3, description: ''' +**AVOID** guidelines are the dual to "prefer": stuff you shouldn't do but where +there may be good reasons to on rare occasions. +'''); + static const Kind CONSIDER = const Kind._('Consider', + ordinal: 4, description: ''' +**CONSIDER** guidelines are practices that you might or might not want to +follow, depending on circumstances, precedents, and your own preference. +'''); + /// List of supported kinds in priority order. + static Iterable get supported => [DO, DONT, PREFER, AVOID, CONSIDER]; final String name; final bool custom; - factory Kind(String name) { + /// Description (in markdown). + final String description; + + final int ordinal; + + factory Kind(String name, {String description, int ordinal}) { var label = name.toUpperCase(); switch (label) { case 'DO': @@ -112,11 +157,50 @@ class Kind { case 'CONSIDER': return CONSIDER; default: - return new Kind._(label, custom: true); + return new Kind._(label, + custom: true, description: description, ordinal: ordinal); } } - Kind._(this.name, {this.custom: false}); + const Kind._(this.name, {this.custom: false, this.description, this.ordinal}); + + @override + int compareTo(Kind other) => this.ordinal - other.ordinal; +} + +/// Describes a lint rule. +abstract class LintRule extends Linter implements Comparable { + + /// Description (in markdown format) suitable for display in a detailed lint + /// description. + final String details; + /// Short description suitable for display in console output. + final String description; + /// Lint group (for example, 'Style Guide') + final Group group; + /// Lint kind (DO|DON'T|PREFER|AVOID|CONSIDER). + final Kind kind; + /// Lint maturity (STABLE|EXPERIMENTAL). + final Maturity maturity; + /// Lint name. + final CamelCaseString name; + + LintRule({String name, this.group, this.kind, this.description, this.details, + this.maturity: Maturity.STABLE}) : name = new CamelCaseString(name); + + @override + int compareTo(LintRule other) { + var k = kind.compareTo(other.kind); + if (k != 0) { + return k; + } + return name.value.compareTo(other.name.value); + } + + void reportLint(AstNode node) { + reporter.reportErrorForNode( + new LintCode(name.value, description), node, []); + } } /// Thrown when an error occurs in linting. @@ -142,28 +226,30 @@ class LinterOptions extends DriverOptions { Iterable get enabledLints => _enabledLints(); } -/// Describes a lint rule. -abstract class LintRule extends Linter { +class Maturity implements Comparable { + static const Maturity STABLE = const Maturity._('STABLE', ordinal: 0); + static const Maturity EXPERIMENTAL = + const Maturity._('EXPERIMENTAL', ordinal: 1); - /// Description (in markdown format) suitable for display in a detailed lint - /// description. - final String details; - /// Short description suitable for display in console output. - final String description; - /// Lint group (for example, 'Style Guide') - final Group group; - /// Lint kind (DO|DON'T|PREFER|AVOID|CONSIDER) - final Kind kind; - /// Lint name. - final CamelCaseString name; + final String name; + final int ordinal; - LintRule({String name, this.group, this.kind, this.description, this.details}) - : name = new CamelCaseString(name); - - void reportLint(AstNode node) { - reporter.reportErrorForNode( - new LintCode(name.value, description), node, []); + factory Maturity(String name, {int ordinal}) { + var normalized = name.toUpperCase(); + switch (normalized) { + case 'STABLE': + return STABLE; + case 'EXPERIMENTAL': + return EXPERIMENTAL; + default: + return new Maturity._(name, ordinal: ordinal); + } } + + const Maturity._(this.name, {this.ordinal}); + + @override + int compareTo(Maturity other) => this.ordinal - other.ordinal; } class PrintingReporter implements Reporter, Logger { diff --git a/lib/src/rules.dart b/lib/src/rules.dart index 359bd278305..31a03fb497f 100644 --- a/lib/src/rules.dart +++ b/lib/src/rules.dart @@ -15,3 +15,7 @@ final Map ruleMap = { 'super_goes_last': new SuperGoesLast(), 'unnecessary_brace_in_string_interp': new UnnecessaryBraceInStringInterp() }; + +/// Sorted list of contributed lint rules. +final List rules = + new List.from(ruleMap.values, growable: false)..sort(); diff --git a/lib/src/rules/unnecessary_brace_in_string_interp.dart b/lib/src/rules/unnecessary_brace_in_string_interp.dart index 8c6412e34de..769d6bccc9b 100644 --- a/lib/src/rules/unnecessary_brace_in_string_interp.dart +++ b/lib/src/rules/unnecessary_brace_in_string_interp.dart @@ -35,7 +35,8 @@ class UnnecessaryBraceInStringInterp extends LintRule { description: desc, details: details, group: Group.STYLE_GUIDE, - kind: Kind.AVOID); + kind: Kind.AVOID, + maturity: Maturity.EXPERIMENTAL); @override AstVisitor getVisitor() => new Visitor(this); diff --git a/test/lint_test.dart b/test/lint_test.dart index 928d3f613cd..8bb168f0bdc 100644 --- a/test/lint_test.dart +++ b/test/lint_test.dart @@ -131,6 +131,51 @@ void defineLinterEngineTests() { expect(processFile(f), isFalse); }); }); + + group('dtos', () { + group('hyperlink', () { + test('html', () { + Hyperlink link = new Hyperlink('dart', 'http://dartlang.org'); + expect(link.html, equals('dart')); + }); + test('html - strong', () { + Hyperlink link = + new Hyperlink('dart', 'http://dartlang.org', bold: true); + expect(link.html, equals( + 'dart')); + }); + }); + group('kind', () { + test('priority', () { + expect(Kind.supported, orderedEquals( + [Kind.DO, Kind.DONT, Kind.PREFER, Kind.AVOID, Kind.CONSIDER])); + }); + test('comparing', () { + expect(Kind.DO.compareTo(Kind.DONT), equals(-1)); + }); + }); + group('rule', () { + test('sorting', () { + expect(Kind.supported, orderedEquals( + [Kind.DO, Kind.DONT, Kind.PREFER, Kind.AVOID, Kind.CONSIDER])); + }); + test('comparing', () { + LintRule r1 = new MockLintRule('Bar', Kind.DO); + LintRule r2 = new MockLintRule('Foo', Kind.DO); + expect(r1.compareTo(r2), equals(-1)); + }); + }); + group('maturity', () { + test('comparing', () { + // Custom + Maturity m1 = new Maturity('Foo', ordinal: 0); + Maturity m2 = new Maturity('Bar', ordinal: 1); + expect(m1.compareTo(m2), equals(-1)); + // Builtin + expect(Maturity.STABLE.compareTo(Maturity.EXPERIMENTAL), equals(-1)); + }); + }); + }); }); } @@ -306,6 +351,13 @@ class MockLinter extends LintRule { AstVisitor getVisitor() => visitorCallback(); } +class MockLintRule extends LintRule { + MockLintRule(String name, Kind kind) : super(name: name, kind: kind); + + @override + AstVisitor getVisitor() => new MockVisitor(null); +} + class MockVisitor extends GeneralizingAstVisitor { final nodeVisitor; diff --git a/tool/doc.dart b/tool/doc.dart index 66d2e2fdb18..b22bb927dfd 100644 --- a/tool/doc.dart +++ b/tool/doc.dart @@ -11,7 +11,6 @@ import 'package:dart_lint/src/linter.dart'; import 'package:dart_lint/src/rules.dart'; import 'package:markdown/markdown.dart'; - /// Generates lint rule docs for publishing to http://dart-lang.github.io/ void main([args]) { var parser = new ArgParser(allowTrailingOptions: true); @@ -36,7 +35,13 @@ void main([args]) { } } - ruleMap.values.forEach((l) => new Generator(l).generate(outDir)); + var rules = ruleMap.values; + + // Generate index + new Indexer(rules).generate(outDir); + + // Generate rule files + rules.forEach((l) => new Generator(l).generate(outDir)); } void printUsage(ArgParser parser, [String error]) { @@ -59,6 +64,7 @@ class Generator { String get group => rule.group.name; String get humanReadableName => rule.name.humanized; String get kind => rule.kind.name; + String get maturity => rule.maturity.name; String get name => rule.name.value; generate([String filePath]) { @@ -93,6 +99,7 @@ class Generator {

$humanReadableName

Group: $group

Kind: $kind

+

Maturity: $maturity

View the Project on GitHub dart-lang/dart_lint