Linter Demo Errors: 2Warnings: 105File: /home/fstrocco/Dart/dart/benchmark/analyzer/lib/src/generated/resolver.dart // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. library engine.resolver; import "dart:math" as math; import 'dart:collection'; import 'package:analyzer/src/generated/utilities_collection.dart'; import 'ast.dart'; import 'constant.dart'; import 'element.dart'; import 'element_resolver.dart'; import 'engine.dart'; import 'error.dart'; import 'error_verifier.dart'; import 'html.dart' as ht; import 'java_core.dart'; import 'java_engine.dart'; import 'scanner.dart' as sc; import 'sdk.dart' show DartSdk, SdkLibrary; import 'source.dart'; import 'static_type_analyzer.dart'; import 'utilities_dart.dart'; /** * Callback signature used by ImplicitConstructorBuilder to register * computations to be performed, and their dependencies. A call to this * callback indicates that [computation] may be used to compute implicit * constructors for [classElement], but that the computation may not be invoked * until after implicit constructors have been built for [superclassElement]. */ typedef void ImplicitConstructorBuilderCallback(ClassElement classElement, ClassElement superclassElement, void computation()); typedef LibraryResolver LibraryResolverFactory(AnalysisContext context); typedef ResolverVisitor ResolverVisitorFactory( Library library, Source source, TypeProvider typeProvider); typedef StaticTypeAnalyzer StaticTypeAnalyzerFactory(ResolverVisitor visitor); typedef TypeResolverVisitor TypeResolverVisitorFactory( Library library, Source source, TypeProvider typeProvider); typedef void VoidFunction(); /** * Instances of the class `BestPracticesVerifier` traverse an AST structure looking for * violations of Dart best practices. */ class BestPracticesVerifier extends RecursiveAstVisitor { // static String _HASHCODE_GETTER_NAME = "hashCode"; static String _NULL_TYPE_NAME = "Null"; static String _TO_INT_METHOD_NAME = "toInt"; /** * 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. */ final ErrorReporter _errorReporter; /** * The type Future, which is needed for determining whether it is safe * to have a bare "return;" in an async method. */ final InterfaceType _futureNullType; /** * Create a new instance of the [BestPracticesVerifier]. * * @param errorReporter the error reporter */ BestPracticesVerifier(this._errorReporter, TypeProvider typeProvider) : _futureNullType = typeProvider.futureNullType; @override Object visitArgumentList(ArgumentList node) { _checkForArgumentTypesNotAssignableInList(node); return super.visitArgumentList(node); } @override Object visitAsExpression(AsExpression node) { _checkForUnnecessaryCast(node); return super.visitAsExpression(node); } @override Object visitAssignmentExpression(AssignmentExpression node) { sc.TokenType operatorType = node.operator.type; if (operatorType == sc.TokenType.EQ) { _checkForUseOfVoidResult(node.rightHandSide); _checkForInvalidAssignment(node.leftHandSide, node.rightHandSide); } else { _checkForDeprecatedMemberUse(node.bestElement, node); } return super.visitAssignmentExpression(node); } @override Object visitBinaryExpression(BinaryExpression node) { _checkForDivisionOptimizationHint(node); _checkForDeprecatedMemberUse(node.bestElement, node); return super.visitBinaryExpression(node); } @override Object visitClassDeclaration(ClassDeclaration node) { ClassElement outerClass = _enclosingClass; try { _enclosingClass = node.element; // Commented out until we decide that we want this hint in the analyzer // checkForOverrideEqualsButNotHashCode(node); return super.visitClassDeclaration(node); } finally { _enclosingClass = outerClass; } } @override Object visitExportDirective(ExportDirective node) { _checkForDeprecatedMemberUse(node.uriElement, node); return super.visitExportDirective(node); } @override Object visitFunctionDeclaration(FunctionDeclaration node) { _checkForMissingReturn(node.returnType, node.functionExpression.body); return super.visitFunctionDeclaration(node); } @override Object visitImportDirective(ImportDirective node) { _checkForDeprecatedMemberUse(node.uriElement, node); ImportElement importElement = node.element; if (importElement != null) { if (importElement.isDeferred) { _checkForLoadLibraryFunction(node, importElement); } } return super.visitImportDirective(node); } @override Object visitIndexExpression(IndexExpression node) { _checkForDeprecatedMemberUse(node.bestElement, node); return super.visitIndexExpression(node); } @override Object visitInstanceCreationExpression(InstanceCreationExpression node) { _checkForDeprecatedMemberUse(node.staticElement, node); return super.visitInstanceCreationExpression(node); } @override Object visitIsExpression(IsExpression node) { _checkAllTypeChecks(node); return super.visitIsExpression(node); } @override Object visitMethodDeclaration(MethodDeclaration node) { // This was determined to not be a good hint, see: dartbug.com/16029 //checkForOverridingPrivateMember(node); _checkForMissingReturn(node.returnType, node.body); return super.visitMethodDeclaration(node); } @override Object visitPostfixExpression(PostfixExpression node) { _checkForDeprecatedMemberUse(node.bestElement, node); return super.visitPostfixExpression(node); } @override Object visitPrefixExpression(PrefixExpression node) { _checkForDeprecatedMemberUse(node.bestElement, node); return super.visitPrefixExpression(node); } @override Object visitRedirectingConstructorInvocation( RedirectingConstructorInvocation node) { _checkForDeprecatedMemberUse(node.staticElement, node); return super.visitRedirectingConstructorInvocation(node); } @override Object visitSimpleIdentifier(SimpleIdentifier node) { _checkForDeprecatedMemberUseAtIdentifier(node); return super.visitSimpleIdentifier(node); } @override Object visitSuperConstructorInvocation(SuperConstructorInvocation node) { _checkForDeprecatedMemberUse(node.staticElement, node); return super.visitSuperConstructorInvocation(node); } @override Object visitVariableDeclaration(VariableDeclaration node) { _checkForUseOfVoidResult(node.initializer); _checkForInvalidAssignment(node.name, node.initializer); return super.visitVariableDeclaration(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], [HintCode.TYPE_CHECK_IS_NULL], * [HintCode.UNNECESSARY_TYPE_CHECK_TRUE], and * [HintCode.UNNECESSARY_TYPE_CHECK_FALSE]. */ bool _checkAllTypeChecks(IsExpression node) { Expression expression = node.expression; TypeName typeName = node.type; DartType lhsType = expression.staticType; DartType rhsType = typeName.type; if (lhsType == null || rhsType == null) { return false; } String rhsNameStr = typeName.name.name; // if x is dynamic if (rhsType.isDynamic && rhsNameStr == sc.Keyword.DYNAMIC.syntax) { if (node.notOperator == null) { // the is case _errorReporter.reportErrorForNode( HintCode.UNNECESSARY_TYPE_CHECK_TRUE, node); } else { // the is not case _errorReporter.reportErrorForNode( 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 x is Object or null is Null if (rhsType.isObject || (expression is NullLiteral && rhsNameStr == _NULL_TYPE_NAME)) { if (node.notOperator == null) { // the is case _errorReporter.reportErrorForNode( HintCode.UNNECESSARY_TYPE_CHECK_TRUE, node); } else { // the is not case _errorReporter.reportErrorForNode( HintCode.UNNECESSARY_TYPE_CHECK_FALSE, node); } return true; } else if (rhsNameStr == _NULL_TYPE_NAME) { if (node.notOperator == null) { // the is case _errorReporter.reportErrorForNode(HintCode.TYPE_CHECK_IS_NULL, node); } else { // the is not case _errorReporter.reportErrorForNode( HintCode.TYPE_CHECK_IS_NOT_NULL, node); } return true; } } return false; } /** * This verifies that the passed expression can be assigned to its corresponding parameters. * * This method corresponds to ErrorVerifier.checkForArgumentTypeNotAssignable. * * TODO (jwren) In the ErrorVerifier there are other warnings that we could have a corresponding * hint for: see other callers of ErrorVerifier.checkForArgumentTypeNotAssignable(..). * * @param expression the expression to evaluate * @param expectedStaticType the expected static type of the parameter * @param actualStaticType the actual static type of the argument * @param expectedPropagatedType the expected propagated type of the parameter, may be * `null` * @param actualPropagatedType the expected propagated type of the parameter, may be `null` * @return `true` if and only if an hint code is generated on the passed node * See [HintCode.ARGUMENT_TYPE_NOT_ASSIGNABLE]. */ bool _checkForArgumentTypeNotAssignable(Expression expression, DartType expectedStaticType, DartType actualStaticType, DartType expectedPropagatedType, DartType actualPropagatedType, ErrorCode hintCode) { // // Warning case: test static type information // if (actualStaticType != null && expectedStaticType != null) { if (!actualStaticType.isAssignableTo(expectedStaticType)) { // A warning was created in the ErrorVerifier, return false, don't // create a hint when a warning has already been created. return false; } } // // Hint case: test propagated type information // // Compute the best types to use. DartType expectedBestType = expectedPropagatedType != null ? expectedPropagatedType : expectedStaticType; DartType actualBestType = actualPropagatedType != null ? actualPropagatedType : actualStaticType; if (actualBestType != null && expectedBestType != null) { if (!actualBestType.isAssignableTo(expectedBestType)) { _errorReporter.reportTypeErrorForNode( hintCode, expression, [actualBestType, expectedBestType]); return true; } } return false; } /** * This verifies that the passed argument can be assigned to its corresponding parameter. * * This method corresponds to ErrorCode.checkForArgumentTypeNotAssignableForArgument. * * @param argument the argument to evaluate * @return `true` if and only if an hint code is generated on the passed node * See [HintCode.ARGUMENT_TYPE_NOT_ASSIGNABLE]. */ bool _checkForArgumentTypeNotAssignableForArgument(Expression argument) { if (argument == null) { return false; } ParameterElement staticParameterElement = argument.staticParameterElement; DartType staticParameterType = staticParameterElement == null ? null : staticParameterElement.type; ParameterElement propagatedParameterElement = argument.propagatedParameterElement; DartType propagatedParameterType = propagatedParameterElement == null ? null : propagatedParameterElement.type; return _checkForArgumentTypeNotAssignableWithExpectedTypes(argument, staticParameterType, propagatedParameterType, HintCode.ARGUMENT_TYPE_NOT_ASSIGNABLE); } /** * This verifies that the passed expression can be assigned to its corresponding parameters. * * This method corresponds to ErrorCode.checkForArgumentTypeNotAssignableWithExpectedTypes. * * @param expression the expression to evaluate * @param expectedStaticType the expected static type * @param expectedPropagatedType the expected propagated type, may be `null` * @return `true` if and only if an hint code is generated on the passed node * See [HintCode.ARGUMENT_TYPE_NOT_ASSIGNABLE]. */ bool _checkForArgumentTypeNotAssignableWithExpectedTypes( Expression expression, DartType expectedStaticType, DartType expectedPropagatedType, ErrorCode errorCode) => _checkForArgumentTypeNotAssignable(expression, expectedStaticType, expression.staticType, expectedPropagatedType, expression.propagatedType, errorCode); /** * This verifies that the passed arguments can be assigned to their corresponding parameters. * * This method corresponds to ErrorCode.checkForArgumentTypesNotAssignableInList. * * @param node the arguments to evaluate * @return `true` if and only if an hint code is generated on the passed node * See [HintCode.ARGUMENT_TYPE_NOT_ASSIGNABLE]. */ bool _checkForArgumentTypesNotAssignableInList(ArgumentList argumentList) { if (argumentList == null) { return false; } bool problemReported = false; for (Expression argument in argumentList.arguments) { if (_checkForArgumentTypeNotAssignableForArgument(argument)) { problemReported = true; } } return problemReported; } /** * Given some [Element], look at the associated metadata and report the use of the member if * it is declared as deprecated. * * @param element some element to check for deprecated use of * @param node the node use for the location of the error * @return `true` if and only if a hint code is generated on the passed node * See [HintCode.DEPRECATED_MEMBER_USE]. */ bool _checkForDeprecatedMemberUse(Element element, AstNode node) { if (element != null && element.isDeprecated) { String displayName = element.displayName; if (element is ConstructorElement) { // TODO(jwren) We should modify ConstructorElement.getDisplayName(), // or have the logic centralized elsewhere, instead of doing this logic // here. ConstructorElement constructorElement = element; displayName = constructorElement.enclosingElement.displayName; if (!constructorElement.displayName.isEmpty) { displayName = "$displayName.${constructorElement.displayName}"; } } _errorReporter.reportErrorForNode( HintCode.DEPRECATED_MEMBER_USE, node, [displayName]); return true; } return false; } /** * For [SimpleIdentifier]s, only call [checkForDeprecatedMemberUse] * if the node is not in a declaration context. * * Also, if the identifier is a constructor name in a constructor invocation, then calls to the * deprecated constructor will be caught by * [visitInstanceCreationExpression] and * [visitSuperConstructorInvocation], and can be ignored by * this visit method. * * @param identifier some simple identifier to check for deprecated use of * @return `true` if and only if a hint code is generated on the passed node * See [HintCode.DEPRECATED_MEMBER_USE]. */ bool _checkForDeprecatedMemberUseAtIdentifier(SimpleIdentifier identifier) { if (identifier.inDeclarationContext()) { return false; } AstNode parent = identifier.parent; if ((parent is ConstructorName && identical(identifier, parent.name)) || (parent is SuperConstructorInvocation && identical(identifier, parent.constructorName)) || parent is HideCombinator) { return false; } return _checkForDeprecatedMemberUse(identifier.bestElement, identifier); } /** * 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) { // Return if the operator is not '/' if (node.operator.type != sc.TokenType.SLASH) { return false; } // Return if the '/' operator is not defined in core, or if we don't know // its static or propagated type MethodElement methodElement = node.bestElement; if (methodElement == null) { return false; } LibraryElement libraryElement = methodElement.library; if (libraryElement != null && !libraryElement.isDartCore) { return false; } // Report error if the (x/y) has toInt() invoked on it 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.reportErrorForNode( HintCode.DIVISION_OPTIMIZATION, methodInvocation); return true; } } } return false; } /** * This verifies that the passed left hand side and right hand side represent a valid assignment. * * This method corresponds to ErrorVerifier.checkForInvalidAssignment. * * @param lhs the left hand side expression * @param rhs the right hand side expression * @return `true` if and only if an error code is generated on the passed node * See [HintCode.INVALID_ASSIGNMENT]. */ bool _checkForInvalidAssignment(Expression lhs, Expression rhs) { if (lhs == null || rhs == null) { return false; } VariableElement leftVariableElement = ErrorVerifier.getVariableElement(lhs); DartType leftType = (leftVariableElement == null) ? ErrorVerifier.getStaticType(lhs) : leftVariableElement.type; DartType staticRightType = ErrorVerifier.getStaticType(rhs); if (!staticRightType.isAssignableTo(leftType)) { // The warning was generated on this rhs return false; } // Test for, and then generate the hint DartType bestRightType = rhs.bestType; if (leftType != null && bestRightType != null) { if (!bestRightType.isAssignableTo(leftType)) { _errorReporter.reportTypeErrorForNode( HintCode.INVALID_ASSIGNMENT, rhs, [bestRightType, leftType]); return true; } } return false; } /** * Check that the imported library does not define a loadLibrary function. The import has already * been determined to be deferred when this is called. * * @param node the import directive to evaluate * @param importElement the [ImportElement] retrieved from the node * @return `true` if and only if an error code is generated on the passed node * See [CompileTimeErrorCode.IMPORT_DEFERRED_LIBRARY_WITH_LOAD_FUNCTION]. */ bool _checkForLoadLibraryFunction( ImportDirective node, ImportElement importElement) { LibraryElement importedLibrary = importElement.importedLibrary; if (importedLibrary == null) { return false; } if (importedLibrary.hasLoadLibraryFunction) { _errorReporter.reportErrorForNode( HintCode.IMPORT_DEFERRED_LIBRARY_WITH_LOAD_FUNCTION, node, [importedLibrary.name]); return true; } return false; } /** * Generate a hint for functions or methods that have a return type, but do not have a return * statement on all branches. At the end of blocks with no return, Dart implicitly returns * `null`, avoiding these implicit returns is considered a best practice. * * Note: for async functions/methods, this hint only applies when the * function has a return type that Future is not assignable to. * * @param node the binary expression to check * @param body the function body * @return `true` if and only if a hint code is generated on the passed node * See [HintCode.MISSING_RETURN]. */ bool _checkForMissingReturn(TypeName returnType, FunctionBody body) { // Check that the method or function has a return type, and a function body if (returnType == null || body == null) { return false; } // Check that the body is a BlockFunctionBody if (body is! BlockFunctionBody) { return false; } // Generators are never required to have a return statement. if (body.isGenerator) { return false; } // Check that the type is resolvable, and is not "void" DartType returnTypeType = returnType.type; if (returnTypeType == null || returnTypeType.isVoid) { return false; } // For async, give no hint if Future is assignable to the return // type. if (body.isAsynchronous && _futureNullType.isAssignableTo(returnTypeType)) { return false; } // Check the block for a return statement, if not, create the hint BlockFunctionBody blockFunctionBody = body as BlockFunctionBody; if (!ExitDetector.exits(blockFunctionBody)) { _errorReporter.reportErrorForNode( HintCode.MISSING_RETURN, returnType, [returnTypeType.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.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.reportErrorForNode( // HintCode.OVERRIDE_EQUALS_BUT_NOT_HASH_CODE, // node.name, // [classElement.displayName]); // return true; // } // } // 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) { // TODO(jwren) After dartbug.com/13732, revisit this, we should be able to // remove the (x is! TypeParameterType) checks. AstNode parent = node.parent; if (parent is ConditionalExpression && (node == parent.thenExpression || node == parent.elseExpression)) { Expression thenExpression = parent.thenExpression; DartType thenType; if (thenExpression is AsExpression) { thenType = thenExpression.expression.staticType; } else { thenType = thenExpression.staticType; } Expression elseExpression = parent.elseExpression; DartType elseType; if (elseExpression is AsExpression) { elseType = elseExpression.expression.staticType; } else { elseType = elseExpression.staticType; } if (thenType != null && elseType != null && !thenType.isDynamic && !elseType.isDynamic && !thenType.isMoreSpecificThan(elseType) && !elseType.isMoreSpecificThan(thenType)) { return false; } } DartType lhsType = node.expression.staticType; DartType rhsType = node.type.type; if (lhsType != null && rhsType != null && !lhsType.isDynamic && !rhsType.isDynamic && lhsType.isMoreSpecificThan(rhsType)) { _errorReporter.reportErrorForNode(HintCode.UNNECESSARY_CAST, node); return true; } return false; } /** * Check for situations where the result of a method or function is used, when it returns 'void'. * * TODO(jwren) Many other situations of use could be covered. We currently cover the cases var x = * m() and x = m(), but we could also cover cases such as m().x, m()[k], a + m(), f(m()), return * m(). * * @param node expression on the RHS of some assignment * @return `true` if and only if a hint code is generated on the passed node * See [HintCode.USE_OF_VOID_RESULT]. */ bool _checkForUseOfVoidResult(Expression expression) { if (expression == null || expression is! MethodInvocation) { return false; } MethodInvocation methodInvocation = expression as MethodInvocation; if (identical(methodInvocation.staticType, VoidTypeImpl.instance)) { SimpleIdentifier methodName = methodInvocation.methodName; _errorReporter.reportErrorForNode( HintCode.USE_OF_VOID_RESULT, methodName, [methodName.name]); return true; } return false; } /** * 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; } } /** * Instances of the class `ClassScope` implement the scope defined by a class. */ class ClassScope extends EnclosedScope { /** * Initialize a newly created scope enclosed within another scope. * * @param enclosingScope the scope in which this scope is lexically enclosed * @param typeElement the element representing the type represented by this scope */ ClassScope(Scope enclosingScope, ClassElement typeElement) : super(enclosingScope) { if (typeElement == null) { throw new IllegalArgumentException("class element cannot be null"); } _defineMembers(typeElement); } @override AnalysisError getErrorForDuplicate(Element existing, Element duplicate) { if (existing is PropertyAccessorElement && duplicate is MethodElement) { if (existing.nameOffset < duplicate.nameOffset) { return new AnalysisError.con2(duplicate.source, duplicate.nameOffset, duplicate.displayName.length, CompileTimeErrorCode.METHOD_AND_GETTER_WITH_SAME_NAME, [existing.displayName]); } else { return new AnalysisError.con2(existing.source, existing.nameOffset, existing.displayName.length, CompileTimeErrorCode.GETTER_AND_METHOD_WITH_SAME_NAME, [existing.displayName]); } } return super.getErrorForDuplicate(existing, duplicate); } /** * Define the instance members defined by the class. * * @param typeElement the element representing the type represented by this scope */ void _defineMembers(ClassElement typeElement) { for (PropertyAccessorElement accessor in typeElement.accessors) { define(accessor); } for (MethodElement method in typeElement.methods) { define(method); } } } /** * A `CompilationUnitBuilder` builds an element model for a single compilation * unit. */ class CompilationUnitBuilder { /** * Build the compilation unit element for the given [source] based on the * compilation [unit] associated with the source. Throw an AnalysisException * if the element could not be built. */ CompilationUnitElementImpl buildCompilationUnit( Source source, CompilationUnit unit) { return PerformanceStatistics.resolve.makeCurrentWhile(() { 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.enums = holder.enums; element.functions = holder.functions; element.source = source; element.typeAliases = holder.typeAliases; element.types = holder.types; element.topLevelVariables = holder.topLevelVariables; unit.element = element; holder.validate(); return element; }); } } /** * Instances of the class `ConstantVerifier` traverse an AST structure looking for additional * errors and warnings not covered by the parser and resolver. In particular, it looks for errors * and warnings related to constant expressions. */ class ConstantVerifier extends RecursiveAstVisitor { /** * The error reporter by which errors will be reported. */ final ErrorReporter _errorReporter; /** * The type provider used to access the known types. */ final TypeProvider _typeProvider; /** * The type representing the type 'bool'. */ InterfaceType _boolType; /** * The type representing the type 'int'. */ InterfaceType _intType; /** * The type representing the type 'num'. */ InterfaceType _numType; /** * The type representing the type 'string'. */ InterfaceType _stringType; /** * The current library that is being analyzed. */ final LibraryElement _currentLibrary; /** * Initialize a newly created constant verifier. * * @param errorReporter the error reporter by which errors will be reported */ ConstantVerifier( this._errorReporter, this._currentLibrary, this._typeProvider) { this._boolType = _typeProvider.boolType; this._intType = _typeProvider.intType; this._numType = _typeProvider.numType; this._stringType = _typeProvider.stringType; } @override Object visitAnnotation(Annotation node) { super.visitAnnotation(node); // check annotation creation Element element = node.element; if (element is ConstructorElement) { ConstructorElement constructorElement = element; // should 'const' constructor if (!constructorElement.isConst) { _errorReporter.reportErrorForNode( CompileTimeErrorCode.NON_CONSTANT_ANNOTATION_CONSTRUCTOR, node); return null; } // should have arguments ArgumentList argumentList = node.arguments; if (argumentList == null) { _errorReporter.reportErrorForNode( CompileTimeErrorCode.NO_ANNOTATION_CONSTRUCTOR_ARGUMENTS, node); return null; } // arguments should be constants _validateConstantArguments(argumentList); } return null; } @override Object visitConstructorDeclaration(ConstructorDeclaration node) { if (node.constKeyword != null) { _validateConstructorInitializers(node); _validateFieldInitializers(node.parent as ClassDeclaration, node); } _validateDefaultValues(node.parameters); return super.visitConstructorDeclaration(node); } @override Object visitFunctionExpression(FunctionExpression node) { super.visitFunctionExpression(node); _validateDefaultValues(node.parameters); return null; } @override Object visitInstanceCreationExpression(InstanceCreationExpression node) { if (node.isConst) { EvaluationResultImpl evaluationResult = node.evaluationResult; // Note: evaluationResult might be null if there are circular references // among constants. if (evaluationResult != null) { _reportErrors(evaluationResult.errors, null); } } _validateInstanceCreationArguments(node); return super.visitInstanceCreationExpression(node); } @override Object visitListLiteral(ListLiteral node) { super.visitListLiteral(node); if (node.constKeyword != null) { DartObjectImpl result; for (Expression element in node.elements) { result = _validate(element, CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT); if (result != null) { _reportErrorIfFromDeferredLibrary(element, CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT_FROM_DEFERRED_LIBRARY); } } } return null; } @override Object visitMapLiteral(MapLiteral node) { super.visitMapLiteral(node); bool isConst = node.constKeyword != null; bool reportEqualKeys = true; HashSet keys = new HashSet(); List invalidKeys = new List(); for (MapLiteralEntry entry in node.entries) { Expression key = entry.key; if (isConst) { DartObjectImpl keyResult = _validate(key, CompileTimeErrorCode.NON_CONSTANT_MAP_KEY); Expression valueExpression = entry.value; DartObjectImpl valueResult = _validate( valueExpression, CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE); if (valueResult != null) { _reportErrorIfFromDeferredLibrary(valueExpression, CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE_FROM_DEFERRED_LIBRARY); } if (keyResult != null) { _reportErrorIfFromDeferredLibrary(key, CompileTimeErrorCode.NON_CONSTANT_MAP_KEY_FROM_DEFERRED_LIBRARY); if (keys.contains(keyResult)) { invalidKeys.add(key); } else { keys.add(keyResult); } DartType type = keyResult.type; if (_implementsEqualsWhenNotAllowed(type)) { _errorReporter.reportErrorForNode( CompileTimeErrorCode.CONST_MAP_KEY_EXPRESSION_TYPE_IMPLEMENTS_EQUALS, key, [type.displayName]); } } } else { // Note: we throw the errors away because this isn't actually a const. AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER; ErrorReporter subErrorReporter = new ErrorReporter(errorListener, _errorReporter.source); DartObjectImpl result = key .accept(new ConstantVisitor.con1(_typeProvider, subErrorReporter)); if (result != null) { if (keys.contains(result)) { invalidKeys.add(key); } else { keys.add(result); } } else { reportEqualKeys = false; } } } if (reportEqualKeys) { for (Expression key in invalidKeys) { _errorReporter.reportErrorForNode( StaticWarningCode.EQUAL_KEYS_IN_MAP, key); } } return null; } @override Object visitMethodDeclaration(MethodDeclaration node) { super.visitMethodDeclaration(node); _validateDefaultValues(node.parameters); return null; } @override Object visitSwitchStatement(SwitchStatement node) { // TODO(paulberry): to minimize error messages, it would be nice to // compare all types with the most popular type rather than the first // type. NodeList switchMembers = node.members; bool foundError = false; DartType firstType = null; for (SwitchMember switchMember in switchMembers) { if (switchMember is SwitchCase) { SwitchCase switchCase = switchMember; Expression expression = switchCase.expression; DartObjectImpl caseResult = _validate( expression, CompileTimeErrorCode.NON_CONSTANT_CASE_EXPRESSION); if (caseResult != null) { _reportErrorIfFromDeferredLibrary(expression, CompileTimeErrorCode.NON_CONSTANT_CASE_EXPRESSION_FROM_DEFERRED_LIBRARY); DartObject value = caseResult; if (firstType == null) { firstType = value.type; } else { DartType nType = value.type; if (firstType != nType) { _errorReporter.reportErrorForNode( CompileTimeErrorCode.INCONSISTENT_CASE_EXPRESSION_TYPES, expression, [expression.toSource(), firstType.displayName]); foundError = true; } } } } } if (!foundError) { _checkForCaseExpressionTypeImplementsEquals(node, firstType); } return super.visitSwitchStatement(node); } @override Object visitVariableDeclaration(VariableDeclaration node) { super.visitVariableDeclaration(node); Expression initializer = node.initializer; if (initializer != null && node.isConst) { VariableElementImpl element = node.element as VariableElementImpl; EvaluationResultImpl result = element.evaluationResult; if (result == null) { // // Normally we don't need to visit const variable declarations because // we have already computed their values. But if we missed it for some // reason, this gives us a second chance. // result = new EvaluationResultImpl.con1(_validate(initializer, CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE)); element.evaluationResult = result; return null; } _reportErrors(result.errors, CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE); _reportErrorIfFromDeferredLibrary(initializer, CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE_FROM_DEFERRED_LIBRARY); } return null; } /** * This verifies that the passed switch statement does not have a case expression with the * operator '==' overridden. * * @param node the switch statement to evaluate * @param type the common type of all 'case' expressions * @return `true` if and only if an error code is generated on the passed node * See [CompileTimeErrorCode.CASE_EXPRESSION_TYPE_IMPLEMENTS_EQUALS]. */ bool _checkForCaseExpressionTypeImplementsEquals( SwitchStatement node, DartType type) { if (!_implementsEqualsWhenNotAllowed(type)) { return false; } // report error _errorReporter.reportErrorForToken( CompileTimeErrorCode.CASE_EXPRESSION_TYPE_IMPLEMENTS_EQUALS, node.switchKeyword, [type.displayName]); return true; } /** * @return `true` if given [Type] implements operator ==, and it is not * int or String. */ bool _implementsEqualsWhenNotAllowed(DartType type) { // ignore int or String if (type == null || type == _intType || type == _typeProvider.stringType) { return false; } else if (type == _typeProvider.doubleType) { return true; } // prepare ClassElement Element element = type.element; if (element is! ClassElement) { return false; } ClassElement classElement = element as ClassElement; // lookup for == MethodElement method = classElement.lookUpConcreteMethod("==", _currentLibrary); if (method == null || method.enclosingElement.type.isObject) { return false; } // there is == that we don't like return true; } /** * Given some computed [Expression], this method generates the passed [ErrorCode] on * the node if its' value consists of information from a deferred library. * * @param expression the expression to be tested for a deferred library reference * @param errorCode the error code to be used if the expression is or consists of a reference to a * deferred library */ void _reportErrorIfFromDeferredLibrary( Expression expression, ErrorCode errorCode) { DeferredLibraryReferenceDetector referenceDetector = new DeferredLibraryReferenceDetector(); expression.accept(referenceDetector); if (referenceDetector.result) { _errorReporter.reportErrorForNode(errorCode, expression); } } /** * Report any errors in the given list. Except for special cases, use the given error code rather * than the one reported in the error. * * @param errors the errors that need to be reported * @param errorCode the error code to be used */ void _reportErrors(List errors, ErrorCode errorCode) { for (AnalysisError data in errors) { ErrorCode dataErrorCode = data.errorCode; if (identical(dataErrorCode, CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION) || identical( dataErrorCode, CompileTimeErrorCode.CONST_EVAL_THROWS_IDBZE) || identical(dataErrorCode, CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING) || identical(dataErrorCode, CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL) || identical(dataErrorCode, CompileTimeErrorCode.CONST_EVAL_TYPE_INT) || identical(dataErrorCode, CompileTimeErrorCode.CONST_EVAL_TYPE_NUM) || identical(dataErrorCode, CheckedModeCompileTimeErrorCode.CONST_CONSTRUCTOR_FIELD_TYPE_MISMATCH) || identical(dataErrorCode, CheckedModeCompileTimeErrorCode.CONST_CONSTRUCTOR_PARAM_TYPE_MISMATCH) || identical(dataErrorCode, CheckedModeCompileTimeErrorCode.VARIABLE_TYPE_MISMATCH)) { _errorReporter.reportError(data); } else if (errorCode != null) { _errorReporter.reportError(new AnalysisError.con2( data.source, data.offset, data.length, errorCode)); } } } /** * Validate that the given expression is a compile time constant. Return the value of the compile * time constant, or `null` if the expression is not a compile time constant. * * @param expression the expression to be validated * @param errorCode the error code to be used if the expression is not a compile time constant * @return the value of the compile time constant */ DartObjectImpl _validate(Expression expression, ErrorCode errorCode) { RecordingErrorListener errorListener = new RecordingErrorListener(); ErrorReporter subErrorReporter = new ErrorReporter(errorListener, _errorReporter.source); DartObjectImpl result = expression .accept(new ConstantVisitor.con1(_typeProvider, subErrorReporter)); _reportErrors(errorListener.errors, errorCode); return result; } /** * Validate that if the passed arguments are constant expressions. * * @param argumentList the argument list to evaluate */ void _validateConstantArguments(ArgumentList argumentList) { for (Expression argument in argumentList.arguments) { if (argument is NamedExpression) { argument = (argument as NamedExpression).expression; } _validate( argument, CompileTimeErrorCode.CONST_WITH_NON_CONSTANT_ARGUMENT); } } /** * Validates that the expressions of the given initializers (of a constant constructor) are all * compile time constants. * * @param constructor the constant constructor declaration to validate */ void _validateConstructorInitializers(ConstructorDeclaration constructor) { List parameterElements = constructor.parameters.parameterElements; NodeList initializers = constructor.initializers; for (ConstructorInitializer initializer in initializers) { if (initializer is ConstructorFieldInitializer) { ConstructorFieldInitializer fieldInitializer = initializer; _validateInitializerExpression( parameterElements, fieldInitializer.expression); } if (initializer is RedirectingConstructorInvocation) { RedirectingConstructorInvocation invocation = initializer; _validateInitializerInvocationArguments( parameterElements, invocation.argumentList); } if (initializer is SuperConstructorInvocation) { SuperConstructorInvocation invocation = initializer; _validateInitializerInvocationArguments( parameterElements, invocation.argumentList); } } } /** * Validate that the default value associated with each of the parameters in the given list is a * compile time constant. * * @param parameters the list of parameters to be validated */ void _validateDefaultValues(FormalParameterList parameters) { if (parameters == null) { return; } for (FormalParameter parameter in parameters.parameters) { if (parameter is DefaultFormalParameter) { DefaultFormalParameter defaultParameter = parameter; Expression defaultValue = defaultParameter.defaultValue; DartObjectImpl result; if (defaultValue == null) { result = new DartObjectImpl(_typeProvider.nullType, NullState.NULL_STATE); } else { result = _validate( defaultValue, CompileTimeErrorCode.NON_CONSTANT_DEFAULT_VALUE); if (result != null) { _reportErrorIfFromDeferredLibrary(defaultValue, CompileTimeErrorCode.NON_CONSTANT_DEFAULT_VALUE_FROM_DEFERRED_LIBRARY); } } VariableElementImpl element = parameter.element as VariableElementImpl; element.evaluationResult = new EvaluationResultImpl.con1(result); } } } /** * Validates that the expressions of any field initializers in the class declaration are all * compile time constants. Since this is only required if the class has a constant constructor, * the error is reported at the constructor site. * * @param classDeclaration the class which should be validated * @param errorSite the site at which errors should be reported. */ void _validateFieldInitializers( ClassDeclaration classDeclaration, ConstructorDeclaration errorSite) { NodeList members = classDeclaration.members; for (ClassMember member in members) { if (member is FieldDeclaration) { FieldDeclaration fieldDeclaration = member; if (!fieldDeclaration.isStatic) { for (VariableDeclaration variableDeclaration in fieldDeclaration.fields.variables) { Expression initializer = variableDeclaration.initializer; if (initializer != null) { // Ignore any errors produced during validation--if the constant // can't be eavluated we'll just report a single error. AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER; ErrorReporter subErrorReporter = new ErrorReporter(errorListener, _errorReporter.source); DartObjectImpl result = initializer.accept( new ConstantVisitor.con1(_typeProvider, subErrorReporter)); if (result == null) { _errorReporter.reportErrorForNode( CompileTimeErrorCode.CONST_CONSTRUCTOR_WITH_FIELD_INITIALIZED_BY_NON_CONST, errorSite, [variableDeclaration.name.name]); } } } } } } } /** * Validates that the given expression is a compile time constant. * * @param parameterElements the elements of parameters of constant constructor, they are * considered as a valid potentially constant expressions * @param expression the expression to validate */ void _validateInitializerExpression( List parameterElements, Expression expression) { RecordingErrorListener errorListener = new RecordingErrorListener(); ErrorReporter subErrorReporter = new ErrorReporter(errorListener, _errorReporter.source); DartObjectImpl result = expression.accept( new _ConstantVerifier_validateInitializerExpression( _typeProvider, subErrorReporter, this, parameterElements)); _reportErrors(errorListener.errors, CompileTimeErrorCode.NON_CONSTANT_VALUE_IN_INITIALIZER); if (result != null) { _reportErrorIfFromDeferredLibrary(expression, CompileTimeErrorCode.NON_CONSTANT_VALUE_IN_INITIALIZER_FROM_DEFERRED_LIBRARY); } } /** * Validates that all of the arguments of a constructor initializer are compile time constants. * * @param parameterElements the elements of parameters of constant constructor, they are * considered as a valid potentially constant expressions * @param argumentList the argument list to validate */ void _validateInitializerInvocationArguments( List parameterElements, ArgumentList argumentList) { if (argumentList == null) { return; } for (Expression argument in argumentList.arguments) { _validateInitializerExpression(parameterElements, argument); } } /** * Validate that if the passed instance creation is 'const' then all its arguments are constant * expressions. * * @param node the instance creation evaluate */ void _validateInstanceCreationArguments(InstanceCreationExpression node) { if (!node.isConst) { return; } ArgumentList argumentList = node.argumentList; if (argumentList == null) { return; } _validateConstantArguments(argumentList); } } /** * 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]. */ class Dart2JSVerifier extends RecursiveAstVisitor { /** * The name of the `double` type. */ static String _DOUBLE_TYPE_NAME = "double"; /** * The error reporter by which errors will be reported. */ final ErrorReporter _errorReporter; /** * Create a new instance of the [Dart2JSVerifier]. * * @param errorReporter the error reporter */ Dart2JSVerifier(this._errorReporter); @override 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], * [HintCode.IS_INT], * [HintCode.IS_NOT_DOUBLE], and * [HintCode.IS_NOT_INT]. */ bool _checkForIsDoubleHints(IsExpression node) { TypeName typeName = node.type; DartType type = typeName.type; if (type != null && type.element != null) { Element element = type.element; String typeNameStr = element.name; LibraryElement libraryElement = element.library; // if (typeNameStr.equals(INT_TYPE_NAME) && libraryElement != null // && libraryElement.isDartCore()) { // if (node.getNotOperator() == null) { // errorReporter.reportError(HintCode.IS_INT, node); // } else { // errorReporter.reportError(HintCode.IS_NOT_INT, node); // } // return true; // } else if (typeNameStr == _DOUBLE_TYPE_NAME && libraryElement != null && libraryElement.isDartCore) { if (node.notOperator == null) { _errorReporter.reportErrorForNode(HintCode.IS_DOUBLE, node); } else { _errorReporter.reportErrorForNode(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]. */ class DeadCodeVerifier extends RecursiveAstVisitor { /** * The error reporter by which errors will be reported. */ final ErrorReporter _errorReporter; /** * Create a new instance of the [DeadCodeVerifier]. * * @param errorReporter the error reporter */ DeadCodeVerifier(this._errorReporter); @override Object visitBinaryExpression(BinaryExpression node) { sc.Token operator = node.operator; bool isAmpAmp = operator.type == sc.TokenType.AMPERSAND_AMPERSAND; bool isBarBar = operator.type == sc.TokenType.BAR_BAR; if (isAmpAmp || isBarBar) { Expression lhsCondition = node.leftOperand; if (!_isDebugConstant(lhsCondition)) { EvaluationResultImpl lhsResult = _getConstantBooleanValue(lhsCondition); if (lhsResult != null) { if (lhsResult.value.isTrue && isBarBar) { // report error on else block: true || !e! _errorReporter.reportErrorForNode( HintCode.DEAD_CODE, node.rightOperand); // only visit the LHS: _safelyVisit(lhsCondition); return null; } else if (lhsResult.value.isFalse && isAmpAmp) { // report error on if block: false && !e! _errorReporter.reportErrorForNode( HintCode.DEAD_CODE, node.rightOperand); // only visit the LHS: _safelyVisit(lhsCondition); return null; } } } // How do we want to handle the RHS? It isn't dead code, but "pointless" // or "obscure"... // Expression rhsCondition = node.getRightOperand(); // ValidResult rhsResult = getConstantBooleanValue(rhsCondition); // if (rhsResult != null) { // if (rhsResult == ValidResult.RESULT_TRUE && isBarBar) { // // report error on else block: !e! || true // errorReporter.reportError(HintCode.DEAD_CODE, node.getRightOperand()); // // only visit the RHS: // safelyVisit(rhsCondition); // return null; // } else if (rhsResult == ValidResult.RESULT_FALSE && isAmpAmp) { // // report error on if block: !e! && false // errorReporter.reportError(HintCode.DEAD_CODE, node.getRightOperand()); // // only visit the RHS: // safelyVisit(rhsCondition); // 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 */ @override Object visitBlock(Block node) { NodeList statements = node.statements; _checkForDeadStatementsInNodeList(statements); return null; } @override Object visitConditionalExpression(ConditionalExpression node) { Expression conditionExpression = node.condition; _safelyVisit(conditionExpression); if (!_isDebugConstant(conditionExpression)) { EvaluationResultImpl result = _getConstantBooleanValue(conditionExpression); if (result != null) { if (result.value.isTrue) { // report error on else block: true ? 1 : !2! _errorReporter.reportErrorForNode( HintCode.DEAD_CODE, node.elseExpression); _safelyVisit(node.thenExpression); return null; } else { // report error on if block: false ? !1! : 2 _errorReporter.reportErrorForNode( HintCode.DEAD_CODE, node.thenExpression); _safelyVisit(node.elseExpression); return null; } } } return super.visitConditionalExpression(node); } @override Object visitIfStatement(IfStatement node) { Expression conditionExpression = node.condition; _safelyVisit(conditionExpression); if (!_isDebugConstant(conditionExpression)) { EvaluationResultImpl result = _getConstantBooleanValue(conditionExpression); if (result != null) { if (result.value.isTrue) { // report error on else block: if(true) {} else {!} Statement elseStatement = node.elseStatement; if (elseStatement != null) { _errorReporter.reportErrorForNode( HintCode.DEAD_CODE, elseStatement); _safelyVisit(node.thenStatement); return null; } } else { // report error on if block: if (false) {!} else {} _errorReporter.reportErrorForNode( HintCode.DEAD_CODE, node.thenStatement); _safelyVisit(node.elseStatement); return null; } } } return super.visitIfStatement(node); } @override Object visitSwitchCase(SwitchCase node) { _checkForDeadStatementsInNodeList(node.statements); return super.visitSwitchCase(node); } @override Object visitSwitchDefault(SwitchDefault node) { _checkForDeadStatementsInNodeList(node.statements); return super.visitSwitchDefault(node); } @override Object visitTryStatement(TryStatement node) { _safelyVisit(node.body); _safelyVisit(node.finallyBlock); NodeList catchClauses = node.catchClauses; int numOfCatchClauses = catchClauses.length; List visitedTypes = new List(); for (int i = 0; i < numOfCatchClauses; i++) { CatchClause catchClause = catchClauses[i]; if (catchClause.onKeyword != null) { // on-catch clause found, verify that the exception type is not a // subtype of a previous on-catch exception type TypeName typeName = catchClause.exceptionType; if (typeName != null && typeName.type != null) { DartType currentType = typeName.type; if (currentType.isObject) { // Found catch clause clause that has Object as an exception type, // this is equivalent to having a catch clause that doesn't have an // exception type, visit the block, but generate an error on any // following catch clauses (and don't visit them). _safelyVisit(catchClause); if (i + 1 != numOfCatchClauses) { // this catch clause is not the last in the try statement CatchClause nextCatchClause = catchClauses[i + 1]; CatchClause lastCatchClause = catchClauses[numOfCatchClauses - 1]; int offset = nextCatchClause.offset; int length = lastCatchClause.end - offset; _errorReporter.reportErrorForOffset( HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH, offset, length); return null; } } for (DartType type in visitedTypes) { if (currentType.isSubtypeOf(type)) { CatchClause lastCatchClause = catchClauses[numOfCatchClauses - 1]; int offset = catchClause.offset; int length = lastCatchClause.end - offset; _errorReporter.reportErrorForOffset( HintCode.DEAD_CODE_ON_CATCH_SUBTYPE, offset, length, [ currentType.displayName, type.displayName ]); return null; } } visitedTypes.add(currentType); } _safelyVisit(catchClause); } else { // Found catch clause clause that doesn't have an exception type, // visit the block, but generate an error on any following catch clauses // (and don't visit them). _safelyVisit(catchClause); if (i + 1 != numOfCatchClauses) { // this catch clause is not the last in the try statement CatchClause nextCatchClause = catchClauses[i + 1]; CatchClause lastCatchClause = catchClauses[numOfCatchClauses - 1]; int offset = nextCatchClause.offset; int length = lastCatchClause.end - offset; _errorReporter.reportErrorForOffset( HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH, offset, length); return null; } } } return null; } @override Object visitWhileStatement(WhileStatement node) { Expression conditionExpression = node.condition; _safelyVisit(conditionExpression); if (!_isDebugConstant(conditionExpression)) { EvaluationResultImpl result = _getConstantBooleanValue(conditionExpression); if (result != null) { if (result.value.isFalse) { // report error on if block: while (false) {!} _errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node.body); return null; } } } _safelyVisit(node.body); return null; } /** * Given some [NodeList] of [Statement]s, from either a [Block] or * [SwitchMember], this loops through the list in reverse order searching for statements * after a return, unlabeled break or unlabeled continue statement to mark them as dead code. * * @param statements some ordered list of statements in a [Block] or [SwitchMember] */ void _checkForDeadStatementsInNodeList(NodeList statements) { int size = statements.length; for (int i = 0; i < size; i++) { Statement currentStatement = statements[i]; _safelyVisit(currentStatement); bool returnOrBreakingStatement = currentStatement is ReturnStatement || (currentStatement is BreakStatement && currentStatement.label == null) || (currentStatement is ContinueStatement && currentStatement.label == null); if (returnOrBreakingStatement && i != size - 1) { Statement nextStatement = statements[i + 1]; Statement lastStatement = statements[size - 1]; int offset = nextStatement.offset; int length = lastStatement.end - offset; _errorReporter.reportErrorForOffset(HintCode.DEAD_CODE, offset, length); return; } } } /** * 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 */ EvaluationResultImpl _getConstantBooleanValue(Expression expression) { if (expression is BooleanLiteral) { if (expression.value) { return new EvaluationResultImpl.con1( new DartObjectImpl(null, BoolState.from(true))); } else { return new EvaluationResultImpl.con1( new DartObjectImpl(null, BoolState.from(false))); } } // Don't consider situations where we could evaluate to a constant boolean // expression with the ConstantVisitor // else { // EvaluationResultImpl result = expression.accept(new ConstantVisitor()); // if (result == ValidResult.RESULT_TRUE) { // return ValidResult.RESULT_TRUE; // } else if (result == ValidResult.RESULT_FALSE) { // return ValidResult.RESULT_FALSE; // } // return null; // } 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; element = identifier.staticElement; } else if (expression is PropertyAccess) { PropertyAccess propertyAccess = expression; element = propertyAccess.propertyName.staticElement; } if (element is PropertyAccessorElement) { PropertyInducingElement variable = element.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 `DeclarationResolver` are used to resolve declarations in an AST * structure to already built elements. */ class DeclarationResolver extends RecursiveAstVisitor { /** * 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); } @override Object visitCatchClause(CatchClause node) { SimpleIdentifier exceptionParameter = node.exceptionParameter; if (exceptionParameter != null) { List localVariables = _enclosingExecutable.localVariables; _findIdentifier(localVariables, exceptionParameter); SimpleIdentifier stackTraceParameter = node.stackTraceParameter; if (stackTraceParameter != null) { _findIdentifier(localVariables, stackTraceParameter); } } return super.visitCatchClause(node); } @override Object visitClassDeclaration(ClassDeclaration node) { ClassElement outerClass = _enclosingClass; try { SimpleIdentifier className = node.name; _enclosingClass = _findIdentifier(_enclosingUnit.types, className); return super.visitClassDeclaration(node); } finally { _enclosingClass = outerClass; } } @override Object visitClassTypeAlias(ClassTypeAlias node) { ClassElement outerClass = _enclosingClass; try { SimpleIdentifier className = node.name; _enclosingClass = _findIdentifier(_enclosingUnit.types, className); return super.visitClassTypeAlias(node); } finally { _enclosingClass = outerClass; } } @override 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; } } @override Object visitDeclaredIdentifier(DeclaredIdentifier node) { SimpleIdentifier variableName = node.identifier; _findIdentifier(_enclosingExecutable.localVariables, variableName); return super.visitDeclaredIdentifier(node); } @override 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) { // TODO(brianwilkerson) Report this internal error. } else { _enclosingExecutable = element.initializer; } defaultValue.accept(this); } finally { _enclosingExecutable = outerExecutable; } } ParameterElement outerParameter = _enclosingParameter; try { _enclosingParameter = element; return super.visitDefaultFormalParameter(node); } finally { _enclosingParameter = outerParameter; } } @override Object visitEnumDeclaration(EnumDeclaration node) { ClassElement enclosingEnum = _findIdentifier(_enclosingUnit.enums, node.name); List constants = enclosingEnum.fields; for (EnumConstantDeclaration constant in node.constants) { _findIdentifier(constants, constant.name); } return super.visitEnumDeclaration(node); } @override Object visitExportDirective(ExportDirective node) { String uri = _getStringValue(node.uri); if (uri != null) { LibraryElement library = _enclosingUnit.library; ExportElement exportElement = _findExport(library.exports, _enclosingUnit.context.sourceFactory.resolveUri( _enclosingUnit.source, uri)); node.element = exportElement; } return super.visitExportDirective(node); } @override 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); } } @override Object visitFunctionDeclaration(FunctionDeclaration node) { ExecutableElement outerExecutable = _enclosingExecutable; try { SimpleIdentifier functionName = node.name; sc.Token property = node.propertyKeyword; if (property == null) { if (_enclosingExecutable != null) { _enclosingExecutable = _findIdentifier(_enclosingExecutable.functions, functionName); } else { _enclosingExecutable = _findIdentifier(_enclosingUnit.functions, functionName); } } else { PropertyAccessorElement accessor = _findIdentifier(_enclosingUnit.accessors, functionName); if ((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; } } @override Object visitFunctionExpression(FunctionExpression node) { if (node.parent is! FunctionDeclaration) { FunctionElement element = _findAtOffset(_enclosingExecutable.functions, node.beginToken.offset); node.element = element; } ExecutableElement outerExecutable = _enclosingExecutable; try { _enclosingExecutable = node.element; return super.visitFunctionExpression(node); } finally { _enclosingExecutable = outerExecutable; } } @override Object visitFunctionTypeAlias(FunctionTypeAlias node) { FunctionTypeAliasElement outerAlias = _enclosingAlias; try { SimpleIdentifier aliasName = node.name; _enclosingAlias = _findIdentifier(_enclosingUnit.functionTypeAliases, aliasName); return super.visitFunctionTypeAlias(node); } finally { _enclosingAlias = outerAlias; } } @override 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); } } @override Object visitImportDirective(ImportDirective node) { String uri = _getStringValue(node.uri); if (uri != null) { LibraryElement library = _enclosingUnit.library; ImportElement importElement = _findImport(library.imports, _enclosingUnit.context.sourceFactory.resolveUri( _enclosingUnit.source, uri), node.prefix); node.element = importElement; } return super.visitImportDirective(node); } @override Object visitLabeledStatement(LabeledStatement node) { for (Label label in node.labels) { SimpleIdentifier labelName = label.label; _findIdentifier(_enclosingExecutable.labels, labelName); } return super.visitLabeledStatement(node); } @override Object visitLibraryDirective(LibraryDirective node) { node.element = _enclosingUnit.library; return super.visitLibraryDirective(node); } @override 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 = _findWithNameAndOffset( _enclosingClass.methods, nameOfMethod, methodName.offset); methodName.staticElement = _enclosingExecutable; } else { PropertyAccessorElement accessor = _findIdentifier(_enclosingClass.accessors, methodName); if ((property as sc.KeywordToken).keyword == sc.Keyword.SET) { accessor = accessor.variable.setter; methodName.staticElement = accessor; } _enclosingExecutable = accessor; } return super.visitMethodDeclaration(node); } finally { _enclosingExecutable = outerExecutable; } } @override Object visitPartDirective(PartDirective node) { String uri = _getStringValue(node.uri); if (uri != null) { Source partSource = _enclosingUnit.context.sourceFactory.resolveUri( _enclosingUnit.source, uri); node.element = _findPart(_enclosingUnit.library.parts, partSource); } return super.visitPartDirective(node); } @override Object visitPartOfDirective(PartOfDirective node) { node.element = _enclosingUnit.library; return super.visitPartOfDirective(node); } @override 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); } @override Object visitSwitchCase(SwitchCase node) { for (Label label in node.labels) { SimpleIdentifier labelName = label.label; _findIdentifier(_enclosingExecutable.labels, labelName); } return super.visitSwitchCase(node); } @override Object visitSwitchDefault(SwitchDefault node) { for (Label label in node.labels) { SimpleIdentifier labelName = label.label; _findIdentifier(_enclosingExecutable.labels, labelName); } return super.visitSwitchDefault(node); } @override Object visitTypeParameter(TypeParameter node) { SimpleIdentifier parameterName = node.name; if (_enclosingClass != null) { _findIdentifier(_enclosingClass.typeParameters, parameterName); } else if (_enclosingAlias != null) { _findIdentifier(_enclosingAlias.typeParameters, parameterName); } return super.visitTypeParameter(node); } @override Object visitVariableDeclaration(VariableDeclaration node) { VariableElement element = null; SimpleIdentifier variableName = node.name; if (_enclosingExecutable != null) { element = _findIdentifier(_enclosingExecutable.localVariables, variableName); } if (element == null && _enclosingClass != null) { element = _findIdentifier(_enclosingClass.fields, variableName); } if (element == null && _enclosingUnit != null) { element = _findIdentifier(_enclosingUnit.topLevelVariables, variableName); } Expression initializer = node.initializer; if (initializer != null) { ExecutableElement outerExecutable = _enclosingExecutable; try { if (element == null) { // TODO(brianwilkerson) Report this internal error. } else { _enclosingExecutable = element.initializer; } return super.visitVariableDeclaration(node); } finally { _enclosingExecutable = outerExecutable; } } return super.visitVariableDeclaration(node); } /** * 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 _findAtOffset(List elements, int offset) => _findWithNameAndOffset(elements, "", offset); /** * 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 _findExport(List exports, Source source) { for (ExportElement export in exports) { if (export.exportedLibrary.source == source) { return export; } } return null; } /** * 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 _findIdentifier(List elements, SimpleIdentifier identifier) { Element element = _findWithNameAndOffset(elements, identifier.name, identifier.offset); identifier.staticElement = element; return element; } /** * 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 _findImport( List 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; } /** * 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 _findPart( List 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 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 _findWithNameAndOffset( List elements, String name, int offset) { for (Element element in elements) { if (element.displayName == name && element.nameOffset == offset) { 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 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 : _findIdentifier(parameters, parameterName); if (element == null) { StringBuffer buffer = new StringBuffer(); buffer.writeln("Invalid state found in the Analysis Engine:"); buffer.writeln( "DeclarationResolver.getElementForParameter() is visiting a parameter that does not appear to be in a method or function."); buffer.writeln("Ancestors:"); AstNode parent = node.parent; while (parent != null) { buffer.writeln(parent.runtimeType.toString()); buffer.writeln("---------"); parent = parent.parent; } AnalysisEngine.instance.logger.logError(buffer.toString(), new CaughtException(new AnalysisException(), null)); } 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 `ElementBuilder` traverse an AST structure and build the element * model representing the AST structure. */ class ElementBuilder extends RecursiveAstVisitor { /** * 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 _functionTypesToFix = null; /** * A table mapping field names to field elements for the fields defined in the current class, or * `null` if we are not in the scope of a class. */ HashMap _fieldMap; /** * 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; } @override Object visitBlock(Block node) { bool wasInField = _inFieldContext; _inFieldContext = false; try { node.visitChildren(this); } finally { _inFieldContext = wasInField; } return null; } @override Object visitCatchClause(CatchClause node) { SimpleIdentifier exceptionParameter = node.exceptionParameter; if (exceptionParameter != null) { // exception LocalVariableElementImpl exception = new LocalVariableElementImpl.forNode(exceptionParameter); _currentHolder.addLocalVariable(exception); exceptionParameter.staticElement = exception; // stack trace SimpleIdentifier stackTraceParameter = node.stackTraceParameter; if (stackTraceParameter != null) { LocalVariableElementImpl stackTrace = new LocalVariableElementImpl.forNode(stackTraceParameter); _currentHolder.addLocalVariable(stackTrace); stackTraceParameter.staticElement = stackTrace; } } return super.visitCatchClause(node); } @override Object visitClassDeclaration(ClassDeclaration node) { ElementHolder holder = new ElementHolder(); _isValidMixin = true; _functionTypesToFix = new List(); // // Process field declarations before constructors and methods so that field // formal parameters can be correctly resolved to their fields. // ElementHolder previousHolder = _currentHolder; _currentHolder = holder; try { List nonFields = new List(); node.visitChildren( new _ElementBuilder_visitClassDeclaration(this, nonFields)); _buildFieldMap(holder.fieldsWithoutFlushing); int count = nonFields.length; for (int i = 0; i < count; i++) { nonFields[i].accept(this); } } finally { _currentHolder = previousHolder; } SimpleIdentifier className = node.name; ClassElementImpl element = new ClassElementImpl.forNode(className); List typeParameters = holder.typeParameters; List typeArguments = _createTypeParameterTypes(typeParameters); InterfaceTypeImpl interfaceType = new InterfaceTypeImpl.con1(element); interfaceType.typeArguments = typeArguments; element.type = interfaceType; List constructors = holder.constructors; if (constructors.length == 0) { // // Create the default constructor. // constructors = _createDefaultConstructors(interfaceType); } element.abstract = node.isAbstract; element.accessors = holder.accessors; element.constructors = constructors; element.fields = holder.fields; element.methods = holder.methods; element.typeParameters = typeParameters; element.validMixin = _isValidMixin; int functionTypeCount = _functionTypesToFix.length; for (int i = 0; i < functionTypeCount; i++) { _functionTypesToFix[i].typeArguments = typeArguments; } _functionTypesToFix = null; _currentHolder.addType(element); className.staticElement = element; _fieldMap = null; holder.validate(); return null; } /** * Implementation of this method should be synchronized with * [visitClassDeclaration]. */ void visitClassDeclarationIncrementally(ClassDeclaration node) { // // Process field declarations before constructors and methods so that field // formal parameters can be correctly resolved to their fields. // ClassElement classElement = node.element; _buildFieldMap(classElement.fields); } @override Object visitClassTypeAlias(ClassTypeAlias node) { ElementHolder holder = new ElementHolder(); _functionTypesToFix = new List(); _visitChildren(holder, node); SimpleIdentifier className = node.name; ClassElementImpl element = new ClassElementImpl.forNode(className); element.abstract = node.abstractKeyword != null; element.typedef = true; List typeParameters = holder.typeParameters; element.typeParameters = typeParameters; List typeArguments = _createTypeParameterTypes(typeParameters); InterfaceTypeImpl interfaceType = new InterfaceTypeImpl.con1(element); interfaceType.typeArguments = typeArguments; element.type = interfaceType; // set default constructor element.constructors = _createDefaultConstructors(interfaceType); for (FunctionTypeImpl functionType in _functionTypesToFix) { functionType.typeArguments = typeArguments; } _functionTypesToFix = null; _currentHolder.addType(element); className.staticElement = element; holder.validate(); return null; } @override Object visitConstructorDeclaration(ConstructorDeclaration node) { _isValidMixin = false; ElementHolder holder = new ElementHolder(); bool wasInFunction = _inFunction; _inFunction = true; try { _visitChildren(holder, node); } finally { _inFunction = wasInFunction; } FunctionBody body = node.body; SimpleIdentifier constructorName = node.name; ConstructorElementImpl element = new ConstructorElementImpl.forNode(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; if (body.isAsynchronous) { element.asynchronous = true; } if (body.isGenerator) { element.generator = true; } _currentHolder.addConstructor(element); node.element = element; if (constructorName == null) { Identifier returnType = node.returnType; if (returnType != null) { element.nameOffset = returnType.offset; element.nameEnd = returnType.end; } } else { constructorName.staticElement = element; element.periodOffset = node.period.offset; element.nameEnd = constructorName.end; } holder.validate(); return null; } @override Object visitDeclaredIdentifier(DeclaredIdentifier node) { SimpleIdentifier variableName = node.identifier; LocalVariableElementImpl element = new LocalVariableElementImpl.forNode(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 = node.isConst; element.final2 = node.isFinal; _currentHolder.addLocalVariable(element); variableName.staticElement = element; return super.visitDeclaredIdentifier(node); } @override Object visitDefaultFormalParameter(DefaultFormalParameter node) { ElementHolder holder = new ElementHolder(); NormalFormalParameter normalParameter = node.parameter; SimpleIdentifier parameterName = normalParameter.identifier; ParameterElementImpl parameter; if (normalParameter is FieldFormalParameter) { parameter = new DefaultFieldFormalParameterElementImpl(parameterName); FieldElement field = _fieldMap == null ? null : _fieldMap[parameterName.name]; if (field != null) { (parameter as DefaultFieldFormalParameterElementImpl).field = field; } } else { parameter = new DefaultParameterElementImpl(parameterName); } parameter.const3 = node.isConst; parameter.final2 = node.isFinal; parameter.parameterKind = node.kind; // set initializer, default value range Expression defaultValue = node.defaultValue; if (defaultValue != null) { _visit(holder, defaultValue); FunctionElementImpl initializer = new FunctionElementImpl.forOffset(defaultValue.beginToken.offset); initializer.functions = holder.functions; initializer.labels = holder.labels; initializer.localVariables = holder.localVariables; initializer.parameters = holder.parameters; initializer.synthetic = true; parameter.initializer = initializer; parameter.defaultValueCode = defaultValue.toSource(); } // visible range _setParameterVisibleRange(node, parameter); _currentHolder.addParameter(parameter); parameterName.staticElement = parameter; normalParameter.accept(this); holder.validate(); return null; } @override Object visitEnumDeclaration(EnumDeclaration node) { SimpleIdentifier enumName = node.name; ClassElementImpl enumElement = new ClassElementImpl.forNode(enumName); enumElement.enum2 = true; InterfaceTypeImpl enumType = new InterfaceTypeImpl.con1(enumElement); enumElement.type = enumType; _currentHolder.addEnum(enumElement); enumName.staticElement = enumElement; return super.visitEnumDeclaration(node); } @override Object visitFieldDeclaration(FieldDeclaration node) { bool wasInField = _inFieldContext; _inFieldContext = true; try { node.visitChildren(this); } finally { _inFieldContext = wasInField; } return null; } @override Object visitFieldFormalParameter(FieldFormalParameter node) { if (node.parent is! DefaultFormalParameter) { SimpleIdentifier parameterName = node.identifier; FieldElement field = _fieldMap == null ? null : _fieldMap[parameterName.name]; FieldFormalParameterElementImpl parameter = new FieldFormalParameterElementImpl(parameterName); parameter.const3 = node.isConst; parameter.final2 = node.isFinal; parameter.parameterKind = node.kind; if (field != null) { parameter.field = field; } _currentHolder.addParameter(parameter); parameterName.staticElement = parameter; } // // The children of this parameter include any parameters defined on the type // of this parameter. // ElementHolder holder = new ElementHolder(); _visitChildren(holder, node); (node.element as ParameterElementImpl).parameters = holder.parameters; holder.validate(); return null; } @override 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; } FunctionBody body = expression.body; sc.Token property = node.propertyKeyword; if (property == null || _inFunction) { SimpleIdentifier functionName = node.name; FunctionElementImpl element = new FunctionElementImpl.forNode(functionName); element.functions = holder.functions; element.labels = holder.labels; element.localVariables = holder.localVariables; element.parameters = holder.parameters; if (body.isAsynchronous) { element.asynchronous = true; } if (body.isGenerator) { element.generator = true; } if (_inFunction) { Block enclosingBlock = node.getAncestor((node) => node is 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) { // TODO(brianwilkerson) Report this internal error. return null; } String propertyName = propertyNameNode.name; TopLevelVariableElementImpl variable = _currentHolder .getTopLevelVariable(propertyName) as TopLevelVariableElementImpl; if (variable == null) { variable = new TopLevelVariableElementImpl(node.name.name, -1); variable.final2 = true; variable.synthetic = true; _currentHolder.addTopLevelVariable(variable); } if (node.isGetter) { PropertyAccessorElementImpl getter = new PropertyAccessorElementImpl.forNode(propertyNameNode); getter.functions = holder.functions; getter.labels = holder.labels; getter.localVariables = holder.localVariables; if (body.isAsynchronous) { getter.asynchronous = true; } if (body.isGenerator) { getter.generator = true; } 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.forNode(propertyNameNode); setter.functions = holder.functions; setter.labels = holder.labels; setter.localVariables = holder.localVariables; setter.parameters = holder.parameters; if (body.isAsynchronous) { setter.asynchronous = true; } if (body.isGenerator) { setter.generator = true; } 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; } @override Object visitFunctionExpression(FunctionExpression node) { ElementHolder holder = new ElementHolder(); bool wasInFunction = _inFunction; _inFunction = true; try { _visitChildren(holder, node); } finally { _inFunction = wasInFunction; } FunctionBody body = node.body; FunctionElementImpl element = new FunctionElementImpl.forOffset(node.beginToken.offset); element.functions = holder.functions; element.labels = holder.labels; element.localVariables = holder.localVariables; element.parameters = holder.parameters; if (body.isAsynchronous) { element.asynchronous = true; } if (body.isGenerator) { element.generator = true; } if (_inFunction) { Block enclosingBlock = node.getAncestor((node) => node is 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; } @override Object visitFunctionTypeAlias(FunctionTypeAlias node) { ElementHolder holder = new ElementHolder(); _visitChildren(holder, node); SimpleIdentifier aliasName = node.name; List parameters = holder.parameters; List typeParameters = holder.typeParameters; FunctionTypeAliasElementImpl element = new FunctionTypeAliasElementImpl.forNode(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; } @override Object visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) { if (node.parent is! DefaultFormalParameter) { SimpleIdentifier parameterName = node.identifier; ParameterElementImpl parameter = new ParameterElementImpl.forNode(parameterName); parameter.parameterKind = node.kind; _setParameterVisibleRange(node, parameter); _currentHolder.addParameter(parameter); parameterName.staticElement = parameter; } // // The children of this parameter include any parameters defined on the type //of this parameter. // ElementHolder holder = new ElementHolder(); _visitChildren(holder, node); (node.element as ParameterElementImpl).parameters = holder.parameters; holder.validate(); return null; } @override Object visitInstanceCreationExpression(InstanceCreationExpression node) { if (node.isConst) { node.constantHandle = new ConstantInstanceCreationHandle(); } return super.visitInstanceCreationExpression(node); } @override 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); } @override Object visitMethodDeclaration(MethodDeclaration node) { try { 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; FunctionBody body = node.body; 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(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; if (body.isAsynchronous) { element.asynchronous = true; } if (body.isGenerator) { element.generator = true; } _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(node.name.name, -1); field.final2 = true; field.static = isStatic; field.synthetic = true; _currentHolder.addField(field); } if (node.isGetter) { PropertyAccessorElementImpl getter = new PropertyAccessorElementImpl.forNode(propertyNameNode); getter.functions = holder.functions; getter.labels = holder.labels; getter.localVariables = holder.localVariables; if (body.isAsynchronous) { getter.asynchronous = true; } if (body.isGenerator) { getter.generator = true; } getter.variable = field; getter.abstract = node.isAbstract; getter.getter = true; getter.static = isStatic; field.getter = getter; _currentHolder.addAccessor(getter); propertyNameNode.staticElement = getter; } else { PropertyAccessorElementImpl setter = new PropertyAccessorElementImpl.forNode(propertyNameNode); setter.functions = holder.functions; setter.labels = holder.labels; setter.localVariables = holder.localVariables; setter.parameters = holder.parameters; if (body.isAsynchronous) { setter.asynchronous = true; } if (body.isGenerator) { setter.generator = true; } setter.variable = field; setter.abstract = node.isAbstract; setter.setter = true; setter.static = isStatic; field.setter = setter; field.final2 = false; _currentHolder.addAccessor(setter); propertyNameNode.staticElement = setter; } } holder.validate(); } catch (exception, stackTrace) { if (node.name.staticElement == null) { ClassDeclaration classNode = node.getAncestor((node) => node is ClassDeclaration); StringBuffer buffer = new StringBuffer(); buffer.write("The element for the method "); buffer.write(node.name); buffer.write(" in "); buffer.write(classNode.name); buffer.write(" was not set while trying to build the element model."); AnalysisEngine.instance.logger.logError( buffer.toString(), new CaughtException(exception, stackTrace)); } else { String message = "Exception caught in ElementBuilder.visitMethodDeclaration()"; AnalysisEngine.instance.logger.logError( message, new CaughtException(exception, stackTrace)); } } finally { if (node.name.staticElement == null) { ClassDeclaration classNode = node.getAncestor((node) => node is ClassDeclaration); StringBuffer buffer = new StringBuffer(); buffer.write("The element for the method "); buffer.write(node.name); buffer.write(" in "); buffer.write(classNode.name); buffer.write(" was not set while trying to resolve types."); AnalysisEngine.instance.logger.logError(buffer.toString(), new CaughtException( new AnalysisException(buffer.toString()), null)); } } return null; } @override Object visitSimpleFormalParameter(SimpleFormalParameter node) { if (node.parent is! DefaultFormalParameter) { SimpleIdentifier parameterName = node.identifier; ParameterElementImpl parameter = new ParameterElementImpl.forNode(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); } @override Object visitSuperExpression(SuperExpression node) { _isValidMixin = false; return super.visitSuperExpression(node); } @override 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); } @override 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); } @override Object visitTypeParameter(TypeParameter node) { SimpleIdentifier parameterName = node.name; TypeParameterElementImpl typeParameter = new TypeParameterElementImpl.forNode(parameterName); TypeParameterTypeImpl typeParameterType = new TypeParameterTypeImpl(typeParameter); typeParameter.type = typeParameterType; _currentHolder.addTypeParameter(typeParameter); parameterName.staticElement = typeParameter; return super.visitTypeParameter(node); } @override Object visitVariableDeclaration(VariableDeclaration node) { bool isConst = node.isConst; bool isFinal = node.isFinal; bool hasInitializer = node.initializer != null; VariableElementImpl element; if (_inFieldContext) { SimpleIdentifier fieldName = node.name; FieldElementImpl field; if (isConst && hasInitializer) { field = new ConstFieldElementImpl.con1(fieldName); } else { field = new FieldElementImpl.forNode(fieldName); } element = field; _currentHolder.addField(field); fieldName.staticElement = field; } else if (_inFunction) { SimpleIdentifier variableName = node.name; LocalVariableElementImpl variable; if (isConst && hasInitializer) { variable = new ConstLocalVariableElementImpl.forNode(variableName); } else { variable = new LocalVariableElementImpl.forNode(variableName); } element = variable; Block enclosingBlock = node.getAncestor((node) => node is Block); // TODO(brianwilkerson) This isn't right for variables declared in a for // loop. variable.setVisibleRange(enclosingBlock.offset, enclosingBlock.length); _currentHolder.addLocalVariable(variable); variableName.staticElement = element; } else { SimpleIdentifier variableName = node.name; TopLevelVariableElementImpl variable; if (isConst && hasInitializer) { variable = new ConstTopLevelVariableElementImpl(variableName); } else { variable = new TopLevelVariableElementImpl.forNode(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.forOffset(node.initializer.beginToken.offset); initializer.functions = holder.functions; initializer.labels = holder.labels; initializer.localVariables = holder.localVariables; initializer.synthetic = true; element.initializer = initializer; holder.validate(); } if (element is PropertyInducingElementImpl) { if (_inFieldContext) { (element as FieldElementImpl).static = (node.parent.parent as FieldDeclaration).isStatic; } PropertyAccessorElementImpl getter = new PropertyAccessorElementImpl.forVariable(element); getter.getter = true; _currentHolder.addAccessor(getter); element.getter = getter; if (!isConst && !isFinal) { PropertyAccessorElementImpl setter = new PropertyAccessorElementImpl.forVariable(element); setter.setter = true; ParameterElementImpl parameter = new ParameterElementImpl("_${element.name}", element.nameOffset); parameter.synthetic = true; parameter.parameterKind = ParameterKind.REQUIRED; setter.parameters = [parameter]; _currentHolder.addAccessor(setter); element.setter = setter; } } return null; } /** * Build the table mapping field names to field elements for the fields defined in the current * class. * * @param fields the field elements defined in the current class */ void _buildFieldMap(List fields) { _fieldMap = new HashMap(); int count = fields.length; for (int i = 0; i < count; i++) { FieldElement field = fields[i]; _fieldMap[field.name] = field; } } /** * 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 _createDefaultConstructors( InterfaceTypeImpl interfaceType) { ConstructorElementImpl constructor = new ConstructorElementImpl.forNode(null); constructor.synthetic = true; constructor.returnType = interfaceType; FunctionTypeImpl type = new FunctionTypeImpl.con1(constructor); _functionTypesToFix.add(type); constructor.type = type; return [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 _createTypeParameterTypes( List typeParameters) { int typeParameterCount = typeParameters.length; List typeArguments = new List(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.body; } else if (parent is FunctionExpression) { return parent.body; } else if (parent is MethodDeclaration) { return parent.body; } parent = parent.parent; } return null; } /** * 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. */ class ElementHolder { List _accessors; List _constructors; List _enums; List _fields; List _functions; List _labels; List _localVariables; List _methods; List _parameters; List _topLevelVariables; List _types; List _typeAliases; List _typeParameters; List get accessors { if (_accessors == null) { return PropertyAccessorElementImpl.EMPTY_ARRAY; } List result = _accessors; _accessors = null; return result; } List get constructors { if (_constructors == null) { return ConstructorElementImpl.EMPTY_ARRAY; } List result = _constructors; _constructors = null; return result; } List get enums { if (_enums == null) { return ClassElementImpl.EMPTY_ARRAY; } List result = _enums; _enums = null; return result; } List get fields { if (_fields == null) { return FieldElementImpl.EMPTY_ARRAY; } List result = _fields; _fields = null; return result; } List get fieldsWithoutFlushing { if (_fields == null) { return FieldElementImpl.EMPTY_ARRAY; } List result = _fields; return result; } List get functions { if (_functions == null) { return FunctionElementImpl.EMPTY_ARRAY; } List result = _functions; _functions = null; return result; } List get labels { if (_labels == null) { return LabelElementImpl.EMPTY_ARRAY; } List result = _labels; _labels = null; return result; } List get localVariables { if (_localVariables == null) { return LocalVariableElementImpl.EMPTY_ARRAY; } List result = _localVariables; _localVariables = null; return result; } List get methods { if (_methods == null) { return MethodElementImpl.EMPTY_ARRAY; } List result = _methods; _methods = null; return result; } List get parameters { if (_parameters == null) { return ParameterElementImpl.EMPTY_ARRAY; } List result = _parameters; _parameters = null; return result; } List get topLevelVariables { if (_topLevelVariables == null) { return TopLevelVariableElementImpl.EMPTY_ARRAY; } List result = _topLevelVariables; _topLevelVariables = null; return result; } List get typeAliases { if (_typeAliases == null) { return FunctionTypeAliasElementImpl.EMPTY_ARRAY; } List result = _typeAliases; _typeAliases = null; return result; } List get typeParameters { if (_typeParameters == null) { return TypeParameterElementImpl.EMPTY_ARRAY; } List result = _typeParameters; _typeParameters = null; return result; } List get types { if (_types == null) { return ClassElementImpl.EMPTY_ARRAY; } List result = _types; _types = null; return result; } void addAccessor(PropertyAccessorElement element) { if (_accessors == null) { _accessors = new List(); } _accessors.add(element); } void addConstructor(ConstructorElement element) { if (_constructors == null) { _constructors = new List(); } _constructors.add(element); } void addEnum(ClassElement element) { if (_enums == null) { _enums = new List(); } _enums.add(element); } void addField(FieldElement element) { if (_fields == null) { _fields = new List(); } _fields.add(element); } void addFunction(FunctionElement element) { if (_functions == null) { _functions = new List(); } _functions.add(element); } void addLabel(LabelElement element) { if (_labels == null) { _labels = new List(); } _labels.add(element); } void addLocalVariable(LocalVariableElement element) { if (_localVariables == null) { _localVariables = new List(); } _localVariables.add(element); } void addMethod(MethodElement element) { if (_methods == null) { _methods = new List(); } _methods.add(element); } void addParameter(ParameterElement element) { if (_parameters == null) { _parameters = new List(); } _parameters.add(element); } void addTopLevelVariable(TopLevelVariableElement element) { if (_topLevelVariables == null) { _topLevelVariables = new List(); } _topLevelVariables.add(element); } void addType(ClassElement element) { if (_types == null) { _types = new List(); } _types.add(element); } void addTypeAlias(FunctionTypeAliasElement element) { if (_typeAliases == null) { _typeAliases = new List(); } _typeAliases.add(element); } void addTypeParameter(TypeParameterElement element) { if (_typeParameters == null) { _typeParameters = new List(); } _typeParameters.add(element); } FieldElement getField(String fieldName) { if (_fields == null) { return null; } for (FieldElement field in _fields) { if (field.name == fieldName) { return field; } } return null; } TopLevelVariableElement getTopLevelVariable(String variableName) { if (_topLevelVariables == null) { return null; } for (TopLevelVariableElement variable in _topLevelVariables) { if (variable.name == variableName) { return variable; } } return null; } void validate() { StringBuffer buffer = new StringBuffer(); if (_accessors != null) { buffer.write(_accessors.length); buffer.write(" accessors"); } if (_constructors != null) { if (buffer.length > 0) { buffer.write("; "); } buffer.write(_constructors.length); buffer.write(" constructors"); } if (_fields != null) { if (buffer.length > 0) { buffer.write("; "); } buffer.write(_fields.length); buffer.write(" fields"); } if (_functions != null) { if (buffer.length > 0) { buffer.write("; "); } buffer.write(_functions.length); buffer.write(" functions"); } if (_labels != null) { if (buffer.length > 0) { buffer.write("; "); } buffer.write(_labels.length); buffer.write(" labels"); } if (_localVariables != null) { if (buffer.length > 0) { buffer.write("; "); } buffer.write(_localVariables.length); buffer.write(" local variables"); } if (_methods != null) { if (buffer.length > 0) { buffer.write("; "); } buffer.write(_methods.length); buffer.write(" methods"); } if (_parameters != null) { if (buffer.length > 0) { buffer.write("; "); } buffer.write(_parameters.length); buffer.write(" parameters"); } if (_topLevelVariables != null) { if (buffer.length > 0) { buffer.write("; "); } buffer.write(_topLevelVariables.length); buffer.write(" top-level variables"); } if (_types != null) { if (buffer.length > 0) { buffer.write("; "); } buffer.write(_types.length); buffer.write(" types"); } if (_typeAliases != null) { if (buffer.length > 0) { buffer.write("; "); } buffer.write(_typeAliases.length); buffer.write(" type aliases"); } if (_typeParameters != null) { if (buffer.length > 0) { buffer.write("; "); } buffer.write(_typeParameters.length); buffer.write(" type parameters"); } if (buffer.length > 0) { AnalysisEngine.instance.logger .logError("Failed to capture elements: $buffer"); } } } /** * Instances of the class `EnclosedScope` implement a scope that is lexically enclosed in * another scope. */ class EnclosedScope extends Scope { /** * The scope in which this scope is lexically enclosed. */ final Scope enclosingScope; /** * A table mapping names that will be defined in this scope, but right now are not initialized. * According to the scoping rules these names are hidden, even if they were defined in an outer * scope. */ HashMap _hiddenElements = new HashMap(); /** * A flag indicating whether there are any names defined in this scope. */ bool _hasHiddenName = false; /** * Initialize a newly created scope enclosed within another scope. * * @param enclosingScope the scope in which this scope is lexically enclosed */ EnclosedScope(this.enclosingScope); @override AnalysisErrorListener get errorListener => enclosingScope.errorListener; /** * Record that given element is declared in this scope, but hasn't been initialized yet, so it is * error to use. If there is already an element with the given name defined in an outer scope, * then it will become unavailable. * * @param element the element declared, but not initialized in this scope */ void hide(Element element) { if (element != null) { String name = element.name; if (name != null && !name.isEmpty) { _hiddenElements[name] = element; _hasHiddenName = true; } } } @override Element internalLookup( Identifier identifier, String name, LibraryElement referencingLibrary) { Element element = localLookup(name, referencingLibrary); if (element != null) { return element; } // May be there is a hidden Element. if (_hasHiddenName) { Element hiddenElement = _hiddenElements[name]; if (hiddenElement != null) { errorListener.onError(new AnalysisError.con2(getSource(identifier), identifier.offset, identifier.length, CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, [])); return hiddenElement; } } // Check enclosing scope. return enclosingScope.internalLookup(identifier, name, referencingLibrary); } } /** * Instances of the class `EnumMemberBuilder` build the members in enum declarations. */ class EnumMemberBuilder extends RecursiveAstVisitor { /** * The type provider used to access the types needed to build an element model for enum * declarations. */ final TypeProvider _typeProvider; /** * Initialize a newly created enum member builder. * * @param typeProvider the type provider used to access the types needed to build an element model * for enum declarations */ EnumMemberBuilder(this._typeProvider); @override Object visitEnumDeclaration(EnumDeclaration node) { // // Finish building the enum. // ClassElementImpl enumElement = node.name.staticElement as ClassElementImpl; InterfaceType enumType = enumElement.type; enumElement.supertype = _typeProvider.objectType; // // Populate the fields. // List fields = new List(); List getters = new List(); InterfaceType intType = _typeProvider.intType; String indexFieldName = "index"; FieldElementImpl indexField = new FieldElementImpl(indexFieldName, -1); indexField.final2 = true; indexField.synthetic = true; indexField.type = intType; fields.add(indexField); getters.add(_createGetter(indexField)); ConstFieldElementImpl valuesField = new ConstFieldElementImpl.con2("values", -1); valuesField.static = true; valuesField.const3 = true; valuesField.synthetic = true; valuesField.type = _typeProvider.listType.substitute4([enumType]); fields.add(valuesField); getters.add(_createGetter(valuesField)); // // Build the enum constants. // NodeList constants = node.constants; List constantValues = new List(); int constantCount = constants.length; for (int i = 0; i < constantCount; i++) { SimpleIdentifier constantName = constants[i].name; FieldElementImpl constantField = new ConstFieldElementImpl.con1(constantName); constantField.static = true; constantField.const3 = true; constantField.type = enumType; // // Create a value for the constant. // HashMap fieldMap = new HashMap(); fieldMap[indexFieldName] = new DartObjectImpl(intType, new IntState(i)); DartObjectImpl value = new DartObjectImpl(enumType, new GenericState(fieldMap)); constantValues.add(value); constantField.evaluationResult = new EvaluationResultImpl.con1(value); fields.add(constantField); getters.add(_createGetter(constantField)); constantName.staticElement = constantField; } // // Build the value of the 'values' field. // valuesField.evaluationResult = new EvaluationResultImpl.con1( new DartObjectImpl(valuesField.type, new ListState(constantValues))); // // Finish building the enum. // enumElement.fields = fields; enumElement.accessors = getters; // Client code isn't allowed to invoke the constructor, so we do not model // it. return super.visitEnumDeclaration(node); } /** * Create a getter that corresponds to the given field. * * @param field the field for which a getter is to be created * @return the getter that was created */ PropertyAccessorElement _createGetter(FieldElementImpl field) { PropertyAccessorElementImpl getter = new PropertyAccessorElementImpl.forVariable(field); getter.getter = true; getter.returnType = field.type; getter.type = new FunctionTypeImpl.con1(getter); field.getter = getter; return getter; } } /** * Instances of the class `ExitDetector` determine whether the visited AST node is guaranteed * to terminate by executing a `return` statement, `throw` expression, `rethrow` * expression, or simple infinite loop such as `while(true)`. */ class ExitDetector extends GeneralizingAstVisitor { /** * Set to `true` when a `break` is encountered, and reset to `false` when a * `do`, `while`, `for` or `switch` block is entered. */ bool _enclosingBlockContainsBreak = false; @override bool visitArgumentList(ArgumentList node) => _visitExpressions(node.arguments); @override bool visitAsExpression(AsExpression node) => _nodeExits(node.expression); @override bool visitAssertStatement(AssertStatement node) => _nodeExits(node.condition); @override bool visitAssignmentExpression(AssignmentExpression node) => _nodeExits(node.leftHandSide) || _nodeExits(node.rightHandSide); @override bool visitAwaitExpression(AwaitExpression node) => _nodeExits(node.expression); @override bool visitBinaryExpression(BinaryExpression node) { Expression lhsExpression = node.leftOperand; sc.TokenType operatorType = node.operator.type; // If the operator is || and the left hand side is false literal, don't // consider the RHS of the binary expression. // TODO(jwren) Do we want to take constant expressions into account, // evaluate if(false) {} differently than if(), when // evaluates to a constant false value? if (operatorType == sc.TokenType.BAR_BAR) { if (lhsExpression is BooleanLiteral) { BooleanLiteral booleanLiteral = lhsExpression; if (!booleanLiteral.value) { return false; } } } // If the operator is && and the left hand side is true literal, don't // consider the RHS of the binary expression. if (operatorType == sc.TokenType.AMPERSAND_AMPERSAND) { if (lhsExpression is BooleanLiteral) { BooleanLiteral booleanLiteral = lhsExpression; if (booleanLiteral.value) { return false; } } } Expression rhsExpression = node.rightOperand; return _nodeExits(lhsExpression) || _nodeExits(rhsExpression); } @override bool visitBlock(Block node) => _visitStatements(node.statements); @override bool visitBlockFunctionBody(BlockFunctionBody node) => _nodeExits(node.block); @override bool visitBreakStatement(BreakStatement node) { _enclosingBlockContainsBreak = true; return false; } @override bool visitCascadeExpression(CascadeExpression node) => _nodeExits(node.target) || _visitExpressions(node.cascadeSections); @override bool visitConditionalExpression(ConditionalExpression node) { Expression conditionExpression = node.condition; Expression thenStatement = node.thenExpression; Expression elseStatement = node.elseExpression; // TODO(jwren) Do we want to take constant expressions into account, // evaluate if(false) {} differently than if(), when // evaluates to a constant false value? if (_nodeExits(conditionExpression)) { return true; } if (thenStatement == null || elseStatement == null) { return false; } return thenStatement.accept(this) && elseStatement.accept(this); } @override bool visitContinueStatement(ContinueStatement node) => false; @override bool visitDoStatement(DoStatement node) { bool outerBreakValue = _enclosingBlockContainsBreak; _enclosingBlockContainsBreak = false; try { Expression conditionExpression = node.condition; if (_nodeExits(conditionExpression)) { return true; } // TODO(jwren) Do we want to take all constant expressions into account? if (conditionExpression is BooleanLiteral) { BooleanLiteral booleanLiteral = conditionExpression; // If do {} while (true), and the body doesn't return or the body // doesn't have a break, then return true. bool blockReturns = _nodeExits(node.body); if (booleanLiteral.value && (blockReturns || !_enclosingBlockContainsBreak)) { return true; } } return false; } finally { _enclosingBlockContainsBreak = outerBreakValue; } } @override bool visitEmptyStatement(EmptyStatement node) => false; @override bool visitExpressionStatement(ExpressionStatement node) => _nodeExits(node.expression); @override bool visitForEachStatement(ForEachStatement node) { bool outerBreakValue = _enclosingBlockContainsBreak; _enclosingBlockContainsBreak = false; try { return _nodeExits(node.iterable); } finally { _enclosingBlockContainsBreak = outerBreakValue; } } @override bool visitForStatement(ForStatement node) { bool outerBreakValue = _enclosingBlockContainsBreak; _enclosingBlockContainsBreak = false; try { if (node.variables != null && _visitVariableDeclarations(node.variables.variables)) { return true; } if (node.initialization != null && _nodeExits(node.initialization)) { return true; } Expression conditionExpression = node.condition; if (conditionExpression != null && _nodeExits(conditionExpression)) { return true; } if (_visitExpressions(node.updaters)) { return true; } // TODO(jwren) Do we want to take all constant expressions into account? // If for(; true; ) (or for(;;)), and the body doesn't return or the body // doesn't have a break, then return true. bool implicitOrExplictTrue = conditionExpression == null || (conditionExpression is BooleanLiteral && conditionExpression.value); if (implicitOrExplictTrue) { bool blockReturns = _nodeExits(node.body); if (blockReturns || !_enclosingBlockContainsBreak) { return true; } } return false; } finally { _enclosingBlockContainsBreak = outerBreakValue; } } @override bool visitFunctionDeclarationStatement(FunctionDeclarationStatement node) => false; @override bool visitFunctionExpression(FunctionExpression node) => false; @override bool visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { if (_nodeExits(node.function)) { return true; } return node.argumentList.accept(this); } @override bool visitIdentifier(Identifier node) => false; @override bool visitIfStatement(IfStatement node) { Expression conditionExpression = node.condition; Statement thenStatement = node.thenStatement; Statement elseStatement = node.elseStatement; if (_nodeExits(conditionExpression)) { return true; } // TODO(jwren) Do we want to take all constant expressions into account? if (conditionExpression is BooleanLiteral) { BooleanLiteral booleanLiteral = conditionExpression; if (booleanLiteral.value) { // if(true) ... return _nodeExits(thenStatement); } else if (elseStatement != null) { // if (false) ... return _nodeExits(elseStatement); } } if (thenStatement == null || elseStatement == null) { return false; } return _nodeExits(thenStatement) && _nodeExits(elseStatement); } @override bool visitIndexExpression(IndexExpression node) { Expression target = node.realTarget; if (_nodeExits(target)) { return true; } if (_nodeExits(node.index)) { return true; } return false; } @override bool visitInstanceCreationExpression(InstanceCreationExpression node) => _nodeExits(node.argumentList); @override bool visitIsExpression(IsExpression node) => node.expression.accept(this); @override bool visitLabel(Label node) => false; @override bool visitLabeledStatement(LabeledStatement node) => node.statement.accept(this); @override bool visitLiteral(Literal node) => false; @override bool visitMethodInvocation(MethodInvocation node) { Expression target = node.realTarget; if (target != null && target.accept(this)) { return true; } return _nodeExits(node.argumentList); } @override bool visitNamedExpression(NamedExpression node) => node.expression.accept(this); @override bool visitParenthesizedExpression(ParenthesizedExpression node) => node.expression.accept(this); @override bool visitPostfixExpression(PostfixExpression node) => false; @override bool visitPrefixExpression(PrefixExpression node) => false; @override bool visitPropertyAccess(PropertyAccess node) { Expression target = node.realTarget; if (target != null && target.accept(this)) { return true; } return false; } @override bool visitRethrowExpression(RethrowExpression node) => true; @override bool visitReturnStatement(ReturnStatement node) => true; @override bool visitSuperExpression(SuperExpression node) => false; @override bool visitSwitchCase(SwitchCase node) => _visitStatements(node.statements); @override bool visitSwitchDefault(SwitchDefault node) => _visitStatements(node.statements); @override bool visitSwitchStatement(SwitchStatement node) { bool outerBreakValue = _enclosingBlockContainsBreak; _enclosingBlockContainsBreak = false; try { bool hasDefault = false; List members = node.members; for (int i = 0; i < members.length; i++) { SwitchMember switchMember = members[i]; if (switchMember is SwitchDefault) { hasDefault = true; // If this is the last member and there are no statements, return // false if (switchMember.statements.isEmpty && i + 1 == members.length) { return false; } } // For switch members with no statements, don't visit the children, // otherwise, return false if no return is found in the children // statements. if (!switchMember.statements.isEmpty && !switchMember.accept(this)) { return false; } } // All of the members exit, determine whether there are possible cases // that are not caught by the members. DartType type = node.expression == null ? null : node.expression.bestType; if (type is InterfaceType) { ClassElement element = type.element; if (element != null && element.isEnum) { // If some of the enum values are not covered, then a warning will // have already been generated, so there's no point in generating a // hint. return true; } } return hasDefault; } finally { _enclosingBlockContainsBreak = outerBreakValue; } } @override bool visitThisExpression(ThisExpression node) => false; @override bool visitThrowExpression(ThrowExpression node) => true; @override bool visitTryStatement(TryStatement node) { if (_nodeExits(node.body)) { return true; } Block finallyBlock = node.finallyBlock; if (_nodeExits(finallyBlock)) { return true; } return false; } @override bool visitTypeName(TypeName node) => false; @override bool visitVariableDeclaration(VariableDeclaration node) { Expression initializer = node.initializer; if (initializer != null) { return initializer.accept(this); } return false; } @override bool visitVariableDeclarationList(VariableDeclarationList node) => _visitVariableDeclarations(node.variables); @override bool visitVariableDeclarationStatement(VariableDeclarationStatement node) { NodeList variables = node.variables.variables; for (int i = 0; i < variables.length; i++) { if (variables[i].accept(this)) { return true; } } return false; } @override bool visitWhileStatement(WhileStatement node) { bool outerBreakValue = _enclosingBlockContainsBreak; _enclosingBlockContainsBreak = false; try { Expression conditionExpression = node.condition; if (conditionExpression.accept(this)) { return true; } // TODO(jwren) Do we want to take all constant expressions into account? if (conditionExpression is BooleanLiteral) { BooleanLiteral booleanLiteral = conditionExpression; // If while(true), and the body doesn't return or the body doesn't have // a break, then return true. bool blockReturns = node.body.accept(this); if (booleanLiteral.value && (blockReturns || !_enclosingBlockContainsBreak)) { return true; } } return false; } finally { _enclosingBlockContainsBreak = outerBreakValue; } } /** * Return `true` if the given node exits. * * @param node the node being tested * @return `true` if the given node exits */ bool _nodeExits(AstNode node) { if (node == null) { return false; } return node.accept(this); } bool _visitExpressions(NodeList expressions) { for (int i = expressions.length - 1; i >= 0; i--) { if (expressions[i].accept(this)) { return true; } } return false; } bool _visitStatements(NodeList statements) { for (int i = statements.length - 1; i >= 0; i--) { if (statements[i].accept(this)) { return true; } } return false; } bool _visitVariableDeclarations( NodeList variableDeclarations) { for (int i = variableDeclarations.length - 1; i >= 0; i--) { if (variableDeclarations[i].accept(this)) { return true; } } return false; } /** * Return `true` if the given [node] exits. */ static bool exits(AstNode node) { return new ExitDetector()._nodeExits(node); } } /** * Instances of the class `FunctionScope` implement the scope defined by a function. */ class FunctionScope extends EnclosedScope { final ExecutableElement _functionElement; bool _parametersDefined = false; /** * Initialize a newly created scope enclosed within another scope. * * @param enclosingScope the scope in which this scope is lexically enclosed * @param functionElement the element representing the type represented by this scope */ FunctionScope(Scope enclosingScope, this._functionElement) : super(new EnclosedScope(enclosingScope)) { if (_functionElement == null) { throw new IllegalArgumentException("function element cannot be null"); } } /** * Define the parameters for the given function in the scope that encloses this function. */ void defineParameters() { if (_parametersDefined) { return; } _parametersDefined = true; Scope parameterScope = enclosingScope; for (ParameterElement parameter in _functionElement.parameters) { if (!parameter.isInitializingFormal) { parameterScope.define(parameter); } } } } /** * Instances of the class `FunctionTypeScope` implement the scope defined by a function type * alias. */ class FunctionTypeScope extends EnclosedScope { final FunctionTypeAliasElement _typeElement; bool _parametersDefined = false; /** * Initialize a newly created scope enclosed within another scope. * * @param enclosingScope the scope in which this scope is lexically enclosed * @param typeElement the element representing the type alias represented by this scope */ FunctionTypeScope(Scope enclosingScope, this._typeElement) : super(new EnclosedScope(enclosingScope)) { _defineTypeParameters(); } /** * Define the parameters for the function type alias. * * @param typeElement the element representing the type represented by this scope */ void defineParameters() { if (_parametersDefined) { return; } _parametersDefined = true; for (ParameterElement parameter in _typeElement.parameters) { define(parameter); } } /** * Define the type parameters for the function type alias. * * @param typeElement the element representing the type represented by this scope */ void _defineTypeParameters() { Scope typeParameterScope = enclosingScope; for (TypeParameterElement typeParameter in _typeElement.typeParameters) { typeParameterScope.define(typeParameter); } } } /** * A visitor that visits ASTs and fills [UsedImportedElements]. */ class GatherUsedImportedElementsVisitor extends RecursiveAstVisitor { final LibraryElement library; final UsedImportedElements usedElements = new UsedImportedElements(); GatherUsedImportedElementsVisitor(this.library); @override void visitExportDirective(ExportDirective node) { _visitMetadata(node.metadata); } @override void visitImportDirective(ImportDirective node) { _visitMetadata(node.metadata); } @override void visitLibraryDirective(LibraryDirective node) { _visitMetadata(node.metadata); } @override void visitPrefixedIdentifier(PrefixedIdentifier node) { // If the prefixed identifier references some A.B, where A is a library // prefix, then we can lookup the associated ImportDirective in // prefixElementMap and remove it from the unusedImports list. SimpleIdentifier prefixIdentifier = node.prefix; Element element = prefixIdentifier.staticElement; if (element is PrefixElement) { usedElements.prefixes.add(element); return; } // Otherwise, pass the prefixed identifier element and name onto // visitIdentifier. _visitIdentifier(element, prefixIdentifier.name); } @override void visitSimpleIdentifier(SimpleIdentifier node) { _visitIdentifier(node.staticElement, node.name); } void _visitIdentifier(Element element, String name) { if (element == null) { return; } // If the element is multiply defined then call this method recursively for // each of the conflicting elements. if (element is MultiplyDefinedElement) { MultiplyDefinedElement multiplyDefinedElement = element; for (Element elt in multiplyDefinedElement.conflictingElements) { _visitIdentifier(elt, name); } return; } else if (element is PrefixElement) { usedElements.prefixes.add(element); return; } else if (element.enclosingElement is! CompilationUnitElement) { // Identifiers that aren't a prefix element and whose enclosing element // isn't a CompilationUnit are ignored- this covers the case the // identifier is a relative-reference, a reference to an identifier not // imported by this library. return; } // Ignore if an unknown library. LibraryElement containingLibrary = element.library; if (containingLibrary == null) { return; } // Ignore if a local element. if (library == containingLibrary) { return; } // Remember the element. usedElements.elements.add(element); } /** * 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 annotations) { int count = annotations.length; for (int i = 0; i < count; i++) { annotations[i].accept(this); } } } /** * An [AstVisitor] that fills [UsedLocalElements]. */ class GatherUsedLocalElementsVisitor extends RecursiveAstVisitor { final UsedLocalElements usedElements = new UsedLocalElements(); final LibraryElement _enclosingLibrary; ClassElement _enclosingClass; ExecutableElement _enclosingExec; GatherUsedLocalElementsVisitor(this._enclosingLibrary); @override visitCatchClause(CatchClause node) { SimpleIdentifier exceptionParameter = node.exceptionParameter; SimpleIdentifier stackTraceParameter = node.stackTraceParameter; if (exceptionParameter != null) { Element element = exceptionParameter.staticElement; usedElements.addCatchException(element); if (stackTraceParameter != null || node.onKeyword == null) { usedElements.addElement(element); } } if (stackTraceParameter != null) { Element element = stackTraceParameter.staticElement; usedElements.addCatchStackTrace(element); } super.visitCatchClause(node); } @override visitClassDeclaration(ClassDeclaration node) { ClassElement enclosingClassOld = _enclosingClass; try { _enclosingClass = node.element; super.visitClassDeclaration(node); } finally { _enclosingClass = enclosingClassOld; } } @override visitFunctionDeclaration(FunctionDeclaration node) { ExecutableElement enclosingExecOld = _enclosingExec; try { _enclosingExec = node.element; super.visitFunctionDeclaration(node); } finally { _enclosingExec = enclosingExecOld; } } @override visitFunctionExpression(FunctionExpression node) { if (node.parent is! FunctionDeclaration) { usedElements.addElement(node.element); } super.visitFunctionExpression(node); } @override visitMethodDeclaration(MethodDeclaration node) { ExecutableElement enclosingExecOld = _enclosingExec; try { _enclosingExec = node.element; super.visitMethodDeclaration(node); } finally { _enclosingExec = enclosingExecOld; } } @override visitSimpleIdentifier(SimpleIdentifier node) { if (node.inDeclarationContext()) { return; } Element element = node.staticElement; bool isIdentifierRead = _isReadIdentifier(node); if (element is LocalVariableElement) { if (isIdentifierRead) { usedElements.addElement(element); } } else { _useIdentifierElement(node); if (element == null || element is! LocalElement && !identical(element, _enclosingExec)) { usedElements.members.add(node.name); if (isIdentifierRead) { usedElements.readMembers.add(node.name); } } } } @override visitTypeName(TypeName node) { _useIdentifierElement(node.name); } /** * Marks an [Element] of [node] as used in the library. */ void _useIdentifierElement(Identifier node) { Element element = node.staticElement; if (element == null) { return; } // check if a local element if (!identical(element.library, _enclosingLibrary)) { return; } // ignore references to an element from itself if (identical(element, _enclosingClass)) { return; } if (identical(element, _enclosingExec)) { return; } // ignore places where the element is not actually used if (node.parent is TypeName) { AstNode parent2 = node.parent.parent; if (parent2 is IsExpression) { return; } // We need to instantiate/extend/implement a class to actually use it. // OTOH, function type aliases are used to define closure structures. if (parent2 is VariableDeclarationList && element is ClassElement) { return; } } // OK usedElements.addElement(element); } static bool _isReadIdentifier(SimpleIdentifier node) { // not reading at all if (!node.inGetterContext()) { return false; } // check if useless reading AstNode parent = node.parent; if (parent.parent is ExpressionStatement && (parent is PrefixExpression || parent is PostfixExpression || parent is AssignmentExpression && parent.leftHandSide == node)) { // v++; // ++v; // v += 2; return false; } // OK return true; } } /** * 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]. */ class HintGenerator { final List _compilationUnits; final InternalAnalysisContext _context; final AnalysisErrorListener _errorListener; LibraryElement _library; GatherUsedImportedElementsVisitor _usedImportedElementsVisitor; bool _enableDart2JSHints = false; /** * The inheritance manager used to find overridden methods. */ InheritanceManager _manager; GatherUsedLocalElementsVisitor _usedLocalElementsVisitor; HintGenerator(this._compilationUnits, this._context, this._errorListener) { _library = _compilationUnits[0].element.library; _usedImportedElementsVisitor = new GatherUsedImportedElementsVisitor(_library); _enableDart2JSHints = _context.analysisOptions.dart2jsHint; _manager = new InheritanceManager(_compilationUnits[0].element.library); _usedLocalElementsVisitor = new GatherUsedLocalElementsVisitor(_library); } void generateForLibrary() { PerformanceStatistics.hints.makeCurrentWhile(() { for (CompilationUnit unit in _compilationUnits) { CompilationUnitElement element = unit.element; if (element != null) { _generateForCompilationUnit(unit, element.source); } } CompilationUnit definingUnit = _compilationUnits[0]; ErrorReporter definingUnitErrorReporter = new ErrorReporter(_errorListener, definingUnit.element.source); { ImportsVerifier importsVerifier = new ImportsVerifier(); importsVerifier.addImports(definingUnit); importsVerifier .removeUsedElements(_usedImportedElementsVisitor.usedElements); importsVerifier.generateDuplicateImportHints(definingUnitErrorReporter); importsVerifier.generateUnusedImportHints(definingUnitErrorReporter); } _library.accept(new UnusedLocalElementsVerifier( _errorListener, _usedLocalElementsVisitor.usedElements)); }); } void _generateForCompilationUnit(CompilationUnit unit, Source source) { ErrorReporter errorReporter = new ErrorReporter(_errorListener, source); unit.accept(_usedImportedElementsVisitor); // dead code analysis unit.accept(new DeadCodeVerifier(errorReporter)); unit.accept(_usedLocalElementsVisitor); // dart2js analysis if (_enableDart2JSHints) { unit.accept(new Dart2JSVerifier(errorReporter)); } // Dart best practices unit.accept( new BestPracticesVerifier(errorReporter, _context.typeProvider)); unit.accept(new OverrideVerifier(errorReporter, _manager)); // Find to-do comments new ToDoFinder(errorReporter).findIn(unit); // pub analysis // TODO(danrubel/jwren) Commented out until bugs in the pub verifier are // fixed // unit.accept(new PubVerifier(context, errorReporter)); } } /** * Instances of the class {@code HtmlTagInfo} record information about the tags used in an HTML * file. */ class HtmlTagInfo { /** * An array containing all of the tags used in the HTML file. */ List allTags; /** * A table mapping the id's defined in the HTML file to an array containing the names of tags with * that identifier. */ HashMap idToTagMap; /** * A table mapping the classes defined in the HTML file to an array containing the names of tags * with that class. */ HashMap> classToTagsMap; /** * Initialize a newly created information holder to hold the given information about the tags in * an HTML file. * * @param allTags an array containing all of the tags used in the HTML file * @param idToTagMap a table mapping the id's defined in the HTML file to an array containing the * names of tags with that identifier * @param classToTagsMap a table mapping the classes defined in the HTML file to an array * containing the names of tags with that class */ HtmlTagInfo(List allTags, HashMap idToTagMap, HashMap> classToTagsMap) { this.allTags = allTags; this.idToTagMap = idToTagMap; this.classToTagsMap = classToTagsMap; } /** * Return an array containing the tags that have the given class, or {@code null} if there are no * such tags. * * @return an array containing the tags that have the given class */ List getTagsWithClass(String identifier) { return classToTagsMap[identifier]; } /** * Return the tag that has the given identifier, or {@code null} if there is no such tag (the * identifier is not defined). * * @return the tag that has the given identifier */ String getTagWithId(String identifier) { return idToTagMap[identifier]; } } /** * Instances of the class {@code HtmlTagInfoBuilder} gather information about the tags used in one * or more HTML structures. */ class HtmlTagInfoBuilder implements ht.XmlVisitor { /** * The name of the 'id' attribute. */ static final String ID_ATTRIBUTE = "id"; /** * The name of the 'class' attribute. */ static final String ID_CLASS = "class"; /** * A set containing all of the tag names used in the HTML. */ HashSet tagSet = new HashSet(); /** * A table mapping the id's that are defined to the tag name with that id. */ HashMap idMap = new HashMap(); /** * A table mapping the classes that are defined to a set of the tag names with that class. */ HashMap> classMap = new HashMap>(); /** * Initialize a newly created HTML tag info builder. */ HtmlTagInfoBuilder(); /** * Create a tag information holder holding all of the information gathered about the tags in the * HTML structures that were visited. * * @return the information gathered about the tags in the visited HTML structures */ HtmlTagInfo getTagInfo() { List allTags = tagSet.toList(); HashMap> classToTagsMap = new HashMap>(); classMap.forEach((String key, Set tags) { classToTagsMap[key] = tags.toList(); }); return new HtmlTagInfo(allTags, idMap, classToTagsMap); } @override visitHtmlScriptTagNode(ht.HtmlScriptTagNode node) { visitXmlTagNode(node); } @override visitHtmlUnit(ht.HtmlUnit node) { node.visitChildren(this); } @override visitXmlAttributeNode(ht.XmlAttributeNode node) {} @override visitXmlTagNode(ht.XmlTagNode node) { node.visitChildren(this); String tagName = node.tag; tagSet.add(tagName); for (ht.XmlAttributeNode attribute in node.attributes) { String attributeName = attribute.name; if (attributeName == ID_ATTRIBUTE) { String attributeValue = attribute.text; if (attributeValue != null) { String tag = idMap[attributeValue]; if (tag == null) { idMap[attributeValue] = tagName; } else { // reportError(HtmlWarningCode.MULTIPLY_DEFINED_ID, valueToken); } } } else if (attributeName == ID_CLASS) { String attributeValue = attribute.text; if (attributeValue != null) { HashSet tagList = classMap[attributeValue]; if (tagList == null) { tagList = new HashSet(); classMap[attributeValue] = tagList; } else { // reportError(HtmlWarningCode.MULTIPLY_DEFINED_ID, valueToken); } tagList.add(tagName); } } } } // /** // * 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 // */ // private void reportError(ErrorCode errorCode, Token token, Object... arguments) { // errorListener.onError(new AnalysisError( // htmlElement.getSource(), // token.getOffset(), // token.getLength(), // errorCode, // arguments)); // } // // /** // * 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 // */ // private void reportError(ErrorCode errorCode, int offset, int length, Object... arguments) { // errorListener.onError(new AnalysisError( // htmlElement.getSource(), // offset, // length, // errorCode, // arguments)); // } } /** * Instances of the class `HtmlUnitBuilder` build an element model for a single HTML unit. */ class HtmlUnitBuilder implements ht.XmlVisitor { static String _SRC = "src"; /** * The analysis context in which the element model will be built. */ final InternalAnalysisContext _context; /** * The error listener to which errors will be reported. */ RecordingErrorListener _errorListener; /** * The HTML element being built. */ HtmlElementImpl _htmlElement; /** * The elements in the path from the HTML unit to the current tag node. */ List _parentNodes; /** * The script elements being built. */ List _scripts; /** * A set of the libraries that were resolved while resolving the HTML unit. */ Set _resolvedLibraries = new HashSet(); /** * Initialize a newly created HTML unit builder. * * @param context the analysis context in which the element model will be built */ HtmlUnitBuilder(this._context) { this._errorListener = new RecordingErrorListener(); } /** * Return the listener to which analysis errors will be reported. * * @return the listener to which analysis errors will be reported */ RecordingErrorListener get errorListener => _errorListener; /** * Return an array containing information about all of the libraries that were resolved. * * @return an array containing the libraries that were resolved */ Set get resolvedLibraries => _resolvedLibraries; /** * Build the HTML element for the given source. * * @param source the source describing the compilation unit * @param unit the AST structure representing the HTML * @throws AnalysisException if the analysis could not be performed */ HtmlElementImpl buildHtmlElement(Source source, ht.HtmlUnit unit) { HtmlElementImpl result = new HtmlElementImpl(_context, source.shortName); result.source = source; _htmlElement = result; unit.accept(this); _htmlElement = null; unit.element = result; return result; } @override Object visitHtmlScriptTagNode(ht.HtmlScriptTagNode node) { if (_parentNodes.contains(node)) { return _reportCircularity(node); } _parentNodes.add(node); try { Source htmlSource = _htmlElement.source; ht.XmlAttributeNode scriptAttribute = _getScriptSourcePath(node); String scriptSourcePath = scriptAttribute == null ? null : scriptAttribute.text; if (node.attributeEnd.type == ht.TokenType.GT && scriptSourcePath == null) { EmbeddedHtmlScriptElementImpl script = new EmbeddedHtmlScriptElementImpl(node); try { LibraryResolver resolver = new LibraryResolver(_context); LibraryElementImpl library = resolver.resolveEmbeddedLibrary(htmlSource, node.script, true); script.scriptLibrary = library; _resolvedLibraries.addAll(resolver.resolvedLibraries); _errorListener.addAll(resolver.errorListener); } on AnalysisException catch (exception, stackTrace) { //TODO (danrubel): Handle or forward the exception AnalysisEngine.instance.logger.logError( "Could not resolve script tag", new CaughtException(exception, stackTrace)); } node.scriptElement = script; _scripts.add(script); } else { ExternalHtmlScriptElementImpl script = new ExternalHtmlScriptElementImpl(node); if (scriptSourcePath != null) { try { scriptSourcePath = Uri.encodeFull(scriptSourcePath); // Force an exception to be thrown if the URI is invalid so that we // can report the problem. parseUriWithException(scriptSourcePath); Source scriptSource = _context.sourceFactory.resolveUri(htmlSource, scriptSourcePath); script.scriptSource = scriptSource; if (!_context.exists(scriptSource)) { _reportValueError(HtmlWarningCode.URI_DOES_NOT_EXIST, scriptAttribute, [scriptSourcePath]); } } on URISyntaxException { _reportValueError(HtmlWarningCode.INVALID_URI, scriptAttribute, [scriptSourcePath]); } } node.scriptElement = script; _scripts.add(script); } } finally { _parentNodes.remove(node); } return null; } @override Object visitHtmlUnit(ht.HtmlUnit node) { _parentNodes = new List(); _scripts = new List(); try { node.visitChildren(this); _htmlElement.scripts = new List.from(_scripts); } finally { _scripts = null; _parentNodes = null; } return null; } @override Object visitXmlAttributeNode(ht.XmlAttributeNode node) => null; @override Object visitXmlTagNode(ht.XmlTagNode node) { if (_parentNodes.contains(node)) { return _reportCircularity(node); } _parentNodes.add(node); try { 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 == _SRC) { return attribute; } } return null; } Object _reportCircularity(ht.XmlTagNode node) { // // This should not be possible, but we have an error report that suggests // that it happened at least once. This code will guard against infinite // recursion and might help us identify the cause of the issue. // StringBuffer buffer = new StringBuffer(); buffer.write("Found circularity in XML nodes: "); bool first = true; for (ht.XmlTagNode pathNode in _parentNodes) { if (first) { first = false; } else { buffer.write(", "); } String tagName = pathNode.tag; if (identical(pathNode, node)) { buffer.write("*"); buffer.write(tagName); buffer.write("*"); } else { buffer.write(tagName); } } AnalysisEngine.instance.logger.logError(buffer.toString()); return null; } /** * 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 _reportErrorForOffset( ErrorCode errorCode, int offset, int length, List 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 arguments) { int offset = attribute.valueToken.offset + 1; int length = attribute.valueToken.length - 2; _reportErrorForOffset(errorCode, offset, length, arguments); } } /** * Instances of the class `ImplicitConstructorBuilder` are used to build * implicit constructors for mixin applications, and to check for errors * related to super constructor calls in class declarations with mixins. * * The visitor methods don't directly build the implicit constructors or check * for errors, since they don't in general visit the classes in the proper * order to do so correctly. Instead, they pass closures to * ImplicitConstructorBuilderCallback to inform it of the computations to be * done and their ordering dependencies. */ class ImplicitConstructorBuilder extends SimpleElementVisitor { final AnalysisErrorListener errorListener; /** * Callback to receive the computations to be performed. */ final ImplicitConstructorBuilderCallback _callback; /** * Initialize a newly created visitor to build implicit constructors. * * The visit methods will pass closures to [_callback] to indicate what * computation needs to be performed, and its dependency order. */ ImplicitConstructorBuilder(this.errorListener, this._callback); @override void visitClassElement(ClassElementImpl classElement) { classElement.mixinErrorsReported = false; if (classElement.isTypedef) { _visitClassTypeAlias(classElement); } else { _visitClassDeclaration(classElement); } } @override void visitCompilationUnitElement(CompilationUnitElement element) { element.types.forEach(visitClassElement); } @override void visitLibraryElement(LibraryElement element) { element.units.forEach(visitCompilationUnitElement); } /** * Create an implicit constructor that is copied from the given constructor, but that is in the * given class. * * @param classType the class in which the implicit constructor is defined * @param explicitConstructor the constructor on which the implicit constructor is modeled * @param parameterTypes the types to be replaced when creating parameters * @param argumentTypes the types with which the parameters are to be replaced * @return the implicit constructor that was created */ ConstructorElement _createImplicitContructor(InterfaceType classType, ConstructorElement explicitConstructor, List parameterTypes, List argumentTypes) { ConstructorElementImpl implicitConstructor = new ConstructorElementImpl(explicitConstructor.name, -1); implicitConstructor.synthetic = true; implicitConstructor.redirectedConstructor = explicitConstructor; implicitConstructor.const2 = explicitConstructor.isConst; implicitConstructor.returnType = classType; List explicitParameters = explicitConstructor.parameters; int count = explicitParameters.length; if (count > 0) { List implicitParameters = new List(count); for (int i = 0; i < count; i++) { ParameterElement explicitParameter = explicitParameters[i]; ParameterElementImpl implicitParameter = new ParameterElementImpl(explicitParameter.name, -1); implicitParameter.const3 = explicitParameter.isConst; implicitParameter.final2 = explicitParameter.isFinal; implicitParameter.parameterKind = explicitParameter.parameterKind; implicitParameter.synthetic = true; implicitParameter.type = explicitParameter.type.substitute2(argumentTypes, parameterTypes); implicitParameters[i] = implicitParameter; } implicitConstructor.parameters = implicitParameters; } FunctionTypeImpl type = new FunctionTypeImpl.con1(implicitConstructor); type.typeArguments = classType.typeArguments; implicitConstructor.type = type; return implicitConstructor; } /** * Find all the constructors that should be forwarded from the given * [superType], to the class or mixin application [classElement], * and pass information about them to [callback]. * * Return true if some constructors were considered. (A false return value * can only happen if the supeclass is a built-in type, in which case it * can't be used as a mixin anyway). */ bool _findForwardedConstructors(ClassElementImpl classElement, InterfaceType superType, void callback( ConstructorElement explicitConstructor, List parameterTypes, List argumentTypes)) { ClassElement superclassElement = superType.element; List constructors = superclassElement.constructors; int count = constructors.length; if (count == 0) { return false; } List parameterTypes = TypeParameterTypeImpl.getTypes(superType.typeParameters); List argumentTypes = _getArgumentTypes(superType, parameterTypes); for (int i = 0; i < count; i++) { ConstructorElement explicitConstructor = constructors[i]; if (!explicitConstructor.isFactory && classElement.isSuperConstructorAccessible(explicitConstructor)) { callback(explicitConstructor, parameterTypes, argumentTypes); } } return true; } /** * Return a list of argument types that corresponds to the [parameterTypes] * and that are derived from the type arguments of the given [superType]. */ List _getArgumentTypes( InterfaceType superType, List parameterTypes) { DynamicTypeImpl dynamic = DynamicTypeImpl.instance; int parameterCount = parameterTypes.length; List types = new List(parameterCount); if (superType == null) { types = new List.filled(parameterCount, dynamic); } else { List typeArguments = superType.typeArguments; int argumentCount = math.min(typeArguments.length, parameterCount); for (int i = 0; i < argumentCount; i++) { types[i] = typeArguments[i]; } for (int i = argumentCount; i < parameterCount; i++) { types[i] = dynamic; } } return types; } void _visitClassDeclaration(ClassElementImpl classElement) { DartType superType = classElement.supertype; if (superType != null && classElement.mixins.isNotEmpty) { // We don't need to build any implicitly constructors for the mixin // application (since there isn't an explicit element for it), but we // need to verify that they _could_ be built. if (superType is! InterfaceType) { TypeProvider typeProvider = classElement.context.typeProvider; superType = typeProvider.objectType; } ClassElement superElement = superType.element; if (superElement != null) { _callback(classElement, superElement, () { bool constructorFound = false; void callback(ConstructorElement explicitConstructor, List parameterTypes, List argumentTypes) { constructorFound = true; } if (_findForwardedConstructors(classElement, superType, callback) && !constructorFound) { SourceRange withRange = classElement.withClauseRange; errorListener.onError(new AnalysisError.con2(classElement.source, withRange.offset, withRange.length, CompileTimeErrorCode.MIXIN_HAS_NO_CONSTRUCTORS, [superElement.name])); classElement.mixinErrorsReported = true; } }); } } } void _visitClassTypeAlias(ClassElementImpl classElement) { InterfaceType superType = classElement.supertype; if (superType is InterfaceType) { ClassElement superElement = superType.element; _callback(classElement, superElement, () { List implicitConstructors = new List(); void callback(ConstructorElement explicitConstructor, List parameterTypes, List argumentTypes) { implicitConstructors.add(_createImplicitContructor(classElement.type, explicitConstructor, parameterTypes, argumentTypes)); } if (_findForwardedConstructors(classElement, superType, callback)) { if (implicitConstructors.isEmpty) { errorListener.onError(new AnalysisError.con2(classElement.source, classElement.nameOffset, classElement.name.length, CompileTimeErrorCode.MIXIN_HAS_NO_CONSTRUCTORS, [superElement.name])); } else { classElement.constructors = implicitConstructors; } } }); } } } /** * An instance of this class is capable of running ImplicitConstructorBuilder * over all classes in a library cycle. */ class ImplicitConstructorComputer { /** * Directed graph of dependencies between classes that need to have their * implicit constructors computed. Each edge in the graph points from a * derived class to its superclass. Implicit constructors will be computed * for the superclass before they are compute for the derived class. */ DirectedGraph _dependencies = new DirectedGraph(); /** * Map from ClassElement to the function which will compute the class's * implicit constructors. */ Map _computations = new HashMap(); /** * Add the given [libraryElement] to the list of libraries which need to have * implicit constructors built for them. */ void add(AnalysisErrorListener errorListener, LibraryElement libraryElement) { libraryElement .accept(new ImplicitConstructorBuilder(errorListener, _defer)); } /** * Compute the implicit constructors for all compilation units that have been * passed to [add]. */ void compute() { List> topologicalSort = _dependencies.computeTopologicalSort(); for (List classesInCycle in topologicalSort) { // Note: a cycle could occur if there is a loop in the inheritance graph. // Such loops are forbidden by Dart but could occur in the analysis of // incorrect code. If this happens, we simply visit the classes // constituting the loop in any order. for (ClassElement classElement in classesInCycle) { VoidFunction computation = _computations[classElement]; if (computation != null) { computation(); } } } } /** * Defer execution of [computation], which builds implicit constructors for * [classElement], until after implicit constructors have been built for * [superclassElement]. */ void _defer(ClassElement classElement, ClassElement superclassElement, void computation()) { assert(!_computations.containsKey(classElement)); _computations[classElement] = computation; _dependencies.addEdge(classElement, superclassElement); } } /** * Instances of the class `ImplicitLabelScope` represent the scope statements * that can be the target of unlabeled break and continue statements. */ class ImplicitLabelScope { /** * The implicit label scope associated with the top level of a function. */ static const ImplicitLabelScope ROOT = const ImplicitLabelScope._(null, null); /** * The implicit label scope enclosing this implicit label scope. */ final ImplicitLabelScope outerScope; /** * The statement that acts as a target for break and/or continue statements * at this scoping level. */ final Statement statement; /** * Private constructor. */ const ImplicitLabelScope._(this.outerScope, this.statement); /** * Get the statement which should be the target of an unlabeled `break` or * `continue` statement, or `null` if there is no appropriate target. */ Statement getTarget(bool isContinue) { if (outerScope == null) { // This scope represents the toplevel of a function body, so it doesn't // match either break or continue. return null; } if (isContinue && statement is SwitchStatement) { return outerScope.getTarget(isContinue); } return statement; } /** * Initialize a newly created scope to represent a switch statement or loop * nested within the current scope. [statement] is the statement associated * with the newly created scope. */ ImplicitLabelScope nest(Statement statement) => new ImplicitLabelScope._(this, statement); } /** * 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. */ class ImportsVerifier /*extends RecursiveAstVisitor*/ { /** * 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]. */ final List _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. */ final List _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]. */ final HashMap> _libraryMap = new HashMap>(); /** * 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]. */ final HashMap _namespaceMap = new HashMap(); /** * This is a map between prefix elements and the import directives 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. * * TODO (jwren) Since multiple [ImportDirective]s can share the same [PrefixElement], * it is possible to have an unreported unused import in situations where two imports use the same * prefix and at least one import directive is used. */ final HashMap> _prefixElementMap = new HashMap>(); void addImports(CompilationUnit node) { for (Directive directive in node.directives) { if (directive is ImportDirective) { ImportDirective importDirective = directive; LibraryElement libraryElement = importDirective.uriElement; if (libraryElement != null) { _unusedImports.add(importDirective); // // Initialize prefixElementMap // if (importDirective.asKeyword != null) { SimpleIdentifier prefixIdentifier = importDirective.prefix; if (prefixIdentifier != null) { Element element = prefixIdentifier.staticElement; if (element is PrefixElement) { PrefixElement prefixElementKey = element; List list = _prefixElementMap[prefixElementKey]; if (list == null) { list = new List(); _prefixElementMap[prefixElementKey] = list; } list.add(importDirective); } // TODO (jwren) Can the element ever not be a PrefixElement? } } // // Initialize libraryMap: libraryElement -> importDirective // _putIntoLibraryMap(libraryElement, importDirective); // // For this new addition to the libraryMap, also recursively add any // exports from the libraryElement. // _addAdditionalLibrariesForExports( libraryElement, importDirective, new List()); } } } if (_unusedImports.length > 1) { // order the list of unusedImports to find duplicates in faster than // O(n^2) time List 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) { // Add either the currentDirective or nextDirective depending on which // comes second, this guarantees that the first of the duplicates // won't be highlighted. if (currentDirective.offset < nextDirective.offset) { _duplicateImports.add(nextDirective); } else { _duplicateImports.add(currentDirective); } } currentDirective = nextDirective; } } } /** * 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.reportErrorForNode( 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) { // Check that the import isn't dart:core ImportElement importElement = unusedImport.element; if (importElement != null) { LibraryElement libraryElement = importElement.importedLibrary; if (libraryElement != null && libraryElement.isDartCore) { continue; } } errorReporter.reportErrorForNode( HintCode.UNUSED_IMPORT, unusedImport.uri); } } /** * Remove elements from [_unusedImports] using the given [usedElements]. */ void removeUsedElements(UsedImportedElements usedElements) { // Stop if all the imports are known to be used. if (_unusedImports.isEmpty) { return; } // Process import prefixes. for (PrefixElement prefix in usedElements.prefixes) { List importDirectives = _prefixElementMap[prefix]; if (importDirectives != null) { for (ImportDirective importDirective in importDirectives) { _unusedImports.remove(importDirective); } } } // Process top-level elements. for (Element element in usedElements.elements) { // Stop if all the imports are known to be used. if (_unusedImports.isEmpty) { return; } // Prepare import directives for this library. LibraryElement library = element.library; List importsLibrary = _libraryMap[library]; if (importsLibrary == null) { continue; } // If there is only one import directive for this library, then it must be // the directive that this element is imported with, remove it from the // unusedImports list. if (importsLibrary.length == 1) { ImportDirective usedImportDirective = importsLibrary[0]; _unusedImports.remove(usedImportDirective); continue; } // Otherwise, find import directives using namespaces. String name = element.displayName; for (ImportDirective importDirective in importsLibrary) { Namespace namespace = _computeNamespace(importDirective); if (namespace != null && namespace.get(name) != null) { _unusedImports.remove(importDirective); } } } } /** * Recursively add any exported library elements into the [libraryMap]. */ void _addAdditionalLibrariesForExports(LibraryElement library, ImportDirective importDirective, List 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) { // If the namespace isn't in the namespaceMap, then compute and put it in // the map. ImportElement importElement = importDirective.element; if (importElement != null) { NamespaceBuilder builder = new NamespaceBuilder(); namespace = builder.createImportNamespaceForDirective(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 importList = _libraryMap[libraryElement]; if (importList == null) { importList = new List(); _libraryMap[libraryElement] = importList; } importList.add(importDirective); } } /** * Instances of the class `InheritanceManager` manage the knowledge of where class members * (methods, getters & setters) are inherited from. */ class InheritanceManager { /** * The [LibraryElement] that is managed by this manager. */ LibraryElement _library; /** * This is a mapping between each [ClassElement] and a map between the [String] member * names and the associated [ExecutableElement] in the mixin and superclass chain. */ HashMap _classLookup; /** * This is a mapping between each [ClassElement] and a map between the [String] member * names and the associated [ExecutableElement] in the interface set. */ HashMap _interfaceLookup; /** * A map between each visited [ClassElement] and the set of [AnalysisError]s found on * the class element. */ HashMap> _errorsInClassElement = new HashMap>(); /** * Initialize a newly created inheritance manager. * * @param library the library element context that the inheritance mappings are being generated */ InheritanceManager(LibraryElement library) { this._library = library; _classLookup = new HashMap(); _interfaceLookup = new HashMap(); } /** * Set the new library element context. * * @param library the new library element */ void set libraryElement(LibraryElement library) { this._library = library; } /** * Return the set of [AnalysisError]s found on the passed [ClassElement], or * `null` if there are none. * * @param classElt the class element to query * @return the set of [AnalysisError]s found on the passed [ClassElement], or * `null` if there are none */ HashSet getErrors(ClassElement classElt) => _errorsInClassElement[classElt]; /** * Get and return a mapping between the set of all string names of the members inherited from the * passed [ClassElement] superclass hierarchy, and the associated [ExecutableElement]. * * @param classElt the class element to query * @return a mapping between the set of all members inherited from the passed [ClassElement] * superclass hierarchy, and the associated [ExecutableElement] */ MemberMap getMapOfMembersInheritedFromClasses(ClassElement classElt) => _computeClassChainLookupMap(classElt, new HashSet()); /** * Get and return a mapping between the set of all string names of the members inherited from the * passed [ClassElement] interface hierarchy, and the associated [ExecutableElement]. * * @param classElt the class element to query * @return a mapping between the set of all string names of the members inherited from the passed * [ClassElement] interface hierarchy, and the associated [ExecutableElement]. */ MemberMap getMapOfMembersInheritedFromInterfaces(ClassElement classElt) => _computeInterfaceLookupMap(classElt, new HashSet()); /** * Given some [ClassElement] and some member name, this returns the * [ExecutableElement] that the class inherits from the mixins, * superclasses or interfaces, that has the member name, if no member is inherited `null` is * returned. * * @param classElt the class element to query * @param memberName the name of the executable element to find and return * @return the inherited executable element with the member name, or `null` if no such * member exists */ ExecutableElement lookupInheritance( ClassElement classElt, String memberName) { if (memberName == null || memberName.isEmpty) { return null; } ExecutableElement executable = _computeClassChainLookupMap( classElt, new HashSet()).get(memberName); if (executable == null) { return _computeInterfaceLookupMap(classElt, new HashSet()) .get(memberName); } return executable; } /** * Given some [ClassElement] and some member name, this returns the * [ExecutableElement] that the class either declares itself, or * inherits, that has the member name, if no member is inherited `null` is returned. * * @param classElt the class element to query * @param memberName the name of the executable element to find and return * @return the inherited executable element with the member name, or `null` if no such * member exists */ ExecutableElement lookupMember(ClassElement classElt, String memberName) { ExecutableElement element = _lookupMemberInClass(classElt, memberName); if (element != null) { return element; } return lookupInheritance(classElt, memberName); } /** * Given some [InterfaceType] and some member name, this returns the * [FunctionType] of the [ExecutableElement] that the * class either declares itself, or inherits, that has the member name, if no member is inherited * `null` is returned. The returned [FunctionType] has all type * parameters substituted with corresponding type arguments from the given [InterfaceType]. * * @param interfaceType the interface type to query * @param memberName the name of the executable element to find and return * @return the member's function type, or `null` if no such member exists */ FunctionType lookupMemberType( InterfaceType interfaceType, String memberName) { ExecutableElement iteratorMember = lookupMember(interfaceType.element, memberName); if (iteratorMember == null) { return null; } return substituteTypeArgumentsInMemberFromInheritance( iteratorMember.type, memberName, interfaceType); } /** * Determine the set of methods which is overridden by the given class member. If no member is * inherited, an empty list is returned. If one of the inherited members is a * [MultiplyInheritedExecutableElement], then it is expanded into its constituent inherited * elements. * * @param classElt the class to query * @param memberName the name of the class member to query * @return a list of overridden methods */ List lookupOverrides( ClassElement classElt, String memberName) { List result = new List(); if (memberName == null || memberName.isEmpty) { return result; } List interfaceMaps = _gatherInterfaceLookupMaps(classElt, new HashSet()); if (interfaceMaps != null) { for (MemberMap interfaceMap in interfaceMaps) { ExecutableElement overriddenElement = interfaceMap.get(memberName); if (overriddenElement != null) { if (overriddenElement is MultiplyInheritedExecutableElement) { MultiplyInheritedExecutableElement multiplyInheritedElement = overriddenElement; for (ExecutableElement element in multiplyInheritedElement.inheritedElements) { result.add(element); } } else { result.add(overriddenElement); } } } } return result; } /** * This method takes some inherited [FunctionType], and resolves all the parameterized types * in the function type, dependent on the class in which it is being overridden. * * @param baseFunctionType the function type that is being overridden * @param memberName the name of the member, this is used to lookup the inheritance path of the * override * @param definingType the type that is overriding the member * @return the passed function type with any parameterized types substituted */ FunctionType substituteTypeArgumentsInMemberFromInheritance( FunctionType baseFunctionType, String memberName, InterfaceType definingType) { // if the baseFunctionType is null, or does not have any parameters, // return it. if (baseFunctionType == null || baseFunctionType.typeArguments.length == 0) { return baseFunctionType; } // First, generate the path from the defining type to the overridden member Queue inheritancePath = new Queue(); _computeInheritancePath(inheritancePath, definingType, memberName); if (inheritancePath == null || inheritancePath.isEmpty) { // TODO(jwren) log analysis engine error return baseFunctionType; } FunctionType functionTypeToReturn = baseFunctionType; // loop backward through the list substituting as we go: while (!inheritancePath.isEmpty) { InterfaceType lastType = inheritancePath.removeLast(); List parameterTypes = lastType.element.type.typeArguments; List argumentTypes = lastType.typeArguments; functionTypeToReturn = functionTypeToReturn.substitute2(argumentTypes, parameterTypes); } return functionTypeToReturn; } /** * Compute and return a mapping between the set of all string names of the members inherited from * the passed [ClassElement] superclass hierarchy, and the associated * [ExecutableElement]. * * @param classElt the class element to query * @param visitedClasses a set of visited classes passed back into this method when it calls * itself recursively * @return a mapping between the set of all string names of the members inherited from the passed * [ClassElement] superclass hierarchy, and the associated [ExecutableElement] */ MemberMap _computeClassChainLookupMap( ClassElement classElt, HashSet visitedClasses) { MemberMap resultMap = _classLookup[classElt]; if (resultMap != null) { return resultMap; } else { resultMap = new MemberMap(); } ClassElement superclassElt = null; InterfaceType supertype = classElt.supertype; if (supertype != null) { superclassElt = supertype.element; } else { // classElt is Object _classLookup[classElt] = resultMap; return resultMap; } if (superclassElt != null) { if (!visitedClasses.contains(superclassElt)) { visitedClasses.add(superclassElt); try { resultMap = new MemberMap.con2( _computeClassChainLookupMap(superclassElt, visitedClasses)); // // Substitute the super types down the hierarchy. // _substituteTypeParametersDownHierarchy(supertype, resultMap); // // Include the members from the superclass in the resultMap. // _recordMapWithClassMembers(resultMap, supertype, false); } finally { visitedClasses.remove(superclassElt); } } else { // This case happens only when the superclass was previously visited and // not in the lookup, meaning this is meant to shorten the compute for // recursive cases. _classLookup[superclassElt] = resultMap; return resultMap; } } // // Include the members from the mixins in the resultMap. If there are // multiple mixins, visit them in the order listed so that methods in later // mixins will overwrite identically-named methods in earlier mixins. // List mixins = classElt.mixins; for (InterfaceType mixin in mixins) { ClassElement mixinElement = mixin.element; if (mixinElement != null) { if (!visitedClasses.contains(mixinElement)) { visitedClasses.add(mixinElement); try { MemberMap map = new MemberMap.con2( _computeClassChainLookupMap(mixinElement, visitedClasses)); // // Substitute the super types down the hierarchy. // _substituteTypeParametersDownHierarchy(mixin, map); // // Include the members from the superclass in the resultMap. // _recordMapWithClassMembers(map, mixin, false); // // Add the members from map into result map. // for (int j = 0; j < map.size; j++) { String key = map.getKey(j); ExecutableElement value = map.getValue(j); if (key != null) { if (resultMap.get(key) == null || (resultMap.get(key) != null && !_isAbstract(value))) { resultMap.put(key, value); } } } } finally { visitedClasses.remove(mixinElement); } } else { // This case happens only when the superclass was previously visited // and not in the lookup, meaning this is meant to shorten the compute // for recursive cases. _classLookup[mixinElement] = resultMap; return resultMap; } } } _classLookup[classElt] = resultMap; return resultMap; } /** * Compute and return the inheritance path given the context of a type and a member that is * overridden in the inheritance path (for which the type is in the path). * * @param chain the inheritance path that is built up as this method calls itself recursively, * when this method is called an empty [LinkedList] should be provided * @param currentType the current type in the inheritance path * @param memberName the name of the member that is being looked up the inheritance path */ void _computeInheritancePath(Queue chain, InterfaceType currentType, String memberName) { // TODO (jwren) create a public version of this method which doesn't require // the initial chain to be provided, then provided tests for this // functionality in InheritanceManagerTest chain.add(currentType); ClassElement classElt = currentType.element; InterfaceType supertype = classElt.supertype; // Base case- reached Object if (supertype == null) { // Looked up the chain all the way to Object, return null. // This should never happen. return; } // If we are done, return the chain // We are not done if this is the first recursive call on this method. if (chain.length != 1) { // We are done however if the member is in this classElt if (_lookupMemberInClass(classElt, memberName) != null) { return; } } // Mixins- note that mixins call lookupMemberInClass, not lookupMember List mixins = classElt.mixins; for (int i = mixins.length - 1; i >= 0; i--) { ClassElement mixinElement = mixins[i].element; if (mixinElement != null) { ExecutableElement elt = _lookupMemberInClass(mixinElement, memberName); if (elt != null) { // this is equivalent (but faster than) calling this method // recursively // (return computeInheritancePath(chain, mixins[i], memberName);) chain.add(mixins[i]); return; } } } // Superclass ClassElement superclassElt = supertype.element; if (lookupMember(superclassElt, memberName) != null) { _computeInheritancePath(chain, supertype, memberName); return; } // Interfaces List interfaces = classElt.interfaces; for (InterfaceType interfaceType in interfaces) { ClassElement interfaceElement = interfaceType.element; if (interfaceElement != null && lookupMember(interfaceElement, memberName) != null) { _computeInheritancePath(chain, interfaceType, memberName); return; } } } /** * Compute and return a mapping between the set of all string names of the members inherited from * the passed [ClassElement] interface hierarchy, and the associated * [ExecutableElement]. * * @param classElt the class element to query * @param visitedInterfaces a set of visited classes passed back into this method when it calls * itself recursively * @return a mapping between the set of all string names of the members inherited from the passed * [ClassElement] interface hierarchy, and the associated [ExecutableElement] */ MemberMap _computeInterfaceLookupMap( ClassElement classElt, HashSet visitedInterfaces) { MemberMap resultMap = _interfaceLookup[classElt]; if (resultMap != null) { return resultMap; } List lookupMaps = _gatherInterfaceLookupMaps(classElt, visitedInterfaces); if (lookupMaps == null) { resultMap = new MemberMap(); } else { HashMap> unionMap = _unionInterfaceLookupMaps(lookupMaps); resultMap = _resolveInheritanceLookup(classElt, unionMap); } _interfaceLookup[classElt] = resultMap; return resultMap; } /** * Collect a list of interface lookup maps whose elements correspond to all of the classes * directly above [classElt] in the class hierarchy (the direct superclass if any, all * mixins, and all direct superinterfaces). Each item in the list is the interface lookup map * returned by [computeInterfaceLookupMap] for the corresponding super, except with type * parameters appropriately substituted. * * @param classElt the class element to query * @param visitedInterfaces a set of visited classes passed back into this method when it calls * itself recursively * @return `null` if there was a problem (such as a loop in the class hierarchy) or if there * are no classes above this one in the class hierarchy. Otherwise, a list of interface * lookup maps. */ List _gatherInterfaceLookupMaps( ClassElement classElt, HashSet visitedInterfaces) { InterfaceType supertype = classElt.supertype; ClassElement superclassElement = supertype != null ? supertype.element : null; List mixins = classElt.mixins; List interfaces = classElt.interfaces; // Recursively collect the list of mappings from all of the interface types List lookupMaps = new List(); // // Superclass element // if (superclassElement != null) { if (!visitedInterfaces.contains(superclassElement)) { try { visitedInterfaces.add(superclassElement); // // Recursively compute the map for the super type. // MemberMap map = _computeInterfaceLookupMap(superclassElement, visitedInterfaces); map = new MemberMap.con2(map); // // Substitute the super type down the hierarchy. // _substituteTypeParametersDownHierarchy(supertype, map); // // Add any members from the super type into the map as well. // _recordMapWithClassMembers(map, supertype, true); lookupMaps.add(map); } finally { visitedInterfaces.remove(superclassElement); } } else { return null; } } // // Mixin elements // for (int i = mixins.length - 1; i >= 0; i--) { InterfaceType mixinType = mixins[i]; ClassElement mixinElement = mixinType.element; if (mixinElement != null) { if (!visitedInterfaces.contains(mixinElement)) { try { visitedInterfaces.add(mixinElement); // // Recursively compute the map for the mixin. // MemberMap map = _computeInterfaceLookupMap(mixinElement, visitedInterfaces); map = new MemberMap.con2(map); // // Substitute the mixin type down the hierarchy. // _substituteTypeParametersDownHierarchy(mixinType, map); // // Add any members from the mixin type into the map as well. // _recordMapWithClassMembers(map, mixinType, true); lookupMaps.add(map); } finally { visitedInterfaces.remove(mixinElement); } } else { return null; } } } // // Interface elements // for (InterfaceType interfaceType in interfaces) { ClassElement interfaceElement = interfaceType.element; if (interfaceElement != null) { if (!visitedInterfaces.contains(interfaceElement)) { try { visitedInterfaces.add(interfaceElement); // // Recursively compute the map for the interfaces. // MemberMap map = _computeInterfaceLookupMap(interfaceElement, visitedInterfaces); map = new MemberMap.con2(map); // // Substitute the supertypes down the hierarchy // _substituteTypeParametersDownHierarchy(interfaceType, map); // // And add any members from the interface into the map as well. // _recordMapWithClassMembers(map, interfaceType, true); lookupMaps.add(map); } finally { visitedInterfaces.remove(interfaceElement); } } else { return null; } } } if (lookupMaps.length == 0) { return null; } return lookupMaps; } /** * Given some [ClassElement], this method finds and returns the [ExecutableElement] of * the passed name in the class element. Static members, members in super types and members not * accessible from the current library are not considered. * * @param classElt the class element to query * @param memberName the name of the member to lookup in the class * @return the found [ExecutableElement], or `null` if no such member was found */ ExecutableElement _lookupMemberInClass( ClassElement classElt, String memberName) { List methods = classElt.methods; for (MethodElement method in methods) { if (memberName == method.name && method.isAccessibleIn(_library) && !method.isStatic) { return method; } } List accessors = classElt.accessors; for (PropertyAccessorElement accessor in accessors) { if (memberName == accessor.name && accessor.isAccessibleIn(_library) && !accessor.isStatic) { return accessor; } } return null; } /** * Record the passed map with the set of all members (methods, getters and setters) in the type * into the passed map. * * @param map some non-`null` map to put the methods and accessors from the passed * [ClassElement] into * @param type the type that will be recorded into the passed map * @param doIncludeAbstract `true` if abstract members will be put into the map */ void _recordMapWithClassMembers( MemberMap map, InterfaceType type, bool doIncludeAbstract) { List methods = type.methods; for (MethodElement method in methods) { if (method.isAccessibleIn(_library) && !method.isStatic && (doIncludeAbstract || !method.isAbstract)) { map.put(method.name, method); } } List accessors = type.accessors; for (PropertyAccessorElement accessor in accessors) { if (accessor.isAccessibleIn(_library) && !accessor.isStatic && (doIncludeAbstract || !accessor.isAbstract)) { map.put(accessor.name, accessor); } } } /** * This method is used to report errors on when they are found computing inheritance information. * See [ErrorVerifier.checkForInconsistentMethodInheritance] to see where these generated * error codes are reported back into the analysis engine. * * @param classElt the location of the source for which the exception occurred * @param offset the offset of the location of the error * @param length the length of the location of the error * @param errorCode the error code to be associated with this error * @param arguments the arguments used to build the error message */ void _reportError(ClassElement classElt, int offset, int length, ErrorCode errorCode, List arguments) { HashSet