blob: ed549baab1d1dc602155297f23d7934bbd994777 [file] [log] [blame]
// 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();
}
}