blob: 71cf0f08791d3e21741fc1328aa32af7e7ee1a4a [file] [log] [blame]
// Copyright (c) 2019, 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/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
/// AST visitor that collects names that are referenced, but the target element
/// is not defined in the syntactic scope of the reference. Resolution of
/// such names will change when the import scope of the library changes.
class NotSyntacticScopeReferencedNamesCollector
extends GeneralizingAstVisitor<void> {
/// The library that contains resolved AST(s) being visited.
final LibraryElement enclosingLibraryElement;
/// The names that we need to check.
final Set<String> interestingNames;
/// All the referenced unqualified names.
final Set<String> referencedNames = Set<String>();
/// The subset of [interestingNames] that are resolved to a top-level
/// element that is not in the syntactic scope of the reference, and the
/// library [Uri] is the value in the mapping.
final Map<String, Uri> importedNames = {};
/// The subset of [interestingNames] that are resolved to inherited names.
final Set<String> inheritedNames = Set<String>();
Element enclosingUnitMemberElement;
NotSyntacticScopeReferencedNamesCollector(
this.enclosingLibraryElement,
this.interestingNames,
);
@override
void visitCombinator(Combinator node) {}
@override
void visitCompilationUnitMember(CompilationUnitMember node) {
enclosingUnitMemberElement = node.declaredElement;
super.visitCompilationUnitMember(node);
enclosingUnitMemberElement = null;
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
var name = node.name;
referencedNames.add(name);
if (node.isQualified) return;
if (!interestingNames.contains(name)) return;
var element = node.staticElement;
if (element == null) return;
if (_inSyntacticScope(element)) return;
if (element.enclosingElement is CompilationUnitElement) {
importedNames[name] = element.librarySource.uri;
} else {
inheritedNames.add(name);
}
}
bool _inSyntacticScope(Element element) {
if (element.enclosingElement is CompilationUnitElement &&
element.enclosingElement.enclosingElement == enclosingLibraryElement) {
return true;
}
while (element != null) {
if (element == enclosingUnitMemberElement) return true;
element = element.enclosingElement;
}
return false;
}
}
/// AST visitor that collects syntactic scope names visible at the [offset].
///
/// The AST does not need to be resolved.
class SyntacticScopeNamesCollector extends RecursiveAstVisitor<void> {
final Set<String> names;
final int offset;
SyntacticScopeNamesCollector(this.names, this.offset);
@override
void visitBlock(Block node) {
if (!_isCoveredBy(node)) return;
super.visitBlock(node);
for (var statement in node.statements) {
if (statement is FunctionDeclarationStatement) {
_addName(statement.functionDeclaration.name);
} else if (statement is VariableDeclarationStatement) {
_addVariables(statement.variables);
}
}
}
@override
void visitCatchClause(CatchClause node) {
if (!_isCoveredBy(node)) return;
if (node.exceptionParameter != null) {
_addName(node.exceptionParameter);
}
if (node.stackTraceParameter != null) {
_addName(node.stackTraceParameter);
}
node.body.accept(this);
}
@override
void visitClassDeclaration(ClassDeclaration node) {
if (!_isCoveredBy(node)) return;
_addTypeParameters(node.typeParameters);
_visitClassOrMixinMembers(node);
}
@override
void visitClassTypeAlias(ClassTypeAlias node) {
if (!_isCoveredBy(node)) return;
_addTypeParameters(node.typeParameters);
}
@override
void visitConstructorDeclaration(ConstructorDeclaration node) {
if (!_isCoveredBy(node)) return;
_addFormalParameters(node.parameters);
node.body.accept(this);
}
@override
void visitForEachStatement(ForEachStatement node) {
if (!_isCoveredBy(node)) return;
if (node.loopVariable != null && _isCoveredBy(node.body)) {
_addName(node.loopVariable.identifier);
}
node.iterable?.accept(this);
node.body.accept(this);
}
@override
void visitForElement(ForElement node) {
if (!_isCoveredBy(node)) return;
_addForLoopParts(node.forLoopParts, node.body);
super.visitForElement(node);
}
@override
void visitForStatement(ForStatement node) {
if (!_isCoveredBy(node)) return;
if (node.variables != null) {
_addVariables(node.variables);
}
node.condition?.accept(this);
node.updaters?.accept(this);
node.body.accept(this);
}
@override
void visitForStatement2(ForStatement2 node) {
if (!_isCoveredBy(node)) return;
_addForLoopParts(node.forLoopParts, node.body);
super.visitForStatement2(node);
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
if (!_isCoveredBy(node)) return;
var function = node.functionExpression;
_addTypeParameters(function.typeParameters);
if (function.parameters != null && offset > function.parameters.offset) {
_addFormalParameters(function.parameters);
}
function.body.accept(this);
}
@override
void visitFunctionTypeAlias(FunctionTypeAlias node) {
if (!_isCoveredBy(node)) return;
_addTypeParameters(node.typeParameters);
if (offset > node.parameters.offset) {
_addFormalParameters(node.parameters);
}
}
@override
void visitGenericFunctionType(GenericFunctionType node) {
if (!_isCoveredBy(node)) return;
_addTypeParameters(node.typeParameters);
if (offset > node.parameters.offset) {
_addFormalParameters(node.parameters);
}
}
@override
void visitGenericTypeAlias(GenericTypeAlias node) {
if (!_isCoveredBy(node)) return;
_addTypeParameters(node.typeParameters);
node.functionType.accept(this);
}
@override
void visitMethodDeclaration(MethodDeclaration node) {
if (!_isCoveredBy(node)) return;
_addTypeParameters(node.typeParameters);
if (node.parameters != null && offset > node.parameters.offset) {
_addFormalParameters(node.parameters);
}
node.body.accept(this);
}
@override
void visitMixinDeclaration(MixinDeclaration node) {
if (!_isCoveredBy(node)) return;
_addTypeParameters(node.typeParameters);
_visitClassOrMixinMembers(node);
}
void _addForLoopParts(ForLoopParts forLoopParts, AstNode body) {
if (forLoopParts is ForEachPartsWithDeclaration) {
if (_isCoveredBy(body)) {
_addName(forLoopParts.loopVariable.identifier);
}
} else if (forLoopParts is ForPartsWithDeclarations) {
_addVariables(forLoopParts.variables);
}
}
void _addFormalParameter(FormalParameter parameter) {
if (parameter is DefaultFormalParameter) {
_addFormalParameter(parameter.parameter);
} else if (parameter is FunctionTypedFormalParameter) {
_addName(parameter.identifier);
var parameters = parameter.parameters;
if (parameters != null && _isCoveredBy(parameters)) {
_addFormalParameters(parameters);
}
} else if (parameter is SimpleFormalParameter) {
_addName(parameter.identifier);
} else {
throw UnimplementedError('(${parameter.runtimeType}) $parameter');
}
}
void _addFormalParameters(FormalParameterList parameterList) {
for (var parameter in parameterList.parameters) {
_addFormalParameter(parameter);
}
}
void _addName(SimpleIdentifier node) {
if (node != null) {
names.add(node.name);
}
}
void _addTypeParameters(TypeParameterList typeParameterList) {
if (typeParameterList == null) return;
for (var typeParameter in typeParameterList.typeParameters) {
_addName(typeParameter.name);
}
}
void _addVariables(VariableDeclarationList variableList) {
for (var field in variableList.variables) {
_addName(field.name);
}
}
bool _isCoveredBy(AstNode node) {
return node.offset < offset && offset < node.end;
}
void _visitClassOrMixinMembers(ClassOrMixinDeclaration node) {
if (offset < node.leftBracket.offset) return;
for (var member in node.members) {
if (member is FieldDeclaration) {
_addVariables(member.fields);
} else if (member is MethodDeclaration) {
_addName(member.name);
}
}
node.members.accept(this);
}
}