analyzer_cli move to SDK.

Tracking bug: https://github.com/dart-lang/sdk/issues/24731

Note, dartium build changes are in a separate CL: https://codereview.chromium.org/1453413006/

Some tests had to be disabled for want of mockito in the SDK; tracking their reimplementation is here: https://github.com/dart-lang/sdk/issues/24994

BUG=24731
R=whesse@google.com

Review URL: https://codereview.chromium.org/1459683003 .
This commit is contained in:
pq 2015-11-19 14:52:54 -08:00
parent aaf37ad035
commit 9ae1265ff5
51 changed files with 3427 additions and 9 deletions

3
DEPS
View file

@ -38,7 +38,6 @@ vars = {
# Revisions of /third_party/* dependencies.
"7zip_rev" : "@19997",
"analyzer_cli_rev" : "@c93a3d2d1c7715a6c7a067ebdbc9ba716b865226",
"args_tag": "@0.13.0",
"async_tag": "@1.2.0",
"barback_tag" : "@0.15.2+7",
@ -172,8 +171,6 @@ deps = {
(Var("github_mirror") % "dart-services") +
Var("dart_services_rev"),
Var("dart_root") + "/third_party/pkg/analyzer_cli":
(Var("github_mirror") % "analyzer_cli") + Var("analyzer_cli_rev"),
Var("dart_root") + "/third_party/pkg/args":
(Var("github_mirror") % "args") + Var("args_tag"),
Var("dart_root") + "/third_party/pkg/async":

11
pkg/analyzer_cli/.gitignore vendored Normal file
View file

@ -0,0 +1,11 @@
.buildlog
.DS_Store
.idea
/.packages
.project
.pub/
.settings/
analyzer_cli.iml
build/
packages
pubspec.lock

View file

@ -0,0 +1,16 @@
# Changelog
## 1.1.1
- Removed outmoded `--url-mapping` command line option.
## 1.1.0
- New `--lints` command line option.
## 0.0.
## 0.0.1
- Initial version

View file

@ -0,0 +1,87 @@
# dartanalyzer
Use _dartanalyzer_ to statically analyze your code at the command line,
checking for errors and warnings that are specified in the
[Dart Language Specification](https://www.dartlang.org/docs/spec/).
DartPad, code editors, and IDEs such as WebStorm use the same
analysis engine that dartanalyzer uses.
If you want to _contribute_ to the dartanalyzer project, see the
[contributor docs](https://github.com/dart-lang/analyzer_cli/blob/master/CONTRIBUTOR.md).
This page contains information about _using_ the dartanalyzer command-line tool.
## Basic usage
Run the analyzer from the top directory of the package.
Here's an example of testing a Dart file.
```
dartanalyzer bin/test.dart
```
## Options
The following are the most commonly used options for dartanalyzer:
* `--packages=`<br>
Specify the path to the package resolution configuration file.
For more information see
[Package Resolution Configuration File](https://github.com/lrhn/dep-pkgspec/blob/master/DEP-pkgspec.md).
This option cannot be used with `--package-root`.
* `--package-warnings`<br>
Show warnings not only for code in the specified .dart file and
others in its library, but also for libraries imported with `package:`.
* `--options=`<br>
Specify the path to an analysis options file.
* `--lints`<br>
Show the results from the linter.
* `--no-hints`<br>
Don't show hints for improving the code.
* `--ignore-unrecognized-flags`<br>
Rather than printing the help message,
ignore any unrecognized command-line flags.
* `--version`<br>
Show the analyzer version.
* `-h` _or_ `--help`<br>
Show all the command-line options.
The following are advanced options to use with dartanalyzer:
* `-b` _or_ `--batch`<br>
Run in batch mode.
* `--dart-sdk=`<br>
Specify the directory that contains the Dart SDK.
* `--fatal-warnings`<br>
Except for type warnings, treat warnings as fatal.
* `--format=machine`<br>
Produce output in a format suitable for parsing.
* `--url-mapping=libraryUri,/path/to/library.dart`<br>
Tells the analyzer to use the specified library as the source
for that particular import.
The following options are deprecated:
* `-p` _or_ `--package-root=`<br>
**Deprecated.** Specify the directory to search for any libraries that are
imported using `package:`. _This option is replaced as of Dart 1.12 with
`--packages`._
* `--machine`<br>
**Deprecated.** Replaced by `--format`.
* `--show-package-warnings`<br>
**Deprecated.** Replaced by `--package-warnings`.
* `--show-sdk-warnings`<br>
**Deprecated.** Replaced by `--warnings`.

View file

@ -0,0 +1,16 @@
#!/usr/bin/env dart
// 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:analyzer_cli/src/driver.dart';
/// The entry point for the analyzer.
void main(List<String> args) {
var starter = new Driver();
starter.start(args);
}

View file

@ -0,0 +1,303 @@
// 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 analyzer_cli.src.analyzer_impl;
import 'dart:collection';
import 'dart:io';
import 'package:analyzer/src/generated/element.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/error.dart';
import 'package:analyzer/src/generated/java_engine.dart';
import 'package:analyzer/src/generated/java_io.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:analyzer/src/generated/utilities_general.dart';
import 'package:analyzer_cli/src/driver.dart';
import 'package:analyzer_cli/src/error_formatter.dart';
import 'package:analyzer_cli/src/options.dart';
DirectoryBasedDartSdk sdk;
/// The maximum number of sources for which AST structures should be kept in the cache.
const int _maxCacheSize = 512;
int currentTimeMillis() => new DateTime.now().millisecondsSinceEpoch;
/// Analyzes single library [File].
class AnalyzerImpl {
final CommandLineOptions options;
final int startTime;
final AnalysisContext context;
final Source librarySource;
/// All [Source]s references by the analyzed library.
final Set<Source> sources = new Set<Source>();
/// All [AnalysisErrorInfo]s in the analyzed library.
final List<AnalysisErrorInfo> errorInfos = new List<AnalysisErrorInfo>();
/// [HashMap] between sources and analysis error infos.
final HashMap<Source, AnalysisErrorInfo> sourceErrorsMap =
new HashMap<Source, AnalysisErrorInfo>();
/// If the file specified on the command line is part of a package, the name
/// of that package. Otherwise `null`. This allows us to analyze the file
/// specified on the command line as though it is reached via a "package:"
/// URI, but avoid suppressing its output in the event that the user has not
/// specified the "--package-warnings" option.
String _selfPackageName;
AnalyzerImpl(this.context, this.librarySource, this.options, this.startTime);
/// 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);
status = status.max(severity);
}
}
return status;
}
void addCompilationUnitSource(CompilationUnitElement unit,
Set<LibraryElement> libraries, Set<CompilationUnitElement> units) {
if (unit == null || units.contains(unit)) {
return;
}
units.add(unit);
sources.add(unit.source);
}
void addLibrarySources(LibraryElement library, Set<LibraryElement> libraries,
Set<CompilationUnitElement> units) {
if (library == null || !libraries.add(library)) {
return;
}
// Maybe skip library.
{
UriKind uriKind = library.source.uriKind;
// Optionally skip package: libraries.
if (!options.showPackageWarnings && _isOtherPackage(library.source.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);
}
}
/// 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);
}
/// Fills [errorInfos] using [sources].
void prepareErrors() {
for (Source source in sources) {
context.computeErrors(source);
errorInfos.add(context.getErrors(source));
}
}
/// Fills [sources].
void prepareSources(LibraryElement library) {
var units = new Set<CompilationUnitElement>();
var libraries = new Set<LibraryElement>();
addLibrarySources(library, libraries, units);
}
/// Setup local fields such as the analysis context for analysis.
void setupForAnalysis() {
sources.clear();
errorInfos.clear();
Uri libraryUri = librarySource.uri;
if (libraryUri.scheme == 'package' && libraryUri.pathSegments.length > 0) {
_selfPackageName = libraryUri.pathSegments[0];
}
}
/// The sync version of analysis.
ErrorSeverity _analyzeSync(int printMode) {
// Don't try to analyze parts.
if (context.computeKindOf(librarySource) == SourceKind.PART) {
stderr.writeln("Only libraries can be analyzed.");
stderr.writeln(
"${librarySource.fullName} 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;
}
bool _isDesiredError(AnalysisError error) {
if (error.errorCode.type == ErrorType.TODO) {
return false;
}
if (computeSeverity(error, options) == ErrorSeverity.INFO &&
options.disableHints) {
return false;
}
return true;
}
/// Determine whether the given URI refers to a package other than the package
/// being analyzed.
bool _isOtherPackage(Uri uri) {
if (uri.scheme != 'package') {
return false;
}
if (_selfPackageName != null &&
uri.pathSegments.length > 0 &&
uri.pathSegments[0] == _selfPackageName) {
return false;
}
return true;
}
_printColdPerf() {
// Print cold VM performance numbers.
int totalTime = currentTimeMillis() - startTime;
int otherTime = totalTime;
for (PerformanceTag tag in PerformanceTag.all) {
if (tag != PerformanceTag.UNKNOWN) {
int tagTime = tag.elapsedMs;
outSink.writeln('${tag.label}-cold:$tagTime');
otherTime -= tagTime;
}
}
outSink.writeln('other-cold:$otherTime');
outSink.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.
StringSink sink = options.machineFormat ? errorSink : outSink;
// Print errors.
ErrorFormatter formatter =
new ErrorFormatter(sink, options, _isDesiredError);
formatter.formatErrors(errorInfos);
}
/// Compute the severity of the error; however:
/// * if [options.enableTypeChecks] is false, then de-escalate checked-mode
/// compile time errors to a severity of [ErrorSeverity.INFO].
/// * if [options.hintsAreFatal] is true, escalate hints to errors.
static ErrorSeverity computeSeverity(
AnalysisError error, CommandLineOptions options) {
if (!options.enableTypeChecks &&
error.errorCode.type == ErrorType.CHECKED_MODE_COMPILE_TIME_ERROR) {
return ErrorSeverity.INFO;
}
if (options.hintsAreFatal && error.errorCode is HintCode) {
return ErrorSeverity.ERROR;
}
return error.errorCode.errorSeverity;
}
/// Return the corresponding package directory or `null` if none is found.
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;
}
}
/// This [Logger] prints out information comments to [outSink] and error messages
/// to [errorSink].
class StdLogger extends Logger {
StdLogger();
@override
void logError(String message, [CaughtException exception]) {
errorSink.writeln(message);
if (exception != null) {
errorSink.writeln(exception);
}
}
@override
void logError2(String message, Object exception) {
errorSink.writeln(message);
if (exception != null) {
errorSink.writeln(exception.toString());
}
}
@override
void logInformation(String message, [CaughtException exception]) {
outSink.writeln(message);
if (exception != null) {
outSink.writeln(exception);
}
}
@override
void logInformation2(String message, Object exception) {
outSink.writeln(message);
if (exception != null) {
outSink.writeln(exception.toString());
}
}
}

View file

@ -0,0 +1,196 @@
// 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 analyzer_cli.src.bootloader;
import 'dart:async';
import 'dart:isolate';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/source/analysis_options_provider.dart';
import 'package:analyzer/src/context/context.dart';
import 'package:analyzer/src/generated/engine.dart' as engine;
import 'package:analyzer/src/plugin/plugin_configuration.dart';
import 'package:analyzer_cli/src/driver.dart';
import 'package:analyzer_cli/src/options.dart';
import 'package:source_span/source_span.dart';
import 'package:yaml/src/yaml_node.dart';
const _analyzerPackageName = 'analyzer';
/// Return non-null if there is a validation issue with this plugin.
String validate(PluginInfo plugin) {
var missing = <String>[];
if (plugin.className == null) {
missing.add('class name');
}
if (plugin.libraryUri == null) {
missing.add('library uri');
}
if (missing.isEmpty) {
// All good.
return null;
}
return 'Plugin ${plugin.name} skipped, config missing: ${missing.join(", ")}';
}
List<PluginInfo> _validate(Iterable<PluginInfo> plugins) {
List<PluginInfo> validated = <PluginInfo>[];
plugins.forEach((PluginInfo plugin) {
String validation = validate(plugin);
if (validation != null) {
errorSink.writeln(validation);
} else {
validated.add(plugin);
}
});
return validated;
}
/// Source code assembler.
class Assembler {
/// Plugins to configure.
final Iterable<PluginInfo> plugins;
/// Create an assembler for the given plugin [config].
Assembler(this.plugins);
/// A string enumerating required package `import`s.
String get enumerateImports =>
plugins.map((PluginInfo p) => "import '${p.libraryUri}';").join('\n');
/// A string listing initialized plugin instances.
String get pluginList =>
plugins.map((PluginInfo p) => 'new ${p.className}()').join(', ');
/// Create a file containing a `main()` suitable for loading in spawned
/// isolate.
String createMain() => _generateMain();
String _generateMain() => """
// 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.
// This code was auto-generated, is not intended to be edited, and is subject to
// significant change. Please see the README file for more information.
import 'package:analyzer_cli/src/driver.dart';
$enumerateImports
void main(List<String> args) {
var starter = new Driver();
starter.userDefinedPlugins = [$pluginList];
starter.start(args);
}
""";
}
/// Given environment information extracted from command-line `args`, creates a
/// a loadable analyzer "image".
class BootLoader {
/// Emits an error message to [errorSink] if plugin config can't be read.
static final ErrorHandler _pluginConfigErrorHandler = (Exception e) {
String details;
if (e is PluginConfigFormatException) {
details = e.message;
var node = e.yamlNode;
if (node is YamlNode) {
SourceLocation location = node.span.start;
details += ' (line ${location.line}, column ${location.column})';
}
} else {
details = e.toString();
}
errorSink.writeln('Plugin configuration skipped: $details');
};
/// Reads plugin config info from `.analysis_options`.
PluginConfigOptionsProcessor _pluginOptionsProcessor =
new PluginConfigOptionsProcessor(_pluginConfigErrorHandler);
/// Create a loadable analyzer image configured with plugins derived from
/// the given analyzer command-line `args`.
Image createImage(List<String> args) {
// Parse commandline options.
CommandLineOptions options = CommandLineOptions.parse(args);
// Process analysis options file (and notify all interested parties).
_processAnalysisOptions(options);
// TODO(pquitslund): Pass in .packages info
return new Image(_pluginOptionsProcessor.config,
args: args, packageRootPath: options.packageRootPath);
}
void _processAnalysisOptions(CommandLineOptions options) {
// Determine options file path.
var filePath = options.analysisOptionsFile ??
engine.AnalysisEngine.ANALYSIS_OPTIONS_FILE;
try {
var file = PhysicalResourceProvider.INSTANCE.getFile(filePath);
AnalysisOptionsProvider analysisOptionsProvider =
new AnalysisOptionsProvider();
Map<String, YamlNode> options =
analysisOptionsProvider.getOptionsFromFile(file);
//TODO(pq): thread in proper context.
var temporaryContext = new AnalysisContextImpl();
_pluginOptionsProcessor.optionsProcessed(temporaryContext, options);
} on Exception catch (e) {
_pluginOptionsProcessor.onError(e);
}
}
}
/// A loadable "image" of a a configured analyzer instance.
class Image {
/// (Optional) package root path.
final String packageRootPath;
/// (Optional) package map.
final Map<String, Uri> packages;
/// (Optional) args to be passed on to the loaded main.
final List<String> args;
/// Plugin configuration.
final PluginConfig config;
/// Create an image with the given [config] and optionally [packages],
/// [packageRootPath], and command line [args].
Image(this.config, {this.packages, this.packageRootPath, this.args});
/// Load this image.
///
/// Loading an image consists in assembling an analyzer `main()`, configured
/// to include the appropriate analyzer plugins as specified in
/// `.analyzer_options` which is then run in a spawned isolate.
Future load() {
List<PluginInfo> plugins = _validate(config.plugins);
String mainSource = new Assembler(plugins).createMain();
Completer completer = new Completer();
ReceivePort exitListener = new ReceivePort();
exitListener.listen((data) {
completer.complete();
exitListener.close();
});
Uri uri =
Uri.parse('data:application/dart;charset=utf-8,${Uri.encodeComponent(
mainSource)}');
// TODO(pquitslund): update once .packages are supported.
String packageRoot =
packageRootPath != null ? packageRootPath : './packages';
Uri packageUri = new Uri.file(packageRoot);
Isolate.spawnUri(uri, args, null /* msg */,
packageRoot: packageUri, onExit: exitListener.sendPort);
return completer.future;
}
}

View file

@ -0,0 +1,641 @@
// 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 analyzer_cli.src.driver;
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:analyzer/file_system/file_system.dart' as fileSystem;
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/plugin/options.dart';
import 'package:analyzer/source/analysis_options_provider.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/source/sdk_ext.dart';
import 'package:analyzer/src/generated/constant.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/error.dart';
import 'package:analyzer/src/generated/interner.dart';
import 'package:analyzer/src/generated/java_engine.dart';
import 'package:analyzer/src/generated/java_io.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:analyzer/src/services/lint.dart';
import 'package:analyzer/src/task/options.dart';
import 'package:analyzer_cli/src/analyzer_impl.dart';
import 'package:analyzer_cli/src/options.dart';
import 'package:linter/src/plugin/linter_plugin.dart';
import 'package:package_config/discovery.dart' as pkgDiscovery;
import 'package:package_config/packages.dart' show Packages;
import 'package:package_config/packages_file.dart' as pkgfile show parse;
import 'package:package_config/src/packages_impl.dart' show MapPackages;
import 'package:path/path.dart' as path;
import 'package:plugin/plugin.dart';
import 'package:yaml/yaml.dart';
/// The maximum number of sources for which AST structures should be kept in the
/// cache.
const int _maxCacheSize = 512;
/// Shared IO sink for standard error reporting.
///
/// *Visible for testing.*
StringSink errorSink = stderr;
/// Shared IO sink for standard out reporting.
///
/// *Visible for testing.*
StringSink outSink = stdout;
/// Test this option map to see if it specifies lint rules.
bool containsLintRuleEntry(Map<String, YamlNode> options) {
var linterNode = options['linter'];
return linterNode is YamlMap && linterNode.containsKey('rules');
}
typedef ErrorSeverity _BatchRunnerHandler(List<String> args);
class Driver {
/// The plugins that are defined outside the `analyzer_cli` package.
List<Plugin> _userDefinedPlugins = <Plugin>[];
/// Indicates whether the analyzer is running in batch mode.
bool _isBatch;
/// The context that was most recently created by a call to [_analyzeAll], or
/// `null` if [_analyzeAll] hasn't been called yet.
AnalysisContext _context;
/// If [_context] is not `null`, the [CommandLineOptions] that guided its
/// creation.
CommandLineOptions _previousOptions;
/// This Driver's current analysis context.
///
/// *Visible for testing.*
AnalysisContext get context => _context;
/// Set the [plugins] that are defined outside the `analyzer_cli` package.
void set userDefinedPlugins(List<Plugin> plugins) {
_userDefinedPlugins = plugins == null ? <Plugin>[] : plugins;
}
/// Use the given command-line [args] to start this analysis driver.
void start(List<String> args) {
StringUtilities.INTERNER = new MappedInterner();
_processPlugins();
// Parse commandline options.
CommandLineOptions options = CommandLineOptions.parse(args);
// Cache options of interest to inform analysis.
_setupEnv(options);
// Do analysis.
if (_isBatch) {
_BatchRunner.runAsBatch(args, (List<String> args) {
CommandLineOptions options = CommandLineOptions.parse(args);
return _analyzeAll(options);
});
} else {
ErrorSeverity severity = _analyzeAll(options);
// In case of error propagate exit code.
if (severity == ErrorSeverity.ERROR) {
exitCode = severity.ordinal;
}
}
}
/// Perform analysis according to the given [options].
ErrorSeverity _analyzeAll(CommandLineOptions options) {
if (!options.machineFormat) {
outSink.writeln("Analyzing ${options.sourceFiles}...");
}
// Create a context, or re-use the previous one.
try {
_createAnalysisContext(options);
} on _DriverError catch (error) {
outSink.writeln(error.msg);
return ErrorSeverity.ERROR;
}
// Add all the files to be analyzed en masse to the context. Skip any
// files that were added earlier (whether explicitly or implicitly) to
// avoid causing those files to be unnecessarily re-read.
Set<Source> knownSources = _context.sources.toSet();
List<Source> sourcesToAnalyze = <Source>[];
ChangeSet changeSet = new ChangeSet();
for (String sourcePath in options.sourceFiles) {
sourcePath = sourcePath.trim();
// Check that file exists.
if (!new File(sourcePath).existsSync()) {
errorSink.writeln('File not found: $sourcePath');
exitCode = ErrorSeverity.ERROR.ordinal;
//Fail fast; don't analyze more files
return ErrorSeverity.ERROR;
}
// Check that file is Dart file.
if (!AnalysisEngine.isDartFileName(sourcePath)) {
errorSink.writeln('$sourcePath is not a Dart file');
exitCode = ErrorSeverity.ERROR.ordinal;
// Fail fast; don't analyze more files.
return ErrorSeverity.ERROR;
}
Source source = _computeLibrarySource(sourcePath);
if (!knownSources.contains(source)) {
changeSet.addedSource(source);
}
sourcesToAnalyze.add(source);
}
_context.applyChanges(changeSet);
// Analyze the libraries.
ErrorSeverity allResult = ErrorSeverity.NONE;
var libUris = <Uri>[];
var parts = <Source>[];
for (Source source in sourcesToAnalyze) {
if (context.computeKindOf(source) == SourceKind.PART) {
parts.add(source);
continue;
}
ErrorSeverity status = _runAnalyzer(source, options);
allResult = allResult.max(status);
libUris.add(source.uri);
}
// Check that each part has a corresponding source in the input list.
for (Source part in parts) {
bool found = false;
for (var lib in context.getLibrariesContaining(part)) {
if (libUris.contains(lib.uri)) {
found = true;
}
}
if (!found) {
errorSink.writeln("${part.fullName} is a part and cannot be analyzed.");
errorSink.writeln("Please pass in a library that contains this part.");
exitCode = ErrorSeverity.ERROR.ordinal;
allResult = allResult.max(ErrorSeverity.ERROR);
}
}
return allResult;
}
/// Determine whether the context created during a previous call to
/// [_analyzeAll] can be re-used in order to analyze using [options].
bool _canContextBeReused(CommandLineOptions options) {
// TODO(paulberry): add a command-line option that disables context re-use.
if (_context == null) {
return false;
}
if (options.packageRootPath != _previousOptions.packageRootPath) {
return false;
}
if (options.packageConfigPath != _previousOptions.packageConfigPath) {
return false;
}
if (!_equalMaps(
options.definedVariables, _previousOptions.definedVariables)) {
return false;
}
if (options.log != _previousOptions.log) {
return false;
}
if (options.disableHints != _previousOptions.disableHints) {
return false;
}
if (options.enableStrictCallChecks !=
_previousOptions.enableStrictCallChecks) {
return false;
}
if (options.showPackageWarnings != _previousOptions.showPackageWarnings) {
return false;
}
if (options.showSdkWarnings != _previousOptions.showSdkWarnings) {
return false;
}
if (options.lints != _previousOptions.lints) {
return false;
}
if (options.strongMode != _previousOptions.strongMode) {
return false;
}
if (options.enableSuperMixins != _previousOptions.enableSuperMixins) {
return false;
}
return true;
}
/// Decide on the appropriate policy for which files need to be fully parsed
/// and which files need to be diet parsed, based on [options], and return an
/// [AnalyzeFunctionBodiesPredicate] that implements this policy.
AnalyzeFunctionBodiesPredicate _chooseDietParsingPolicy(
CommandLineOptions options) {
if (_isBatch) {
// As analyzer is currently implemented, once a file has been diet
// parsed, it can't easily be un-diet parsed without creating a brand new
// context and losing caching. In batch mode, we can't predict which
// files we'll need to generate errors and warnings for in the future, so
// we can't safely diet parse anything.
return (Source source) => true;
}
// Determine the set of packages requiring a full parse. Use null to
// represent the case where all packages require a full parse.
Set<String> packagesRequiringFullParse;
if (options.showPackageWarnings) {
// We are showing warnings from all packages so all packages require a
// full parse.
packagesRequiringFullParse = null;
} else {
// We aren't showing warnings for dependent packages, but we may still
// need to show warnings for "self" packages, so we need to do a full
// parse in any package containing files mentioned on the command line.
// TODO(paulberry): implement this. As a temporary workaround, we're
// fully parsing all packages.
packagesRequiringFullParse = null;
}
return (Source source) {
if (source.uri.scheme == 'dart') {
return options.showSdkWarnings;
} else if (source.uri.scheme == 'package') {
if (packagesRequiringFullParse == null) {
return true;
} else if (source.uri.pathSegments.length == 0) {
// We should never see a URI like this, but fully parse it to be
// safe.
return true;
} else {
return packagesRequiringFullParse
.contains(source.uri.pathSegments[0]);
}
} else {
return true;
}
};
}
/// Decide on the appropriate method for resolving URIs based on the given
/// [options] and [customUrlMappings] settings, and return a
/// [SourceFactory] that has been configured accordingly.
SourceFactory _chooseUriResolutionPolicy(CommandLineOptions options) {
Packages packages;
Map<String, List<fileSystem.Folder>> packageMap;
UriResolver packageUriResolver;
// Process options, caching package resolution details.
if (options.packageConfigPath != null) {
String packageConfigPath = options.packageConfigPath;
Uri fileUri = new Uri.file(packageConfigPath);
try {
File configFile = new File.fromUri(fileUri).absolute;
List<int> bytes = configFile.readAsBytesSync();
Map<String, Uri> map = pkgfile.parse(bytes, configFile.uri);
packages = new MapPackages(map);
packageMap = _getPackageMap(packages);
} catch (e) {
printAndFail(
'Unable to read package config data from $packageConfigPath: $e');
}
} else if (options.packageRootPath != null) {
packageMap = _PackageRootPackageMapBuilder
.buildPackageMap(options.packageRootPath);
JavaFile packageDirectory = new JavaFile(options.packageRootPath);
packageUriResolver = new PackageUriResolver([packageDirectory]);
} else {
fileSystem.Resource cwd =
PhysicalResourceProvider.INSTANCE.getResource('.');
// Look for .packages.
packages = _discoverPackagespec(new Uri.directory(cwd.path));
if (packages != null) {
packageMap = _getPackageMap(packages);
} else {
// Fall back to pub list-package-dirs.
PubPackageMapProvider pubPackageMapProvider =
new PubPackageMapProvider(PhysicalResourceProvider.INSTANCE, sdk);
PackageMapInfo packageMapInfo =
pubPackageMapProvider.computePackageMap(cwd);
packageMap = packageMapInfo.packageMap;
// Only create a packageUriResolver if pub list-package-dirs succeeded.
// If it failed, that's not a problem; it simply means we have no way
// to resolve packages.
if (packageMapInfo.packageMap != null) {
packageUriResolver = new PackageMapUriResolver(
PhysicalResourceProvider.INSTANCE, packageMap);
}
}
}
// Now, build our resolver list.
// 'dart:' URIs come first.
List<UriResolver> resolvers = [new DartUriResolver(sdk)];
// Next SdkExts.
if (packageMap != null) {
resolvers.add(new SdkExtUriResolver(packageMap));
}
// Then package URIs.
if (packageUriResolver != null) {
resolvers.add(packageUriResolver);
}
// Finally files.
resolvers.add(new FileUriResolver());
return new SourceFactory(resolvers, packages);
}
/// Convert the given [sourcePath] (which may be relative to the current
/// working directory) to a [Source] object that can be fed to the analysis
/// context.
Source _computeLibrarySource(String sourcePath) {
sourcePath = _normalizeSourcePath(sourcePath);
JavaFile sourceFile = new JavaFile(sourcePath);
Source source = sdk.fromFileUri(sourceFile.toURI());
if (source != null) {
return source;
}
source = new FileBasedSource(sourceFile, sourceFile.toURI());
Uri uri = _context.sourceFactory.restoreUri(source);
if (uri == null) {
return source;
}
return new FileBasedSource(sourceFile, uri);
}
/// Create an analysis context that is prepared to analyze sources according
/// to the given [options], and store it in [_context].
void _createAnalysisContext(CommandLineOptions options) {
if (_canContextBeReused(options)) {
return;
}
_previousOptions = options;
// Choose a package resolution policy and a diet parsing policy based on
// the command-line options.
SourceFactory sourceFactory = _chooseUriResolutionPolicy(options);
AnalyzeFunctionBodiesPredicate dietParsingPolicy =
_chooseDietParsingPolicy(options);
// Create a context using these policies.
AnalysisContext context = AnalysisEngine.instance.createAnalysisContext();
context.sourceFactory = sourceFactory;
Map<String, String> definedVariables = options.definedVariables;
if (!definedVariables.isEmpty) {
DeclaredVariables declaredVariables = context.declaredVariables;
definedVariables.forEach((String variableName, String value) {
declaredVariables.define(variableName, value);
});
}
if (options.log) {
AnalysisEngine.instance.logger = new StdLogger();
}
// Set context options.
AnalysisOptionsImpl contextOptions = new AnalysisOptionsImpl();
contextOptions.cacheSize = _maxCacheSize;
contextOptions.hint = !options.disableHints;
contextOptions.enableStrictCallChecks = options.enableStrictCallChecks;
contextOptions.enableSuperMixins = options.enableSuperMixins;
contextOptions.analyzeFunctionBodiesPredicate = dietParsingPolicy;
contextOptions.generateImplicitErrors = options.showPackageWarnings;
contextOptions.generateSdkErrors = options.showSdkWarnings;
contextOptions.lint = options.lints;
contextOptions.strongMode = options.strongMode;
context.analysisOptions = contextOptions;
_context = context;
// Process analysis options file (and notify all interested parties).
_processAnalysisOptions(options, context);
}
/// Return discovered packagespec, or `null` if none is found.
Packages _discoverPackagespec(Uri root) {
try {
Packages packages = pkgDiscovery.findPackagesFromFile(root);
if (packages != Packages.noPackages) {
return packages;
}
} catch (_) {
// Ignore and fall through to null.
}
return null;
}
fileSystem.File _getOptionsFile(CommandLineOptions options) {
fileSystem.File file;
String filePath = options.analysisOptionsFile;
if (filePath != null) {
file = PhysicalResourceProvider.INSTANCE.getFile(filePath);
if (!file.exists) {
printAndFail('Options file not found: $filePath',
exitCode: ErrorSeverity.ERROR.ordinal);
}
} else {
filePath = AnalysisEngine.ANALYSIS_OPTIONS_FILE;
file = PhysicalResourceProvider.INSTANCE.getFile(filePath);
}
return file;
}
Map<String, List<fileSystem.Folder>> _getPackageMap(Packages packages) {
if (packages == null) {
return null;
}
Map<String, List<fileSystem.Folder>> folderMap =
new Map<String, List<fileSystem.Folder>>();
packages.asMap().forEach((String packagePath, Uri uri) {
folderMap[packagePath] = [
PhysicalResourceProvider.INSTANCE.getFolder(path.fromUri(uri))
];
});
return folderMap;
}
void _processAnalysisOptions(
CommandLineOptions options, AnalysisContext context) {
fileSystem.File file = _getOptionsFile(options);
List<OptionsProcessor> optionsProcessors =
AnalysisEngine.instance.optionsPlugin.optionsProcessors;
try {
AnalysisOptionsProvider analysisOptionsProvider =
new AnalysisOptionsProvider();
Map<String, YamlNode> optionMap =
analysisOptionsProvider.getOptionsFromFile(file);
optionsProcessors.forEach(
(OptionsProcessor p) => p.optionsProcessed(context, optionMap));
// Fill in lint rule defaults in case lints are enabled and rules are
// not specified in an options file.
if (options.lints && !containsLintRuleEntry(optionMap)) {
setLints(context, linterPlugin.contributedRules);
}
// Ask engine to further process options.
if (optionMap != null) {
configureContextOptions(context, optionMap);
}
} on Exception catch (e) {
optionsProcessors.forEach((OptionsProcessor p) => p.onError(e));
}
}
void _processPlugins() {
List<Plugin> plugins = <Plugin>[];
plugins.add(linterPlugin);
plugins.addAll(_userDefinedPlugins);
AnalysisEngine.instance.userDefinedPlugins = plugins;
// This ensures that AE extension manager processes plugins.
AnalysisEngine.instance.taskManager;
}
/// Analyze a single source.
ErrorSeverity _runAnalyzer(Source source, CommandLineOptions options) {
int startTime = currentTimeMillis();
AnalyzerImpl analyzer =
new AnalyzerImpl(_context, source, options, startTime);
var errorSeverity = analyzer.analyzeSync();
if (errorSeverity == ErrorSeverity.ERROR) {
exitCode = errorSeverity.ordinal;
}
if (options.warningsAreFatal && errorSeverity == ErrorSeverity.WARNING) {
exitCode = errorSeverity.ordinal;
}
return errorSeverity;
}
void _setupEnv(CommandLineOptions options) {
// In batch mode, SDK is specified on the main command line rather than in
// the command lines sent to stdin. So process it before deciding whether
// to activate batch mode.
if (sdk == null) {
sdk = new DirectoryBasedDartSdk(new JavaFile(options.dartSdkPath));
}
_isBatch = options.shouldBatch;
}
/// Perform a deep comparison of two string maps.
static bool _equalMaps(Map<String, String> m1, Map<String, String> m2) {
if (m1.length != m2.length) {
return false;
}
for (String key in m1.keys) {
if (!m2.containsKey(key) || m1[key] != m2[key]) {
return false;
}
}
return true;
}
/// Convert [sourcePath] into an absolute path.
static String _normalizeSourcePath(String sourcePath) =>
path.normalize(new File(sourcePath).absolute.path);
}
/// Provides a framework to read command line options from stdin and feed them
/// to a callback.
class _BatchRunner {
/// Run the tool in 'batch' mode, receiving command lines through stdin and
/// returning pass/fail status through stdout. This feature is intended for
/// use in unit testing.
static void runAsBatch(List<String> sharedArgs, _BatchRunnerHandler handler) {
outSink.writeln('>>> BATCH START');
Stopwatch stopwatch = new Stopwatch();
stopwatch.start();
int testsFailed = 0;
int totalTests = 0;
ErrorSeverity batchResult = ErrorSeverity.NONE;
// Read line from stdin.
Stream cmdLine =
stdin.transform(UTF8.decoder).transform(new LineSplitter());
cmdLine.listen((String line) {
// Maybe finish.
if (line.isEmpty) {
var time = stopwatch.elapsedMilliseconds;
outSink.writeln(
'>>> BATCH END (${totalTests - testsFailed}/$totalTests) ${time}ms');
exitCode = batchResult.ordinal;
}
// Prepare aruments.
var args;
{
var lineArgs = line.split(new RegExp('\\s+'));
args = new List<String>();
args.addAll(sharedArgs);
args.addAll(lineArgs);
args.remove('-b');
args.remove('--batch');
}
// Analyze single set of arguments.
try {
totalTests++;
ErrorSeverity result = handler(args);
bool resultPass = result != ErrorSeverity.ERROR;
if (!resultPass) {
testsFailed++;
}
batchResult = batchResult.max(result);
// Write stderr end token and flush.
errorSink.writeln('>>> EOF STDERR');
String resultPassString = resultPass ? 'PASS' : 'FAIL';
outSink.writeln(
'>>> TEST $resultPassString ${stopwatch.elapsedMilliseconds}ms');
} catch (e, stackTrace) {
errorSink.writeln(e);
errorSink.writeln(stackTrace);
errorSink.writeln('>>> EOF STDERR');
outSink.writeln('>>> TEST CRASH');
}
});
}
}
class _DriverError implements Exception {
String msg;
_DriverError(this.msg);
}
/// [SdkExtUriResolver] needs a Map from package name to folder. In the case
/// that the analyzer is invoked with a --package-root option, we need to
/// manually create this mapping. Given [packageRootPath],
/// [_PackageRootPackageMapBuilder] creates a simple mapping from package name
/// to full path on disk (resolving any symbolic links).
class _PackageRootPackageMapBuilder {
static Map<String, List<fileSystem.Folder>> buildPackageMap(
String packageRootPath) {
var packageRoot = new Directory(packageRootPath);
if (!packageRoot.existsSync()) {
throw new _DriverError(
'Package root directory ($packageRootPath) does not exist.');
}
var packages = packageRoot.listSync(followLinks: false);
var result = new Map<String, List<fileSystem.Folder>>();
for (var package in packages) {
var packageName = path.basename(package.path);
var realPath = package.resolveSymbolicLinksSync();
result[packageName] = [
PhysicalResourceProvider.INSTANCE.getFolder(realPath)
];
}
return result;
}
}

View file

@ -0,0 +1,196 @@
// 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 analyzer_cli.src.error_formatter;
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/error.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer_cli/src/analyzer_impl.dart';
import 'package:analyzer_cli/src/options.dart';
/// Allows any [AnalysisError].
bool _anyError(AnalysisError error) => true;
/// Returns `true` if [AnalysisError] should be printed.
typedef bool _ErrorFilter(AnalysisError error);
/// Helper for formatting [AnalysisError]s.
/// The two format options are a user consumable format and a machine consumable format.
class ErrorFormatter {
final StringSink out;
final CommandLineOptions options;
final _ErrorFilter errorFilter;
ErrorFormatter(this.out, this.options, [this.errorFilter = _anyError]);
void formatError(
Map<AnalysisError, LineInfo> errorToLine, AnalysisError error) {
Source source = error.source;
LineInfo_Location location = errorToLine[error].getLocation(error.offset);
int length = error.length;
ErrorSeverity severity =
AnalyzerImpl.computeSeverity(error, options);
if (options.machineFormat) {
if (severity == ErrorSeverity.WARNING && options.warningsAreFatal) {
severity = ErrorSeverity.ERROR;
}
out.write(severity);
out.write('|');
out.write(error.errorCode.type);
out.write('|');
out.write(error.errorCode.name);
out.write('|');
out.write(escapePipe(source.fullName));
out.write('|');
out.write(location.lineNumber);
out.write('|');
out.write(location.columnNumber);
out.write('|');
out.write(length);
out.write('|');
out.write(escapePipe(error.message));
} else {
String errorType = severity.displayName;
if (error.errorCode.type == ErrorType.HINT ||
error.errorCode.type == ErrorType.LINT) {
errorType = error.errorCode.type.displayName;
}
// [warning] 'foo' is not a... (/Users/.../tmp/foo.dart, line 1, col 2)
out.write('[$errorType] ${error.message} ');
out.write('(${source.fullName}');
out.write(', line ${location.lineNumber}, col ${location.columnNumber})');
}
out.writeln();
}
void formatErrors(List<AnalysisErrorInfo> errorInfos) {
var errors = new List<AnalysisError>();
var errorToLine = new Map<AnalysisError, LineInfo>();
for (AnalysisErrorInfo errorInfo in errorInfos) {
for (AnalysisError error in errorInfo.errors) {
if (errorFilter(error)) {
errors.add(error);
errorToLine[error] = errorInfo.lineInfo;
}
}
}
// Sort errors.
errors.sort((AnalysisError error1, AnalysisError error2) {
// Severity.
ErrorSeverity severity1 =
AnalyzerImpl.computeSeverity(error1, options);
ErrorSeverity severity2 =
AnalyzerImpl.computeSeverity(error2, options);
int compare = severity2.compareTo(severity1);
if (compare != 0) {
return compare;
}
// Path.
compare = Comparable.compare(error1.source.fullName.toLowerCase(),
error2.source.fullName.toLowerCase());
if (compare != 0) {
return compare;
}
// Offset.
return error1.offset - error2.offset;
});
// Format errors.
int errorCount = 0;
int warnCount = 0;
int hintCount = 0;
int lintCount = 0;
for (AnalysisError error in errors) {
ErrorSeverity severity =
AnalyzerImpl.computeSeverity(error, options);
if (severity == ErrorSeverity.ERROR) {
errorCount++;
} else if (severity == ErrorSeverity.WARNING) {
if (options.warningsAreFatal) {
errorCount++;
} else {
warnCount++;
}
} else if (error.errorCode.type == ErrorType.HINT) {
hintCount++;
} else if (error.errorCode.type == ErrorType.LINT) {
lintCount++;
}
formatError(errorToLine, error);
}
// Print statistics.
if (!options.machineFormat) {
var hasErrors = errorCount != 0;
var hasWarns = warnCount != 0;
var hasHints = hintCount != 0;
var hasLints = lintCount != 0;
bool hasContent = false;
if (hasErrors) {
out.write(errorCount);
out.write(' ');
out.write(pluralize("error", errorCount));
hasContent = true;
}
if (hasWarns) {
if (hasContent) {
if (!hasHints && !hasLints) {
out.write(' and ');
} else {
out.write(", ");
}
}
out.write(warnCount);
out.write(' ');
out.write(pluralize("warning", warnCount));
hasContent = true;
}
if (hasHints) {
if (hasContent) {
if (!hasLints) {
out.write(' and ');
} else {
out.write(", ");
}
}
out.write(hintCount);
out.write(' ');
out.write(pluralize("hint", hintCount));
hasContent = true;
}
if (hasLints) {
if (hasContent) {
out.write(" and ");
}
out.write(lintCount);
out.write(' ');
out.write(pluralize("lint", lintCount));
hasContent = true;
}
if (hasContent) {
out.writeln(" found.");
} else {
out.writeln("No issues found");
}
}
}
static String escapePipe(String input) {
var result = new StringBuffer();
for (var c in input.codeUnits) {
if (c == '\\' || c == '|') {
result.write('\\');
}
result.writeCharCode(c);
}
return result.toString();
}
static String pluralize(String word, int count) {
if (count == 1) {
return word;
} else {
return word + "s";
}
}
}

View file

@ -0,0 +1,485 @@
// 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 analyzer_cli.src.options;
import 'dart:io';
import 'package:analyzer_cli/src/driver.dart';
import 'package:args/args.dart';
import 'package:cli_util/cli_util.dart' show getSdkDir;
const _binaryName = 'dartanalyzer';
/// Shared exit handler.
///
/// *Visible for testing.*
ExitHandler exitHandler = exit;
/// Print the given message to stderr and exit with the given [exitCode]
void printAndFail(String message, {int exitCode: 15}) {
errorSink.writeln(message);
exitHandler(exitCode);
}
/// Exit handler.
///
/// *Visible for testing.*
typedef void ExitHandler(int code);
/// Analyzer commandline configuration options.
class CommandLineOptions {
/// The path to an analysis options file
final String analysisOptionsFile;
/// The path to the dart SDK
String dartSdkPath;
/// A table mapping the names of defined variables to their values.
final Map<String, String> definedVariables;
/// Whether to report hints
final bool disableHints;
/// Whether to display version information
final bool displayVersion;
/// Whether to enable null-aware operators (DEP 9).
final bool enableNullAwareOperators;
/// Whether to strictly follow the specification when generating warnings on
/// "call" methods (fixes dartbug.com/21938).
final bool enableStrictCallChecks;
/// Whether to relax restrictions on mixins (DEP 34).
final bool enableSuperMixins;
/// Whether to treat type mismatches found during constant evaluation as
/// errors.
final bool enableTypeChecks;
/// Whether to treat hints as fatal
final bool hintsAreFatal;
/// Whether to ignore unrecognized flags
final bool ignoreUnrecognizedFlags;
/// Whether to report lints
final bool lints;
/// Whether to log additional analysis messages and exceptions
final bool log;
/// Whether to use machine format for error display
final bool machineFormat;
/// The path to the package root
final String packageRootPath;
/// The path to a `.packages` configuration file
final String packageConfigPath;
/// Batch mode (for unit testing)
final bool shouldBatch;
/// Whether to show package: warnings
final bool showPackageWarnings;
/// Whether to show SDK warnings
final bool showSdkWarnings;
/// The source files to analyze
final List<String> sourceFiles;
/// Whether to treat warnings as fatal
final bool warningsAreFatal;
/// Whether to use strong static checking.
final bool strongMode;
/// Initialize options from the given parsed [args].
CommandLineOptions._fromArgs(
ArgResults args, Map<String, String> definedVariables)
: dartSdkPath = args['dart-sdk'],
this.definedVariables = definedVariables,
analysisOptionsFile = args['options'],
disableHints = args['no-hints'],
displayVersion = args['version'],
enableNullAwareOperators = args['enable-null-aware-operators'],
enableStrictCallChecks = args['enable-strict-call-checks'],
enableSuperMixins = args['supermixin'],
enableTypeChecks = args['enable_type_checks'],
hintsAreFatal = args['fatal-hints'],
ignoreUnrecognizedFlags = args['ignore-unrecognized-flags'],
lints = args['lints'],
log = args['log'],
machineFormat = args['machine'] || args['format'] == 'machine',
packageConfigPath = args['packages'],
packageRootPath = args['package-root'],
shouldBatch = args['batch'],
showPackageWarnings =
args['show-package-warnings'] || args['package-warnings'],
showSdkWarnings = args['show-sdk-warnings'] || args['warnings'],
sourceFiles = args.rest,
warningsAreFatal = args['fatal-warnings'],
strongMode = args['strong'];
/// Parse [args] into [CommandLineOptions] describing the specified
/// analyzer options. In case of a format error, calls [printAndFail], which
/// by default prints an error message to stderr and exits.
static CommandLineOptions parse(List<String> args,
[printAndFail = printAndFail]) {
CommandLineOptions options = _parse(args);
// Check SDK.
{
// Infer if unspecified.
if (options.dartSdkPath == null) {
Directory sdkDir = getSdkDir(args);
if (sdkDir != null) {
options.dartSdkPath = sdkDir.path;
}
}
var sdkPath = options.dartSdkPath;
// Check that SDK is specified.
if (sdkPath == null) {
printAndFail('No Dart SDK found.');
}
// Check that SDK is existing directory.
if (!(new Directory(sdkPath)).existsSync()) {
printAndFail('Invalid Dart SDK path: $sdkPath');
}
}
// Check package config.
{
if (options.packageRootPath != null &&
options.packageConfigPath != null) {
printAndFail("Cannot specify both '--package-root' and '--packages.");
}
}
// OK. Report deprecated options.
if (options.enableNullAwareOperators) {
stderr.writeln(
"Info: Option '--enable-null-aware-operators' is no longer needed. Null aware operators are supported by default.");
}
return options;
}
static String _getVersion() {
try {
// This is relative to bin/snapshot, so ../..
String versionPath =
Platform.script.resolve('../../version').toFilePath();
File versionFile = new File(versionPath);
return versionFile.readAsStringSync().trim();
} catch (_) {
// This happens when the script is not running in the context of an SDK.
return "<unknown>";
}
}
static CommandLineOptions _parse(List<String> args) {
args = args.expand((String arg) => arg.split('=')).toList();
var parser = new CommandLineParser()
..addFlag('batch',
abbr: 'b',
help: 'Read commands from standard input (for testing).',
defaultsTo: false,
negatable: false)
..addOption('dart-sdk', help: 'The path to the Dart SDK.')
..addOption('packages',
help:
'Path to the package resolution configuration file, which supplies a mapping of package names to paths. This option cannot be used with --package-root.')
..addOption('package-root',
abbr: 'p',
help:
'Path to a package root directory (deprecated). This option cannot be used with --packages.')
..addOption('options', help: 'Path to an analysis options file.')
..addOption('format',
help: 'Specifies the format in which errors are displayed.')
..addFlag('machine',
help: 'Print errors in a format suitable for parsing (deprecated).',
defaultsTo: false,
negatable: false)
..addFlag('version',
help: 'Print the analyzer version.',
defaultsTo: false,
negatable: false)
..addFlag('lints',
help: 'Show lint results.', defaultsTo: false, negatable: false)
..addFlag('no-hints',
help: 'Do not show hint results.',
defaultsTo: false,
negatable: false)
..addFlag('ignore-unrecognized-flags',
help: 'Ignore unrecognized command line flags.',
defaultsTo: false,
negatable: false)
..addFlag('fatal-hints',
help: 'Treat hints as fatal.', defaultsTo: false, negatable: false)
..addFlag('fatal-warnings',
help: 'Treat non-type warnings as fatal.',
defaultsTo: false,
negatable: false)
..addFlag('package-warnings',
help: 'Show warnings from package: imports.',
defaultsTo: false,
negatable: false)
..addFlag('show-package-warnings',
help: 'Show warnings from package: imports (deprecated).',
defaultsTo: false,
negatable: false)
..addFlag('warnings',
help: 'Show warnings from SDK imports.',
defaultsTo: false,
negatable: false)
..addFlag('show-sdk-warnings',
help: 'Show warnings from SDK imports (deprecated).',
defaultsTo: false,
negatable: false)
..addFlag('help',
abbr: 'h',
help: 'Display this help message.',
defaultsTo: false,
negatable: false)
..addOption('url-mapping',
help: '--url-mapping=libraryUri,/path/to/library.dart directs the '
'analyzer to use "library.dart" as the source for an import '
'of "libraryUri".',
allowMultiple: true,
splitCommas: false)
//
// Hidden flags.
//
..addFlag('enable-async',
help: 'Enable support for the proposed async feature.',
defaultsTo: false,
negatable: false,
hide: true)
..addFlag('enable-enum',
help: 'Enable support for the proposed enum feature.',
defaultsTo: false,
negatable: false,
hide: true)
..addFlag('enable-null-aware-operators',
help: 'Enable support for null-aware operators (DEP 9).',
defaultsTo: false,
negatable: false,
hide: true)
..addFlag('enable-strict-call-checks',
help: 'Fix issue 21938.',
defaultsTo: false,
negatable: false,
hide: true)
..addFlag('enable-new-task-model',
help: 'Ennable new task model.',
defaultsTo: false,
negatable: false,
hide: true)
..addFlag('supermixin',
help: 'Relax restrictions on mixins (DEP 34).',
defaultsTo: false,
negatable: false,
hide: true)
..addFlag('log',
help: 'Log additional messages and exceptions.',
defaultsTo: false,
negatable: false,
hide: true)
..addFlag('enable_type_checks',
help: 'Check types in constant evaluation.',
defaultsTo: false,
negatable: false,
hide: true)
..addFlag('strong',
help: 'Enable strong static checks (https://goo.gl/DqcBsw)');
try {
// TODO(scheglov) https://code.google.com/p/dart/issues/detail?id=11061
args =
args.map((String arg) => arg == '-batch' ? '--batch' : arg).toList();
Map<String, String> definedVariables = <String, String>{};
var results = parser.parse(args, definedVariables);
// Help requests.
if (results['help']) {
_showUsage(parser);
exit(0);
}
// Batch mode and input files.
if (results['batch']) {
if (results.rest.isNotEmpty) {
stderr.writeln('No source files expected in the batch mode.');
_showUsage(parser);
exit(15);
}
} else if (results['version']) {
print('$_binaryName version ${_getVersion()}');
exit(0);
} else {
if (results.rest.isEmpty) {
_showUsage(parser);
exit(15);
}
}
return new CommandLineOptions._fromArgs(results, definedVariables);
} on FormatException catch (e) {
stderr.writeln(e.message);
_showUsage(parser);
exit(15);
}
}
static _showUsage(parser) {
stderr
.writeln('Usage: $_binaryName [options...] <libraries to analyze...>');
stderr.writeln(parser.getUsage());
stderr.writeln('');
stderr.writeln(
'For more information, see http://www.dartlang.org/tools/analyzer.');
}
}
/// Commandline argument parser.
///
/// TODO(pquitslund): when the args package supports ignoring unrecognized
/// options/flags, this class can be replaced with a simple [ArgParser]
/// instance.
class CommandLineParser {
final List<String> _knownFlags;
final bool _alwaysIgnoreUnrecognized;
final ArgParser _parser;
/// Creates a new command line parser.
CommandLineParser({bool alwaysIgnoreUnrecognized: false})
: _knownFlags = <String>[],
_alwaysIgnoreUnrecognized = alwaysIgnoreUnrecognized,
_parser = new ArgParser(allowTrailingOptions: true);
ArgParser get parser => _parser;
/// Defines a flag.
/// See [ArgParser.addFlag()].
void addFlag(String name,
{String abbr,
String help,
bool defaultsTo: false,
bool negatable: true,
void callback(bool value),
bool hide: false}) {
_knownFlags.add(name);
_parser.addFlag(name,
abbr: abbr,
help: help,
defaultsTo: defaultsTo,
negatable: negatable,
callback: callback,
hide: hide);
}
/// Defines a value-taking option.
/// See [ArgParser.addOption()].
void addOption(String name,
{String abbr,
String help,
List<String> allowed,
Map<String, String> allowedHelp,
String defaultsTo,
void callback(value),
bool allowMultiple: false,
bool splitCommas}) {
_knownFlags.add(name);
_parser.addOption(name,
abbr: abbr,
help: help,
allowed: allowed,
allowedHelp: allowedHelp,
defaultsTo: defaultsTo,
callback: callback,
allowMultiple: allowMultiple,
splitCommas: splitCommas);
}
/// Generates a string displaying usage information for the defined options.
/// See [ArgParser.usage].
String getUsage() => _parser.usage;
/// Parses [args], a list of command-line arguments, matches them against the
/// flags and options defined by this parser, and returns the result. The
/// values of any defined variables are captured in the given map.
/// See [ArgParser].
ArgResults parse(List<String> args, Map<String, String> definedVariables) =>
_parser.parse(
_filterUnknowns(parseDefinedVariables(args, definedVariables)));
List<String> parseDefinedVariables(
List<String> args, Map<String, String> definedVariables) {
int count = args.length;
List<String> remainingArgs = <String>[];
for (int i = 0; i < count; i++) {
String arg = args[i];
if (arg == '--') {
while (i < count) {
remainingArgs.add(args[i++]);
}
} else if (arg.startsWith("-D")) {
definedVariables[arg.substring(2)] = args[++i];
} else {
remainingArgs.add(arg);
}
}
return remainingArgs;
}
List<String> _filterUnknowns(List<String> args) {
// Only filter args if the ignore flag is specified, or if
// _alwaysIgnoreUnrecognized was set to true.
if (_alwaysIgnoreUnrecognized ||
args.contains('--ignore-unrecognized-flags')) {
//TODO(pquitslund): replace w/ the following once library skew issues are
// sorted out
//return args.where((arg) => !arg.startsWith('--') ||
// _knownFlags.contains(arg.substring(2)));
// Filter all unrecognized flags and options.
List<String> filtered = <String>[];
for (int i = 0; i < args.length; ++i) {
String arg = args[i];
if (arg.startsWith('--') && arg.length > 2) {
String option = arg.substring(2);
// strip the last '=value'
int equalsOffset = option.lastIndexOf('=');
if (equalsOffset != -1) {
option = option.substring(0, equalsOffset);
}
// Check the option
if (!_knownFlags.contains(option)) {
//"eat" params by advancing to the next flag/option
i = _getNextFlagIndex(args, i);
} else {
filtered.add(arg);
}
} else {
filtered.add(arg);
}
}
return filtered;
} else {
return args;
}
}
int _getNextFlagIndex(args, i) {
for (; i < args.length; ++i) {
if (args[i].startsWith('--')) {
return i;
}
}
return i;
}
}

View file

@ -0,0 +1,110 @@
// 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 analyzer_cli.src.plugin.plugin_manager;
import 'dart:io';
import 'package:analyzer/src/plugin/plugin_configuration.dart';
import 'package:path/path.dart' as path;
const _manifestFileName = 'plugins.yaml';
/// Given a local configuration (as defined in `.analysis_options`)
/// and information from a plugin manifest, return plugin info
/// appropriate for configuring this plugin.
PluginInfo combine(PluginInfo localConfig, PluginInfo manifestInfo) {
return new PluginInfo(
name: localConfig.name,
version: manifestInfo.version,
className: manifestInfo.className,
libraryUri: manifestInfo.libraryUri);
}
/// Call-back to allow for the injection of manifest readers that do not need
/// to go to disk (for testing purposes).
typedef String ManifestReader(Uri uri);
/// Wraps a [plugin] info object elaborated with any configuration information
/// extracted from an associated manifest and [status].
class PluginDetails {
/// Plugin status.
final PluginStatus status;
/// Plugin info.
final PluginInfo plugin;
/// Wrap a [plugin] with [status] info.
PluginDetails(this.plugin) : status = PluginStatus.Applicable;
PluginDetails.notApplicable(this.plugin)
: status = PluginStatus.NotApplicable;
PluginDetails.notFound(this.plugin) : status = PluginStatus.NotFound;
}
/// Manages plugin information derived from plugin manifests.
class PluginManager {
/// Mapping from package name to package location.
final Map<String, Uri> _packageMap;
/// The package naming the app to host plugins.
final String hostPackage;
/// Function to perform the reading of manifest URIs. (For testing.)
ManifestReader _manifestReader;
/// Create a plugin manager with backing package map information.
PluginManager(this._packageMap, this.hostPackage,
[ManifestReader manifestReader]) {
_manifestReader =
manifestReader != null ? manifestReader : _findAndReadManifestAtUri;
}
/// Find a plugin manifest describing the given [pluginPackage].
PluginManifest findManifest(String pluginPackage) {
Uri uri = _packageMap[pluginPackage];
String contents = _manifestReader(uri);
if (contents == null) {
return null;
}
return parsePluginManifestString(contents);
}
/// Return [PluginDetails] derived from associated plugin manifests
/// corresponding to plugins specified in the given [config].
Iterable<PluginDetails> getPluginDetails(PluginConfig config) =>
config.plugins.map((PluginInfo localConfig) {
PluginManifest manifest = findManifest(localConfig.name);
return _getDetails(localConfig, manifest);
});
String _findAndReadManifestAtUri(Uri uri) {
File manifestFile = _findManifest(uri);
return manifestFile?.readAsStringSync();
}
File _findManifest(Uri uri) {
if (uri == null) {
return null;
}
Directory directory = new Directory.fromUri(uri);
File file = new File(path.join(directory.path, _manifestFileName));
return file.existsSync() ? file : null;
}
PluginDetails _getDetails(PluginInfo localConfig, PluginManifest manifest) {
if (manifest == null) {
return new PluginDetails.notFound(localConfig);
}
if (!manifest.contributesTo.contains(hostPackage)) {
return new PluginDetails.notApplicable(localConfig);
}
return new PluginDetails(combine(localConfig, manifest.plugin));
}
}
/// Describes plugin status.
enum PluginStatus { Applicable, NotApplicable, NotFound }

View file

@ -0,0 +1,18 @@
name: analyzer_cli
version: 1.1.2
author: Dart Team <misc@dartlang.org>
description: Command line interface for the Dart Analyzer.
homepage: https://github.com/dart-lang/analyzer_cli
environment:
sdk: '>=1.12.0 <2.0.0'
dependencies:
analyzer: ^0.26.1+17
args: ^0.13.0
cli_util: ^0.0.1
linter: ^0.1.3+4
package_config: ^0.1.1
plugin: ^0.1.0
yaml: ^2.1.2
dev_dependencies:
typed_mock: '>=0.0.4 <1.0.0'
test: ^0.12.0

View file

@ -0,0 +1,23 @@
// 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 'driver_test.dart' as driver;
import 'error_test.dart' as error;
import 'options_test.dart' as options;
import 'plugin_manager_test.dart' as plugin_manager;
import 'reporter_test.dart' as reporter;
import 'sdk_ext_test.dart' as sdk_ext;
import 'strong_mode_test.dart' as strong_mode;
import 'super_mixin_test.dart' as super_mixin;
main() {
driver.main();
error.main();
options.main();
plugin_manager.main();
reporter.main();
sdk_ext.main();
strong_mode.main();
super_mixin.main();
}

View file

@ -0,0 +1,7 @@
analyzer:
plugins:
angular2_analyzer_plugin:
path: /Users/pquitslund/src/git/clones/angular2-dart-analyzer/analyzer_plugin/
class_name: AngularAnalyzerPlugin
library_uri: package:angular2_analyzer_plugin/plugin.dart

View file

@ -0,0 +1,6 @@
analyzer:
plugins:
- lists
- are
- not
- supported

View file

@ -0,0 +1,3 @@
error

View file

@ -0,0 +1,3 @@
main() {
int unused;
}

View file

@ -0,0 +1,3 @@
main() {
undefined();
}

View file

@ -0,0 +1,3 @@
library example;
part 'part1.dart';

View file

@ -0,0 +1 @@
part of example;

View file

@ -0,0 +1 @@
part of nothing;

View file

@ -0,0 +1,3 @@
linter:
rules:
- camel_case_types

View file

@ -0,0 +1,7 @@
// 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 analyzer_cli.test.data.linter_project.test_file;
class a {}

View file

@ -0,0 +1 @@
# empty

View file

@ -0,0 +1,7 @@
// 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 analyzer_cli.test.data.no_lints_project.test_file;
class a {}

View file

@ -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.
import 'dart:foo';
main() {
// import of 'dart:foo' should resolve to a file which defines bar().
bar();
}

View file

@ -0,0 +1,5 @@
analyzer:
errors:
unused_local_variable: ignore
language:
enableSuperMixins: true

View file

@ -0,0 +1,7 @@
// 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 analyzer_cli.test.data.options_test_project.test_file;
class a {}

View file

@ -0,0 +1,3 @@
{
"dart:foo": "foo.dart"
}

View file

@ -0,0 +1,5 @@
// 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.
void bar() {}

View file

@ -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.
import 'dart:foo';
main() {
// import of 'dart:foo' should resolve to a file which defines bar().
bar();
}

View file

@ -0,0 +1,7 @@
analyzer:
plugins:
my_plugin1:
version: any
library_uri: 'package:my_plugin/my_plugin.dart'
class_name: MyPlugin

View file

@ -0,0 +1,18 @@
// 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.
/// This produces an error with --strong enabled, but not otherwise.
class MyIterable extends Iterable<String> {
// Error: invalid override
Iterator<Object> get iterator => [1, 2, 3].iterator;
}
main() {
var i = new MyIterable().iterator..moveNext();
print(i.current);
// Error: type check failed
List<String> list = <dynamic>[1, 2, 3];
print(list);
}

View file

@ -0,0 +1,18 @@
// 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.
/// This produces errors normally, but --supermixin disables them.
class Test extends Object with C {
void foo() {}
}
abstract class B {
void foo();
}
abstract class C extends B {
void bar() {
super.foo();
}
}

View file

@ -0,0 +1,5 @@
// 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 analyzer_cli.test.data.test_file;

View file

@ -0,0 +1,2 @@
test_plugin:
foo: bar

View file

@ -0,0 +1,484 @@
// 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.
@TestOn("vm")
library analyzer_cli.test.driver;
import 'dart:io';
import 'package:analyzer/plugin/options.dart';
import 'package:analyzer/source/analysis_options_provider.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/plugin/plugin_configuration.dart';
import 'package:analyzer/src/services/lint.dart';
import 'package:analyzer_cli/src/bootloader.dart';
import 'package:analyzer_cli/src/driver.dart';
import 'package:analyzer_cli/src/options.dart';
import 'package:path/path.dart' as path;
import 'package:plugin/plugin.dart';
import 'package:test/test.dart';
import 'package:yaml/src/yaml_node.dart';
main() {
group('Driver', () {
StringSink savedOutSink, savedErrorSink;
int savedExitCode;
setUp(() {
savedOutSink = outSink;
savedErrorSink = errorSink;
savedExitCode = exitCode;
outSink = new StringBuffer();
errorSink = new StringBuffer();
});
tearDown(() {
outSink = savedOutSink;
errorSink = savedErrorSink;
exitCode = savedExitCode;
});
group('options', () {
test('custom processor', () {
Driver driver = new Driver();
TestProcessor processor = new TestProcessor();
driver.userDefinedPlugins = [new TestPlugin(processor)];
driver.start([
'--options',
'test/data/test_options.yaml',
'test/data/test_file.dart'
]);
expect(processor.options['test_plugin'], isNotNull);
expect(processor.exception, isNull);
});
});
group('exit codes', () {
StringSink savedOutSink, savedErrorSink;
int savedExitCode;
ExitHandler savedExitHandler;
setUp(() {
savedOutSink = outSink;
savedErrorSink = errorSink;
savedExitCode = exitCode;
savedExitHandler = exitHandler;
exitHandler = (code) => exitCode = code;
outSink = new StringBuffer();
errorSink = new StringBuffer();
});
tearDown(() {
outSink = savedOutSink;
errorSink = savedErrorSink;
exitCode = savedExitCode;
exitHandler = savedExitHandler;
});
test('fatal hints', () {
drive('test/data/file_with_hint.dart', args: ['--fatal-hints']);
expect(exitCode, 3);
});
test('not fatal hints', () {
drive('test/data/file_with_hint.dart');
expect(exitCode, 0);
});
test('fatal errors', () {
drive('test/data/file_with_error.dart');
expect(exitCode, 3);
});
test('not fatal warnings', () {
drive('test/data/file_with_warning.dart');
expect(exitCode, 0);
});
test('fatal warnings', () {
drive('test/data/file_with_warning.dart', args: ['--fatal-warnings']);
expect(exitCode, 3);
});
test('missing options file', () {
drive('test/data/test_file.dart', options: 'test/data/NO_OPTIONS_HERE');
expect(exitCode, 3);
});
test('missing dart file', () {
drive('test/data/NO_DART_FILE_HERE.dart');
expect(exitCode, 3);
});
test('part file', () {
drive('test/data/library_and_parts/part2.dart');
expect(exitCode, 3);
});
test('non-dangling part file', () {
Driver driver = new Driver();
driver.start([
'test/data/library_and_parts/lib.dart',
'test/data/library_and_parts/part1.dart',
]);
expect(exitCode, 0);
});
test('extra part file', () {
Driver driver = new Driver();
driver.start([
'test/data/library_and_parts/lib.dart',
'test/data/library_and_parts/part1.dart',
'test/data/library_and_parts/part2.dart',
]);
expect(exitCode, 3);
});
});
group('linter', () {
group('lints in options', () {
StringSink savedOutSink;
Driver driver;
setUp(() {
savedOutSink = outSink;
outSink = new StringBuffer();
driver = new Driver();
driver.start([
'--options',
'test/data/linter_project/.analysis_options',
'--lints',
'test/data/linter_project/test_file.dart'
]);
});
tearDown(() {
outSink = savedOutSink;
});
test('gets analysis options', () {
/// Lints should be enabled.
expect(driver.context.analysisOptions.lint, isTrue);
/// The .analysis_options file only specifies 'camel_case_types'.
var lintNames = getLints(driver.context).map((r) => r.name);
expect(lintNames, orderedEquals(['camel_case_types']));
});
test('generates lints', () {
expect(outSink.toString(),
contains('[lint] Name types using UpperCamelCase.'));
});
});
group('default lints', () {
StringSink savedOutSink;
Driver driver;
setUp(() {
savedOutSink = outSink;
outSink = new StringBuffer();
driver = new Driver();
driver.start([
'--lints',
'test/data/linter_project/test_file.dart',
'--options',
'test/data/linter_project/.analysis_options'
]);
});
tearDown(() {
outSink = savedOutSink;
});
test('gets default lints', () {
/// Lints should be enabled.
expect(driver.context.analysisOptions.lint, isTrue);
/// Default list should include camel_case_types.
var lintNames = getLints(driver.context).map((r) => r.name);
expect(lintNames, contains('camel_case_types'));
});
test('generates lints', () {
expect(outSink.toString(),
contains('[lint] Name types using UpperCamelCase.'));
});
});
group('no `--lints` flag (none in options)', () {
StringSink savedOutSink;
Driver driver;
setUp(() {
savedOutSink = outSink;
outSink = new StringBuffer();
driver = new Driver();
driver.start([
'test/data/no_lints_project/test_file.dart',
'--options',
'test/data/no_lints_project/.analysis_options'
]);
});
tearDown(() {
outSink = savedOutSink;
});
test('lints disabled', () {
expect(driver.context.analysisOptions.lint, isFalse);
});
test('no registered lints', () {
expect(getLints(driver.context), isEmpty);
});
test('no generated warnings', () {
expect(outSink.toString(), contains('No issues found'));
});
});
});
test('containsLintRuleEntry', () {
Map<String, YamlNode> options;
options = parseOptions('''
linter:
rules:
- foo
''');
expect(containsLintRuleEntry(options), true);
options = parseOptions('''
''');
expect(containsLintRuleEntry(options), false);
options = parseOptions('''
linter:
rules:
# - foo
''');
expect(containsLintRuleEntry(options), true);
options = parseOptions('''
linter:
# rules:
# - foo
''');
expect(containsLintRuleEntry(options), false);
});
group('options processing', () {
group('error filters', () {
StringSink savedOutSink;
Driver driver;
setUp(() {
savedOutSink = outSink;
outSink = new StringBuffer();
driver = new Driver();
driver.start([
'test/data/options_tests_project/test_file.dart',
'--options',
'test/data/options_tests_project/.analysis_options'
]);
});
tearDown(() {
outSink = savedOutSink;
});
test('filters', () {
var filters =
driver.context.getConfigurationData(CONFIGURED_ERROR_FILTERS);
expect(filters, hasLength(1));
var unused_error = new AnalysisError(
new TestSource(), 0, 1, HintCode.UNUSED_LOCAL_VARIABLE, [
['x']
]);
expect(filters.any((filter) => filter(unused_error)), isTrue);
});
test('language config', () {
expect(driver.context.analysisOptions.enableSuperMixins, isTrue);
});
});
});
group('in temp directory', () {
StringSink savedOutSink, savedErrorSink;
int savedExitCode;
Directory savedCurrentDirectory;
Directory tempDir;
setUp(() {
savedOutSink = outSink;
savedErrorSink = errorSink;
savedExitCode = exitCode;
outSink = new StringBuffer();
errorSink = new StringBuffer();
savedCurrentDirectory = Directory.current;
tempDir = Directory.systemTemp.createTempSync('analyzer_');
});
tearDown(() {
outSink = savedOutSink;
errorSink = savedErrorSink;
exitCode = savedExitCode;
Directory.current = savedCurrentDirectory;
tempDir.deleteSync(recursive: true);
});
test('packages folder', () {
Directory.current = tempDir;
new File(path.join(tempDir.path, 'test.dart')).writeAsStringSync('''
import 'package:foo/bar.dart';
main() {
baz();
}
''');
Directory packagesDir =
new Directory(path.join(tempDir.path, 'packages'));
packagesDir.createSync();
Directory fooDir = new Directory(path.join(packagesDir.path, 'foo'));
fooDir.createSync();
new File(path.join(fooDir.path, 'bar.dart')).writeAsStringSync('''
void baz() {}
''');
new Driver().start(['test.dart']);
expect(exitCode, 0);
});
test('no package resolution', () {
Directory.current = tempDir;
new File(path.join(tempDir.path, 'test.dart')).writeAsStringSync('''
import 'package:path/path.dart';
main() {}
''');
new Driver().start(['test.dart']);
expect(exitCode, 3);
String stdout = outSink.toString();
expect(stdout, contains('[error] Target of URI does not exist'));
expect(stdout, contains('1 error found.'));
expect(errorSink.toString(), '');
});
test('bad package root', () {
new Driver().start(['--package-root', 'does/not/exist', 'test.dart']);
String stdout = outSink.toString();
expect(exitCode, 3);
expect(
stdout,
contains(
'Package root directory (does/not/exist) does not exist.'));
});
});
});
group('Bootloader', () {
group('plugin processing', () {
StringSink savedErrorSink;
setUp(() {
savedErrorSink = errorSink;
errorSink = new StringBuffer();
});
tearDown(() {
errorSink = savedErrorSink;
});
test('bad format', () {
BootLoader loader = new BootLoader();
loader.createImage([
'--options',
'test/data/bad_plugin_options.yaml',
'test/data/test_file.dart'
]);
expect(
errorSink.toString(),
equals('Plugin configuration skipped: Unrecognized plugin config '
'format, expected `YamlMap`, got `YamlList` '
'(line 2, column 4)\n'));
});
test('plugin config', () {
BootLoader loader = new BootLoader();
Image image = loader.createImage([
'--options',
'test/data/plugin_options.yaml',
'test/data/test_file.dart'
]);
var plugins = image.config.plugins;
expect(plugins, hasLength(1));
expect(plugins.first.name, equals('my_plugin1'));
});
group('plugin validation', () {
test('requires class name', () {
expect(
validate(new PluginInfo(
name: 'test_plugin', libraryUri: 'my_package/foo.dart')),
isNotNull);
});
test('requires library URI', () {
expect(
validate(
new PluginInfo(name: 'test_plugin', className: 'MyPlugin')),
isNotNull);
});
test('check', () {
expect(
validate(new PluginInfo(
name: 'test_plugin',
className: 'MyPlugin',
libraryUri: 'my_package/foo.dart')),
isNull);
});
});
});
});
}
const emptyOptionsFile = 'test/data/empty_options.yaml';
/// Start a driver for the given [source], optionally providing additional
/// [args] and an [options] file path. The value of [options] defaults to
/// an empty options file to avoid unwanted configuration from an otherwise
/// discovered options file.
void drive(String source,
{String options: emptyOptionsFile,
List<String> args: const <String>[]}) =>
new Driver().start(['--options', options, source]..addAll(args));
Map<String, YamlNode> parseOptions(String src) =>
new AnalysisOptionsProvider().getOptionsFromString(src);
class TestPlugin extends Plugin {
TestProcessor processor;
TestPlugin(this.processor);
@override
String get uniqueIdentifier => 'test_plugin.core';
@override
void registerExtensionPoints(RegisterExtensionPoint register) {
// None
}
@override
void registerExtensions(RegisterExtension register) {
register(OPTIONS_PROCESSOR_EXTENSION_POINT_ID, processor);
}
}
class TestProcessor extends OptionsProcessor {
Map<String, YamlNode> options;
Exception exception;
@override
void onError(Exception exception) {
this.exception = exception;
}
@override
void optionsProcessed(
AnalysisContext context, Map<String, YamlNode> options) {
this.options = options;
}
}
class TestSource implements Source {
TestSource();
@override
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}

View file

@ -0,0 +1,55 @@
// 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.
@TestOn("vm")
library analyzer_cli.test.error;
import 'package:test/test.dart';
import 'utils.dart';
void main() {
group('error', () {
test("a valid Dart file doesn't throw any errors", () {
expect(errorsForFile('void main() => print("Hello, world!");'), isNull);
});
test("an empty Dart file doesn't throw any errors", () {
expect(errorsForFile(''), isNull);
});
test("an error on the first line", () {
expect(errorsForFile('void foo;\n'), equals(
"Error in test.dart: Variables cannot have a type of 'void'\n"));
});
test("an error on the last line", () {
expect(errorsForFile('\nvoid foo;'), equals(
"Error in test.dart: Variables cannot have a type of 'void'\n"));
});
test("an error in the middle", () {
expect(errorsForFile('\nvoid foo;\n'), equals(
"Error in test.dart: Variables cannot have a type of 'void'\n"));
});
var veryLongString = new List.filled(107, ' ').join('');
test("an error at the end of a very long line", () {
expect(errorsForFile('$veryLongString void foo;'), equals(
"Error in test.dart: Variables cannot have a type of 'void'\n"));
});
test("an error at the beginning of a very long line", () {
expect(errorsForFile('void foo; $veryLongString'), equals(
"Error in test.dart: Variables cannot have a type of 'void'\n"));
});
test("an error in the middle of a very long line", () {
expect(errorsForFile('$veryLongString void foo;$veryLongString'), equals(
"Error in test.dart: Variables cannot have a type of 'void'\n"));
});
});
}

View file

@ -0,0 +1,51 @@
// 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 analyzer_cli.test.mocks;
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer_cli/src/options.dart';
import 'package:typed_mock/typed_mock.dart';
class MockAnalysisError extends TypedMock implements AnalysisError {
@override
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class MockAnalysisErrorInfo extends TypedMock implements AnalysisErrorInfo {
@override
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class MockCommandLineOptions extends TypedMock implements CommandLineOptions {
@override
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class MockErrorCode extends TypedMock implements ErrorCode {
@override
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class MockErrorType extends TypedMock implements ErrorType {
@override
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class MockLineInfo extends TypedMock implements LineInfo {
@override
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class MockLineInfo_Location extends TypedMock implements LineInfo_Location {
@override
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class MockSource extends TypedMock implements Source {
@override
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}

View file

@ -0,0 +1,191 @@
// 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.
@TestOn("vm")
library analyzer_cli.test.options;
import 'package:analyzer_cli/src/options.dart';
import 'package:args/args.dart';
import 'package:test/test.dart';
main() {
group('CommandLineOptions', () {
group('parse', () {
test('defaults', () {
CommandLineOptions options =
CommandLineOptions.parse(['--dart-sdk', '.', 'foo.dart']);
expect(options, isNotNull);
expect(options.dartSdkPath, isNotNull);
expect(options.disableHints, isFalse);
expect(options.lints, isFalse);
expect(options.displayVersion, isFalse);
expect(options.enableStrictCallChecks, isFalse);
expect(options.enableSuperMixins, isFalse);
expect(options.enableTypeChecks, isFalse);
expect(options.hintsAreFatal, isFalse);
expect(options.ignoreUnrecognizedFlags, isFalse);
expect(options.log, isFalse);
expect(options.machineFormat, isFalse);
expect(options.packageRootPath, isNull);
expect(options.shouldBatch, isFalse);
expect(options.showPackageWarnings, isFalse);
expect(options.showSdkWarnings, isFalse);
expect(options.sourceFiles, equals(['foo.dart']));
expect(options.warningsAreFatal, isFalse);
expect(options.strongMode, isFalse);
});
test('batch', () {
CommandLineOptions options =
CommandLineOptions.parse(['--dart-sdk', '.', '--batch']);
expect(options.shouldBatch, isTrue);
});
test('defined variables', () {
CommandLineOptions options = CommandLineOptions
.parse(['--dart-sdk', '.', '-Dfoo=bar', 'foo.dart']);
expect(options.definedVariables['foo'], equals('bar'));
expect(options.definedVariables['bar'], isNull);
});
test('enable strict call checks', () {
CommandLineOptions options = CommandLineOptions.parse(
['--dart-sdk', '.', '--enable-strict-call-checks', 'foo.dart']);
expect(options.enableStrictCallChecks, isTrue);
});
test('enable super mixins', () {
CommandLineOptions options = CommandLineOptions
.parse(['--dart-sdk', '.', '--supermixin', 'foo.dart']);
expect(options.enableSuperMixins, isTrue);
});
test('enable type checks', () {
CommandLineOptions options = CommandLineOptions
.parse(['--dart-sdk', '.', '--enable_type_checks', 'foo.dart']);
expect(options.enableTypeChecks, isTrue);
});
test('hintsAreFatal', () {
CommandLineOptions options = CommandLineOptions
.parse(['--dart-sdk', '.', '--fatal-hints', 'foo.dart']);
expect(options.hintsAreFatal, isTrue);
});
test('log', () {
CommandLineOptions options =
CommandLineOptions.parse(['--dart-sdk', '.', '--log', 'foo.dart']);
expect(options.log, isTrue);
});
test('machine format', () {
CommandLineOptions options = CommandLineOptions
.parse(['--dart-sdk', '.', '--format=machine', 'foo.dart']);
expect(options.machineFormat, isTrue);
});
test('no-hints', () {
CommandLineOptions options = CommandLineOptions
.parse(['--dart-sdk', '.', '--no-hints', 'foo.dart']);
expect(options.disableHints, isTrue);
});
test('options', () {
CommandLineOptions options = CommandLineOptions.parse(
['--dart-sdk', '.', '--options', 'options.yaml', 'foo.dart']);
expect(options.analysisOptionsFile, equals('options.yaml'));
});
test('lints', () {
CommandLineOptions options = CommandLineOptions
.parse(['--dart-sdk', '.', '--lints', 'foo.dart']);
expect(options.lints, isTrue);
});
test('package root', () {
CommandLineOptions options = CommandLineOptions
.parse(['--dart-sdk', '.', '-p', 'bar', 'foo.dart']);
expect(options.packageRootPath, equals('bar'));
});
test('package warnings', () {
CommandLineOptions options = CommandLineOptions
.parse(['--dart-sdk', '.', '--package-warnings', 'foo.dart']);
expect(options.showPackageWarnings, isTrue);
});
test('sdk warnings', () {
CommandLineOptions options = CommandLineOptions
.parse(['--dart-sdk', '.', '--warnings', 'foo.dart']);
expect(options.showSdkWarnings, isTrue);
});
test('sourceFiles', () {
CommandLineOptions options = CommandLineOptions.parse(
['--dart-sdk', '.', '--log', 'foo.dart', 'foo2.dart', 'foo3.dart']);
expect(options.sourceFiles,
equals(['foo.dart', 'foo2.dart', 'foo3.dart']));
});
test('warningsAreFatal', () {
CommandLineOptions options = CommandLineOptions
.parse(['--dart-sdk', '.', '--fatal-warnings', 'foo.dart']);
expect(options.warningsAreFatal, isTrue);
});
test('notice unrecognized flags', () {
expect(
() => new CommandLineParser()
.parse(['--bar', '--baz', 'foo.dart'], {}),
throwsA(new isInstanceOf<FormatException>()));
});
test('ignore unrecognized flags', () {
CommandLineOptions options = CommandLineOptions.parse([
'--ignore-unrecognized-flags',
'--bar',
'--baz',
'--dart-sdk',
'.',
'foo.dart'
]);
expect(options, isNotNull);
expect(options.sourceFiles, equals(['foo.dart']));
});
test('ignore unrecognized options', () {
CommandLineParser parser =
new CommandLineParser(alwaysIgnoreUnrecognized: true);
parser.addOption('optionA');
parser.addFlag('flagA');
ArgResults argResults =
parser.parse(['--optionA=1', '--optionB=2', '--flagA'], {});
expect(argResults['optionA'], '1');
expect(argResults['flagA'], isTrue);
});
test('strong mode', () {
CommandLineOptions options = CommandLineOptions
.parse(['--strong', 'foo.dart']);
expect(options.strongMode, isTrue);
});
test("can't specify package and package-root", () {
var failureMessage;
CommandLineOptions.parse(
['--package-root', '.', '--packages', '.', 'foo.dart'],
(msg) => failureMessage = msg);
expect(failureMessage,
equals("Cannot specify both '--package-root' and '--packages."));
});
test("bad SDK dir", () {
var failureMessage;
CommandLineOptions.parse(
['--dart-sdk', '&&&&&', 'foo.dart'], (msg) => failureMessage = msg);
expect(failureMessage, equals('Invalid Dart SDK path: &&&&&'));
});
});
});
}

View file

@ -0,0 +1,94 @@
// 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.
@TestOn("vm")
library analyzer_cli.test.plugin_manager_test;
import 'package:analyzer/src/plugin/plugin_configuration.dart';
import 'package:analyzer_cli/src/plugin/plugin_manager.dart';
import 'package:test/test.dart';
main() {
group('plugin manager tests', () {
test('combine plugin info', () {
PluginInfo localInfo = new PluginInfo(name: 'my_plugin');
PluginInfo manifestInfo = new PluginInfo(
className: 'MyPlugin', libraryUri: 'my_plugin/my_plugin.dart');
PluginInfo merged = combine(localInfo, manifestInfo);
expect(merged.name, equals('my_plugin'));
expect(merged.className, equals('MyPlugin'));
expect(merged.libraryUri, equals('my_plugin/my_plugin.dart'));
});
test('find manifest', () {
const manifestSrc = '''
library_uri: 'my_plugin/my_plugin.dart'
''';
var packageMap = {'my_plugin': new Uri.file('my_plugin')};
PluginManager pm =
new PluginManager(packageMap, 'analyzer', (Uri uri) => manifestSrc);
PluginManifest manifest = pm.findManifest('my_plugin');
expect(manifest, isNotNull);
expect(manifest.plugin.libraryUri, equals('my_plugin/my_plugin.dart'));
});
final plugin1Uri = new Uri.file('my_plugin1');
final plugin2Uri = new Uri.file('my_plugin2');
final plugin3Uri = new Uri.file('my_plugin3');
const serverPluginManifest = '''
library_uri: 'my_plugin2/my_plugin2.dart'
contributes_to: analysis_server
''';
const analyzerPluginManifest = '''
library_uri: 'my_plugin3/my_plugin3.dart'
contributes_to: analyzer
''';
var packageMap = {
'my_plugin': plugin1Uri,
'my_plugin2': plugin2Uri,
'my_plugin3': plugin3Uri
};
var manifestReader = (Uri uri) {
if (uri == plugin2Uri) return serverPluginManifest;
if (uri == plugin3Uri) return analyzerPluginManifest;
return null;
};
test('get plugin details', () {
PluginManager pm =
new PluginManager(packageMap, 'analysis_server', manifestReader);
PluginInfo notFound = new PluginInfo(name: 'my_plugin1');
PluginInfo applicable = new PluginInfo(name: 'my_plugin2');
PluginInfo notApplicable = new PluginInfo(name: 'my_plugin3');
PluginConfig localConfig =
new PluginConfig([notFound, applicable, notApplicable]);
Iterable<PluginDetails> details = pm.getPluginDetails(localConfig);
expect(details, hasLength(3));
List<PluginDetails> plugins = sortByName(details);
expect(plugins[0].plugin.name, equals('my_plugin1'));
expect(plugins[0].status, equals(PluginStatus.NotFound));
expect(plugins[1].plugin.name, equals('my_plugin2'));
expect(
plugins[1].plugin.libraryUri, equals('my_plugin2/my_plugin2.dart'));
expect(plugins[1].status, equals(PluginStatus.Applicable));
expect(plugins[2].plugin.name, equals('my_plugin3'));
expect(plugins[2].status, equals(PluginStatus.NotApplicable));
});
});
}
List<PluginDetails> sortByName(Iterable<PluginDetails> details) =>
details.toList()
..sort((p1, p2) => p1.plugin.name.compareTo(p2.plugin.name));

View file

@ -0,0 +1,77 @@
// 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.
//@TestOn("vm")
library analyzer_cli.test.formatter;
//import 'package:analyzer/analyzer.dart';
//import 'package:analyzer_cli/src/error_formatter.dart';
//import 'package:test/test.dart' hide ErrorFormatter;
//import 'mocks.dart';
//TODO(pq): fix mocks and reimplement
main() {
// group('reporter', () {
// var out = new StringBuffer();
//
// tearDown(() => out.clear());
//
// // Options
// var options = new MockCommandLineOptions();
// when(options.disableHints).thenReturn(true);
// when(options.enableTypeChecks).thenReturn(true);
// when(options.machineFormat).thenReturn(false);
// when(options.hintsAreFatal).thenReturn(false);
//
// var reporter = new ErrorFormatter(out, options);
//
// test('error', () {
// var error = mockError(ErrorType.SYNTACTIC_ERROR, ErrorSeverity.ERROR);
// reporter.formatErrors([error]);
//
// expect(out.toString(),
// equals('''[error] MSG (/foo/bar/baz.dart, line 3, col 3)
//1 error found.
//'''));
// });
//
// test('hint', () {
// var error = mockError(ErrorType.HINT, ErrorSeverity.INFO);
// reporter.formatErrors([error]);
//
// expect(out.toString(),
// equals('''[hint] MSG (/foo/bar/baz.dart, line 3, col 3)
//1 hint found.
//'''));
// });
// });
}
//MockAnalysisErrorInfo mockError(ErrorType type, ErrorSeverity severity) {
// // ErrorInfo
// var info = new MockAnalysisErrorInfo();
// var error = new MockAnalysisError();
// var lineInfo = new MockLineInfo();
// var location = new MockLineInfo_Location();
// when(location.columnNumber).thenReturn(3);
// when(location.lineNumber).thenReturn(3);
// when(lineInfo.getLocation(any)).thenReturn(location);
// when(info.lineInfo).thenReturn(lineInfo);
//
// // Details
// var code = new MockErrorCode();
// when(code.type).thenReturn(type);
// when(code.errorSeverity).thenReturn(severity);
// when(code.name).thenReturn('mock_code');
// when(error.errorCode).thenReturn(code);
// when(error.message).thenReturn('MSG');
// var source = new MockSource();
// when(source.fullName).thenReturn('/foo/bar/baz.dart');
// when(error.source).thenReturn(source);
// when(info.errors).thenReturn([error]);
//
// return info;
//}

View file

@ -0,0 +1,55 @@
// 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.
@TestOn("vm")
/// Test that sdk extensions are properly detected in various scenarios.
library analyzer_cli.test.sdk_ext;
import 'dart:io';
import 'package:analyzer_cli/src/driver.dart' show Driver, errorSink, outSink;
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'utils.dart';
main() {
group('Sdk extensions', () {
StringSink savedOutSink, savedErrorSink;
int savedExitCode;
Directory savedCurrentDirectory;
setUp(() {
savedOutSink = outSink;
savedErrorSink = errorSink;
savedExitCode = exitCode;
outSink = new StringBuffer();
errorSink = new StringBuffer();
savedCurrentDirectory = Directory.current;
});
tearDown(() {
outSink = savedOutSink;
errorSink = savedErrorSink;
exitCode = savedExitCode;
Directory.current = savedCurrentDirectory;
});
test('--packages option supplied', () async {
var testDir = path.join(testDirectory, 'data', 'no_packages_file');
Directory.current = new Directory(testDir);
var packagesPath = path.join('..', 'packages_file', '.packages');
new Driver().start(['--packages', packagesPath, 'sdk_ext_user.dart']);
expect(exitCode, 0);
});
test('.packages file present', () async {
var testDir = path.join(testDirectory, 'data', 'packages_file');
Directory.current = new Directory(testDir);
new Driver().start(['sdk_ext_user.dart']);
expect(exitCode, 0);
});
});
}

View file

@ -0,0 +1,53 @@
// 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.
@TestOn("vm")
library analyzer_cli.test.strong_mode;
import 'dart:io';
import 'package:analyzer_cli/src/driver.dart' show Driver, errorSink, outSink;
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'driver_test.dart';
import 'utils.dart';
/// End-to-end test for --strong checking.
///
/// Most strong mode tests are in Analyzer, but this verifies the option is
/// working and producing extra errors as expected.
///
/// Generally we don't want a lot of cases here as it requires spinning up a
/// full analysis context.
void main() {
group('--strong', () {
StringSink savedOutSink, savedErrorSink;
int savedExitCode;
setUp(() {
savedOutSink = outSink;
savedErrorSink = errorSink;
savedExitCode = exitCode;
outSink = new StringBuffer();
errorSink = new StringBuffer();
});
tearDown(() {
outSink = savedOutSink;
errorSink = savedErrorSink;
exitCode = savedExitCode;
});
test('produces stricter errors', () async {
var testPath = path.join(testDirectory, 'data/strong_example.dart');
new Driver().start(['--options', emptyOptionsFile, '--strong', testPath]);
expect(exitCode, 3);
var stdout = outSink.toString();
expect(stdout, contains('[error] Invalid override'));
expect(stdout, contains('[error] Type check failed'));
expect(stdout, contains('2 errors found.'));
expect(errorSink.toString(), '');
});
});
}

View file

@ -0,0 +1,67 @@
// 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.
@TestOn("vm")
library analyzer_cli.test.super_mixin;
import 'dart:io';
import 'package:analyzer_cli/src/driver.dart' show Driver, errorSink, outSink;
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'utils.dart';
/// End-to-end test for --supermixins.
///
/// Most super mixin tests are in Analyzer, but this verifies the option is
/// working and producing extra errors as expected.
///
/// Generally we don't want a lot of cases here as it requires spinning up a
/// full analysis context.
void main() {
group('--supermixins', () {
StringSink savedOutSink, savedErrorSink;
int savedExitCode;
setUp(() {
savedOutSink = outSink;
savedErrorSink = errorSink;
savedExitCode = exitCode;
outSink = new StringBuffer();
errorSink = new StringBuffer();
});
tearDown(() {
outSink = savedOutSink;
errorSink = savedErrorSink;
exitCode = savedExitCode;
});
test('produces errors when option absent', () async {
var testPath = path.join(testDirectory, 'data/super_mixin_example.dart');
new Driver().start([testPath]);
expect(exitCode, 3);
var stdout = outSink.toString();
expect(
stdout,
contains(
"[error] The class 'C' cannot be used as a mixin because it extends a class other than Object"));
expect(
stdout,
contains(
"[error] The class 'C' cannot be used as a mixin because it references 'super'"));
expect(stdout, contains('2 errors found.'));
expect(errorSink.toString(), '');
});
test('produces no errors when option present', () async {
var testPath = path.join(testDirectory, 'data/super_mixin_example.dart');
new Driver().start(['--supermixin', testPath]);
expect(exitCode, 0);
var stdout = outSink.toString();
expect(stdout, contains('No issues found'));
});
});
}

View file

@ -0,0 +1,56 @@
// 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 analyzer_cli.test.utils;
import 'dart:io';
import 'dart:mirrors';
import 'package:analyzer/analyzer.dart';
import 'package:path/path.dart' as pathos;
/// Returns the string representation of the [AnalyzerErrorGroup] thrown when
/// parsing [contents] as a Dart file. If [contents] doesn't throw any errors,
/// this will return null.
///
/// This replaces the filename in the error string with its basename, since the
/// full path will vary from machine to machine. It also replaces the exception
/// message with "..." to decouple these tests from the specific exception
/// messages.
String errorsForFile(String contents) {
return withTempDir((temp) {
var path = pathos.join(temp, 'test.dart');
new File(path).writeAsStringSync(contents);
try {
parseDartFile(path);
} on AnalyzerErrorGroup catch (e) {
return e.toString().replaceAllMapped(
new RegExp(r"^(Error on line \d+ of )((?:[A-Z]+:)?[^:]+): .*$",
multiLine: true),
(match) => match[1] + pathos.basename(match[2]) + ': ...');
}
return null;
});
}
/// Creates a temporary directory and passes its path to [fn]. Once [fn]
/// completes, the temporary directory and all its contents will be deleted.
///
/// Returns the return value of [fn].
dynamic withTempDir(fn(String path)) {
var tempDir = Directory.systemTemp.createTempSync('analyzer_').path;
try {
return fn(tempDir);
} finally {
new Directory(tempDir).deleteSync(recursive: true);
}
}
/// Gets the test directory in a way that works with
/// package:test and package:unittest.
/// See <https://github.com/dart-lang/test/issues/110> for more info.
final String testDirectory = pathos.dirname(
pathos.fromUri((reflectClass(_TestUtils).owner as LibraryMirror).uri));
class _TestUtils {}

View file

@ -43,7 +43,7 @@ fi
DART_ROOT="$(cd "${SDK_DIR}/.." ; pwd -P)"
ANALYZER="$DART_ROOT/third_party/pkg/analyzer_cli/bin/analyzer.dart"
ANALYZER="$DART_ROOT/pkg/analyzer_cli/bin/analyzer.dart"
if [[ `uname` == 'Darwin' ]];
then

View file

@ -38,7 +38,7 @@ for %%i in ("%SDK_DIR%\..\") do set DART_ROOT=%%~fi
rem Remove trailing backslash if there is one
if %DART_ROOT:~-1%==\ set DART_ROOT=%DART_ROOT:~0,-1%
set ANALYZER=%DART_ROOT%\third_party\pkg\analyzer_cli\bin\analyzer.dart
set ANALYZER=%DART_ROOT%\pkg\analyzer_cli\bin\analyzer.dart
rem DART_CONFIGURATION defaults to ReleaseIA32
if "%DART_CONFIGURATION%"=="" set DART_CONFIGURATION=ReleaseIA32

View file

@ -29,8 +29,9 @@ import sys
import utils
# Useful messages when we find orphaned checkouts.
old_directories = {'package_config':
'Please remove third_party/pkg/package_config.'}
old_directories = {
'package_config': 'Please remove third_party/pkg/package_config.',
'analyzer_cli': 'Please remove third_party/pkg/analyzer_cli.'}
def get_options():
result = optparse.OptionParser()

View file

@ -18,7 +18,7 @@
'<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)dart<(EXECUTABLE_SUFFIX)',
'../../sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart',
'<(SHARED_INTERMEDIATE_DIR)/packages.stamp',
'<!@(["python", "../../tools/list_files.py", "\\.dart$", "../../third_party/pkg/analyzer_cli"])',
'<!@(["python", "../../tools/list_files.py", "\\.dart$", "../../pkg/analyzer_cli"])',
],
'outputs': [
'<(SHARED_INTERMEDIATE_DIR)/dartanalyzer.dart.snapshot',
@ -27,7 +27,7 @@
'<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)dart<(EXECUTABLE_SUFFIX)',
'--snapshot=<(SHARED_INTERMEDIATE_DIR)/dartanalyzer.dart.snapshot',
'--package-root=<(PRODUCT_DIR)/packages/',
'../../third_party/pkg/analyzer_cli/bin/analyzer.dart',
'../../pkg/analyzer_cli/bin/analyzer.dart',
],
},
],