blob: 4266226bf2030c504ad91e56e2e146813ee75f1e [file] [log] [blame]
// 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_dart.dart';
import 'utilities_general.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();
try {
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;
return element;
} finally {
timeCounter.stop();
}
}
}
/**
* 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 is! TypeParameterType && rhsType is! TypeParameterType && 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();
try {
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);
} finally {
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