| // Copyright (c) 2013, 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. |
| |
| /// A library for compiling Dart code and manipulating analyzer parse trees. |
| import 'dart:async'; |
| import 'dart:io'; |
| |
| import 'package:analyzer/dart/analysis/analysis_context.dart'; |
| import 'package:analyzer/dart/analysis/context_builder.dart'; |
| import 'package:analyzer/dart/analysis/context_locator.dart'; |
| import 'package:analyzer/dart/analysis/session.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:path/path.dart' as p; |
| |
| import 'exceptions.dart'; |
| import 'io.dart'; |
| import 'log.dart' as log; |
| import 'utils.dart'; |
| |
| /// Returns whether [dart] looks like an entrypoint file. |
| bool isEntrypoint(CompilationUnit dart) { |
| // Allow two or fewer arguments so that entrypoints intended for use with |
| // [spawnUri] get counted. |
| // |
| // TODO(nweiz): this misses the case where a Dart file doesn't contain main(), |
| // but it parts in another file that does. |
| return dart.declarations.any((node) { |
| return node is FunctionDeclaration && |
| node.name.name == "main" && |
| node.functionExpression.parameters.parameters.length <= 2; |
| }); |
| } |
| |
| /// Snapshots the Dart executable at [executableUrl] to a snapshot at |
| /// [snapshotPath]. |
| /// |
| /// If [packagesFile] is passed, it's used to resolve `package:` URIs in the |
| /// executable. Otherwise, a `packages/` directory or a package spec is inferred |
| /// from the executable's location. |
| /// |
| /// If [name] is passed, it is used to describe the executable in logs and error |
| /// messages. |
| /// |
| /// When running in Dart 2 mode, this automatically creates a Dart 2-compatible |
| /// snapshot as well at `$snapshotPath.dart2`. |
| Future snapshot(Uri executableUrl, String snapshotPath, |
| {Uri packagesFile, String name}) async { |
| name = log.bold(name ?? executableUrl.toString()); |
| |
| var dart1Args = [ |
| '--no-preview-dart-2', |
| '--snapshot=$snapshotPath', |
| executableUrl.toString() |
| ]; |
| |
| var dart2Path = '$snapshotPath.dart2'; |
| var dart2Args = |
| isDart2 ? ['--snapshot=$dart2Path', executableUrl.toString()] : null; |
| |
| if (packagesFile != null) { |
| dart1Args.insert(0, "--packages=$packagesFile"); |
| |
| // Resolve [packagesFile] in case it's relative to work around sdk#33177. |
| dart2Args?.insert(0, "--packages=${Uri.base.resolveUri(packagesFile)}"); |
| } |
| |
| var processes = [runProcess(Platform.executable, dart1Args)]; |
| if (isDart2) processes.add(runProcess(Platform.executable, dart2Args)); |
| var results = await Future.wait(processes); |
| |
| var failure = |
| results.firstWhere((result) => !result.success, orElse: () => null); |
| if (failure == null) { |
| log.message("Precompiled $name."); |
| } else { |
| // Don't leave partial results. |
| deleteEntry(snapshotPath); |
| deleteEntry(dart2Path); |
| |
| throw new ApplicationException(log.yellow("Failed to precompile $name:\n") + |
| failure.stderr.join('\n')); |
| } |
| } |
| |
| class AnalysisContextManager { |
| /// The map from a context root directory to to the context. |
| final Map<String, AnalysisContext> _contexts = {}; |
| |
| /// Ensure that there are analysis contexts for the directory with the |
| /// given [path]. If any previously added root covers the [path], keep |
| /// the previously created analysis context. |
| /// |
| /// This method does not discover analysis roots "up", it only looks down |
| /// the given [path]. It is expected that the client knows analysis roots |
| /// in advance. Pub does know, it is the packages it works with. |
| void createContextsForDirectory(String path) { |
| _throwIfNotAbsolutePath(path); |
| |
| // We add all contexts below the given directory. |
| // So, children contexts must also have been added. |
| if (_contexts.containsKey(path)) { |
| return; |
| } |
| |
| // Add new contexts for the given path. |
| var contextLocator = new ContextLocator(); |
| var roots = contextLocator.locateRoots(includedPaths: [path]); |
| for (var root in roots) { |
| String contextRootPath = root.root.path; |
| |
| // If there is already a context for this context root path, keep it. |
| if (_contexts.containsKey(contextRootPath)) { |
| continue; |
| } |
| |
| var contextBuilder = new ContextBuilder(); |
| var context = contextBuilder.createContext(contextRoot: root); |
| _contexts[contextRootPath] = context; |
| } |
| } |
| |
| /// Parse the file with the given [path] into AST. |
| /// |
| /// One of the containing directories must be used to create analysis |
| /// contexts using [createContextsForDirectory]. Throws [StateError] if |
| /// this has not been done. |
| /// |
| /// Throws [AnalyzerErrorGroup] is the file has parsing errors. |
| CompilationUnit parse(String path) { |
| var parseResult = _getExistingSession(path).getParsedAstSync(path); |
| if (parseResult.errors.isNotEmpty) { |
| throw new AnalyzerErrorGroup(parseResult.errors); |
| } |
| return parseResult.unit; |
| } |
| |
| /// Return import and export directives in the file with the given [path]. |
| /// |
| /// One of the containing directories must be used to create analysis |
| /// contexts using [createContextsForDirectory]. Throws [StateError] if |
| /// this has not been done. |
| /// |
| /// Throws [AnalyzerErrorGroup] is the file has parsing errors. |
| List<UriBasedDirective> parseImportsAndExports(String path) { |
| var unit = parse(path); |
| var uriDirectives = <UriBasedDirective>[]; |
| for (var directive in unit.directives) { |
| if (directive is UriBasedDirective) { |
| uriDirectives.add(directive); |
| } |
| } |
| return uriDirectives; |
| } |
| |
| AnalysisSession _getExistingSession(String path) { |
| _throwIfNotAbsolutePath(path); |
| |
| for (var context in _contexts.values) { |
| if (context.contextRoot.isAnalyzed(path)) { |
| return context.currentSession; |
| } |
| } |
| |
| throw new StateError('Unable to find the context to $path'); |
| } |
| |
| /// The driver supports only absolute paths, this method is used to validate |
| /// any input paths to prevent errors later. |
| void _throwIfNotAbsolutePath(String path) { |
| if (!p.isAbsolute(path)) { |
| throw new ArgumentError('Only absolute paths are supported: $path'); |
| } |
| } |
| } |
| |
| /// An error class that contains multiple [AnalysisError]s. |
| class AnalyzerErrorGroup implements Exception { |
| final List<AnalysisError> errors; |
| |
| AnalyzerErrorGroup(this.errors); |
| |
| String get message => toString(); |
| |
| @override |
| String toString() => errors.join("\n"); |
| } |