| // Copyright (c) 2016, 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/dart/analysis/results.dart'; |
| import 'package:analyzer/dart/analysis/session.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/error/error.dart'; |
| |
| import '../asset/id.dart'; |
| import '../builder/build_step.dart'; |
| |
| /// Standard interface for resolving Dart source code as part of a build. |
| abstract class Resolver { |
| /// Returns whether [assetId] represents an Dart library file. |
| /// |
| /// This will be `false` in the case where the file is not Dart source code, |
| /// or is a `part of` file (not a standalone Dart library). |
| Future<bool> isLibrary(AssetId assetId); |
| |
| /// All libraries resolved by this resolver. |
| /// |
| /// This includes the following libraries: |
| /// - The primary input of this resolver (in other words, the |
| /// [BuildStep.inputId] of a build step). |
| /// - Libraries resolved with a direct [libraryFor] call. |
| /// - Every public `dart:` library part of the SDK. |
| /// - All libraries recursively accessible from the mentioned sources, for |
| /// instance because due to imports or exports. |
| Stream<LibraryElement> get libraries; |
| |
| /// Returns the parsed [AstNode] for [Element]. |
| /// |
| /// This should always be preferred over using the [AnalysisSession] |
| /// directly, because it avoids [InconsistentAnalysisException] issues. |
| /// |
| /// If [resolve] is `true` then you will get a resolved ast node, otherwise |
| /// it will only be a parsed ast node. |
| /// |
| /// Returns `null` if the ast node can not be found. This can happen if an |
| /// element is coming from a summary, or is unavailable for some other |
| /// reason. |
| Future<AstNode?> astNodeFor(Element element, {bool resolve = false}); |
| |
| /// Returns a parsed AST structor representing the file defined in [assetId]. |
| /// |
| /// * If the [assetId] has syntax errors, and [allowSyntaxErrors] is set to |
| /// `false` (the default), throws a [SyntaxErrorInAssetException]. |
| /// |
| /// This is a much cheaper api compared to [libraryFor], because it will only |
| /// parse a single file and does not give you a resolved element model. |
| Future<CompilationUnit> compilationUnitFor(AssetId assetId, |
| {bool allowSyntaxErrors = false}); |
| |
| /// Returns a resolved library representing the file defined in [assetId]. |
| /// |
| /// * Throws [NonLibraryAssetException] if [assetId] is not a Dart library. |
| /// * If the [assetId] has syntax errors, and [allowSyntaxErrors] is set to |
| /// `false` (the default), throws a [SyntaxErrorInAssetException]. |
| Future<LibraryElement> libraryFor(AssetId assetId, |
| {bool allowSyntaxErrors = false}); |
| |
| /// Returns the first resolved library identified by [libraryName]. |
| /// |
| /// A library is resolved if it's recursively accessible from the entry point |
| /// or subsequent calls to [libraryFor]. In other words, this searches for |
| /// libraries in [libraries]. |
| /// If no library can be found, returns `null`. |
| /// |
| /// **NOTE**: In general, its recommended to use [libraryFor] with an absolute |
| /// asset id instead of a named identifier that has the possibility of not |
| /// being unique. |
| Future<LibraryElement?> findLibraryByName(String libraryName); |
| |
| /// Returns the [AssetId] of the Dart library or part declaring [element]. |
| /// |
| /// If [element] is defined in the SDK or in a summary throws |
| /// `UnresolvableAssetException`, although a non-throwing return here does not |
| /// guarantee that the asset is readable. |
| /// |
| /// The returned asset is not necessarily the asset that should be imported to |
| /// use the element, it may be a part file instead of the library. |
| Future<AssetId> assetIdForElement(Element element); |
| } |
| |
| /// A resolver that should be manually released at the end of a build step. |
| abstract class ReleasableResolver implements Resolver { |
| /// Release this resolver so it can be updated by following build steps. |
| void release(); |
| } |
| |
| /// A factory that returns a resolver for a given [BuildStep]. |
| abstract class Resolvers { |
| const Resolvers(); |
| |
| Future<ReleasableResolver> get(BuildStep buildStep); |
| |
| /// Reset the state of any caches within [Resolver] instances produced by |
| /// this [Resolvers]. |
| /// |
| /// In between calls to [reset] no Assets should change, so every call to |
| /// `BuildStep.readAsString` for a given AssetId should return identical |
| /// contents. Any time an Asset's contents may change [reset] must be called. |
| void reset() {} |
| } |
| |
| /// Thrown when attempting to read a non-Dart library in a [Resolver]. |
| class NonLibraryAssetException implements Exception { |
| final AssetId assetId; |
| |
| const NonLibraryAssetException(this.assetId); |
| |
| @override |
| String toString() => 'Asset [$assetId] is not a Dart library. ' |
| 'It may be a part file or a file without Dart source code.'; |
| } |
| |
| /// Exception thrown by a resolver when attempting to resolve a Dart library |
| /// with syntax errors. |
| /// |
| /// Builders are not expected to catch this exception unless they have special |
| /// behavior for inputs with syntax errors. This exception has a descriptive |
| /// [toString] implementation that the build system will show to users. |
| class SyntaxErrorInAssetException implements Exception { |
| static const _maxErrorsInToString = 3; |
| |
| /// The syntactically invalid [AssetId] that couldn't be resolved. |
| final AssetId assetId; |
| |
| /// A list analysis error results for files related to the [assetId]. |
| /// |
| /// In addition to the asset itself, the resolver also considers syntax errors |
| /// in part files. |
| final List<AnalysisResultWithErrors> filesWithErrors; |
| |
| SyntaxErrorInAssetException(this.assetId, this.filesWithErrors) |
| : assert(filesWithErrors.isNotEmpty); |
| |
| /// The errors reported by the parser when trying to resolve the [assetId]. |
| /// |
| /// This only contains syntax errors since most semantic errors are expected |
| /// during a build (e.g. due to missing part files that haven't been generated |
| /// yet). |
| Iterable<AnalysisError> get syntaxErrors { |
| return filesWithErrors |
| .expand((result) => result.errors) |
| .where(_isSyntaxError); |
| } |
| |
| /// A map from [syntaxErrors] to per-file results |
| Map<AnalysisError, AnalysisResultWithErrors> get errorToResult { |
| return { |
| for (final file in filesWithErrors) |
| for (final error in file.errors) |
| if (_isSyntaxError(error)) error: file, |
| }; |
| } |
| |
| bool _isSyntaxError(AnalysisError error) { |
| return error.errorCode.type == ErrorType.SYNTACTIC_ERROR; |
| } |
| |
| @override |
| String toString() { |
| final buffer = StringBuffer() |
| ..writeln('This builder requires Dart inputs without syntax errors.') |
| ..writeln('However, ${assetId.uri} (or an existing part) contains the ' |
| 'following errors.'); |
| |
| // Avoid generating too much output for syntax errors. The user likely has |
| // an editor that shows them anyway. |
| final entries = errorToResult.entries.toList(); |
| for (final errorAndResult in entries.take(_maxErrorsInToString)) { |
| final error = errorAndResult.key; |
| // Use a short name: We present the full context by including the asset id |
| // and this is easier to skim through |
| final sourceName = error.source.shortName; |
| |
| final lineInfo = errorAndResult.value.lineInfo; |
| final position = lineInfo.getLocation(error.offset); |
| |
| // Output messages like "foo.dart:3:4: Expected a semicolon here." |
| buffer.writeln( |
| '$sourceName:${position.lineNumber}:${position.columnNumber}: ' |
| '${error.message}'); |
| } |
| |
| final additionalErrors = entries.length - _maxErrorsInToString; |
| if (additionalErrors > 0) { |
| buffer.writeln('And $additionalErrors more...'); |
| } |
| |
| buffer.writeln('\nTry fixing the errors and re-running the build.'); |
| |
| return buffer.toString(); |
| } |
| } |