blob: b84b2273655ddc0fac6682a71e3eb9e2e8bdb92b [file] [log] [blame]
// Copyright (c) 2018, 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/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/generated/engine.dart';
/// This returns the offset used for finding the corresponding AST node.
///
/// If the fragment is named, the [Fragment.nameOffset2] is used. If the
/// fragment is a a [ConstructorFragment] for an unnamed constructor, the
/// [ConstructorFragment.typeNameOffset] is used.
int? _getFragmentNameOffset(Fragment fragment) {
var nameOffset = fragment.nameOffset2;
if (nameOffset == null) {
if (fragment is ConstructorFragment) {
nameOffset = fragment.typeNameOffset;
}
}
return nameOffset;
}
abstract class AnalysisResultImpl implements AnalysisResult {
@override
final AnalysisSession session;
AnalysisResultImpl({
required this.session,
});
}
/// A visitor which locates the [AstNode] which declares [element].
class DeclarationByElementLocator extends UnifyingAstVisitor<void> {
// TODO(srawlins): This visitor could be further optimized by special casing each static
// type of [element]. For example, for library-level elements (classes etc),
// we can iterate over the compilation unit's declarations.
final Fragment fragment;
final int _nameOffset;
AstNode? result;
DeclarationByElementLocator(this.fragment, this._nameOffset);
@override
void visitNode(AstNode node) {
if (result != null) return;
if (node.endToken.end < _nameOffset || node.offset > _nameOffset) {
return;
}
if (fragment is InterfaceFragment) {
if (node is ClassDeclaration) {
if (_hasOffset2(node.name)) {
result = node;
}
} else if (node is ClassTypeAlias) {
if (_hasOffset2(node.name)) {
result = node;
}
} else if (node is EnumDeclaration) {
if (_hasOffset2(node.name)) {
result = node;
}
} else if (node is MixinDeclaration) {
if (_hasOffset2(node.name)) {
result = node;
}
} else if (node is ExtensionTypeDeclaration) {
if (_hasOffset2(node.name)) {
result = node;
}
}
} else if (fragment is ConstructorFragment) {
if (node is ConstructorDeclaration) {
if (node.name != null) {
if (_hasOffset2(node.name)) {
result = node;
}
} else {
if (_hasOffset(node.returnType)) {
result = node;
}
}
}
} else if (fragment is ExtensionFragment) {
if (node is ExtensionDeclaration) {
if (_hasOffset2(node.name)) {
result = node;
}
}
} else if (fragment is FieldFragment) {
if (node is EnumConstantDeclaration) {
if (_hasOffset2(node.name)) {
result = node;
}
} else if (node is VariableDeclaration) {
if (_hasOffset2(node.name)) {
result = node;
}
}
} else if (fragment is TopLevelFunctionFragment) {
if (node is FunctionDeclaration && _hasOffset2(node.name)) {
result = node;
}
} else if (fragment is LocalFunctionFragment) {
if (node is FunctionDeclaration && _hasOffset2(node.name)) {
result = node;
}
} else if (fragment is LocalVariableFragment) {
if (node is VariableDeclaration && _hasOffset2(node.name)) {
result = node;
}
} else if (fragment is MethodFragment) {
if (node is MethodDeclaration && _hasOffset2(node.name)) {
result = node;
}
} else if (fragment is FormalParameterFragment) {
if (node is FormalParameter && _hasOffset2(node.name)) {
result = node;
}
} else if (fragment is PropertyAccessorFragment) {
if (node is FunctionDeclaration) {
if (_hasOffset2(node.name)) {
result = node;
}
} else if (node is MethodDeclaration) {
if (_hasOffset2(node.name)) {
result = node;
}
}
} else if (fragment is TopLevelVariableFragment) {
if (node is VariableDeclaration && _hasOffset2(node.name)) {
result = node;
}
} else if (fragment is TypeAliasFragment) {
if (node is GenericTypeAlias) {
if (_hasOffset2(node.name)) {
result = node;
}
}
}
if (result == null) {
node.visitChildren(this);
}
}
bool _hasOffset(AstNode? node) {
return node?.offset == _nameOffset;
}
bool _hasOffset2(Token? token) {
return token?.offset == _nameOffset;
}
}
class ElementDeclarationResultImpl
implements
// ignore:deprecated_member_use_from_same_package
ElementDeclarationResult,
FragmentDeclarationResult {
@override
final Fragment fragment;
@override
final AstNode node;
@override
final ParsedUnitResult? parsedUnit;
@override
final ResolvedUnitResult? resolvedUnit;
ElementDeclarationResultImpl(
this.fragment, this.node, this.parsedUnit, this.resolvedUnit);
}
class ErrorsResultImpl implements ErrorsResult {
@override
final List<AnalysisError> errors;
@override
final bool isLibrary;
@override
final bool isPart;
@override
final LineInfo lineInfo;
@override
final AnalysisSession session;
@override
final Uri uri;
@override
File file;
@override
final String content;
@override
final AnalysisOptions analysisOptions;
ErrorsResultImpl({
required this.session,
required this.file,
required this.content,
required this.uri,
required this.lineInfo,
required this.isLibrary,
required this.isPart,
required this.errors,
required this.analysisOptions,
});
@override
String get path => file.path;
}
class FileResultImpl extends AnalysisResultImpl implements FileResult {
final FileState fileState;
@override
final String content;
@override
final LineInfo lineInfo;
@override
final bool isLibrary;
@override
final bool isPart;
FileResultImpl({
required super.session,
required this.fileState,
}) : content = fileState.content,
lineInfo = fileState.lineInfo,
isLibrary = fileState.kind is LibraryFileKind,
isPart = fileState.kind is PartFileKind;
@override
AnalysisOptions get analysisOptions => fileState.analysisOptions;
@override
File get file => fileState.resource;
@override
String get path => fileState.path;
@override
Uri get uri => fileState.uri;
}
class LibraryElementResultImpl implements LibraryElementResult {
@override
final LibraryElementImpl element2;
LibraryElementResultImpl(this.element2);
}
class ParsedLibraryResultImpl extends AnalysisResultImpl
implements ParsedLibraryResult {
@override
final List<ParsedUnitResult> units;
ParsedLibraryResultImpl({
required super.session,
required this.units,
});
@Deprecated('Use getFragmentDeclaration() instead')
@override
ElementDeclarationResultImpl? getElementDeclaration2(Fragment fragment) {
return getFragmentDeclaration(fragment);
}
@override
ElementDeclarationResultImpl? getFragmentDeclaration(Fragment fragment) {
var nameOffset = _getFragmentNameOffset(fragment);
if (fragment is LibraryFragment || nameOffset == null) {
return null;
}
var elementPath = fragment.libraryFragment!.source.fullName;
var unitResult = units.firstWhere(
(r) => r.path == elementPath,
orElse: () {
var elementStr = fragment.element.displayName;
throw ArgumentError('Element (${fragment.runtimeType}) $elementStr is '
'not defined in this library.');
},
);
var locator = DeclarationByElementLocator(fragment, nameOffset);
unitResult.unit.accept(locator);
var declaration = locator.result;
if (declaration == null) {
return null;
}
return ElementDeclarationResultImpl(
fragment, declaration, unitResult, null);
}
}
class ParsedUnitResultImpl extends FileResultImpl implements ParsedUnitResult {
@override
final CompilationUnit unit;
@override
final List<AnalysisError> errors;
ParsedUnitResultImpl({
required super.session,
required super.fileState,
required this.unit,
required this.errors,
});
}
class ParseStringResultImpl implements ParseStringResult {
@override
final String content;
@override
final List<AnalysisError> errors;
@override
final CompilationUnit unit;
ParseStringResultImpl(this.content, this.unit, this.errors);
@override
LineInfo get lineInfo => unit.lineInfo;
}
class ResolvedForCompletionResultImpl {
final AnalysisSession analysisSession;
final FileState fileState;
final String path;
final Uri uri;
final bool exists;
final String content;
final LineInfo lineInfo;
/// The full parsed unit.
final CompilationUnit parsedUnit;
/// The full element for the unit.
final LibraryFragment unitElement;
/// Nodes from [parsedUnit] that were resolved to provide enough context
/// to perform completion. How much is enough depends on the location
/// where resolution for completion was requested, and our knowledge
/// how completion contributors work and what information they expect.
///
/// This is usually a small subset of the whole unit - a method, a field.
/// It could be even empty if the location does not provide any context
/// information for any completion contributor, e.g. a type annotation.
/// But it could be the whole unit as well, if the location is not something
/// we have an optimization for.
///
/// If this list is not empty, then the last node contains the requested
/// offset. Other nodes are provided mostly FYI.
final List<AstNode> resolvedNodes;
ResolvedForCompletionResultImpl({
required this.analysisSession,
required this.fileState,
required this.path,
required this.uri,
required this.exists,
required this.content,
required this.lineInfo,
required this.parsedUnit,
required this.unitElement,
required this.resolvedNodes,
});
}
class ResolvedLibraryResultImpl extends AnalysisResultImpl
implements ResolvedLibraryResult {
@override
final LibraryElementImpl element2;
@override
final List<ResolvedUnitResult> units;
ResolvedLibraryResultImpl({
required super.session,
required this.element2,
required this.units,
});
@override
TypeProviderImpl get typeProvider => element2.typeProvider;
@Deprecated('Use getFragmentDeclaration() instead')
@override
ElementDeclarationResultImpl? getElementDeclaration2(Fragment fragment) {
return getFragmentDeclaration(fragment);
}
@override
ElementDeclarationResultImpl? getFragmentDeclaration(Fragment fragment) {
var nameOffset = _getFragmentNameOffset(fragment);
if (fragment is LibraryFragment || nameOffset == null) {
return null;
}
var elementPath = fragment.libraryFragment!.source.fullName;
var unitResult = units.firstWhere(
(r) => r.path == elementPath,
orElse: () {
var elementStr = fragment.element.displayName;
throw ArgumentError('Element (${fragment.runtimeType}) $elementStr is '
'not defined in this library.');
},
);
var locator = DeclarationByElementLocator(fragment, nameOffset);
unitResult.unit.accept(locator);
var declaration = locator.result;
if (declaration == null) {
return null;
}
return ElementDeclarationResultImpl(
fragment, declaration, null, unitResult);
}
@override
ResolvedUnitResult? unitWithPath(String path) {
for (var unit in units) {
if (unit.path == path) {
return unit;
}
}
return null;
}
}
class ResolvedUnitResultImpl extends FileResultImpl
implements ResolvedUnitResult {
@override
final CompilationUnitImpl unit;
@override
final List<AnalysisError> errors;
ResolvedUnitResultImpl({
required super.session,
required super.fileState,
required this.unit,
required this.errors,
});
@override
bool get exists => fileState.exists;
@override
LibraryElementImpl get libraryElement2 {
return libraryFragment.element;
}
@override
CompilationUnitElementImpl get libraryFragment => unit.declaredFragment!;
@override
TypeProviderImpl get typeProvider => libraryElement2.typeProvider;
@override
TypeSystemImpl get typeSystem => libraryElement2.typeSystem;
}
class UnitElementResultImpl extends FileResultImpl
implements UnitElementResult {
@override
final CompilationUnitElementImpl fragment;
UnitElementResultImpl({
required super.session,
required super.fileState,
required this.fragment,
});
}