| // 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/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/src/dart/analysis/dependency/node.dart'; |
| import 'package:analyzer/src/dart/ast/token.dart'; |
| import 'package:meta/meta.dart'; |
| |
| /// Collector of information about external nodes referenced by a node. |
| /// |
| /// The workflow for using it is that the library builder creates a new |
| /// instance, fills it with names of import prefixes using [addImportPrefix]. |
| /// Then for each node defined in the library, [collect] is called with |
| /// corresponding AST nodes to record references to external names, and |
| /// construct the API or implementation [Dependencies]. |
| class ReferenceCollector { |
| /// Local scope inside the node, containing local names such as parameters, |
| /// local variables, local functions, local type parameters, etc. |
| final _LocalScopes _localScopes = _LocalScopes(); |
| |
| /// The list of names that are referenced without any prefix, neither an |
| /// import prefix, nor a target expression. |
| _NameSet _unprefixedReferences = _NameSet(); |
| |
| /// The list of names that are referenced using an import prefix. |
| /// |
| /// It is filled by [addImportPrefix] and shared across all nodes. |
| List<_ReferencedImportPrefixedNames> _importPrefixedReferences = []; |
| |
| /// The list of names that are referenced with `super`. |
| _NameSet _superReferences = _NameSet(); |
| |
| /// The set of referenced class members. |
| _ClassMemberReferenceSet _memberReferences = _ClassMemberReferenceSet(); |
| |
| /// Record that the [name] is a name of an import prefix. |
| /// |
| /// So, when we see code like `prefix.foo` we know that `foo` should be |
| /// resolved in the import scope that corresponds to `prefix` (unless the |
| /// name `prefix` is shadowed by a local declaration). |
| void addImportPrefix(String name) { |
| assert(_localScopes.isEmpty); |
| for (var import in _importPrefixedReferences) { |
| if (import.prefix == name) { |
| return; |
| } |
| } |
| _importPrefixedReferences.add(_ReferencedImportPrefixedNames(name)); |
| } |
| |
| /// Construct and return a new [Dependencies] with the given [tokenSignature] |
| /// and all recorded references to external nodes in the given AST nodes. |
| Dependencies collect(List<int> tokenSignature, |
| {String enclosingClassName, |
| String thisNodeName, |
| List<ConstructorInitializer> constructorInitializers, |
| TypeName enclosingSuperClass, |
| Expression expression, |
| ExtendsClause extendsClause, |
| FormalParameterList formalParameters, |
| FormalParameterList formalParametersForImpl, |
| FunctionBody functionBody, |
| ImplementsClause implementsClause, |
| OnClause onClause, |
| ConstructorName redirectedConstructor, |
| TypeAnnotation returnType, |
| TypeName superClass, |
| TypeAnnotation type, |
| TypeParameterList typeParameters, |
| TypeParameterList typeParameters2, |
| WithClause withClause}) { |
| _localScopes.enter(); |
| |
| // The name of the node shadows any external names. |
| if (enclosingClassName != null) { |
| _localScopes.add(enclosingClassName); |
| } |
| if (thisNodeName != null) { |
| _localScopes.add(thisNodeName); |
| } |
| |
| // Add type parameters first, they might be referenced later. |
| _visitTypeParameterList(typeParameters); |
| _visitTypeParameterList(typeParameters2); |
| |
| // Parts of classes. |
| _visitTypeAnnotation(extendsClause?.superclass); |
| _visitTypeAnnotation(superClass); |
| _visitTypeAnnotations(withClause?.mixinTypes); |
| _visitTypeAnnotations(onClause?.superclassConstraints); |
| _visitTypeAnnotations(implementsClause?.interfaces); |
| |
| // Parts of executables. |
| _visitFormalParameterList(formalParameters); |
| _visitFormalParameterListImpl(formalParametersForImpl); |
| _visitTypeAnnotation(returnType); |
| _visitFunctionBody(functionBody); |
| |
| // Parts of constructors. |
| _visitConstructorInitializers(enclosingSuperClass, constructorInitializers); |
| _visitConstructorName(redirectedConstructor); |
| |
| // Parts of variables. |
| _visitTypeAnnotation(type); |
| _visitExpression(expression); |
| |
| _localScopes.exit(); |
| |
| var unprefixedReferencedNames = _unprefixedReferences.toList(); |
| _unprefixedReferences = _NameSet(); |
| |
| var importPrefixCount = 0; |
| for (var i = 0; i < _importPrefixedReferences.length; i++) { |
| var import = _importPrefixedReferences[i]; |
| if (import.names.isNotEmpty) { |
| importPrefixCount++; |
| } |
| } |
| |
| var importPrefixes = List<String>(importPrefixCount); |
| var importPrefixedReferencedNames = List<List<String>>(importPrefixCount); |
| var importIndex = 0; |
| for (var i = 0; i < _importPrefixedReferences.length; i++) { |
| var import = _importPrefixedReferences[i]; |
| |
| if (import.names.isNotEmpty) { |
| importPrefixes[importIndex] = import.prefix; |
| importPrefixedReferencedNames[importIndex] = import.names.toList(); |
| importIndex++; |
| } |
| |
| import.clear(); |
| } |
| |
| var superReferencedNames = _superReferences.toList(); |
| _superReferences = _NameSet(); |
| |
| var classMemberReferences = _memberReferences.toList(); |
| _memberReferences = _ClassMemberReferenceSet(); |
| |
| return Dependencies( |
| tokenSignature, |
| unprefixedReferencedNames, |
| importPrefixes, |
| importPrefixedReferencedNames, |
| superReferencedNames, |
| classMemberReferences, |
| ); |
| } |
| |
| /// Return the collector for the import prefix with the given [name]. |
| _ReferencedImportPrefixedNames _importPrefix(String name) { |
| assert(!_localScopes.contains(name)); |
| for (var i = 0; i < _importPrefixedReferences.length; i++) { |
| var references = _importPrefixedReferences[i]; |
| if (references.prefix == name) { |
| return references; |
| } |
| } |
| return null; |
| } |
| |
| void _recordClassMemberReference(DartType targetType, String name) { |
| if (targetType is InterfaceType) { |
| _memberReferences.add(targetType.element, name); |
| } |
| } |
| |
| /// Record a new unprefixed name reference. |
| void _recordUnprefixedReference(String name) { |
| assert(!_localScopes.contains(name)); |
| _unprefixedReferences.add(name); |
| } |
| |
| void _visitAdjacentStrings(AdjacentStrings node) { |
| var strings = node.strings; |
| for (var i = 0; i < strings.length; i++) { |
| var string = strings[i]; |
| _visitExpression(string); |
| } |
| } |
| |
| void _visitArgumentList(ArgumentList node) { |
| var arguments = node.arguments; |
| for (var i = 0; i < arguments.length; i++) { |
| var argument = arguments[i]; |
| _visitExpression(argument); |
| } |
| } |
| |
| void _visitAssignmentExpression(AssignmentExpression node) { |
| var assignmentType = node.operator.type; |
| |
| _visitExpression(node.leftHandSide, |
| get: assignmentType != TokenType.EQ, set: true); |
| _visitExpression(node.rightHandSide); |
| |
| if (assignmentType != TokenType.EQ && |
| assignmentType != TokenType.QUESTION_QUESTION_EQ) { |
| var operatorType = operatorFromCompoundAssignment(assignmentType); |
| _recordClassMemberReference( |
| node.leftHandSide.staticType, |
| operatorType.lexeme, |
| ); |
| } |
| } |
| |
| void _visitBinaryExpression(BinaryExpression node) { |
| var operatorName = node.operator.lexeme; |
| var leftOperand = node.leftOperand; |
| if (leftOperand is SuperExpression) { |
| _superReferences.add(operatorName); |
| } else { |
| _visitExpression(leftOperand); |
| _recordClassMemberReference(leftOperand.staticType, operatorName); |
| } |
| _visitExpression(node.rightOperand); |
| } |
| |
| void _visitBlock(Block node) { |
| if (node == null) return; |
| |
| _visitStatements(node.statements); |
| } |
| |
| void _visitCascadeExpression(CascadeExpression node) { |
| _visitExpression(node.target); |
| var sections = node.cascadeSections; |
| for (var i = 0; i < sections.length; i++) { |
| var section = sections[i]; |
| _visitExpression(section); |
| } |
| } |
| |
| void _visitCollectionElement(CollectionElement node) { |
| if (node == null) { |
| return; |
| } else if (node is Expression) { |
| _visitExpression(node); |
| } else if (node is ForElement) { |
| _visitForLoopParts(node.forLoopParts); |
| _visitCollectionElement(node.body); |
| } else if (node is IfElement) { |
| _visitExpression(node.condition); |
| _visitCollectionElement(node.thenElement); |
| _visitCollectionElement(node.elseElement); |
| } else if (node is MapLiteralEntry) { |
| _visitExpression(node.key); |
| _visitExpression(node.value); |
| } else if (node is SpreadElement) { |
| _visitExpression(node.expression); |
| } else { |
| throw UnimplementedError('(${node.runtimeType}) $node'); |
| } |
| } |
| |
| /// Record reference to the constructor of the [type] with the given [name]. |
| void _visitConstructor(TypeName type, SimpleIdentifier name) { |
| _visitTypeAnnotation(type); |
| |
| if (name != null) { |
| _recordClassMemberReference(type.type, name.name); |
| } else { |
| _recordClassMemberReference(type.type, ''); |
| } |
| } |
| |
| void _visitConstructorInitializers( |
| TypeName superClass, List<ConstructorInitializer> initializers) { |
| if (initializers == null) return; |
| |
| for (var i = 0; i < initializers.length; i++) { |
| var initializer = initializers[i]; |
| if (initializer is AssertInitializer) { |
| _visitExpression(initializer.condition); |
| _visitExpression(initializer.message); |
| } else if (initializer is ConstructorFieldInitializer) { |
| _visitExpression(initializer.expression); |
| } else if (initializer is SuperConstructorInvocation) { |
| _visitConstructor(superClass, initializer.constructorName); |
| _visitArgumentList(initializer.argumentList); |
| } else if (initializer is RedirectingConstructorInvocation) { |
| _visitArgumentList(initializer.argumentList); |
| // Strongly speaking, we reference a field of the enclosing class. |
| // |
| // However the current plan is to resolve the whole library on a change. |
| // So, we will resolve the enclosing constructor anyway. |
| } else { |
| throw UnimplementedError('(${initializer.runtimeType}) $initializer'); |
| } |
| } |
| } |
| |
| void _visitConstructorName(ConstructorName node) { |
| if (node == null) return; |
| |
| _visitConstructor(node.type, node.name); |
| } |
| |
| void _visitExpression(Expression node, {bool get: true, bool set: false}) { |
| if (node == null) return; |
| |
| if (node is AdjacentStrings) { |
| _visitAdjacentStrings(node); |
| } else if (node is AsExpression) { |
| _visitExpression(node.expression); |
| _visitTypeAnnotation(node.type); |
| } else if (node is AssignmentExpression) { |
| _visitAssignmentExpression(node); |
| } else if (node is AwaitExpression) { |
| _visitExpression(node.expression); |
| } else if (node is BinaryExpression) { |
| _visitBinaryExpression(node); |
| } else if (node is BooleanLiteral) { |
| // no dependencies |
| } else if (node is CascadeExpression) { |
| _visitCascadeExpression(node); |
| } else if (node is ConditionalExpression) { |
| _visitExpression(node.condition); |
| _visitExpression(node.thenExpression); |
| _visitExpression(node.elseExpression); |
| } else if (node is DoubleLiteral) { |
| // no dependencies |
| } else if (node is FunctionExpression) { |
| _visitFunctionExpression(node); |
| } else if (node is FunctionExpressionInvocation) { |
| _visitExpression(node.function); |
| _visitTypeArguments(node.typeArguments); |
| _visitArgumentList(node.argumentList); |
| } else if (node is IndexExpression) { |
| _visitIndexExpression(node, get: get, set: set); |
| } else if (node is InstanceCreationExpression) { |
| _visitInstanceCreationExpression(node); |
| } else if (node is IntegerLiteral) { |
| // no dependencies |
| } else if (node is IsExpression) { |
| _visitExpression(node.expression); |
| _visitTypeAnnotation(node.type); |
| } else if (node is ListLiteral) { |
| _visitListLiteral(node); |
| } else if (node is MethodInvocation) { |
| _visitMethodInvocation(node); |
| } else if (node is NamedExpression) { |
| _visitExpression(node.expression); |
| } else if (node is NullLiteral) { |
| // no dependencies |
| } else if (node is ParenthesizedExpression) { |
| _visitExpression(node.expression); |
| } else if (node is PostfixExpression) { |
| _visitPostfixExpression(node); |
| } else if (node is PrefixExpression) { |
| _visitPrefixExpression(node); |
| } else if (node is PrefixedIdentifier) { |
| _visitPrefixedIdentifier(node); |
| } else if (node is PropertyAccess) { |
| _visitPropertyAccess(node, get: get, set: set); |
| } else if (node is RethrowExpression) { |
| // no dependencies |
| } else if (node is SetOrMapLiteral) { |
| _visitSetOrMapLiteral(node); |
| } else if (node is SimpleIdentifier) { |
| _visitSimpleIdentifier(node, get: get, set: set); |
| } else if (node is SimpleStringLiteral) { |
| // no dependencies |
| } else if (node is StringInterpolation) { |
| _visitStringInterpolation(node); |
| } else if (node is SymbolLiteral) { |
| // no dependencies |
| } else if (node is ThisExpression) { |
| // Strongly speaking, "this" should add dependencies. |
| // Just like any class reference, it depends on the class hierarchy. |
| // For example adding a new type to the `implements` clause might make |
| // it OK to pass `this` as an argument of an invocation. |
| // |
| // However the current plan is to resolve the whole library on a change. |
| // So, we will resolve all implementations that reference `this`. |
| } else if (node is ThrowExpression) { |
| _visitExpression(node.expression); |
| } else { |
| throw UnimplementedError('(${node.runtimeType}) $node'); |
| } |
| } |
| |
| void _visitExpressionList(NodeList<Expression> nodes) { |
| for (Expression node in nodes) { |
| _visitExpression(node); |
| } |
| } |
| |
| void _visitForLoopParts(ForLoopParts node) { |
| if (node == null) { |
| return; |
| } else if (node is ForPartsWithDeclarations) { |
| _visitVariableList(node.variables); |
| _visitExpression(node.condition); |
| _visitExpressionList(node.updaters); |
| } else if (node is ForPartsWithExpression) { |
| _visitExpression(node.initialization); |
| _visitExpression(node.condition); |
| _visitExpressionList(node.updaters); |
| } else if (node is ForEachPartsWithDeclaration) { |
| var variable = node.loopVariable; |
| _visitTypeAnnotation(variable.type); |
| _visitExpression(node.iterable); |
| _localScopes.add(variable.identifier.name); |
| } else if (node is ForEachPartsWithIdentifier) { |
| _visitExpression(node.identifier); |
| _visitExpression(node.iterable); |
| } else { |
| throw UnimplementedError('(${node.runtimeType}) $node'); |
| } |
| } |
| |
| void _visitFormalParameterList(FormalParameterList node) { |
| if (node == null) return; |
| |
| var parameters = node.parameters; |
| for (var i = 0; i < parameters.length; i++) { |
| FormalParameter parameter = parameters[i]; |
| if (parameter is DefaultFormalParameter) { |
| DefaultFormalParameter defaultParameter = parameter; |
| parameter = defaultParameter.parameter; |
| } |
| if (parameter.identifier != null) { |
| _localScopes.add(parameter.identifier.name); |
| } |
| if (parameter is FieldFormalParameter) { |
| _visitTypeAnnotation(parameter.type); |
| // Strongly speaking, we reference a field of the enclosing class. |
| // |
| // However the current plan is to resolve the whole library on a change. |
| // So, we will resolve the enclosing constructor anyway. |
| } else if (parameter is FunctionTypedFormalParameter) { |
| _visitTypeAnnotation(parameter.returnType); |
| _visitFormalParameterList(parameter.parameters); |
| } else if (parameter is SimpleFormalParameter) { |
| _visitTypeAnnotation(parameter.type); |
| } else { |
| throw StateError('Unexpected: (${parameter.runtimeType}) $parameter'); |
| } |
| } |
| } |
| |
| void _visitFormalParameterListImpl(FormalParameterList node) { |
| if (node == null) return; |
| |
| var parameters = node.parameters; |
| for (var i = 0; i < parameters.length; i++) { |
| FormalParameter parameter = parameters[i]; |
| |
| if (parameter is DefaultFormalParameter) { |
| DefaultFormalParameter defaultParameter = parameter; |
| _visitExpression(defaultParameter.defaultValue); |
| parameter = defaultParameter.parameter; |
| } |
| |
| if (parameter.identifier != null) { |
| _localScopes.add(parameter.identifier.name); |
| } |
| } |
| } |
| |
| void _visitForStatement(ForStatement node) { |
| _localScopes.enter(); |
| |
| _visitForLoopParts(node.forLoopParts); |
| _visitStatement(node.body); |
| |
| _localScopes.exit(); |
| } |
| |
| void _visitFunctionBody(FunctionBody node) { |
| if (node == null) return; |
| |
| if (node is BlockFunctionBody) { |
| _visitStatement(node.block); |
| } else if (node is EmptyFunctionBody) { |
| return; |
| } else if (node is ExpressionFunctionBody) { |
| _visitExpression(node.expression); |
| } else { |
| throw UnimplementedError('(${node.runtimeType}) $node'); |
| } |
| } |
| |
| void _visitFunctionDeclarationStatement(FunctionDeclarationStatement node) { |
| var function = node.functionDeclaration; |
| _visitTypeAnnotation(function.returnType); |
| _visitFunctionExpression(function.functionExpression); |
| } |
| |
| void _visitFunctionExpression(FunctionExpression node) { |
| _localScopes.enter(); |
| _visitTypeParameterList(node.typeParameters); |
| _visitFormalParameterList(node.parameters); |
| _visitFormalParameterListImpl(node.parameters); |
| _visitFunctionBody(node.body); |
| _localScopes.exit(); |
| } |
| |
| void _visitIndexExpression(IndexExpression node, |
| {@required bool get, @required bool set}) { |
| var target = node.target; |
| if (target == null) { |
| // no dependencies |
| } else if (target is SuperExpression) { |
| if (get) { |
| _superReferences.add('[]'); |
| } |
| if (set) { |
| _superReferences.add('[]='); |
| } |
| } else { |
| _visitExpression(target); |
| var targetType = target.staticType; |
| if (get) { |
| _recordClassMemberReference(targetType, '[]'); |
| } |
| if (set) { |
| _recordClassMemberReference(targetType, '[]='); |
| } |
| } |
| |
| _visitExpression(node.index); |
| } |
| |
| void _visitInstanceCreationExpression(InstanceCreationExpression node) { |
| _visitConstructorName(node.constructorName); |
| _visitArgumentList(node.argumentList); |
| } |
| |
| void _visitListLiteral(ListLiteral node) { |
| _visitTypeArguments(node.typeArguments); |
| var elements = node.elements; |
| for (var i = 0; i < elements.length; i++) { |
| var element = elements[i]; |
| _visitCollectionElement(element); |
| } |
| } |
| |
| void _visitMethodInvocation(MethodInvocation node) { |
| var realTarget = node.realTarget; |
| if (realTarget == null) { |
| _visitExpression(node.methodName); |
| } else if (realTarget is SuperExpression) { |
| _superReferences.add(node.methodName.name); |
| } else { |
| _visitExpression(node.target); |
| _recordClassMemberReference( |
| realTarget.staticType, |
| node.methodName.name, |
| ); |
| } |
| _visitTypeArguments(node.typeArguments); |
| _visitArgumentList(node.argumentList); |
| } |
| |
| void _visitPostfixExpression(PostfixExpression node) { |
| _visitExpression(node.operand); |
| |
| var operator = node.operator.type; |
| if (operator == TokenType.MINUS_MINUS) { |
| _recordClassMemberReference(node.operand.staticType, '-'); |
| } else if (operator == TokenType.PLUS_PLUS) { |
| _recordClassMemberReference(node.operand.staticType, '+'); |
| } else { |
| throw UnimplementedError('$operator'); |
| } |
| } |
| |
| void _visitPrefixedIdentifier(PrefixedIdentifier node) { |
| var prefix = node.prefix; |
| var prefixElement = prefix.staticElement; |
| if (prefixElement is PrefixElement) { |
| var prefixName = prefix.name; |
| var importPrefix = _importPrefix(prefixName); |
| importPrefix.add(node.identifier.name); |
| } else { |
| _visitExpression(prefix); |
| _recordClassMemberReference(prefix.staticType, node.identifier.name); |
| } |
| } |
| |
| void _visitPrefixExpression(PrefixExpression node) { |
| _visitExpression(node.operand); |
| |
| var operatorName = node.operator.lexeme; |
| if (operatorName == '-') operatorName = 'unary-'; |
| |
| _recordClassMemberReference(node.operand.staticType, operatorName); |
| } |
| |
| void _visitPropertyAccess(PropertyAccess node, |
| {@required bool get, @required bool set}) { |
| var realTarget = node.realTarget; |
| var name = node.propertyName.name; |
| |
| if (realTarget is SuperExpression) { |
| if (get) { |
| _superReferences.add(name); |
| } |
| if (set) { |
| _superReferences.add('$name='); |
| } |
| } else { |
| _visitExpression(node.target); |
| if (get) { |
| _recordClassMemberReference(realTarget.staticType, name); |
| } |
| if (set) { |
| _recordClassMemberReference(realTarget.staticType, '$name='); |
| } |
| } |
| } |
| |
| void _visitSetOrMapLiteral(SetOrMapLiteral node) { |
| _visitTypeArguments(node.typeArguments); |
| var elements = node.elements; |
| for (var i = 0; i < elements.length; i++) { |
| var element = elements[i]; |
| _visitCollectionElement(element); |
| } |
| } |
| |
| void _visitSimpleIdentifier(SimpleIdentifier node, |
| {@required bool get, @required bool set}) { |
| if (node.isSynthetic) return; |
| |
| var name = node.name; |
| if (_localScopes.contains(name) || name == 'void' || name == 'dynamic') { |
| return; |
| } |
| |
| if (get) { |
| _recordUnprefixedReference(name); |
| } |
| if (set) { |
| _recordUnprefixedReference('$name='); |
| } |
| } |
| |
| void _visitStatement(Statement node) { |
| if (node == null) return; |
| |
| if (node is AssertStatement) { |
| _visitExpression(node.condition); |
| _visitExpression(node.message); |
| } else if (node is Block) { |
| _visitBlock(node); |
| } else if (node is BreakStatement) { |
| // nothing |
| } else if (node is ContinueStatement) { |
| // nothing |
| } else if (node is DoStatement) { |
| _visitStatement(node.body); |
| _visitExpression(node.condition); |
| } else if (node is EmptyStatement) { |
| // nothing |
| } else if (node is ExpressionStatement) { |
| _visitExpression(node.expression); |
| } else if (node is ForStatement) { |
| _visitForStatement(node); |
| } else if (node is FunctionDeclarationStatement) { |
| _visitFunctionDeclarationStatement(node); |
| } else if (node is IfStatement) { |
| _visitExpression(node.condition); |
| _visitStatement(node.thenStatement); |
| _visitStatement(node.elseStatement); |
| } else if (node is LabeledStatement) { |
| _visitStatement(node.statement); |
| } else if (node is ReturnStatement) { |
| _visitExpression(node.expression); |
| } else if (node is SwitchStatement) { |
| _visitSwitchStatement(node); |
| } else if (node is TryStatement) { |
| _visitTryStatement(node); |
| } else if (node is VariableDeclarationStatement) { |
| _visitVariableList(node.variables); |
| } else if (node is WhileStatement) { |
| _visitExpression(node.condition); |
| _visitStatement(node.body); |
| } else if (node is YieldStatement) { |
| _visitExpression(node.expression); |
| } else { |
| throw UnimplementedError('(${node.runtimeType}) $node'); |
| } |
| } |
| |
| void _visitStatements(List<Statement> statements) { |
| _localScopes.enter(); |
| |
| for (var i = 0; i < statements.length; i++) { |
| var statement = statements[i]; |
| if (statement is FunctionDeclarationStatement) { |
| _localScopes.add(statement.functionDeclaration.name.name); |
| } else if (statement is VariableDeclarationStatement) { |
| var variables = statement.variables.variables; |
| for (int i = 0; i < variables.length; i++) { |
| _localScopes.add(variables[i].name.name); |
| } |
| } |
| } |
| |
| for (var i = 0; i < statements.length; i++) { |
| var statement = statements[i]; |
| _visitStatement(statement); |
| } |
| |
| _localScopes.exit(); |
| } |
| |
| void _visitStringInterpolation(StringInterpolation node) { |
| var elements = node.elements; |
| for (var i = 0; i < elements.length; i++) { |
| var element = elements[i]; |
| if (element is InterpolationExpression) { |
| _visitExpression(element.expression); |
| } |
| } |
| } |
| |
| void _visitSwitchStatement(SwitchStatement node) { |
| _visitExpression(node.expression); |
| var members = node.members; |
| for (var i = 0; i < members.length; i++) { |
| var member = members[i]; |
| if (member is SwitchCase) { |
| _visitExpression(member.expression); |
| } |
| _visitStatements(member.statements); |
| } |
| } |
| |
| void _visitTryStatement(TryStatement node) { |
| _visitBlock(node.body); |
| |
| var catchClauses = node.catchClauses; |
| for (var i = 0; i < catchClauses.length; i++) { |
| var catchClause = catchClauses[i]; |
| _visitTypeAnnotation(catchClause.exceptionType); |
| |
| _localScopes.enter(); |
| |
| var exceptionParameter = catchClause.exceptionParameter; |
| if (exceptionParameter != null) { |
| _localScopes.add(exceptionParameter.name); |
| } |
| |
| var stackTraceParameter = catchClause.stackTraceParameter; |
| if (stackTraceParameter != null) { |
| _localScopes.add(stackTraceParameter.name); |
| } |
| |
| _visitBlock(catchClause.body); |
| |
| _localScopes.exit(); |
| } |
| |
| _visitBlock(node.finallyBlock); |
| } |
| |
| void _visitTypeAnnotation(TypeAnnotation node) { |
| if (node == null) return; |
| |
| if (node is GenericFunctionType) { |
| _localScopes.enter(); |
| |
| if (node.typeParameters != null) { |
| var typeParameters = node.typeParameters.typeParameters; |
| for (var i = 0; i < typeParameters.length; i++) { |
| var typeParameter = typeParameters[i]; |
| _localScopes.add(typeParameter.name.name); |
| } |
| for (var i = 0; i < typeParameters.length; i++) { |
| var typeParameter = typeParameters[i]; |
| _visitTypeAnnotation(typeParameter.bound); |
| } |
| } |
| |
| _visitTypeAnnotation(node.returnType); |
| _visitFormalParameterList(node.parameters); |
| |
| _localScopes.exit(); |
| } else if (node is TypeName) { |
| var identifier = node.name; |
| _visitExpression(identifier); |
| _visitTypeArguments(node.typeArguments); |
| } else { |
| throw UnimplementedError('(${node.runtimeType}) $node'); |
| } |
| } |
| |
| void _visitTypeAnnotations(List<TypeAnnotation> typeAnnotations) { |
| if (typeAnnotations == null) return; |
| |
| for (var i = 0; i < typeAnnotations.length; i++) { |
| var typeAnnotation = typeAnnotations[i]; |
| _visitTypeAnnotation(typeAnnotation); |
| } |
| } |
| |
| void _visitTypeArguments(TypeArgumentList node) { |
| if (node == null) return; |
| |
| _visitTypeAnnotations(node.arguments); |
| } |
| |
| void _visitTypeParameterList(TypeParameterList node) { |
| if (node == null) return; |
| |
| var typeParameters = node.typeParameters; |
| |
| // Define all type parameters in the local scope. |
| for (var i = 0; i < typeParameters.length; i++) { |
| var typeParameter = typeParameters[i]; |
| _localScopes.add(typeParameter.name.name); |
| } |
| |
| // Record bounds. |
| for (var i = 0; i < typeParameters.length; i++) { |
| var typeParameter = typeParameters[i]; |
| _visitTypeAnnotation(typeParameter.bound); |
| } |
| } |
| |
| void _visitVariableList(VariableDeclarationList node) { |
| if (node == null) return; |
| |
| _visitTypeAnnotation(node.type); |
| |
| var variables = node.variables; |
| for (int i = 0; i < variables.length; i++) { |
| var variable = variables[i]; |
| _localScopes.add(variable.name.name); |
| _visitExpression(variable.initializer); |
| } |
| } |
| } |
| |
| /// The sorted set of [ClassMemberReference]s. |
| class _ClassMemberReferenceSet { |
| final List<ClassMemberReference> references = []; |
| |
| void add(ClassElement class_, String name) { |
| var target = LibraryQualifiedName(class_.library.source.uri, class_.name); |
| var reference = ClassMemberReference(target, name); |
| if (!references.contains(reference)) { |
| references.add(reference); |
| } |
| } |
| |
| /// Return the sorted list of unique class member references. |
| List<ClassMemberReference> toList() { |
| references.sort(ClassMemberReference.compare); |
| return references; |
| } |
| } |
| |
| /// The stack of names that are defined in a local scope inside the node, |
| /// such as parameters, local variables, local functions, local type |
| /// parameters, etc. |
| class _LocalScopes { |
| /// The stack of name sets. |
| final List<_NameSet> scopes = []; |
| |
| bool get isEmpty => scopes.isEmpty; |
| |
| /// Add the given [name] to the current local scope. |
| void add(String name) { |
| scopes.last.add(name); |
| } |
| |
| /// Return whether the given [name] is defined in one of the local scopes. |
| bool contains(String name) { |
| for (var i = 0; i < scopes.length; i++) { |
| if (scopes[i].contains(name)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /// Enter a new local scope, e.g. a block, or a type parameter scope. |
| void enter() { |
| scopes.add(_NameSet()); |
| } |
| |
| /// Exit the current local scope. |
| void exit() { |
| scopes.removeLast(); |
| } |
| } |
| |
| class _NameSet { |
| final List<String> names = []; |
| |
| bool get isNotEmpty => names.isNotEmpty; |
| |
| void add(String name) { |
| // TODO(scheglov) consider just adding, but toList() sort and unique |
| if (!contains(name)) { |
| names.add(name); |
| } |
| } |
| |
| bool contains(String name) => names.contains(name); |
| |
| List<String> toList() { |
| names.sort(_compareStrings); |
| return names; |
| } |
| |
| static int _compareStrings(String first, String second) { |
| return first.compareTo(second); |
| } |
| } |
| |
| class _ReferencedImportPrefixedNames { |
| final String prefix; |
| _NameSet names = _NameSet(); |
| |
| _ReferencedImportPrefixedNames(this.prefix); |
| |
| void add(String name) { |
| names.add(name); |
| } |
| |
| void clear() { |
| names = _NameSet(); |
| } |
| } |