| // This code was auto-generated, is not intended to be edited, and is subject to |
| // significant change. Please see the README file for more information. |
| library engine.resolver; |
| import 'dart:collection'; |
| import 'java_core.dart'; |
| import 'java_engine.dart'; |
| import 'instrumentation.dart'; |
| import 'source.dart'; |
| import 'error.dart'; |
| import 'scanner.dart' as sc; |
| import 'utilities_general.dart'; |
| import 'utilities_dart.dart'; |
| import 'ast.dart'; |
| import 'parser.dart' show Parser, ParserErrorCode; |
| import 'sdk.dart' show DartSdk, SdkLibrary; |
| import 'element.dart'; |
| import 'html.dart' as ht; |
| import 'engine.dart'; |
| import 'constant.dart'; |
| /** |
| * Instances of the class `CompilationUnitBuilder` build an element model for a single |
| * compilation unit. |
| * |
| * @coverage dart.engine.resolver |
| */ |
| class CompilationUnitBuilder { |
| |
| /** |
| * Build the compilation unit element for the given source. |
| * |
| * @param source the source describing the compilation unit |
| * @param unit the AST structure representing the compilation unit |
| * @return the compilation unit element that was built |
| * @throws AnalysisException if the analysis could not be performed |
| */ |
| CompilationUnitElementImpl buildCompilationUnit(Source source, CompilationUnit unit) { |
| TimeCounter_TimeCounterHandle timeCounter = PerformanceStatistics.resolve.start(); |
| if (unit == null) { |
| return null; |
| } |
| ElementHolder holder = new ElementHolder(); |
| ElementBuilder builder = new ElementBuilder(holder); |
| unit.accept(builder); |
| CompilationUnitElementImpl element = new CompilationUnitElementImpl(source.shortName); |
| element.accessors = holder.accessors; |
| element.functions = holder.functions; |
| element.source = source; |
| element.typeAliases = holder.typeAliases; |
| element.types = holder.types; |
| element.topLevelVariables = holder.topLevelVariables; |
| unit.element = element; |
| timeCounter.stop(); |
| return element; |
| } |
| } |
| /** |
| * Instances of the class `ElementBuilder` traverse an AST structure and build the element |
| * model representing the AST structure. |
| * |
| * @coverage dart.engine.resolver |
| */ |
| class ElementBuilder extends RecursiveASTVisitor<Object> { |
| |
| /** |
| * The element holder associated with the element that is currently being built. |
| */ |
| ElementHolder _currentHolder; |
| |
| /** |
| * A flag indicating whether a variable declaration is in the context of a field declaration. |
| */ |
| bool _inFieldContext = false; |
| |
| /** |
| * A flag indicating whether a variable declaration is within the body of a method or function. |
| */ |
| bool _inFunction = false; |
| |
| /** |
| * A flag indicating whether the class currently being visited can be used as a mixin. |
| */ |
| bool _isValidMixin = false; |
| |
| /** |
| * A collection holding the function types defined in a class that need to have their type |
| * arguments set to the types of the type parameters for the class, or `null` if we are not |
| * currently processing nodes within a class. |
| */ |
| List<FunctionTypeImpl> _functionTypesToFix = null; |
| |
| /** |
| * Initialize a newly created element builder to build the elements for a compilation unit. |
| * |
| * @param initialHolder the element holder associated with the compilation unit being built |
| */ |
| ElementBuilder(ElementHolder initialHolder) { |
| _currentHolder = initialHolder; |
| } |
| Object visitBlock(Block node) { |
| bool wasInField = _inFieldContext; |
| _inFieldContext = false; |
| try { |
| node.visitChildren(this); |
| } finally { |
| _inFieldContext = wasInField; |
| } |
| return null; |
| } |
| Object visitCatchClause(CatchClause node) { |
| SimpleIdentifier exceptionParameter = node.exceptionParameter; |
| if (exceptionParameter != null) { |
| LocalVariableElementImpl exception = new LocalVariableElementImpl(exceptionParameter); |
| _currentHolder.addLocalVariable(exception); |
| exceptionParameter.staticElement = exception; |
| SimpleIdentifier stackTraceParameter = node.stackTraceParameter; |
| if (stackTraceParameter != null) { |
| LocalVariableElementImpl stackTrace = new LocalVariableElementImpl(stackTraceParameter); |
| _currentHolder.addLocalVariable(stackTrace); |
| stackTraceParameter.staticElement = stackTrace; |
| } |
| } |
| return super.visitCatchClause(node); |
| } |
| Object visitClassDeclaration(ClassDeclaration node) { |
| ElementHolder holder = new ElementHolder(); |
| _isValidMixin = true; |
| _functionTypesToFix = new List<FunctionTypeImpl>(); |
| visitChildren(holder, node); |
| SimpleIdentifier className = node.name; |
| ClassElementImpl element = new ClassElementImpl(className); |
| List<TypeParameterElement> typeParameters = holder.typeParameters; |
| List<Type2> typeArguments = createTypeParameterTypes(typeParameters); |
| InterfaceTypeImpl interfaceType = new InterfaceTypeImpl.con1(element); |
| interfaceType.typeArguments = typeArguments; |
| element.type = interfaceType; |
| List<ConstructorElement> constructors = holder.constructors; |
| if (constructors.length == 0) { |
| constructors = createDefaultConstructors(interfaceType); |
| } |
| element.abstract = node.abstractKeyword != null; |
| element.accessors = holder.accessors; |
| element.constructors = constructors; |
| element.fields = holder.fields; |
| element.methods = holder.methods; |
| element.typeParameters = typeParameters; |
| element.validMixin = _isValidMixin; |
| for (FunctionTypeImpl functionType in _functionTypesToFix) { |
| functionType.typeArguments = typeArguments; |
| } |
| _functionTypesToFix = null; |
| _currentHolder.addType(element); |
| className.staticElement = element; |
| holder.validate(); |
| return null; |
| } |
| Object visitClassTypeAlias(ClassTypeAlias node) { |
| ElementHolder holder = new ElementHolder(); |
| _functionTypesToFix = new List<FunctionTypeImpl>(); |
| visitChildren(holder, node); |
| SimpleIdentifier className = node.name; |
| ClassElementImpl element = new ClassElementImpl(className); |
| element.abstract = node.abstractKeyword != null; |
| element.typedef = true; |
| List<TypeParameterElement> typeParameters = holder.typeParameters; |
| element.typeParameters = typeParameters; |
| List<Type2> typeArguments = createTypeParameterTypes(typeParameters); |
| InterfaceTypeImpl interfaceType = new InterfaceTypeImpl.con1(element); |
| interfaceType.typeArguments = typeArguments; |
| element.type = interfaceType; |
| element.constructors = createDefaultConstructors(interfaceType); |
| for (FunctionTypeImpl functionType in _functionTypesToFix) { |
| functionType.typeArguments = typeArguments; |
| } |
| _functionTypesToFix = null; |
| _currentHolder.addType(element); |
| className.staticElement = element; |
| holder.validate(); |
| return null; |
| } |
| Object visitConstructorDeclaration(ConstructorDeclaration node) { |
| _isValidMixin = false; |
| ElementHolder holder = new ElementHolder(); |
| bool wasInFunction = _inFunction; |
| _inFunction = true; |
| try { |
| visitChildren(holder, node); |
| } finally { |
| _inFunction = wasInFunction; |
| } |
| SimpleIdentifier constructorName = node.name; |
| ConstructorElementImpl element = new ConstructorElementImpl(constructorName); |
| if (node.factoryKeyword != null) { |
| element.factory = true; |
| } |
| element.functions = holder.functions; |
| element.labels = holder.labels; |
| element.localVariables = holder.localVariables; |
| element.parameters = holder.parameters; |
| element.const2 = node.constKeyword != null; |
| _currentHolder.addConstructor(element); |
| node.element = element; |
| if (constructorName == null) { |
| Identifier returnType = node.returnType; |
| if (returnType != null) { |
| element.nameOffset = returnType.offset; |
| } |
| } else { |
| constructorName.staticElement = element; |
| } |
| holder.validate(); |
| return null; |
| } |
| Object visitDeclaredIdentifier(DeclaredIdentifier node) { |
| SimpleIdentifier variableName = node.identifier; |
| sc.Token keyword = node.keyword; |
| LocalVariableElementImpl element = new LocalVariableElementImpl(variableName); |
| ForEachStatement statement = node.parent as ForEachStatement; |
| int declarationEnd = node.offset + node.length; |
| int statementEnd = statement.offset + statement.length; |
| element.setVisibleRange(declarationEnd, statementEnd - declarationEnd - 1); |
| element.const3 = matches(keyword, sc.Keyword.CONST); |
| element.final2 = matches(keyword, sc.Keyword.FINAL); |
| _currentHolder.addLocalVariable(element); |
| variableName.staticElement = element; |
| return super.visitDeclaredIdentifier(node); |
| } |
| Object visitDefaultFormalParameter(DefaultFormalParameter node) { |
| ElementHolder holder = new ElementHolder(); |
| visit(holder, node.defaultValue); |
| FunctionElementImpl initializer = new FunctionElementImpl(); |
| initializer.functions = holder.functions; |
| initializer.labels = holder.labels; |
| initializer.localVariables = holder.localVariables; |
| initializer.parameters = holder.parameters; |
| SimpleIdentifier parameterName = node.parameter.identifier; |
| ParameterElementImpl parameter; |
| if (node.parameter is FieldFormalParameter) { |
| parameter = new DefaultFieldFormalParameterElementImpl(parameterName); |
| } else { |
| parameter = new DefaultParameterElementImpl(parameterName); |
| } |
| parameter.const3 = node.isConst; |
| parameter.final2 = node.isFinal; |
| parameter.initializer = initializer; |
| parameter.parameterKind = node.kind; |
| Expression defaultValue = node.defaultValue; |
| if (defaultValue != null) { |
| parameter.setDefaultValueRange(defaultValue.offset, defaultValue.length); |
| } |
| setParameterVisibleRange(node, parameter); |
| _currentHolder.addParameter(parameter); |
| parameterName.staticElement = parameter; |
| node.parameter.accept(this); |
| holder.validate(); |
| return null; |
| } |
| Object visitFieldDeclaration(FieldDeclaration node) { |
| bool wasInField = _inFieldContext; |
| _inFieldContext = true; |
| try { |
| node.visitChildren(this); |
| } finally { |
| _inFieldContext = wasInField; |
| } |
| return null; |
| } |
| Object visitFieldFormalParameter(FieldFormalParameter node) { |
| if (node.parent is! DefaultFormalParameter) { |
| SimpleIdentifier parameterName = node.identifier; |
| FieldFormalParameterElementImpl parameter = new FieldFormalParameterElementImpl(parameterName); |
| parameter.const3 = node.isConst; |
| parameter.final2 = node.isFinal; |
| parameter.parameterKind = node.kind; |
| _currentHolder.addParameter(parameter); |
| parameterName.staticElement = parameter; |
| } |
| ElementHolder holder = new ElementHolder(); |
| visitChildren(holder, node); |
| ((node.element as ParameterElementImpl)).parameters = holder.parameters; |
| holder.validate(); |
| return null; |
| } |
| Object visitFunctionDeclaration(FunctionDeclaration node) { |
| FunctionExpression expression = node.functionExpression; |
| if (expression != null) { |
| ElementHolder holder = new ElementHolder(); |
| bool wasInFunction = _inFunction; |
| _inFunction = true; |
| try { |
| visitChildren(holder, expression); |
| } finally { |
| _inFunction = wasInFunction; |
| } |
| sc.Token property = node.propertyKeyword; |
| if (property == null) { |
| SimpleIdentifier functionName = node.name; |
| FunctionElementImpl element = new FunctionElementImpl.con1(functionName); |
| element.functions = holder.functions; |
| element.labels = holder.labels; |
| element.localVariables = holder.localVariables; |
| element.parameters = holder.parameters; |
| if (_inFunction) { |
| Block enclosingBlock = node.getAncestor(Block); |
| if (enclosingBlock != null) { |
| int functionEnd = node.offset + node.length; |
| int blockEnd = enclosingBlock.offset + enclosingBlock.length; |
| element.setVisibleRange(functionEnd, blockEnd - functionEnd - 1); |
| } |
| } |
| _currentHolder.addFunction(element); |
| expression.element = element; |
| functionName.staticElement = element; |
| } else { |
| SimpleIdentifier propertyNameNode = node.name; |
| if (propertyNameNode == null) { |
| return null; |
| } |
| String propertyName = propertyNameNode.name; |
| TopLevelVariableElementImpl variable = _currentHolder.getTopLevelVariable(propertyName) as TopLevelVariableElementImpl; |
| if (variable == null) { |
| variable = new TopLevelVariableElementImpl.con2(node.name.name); |
| variable.final2 = true; |
| variable.synthetic = true; |
| _currentHolder.addTopLevelVariable(variable); |
| } |
| if (matches(property, sc.Keyword.GET)) { |
| PropertyAccessorElementImpl getter = new PropertyAccessorElementImpl.con1(propertyNameNode); |
| getter.functions = holder.functions; |
| getter.labels = holder.labels; |
| getter.localVariables = holder.localVariables; |
| getter.variable = variable; |
| getter.getter = true; |
| getter.static = true; |
| variable.getter = getter; |
| _currentHolder.addAccessor(getter); |
| expression.element = getter; |
| propertyNameNode.staticElement = getter; |
| } else { |
| PropertyAccessorElementImpl setter = new PropertyAccessorElementImpl.con1(propertyNameNode); |
| setter.functions = holder.functions; |
| setter.labels = holder.labels; |
| setter.localVariables = holder.localVariables; |
| setter.parameters = holder.parameters; |
| setter.variable = variable; |
| setter.setter = true; |
| setter.static = true; |
| variable.setter = setter; |
| variable.final2 = false; |
| _currentHolder.addAccessor(setter); |
| expression.element = setter; |
| propertyNameNode.staticElement = setter; |
| } |
| } |
| holder.validate(); |
| } |
| return null; |
| } |
| Object visitFunctionExpression(FunctionExpression node) { |
| ElementHolder holder = new ElementHolder(); |
| bool wasInFunction = _inFunction; |
| _inFunction = true; |
| try { |
| visitChildren(holder, node); |
| } finally { |
| _inFunction = wasInFunction; |
| } |
| FunctionElementImpl element = new FunctionElementImpl.con2(node.beginToken.offset); |
| element.functions = holder.functions; |
| element.labels = holder.labels; |
| element.localVariables = holder.localVariables; |
| element.parameters = holder.parameters; |
| if (_inFunction) { |
| Block enclosingBlock = node.getAncestor(Block); |
| if (enclosingBlock != null) { |
| int functionEnd = node.offset + node.length; |
| int blockEnd = enclosingBlock.offset + enclosingBlock.length; |
| element.setVisibleRange(functionEnd, blockEnd - functionEnd - 1); |
| } |
| } |
| FunctionTypeImpl type = new FunctionTypeImpl.con1(element); |
| if (_functionTypesToFix != null) { |
| _functionTypesToFix.add(type); |
| } |
| element.type = type; |
| _currentHolder.addFunction(element); |
| node.element = element; |
| holder.validate(); |
| return null; |
| } |
| Object visitFunctionTypeAlias(FunctionTypeAlias node) { |
| ElementHolder holder = new ElementHolder(); |
| visitChildren(holder, node); |
| SimpleIdentifier aliasName = node.name; |
| List<ParameterElement> parameters = holder.parameters; |
| List<TypeParameterElement> typeParameters = holder.typeParameters; |
| FunctionTypeAliasElementImpl element = new FunctionTypeAliasElementImpl(aliasName); |
| element.parameters = parameters; |
| element.typeParameters = typeParameters; |
| FunctionTypeImpl type = new FunctionTypeImpl.con2(element); |
| type.typeArguments = createTypeParameterTypes(typeParameters); |
| element.type = type; |
| _currentHolder.addTypeAlias(element); |
| aliasName.staticElement = element; |
| holder.validate(); |
| return null; |
| } |
| Object visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) { |
| if (node.parent is! DefaultFormalParameter) { |
| SimpleIdentifier parameterName = node.identifier; |
| ParameterElementImpl parameter = new ParameterElementImpl.con1(parameterName); |
| parameter.parameterKind = node.kind; |
| setParameterVisibleRange(node, parameter); |
| _currentHolder.addParameter(parameter); |
| parameterName.staticElement = parameter; |
| } |
| ElementHolder holder = new ElementHolder(); |
| visitChildren(holder, node); |
| ((node.element as ParameterElementImpl)).parameters = holder.parameters; |
| holder.validate(); |
| return null; |
| } |
| Object visitLabeledStatement(LabeledStatement node) { |
| bool onSwitchStatement = node.statement is SwitchStatement; |
| for (Label label in node.labels) { |
| SimpleIdentifier labelName = label.label; |
| LabelElementImpl element = new LabelElementImpl(labelName, onSwitchStatement, false); |
| _currentHolder.addLabel(element); |
| labelName.staticElement = element; |
| } |
| return super.visitLabeledStatement(node); |
| } |
| Object visitMethodDeclaration(MethodDeclaration node) { |
| ElementHolder holder = new ElementHolder(); |
| bool wasInFunction = _inFunction; |
| _inFunction = true; |
| try { |
| visitChildren(holder, node); |
| } finally { |
| _inFunction = wasInFunction; |
| } |
| bool isStatic = node.isStatic; |
| sc.Token property = node.propertyKeyword; |
| if (property == null) { |
| SimpleIdentifier methodName = node.name; |
| String nameOfMethod = methodName.name; |
| if (nameOfMethod == sc.TokenType.MINUS.lexeme && node.parameters.parameters.length == 0) { |
| nameOfMethod = "unary-"; |
| } |
| MethodElementImpl element = new MethodElementImpl.con2(nameOfMethod, methodName.offset); |
| element.abstract = node.isAbstract; |
| element.functions = holder.functions; |
| element.labels = holder.labels; |
| element.localVariables = holder.localVariables; |
| element.parameters = holder.parameters; |
| element.static = isStatic; |
| _currentHolder.addMethod(element); |
| methodName.staticElement = element; |
| } else { |
| SimpleIdentifier propertyNameNode = node.name; |
| String propertyName = propertyNameNode.name; |
| FieldElementImpl field = _currentHolder.getField(propertyName) as FieldElementImpl; |
| if (field == null) { |
| field = new FieldElementImpl.con2(node.name.name); |
| field.final2 = true; |
| field.static = isStatic; |
| field.synthetic = true; |
| _currentHolder.addField(field); |
| } |
| if (matches(property, sc.Keyword.GET)) { |
| PropertyAccessorElementImpl getter = new PropertyAccessorElementImpl.con1(propertyNameNode); |
| getter.functions = holder.functions; |
| getter.labels = holder.labels; |
| getter.localVariables = holder.localVariables; |
| getter.variable = field; |
| getter.abstract = node.body is EmptyFunctionBody && node.externalKeyword == null; |
| getter.getter = true; |
| getter.static = isStatic; |
| field.getter = getter; |
| _currentHolder.addAccessor(getter); |
| propertyNameNode.staticElement = getter; |
| } else { |
| PropertyAccessorElementImpl setter = new PropertyAccessorElementImpl.con1(propertyNameNode); |
| setter.functions = holder.functions; |
| setter.labels = holder.labels; |
| setter.localVariables = holder.localVariables; |
| setter.parameters = holder.parameters; |
| setter.variable = field; |
| setter.abstract = node.body is EmptyFunctionBody && !matches(node.externalKeyword, sc.Keyword.EXTERNAL); |
| setter.setter = true; |
| setter.static = isStatic; |
| field.setter = setter; |
| field.final2 = false; |
| _currentHolder.addAccessor(setter); |
| propertyNameNode.staticElement = setter; |
| } |
| } |
| holder.validate(); |
| return null; |
| } |
| Object visitSimpleFormalParameter(SimpleFormalParameter node) { |
| if (node.parent is! DefaultFormalParameter) { |
| SimpleIdentifier parameterName = node.identifier; |
| ParameterElementImpl parameter = new ParameterElementImpl.con1(parameterName); |
| parameter.const3 = node.isConst; |
| parameter.final2 = node.isFinal; |
| parameter.parameterKind = node.kind; |
| setParameterVisibleRange(node, parameter); |
| _currentHolder.addParameter(parameter); |
| parameterName.staticElement = parameter; |
| } |
| return super.visitSimpleFormalParameter(node); |
| } |
| Object visitSuperExpression(SuperExpression node) { |
| _isValidMixin = false; |
| return super.visitSuperExpression(node); |
| } |
| Object visitSwitchCase(SwitchCase node) { |
| for (Label label in node.labels) { |
| SimpleIdentifier labelName = label.label; |
| LabelElementImpl element = new LabelElementImpl(labelName, false, true); |
| _currentHolder.addLabel(element); |
| labelName.staticElement = element; |
| } |
| return super.visitSwitchCase(node); |
| } |
| Object visitSwitchDefault(SwitchDefault node) { |
| for (Label label in node.labels) { |
| SimpleIdentifier labelName = label.label; |
| LabelElementImpl element = new LabelElementImpl(labelName, false, true); |
| _currentHolder.addLabel(element); |
| labelName.staticElement = element; |
| } |
| return super.visitSwitchDefault(node); |
| } |
| Object visitTypeParameter(TypeParameter node) { |
| SimpleIdentifier parameterName = node.name; |
| TypeParameterElementImpl typeParameter = new TypeParameterElementImpl(parameterName); |
| TypeParameterTypeImpl typeParameterType = new TypeParameterTypeImpl(typeParameter); |
| typeParameter.type = typeParameterType; |
| _currentHolder.addTypeParameter(typeParameter); |
| parameterName.staticElement = typeParameter; |
| return super.visitTypeParameter(node); |
| } |
| Object visitVariableDeclaration(VariableDeclaration node) { |
| sc.Token keyword = ((node.parent as VariableDeclarationList)).keyword; |
| bool isConst = matches(keyword, sc.Keyword.CONST); |
| bool isFinal = matches(keyword, sc.Keyword.FINAL); |
| bool hasInitializer = node.initializer != null; |
| VariableElementImpl element; |
| if (_inFieldContext) { |
| SimpleIdentifier fieldName = node.name; |
| FieldElementImpl field; |
| if (isConst && hasInitializer) { |
| field = new ConstFieldElementImpl(fieldName); |
| } else { |
| field = new FieldElementImpl.con1(fieldName); |
| } |
| element = field; |
| _currentHolder.addField(field); |
| fieldName.staticElement = field; |
| } else if (_inFunction) { |
| SimpleIdentifier variableName = node.name; |
| LocalVariableElementImpl variable; |
| if (isConst && hasInitializer) { |
| variable = new ConstLocalVariableElementImpl(variableName); |
| } else { |
| variable = new LocalVariableElementImpl(variableName); |
| } |
| element = variable; |
| Block enclosingBlock = node.getAncestor(Block); |
| int functionEnd = node.offset + node.length; |
| int blockEnd = enclosingBlock.offset + enclosingBlock.length; |
| variable.setVisibleRange(functionEnd, blockEnd - functionEnd - 1); |
| _currentHolder.addLocalVariable(variable); |
| variableName.staticElement = element; |
| } else { |
| SimpleIdentifier variableName = node.name; |
| TopLevelVariableElementImpl variable; |
| if (isConst && hasInitializer) { |
| variable = new ConstTopLevelVariableElementImpl(variableName); |
| } else { |
| variable = new TopLevelVariableElementImpl.con1(variableName); |
| } |
| element = variable; |
| _currentHolder.addTopLevelVariable(variable); |
| variableName.staticElement = element; |
| } |
| element.const3 = isConst; |
| element.final2 = isFinal; |
| if (hasInitializer) { |
| ElementHolder holder = new ElementHolder(); |
| bool wasInFieldContext = _inFieldContext; |
| _inFieldContext = false; |
| try { |
| visit(holder, node.initializer); |
| } finally { |
| _inFieldContext = wasInFieldContext; |
| } |
| FunctionElementImpl initializer = new FunctionElementImpl(); |
| initializer.functions = holder.functions; |
| initializer.labels = holder.labels; |
| initializer.localVariables = holder.localVariables; |
| initializer.synthetic = true; |
| element.initializer = initializer; |
| holder.validate(); |
| } |
| if (element is PropertyInducingElementImpl) { |
| PropertyInducingElementImpl variable = element as PropertyInducingElementImpl; |
| if (_inFieldContext) { |
| ((variable as FieldElementImpl)).static = matches(((node.parent.parent as FieldDeclaration)).staticKeyword, sc.Keyword.STATIC); |
| } |
| PropertyAccessorElementImpl getter = new PropertyAccessorElementImpl.con2(variable); |
| getter.getter = true; |
| getter.static = variable.isStatic; |
| _currentHolder.addAccessor(getter); |
| variable.getter = getter; |
| if (!isFinal) { |
| PropertyAccessorElementImpl setter = new PropertyAccessorElementImpl.con2(variable); |
| setter.setter = true; |
| setter.static = variable.isStatic; |
| ParameterElementImpl parameter = new ParameterElementImpl.con2("_${variable.name}", variable.nameOffset); |
| parameter.synthetic = true; |
| parameter.parameterKind = ParameterKind.REQUIRED; |
| setter.parameters = <ParameterElement> [parameter]; |
| _currentHolder.addAccessor(setter); |
| variable.setter = setter; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Creates the [ConstructorElement]s array with the single default constructor element. |
| * |
| * @param interfaceType the interface type for which to create a default constructor |
| * @return the [ConstructorElement]s array with the single default constructor element |
| */ |
| List<ConstructorElement> createDefaultConstructors(InterfaceTypeImpl interfaceType) { |
| ConstructorElementImpl constructor = new ConstructorElementImpl(null); |
| constructor.synthetic = true; |
| constructor.returnType = interfaceType; |
| FunctionTypeImpl type = new FunctionTypeImpl.con1(constructor); |
| _functionTypesToFix.add(type); |
| constructor.type = type; |
| return <ConstructorElement> [constructor]; |
| } |
| |
| /** |
| * Create the types associated with the given type parameters, setting the type of each type |
| * parameter, and return an array of types corresponding to the given parameters. |
| * |
| * @param typeParameters the type parameters for which types are to be created |
| * @return an array of types corresponding to the given parameters |
| */ |
| List<Type2> createTypeParameterTypes(List<TypeParameterElement> typeParameters) { |
| int typeParameterCount = typeParameters.length; |
| List<Type2> typeArguments = new List<Type2>(typeParameterCount); |
| for (int i = 0; i < typeParameterCount; i++) { |
| TypeParameterElementImpl typeParameter = typeParameters[i] as TypeParameterElementImpl; |
| TypeParameterTypeImpl typeParameterType = new TypeParameterTypeImpl(typeParameter); |
| typeParameter.type = typeParameterType; |
| typeArguments[i] = typeParameterType; |
| } |
| return typeArguments; |
| } |
| |
| /** |
| * Return the body of the function that contains the given parameter, or `null` if no |
| * function body could be found. |
| * |
| * @param node the parameter contained in the function whose body is to be returned |
| * @return the body of the function that contains the given parameter |
| */ |
| FunctionBody getFunctionBody(FormalParameter node) { |
| ASTNode parent = node.parent; |
| while (parent != null) { |
| if (parent is ConstructorDeclaration) { |
| return ((parent as ConstructorDeclaration)).body; |
| } else if (parent is FunctionExpression) { |
| return ((parent as FunctionExpression)).body; |
| } else if (parent is MethodDeclaration) { |
| return ((parent as MethodDeclaration)).body; |
| } |
| parent = parent.parent; |
| } |
| return null; |
| } |
| |
| /** |
| * Return `true` if the given token is a token for the given keyword. |
| * |
| * @param token the token being tested |
| * @param keyword the keyword being tested for |
| * @return `true` if the given token is a token for the given keyword |
| */ |
| bool matches(sc.Token token, sc.Keyword keyword) => token != null && identical(token.type, sc.TokenType.KEYWORD) && identical(((token as sc.KeywordToken)).keyword, keyword); |
| |
| /** |
| * Sets the visible source range for formal parameter. |
| */ |
| void setParameterVisibleRange(FormalParameter node, ParameterElementImpl element) { |
| FunctionBody body = getFunctionBody(node); |
| if (body != null) { |
| element.setVisibleRange(body.offset, body.length); |
| } |
| } |
| |
| /** |
| * Make the given holder be the current holder while visiting the given node. |
| * |
| * @param holder the holder that will gather elements that are built while visiting the children |
| * @param node the node to be visited |
| */ |
| void visit(ElementHolder holder, ASTNode node) { |
| if (node != null) { |
| ElementHolder previousHolder = _currentHolder; |
| _currentHolder = holder; |
| try { |
| node.accept(this); |
| } finally { |
| _currentHolder = previousHolder; |
| } |
| } |
| } |
| |
| /** |
| * Make the given holder be the current holder while visiting the children of the given node. |
| * |
| * @param holder the holder that will gather elements that are built while visiting the children |
| * @param node the node whose children are to be visited |
| */ |
| void visitChildren(ElementHolder holder, ASTNode node) { |
| if (node != null) { |
| ElementHolder previousHolder = _currentHolder; |
| _currentHolder = holder; |
| try { |
| node.visitChildren(this); |
| } finally { |
| _currentHolder = previousHolder; |
| } |
| } |
| } |
| } |
| /** |
| * Instances of the class `ElementHolder` hold on to elements created while traversing an AST |
| * structure so that they can be accessed when creating their enclosing element. |
| * |
| * @coverage dart.engine.resolver |
| */ |
| class ElementHolder { |
| List<PropertyAccessorElement> _accessors; |
| List<ConstructorElement> _constructors; |
| List<FieldElement> _fields; |
| List<FunctionElement> _functions; |
| List<LabelElement> _labels; |
| List<VariableElement> _localVariables; |
| List<MethodElement> _methods; |
| List<ParameterElement> _parameters; |
| List<TopLevelVariableElement> _topLevelVariables; |
| List<ClassElement> _types; |
| List<FunctionTypeAliasElement> _typeAliases; |
| List<TypeParameterElement> _typeParameters; |
| void addAccessor(PropertyAccessorElement element) { |
| if (_accessors == null) { |
| _accessors = new List<PropertyAccessorElement>(); |
| } |
| _accessors.add(element); |
| } |
| void addConstructor(ConstructorElement element) { |
| if (_constructors == null) { |
| _constructors = new List<ConstructorElement>(); |
| } |
| _constructors.add(element); |
| } |
| void addField(FieldElement element) { |
| if (_fields == null) { |
| _fields = new List<FieldElement>(); |
| } |
| _fields.add(element); |
| } |
| void addFunction(FunctionElement element) { |
| if (_functions == null) { |
| _functions = new List<FunctionElement>(); |
| } |
| _functions.add(element); |
| } |
| void addLabel(LabelElement element) { |
| if (_labels == null) { |
| _labels = new List<LabelElement>(); |
| } |
| _labels.add(element); |
| } |
| void addLocalVariable(LocalVariableElement element) { |
| if (_localVariables == null) { |
| _localVariables = new List<VariableElement>(); |
| } |
| _localVariables.add(element); |
| } |
| void addMethod(MethodElement element) { |
| if (_methods == null) { |
| _methods = new List<MethodElement>(); |
| } |
| _methods.add(element); |
| } |
| void addParameter(ParameterElement element) { |
| if (_parameters == null) { |
| _parameters = new List<ParameterElement>(); |
| } |
| _parameters.add(element); |
| } |
| void addTopLevelVariable(TopLevelVariableElement element) { |
| if (_topLevelVariables == null) { |
| _topLevelVariables = new List<TopLevelVariableElement>(); |
| } |
| _topLevelVariables.add(element); |
| } |
| void addType(ClassElement element) { |
| if (_types == null) { |
| _types = new List<ClassElement>(); |
| } |
| _types.add(element); |
| } |
| void addTypeAlias(FunctionTypeAliasElement element) { |
| if (_typeAliases == null) { |
| _typeAliases = new List<FunctionTypeAliasElement>(); |
| } |
| _typeAliases.add(element); |
| } |
| void addTypeParameter(TypeParameterElement element) { |
| if (_typeParameters == null) { |
| _typeParameters = new List<TypeParameterElement>(); |
| } |
| _typeParameters.add(element); |
| } |
| List<PropertyAccessorElement> get accessors { |
| if (_accessors == null) { |
| return PropertyAccessorElementImpl.EMPTY_ARRAY; |
| } |
| List<PropertyAccessorElement> result = new List.from(_accessors); |
| _accessors = null; |
| return result; |
| } |
| List<ConstructorElement> get constructors { |
| if (_constructors == null) { |
| return ConstructorElementImpl.EMPTY_ARRAY; |
| } |
| List<ConstructorElement> result = new List.from(_constructors); |
| _constructors = null; |
| return result; |
| } |
| FieldElement getField(String fieldName) { |
| if (_fields == null) { |
| return null; |
| } |
| for (FieldElement field in _fields) { |
| if (field.name == fieldName) { |
| return field; |
| } |
| } |
| return null; |
| } |
| List<FieldElement> get fields { |
| if (_fields == null) { |
| return FieldElementImpl.EMPTY_ARRAY; |
| } |
| List<FieldElement> result = new List.from(_fields); |
| _fields = null; |
| return result; |
| } |
| List<FunctionElement> get functions { |
| if (_functions == null) { |
| return FunctionElementImpl.EMPTY_ARRAY; |
| } |
| List<FunctionElement> result = new List.from(_functions); |
| _functions = null; |
| return result; |
| } |
| List<LabelElement> get labels { |
| if (_labels == null) { |
| return LabelElementImpl.EMPTY_ARRAY; |
| } |
| List<LabelElement> result = new List.from(_labels); |
| _labels = null; |
| return result; |
| } |
| List<LocalVariableElement> get localVariables { |
| if (_localVariables == null) { |
| return LocalVariableElementImpl.EMPTY_ARRAY; |
| } |
| List<LocalVariableElement> result = new List.from(_localVariables); |
| _localVariables = null; |
| return result; |
| } |
| List<MethodElement> get methods { |
| if (_methods == null) { |
| return MethodElementImpl.EMPTY_ARRAY; |
| } |
| List<MethodElement> result = new List.from(_methods); |
| _methods = null; |
| return result; |
| } |
| List<ParameterElement> get parameters { |
| if (_parameters == null) { |
| return ParameterElementImpl.EMPTY_ARRAY; |
| } |
| List<ParameterElement> result = new List.from(_parameters); |
| _parameters = null; |
| return result; |
| } |
| TopLevelVariableElement getTopLevelVariable(String variableName) { |
| if (_topLevelVariables == null) { |
| return null; |
| } |
| for (TopLevelVariableElement variable in _topLevelVariables) { |
| if (variable.name == variableName) { |
| return variable; |
| } |
| } |
| return null; |
| } |
| List<TopLevelVariableElement> get topLevelVariables { |
| if (_topLevelVariables == null) { |
| return TopLevelVariableElementImpl.EMPTY_ARRAY; |
| } |
| List<TopLevelVariableElement> result = new List.from(_topLevelVariables); |
| _topLevelVariables = null; |
| return result; |
| } |
| List<FunctionTypeAliasElement> get typeAliases { |
| if (_typeAliases == null) { |
| return FunctionTypeAliasElementImpl.EMPTY_ARRAY; |
| } |
| List<FunctionTypeAliasElement> result = new List.from(_typeAliases); |
| _typeAliases = null; |
| return result; |
| } |
| List<TypeParameterElement> get typeParameters { |
| if (_typeParameters == null) { |
| return TypeParameterElementImpl.EMPTY_ARRAY; |
| } |
| List<TypeParameterElement> result = new List.from(_typeParameters); |
| _typeParameters = null; |
| return result; |
| } |
| List<ClassElement> get types { |
| if (_types == null) { |
| return ClassElementImpl.EMPTY_ARRAY; |
| } |
| List<ClassElement> result = new List.from(_types); |
| _types = null; |
| return result; |
| } |
| void validate() { |
| JavaStringBuilder builder = new JavaStringBuilder(); |
| if (_accessors != null) { |
| builder.append(_accessors.length); |
| builder.append(" accessors"); |
| } |
| if (_constructors != null) { |
| if (builder.length > 0) { |
| builder.append("; "); |
| } |
| builder.append(_constructors.length); |
| builder.append(" constructors"); |
| } |
| if (_fields != null) { |
| if (builder.length > 0) { |
| builder.append("; "); |
| } |
| builder.append(_fields.length); |
| builder.append(" fields"); |
| } |
| if (_functions != null) { |
| if (builder.length > 0) { |
| builder.append("; "); |
| } |
| builder.append(_functions.length); |
| builder.append(" functions"); |
| } |
| if (_labels != null) { |
| if (builder.length > 0) { |
| builder.append("; "); |
| } |
| builder.append(_labels.length); |
| builder.append(" labels"); |
| } |
| if (_localVariables != null) { |
| if (builder.length > 0) { |
| builder.append("; "); |
| } |
| builder.append(_localVariables.length); |
| builder.append(" local variables"); |
| } |
| if (_methods != null) { |
| if (builder.length > 0) { |
| builder.append("; "); |
| } |
| builder.append(_methods.length); |
| builder.append(" methods"); |
| } |
| if (_parameters != null) { |
| if (builder.length > 0) { |
| builder.append("; "); |
| } |
| builder.append(_parameters.length); |
| builder.append(" parameters"); |
| } |
| if (_topLevelVariables != null) { |
| if (builder.length > 0) { |
| builder.append("; "); |
| } |
| builder.append(_topLevelVariables.length); |
| builder.append(" top-level variables"); |
| } |
| if (_types != null) { |
| if (builder.length > 0) { |
| builder.append("; "); |
| } |
| builder.append(_types.length); |
| builder.append(" types"); |
| } |
| if (_typeAliases != null) { |
| if (builder.length > 0) { |
| builder.append("; "); |
| } |
| builder.append(_typeAliases.length); |
| builder.append(" type aliases"); |
| } |
| if (_typeParameters != null) { |
| if (builder.length > 0) { |
| builder.append("; "); |
| } |
| builder.append(_typeParameters.length); |
| builder.append(" type parameters"); |
| } |
| if (builder.length > 0) { |
| AnalysisEngine.instance.logger.logError("Failed to capture elements: ${builder.toString()}"); |
| } |
| } |
| } |
| /** |
| * Instances of the class `HtmlUnitBuilder` build an element model for a single HTML unit. |
| */ |
| class HtmlUnitBuilder implements ht.XmlVisitor<Object> { |
| static String _APPLICATION_DART_IN_DOUBLE_QUOTES = "\"application/dart\""; |
| static String _APPLICATION_DART_IN_SINGLE_QUOTES = "'application/dart'"; |
| static String _SCRIPT = "script"; |
| static String _SRC = "src"; |
| static String _TYPE = "type"; |
| |
| /** |
| * The analysis context in which the element model will be built. |
| */ |
| InternalAnalysisContext _context; |
| |
| /** |
| * The error listener to which errors will be reported. |
| */ |
| RecordingErrorListener errorListener; |
| |
| /** |
| * The modification time of the source for which an element is being built. |
| */ |
| int _modificationStamp = 0; |
| |
| /** |
| * The line information associated with the source for which an element is being built, or |
| * `null` if we are not building an element. |
| */ |
| LineInfo _lineInfo; |
| |
| /** |
| * The HTML element being built. |
| */ |
| HtmlElementImpl _htmlElement; |
| |
| /** |
| * The elements in the path from the HTML unit to the current tag node. |
| */ |
| List<ht.XmlTagNode> _parentNodes; |
| |
| /** |
| * The script elements being built. |
| */ |
| List<HtmlScriptElement> _scripts; |
| |
| /** |
| * A set of the libraries that were resolved while resolving the HTML unit. |
| */ |
| final Set<Library> resolvedLibraries = new Set<Library>(); |
| |
| /** |
| * Initialize a newly created HTML unit builder. |
| * |
| * @param context the analysis context in which the element model will be built |
| */ |
| HtmlUnitBuilder(InternalAnalysisContext context) { |
| this._context = context; |
| this.errorListener = new RecordingErrorListener(); |
| } |
| |
| /** |
| * Build the HTML element for the given source. |
| * |
| * @param source the source describing the compilation unit |
| * @return the HTML element that was built |
| * @throws AnalysisException if the analysis could not be performed |
| */ |
| HtmlElementImpl buildHtmlElement(Source source) => buildHtmlElement2(source, source.modificationStamp, _context.parseHtmlUnit(source)); |
| |
| /** |
| * Build the HTML element for the given source. |
| * |
| * @param source the source describing the compilation unit |
| * @param modificationStamp the modification time of the source for which an element is being |
| * built |
| * @param unit the AST structure representing the HTML |
| * @throws AnalysisException if the analysis could not be performed |
| */ |
| HtmlElementImpl buildHtmlElement2(Source source, int modificationStamp, ht.HtmlUnit unit) { |
| this._modificationStamp = modificationStamp; |
| _lineInfo = _context.computeLineInfo(source); |
| HtmlElementImpl result = new HtmlElementImpl(_context, source.shortName); |
| result.source = source; |
| _htmlElement = result; |
| unit.accept(this); |
| _htmlElement = null; |
| unit.element = result; |
| return result; |
| } |
| Object visitHtmlUnit(ht.HtmlUnit node) { |
| _parentNodes = new List<ht.XmlTagNode>(); |
| _scripts = new List<HtmlScriptElement>(); |
| try { |
| node.visitChildren(this); |
| _htmlElement.scripts = new List.from(_scripts); |
| } finally { |
| _scripts = null; |
| _parentNodes = null; |
| } |
| return null; |
| } |
| Object visitXmlAttributeNode(ht.XmlAttributeNode node) => null; |
| Object visitXmlTagNode(ht.XmlTagNode node) { |
| if (_parentNodes.contains(node)) { |
| JavaStringBuilder builder = new JavaStringBuilder(); |
| builder.append("Found circularity in XML nodes: "); |
| bool first = true; |
| for (ht.XmlTagNode pathNode in _parentNodes) { |
| if (first) { |
| first = false; |
| } else { |
| builder.append(", "); |
| } |
| String tagName = pathNode.tag.lexeme; |
| if (identical(pathNode, node)) { |
| builder.append("*"); |
| builder.append(tagName); |
| builder.append("*"); |
| } else { |
| builder.append(tagName); |
| } |
| } |
| AnalysisEngine.instance.logger.logError(builder.toString()); |
| return null; |
| } |
| _parentNodes.add(node); |
| try { |
| if (isScriptNode(node)) { |
| Source htmlSource = _htmlElement.source; |
| ht.XmlAttributeNode scriptAttribute = getScriptSourcePath(node); |
| String scriptSourcePath = scriptAttribute == null ? null : scriptAttribute.text; |
| if (identical(node.attributeEnd.type, ht.TokenType.GT) && scriptSourcePath == null) { |
| EmbeddedHtmlScriptElementImpl script = new EmbeddedHtmlScriptElementImpl(node); |
| String contents = node.content; |
| int attributeEnd = node.attributeEnd.end; |
| LineInfo_Location location = _lineInfo.getLocation(attributeEnd); |
| sc.StringScanner scanner = new sc.StringScanner(htmlSource, contents, errorListener); |
| scanner.setSourceStart(location.lineNumber, location.columnNumber, attributeEnd); |
| sc.Token firstToken = scanner.tokenize(); |
| List<int> lineStarts = scanner.lineStarts; |
| Parser parser = new Parser(htmlSource, errorListener); |
| CompilationUnit unit = parser.parseCompilationUnit(firstToken); |
| unit.lineInfo = new LineInfo(lineStarts); |
| try { |
| LibraryResolver resolver = new LibraryResolver(_context); |
| LibraryElementImpl library = resolver.resolveEmbeddedLibrary(htmlSource, _modificationStamp, unit, true) as LibraryElementImpl; |
| script.scriptLibrary = library; |
| resolvedLibraries.addAll(resolver.resolvedLibraries); |
| errorListener.addAll(resolver.errorListener); |
| } on AnalysisException catch (exception) { |
| AnalysisEngine.instance.logger.logError3(exception); |
| } |
| _scripts.add(script); |
| } else { |
| ExternalHtmlScriptElementImpl script = new ExternalHtmlScriptElementImpl(node); |
| if (scriptSourcePath != null) { |
| try { |
| scriptSourcePath = Uri.encodeFull(scriptSourcePath); |
| parseUriWithException(scriptSourcePath); |
| Source scriptSource = _context.sourceFactory.resolveUri(htmlSource, scriptSourcePath); |
| script.scriptSource = scriptSource; |
| if (scriptSource == null || !scriptSource.exists()) { |
| reportValueError(HtmlWarningCode.URI_DOES_NOT_EXIST, scriptAttribute, [scriptSourcePath]); |
| } |
| } on URISyntaxException catch (exception) { |
| reportValueError(HtmlWarningCode.INVALID_URI, scriptAttribute, [scriptSourcePath]); |
| } |
| } |
| _scripts.add(script); |
| } |
| } else { |
| node.visitChildren(this); |
| } |
| } finally { |
| _parentNodes.remove(node); |
| } |
| return null; |
| } |
| |
| /** |
| * Return the first source attribute for the given tag node, or `null` if it does not exist. |
| * |
| * @param node the node containing attributes |
| * @return the source attribute contained in the given tag |
| */ |
| ht.XmlAttributeNode getScriptSourcePath(ht.XmlTagNode node) { |
| for (ht.XmlAttributeNode attribute in node.attributes) { |
| if (attribute.name.lexeme == _SRC) { |
| return attribute; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Determine if the specified node is a Dart script. |
| * |
| * @param node the node to be tested (not `null`) |
| * @return `true` if the node is a Dart script |
| */ |
| bool isScriptNode(ht.XmlTagNode node) { |
| if (node.tagNodes.length != 0 || node.tag.lexeme != _SCRIPT) { |
| return false; |
| } |
| for (ht.XmlAttributeNode attribute in node.attributes) { |
| if (attribute.name.lexeme == _TYPE) { |
| ht.Token valueToken = attribute.value; |
| if (valueToken != null) { |
| String value = valueToken.lexeme; |
| if (value == _APPLICATION_DART_IN_DOUBLE_QUOTES || value == _APPLICATION_DART_IN_SINGLE_QUOTES) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Report an error with the given error code at the given location. Use the given arguments to |
| * compose the error message. |
| * |
| * @param errorCode the error code of the error to be reported |
| * @param offset the offset of the first character to be highlighted |
| * @param length the number of characters to be highlighted |
| * @param arguments the arguments used to compose the error message |
| */ |
| void reportError(ErrorCode errorCode, int offset, int length, List<Object> arguments) { |
| errorListener.onError(new AnalysisError.con2(_htmlElement.source, offset, length, errorCode, arguments)); |
| } |
| |
| /** |
| * Report an error with the given error code at the location of the value of the given attribute. |
| * Use the given arguments to compose the error message. |
| * |
| * @param errorCode the error code of the error to be reported |
| * @param offset the offset of the first character to be highlighted |
| * @param length the number of characters to be highlighted |
| * @param arguments the arguments used to compose the error message |
| */ |
| void reportValueError(ErrorCode errorCode, ht.XmlAttributeNode attribute, List<Object> arguments) { |
| int offset = attribute.value.offset + 1; |
| int length = attribute.value.length - 2; |
| reportError(errorCode, offset, length, arguments); |
| } |
| } |
| /** |
| * Instances of the class `BestPracticesVerifier` traverse an AST structure looking for |
| * violations of Dart best practices. |
| * |
| * @coverage dart.engine.resolver |
| */ |
| class BestPracticesVerifier extends RecursiveASTVisitor<Object> { |
| static String _GETTER = "getter"; |
| static String _HASHCODE_GETTER_NAME = "hashCode"; |
| static String _METHOD = "method"; |
| static String _NULL_TYPE_NAME = "Null"; |
| static String _SETTER = "setter"; |
| static String _TO_INT_METHOD_NAME = "toInt"; |
| |
| /** |
| * Given a parenthesized expression, this returns the parent (or recursively grand-parent) of the |
| * expression that is a parenthesized expression, but whose parent is not a parenthesized |
| * expression. |
| * |
| * For example given the code `(((e)))`: `(e) -> (((e)))`. |
| * |
| * @param parenthesizedExpression some expression whose parent is a parenthesized expression |
| * @return the first parent or grand-parent that is a parenthesized expression, that does not have |
| * a parenthesized expression parent |
| */ |
| static ParenthesizedExpression wrapParenthesizedExpression(ParenthesizedExpression parenthesizedExpression) { |
| if (parenthesizedExpression.parent is ParenthesizedExpression) { |
| return wrapParenthesizedExpression(parenthesizedExpression.parent as ParenthesizedExpression); |
| } |
| return parenthesizedExpression; |
| } |
| |
| /** |
| * The class containing the AST nodes being visited, or `null` if we are not in the scope of |
| * a class. |
| */ |
| ClassElement _enclosingClass; |
| |
| /** |
| * The error reporter by which errors will be reported. |
| */ |
| ErrorReporter _errorReporter; |
| |
| /** |
| * Create a new instance of the [BestPracticesVerifier]. |
| * |
| * @param errorReporter the error reporter |
| */ |
| BestPracticesVerifier(ErrorReporter errorReporter) { |
| this._errorReporter = errorReporter; |
| } |
| Object visitAsExpression(AsExpression node) { |
| checkForUnnecessaryCast(node); |
| return super.visitAsExpression(node); |
| } |
| Object visitBinaryExpression(BinaryExpression node) { |
| checkForDivisionOptimizationHint(node); |
| return super.visitBinaryExpression(node); |
| } |
| Object visitClassDeclaration(ClassDeclaration node) { |
| ClassElement outerClass = _enclosingClass; |
| try { |
| _enclosingClass = node.element; |
| return super.visitClassDeclaration(node); |
| } finally { |
| _enclosingClass = outerClass; |
| } |
| } |
| Object visitIsExpression(IsExpression node) { |
| checkAllTypeChecks(node); |
| return super.visitIsExpression(node); |
| } |
| Object visitMethodDeclaration(MethodDeclaration node) { |
| checkForOverridingPrivateMember(node); |
| return super.visitMethodDeclaration(node); |
| } |
| |
| /** |
| * Check for the passed is expression for the unnecessary type check hint codes as well as null |
| * checks expressed using an is expression. |
| * |
| * @param node the is expression to check |
| * @return `true` if and only if a hint code is generated on the passed node |
| * @see HintCode#TYPE_CHECK_IS_NOT_NULL |
| * @see HintCode#TYPE_CHECK_IS_NULL |
| * @see HintCode#UNNECESSARY_TYPE_CHECK_TRUE |
| * @see HintCode#UNNECESSARY_TYPE_CHECK_FALSE |
| */ |
| bool checkAllTypeChecks(IsExpression node) { |
| Expression expression = node.expression; |
| TypeName typeName = node.type; |
| Type2 lhsType = expression.staticType; |
| Type2 rhsType = typeName.type; |
| if (lhsType == null || rhsType == null) { |
| return false; |
| } |
| String rhsNameStr = typeName.name.name; |
| if ((rhsType.isDynamic && rhsNameStr == sc.Keyword.DYNAMIC.syntax)) { |
| if (node.notOperator == null) { |
| _errorReporter.reportError2(HintCode.UNNECESSARY_TYPE_CHECK_TRUE, node, []); |
| } else { |
| _errorReporter.reportError2(HintCode.UNNECESSARY_TYPE_CHECK_FALSE, node, []); |
| } |
| return true; |
| } |
| Element rhsElement = rhsType.element; |
| LibraryElement libraryElement = rhsElement != null ? rhsElement.library : null; |
| if (libraryElement != null && libraryElement.isDartCore) { |
| if (rhsType.isObject || (expression is NullLiteral && rhsNameStr == _NULL_TYPE_NAME)) { |
| if (node.notOperator == null) { |
| _errorReporter.reportError2(HintCode.UNNECESSARY_TYPE_CHECK_TRUE, node, []); |
| } else { |
| _errorReporter.reportError2(HintCode.UNNECESSARY_TYPE_CHECK_FALSE, node, []); |
| } |
| return true; |
| } else if (rhsNameStr == _NULL_TYPE_NAME) { |
| if (node.notOperator == null) { |
| _errorReporter.reportError2(HintCode.TYPE_CHECK_IS_NULL, node, []); |
| } else { |
| _errorReporter.reportError2(HintCode.TYPE_CHECK_IS_NOT_NULL, node, []); |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Check for the passed binary expression for the [HintCode#DIVISION_OPTIMIZATION]. |
| * |
| * @param node the binary expression to check |
| * @return `true` if and only if a hint code is generated on the passed node |
| * @see HintCode#DIVISION_OPTIMIZATION |
| */ |
| bool checkForDivisionOptimizationHint(BinaryExpression node) { |
| if (node.operator.type != sc.TokenType.SLASH) { |
| return false; |
| } |
| MethodElement methodElement = node.bestElement; |
| if (methodElement == null) { |
| return false; |
| } |
| LibraryElement libraryElement = methodElement.library; |
| if (libraryElement != null && !libraryElement.isDartCore) { |
| return false; |
| } |
| if (node.parent is ParenthesizedExpression) { |
| ParenthesizedExpression parenthesizedExpression = wrapParenthesizedExpression(node.parent as ParenthesizedExpression); |
| if (parenthesizedExpression.parent is MethodInvocation) { |
| MethodInvocation methodInvocation = parenthesizedExpression.parent as MethodInvocation; |
| if (_TO_INT_METHOD_NAME == methodInvocation.methodName.name && methodInvocation.argumentList.arguments.isEmpty) { |
| _errorReporter.reportError2(HintCode.DIVISION_OPTIMIZATION, methodInvocation, []); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Check for the passed class declaration for the |
| * [HintCode#OVERRIDE_EQUALS_BUT_NOT_HASH_CODE] hint code. |
| * |
| * @param node the class declaration to check |
| * @return `true` if and only if a hint code is generated on the passed node |
| * @see HintCode#OVERRIDE_EQUALS_BUT_NOT_HASH_CODE |
| */ |
| bool checkForOverrideEqualsButNotHashCode(ClassDeclaration node) { |
| ClassElement classElement = node.element; |
| if (classElement == null) { |
| return false; |
| } |
| MethodElement equalsOperatorMethodElement = classElement.getMethod(sc.TokenType.EQ_EQ.lexeme); |
| if (equalsOperatorMethodElement != null) { |
| PropertyAccessorElement hashCodeElement = classElement.getGetter(_HASHCODE_GETTER_NAME); |
| if (hashCodeElement == null) { |
| _errorReporter.reportError2(HintCode.OVERRIDE_EQUALS_BUT_NOT_HASH_CODE, node.name, [classElement.displayName]); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Check for the passed class declaration for the |
| * [HintCode#OVERRIDE_EQUALS_BUT_NOT_HASH_CODE] hint code. |
| * |
| * @param node the class declaration to check |
| * @return `true` if and only if a hint code is generated on the passed node |
| * @see HintCode#OVERRIDDING_PRIVATE_MEMBER |
| */ |
| bool checkForOverridingPrivateMember(MethodDeclaration node) { |
| if (_enclosingClass == null) { |
| return false; |
| } |
| if (!Identifier.isPrivateName(node.name.name)) { |
| return false; |
| } |
| ExecutableElement executableElement = node.element; |
| if (executableElement == null) { |
| return false; |
| } |
| String elementName = executableElement.name; |
| bool isGetterOrSetter = executableElement is PropertyAccessorElement; |
| InterfaceType superType = _enclosingClass.supertype; |
| if (superType == null) { |
| return false; |
| } |
| ClassElement classElement = superType.element; |
| while (classElement != null) { |
| if (_enclosingClass.library != classElement.library) { |
| if (isGetterOrSetter) { |
| PropertyAccessorElement overriddenAccessor = null; |
| List<PropertyAccessorElement> accessors = classElement.accessors; |
| for (PropertyAccessorElement propertyAccessorElement in accessors) { |
| if (elementName == propertyAccessorElement.name) { |
| overriddenAccessor = propertyAccessorElement; |
| break; |
| } |
| } |
| if (overriddenAccessor != null) { |
| String memberType = ((executableElement as PropertyAccessorElement)).isGetter ? _GETTER : _SETTER; |
| _errorReporter.reportError2(HintCode.OVERRIDDING_PRIVATE_MEMBER, node.name, [ |
| memberType, |
| executableElement.displayName, |
| classElement.displayName]); |
| return true; |
| } |
| } else { |
| MethodElement overriddenMethod = classElement.getMethod(elementName); |
| if (overriddenMethod != null) { |
| _errorReporter.reportError2(HintCode.OVERRIDDING_PRIVATE_MEMBER, node.name, [ |
| _METHOD, |
| executableElement.displayName, |
| classElement.displayName]); |
| return true; |
| } |
| } |
| } |
| superType = classElement.supertype; |
| classElement = superType != null ? superType.element : null; |
| } |
| return false; |
| } |
| |
| /** |
| * Check for the passed as expression for the [HintCode#UNNECESSARY_CAST] hint code. |
| * |
| * @param node the as expression to check |
| * @return `true` if and only if a hint code is generated on the passed node |
| * @see HintCode#UNNECESSARY_CAST |
| */ |
| bool checkForUnnecessaryCast(AsExpression node) { |
| Expression expression = node.expression; |
| TypeName typeName = node.type; |
| Type2 lhsType = expression.staticType; |
| Type2 rhsType = typeName.type; |
| if (lhsType != null && rhsType != null && !lhsType.isDynamic && !rhsType.isDynamic && lhsType.isSubtypeOf(rhsType)) { |
| _errorReporter.reportError2(HintCode.UNNECESSARY_CAST, node, []); |
| return true; |
| } |
| return false; |
| } |
| } |
| /** |
| * Instances of the class `Dart2JSVerifier` traverse an AST structure looking for hints for |
| * code that will be compiled to JS, such as [HintCode#IS_DOUBLE]. |
| * |
| * @coverage dart.engine.resolver |
| */ |
| class Dart2JSVerifier extends RecursiveASTVisitor<Object> { |
| |
| /** |
| * The error reporter by which errors will be reported. |
| */ |
| ErrorReporter _errorReporter; |
| |
| /** |
| * The name of the `double` type. |
| */ |
| static String _DOUBLE_TYPE_NAME = "double"; |
| |
| /** |
| * Create a new instance of the [Dart2JSVerifier]. |
| * |
| * @param errorReporter the error reporter |
| */ |
| Dart2JSVerifier(ErrorReporter errorReporter) { |
| this._errorReporter = errorReporter; |
| } |
| Object visitIsExpression(IsExpression node) { |
| checkForIsDoubleHints(node); |
| return super.visitIsExpression(node); |
| } |
| |
| /** |
| * Check for instances of `x is double`, `x is int`, `x is! double` and |
| * `x is! int`. |
| * |
| * @param node the is expression to check |
| * @return `true` if and only if a hint code is generated on the passed node |
| * @see HintCode#IS_DOUBLE |
| * @see HintCode#IS_INT |
| * @see HintCode#IS_NOT_DOUBLE |
| * @see HintCode#IS_NOT_INT |
| */ |
| bool checkForIsDoubleHints(IsExpression node) { |
| TypeName typeName = node.type; |
| Type2 type = typeName.type; |
| if (type != null && type.element != null) { |
| Element element = type.element; |
| String typeNameStr = element.name; |
| LibraryElement libraryElement = element.library; |
| if (typeNameStr == _DOUBLE_TYPE_NAME && libraryElement != null && libraryElement.isDartCore) { |
| if (node.notOperator == null) { |
| _errorReporter.reportError2(HintCode.IS_DOUBLE, node, []); |
| } else { |
| _errorReporter.reportError2(HintCode.IS_NOT_DOUBLE, node, []); |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| /** |
| * Instances of the class `DeadCodeVerifier` traverse an AST structure looking for cases of |
| * [HintCode#DEAD_CODE]. |
| * |
| * @coverage dart.engine.resolver |
| */ |
| class DeadCodeVerifier extends RecursiveASTVisitor<Object> { |
| |
| /** |
| * The error reporter by which errors will be reported. |
| */ |
| ErrorReporter _errorReporter; |
| |
| /** |
| * Create a new instance of the [DeadCodeVerifier]. |
| * |
| * @param errorReporter the error reporter |
| */ |
| DeadCodeVerifier(ErrorReporter errorReporter) { |
| this._errorReporter = errorReporter; |
| } |
| Object visitBinaryExpression(BinaryExpression node) { |
| sc.Token operator = node.operator; |
| bool isAmpAmp = identical(operator.type, sc.TokenType.AMPERSAND_AMPERSAND); |
| bool isBarBar = identical(operator.type, sc.TokenType.BAR_BAR); |
| if (isAmpAmp || isBarBar) { |
| Expression lhsCondition = node.leftOperand; |
| if (!isDebugConstant(lhsCondition)) { |
| ValidResult lhsResult = getConstantBooleanValue(lhsCondition); |
| if (lhsResult != null) { |
| if (identical(lhsResult, ValidResult.RESULT_TRUE) && isBarBar) { |
| _errorReporter.reportError2(HintCode.DEAD_CODE, node.rightOperand, []); |
| safelyVisit(lhsCondition); |
| return null; |
| } else if (identical(lhsResult, ValidResult.RESULT_FALSE) && isAmpAmp) { |
| _errorReporter.reportError2(HintCode.DEAD_CODE, node.rightOperand, []); |
| safelyVisit(lhsCondition); |
| return null; |
| } |
| } |
| } |
| } |
| return super.visitBinaryExpression(node); |
| } |
| |
| /** |
| * For each [Block], this method reports and error on all statements between the end of the |
| * block and the first return statement (assuming there it is not at the end of the block.) |
| * |
| * @param node the block to evaluate |
| */ |
| Object visitBlock(Block node) { |
| NodeList<Statement> statements = node.statements; |
| int size = statements.length; |
| for (int i = 0; i < size; i++) { |
| Statement currentStatement = statements[i]; |
| safelyVisit(currentStatement); |
| if (currentStatement is ReturnStatement && i != size - 1) { |
| Statement nextStatement = statements[i + 1]; |
| Statement lastStatement = statements[size - 1]; |
| int offset = nextStatement.offset; |
| int length = lastStatement.end - offset; |
| _errorReporter.reportError3(HintCode.DEAD_CODE, offset, length, []); |
| return null; |
| } |
| } |
| return null; |
| } |
| Object visitConditionalExpression(ConditionalExpression node) { |
| Expression conditionExpression = node.condition; |
| safelyVisit(conditionExpression); |
| if (!isDebugConstant(conditionExpression)) { |
| ValidResult result = getConstantBooleanValue(conditionExpression); |
| if (result != null) { |
| if (identical(result, ValidResult.RESULT_TRUE)) { |
| _errorReporter.reportError2(HintCode.DEAD_CODE, node.elseExpression, []); |
| safelyVisit(node.thenExpression); |
| return null; |
| } else { |
| _errorReporter.reportError2(HintCode.DEAD_CODE, node.thenExpression, []); |
| safelyVisit(node.elseExpression); |
| return null; |
| } |
| } |
| } |
| return super.visitConditionalExpression(node); |
| } |
| Object visitIfStatement(IfStatement node) { |
| Expression conditionExpression = node.condition; |
| safelyVisit(conditionExpression); |
| if (!isDebugConstant(conditionExpression)) { |
| ValidResult result = getConstantBooleanValue(conditionExpression); |
| if (result != null) { |
| if (identical(result, ValidResult.RESULT_TRUE)) { |
| Statement elseStatement = node.elseStatement; |
| if (elseStatement != null) { |
| _errorReporter.reportError2(HintCode.DEAD_CODE, elseStatement, []); |
| safelyVisit(node.thenStatement); |
| return null; |
| } |
| } else { |
| _errorReporter.reportError2(HintCode.DEAD_CODE, node.thenStatement, []); |
| safelyVisit(node.elseStatement); |
| return null; |
| } |
| } |
| } |
| return super.visitIfStatement(node); |
| } |
| Object visitTryStatement(TryStatement node) { |
| safelyVisit(node.body); |
| safelyVisit(node.finallyBlock); |
| NodeList<CatchClause> catchClauses = node.catchClauses; |
| int numOfCatchClauses = catchClauses.length; |
| List<Type2> visitedTypes = new List<Type2>(); |
| for (int i = 0; i < numOfCatchClauses; i++) { |
| CatchClause catchClause = catchClauses[i]; |
| if (catchClause.onKeyword != null) { |
| TypeName typeName = catchClause.exceptionType; |
| if (typeName != null && typeName.type != null) { |
| Type2 currentType = typeName.type; |
| if (currentType.isObject) { |
| safelyVisit(catchClause); |
| if (i + 1 != numOfCatchClauses) { |
| CatchClause nextCatchClause = catchClauses[i + 1]; |
| CatchClause lastCatchClause = catchClauses[numOfCatchClauses - 1]; |
| int offset = nextCatchClause.offset; |
| int length = lastCatchClause.end - offset; |
| _errorReporter.reportError3(HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH, offset, length, []); |
| return null; |
| } |
| } |
| for (Type2 type in visitedTypes) { |
| if (currentType.isSubtypeOf(type)) { |
| CatchClause lastCatchClause = catchClauses[numOfCatchClauses - 1]; |
| int offset = catchClause.offset; |
| int length = lastCatchClause.end - offset; |
| _errorReporter.reportError3(HintCode.DEAD_CODE_ON_CATCH_SUBTYPE, offset, length, [currentType.displayName, type.displayName]); |
| return null; |
| } |
| } |
| visitedTypes.add(currentType); |
| } |
| safelyVisit(catchClause); |
| } else { |
| safelyVisit(catchClause); |
| if (i + 1 != numOfCatchClauses) { |
| CatchClause nextCatchClause = catchClauses[i + 1]; |
| CatchClause lastCatchClause = catchClauses[numOfCatchClauses - 1]; |
| int offset = nextCatchClause.offset; |
| int length = lastCatchClause.end - offset; |
| _errorReporter.reportError3(HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH, offset, length, []); |
| return null; |
| } |
| } |
| } |
| return null; |
| } |
| Object visitWhileStatement(WhileStatement node) { |
| Expression conditionExpression = node.condition; |
| safelyVisit(conditionExpression); |
| if (!isDebugConstant(conditionExpression)) { |
| ValidResult result = getConstantBooleanValue(conditionExpression); |
| if (result != null) { |
| if (identical(result, ValidResult.RESULT_FALSE)) { |
| _errorReporter.reportError2(HintCode.DEAD_CODE, node.body, []); |
| return null; |
| } |
| } |
| } |
| safelyVisit(node.body); |
| return null; |
| } |
| |
| /** |
| * Given some [Expression], this method returns [ValidResult#RESULT_TRUE] if it is |
| * `true`, [ValidResult#RESULT_FALSE] if it is `false`, or `null` if the |
| * expression is not a constant boolean value. |
| * |
| * @param expression the expression to evaluate |
| * @return [ValidResult#RESULT_TRUE] if it is `true`, [ValidResult#RESULT_FALSE] |
| * if it is `false`, or `null` if the expression is not a constant boolean |
| * value |
| */ |
| ValidResult getConstantBooleanValue(Expression expression) { |
| if (expression is BooleanLiteral) { |
| if (((expression as BooleanLiteral)).value) { |
| return ValidResult.RESULT_TRUE; |
| } else { |
| return ValidResult.RESULT_FALSE; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Return `true` if and only if the passed expression is resolved to a constant variable. |
| * |
| * @param expression some conditional expression |
| * @return `true` if and only if the passed expression is resolved to a constant variable |
| */ |
| bool isDebugConstant(Expression expression) { |
| Element element = null; |
| if (expression is Identifier) { |
| Identifier identifier = expression as Identifier; |
| element = identifier.staticElement; |
| } else if (expression is PropertyAccess) { |
| PropertyAccess propertyAccess = expression as PropertyAccess; |
| element = propertyAccess.propertyName.staticElement; |
| } |
| if (element is PropertyAccessorElement) { |
| PropertyAccessorElement pae = element as PropertyAccessorElement; |
| PropertyInducingElement variable = pae.variable; |
| return variable != null && variable.isConst; |
| } |
| return false; |
| } |
| |
| /** |
| * If the given node is not `null`, visit this instance of the dead code verifier. |
| * |
| * @param node the node to be visited |
| */ |
| void safelyVisit(ASTNode node) { |
| if (node != null) { |
| node.accept(this); |
| } |
| } |
| } |
| /** |
| * Instances of the class `HintGenerator` traverse a library's worth of dart code at a time to |
| * generate hints over the set of sources. |
| * |
| * @see HintCode |
| * @coverage dart.engine.resolver |
| */ |
| class HintGenerator { |
| List<CompilationUnit> _compilationUnits; |
| AnalysisContext _context; |
| AnalysisErrorListener _errorListener; |
| ImportsVerifier _importsVerifier; |
| bool _enableDart2JSHints = false; |
| HintGenerator(List<CompilationUnit> compilationUnits, AnalysisContext context, AnalysisErrorListener errorListener) { |
| this._compilationUnits = compilationUnits; |
| this._context = context; |
| this._errorListener = errorListener; |
| LibraryElement library = compilationUnits[0].element.library; |
| _importsVerifier = new ImportsVerifier(library); |
| _enableDart2JSHints = context.analysisOptions.dart2jsHint; |
| } |
| void generateForLibrary() { |
| TimeCounter_TimeCounterHandle timeCounter = PerformanceStatistics.hints.start(); |
| for (int i = 0; i < _compilationUnits.length; i++) { |
| CompilationUnitElement element = _compilationUnits[i].element; |
| if (element != null) { |
| if (i == 0) { |
| _importsVerifier.inDefiningCompilationUnit = true; |
| generateForCompilationUnit(_compilationUnits[i], element.source); |
| _importsVerifier.inDefiningCompilationUnit = false; |
| } else { |
| generateForCompilationUnit(_compilationUnits[i], element.source); |
| } |
| } |
| } |
| ErrorReporter definingCompilationUnitErrorReporter = new ErrorReporter(_errorListener, _compilationUnits[0].element.source); |
| _importsVerifier.generateDuplicateImportHints(definingCompilationUnitErrorReporter); |
| _importsVerifier.generateUnusedImportHints(definingCompilationUnitErrorReporter); |
| timeCounter.stop(); |
| } |
| void generateForCompilationUnit(CompilationUnit unit, Source source) { |
| ErrorReporter errorReporter = new ErrorReporter(_errorListener, source); |
| _importsVerifier.visitCompilationUnit(unit); |
| new DeadCodeVerifier(errorReporter).visitCompilationUnit(unit); |
| if (_enableDart2JSHints) { |
| new Dart2JSVerifier(errorReporter).visitCompilationUnit(unit); |
| } |
| new BestPracticesVerifier(errorReporter).visitCompilationUnit(unit); |
| } |
| } |
| /** |
| * Instances of the class `ImportsVerifier` visit all of the referenced libraries in the |
| * source code verifying that all of the imports are used, otherwise a |
| * [HintCode#UNUSED_IMPORT] is generated with |
| * [generateUnusedImportHints]. |
| * |
| * While this class does not yet have support for an "Organize Imports" action, this logic built up |
| * in this class could be used for such an action in the future. |
| * |
| * @coverage dart.engine.resolver |
| */ |
| class ImportsVerifier extends RecursiveASTVisitor<Object> { |
| |
| /** |
| * This is set to `true` if the current compilation unit which is being visited is the |
| * defining compilation unit for the library, its value can be set with |
| * [setInDefiningCompilationUnit]. |
| */ |
| bool _inDefiningCompilationUnit = false; |
| |
| /** |
| * The current library. |
| */ |
| LibraryElement _currentLibrary; |
| |
| /** |
| * A list of [ImportDirective]s that the current library imports, as identifiers are visited |
| * by this visitor and an import has been identified as being used by the library, the |
| * [ImportDirective] is removed from this list. After all the sources in the library have |
| * been evaluated, this list represents the set of unused imports. |
| * |
| * @see ImportsVerifier#generateUnusedImportErrors(ErrorReporter) |
| */ |
| List<ImportDirective> _unusedImports; |
| |
| /** |
| * After the list of [unusedImports] has been computed, this list is a proper subset of the |
| * unused imports that are listed more than once. |
| */ |
| List<ImportDirective> _duplicateImports; |
| |
| /** |
| * This is a map between the set of [LibraryElement]s that the current library imports, and |
| * a list of [ImportDirective]s that imports the library. In cases where the current library |
| * imports a library with a single directive (such as `import lib1.dart;`), the library |
| * element will map to a list of one [ImportDirective], which will then be removed from the |
| * [unusedImports] list. In cases where the current library imports a library with multiple |
| * directives (such as `import lib1.dart; import lib1.dart show C;`), the |
| * [LibraryElement] will be mapped to a list of the import directives, and the namespace |
| * will need to be used to compute the correct [ImportDirective] being used, see |
| * [namespaceMap]. |
| */ |
| Map<LibraryElement, List<ImportDirective>> _libraryMap; |
| |
| /** |
| * In cases where there is more than one import directive per library element, this mapping is |
| * used to determine which of the multiple import directives are used by generating a |
| * [Namespace] for each of the imports to do lookups in the same way that they are done from |
| * the [ElementResolver]. |
| */ |
| Map<ImportDirective, Namespace> _namespaceMap; |
| |
| /** |
| * This is a map between prefix elements and the import directive from which they are derived. In |
| * cases where a type is referenced via a prefix element, the import directive can be marked as |
| * used (removed from the unusedImports) by looking at the resolved `lib` in `lib.X`, |
| * instead of looking at which library the `lib.X` resolves. |
| */ |
| Map<PrefixElement, ImportDirective> _prefixElementMap; |
| |
| /** |
| * Create a new instance of the [ImportsVerifier]. |
| * |
| * @param errorReporter the error reporter |
| */ |
| ImportsVerifier(LibraryElement library) { |
| this._currentLibrary = library; |
| this._unusedImports = new List<ImportDirective>(); |
| this._duplicateImports = new List<ImportDirective>(); |
| this._libraryMap = new Map<LibraryElement, List<ImportDirective>>(); |
| this._namespaceMap = new Map<ImportDirective, Namespace>(); |
| this._prefixElementMap = new Map<PrefixElement, ImportDirective>(); |
| } |
| |
| /** |
| * Any time after the defining compilation unit has been visited by this visitor, this method can |
| * be called to report an [HintCode#DUPLICATE_IMPORT] hint for each of the import directives |
| * in the [duplicateImports] list. |
| * |
| * @param errorReporter the error reporter to report the set of [HintCode#DUPLICATE_IMPORT] |
| * hints to |
| */ |
| void generateDuplicateImportHints(ErrorReporter errorReporter) { |
| for (ImportDirective duplicateImport in _duplicateImports) { |
| errorReporter.reportError2(HintCode.DUPLICATE_IMPORT, duplicateImport.uri, []); |
| } |
| } |
| |
| /** |
| * After all of the compilation units have been visited by this visitor, this method can be called |
| * to report an [HintCode#UNUSED_IMPORT] hint for each of the import directives in the |
| * [unusedImports] list. |
| * |
| * @param errorReporter the error reporter to report the set of [HintCode#UNUSED_IMPORT] |
| * hints to |
| */ |
| void generateUnusedImportHints(ErrorReporter errorReporter) { |
| for (ImportDirective unusedImport in _unusedImports) { |
| Element element = unusedImport.element; |
| if (element is ImportElement) { |
| ImportElement importElement = element as ImportElement; |
| LibraryElement libraryElement = importElement.importedLibrary; |
| if (libraryElement != null && libraryElement.isDartCore) { |
| continue; |
| } |
| } |
| errorReporter.reportError2(HintCode.UNUSED_IMPORT, unusedImport.uri, []); |
| } |
| } |
| Object visitCompilationUnit(CompilationUnit node) { |
| if (_inDefiningCompilationUnit) { |
| NodeList<Directive> directives = node.directives; |
| for (Directive directive in directives) { |
| if (directive is ImportDirective) { |
| ImportDirective importDirective = directive as ImportDirective; |
| LibraryElement libraryElement = importDirective.uriElement; |
| if (libraryElement != null) { |
| _unusedImports.add(importDirective); |
| if (importDirective.asToken != null) { |
| SimpleIdentifier prefixIdentifier = importDirective.prefix; |
| if (prefixIdentifier != null) { |
| Element element = prefixIdentifier.staticElement; |
| if (element is PrefixElement) { |
| PrefixElement prefixElementKey = element as PrefixElement; |
| _prefixElementMap[prefixElementKey] = importDirective; |
| } |
| } |
| } |
| putIntoLibraryMap(libraryElement, importDirective); |
| addAdditionalLibrariesForExports(libraryElement, importDirective, new List<LibraryElement>()); |
| } |
| } |
| } |
| } |
| if (_unusedImports.isEmpty) { |
| return null; |
| } |
| if (_unusedImports.length > 1) { |
| List<ImportDirective> importDirectiveArray = new List.from(_unusedImports); |
| importDirectiveArray.sort(ImportDirective.COMPARATOR); |
| ImportDirective currentDirective = importDirectiveArray[0]; |
| for (int i = 1; i < importDirectiveArray.length; i++) { |
| ImportDirective nextDirective = importDirectiveArray[i]; |
| if (ImportDirective.COMPARATOR(currentDirective, nextDirective) == 0) { |
| if (currentDirective.offset < nextDirective.offset) { |
| _duplicateImports.add(nextDirective); |
| } else { |
| _duplicateImports.add(currentDirective); |
| } |
| } |
| currentDirective = nextDirective; |
| } |
| } |
| return super.visitCompilationUnit(node); |
| } |
| Object visitExportDirective(ExportDirective node) { |
| visitMetadata(node.metadata); |
| return null; |
| } |
| Object visitImportDirective(ImportDirective node) { |
| visitMetadata(node.metadata); |
| return null; |
| } |
| Object visitLibraryDirective(LibraryDirective node) { |
| visitMetadata(node.metadata); |
| return null; |
| } |
| Object visitPrefixedIdentifier(PrefixedIdentifier node) { |
| SimpleIdentifier prefixIdentifier = node.prefix; |
| Element element = prefixIdentifier.staticElement; |
| if (element is PrefixElement) { |
| _unusedImports.remove(_prefixElementMap[element]); |
| return null; |
| } |
| return visitIdentifier(element, prefixIdentifier.name); |
| } |
| Object visitSimpleIdentifier(SimpleIdentifier node) => visitIdentifier(node.staticElement, node.name); |
| void set inDefiningCompilationUnit(bool inDefiningCompilationUnit) { |
| this._inDefiningCompilationUnit = inDefiningCompilationUnit; |
| } |
| |
| /** |
| * Recursively add any exported library elements into the [libraryMap]. |
| */ |
| void addAdditionalLibrariesForExports(LibraryElement library, ImportDirective importDirective, List<LibraryElement> exportPath) { |
| if (exportPath.contains(library)) { |
| return; |
| } |
| exportPath.add(library); |
| for (LibraryElement exportedLibraryElt in library.exportedLibraries) { |
| putIntoLibraryMap(exportedLibraryElt, importDirective); |
| addAdditionalLibrariesForExports(exportedLibraryElt, importDirective, exportPath); |
| } |
| } |
| |
| /** |
| * Lookup and return the [Namespace] from the [namespaceMap], if the map does not |
| * have the computed namespace, compute it and cache it in the map. If the import directive is not |
| * resolved or is not resolvable, `null` is returned. |
| * |
| * @param importDirective the import directive used to compute the returned namespace |
| * @return the computed or looked up [Namespace] |
| */ |
| Namespace computeNamespace(ImportDirective importDirective) { |
| Namespace namespace = _namespaceMap[importDirective]; |
| if (namespace == null) { |
| ImportElement importElement = importDirective.element as ImportElement; |
| if (importElement != null) { |
| NamespaceBuilder builder = new NamespaceBuilder(); |
| namespace = builder.createImportNamespace(importElement); |
| _namespaceMap[importDirective] = namespace; |
| } |
| } |
| return namespace; |
| } |
| |
| /** |
| * The [libraryMap] is a mapping between a library elements and a list of import |
| * directives, but when adding these mappings into the [libraryMap], this method can be |
| * used to simply add the mapping between the library element an an import directive without |
| * needing to check to see if a list needs to be created. |
| */ |
| void putIntoLibraryMap(LibraryElement libraryElement, ImportDirective importDirective) { |
| List<ImportDirective> importList = _libraryMap[libraryElement]; |
| if (importList == null) { |
| importList = new List<ImportDirective>(); |
| _libraryMap[libraryElement] = importList; |
| } |
| importList.add(importDirective); |
| } |
| Object visitIdentifier(Element element, String name) { |
| if (element == null) { |
| return null; |
| } |
| if (element is MultiplyDefinedElement) { |
| MultiplyDefinedElement multiplyDefinedElement = element as MultiplyDefinedElement; |
| for (Element elt in multiplyDefinedElement.conflictingElements) { |
| visitIdentifier(elt, name); |
| } |
| return null; |
| } else if (element is PrefixElement) { |
| _unusedImports.remove(_prefixElementMap[element]); |
| return null; |
| } |
| LibraryElement containingLibrary = element.library; |
| if (containingLibrary == null) { |
| return null; |
| } |
| if (_currentLibrary == containingLibrary) { |
| return null; |
| } |
| List<ImportDirective> importsFromSameLibrary = _libraryMap[containingLibrary]; |
| if (importsFromSameLibrary == null) { |
| return null; |
| } |
| if (importsFromSameLibrary.length == 1) { |
| ImportDirective usedImportDirective = importsFromSameLibrary[0]; |
| _unusedImports.remove(usedImportDirective); |
| } else { |
| for (ImportDirective importDirective in importsFromSameLibrary) { |
| Namespace namespace = computeNamespace(importDirective); |
| if (namespace != null && namespace.get(name) != null) { |
| _unusedImports.remove(importDirective); |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Given some [NodeList] of [Annotation]s, ensure that the identifiers are visited by |
| * this visitor. Specifically, this covers the cases where AST nodes don't have their identifiers |
| * visited by this visitor, but still need their annotations visited. |
| * |
| * @param annotations the list of annotations to visit |
| */ |
| void visitMetadata(NodeList<Annotation> annotations) { |
| for (Annotation annotation in annotations) { |
| Identifier name = annotation.name; |
| visitIdentifier(name.staticElement, name.name); |
| } |
| } |
| } |
| /** |
| * Instances of the class `PubVerifier` traverse an AST structure looking for deviations from |
| * pub best practices. |
| */ |
| class PubVerifier extends RecursiveASTVisitor<Object> { |
| static String _PUBSPEC_YAML = "pubspec.yaml"; |
| |
| /** |
| * The analysis context containing the sources to be analyzed |
| */ |
| AnalysisContext _context; |
| |
| /** |
| * The error reporter by which errors will be reported. |
| */ |
| ErrorReporter _errorReporter; |
| PubVerifier(AnalysisContext context, ErrorReporter errorReporter) { |
| this._context = context; |
| this._errorReporter = errorReporter; |
| } |
| Object visitImportDirective(ImportDirective directive) { |
| return null; |
| } |
| |
| /** |
| * This verifies that the passed file import directive is not contained in a source inside a |
| * package "lib" directory hierarchy referencing a source outside that package "lib" directory |
| * hierarchy. |
| * |
| * @param uriLiteral the import URL (not `null`) |
| * @param path the file path being verified (not `null`) |
| * @return `true` if and only if an error code is generated on the passed node |
| * @see PubSuggestionCode.FILE_IMPORT_INSIDE_LIB_REFERENCES_FILE_OUTSIDE |
| */ |
| bool checkForFileImportInsideLibReferencesFileOutside(StringLiteral uriLiteral, String path) { |
| Source source = getSource(uriLiteral); |
| String fullName = getSourceFullName(source); |
| if (fullName != null) { |
| int pathIndex = 0; |
| int fullNameIndex = fullName.length; |
| while (pathIndex < path.length && JavaString.startsWithBefore(path, "../", pathIndex)) { |
| fullNameIndex = JavaString.lastIndexOf(fullName, '/', fullNameIndex); |
| if (fullNameIndex < 4) { |
| return false; |
| } |
| if (JavaString.startsWithBefore(fullName, "/lib", fullNameIndex - 4)) { |
| String relativePubspecPath = path.substring(0, pathIndex + 3) + _PUBSPEC_YAML; |
| Source pubspecSource = _context.sourceFactory.resolveUri(source, relativePubspecPath); |
| if (pubspecSource != null && pubspecSource.exists()) { |
| _errorReporter.reportError2(PubSuggestionCode.FILE_IMPORT_INSIDE_LIB_REFERENCES_FILE_OUTSIDE, uriLiteral, []); |
| } |
| return true; |
| } |
| pathIndex += 3; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * This verifies that the passed file import directive is not contained in a source outside a |
| * package "lib" directory hierarchy referencing a source inside that package "lib" directory |
| * hierarchy. |
| * |
| * @param uriLiteral the import URL (not `null`) |
| * @param path the file path being verified (not `null`) |
| * @return `true` if and only if an error code is generated on the passed node |
| * @see PubSuggestionCode.FILE_IMPORT_OUTSIDE_LIB_REFERENCES_FILE_INSIDE |
| */ |
| bool checkForFileImportOutsideLibReferencesFileInside(StringLiteral uriLiteral, String path) { |
| if (path.startsWith("lib/")) { |
| if (checkForFileImportOutsideLibReferencesFileInside2(uriLiteral, path, 0)) { |
| return true; |
| } |
| } |
| int pathIndex = path.indexOf("/lib/"); |
| while (pathIndex != -1) { |
| if (checkForFileImportOutsideLibReferencesFileInside2(uriLiteral, path, pathIndex + 1)) { |
| return true; |
| } |
| pathIndex = JavaString.indexOf(path, "/lib/", pathIndex + 4); |
| } |
| return false; |
| } |
| bool checkForFileImportOutsideLibReferencesFileInside2(StringLiteral uriLiteral, String path, int pathIndex) { |
| Source source = getSource(uriLiteral); |
| String relativePubspecPath = path.substring(0, pathIndex) + _PUBSPEC_YAML; |
| Source pubspecSource = _context.sourceFactory.resolveUri(source, relativePubspecPath); |
| if (pubspecSource == null || !pubspecSource.exists()) { |
| return false; |
| } |
| String fullName = getSourceFullName(source); |
| if (fullName != null) { |
| if (!fullName.contains("/lib/")) { |
| _errorReporter.reportError2(PubSuggestionCode.FILE_IMPORT_OUTSIDE_LIB_REFERENCES_FILE_INSIDE, uriLiteral, []); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * This verifies that the passed package import directive does not contain ".." |
| * |
| * @param uriLiteral the import URL (not `null`) |
| * @param path the path to be validated (not `null`) |
| * @return `true` if and only if an error code is generated on the passed node |
| * @see PubSuggestionCode.PACKAGE_IMPORT_CONTAINS_DOT_DOT |
| */ |
| bool checkForPackageImportContainsDotDot(StringLiteral uriLiteral, String path) { |
| if (path.startsWith("../") || path.contains("/../")) { |
| _errorReporter.reportError2(PubSuggestionCode.PACKAGE_IMPORT_CONTAINS_DOT_DOT, uriLiteral, []); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Answer the source associated with the compilation unit containing the given AST node. |
| * |
| * @param node the node (not `null`) |
| * @return the source or `null` if it could not be determined |
| */ |
| Source getSource(ASTNode node) { |
| Source source = null; |
| CompilationUnit unit = node.getAncestor(CompilationUnit); |
| if (unit != null) { |
| CompilationUnitElement element = unit.element; |
| if (element != null) { |
| source = element.source; |
| } |
| } |
| return source; |
| } |
| |
| /** |
| * Answer the full name of the given source. The returned value will have all |
| * [File#separatorChar] replace by '/'. |
| * |
| * @param source the source |
| * @return the full name or `null` if it could not be determined |
| */ |
| String getSourceFullName(Source source) { |
| if (source != null) { |
| String fullName = source.fullName; |
| if (fullName != null) { |
| return fullName.replaceAll(r'\', '/'); |
| } |
| } |
| return null; |
| } |
| } |
| /** |
| * Instances of the class `DeclarationResolver` are used to resolve declarations in an AST |
| * structure to already built elements. |
| */ |
| class DeclarationResolver extends RecursiveASTVisitor<Object> { |
| |
| /** |
| * The compilation unit containing the AST nodes being visited. |
| */ |
| CompilationUnitElement _enclosingUnit; |
| |
| /** |
| * The function type alias containing the AST nodes being visited, or `null` if we are not |
| * in the scope of a function type alias. |
| */ |
| FunctionTypeAliasElement _enclosingAlias; |
| |
| /** |
| * The class containing the AST nodes being visited, or `null` if we are not in the scope of |
| * a class. |
| */ |
| ClassElement _enclosingClass; |
| |
| /** |
| * The method or function containing the AST nodes being visited, or `null` if we are not in |
| * the scope of a method or function. |
| */ |
| ExecutableElement _enclosingExecutable; |
| |
| /** |
| * The parameter containing the AST nodes being visited, or `null` if we are not in the |
| * scope of a parameter. |
| */ |
| ParameterElement _enclosingParameter; |
| |
| /** |
| * Resolve the declarations within the given compilation unit to the elements rooted at the given |
| * element. |
| * |
| * @param unit the compilation unit to be resolved |
| * @param element the root of the element model used to resolve the AST nodes |
| */ |
| void resolve(CompilationUnit unit, CompilationUnitElement element) { |
| _enclosingUnit = element; |
| unit.element = element; |
| unit.accept(this); |
| } |
| Object visitCatchClause(CatchClause node) { |
| SimpleIdentifier exceptionParameter = node.exceptionParameter; |
| if (exceptionParameter != null) { |
| List<LocalVariableElement> localVariables = _enclosingExecutable.localVariables; |
| find3(localVariables, exceptionParameter); |
| SimpleIdentifier stackTraceParameter = node.stackTraceParameter; |
| if (stackTraceParameter != null) { |
| find3(localVariables, stackTraceParameter); |
| } |
| } |
| return super.visitCatchClause(node); |
| } |
| Object visitClassDeclaration(ClassDeclaration node) { |
| ClassElement outerClass = _enclosingClass; |
| try { |
| SimpleIdentifier className = node.name; |
| _enclosingClass = find3(_enclosingUnit.types, className); |
| return super.visitClassDeclaration(node); |
| } finally { |
| _enclosingClass = outerClass; |
| } |
| } |
| Object visitClassTypeAlias(ClassTypeAlias node) { |
| ClassElement outerClass = _enclosingClass; |
| try { |
| SimpleIdentifier className = node.name; |
| _enclosingClass = find3(_enclosingUnit.types, className); |
| return super.visitClassTypeAlias(node); |
| } finally { |
| _enclosingClass = outerClass; |
| } |
| } |
| Object visitConstructorDeclaration(ConstructorDeclaration node) { |
| ExecutableElement outerExecutable = _enclosingExecutable; |
| try { |
| SimpleIdentifier constructorName = node.name; |
| if (constructorName == null) { |
| _enclosingExecutable = _enclosingClass.unnamedConstructor; |
| } else { |
| _enclosingExecutable = _enclosingClass.getNamedConstructor(constructorName.name); |
| constructorName.staticElement = _enclosingExecutable; |
| } |
| node.element = _enclosingExecutable as ConstructorElement; |
| return super.visitConstructorDeclaration(node); |
| } finally { |
| _enclosingExecutable = outerExecutable; |
| } |
| } |
| Object visitDeclaredIdentifier(DeclaredIdentifier node) { |
| SimpleIdentifier variableName = node.identifier; |
| find3(_enclosingExecutable.localVariables, variableName); |
| return super.visitDeclaredIdentifier(node); |
| } |
| Object visitDefaultFormalParameter(DefaultFormalParameter node) { |
| SimpleIdentifier parameterName = node.parameter.identifier; |
| ParameterElement element = getElementForParameter(node, parameterName); |
| Expression defaultValue = node.defaultValue; |
| if (defaultValue != null) { |
| ExecutableElement outerExecutable = _enclosingExecutable; |
| try { |
| if (element == null) { |
| } else { |
| _enclosingExecutable = element.initializer; |
| } |
| defaultValue.accept(this); |
| } finally { |
| _enclosingExecutable = outerExecutable; |
| } |
| } |
| ParameterElement outerParameter = _enclosingParameter; |
| try { |
| _enclosingParameter = element; |
| return super.visitDefaultFormalParameter(node); |
| } finally { |
| _enclosingParameter = outerParameter; |
| } |
| } |
| Object visitExportDirective(ExportDirective node) { |
| String uri = getStringValue(node.uri); |
| if (uri != null) { |
| LibraryElement library = _enclosingUnit.library; |
| ExportElement exportElement = find5(library.exports, _enclosingUnit.context.sourceFactory.resolveUri(_enclosingUnit.source, uri)); |
| node.element = exportElement; |
| } |
| return super.visitExportDirective(node); |
| } |
| Object visitFieldFormalParameter(FieldFormalParameter node) { |
| if (node.parent is! DefaultFormalParameter) { |
| SimpleIdentifier parameterName = node.identifier; |
| ParameterElement element = getElementForParameter(node, parameterName); |
| ParameterElement outerParameter = _enclosingParameter; |
| try { |
| _enclosingParameter = element; |
| return super.visitFieldFormalParameter(node); |
| } finally { |
| _enclosingParameter = outerParameter; |
| } |
| } else { |
| return super.visitFieldFormalParameter(node); |
| } |
| } |
| Object visitFunctionDeclaration(FunctionDeclaration node) { |
| ExecutableElement outerExecutable = _enclosingExecutable; |
| try { |
| SimpleIdentifier functionName = node.name; |
| sc.Token property = node.propertyKeyword; |
| if (property == null) { |
| if (_enclosingExecutable != null) { |
| _enclosingExecutable = find3(_enclosingExecutable.functions, functionName); |
| } else { |
| _enclosingExecutable = find3(_enclosingUnit.functions, functionName); |
| } |
| } else { |
| PropertyAccessorElement accessor = find3(_enclosingUnit.accessors, functionName); |
| if (identical(((property as sc.KeywordToken)).keyword, sc.Keyword.SET)) { |
| accessor = accessor.variable.setter; |
| functionName.staticElement = accessor; |
| } |
| _enclosingExecutable = accessor; |
| } |
| node.functionExpression.element = _enclosingExecutable; |
| return super.visitFunctionDeclaration(node); |
| } finally { |
| _enclosingExecutable = outerExecutable; |
| } |
| } |
| Object visitFunctionExpression(FunctionExpression node) { |
| if (node.parent is! FunctionDeclaration) { |
| FunctionElement element = find2(_enclosingExecutable.functions, node.beginToken.offset); |
| node.element = element; |
| } |
| ExecutableElement outerExecutable = _enclosingExecutable; |
| try { |
| _enclosingExecutable = node.element; |
| return super.visitFunctionExpression(node); |
| } finally { |
| _enclosingExecutable = outerExecutable; |
| } |
| } |
| Object visitFunctionTypeAlias(FunctionTypeAlias node) { |
| FunctionTypeAliasElement outerAlias = _enclosingAlias; |
| try { |
| SimpleIdentifier aliasName = node.name; |
| _enclosingAlias = find3(_enclosingUnit.functionTypeAliases, aliasName); |
| return super.visitFunctionTypeAlias(node); |
| } finally { |
| _enclosingAlias = outerAlias; |
| } |
| } |
| Object visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) { |
| if (node.parent is! DefaultFormalParameter) { |
| SimpleIdentifier parameterName = node.identifier; |
| ParameterElement element = getElementForParameter(node, parameterName); |
| ParameterElement outerParameter = _enclosingParameter; |
| try { |
| _enclosingParameter = element; |
| return super.visitFunctionTypedFormalParameter(node); |
| } finally { |
| _enclosingParameter = outerParameter; |
| } |
| } else { |
| return super.visitFunctionTypedFormalParameter(node); |
| } |
| } |
| Object visitImportDirective(ImportDirective node) { |
| String uri = getStringValue(node.uri); |
| if (uri != null) { |
| LibraryElement library = _enclosingUnit.library; |
| ImportElement importElement = find6(library.imports, _enclosingUnit.context.sourceFactory.resolveUri(_enclosingUnit.source, uri), node.prefix); |
| node.element = importElement; |
| } |
| return super.visitImportDirective(node); |
| } |
| Object visitLabeledStatement(LabeledStatement node) { |
| for (Label label in node.labels) { |
| SimpleIdentifier labelName = label.label; |
| find3(_enclosingExecutable.labels, labelName); |
| } |
| return super.visitLabeledStatement(node); |
| } |
| Object visitLibraryDirective(LibraryDirective node) { |
| node.element = _enclosingUnit.library; |
| return super.visitLibraryDirective(node); |
| } |
| Object visitMethodDeclaration(MethodDeclaration node) { |
| ExecutableElement outerExecutable = _enclosingExecutable; |
| try { |
| sc.Token property = node.propertyKeyword; |
| SimpleIdentifier methodName = node.name; |
| String nameOfMethod = methodName.name; |
| if (nameOfMethod == sc.TokenType.MINUS.lexeme && node.parameters.parameters.length == 0) { |
| nameOfMethod = "unary-"; |
| } |
| if (property == null) { |
| _enclosingExecutable = find4(_enclosingClass.methods, nameOfMethod, methodName.offset); |
| methodName.staticElement = _enclosingExecutable; |
| } else { |
| PropertyAccessorElement accessor = find3(_enclosingClass.accessors, methodName); |
| if (identical(((property as sc.KeywordToken)).keyword, sc.Keyword.SET)) { |
| accessor = accessor.variable.setter; |
| methodName.staticElement = accessor; |
| } |
| _enclosingExecutable = accessor; |
| } |
| return super.visitMethodDeclaration(node); |
| } finally { |
| _enclosingExecutable = outerExecutable; |
| } |
| } |
| Object visitPartDirective(PartDirective node) { |
| String uri = getStringValue(node.uri); |
| if (uri != null) { |
| Source partSource = _enclosingUnit.context.sourceFactory.resolveUri(_enclosingUnit.source, uri); |
| node.element = find(_enclosingUnit.library.parts, partSource); |
| } |
| return super.visitPartDirective(node); |
| } |
| Object visitPartOfDirective(PartOfDirective node) { |
| node.element = _enclosingUnit.library; |
| return super.visitPartOfDirective(node); |
| } |
| Object visitSimpleFormalParameter(SimpleFormalParameter node) { |
| if (node.parent is! DefaultFormalParameter) { |
| SimpleIdentifier parameterName = node.identifier; |
| ParameterElement element = getElementForParameter(node, parameterName); |
| ParameterElement outerParameter = _enclosingParameter; |
| try { |
| _enclosingParameter = element; |
| return super.visitSimpleFormalParameter(node); |
| } finally { |
| _enclosingParameter = outerParameter; |
| } |
| } else { |
| } |
| return super.visitSimpleFormalParameter(node); |
| } |
| Object visitSwitchCase(SwitchCase node) { |
| for (Label label in node.labels) { |
| SimpleIdentifier labelName = label.label; |
| find3(_enclosingExecutable.labels, labelName); |
| } |
| return super.visitSwitchCase(node); |
| } |
| Object visitSwitchDefault(SwitchDefault node) { |
| for (Label label in node.labels) { |
| SimpleIdentifier labelName = label.label; |
| find3(_enclosingExecutable.labels, labelName); |
| } |
| return super.visitSwitchDefault(node); |
| } |
| Object visitTypeParameter(TypeParameter node) { |
| SimpleIdentifier parameterName = node.name; |
| if (_enclosingClass != null) { |
| find3(_enclosingClass.typeParameters, parameterName); |
| } else if (_enclosingAlias != null) { |
| find3(_enclosingAlias.typeParameters, parameterName); |
| } |
| return super.visitTypeParameter(node); |
| } |
| Object visitVariableDeclaration(VariableDeclaration node) { |
| VariableElement element = null; |
| SimpleIdentifier variableName = node.name; |
| if (_enclosingExecutable != null) { |
| element = find3(_enclosingExecutable.localVariables, variableName); |
| } |
| if (element == null && _enclosingClass != null) { |
| element = find3(_enclosingClass.fields, variableName); |
| } |
| if (element == null && _enclosingUnit != null) { |
| element = find3(_enclosingUnit.topLevelVariables, variableName); |
| } |
| Expression initializer = node.initializer; |
| if (initializer != null) { |
| ExecutableElement outerExecutable = _enclosingExecutable; |
| try { |
| if (element == null) { |
| } else { |
| _enclosingExecutable = element.initializer; |
| } |
| return super.visitVariableDeclaration(node); |
| } finally { |
| _enclosingExecutable = outerExecutable; |
| } |
| } |
| return super.visitVariableDeclaration(node); |
| } |
| |
| /** |
| * Return the element for the part with the given source, or `null` if there is no element |
| * for the given source. |
| * |
| * @param parts the elements for the parts |
| * @param partSource the source for the part whose element is to be returned |
| * @return the element for the part with the given source |
| */ |
| CompilationUnitElement find(List<CompilationUnitElement> parts, Source partSource) { |
| for (CompilationUnitElement part in parts) { |
| if (part.source == partSource) { |
| return part; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Return the element in the given array of elements that was created for the declaration at the |
| * given offset. This method should only be used when there is no name |
| * |
| * @param elements the elements of the appropriate kind that exist in the current context |
| * @param offset the offset of the name of the element to be returned |
| * @return the element at the given offset |
| */ |
| Element find2(List<Element> elements, int offset) => find4(elements, "", offset); |
| |
| /** |
| * Return the element in the given array of elements that was created for the declaration with the |
| * given name. |
| * |
| * @param elements the elements of the appropriate kind that exist in the current context |
| * @param identifier the name node in the declaration of the element to be returned |
| * @return the element created for the declaration with the given name |
| */ |
| Element find3(List<Element> elements, SimpleIdentifier identifier) { |
| Element element = find4(elements, identifier.name, identifier.offset); |
| identifier.staticElement = element; |
| return element; |
| } |
| |
| /** |
| * Return the element in the given array of elements that was created for the declaration with the |
| * given name at the given offset. |
| * |
| * @param elements the elements of the appropriate kind that exist in the current context |
| * @param name the name of the element to be returned |
| * @param offset the offset of the name of the element to be returned |
| * @return the element with the given name and offset |
| */ |
| Element find4(List<Element> elements, String name, int offset) { |
| for (Element element in elements) { |
| if (element.displayName == name && element.nameOffset == offset) { |
| return element; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Return the export element from the given array whose library has the given source, or |
| * `null` if there is no such export. |
| * |
| * @param exports the export elements being searched |
| * @param source the source of the library associated with the export element to being searched |
| * for |
| * @return the export element whose library has the given source |
| */ |
| ExportElement find5(List<ExportElement> exports, Source source) { |
| for (ExportElement export in exports) { |
| if (export.exportedLibrary.source == source) { |
| return export; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Return the import element from the given array whose library has the given source and that has |
| * the given prefix, or `null` if there is no such import. |
| * |
| * @param imports the import elements being searched |
| * @param source the source of the library associated with the import element to being searched |
| * for |
| * @param prefix the prefix with which the library was imported |
| * @return the import element whose library has the given source and prefix |
| */ |
| ImportElement find6(List<ImportElement> imports, Source source, SimpleIdentifier prefix) { |
| for (ImportElement element in imports) { |
| if (element.importedLibrary.source == source) { |
| PrefixElement prefixElement = element.prefix; |
| if (prefix == null) { |
| if (prefixElement == null) { |
| return element; |
| } |
| } else { |
| if (prefixElement != null && prefix.name == prefixElement.displayName) { |
| return element; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Search the most closely enclosing list of parameters for a parameter with the given name. |
| * |
| * @param node the node defining the parameter with the given name |
| * @param parameterName the name of the parameter being searched for |
| * @return the element representing the parameter with that name |
| */ |
| ParameterElement getElementForParameter(FormalParameter node, SimpleIdentifier parameterName) { |
| List<ParameterElement> parameters = null; |
| if (_enclosingParameter != null) { |
| parameters = _enclosingParameter.parameters; |
| } |
| if (parameters == null && _enclosingExecutable != null) { |
| parameters = _enclosingExecutable.parameters; |
| } |
| if (parameters == null && _enclosingAlias != null) { |
| parameters = _enclosingAlias.parameters; |
| } |
| ParameterElement element = parameters == null ? null : find3(parameters, parameterName); |
| if (element == null) { |
| PrintStringWriter writer = new PrintStringWriter(); |
| writer.println("Invalid state found in the Analysis Engine:"); |
| writer.println("DeclarationResolver.getElementForParameter() is visiting a parameter that does not appear to be in a method or function."); |
| writer.println("Ancestors:"); |
| ASTNode parent = node.parent; |
| while (parent != null) { |
| writer.println(parent.runtimeType.toString()); |
| writer.println("---------"); |
| parent = parent.parent; |
| } |
| AnalysisEngine.instance.logger.logError2(writer.toString(), new AnalysisException()); |
| } |
| return element; |
| } |
| |
| /** |
| * Return the value of the given string literal, or `null` if the string is not a constant |
| * string without any string interpolation. |
| * |
| * @param literal the string literal whose value is to be returned |
| * @return the value of the given string literal |
| */ |
| String getStringValue(StringLiteral literal) { |
| if (literal is StringInterpolation) { |
| return null; |
| } |
| return literal.stringValue; |
| } |
| } |
| /** |
| * Instances of the class `ElementResolver` are used by instances of [ResolverVisitor] |
| * to resolve references within the AST structure to the elements being referenced. The requirements |
| * for the element resolver are: |
| * <ol> |
| * * Every [SimpleIdentifier] should be resolved to the element to which it refers. |
| * Specifically: |
| * |
| * * An identifier within the declaration of that name should resolve to the element being |
| * declared. |
| * * An identifier denoting a prefix should resolve to the element representing the import that |
| * defines the prefix (an [ImportElement]). |
| * * An identifier denoting a variable should resolve to the element representing the variable (a |
| * [VariableElement]). |
| * * An identifier denoting a parameter should resolve to the element representing the parameter |
| * (a [ParameterElement]). |
| * * An identifier denoting a field should resolve to the element representing the getter or |
| * setter being invoked (a [PropertyAccessorElement]). |
| * * An identifier denoting the name of a method or function being invoked should resolve to the |
| * element representing the method or function (a [ExecutableElement]). |
| * * An identifier denoting a label should resolve to the element representing the label (a |
| * [LabelElement]). |
| * |
| * The identifiers within directives are exceptions to this rule and are covered below. |
| * * Every node containing a token representing an operator that can be overridden ( |
| * [BinaryExpression], [PrefixExpression], [PostfixExpression]) should resolve to |
| * the element representing the method invoked by that operator (a [MethodElement]). |
| * * Every [FunctionExpressionInvocation] should resolve to the element representing the |
| * function being invoked (a [FunctionElement]). This will be the same element as that to |
| * which the name is resolved if the function has a name, but is provided for those cases where an |
| * unnamed function is being invoked. |
| * * Every [LibraryDirective] and [PartOfDirective] should resolve to the element |
| * representing the library being specified by the directive (a [LibraryElement]) unless, in |
| * the case of a part-of directive, the specified library does not exist. |
| * * Every [ImportDirective] and [ExportDirective] should resolve to the element |
| * representing the library being specified by the directive unless the specified library does not |
| * exist (an [ImportElement] or [ExportElement]). |
| * * The identifier representing the prefix in an [ImportDirective] should resolve to the |
| * element representing the prefix (a [PrefixElement]). |
| * * The identifiers in the hide and show combinators in [ImportDirective]s and |
| * [ExportDirective]s should resolve to the elements that are being hidden or shown, |
| * respectively, unless those names are not defined in the specified library (or the specified |
| * library does not exist). |
| * * Every [PartDirective] should resolve to the element representing the compilation unit |
| * being specified by the string unless the specified compilation unit does not exist (a |
| * [CompilationUnitElement]). |
| * </ol> |
| * Note that AST nodes that would represent elements that are not defined are not resolved to |
| * anything. This includes such things as references to undeclared variables (which is an error) and |
| * names in hide and show combinators that are not defined in the imported library (which is not an |
| * error). |
| * |
| * @coverage dart.engine.resolver |
| */ |
| class ElementResolver extends SimpleASTVisitor<Object> { |
| |
| /** |
| * @return `true` if the given identifier is the return type of a constructor declaration. |
| */ |
| static bool isConstructorReturnType(SimpleIdentifier node) { |
| <
|