| // 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. |
| |
| import 'dart:collection'; |
| |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/ast_factory.dart'; |
| import 'package:analyzer/dart/ast/standard_resolution_map.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/error/listener.dart'; |
| import 'package:analyzer/exception/exception.dart'; |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:analyzer/src/context/builder.dart'; |
| import 'package:analyzer/src/dart/ast/ast.dart'; |
| import 'package:analyzer/src/dart/ast/ast_factory.dart'; |
| import 'package:analyzer/src/dart/ast/token.dart'; |
| import 'package:analyzer/src/dart/ast/utilities.dart'; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/element/inheritance_manager2.dart'; |
| import 'package:analyzer/src/dart/element/member.dart' show ConstructorMember; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| import 'package:analyzer/src/dart/resolver/exit_detector.dart'; |
| import 'package:analyzer/src/dart/resolver/scope.dart'; |
| import 'package:analyzer/src/error/codes.dart'; |
| import 'package:analyzer/src/generated/constant.dart'; |
| import 'package:analyzer/src/generated/element_resolver.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/generated/static_type_analyzer.dart'; |
| import 'package:analyzer/src/generated/testing/element_factory.dart'; |
| import 'package:analyzer/src/generated/type_system.dart'; |
| import 'package:analyzer/src/lint/linter.dart'; |
| import 'package:analyzer/src/workspace/workspace.dart'; |
| import 'package:meta/meta.dart'; |
| import 'package:path/path.dart' as path; |
| |
| export 'package:analyzer/src/dart/constant/constant_verifier.dart'; |
| export 'package:analyzer/src/dart/resolver/exit_detector.dart'; |
| export 'package:analyzer/src/dart/resolver/inheritance_manager.dart'; |
| export 'package:analyzer/src/dart/resolver/scope.dart'; |
| export 'package:analyzer/src/generated/type_system.dart'; |
| |
| /// A visitor that will re-write an AST to support the optional `new` and |
| /// `const` feature. |
| class AstRewriteVisitor extends ScopedVisitor { |
| final bool addConstKeyword; |
| final TypeSystem typeSystem; |
| |
| /// Initialize a newly created visitor. |
| AstRewriteVisitor( |
| this.typeSystem, |
| LibraryElement definingLibrary, |
| Source source, |
| TypeProvider typeProvider, |
| AnalysisErrorListener errorListener, |
| {Scope nameScope, |
| this.addConstKeyword: false}) |
| : super(definingLibrary, source, typeProvider, errorListener, |
| nameScope: nameScope); |
| |
| @override |
| void visitMethodInvocation(MethodInvocation node) { |
| super.visitMethodInvocation(node); |
| |
| SimpleIdentifier methodName = node.methodName; |
| if (methodName.isSynthetic) { |
| // This isn't a constructor invocation because the method name is |
| // synthetic. |
| return; |
| } |
| |
| Expression target = node.target; |
| if (target == null) { |
| // Possible cases: C() or C<>() |
| if (node.realTarget != null) { |
| // This isn't a constructor invocation because it's in a cascade. |
| return; |
| } |
| Element element = nameScope.lookup(methodName, definingLibrary); |
| if (element is ClassElement) { |
| AstFactory astFactory = new AstFactoryImpl(); |
| TypeName typeName = astFactory.typeName(methodName, node.typeArguments); |
| ConstructorName constructorName = |
| astFactory.constructorName(typeName, null, null); |
| InstanceCreationExpression instanceCreationExpression = |
| astFactory.instanceCreationExpression( |
| _getKeyword(node), constructorName, node.argumentList); |
| InterfaceType type = getType(typeSystem, element, node.typeArguments); |
| ConstructorElement constructorElement = |
| type.lookUpConstructor(null, definingLibrary); |
| methodName.staticElement = element; |
| methodName.staticType = type; |
| typeName.type = type; |
| constructorName.staticElement = constructorElement; |
| instanceCreationExpression.staticType = type; |
| instanceCreationExpression.staticElement = constructorElement; |
| NodeReplacer.replace(node, instanceCreationExpression); |
| } |
| } else if (target is SimpleIdentifier) { |
| // Possible cases: C.n(), p.C() or p.C<>() |
| if (node.operator.type == TokenType.QUESTION_PERIOD) { |
| // This isn't a constructor invocation because a null aware operator is |
| // being used. |
| } |
| Element element = nameScope.lookup(target, definingLibrary); |
| if (element is ClassElement) { |
| // Possible case: C.n() |
| var constructorElement = element.getNamedConstructor(methodName.name); |
| if (constructorElement != null) { |
| var typeArguments = node.typeArguments; |
| if (typeArguments != null) { |
| errorReporter.reportErrorForNode( |
| StaticTypeWarningCode |
| .WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR, |
| typeArguments, |
| [element.name, constructorElement.name]); |
| } |
| AstFactory astFactory = new AstFactoryImpl(); |
| TypeName typeName = astFactory.typeName(target, null); |
| ConstructorName constructorName = |
| astFactory.constructorName(typeName, node.operator, methodName); |
| InstanceCreationExpression instanceCreationExpression = |
| astFactory.instanceCreationExpression( |
| _getKeyword(node), constructorName, node.argumentList, |
| typeArguments: typeArguments); |
| InterfaceType type = getType(typeSystem, element, null); |
| constructorElement = |
| type.lookUpConstructor(methodName.name, definingLibrary); |
| methodName.staticElement = element; |
| methodName.staticType = type; |
| target.staticElement = element; |
| target.staticType = type; // TODO(scheglov) remove this |
| typeName.type = type; |
| constructorName.staticElement = constructorElement; |
| instanceCreationExpression.staticType = type; |
| instanceCreationExpression.staticElement = constructorElement; |
| NodeReplacer.replace(node, instanceCreationExpression); |
| } |
| } else if (element is PrefixElement) { |
| // Possible cases: p.C() or p.C<>() |
| AstFactory astFactory = new AstFactoryImpl(); |
| Identifier identifier = astFactory.prefixedIdentifier( |
| astFactory.simpleIdentifier(target.token), |
| null, |
| astFactory.simpleIdentifier(methodName.token)); |
| Element prefixedElement = nameScope.lookup(identifier, definingLibrary); |
| if (prefixedElement is ClassElement) { |
| TypeName typeName = astFactory.typeName( |
| astFactory.prefixedIdentifier(target, node.operator, methodName), |
| node.typeArguments); |
| ConstructorName constructorName = |
| astFactory.constructorName(typeName, null, null); |
| InstanceCreationExpression instanceCreationExpression = |
| astFactory.instanceCreationExpression( |
| _getKeyword(node), constructorName, node.argumentList); |
| InterfaceType type = |
| getType(typeSystem, prefixedElement, node.typeArguments); |
| ConstructorElement constructorElement = |
| type.lookUpConstructor(null, definingLibrary); |
| methodName.staticElement = element; |
| methodName.staticType = type; |
| typeName.type = type; |
| constructorName.staticElement = constructorElement; |
| instanceCreationExpression.staticType = type; |
| instanceCreationExpression.staticElement = constructorElement; |
| NodeReplacer.replace(node, instanceCreationExpression); |
| } |
| } |
| } else if (target is PrefixedIdentifier) { |
| // Possible case: p.C.n() |
| Element prefixElement = nameScope.lookup(target.prefix, definingLibrary); |
| target.prefix.staticElement = prefixElement; |
| if (prefixElement is PrefixElement) { |
| Element element = nameScope.lookup(target, definingLibrary); |
| if (element is ClassElement) { |
| var constructorElement = element.getNamedConstructor(methodName.name); |
| if (constructorElement != null) { |
| var typeArguments = node.typeArguments; |
| if (typeArguments != null) { |
| errorReporter.reportErrorForNode( |
| StaticTypeWarningCode |
| .WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR, |
| typeArguments, |
| [element.name, constructorElement.name]); |
| } |
| AstFactory astFactory = new AstFactoryImpl(); |
| TypeName typeName = astFactory.typeName(target, typeArguments); |
| ConstructorName constructorName = |
| astFactory.constructorName(typeName, node.operator, methodName); |
| InstanceCreationExpression instanceCreationExpression = |
| astFactory.instanceCreationExpression( |
| _getKeyword(node), constructorName, node.argumentList); |
| InterfaceType type = getType(typeSystem, element, typeArguments); |
| constructorElement = |
| type.lookUpConstructor(methodName.name, definingLibrary); |
| methodName.staticElement = element; |
| methodName.staticType = type; |
| target.identifier.staticElement = element; |
| typeName.type = type; |
| constructorName.staticElement = constructorElement; |
| instanceCreationExpression.staticType = type; |
| instanceCreationExpression.staticElement = constructorElement; |
| NodeReplacer.replace(node, instanceCreationExpression); |
| } |
| } |
| } |
| } |
| } |
| |
| /// Return the token that should be used in the [InstanceCreationExpression] |
| /// that corresponds to the given invocation [node]. |
| Token _getKeyword(MethodInvocation node) { |
| return addConstKeyword |
| ? new KeywordToken(Keyword.CONST, node.offset) |
| : null; |
| } |
| |
| /// Return the type of the given class [element] after substituting any type |
| /// arguments from the list of [typeArguments] for the class' type parameters. |
| static InterfaceType getType(TypeSystem typeSystem, ClassElement element, |
| TypeArgumentList typeArguments) { |
| DartType type = element.type; |
| |
| List<TypeParameterElement> typeParameters = element.typeParameters; |
| if (typeParameters.isEmpty) { |
| return type; |
| } |
| |
| if (typeArguments == null) { |
| return typeSystem.instantiateToBounds(type); |
| } |
| |
| List<DartType> argumentTypes; |
| if (typeArguments.arguments.length == typeParameters.length) { |
| argumentTypes = typeArguments.arguments |
| .map((TypeAnnotation argument) => argument.type) |
| .toList(); |
| } else { |
| argumentTypes = List<DartType>.filled( |
| typeParameters.length, DynamicTypeImpl.instance); |
| } |
| List<DartType> parameterTypes = typeParameters |
| .map((TypeParameterElement parameter) => parameter.type) |
| .toList(); |
| return type.substitute2(argumentTypes, parameterTypes); |
| } |
| } |
| |
| /// Instances of the class `BestPracticesVerifier` traverse an AST structure |
| /// looking for violations of Dart best practices. |
| class BestPracticesVerifier extends RecursiveAstVisitor<void> { |
| // 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. |
| ClassElementImpl _enclosingClass; |
| |
| /// A flag indicating whether a surrounding member (compilation unit or class) |
| /// is deprecated. |
| bool _inDeprecatedMember; |
| |
| /// The error reporter by which errors will be reported. |
| final ErrorReporter _errorReporter; |
| |
| /// The type [Null]. |
| final InterfaceType _nullType; |
| |
| /// The type Future<Null>, which is needed for determining whether it is safe |
| /// to have a bare "return;" in an async method. |
| final InterfaceType _futureNullType; |
| |
| /// The type system primitives |
| final TypeSystem _typeSystem; |
| |
| /// The current library |
| final LibraryElement _currentLibrary; |
| |
| final _InvalidAccessVerifier _invalidAccessVerifier; |
| |
| /// The [WorkspacePackage] in which [_currentLibrary] is declared. |
| WorkspacePackage _workspacePackage; |
| |
| /// The [LinterContext] used for possible const calculations. |
| LinterContext _linterContext; |
| |
| /// Create a new instance of the [BestPracticesVerifier]. |
| /// |
| /// @param errorReporter the error reporter |
| BestPracticesVerifier( |
| this._errorReporter, |
| TypeProvider typeProvider, |
| this._currentLibrary, { |
| TypeSystem typeSystem, |
| ResourceProvider resourceProvider, |
| DeclaredVariables declaredVariables, |
| AnalysisOptions analysisOptions, |
| }) : _nullType = typeProvider.nullType, |
| _futureNullType = typeProvider.futureNullType, |
| _typeSystem = typeSystem ?? new Dart2TypeSystem(typeProvider), |
| _invalidAccessVerifier = |
| new _InvalidAccessVerifier(_errorReporter, _currentLibrary) { |
| _inDeprecatedMember = _currentLibrary.hasDeprecated; |
| String libraryPath = _currentLibrary.source.fullName; |
| ContextBuilder builder = new ContextBuilder( |
| resourceProvider, null /* sdkManager */, null /* contentCache */); |
| Workspace workspace = |
| ContextBuilder.createWorkspace(resourceProvider, libraryPath, builder); |
| _workspacePackage = workspace.findPackageFor(libraryPath); |
| _linterContext = LinterContextImpl( |
| null /* allUnits */, |
| null /* currentUnit */, |
| declaredVariables, |
| typeProvider, |
| _typeSystem, |
| analysisOptions); |
| } |
| |
| @override |
| void visitAnnotation(Annotation node) { |
| ElementAnnotation element = |
| resolutionMap.elementAnnotationForAnnotation(node); |
| AstNode parent = node.parent; |
| if (element?.isFactory == true) { |
| if (parent is MethodDeclaration) { |
| _checkForInvalidFactory(parent); |
| } else { |
| _errorReporter |
| .reportErrorForNode(HintCode.INVALID_FACTORY_ANNOTATION, node, []); |
| } |
| } else if (element?.isImmutable == true) { |
| if (parent is! ClassOrMixinDeclaration && parent is! ClassTypeAlias) { |
| _errorReporter.reportErrorForNode( |
| HintCode.INVALID_IMMUTABLE_ANNOTATION, node, []); |
| } |
| } else if (element?.isLiteral == true) { |
| if (parent is! ConstructorDeclaration || |
| (parent as ConstructorDeclaration).constKeyword == null) { |
| _errorReporter |
| .reportErrorForNode(HintCode.INVALID_LITERAL_ANNOTATION, node, []); |
| } |
| } else if (element?.isSealed == true) { |
| if (!(parent is ClassDeclaration || parent is ClassTypeAlias)) { |
| _errorReporter.reportErrorForNode( |
| HintCode.INVALID_SEALED_ANNOTATION, node, [node.element.name]); |
| } |
| } else if (element?.isVisibleForTemplate == true || |
| element?.isVisibleForTesting == true) { |
| if (parent is Declaration) { |
| reportInvalidAnnotation(Element declaredElement) { |
| _errorReporter.reportErrorForNode( |
| HintCode.INVALID_VISIBILITY_ANNOTATION, |
| node, |
| [declaredElement.name, node.name.name]); |
| } |
| |
| if (parent is TopLevelVariableDeclaration) { |
| for (VariableDeclaration variable in parent.variables.variables) { |
| if (Identifier.isPrivateName(variable.declaredElement.name)) { |
| reportInvalidAnnotation(variable.declaredElement); |
| } |
| } |
| } else if (parent is FieldDeclaration) { |
| for (VariableDeclaration variable in parent.fields.variables) { |
| if (Identifier.isPrivateName(variable.declaredElement.name)) { |
| reportInvalidAnnotation(variable.declaredElement); |
| } |
| } |
| } else if (parent.declaredElement != null && |
| Identifier.isPrivateName(parent.declaredElement.name)) { |
| reportInvalidAnnotation(parent.declaredElement); |
| } |
| } else { |
| // Something other than a declaration was annotated. Whatever this is, |
| // it probably warrants a Hint, but this has not been specified on |
| // visibleForTemplate or visibleForTesting, so leave it alone for now. |
| } |
| } |
| |
| super.visitAnnotation(node); |
| } |
| |
| @override |
| void visitArgumentList(ArgumentList node) { |
| for (Expression argument in node.arguments) { |
| ParameterElement parameter = argument.staticParameterElement; |
| if (parameter?.isOptionalPositional == true) { |
| _checkForDeprecatedMemberUse(parameter, argument); |
| } |
| } |
| super.visitArgumentList(node); |
| } |
| |
| @override |
| void visitAsExpression(AsExpression node) { |
| _checkForUnnecessaryCast(node); |
| super.visitAsExpression(node); |
| } |
| |
| @override |
| void visitAssignmentExpression(AssignmentExpression node) { |
| TokenType operatorType = node.operator.type; |
| if (operatorType != TokenType.EQ) { |
| _checkForDeprecatedMemberUse(node.staticElement, node); |
| } |
| super.visitAssignmentExpression(node); |
| } |
| |
| @override |
| void visitBinaryExpression(BinaryExpression node) { |
| _checkForDivisionOptimizationHint(node); |
| _checkForDeprecatedMemberUse(node.staticElement, node); |
| super.visitBinaryExpression(node); |
| } |
| |
| @override |
| void visitClassDeclaration(ClassDeclaration node) { |
| var element = AbstractClassElementImpl.getImpl(node.declaredElement); |
| _enclosingClass = element; |
| _invalidAccessVerifier._enclosingClass = element; |
| |
| bool wasInDeprecatedMember = _inDeprecatedMember; |
| if (element != null && element.hasDeprecated) { |
| _inDeprecatedMember = true; |
| } |
| |
| try { |
| // Commented out until we decide that we want this hint in the analyzer |
| // checkForOverrideEqualsButNotHashCode(node); |
| _checkForImmutable(node); |
| _checkForInvalidSealedSuperclass(node); |
| super.visitClassDeclaration(node); |
| } finally { |
| _enclosingClass = null; |
| _invalidAccessVerifier._enclosingClass = null; |
| _inDeprecatedMember = wasInDeprecatedMember; |
| } |
| } |
| |
| @override |
| void visitClassTypeAlias(ClassTypeAlias node) { |
| _checkForImmutable(node); |
| _checkForInvalidSealedSuperclass(node); |
| super.visitClassTypeAlias(node); |
| } |
| |
| @override |
| void visitConstructorDeclaration(ConstructorDeclaration node) { |
| if (resolutionMap.elementDeclaredByConstructorDeclaration(node).isFactory) { |
| if (node.body is BlockFunctionBody) { |
| // Check the block for a return statement, if not, create the hint. |
| if (!ExitDetector.exits(node.body)) { |
| _errorReporter.reportErrorForNode( |
| HintCode.MISSING_RETURN, node, [node.returnType.name]); |
| } |
| } |
| } |
| super.visitConstructorDeclaration(node); |
| } |
| |
| @override |
| void visitExportDirective(ExportDirective node) { |
| _checkForDeprecatedMemberUse(node.uriElement, node); |
| super.visitExportDirective(node); |
| } |
| |
| @override |
| void visitFieldDeclaration(FieldDeclaration node) { |
| bool wasInDeprecatedMember = _inDeprecatedMember; |
| if (_hasDeprecatedAnnotation(node.metadata)) { |
| _inDeprecatedMember = true; |
| } |
| |
| try { |
| super.visitFieldDeclaration(node); |
| } finally { |
| _inDeprecatedMember = wasInDeprecatedMember; |
| } |
| } |
| |
| @override |
| void visitFormalParameterList(FormalParameterList node) { |
| _checkRequiredParameter(node); |
| super.visitFormalParameterList(node); |
| } |
| |
| @override |
| void visitFunctionDeclaration(FunctionDeclaration node) { |
| bool wasInDeprecatedMember = _inDeprecatedMember; |
| ExecutableElement element = node.declaredElement; |
| if (element != null && element.hasDeprecated) { |
| _inDeprecatedMember = true; |
| } |
| try { |
| _checkForMissingReturn( |
| node.returnType, node.functionExpression.body, element, node); |
| super.visitFunctionDeclaration(node); |
| } finally { |
| _inDeprecatedMember = wasInDeprecatedMember; |
| } |
| } |
| |
| @override |
| void visitImportDirective(ImportDirective node) { |
| _checkForDeprecatedMemberUse(node.uriElement, node); |
| ImportElement importElement = node.element; |
| if (importElement != null && importElement.isDeferred) { |
| _checkForLoadLibraryFunction(node, importElement); |
| } |
| super.visitImportDirective(node); |
| } |
| |
| @override |
| void visitIndexExpression(IndexExpression node) { |
| _checkForDeprecatedMemberUse(node.staticElement, node); |
| super.visitIndexExpression(node); |
| } |
| |
| @override |
| void visitInstanceCreationExpression(InstanceCreationExpression node) { |
| _checkForDeprecatedMemberUse(node.staticElement, node); |
| _checkForLiteralConstructorUse(node); |
| super.visitInstanceCreationExpression(node); |
| } |
| |
| @override |
| void visitIsExpression(IsExpression node) { |
| _checkAllTypeChecks(node); |
| super.visitIsExpression(node); |
| } |
| |
| @override |
| void visitMethodDeclaration(MethodDeclaration node) { |
| bool wasInDeprecatedMember = _inDeprecatedMember; |
| ExecutableElement element = node.declaredElement; |
| if (element != null && element.hasDeprecated) { |
| _inDeprecatedMember = true; |
| } |
| try { |
| // This was determined to not be a good hint, see: dartbug.com/16029 |
| //checkForOverridingPrivateMember(node); |
| _checkForMissingReturn(node.returnType, node.body, element, node); |
| _checkForUnnecessaryNoSuchMethod(node); |
| super.visitMethodDeclaration(node); |
| } finally { |
| _inDeprecatedMember = wasInDeprecatedMember; |
| } |
| } |
| |
| @override |
| void visitMethodInvocation(MethodInvocation node) { |
| _checkForNullAwareHints(node, node.operator); |
| DartType staticInvokeType = node.staticInvokeType; |
| Element callElement = staticInvokeType?.element; |
| if (callElement is MethodElement && |
| callElement.name == FunctionElement.CALL_METHOD_NAME) { |
| _checkForDeprecatedMemberUse(callElement, node); |
| } |
| super.visitMethodInvocation(node); |
| } |
| |
| @override |
| void visitMixinDeclaration(MixinDeclaration node) { |
| _enclosingClass = node.declaredElement; |
| _invalidAccessVerifier._enclosingClass = _enclosingClass; |
| |
| bool wasInDeprecatedMember = _inDeprecatedMember; |
| if (_hasDeprecatedAnnotation(node.metadata)) { |
| _inDeprecatedMember = true; |
| } |
| |
| try { |
| _checkForImmutable(node); |
| _checkForInvalidSealedSuperclass(node); |
| super.visitMixinDeclaration(node); |
| } finally { |
| _enclosingClass = null; |
| _invalidAccessVerifier._enclosingClass = null; |
| _inDeprecatedMember = wasInDeprecatedMember; |
| } |
| } |
| |
| @override |
| void visitPostfixExpression(PostfixExpression node) { |
| _checkForDeprecatedMemberUse(node.staticElement, node); |
| super.visitPostfixExpression(node); |
| } |
| |
| @override |
| void visitPrefixExpression(PrefixExpression node) { |
| _checkForDeprecatedMemberUse(node.staticElement, node); |
| super.visitPrefixExpression(node); |
| } |
| |
| @override |
| void visitPropertyAccess(PropertyAccess node) { |
| _checkForNullAwareHints(node, node.operator); |
| super.visitPropertyAccess(node); |
| } |
| |
| @override |
| void visitRedirectingConstructorInvocation( |
| RedirectingConstructorInvocation node) { |
| _checkForDeprecatedMemberUse(node.staticElement, node); |
| super.visitRedirectingConstructorInvocation(node); |
| } |
| |
| @override |
| void visitSimpleIdentifier(SimpleIdentifier node) { |
| _checkForDeprecatedMemberUseAtIdentifier(node); |
| _invalidAccessVerifier.verify(node); |
| super.visitSimpleIdentifier(node); |
| } |
| |
| @override |
| void visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
| _checkForDeprecatedMemberUse(node.staticElement, node); |
| super.visitSuperConstructorInvocation(node); |
| } |
| |
| @override |
| void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { |
| bool wasInDeprecatedMember = _inDeprecatedMember; |
| if (_hasDeprecatedAnnotation(node.metadata)) { |
| _inDeprecatedMember = true; |
| } |
| |
| try { |
| super.visitTopLevelVariableDeclaration(node); |
| } finally { |
| _inDeprecatedMember = wasInDeprecatedMember; |
| } |
| } |
| |
| /// 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; |
| TypeAnnotation typeName = node.type; |
| DartType lhsType = expression.staticType; |
| DartType rhsType = typeName.type; |
| if (lhsType == null || rhsType == null) { |
| return false; |
| } |
| String rhsNameStr = typeName is TypeName ? typeName.name.name : null; |
| // if x is dynamic |
| if (rhsType.isDynamic && rhsNameStr == Keyword.DYNAMIC.lexeme) { |
| 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?.library; |
| 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; |
| } |
| |
| /// 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 |
| /// See [HintCode.DEPRECATED_MEMBER_USE]. |
| void _checkForDeprecatedMemberUse(Element element, AstNode node) { |
| bool isDeprecated(Element element) { |
| if (element is PropertyAccessorElement && element.isSynthetic) { |
| // TODO(brianwilkerson) Why isn't this the implementation for PropertyAccessorElement? |
| Element variable = element.variable; |
| if (variable == null) { |
| return false; |
| } |
| return variable.hasDeprecated; |
| } |
| return element.hasDeprecated; |
| } |
| |
| bool isLocalParameter(Element element, AstNode node) { |
| if (element is ParameterElement) { |
| ExecutableElement definingFunction = element.enclosingElement; |
| FunctionBody body = node.thisOrAncestorOfType<FunctionBody>(); |
| while (body != null) { |
| ExecutableElement enclosingFunction; |
| AstNode parent = body.parent; |
| if (parent is ConstructorDeclaration) { |
| enclosingFunction = parent.declaredElement; |
| } else if (parent is FunctionExpression) { |
| enclosingFunction = parent.declaredElement; |
| } else if (parent is MethodDeclaration) { |
| enclosingFunction = parent.declaredElement; |
| } |
| if (enclosingFunction == definingFunction) { |
| return true; |
| } |
| body = parent?.thisOrAncestorOfType<FunctionBody>(); |
| } |
| } |
| return false; |
| } |
| |
| bool isLibraryInWorkspacePackage(LibraryElement library) { |
| if (_workspacePackage == null || library == null) { |
| // Better to not make a big claim that they _are_ in the same package, |
| // if we were unable to determine what package [_currentLibrary] is in. |
| return false; |
| } |
| return _workspacePackage.contains(library.source.fullName); |
| } |
| |
| if (!_inDeprecatedMember && |
| element != null && |
| isDeprecated(element) && |
| !isLocalParameter(element, node)) { |
| 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. |
| displayName = element.enclosingElement.displayName; |
| if (!element.displayName.isEmpty) { |
| displayName = "$displayName.${element.displayName}"; |
| } |
| } else if (element is LibraryElement) { |
| displayName = element.definingCompilationUnit.source.uri.toString(); |
| } else if (displayName == FunctionElement.CALL_METHOD_NAME && |
| node is MethodInvocation && |
| node.staticInvokeType is InterfaceType) { |
| DartType staticInvokeType = |
| resolutionMap.staticInvokeTypeForInvocationExpression(node); |
| displayName = "${staticInvokeType.displayName}.${element.displayName}"; |
| } |
| LibraryElement library = |
| element is LibraryElement ? element : element.library; |
| HintCode hintCode = isLibraryInWorkspacePackage(library) |
| ? HintCode.DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE |
| : HintCode.DEPRECATED_MEMBER_USE; |
| _errorReporter.reportErrorForNode(hintCode, node, [displayName]); |
| } |
| } |
| |
| /// 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]. |
| void _checkForDeprecatedMemberUseAtIdentifier(SimpleIdentifier identifier) { |
| if (identifier.inDeclarationContext()) { |
| return; |
| } |
| AstNode parent = identifier.parent; |
| if ((parent is ConstructorName && identical(identifier, parent.name)) || |
| (parent is ConstructorDeclaration && |
| identical(identifier, parent.returnType)) || |
| (parent is SuperConstructorInvocation && |
| identical(identifier, parent.constructorName)) || |
| parent is HideCombinator) { |
| return; |
| } |
| _checkForDeprecatedMemberUse(identifier.staticElement, 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 != TokenType.SLASH) { |
| return false; |
| } |
| // Return if the '/' operator is not defined in core, or if we don't know |
| // its static type |
| MethodElement methodElement = node.staticElement; |
| 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 |
| AstNode parent = node.parent; |
| if (parent is ParenthesizedExpression) { |
| ParenthesizedExpression parenthesizedExpression = |
| _wrapParenthesizedExpression(parent); |
| AstNode grandParent = parenthesizedExpression.parent; |
| if (grandParent is MethodInvocation) { |
| if (_TO_INT_METHOD_NAME == grandParent.methodName.name && |
| grandParent.argumentList.arguments.isEmpty) { |
| _errorReporter.reportErrorForNode( |
| HintCode.DIVISION_OPTIMIZATION, grandParent); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /// Checks whether [node] violates the rules of [immutable]. |
| /// |
| /// If [node] is marked with [immutable] or inherits from a class or mixin |
| /// marked with [immutable], this function searches the fields of [node] and |
| /// its superclasses, reporting a hint if any non-final instance fields are |
| /// found. |
| void _checkForImmutable(NamedCompilationUnitMember node) { |
| /// Return `true` if the given class [element] is annotated with the |
| /// `@immutable` annotation. |
| bool isImmutable(ClassElement element) { |
| for (ElementAnnotation annotation in element.metadata) { |
| if (annotation.isImmutable) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /// Return `true` if the given class [element] or any superclass of it is |
| /// annotated with the `@immutable` annotation. |
| bool isOrInheritsImmutable( |
| ClassElement element, HashSet<ClassElement> visited) { |
| if (visited.add(element)) { |
| if (isImmutable(element)) { |
| return true; |
| } |
| for (InterfaceType interface in element.mixins) { |
| if (isOrInheritsImmutable(interface.element, visited)) { |
| return true; |
| } |
| } |
| for (InterfaceType mixin in element.interfaces) { |
| if (isOrInheritsImmutable(mixin.element, visited)) { |
| return true; |
| } |
| } |
| if (element.supertype != null) { |
| return isOrInheritsImmutable(element.supertype.element, visited); |
| } |
| } |
| return false; |
| } |
| |
| /// Return `true` if the given class [element] defines a non-final instance |
| /// field. |
| Iterable<String> nonFinalInstanceFields(ClassElement element) { |
| return element.fields |
| .where((FieldElement field) => |
| !field.isSynthetic && !field.isFinal && !field.isStatic) |
| .map((FieldElement field) => '${element.name}.${field.name}'); |
| } |
| |
| /// Return `true` if the given class [element] defines or inherits a |
| /// non-final field. |
| Iterable<String> definedOrInheritedNonFinalInstanceFields( |
| ClassElement element, HashSet<ClassElement> visited) { |
| Iterable<String> nonFinalFields = []; |
| if (visited.add(element)) { |
| nonFinalFields = nonFinalInstanceFields(element); |
| nonFinalFields = nonFinalFields.followedBy(element.mixins.expand( |
| (InterfaceType mixin) => nonFinalInstanceFields(mixin.element))); |
| if (element.supertype != null) { |
| nonFinalFields = nonFinalFields.followedBy( |
| definedOrInheritedNonFinalInstanceFields( |
| element.supertype.element, visited)); |
| } |
| } |
| return nonFinalFields; |
| } |
| |
| ClassElement element = node.declaredElement; |
| if (isOrInheritsImmutable(element, new HashSet<ClassElement>())) { |
| Iterable<String> nonFinalFields = |
| definedOrInheritedNonFinalInstanceFields( |
| element, new HashSet<ClassElement>()); |
| if (nonFinalFields.isNotEmpty) { |
| _errorReporter.reportErrorForNode( |
| HintCode.MUST_BE_IMMUTABLE, node.name, [nonFinalFields.join(', ')]); |
| } |
| } |
| } |
| |
| void _checkForInvalidFactory(MethodDeclaration decl) { |
| // Check declaration. |
| // Note that null return types are expected to be flagged by other analyses. |
| DartType returnType = decl.returnType?.type; |
| if (returnType is VoidType) { |
| _errorReporter.reportErrorForNode(HintCode.INVALID_FACTORY_METHOD_DECL, |
| decl.name, [decl.name.toString()]); |
| return; |
| } |
| |
| // Check implementation. |
| |
| FunctionBody body = decl.body; |
| if (body is EmptyFunctionBody) { |
| // Abstract methods are OK. |
| return; |
| } |
| |
| // `new Foo()` or `null`. |
| bool factoryExpression(Expression expression) => |
| expression is InstanceCreationExpression || expression is NullLiteral; |
| |
| if (body is ExpressionFunctionBody && factoryExpression(body.expression)) { |
| return; |
| } else if (body is BlockFunctionBody) { |
| NodeList<Statement> statements = body.block.statements; |
| if (statements.isNotEmpty) { |
| Statement last = statements.last; |
| if (last is ReturnStatement && factoryExpression(last.expression)) { |
| return; |
| } |
| } |
| } |
| |
| _errorReporter.reportErrorForNode(HintCode.INVALID_FACTORY_METHOD_IMPL, |
| decl.name, [decl.name.toString()]); |
| } |
| |
| void _checkForInvalidSealedSuperclass(NamedCompilationUnitMember node) { |
| bool currentPackageContains(Element element) { |
| String elementLibraryPath = element.library.source.fullName; |
| return _workspacePackage.contains(elementLibraryPath); |
| } |
| |
| // [NamedCompilationUnitMember.declaredElement] is not necessarily a |
| // ClassElement, but [_checkForInvalidSealedSuperclass] should only be |
| // called with a [ClassOrMixinDeclaration], or a [ClassTypeAlias]. The |
| // `declaredElement` of these specific classes is a [ClassElement]. |
| ClassElement element = node.declaredElement; |
| // TODO(srawlins): Perhaps replace this with a getter on Element, like |
| // `Element.hasOrInheritsSealed`? |
| for (InterfaceType supertype in element.allSupertypes) { |
| ClassElement superclass = supertype.element; |
| if (superclass.hasSealed) { |
| if (!currentPackageContains(superclass)) { |
| if (element.superclassConstraints.contains(supertype)) { |
| // This is a special violation of the sealed class contract, |
| // requiring specific messaging. |
| _errorReporter.reportErrorForNode(HintCode.MIXIN_ON_SEALED_CLASS, |
| node, [superclass.name.toString()]); |
| } else { |
| // This is a regular violation of the sealed class contract. |
| _errorReporter.reportErrorForNode(HintCode.SUBTYPE_OF_SEALED_CLASS, |
| node, [superclass.name.toString()]); |
| } |
| } |
| } |
| } |
| } |
| |
| /// Check that the instance creation node is const if the constructor is |
| /// marked with [literal]. |
| _checkForLiteralConstructorUse(InstanceCreationExpression node) { |
| ConstructorName constructorName = node.constructorName; |
| ConstructorElement constructor = constructorName.staticElement; |
| if (constructor == null) { |
| return; |
| } |
| if (!node.isConst && |
| constructor.hasLiteral && |
| _linterContext.canBeConst(node)) { |
| // Echoing jwren's TODO from _checkForDeprecatedMemberUse: |
| // TODO(jwren) We should modify ConstructorElement.getDisplayName(), or |
| // have the logic centralized elsewhere, instead of doing this logic |
| // here. |
| String fullConstructorName = constructorName.type.name.name; |
| if (constructorName.name != null) { |
| fullConstructorName = '$fullConstructorName.${constructorName.name}'; |
| } |
| HintCode hint = node.keyword?.keyword == Keyword.NEW |
| ? HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW |
| : HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR; |
| _errorReporter.reportErrorForNode(hint, node, [fullConstructorName]); |
| } |
| } |
| |
| /// 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<Null> 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]. |
| void _checkForMissingReturn(TypeAnnotation returnNode, FunctionBody body, |
| ExecutableElement element, AstNode functionNode) { |
| if (body is BlockFunctionBody) { |
| // Prefer the type from the element model, in case we've inferred one. |
| DartType returnType = element?.returnType ?? returnNode?.type; |
| AstNode errorNode = returnNode ?? functionNode; |
| |
| // Skip the check if we're missing a return type (e.g. erroneous code). |
| // Generators are never required to have a return statement. |
| if (returnType == null || body.isGenerator) { |
| return; |
| } |
| |
| var flattenedType = |
| body.isAsynchronous ? _typeSystem.flatten(returnType) : returnType; |
| |
| // dynamic/Null/void are allowed to omit a return. |
| if (flattenedType.isDynamic || |
| flattenedType.isDartCoreNull || |
| flattenedType.isVoid) { |
| return; |
| } |
| // Otherwise issue a warning if the block doesn't have a return. |
| if (!ExitDetector.exits(body)) { |
| _errorReporter.reportErrorForNode( |
| HintCode.MISSING_RETURN, errorNode, [returnType.displayName]); |
| } |
| } |
| } |
| |
| /// Produce several null-aware related hints. |
| void _checkForNullAwareHints(Expression node, Token operator) { |
| if (operator == null || operator.type != TokenType.QUESTION_PERIOD) { |
| return; |
| } |
| |
| // childOfParent is used to know from which branch node comes. |
| var childOfParent = node; |
| var parent = node.parent; |
| while (parent is ParenthesizedExpression) { |
| childOfParent = parent; |
| parent = parent.parent; |
| } |
| |
| // CAN_BE_NULL_AFTER_NULL_AWARE |
| if (parent is MethodInvocation && |
| parent.operator.type != TokenType.QUESTION_PERIOD && |
| _nullType.lookUpMethod(parent.methodName.name, _currentLibrary) == |
| null) { |
| _errorReporter.reportErrorForNode( |
| HintCode.CAN_BE_NULL_AFTER_NULL_AWARE, childOfParent); |
| return; |
| } |
| if (parent is PropertyAccess && |
| parent.operator.type != TokenType.QUESTION_PERIOD && |
| _nullType.lookUpGetter(parent.propertyName.name, _currentLibrary) == |
| null) { |
| _errorReporter.reportErrorForNode( |
| HintCode.CAN_BE_NULL_AFTER_NULL_AWARE, childOfParent); |
| return; |
| } |
| if (parent is CascadeExpression && parent.target == childOfParent) { |
| _errorReporter.reportErrorForNode( |
| HintCode.CAN_BE_NULL_AFTER_NULL_AWARE, childOfParent); |
| return; |
| } |
| |
| // NULL_AWARE_IN_CONDITION |
| if (parent is IfStatement && parent.condition == childOfParent || |
| parent is ForPartsWithDeclarations && |
| parent.condition == childOfParent || |
| parent is DoStatement && parent.condition == childOfParent || |
| parent is WhileStatement && parent.condition == childOfParent || |
| parent is ConditionalExpression && parent.condition == childOfParent || |
| parent is AssertStatement && parent.condition == childOfParent) { |
| _errorReporter.reportErrorForNode( |
| HintCode.NULL_AWARE_IN_CONDITION, childOfParent); |
| return; |
| } |
| |
| // NULL_AWARE_IN_LOGICAL_OPERATOR |
| if (parent is PrefixExpression && parent.operator.type == TokenType.BANG || |
| parent is BinaryExpression && |
| [TokenType.BAR_BAR, TokenType.AMPERSAND_AMPERSAND] |
| .contains(parent.operator.type)) { |
| _errorReporter.reportErrorForNode( |
| HintCode.NULL_AWARE_IN_LOGICAL_OPERATOR, childOfParent); |
| return; |
| } |
| |
| // NULL_AWARE_BEFORE_OPERATOR |
| if (parent is BinaryExpression && |
| ![TokenType.EQ_EQ, TokenType.BANG_EQ, TokenType.QUESTION_QUESTION] |
| .contains(parent.operator.type) && |
| parent.leftOperand == childOfParent) { |
| _errorReporter.reportErrorForNode( |
| HintCode.NULL_AWARE_BEFORE_OPERATOR, childOfParent); |
| return; |
| } |
| } |
| |
| /// 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 && |
| _typeSystem.isMoreSpecificThan(lhsType, rhsType)) { |
| _errorReporter.reportErrorForNode(HintCode.UNNECESSARY_CAST, node); |
| return true; |
| } |
| return false; |
| } |
| |
| /// Generate a hint for `noSuchMethod` methods that do nothing except of |
| /// calling another `noSuchMethod` that is not defined by `Object`. |
| /// |
| /// @return `true` if and only if a hint code is generated on the passed node |
| /// See [HintCode.UNNECESSARY_NO_SUCH_METHOD]. |
| bool _checkForUnnecessaryNoSuchMethod(MethodDeclaration node) { |
| if (node.name.name != FunctionElement.NO_SUCH_METHOD_METHOD_NAME) { |
| return false; |
| } |
| bool isNonObjectNoSuchMethodInvocation(Expression invocation) { |
| if (invocation is MethodInvocation && |
| invocation.target is SuperExpression && |
| invocation.argumentList.arguments.length == 1) { |
| SimpleIdentifier name = invocation.methodName; |
| if (name.name == FunctionElement.NO_SUCH_METHOD_METHOD_NAME) { |
| Element methodElement = name.staticElement; |
| Element classElement = methodElement?.enclosingElement; |
| return methodElement is MethodElement && |
| classElement is ClassElement && |
| !classElement.type.isObject; |
| } |
| } |
| return false; |
| } |
| |
| FunctionBody body = node.body; |
| if (body is ExpressionFunctionBody) { |
| if (isNonObjectNoSuchMethodInvocation(body.expression)) { |
| _errorReporter.reportErrorForNode( |
| HintCode.UNNECESSARY_NO_SUCH_METHOD, node); |
| return true; |
| } |
| } else if (body is BlockFunctionBody) { |
| List<Statement> statements = body.block.statements; |
| if (statements.length == 1) { |
| Statement returnStatement = statements.first; |
| if (returnStatement is ReturnStatement && |
| isNonObjectNoSuchMethodInvocation(returnStatement.expression)) { |
| _errorReporter.reportErrorForNode( |
| HintCode.UNNECESSARY_NO_SUCH_METHOD, node); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| void _checkRequiredParameter(FormalParameterList node) { |
| final requiredParameters = |
| node.parameters.where((p) => p.declaredElement?.hasRequired == true); |
| final nonNamedParamsWithRequired = |
| requiredParameters.where((p) => !p.isNamed); |
| final namedParamsWithRequiredAndDefault = requiredParameters |
| .where((p) => p.isNamed) |
| .where((p) => p.declaredElement.defaultValueCode != null); |
| final paramsToHint = [ |
| nonNamedParamsWithRequired, |
| namedParamsWithRequiredAndDefault |
| ].expand((e) => e); |
| for (final param in paramsToHint) { |
| _errorReporter.reportErrorForNode( |
| HintCode.INVALID_REQUIRED_PARAM, param, [param.identifier.name]); |
| } |
| } |
| |
| /// 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; |
| // } |
| // |
| // /// Return `true` if the given [type] represents `Future<void>`. |
| // bool _isFutureVoid(DartType type) { |
| // if (type.isDartAsyncFuture) { |
| // List<DartType> typeArgs = (type as InterfaceType).typeArguments; |
| // if (typeArgs.length == 1 && typeArgs[0].isVoid) { |
| // return true; |
| // } |
| // } |
| // return false; |
| // } |
| |
| static bool _hasDeprecatedAnnotation(List<Annotation> annotations) { |
| for (var i = 0; i < annotations.length; i++) { |
| if (annotations[i].elementAnnotation.isDeprecated) { |
| 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) { |
| AstNode parent = parenthesizedExpression.parent; |
| if (parent is ParenthesizedExpression) { |
| return _wrapParenthesizedExpression(parent); |
| } |
| return parenthesizedExpression; |
| } |
| } |
| |
| /// Utilities for [LibraryElementImpl] building. |
| class BuildLibraryElementUtils { |
| /// Look through all of the compilation units defined for the given [library], |
| /// looking for getters and setters that are defined in different compilation |
| /// units but that have the same names. If any are found, make sure that they |
| /// have the same variable element. |
| static void patchTopLevelAccessors(LibraryElementImpl library) { |
| // Without parts getters/setters already share the same variable element. |
| List<CompilationUnitElement> parts = library.parts; |
| if (parts.isEmpty) { |
| return; |
| } |
| // Collect getters and setters. |
| Map<String, PropertyAccessorElement> getters = |
| new HashMap<String, PropertyAccessorElement>(); |
| List<PropertyAccessorElement> setters = <PropertyAccessorElement>[]; |
| _collectAccessors(getters, setters, library.definingCompilationUnit); |
| int partLength = parts.length; |
| for (int i = 0; i < partLength; i++) { |
| CompilationUnitElement unit = parts[i]; |
| _collectAccessors(getters, setters, unit); |
| } |
| // Move every setter to the corresponding getter's variable (if exists). |
| int setterLength = setters.length; |
| for (int j = 0; j < setterLength; j++) { |
| PropertyAccessorElement setter = setters[j]; |
| PropertyAccessorElement getter = getters[setter.displayName]; |
| if (getter != null) { |
| TopLevelVariableElementImpl variable = getter.variable; |
| TopLevelVariableElementImpl setterVariable = setter.variable; |
| CompilationUnitElementImpl setterUnit = setterVariable.enclosingElement; |
| setterUnit.replaceTopLevelVariable(setterVariable, variable); |
| variable.setter = setter; |
| (setter as PropertyAccessorElementImpl).variable = variable; |
| } |
| } |
| } |
| |
| /// Add all of the non-synthetic [getters] and [setters] defined in the given |
| /// [unit] that have no corresponding accessor to one of the given |
| /// collections. |
| static void _collectAccessors(Map<String, PropertyAccessorElement> getters, |
| List<PropertyAccessorElement> setters, CompilationUnitElement unit) { |
| List<PropertyAccessorElement> accessors = unit.accessors; |
| int length = accessors.length; |
| for (int i = 0; i < length; i++) { |
| PropertyAccessorElement accessor = accessors[i]; |
| if (accessor.isGetter) { |
| if (!accessor.isSynthetic && accessor.correspondingSetter == null) { |
| getters[accessor.displayName] = accessor; |
| } |
| } else { |
| if (!accessor.isSynthetic && accessor.correspondingGetter == null) { |
| setters.add(accessor); |
| } |
| } |
| } |
| } |
| } |
| |
| /// 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<void> { |
| /// 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 |
| void visitIsExpression(IsExpression node) { |
| _checkForIsDoubleHints(node); |
| 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) { |
| DartType type = node.type.type; |
| Element element = type?.element; |
| if (element != null) { |
| 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; |
| } |
| } |
| |
| /// A visitor that finds dead code and unused labels. |
| class DeadCodeVerifier extends RecursiveAstVisitor<void> { |
| /// The error reporter by which errors will be reported. |
| final ErrorReporter _errorReporter; |
| |
| /// The type system for this visitor |
| final TypeSystem _typeSystem; |
| |
| /// The object used to track the usage of labels within a given label scope. |
| _LabelTracker labelTracker; |
| |
| /// Is `true` if this unit has been parsed as non-nullable. |
| final bool _isNonNullableUnit; |
| |
| /// Initialize a newly created dead code verifier that will report dead code |
| /// to the given [errorReporter] and will use the given [typeSystem] if one is |
| /// provided. |
| DeadCodeVerifier(this._errorReporter, this._isNonNullableUnit, |
| {TypeSystem typeSystem}) |
| : this._typeSystem = typeSystem ?? new Dart2TypeSystem(null); |
| |
| @override |
| void visitAssignmentExpression(AssignmentExpression node) { |
| TokenType operatorType = node.operator.type; |
| if (operatorType == TokenType.QUESTION_QUESTION_EQ) { |
| _checkForDeadNullCoalesce( |
| node.leftHandSide.staticType, node.rightHandSide); |
| } |
| super.visitAssignmentExpression(node); |
| } |
| |
| @override |
| void visitBinaryExpression(BinaryExpression node) { |
| Token operator = node.operator; |
| bool isAmpAmp = operator.type == TokenType.AMPERSAND_AMPERSAND; |
| bool isBarBar = operator.type == TokenType.BAR_BAR; |
| bool isQuestionQuestion = operator.type == TokenType.QUESTION_QUESTION; |
| if (isAmpAmp || isBarBar) { |
| Expression lhsCondition = node.leftOperand; |
| if (!_isDebugConstant(lhsCondition)) { |
| EvaluationResultImpl lhsResult = _getConstantBooleanValue(lhsCondition); |
| if (lhsResult != null) { |
| bool value = lhsResult.value.toBoolValue(); |
| if (value == true && isBarBar) { |
| // Report error on "else" block: true || !e! |
| _errorReporter.reportErrorForNode( |
| HintCode.DEAD_CODE, node.rightOperand); |
| // Only visit the LHS: |
| lhsCondition?.accept(this); |
| return; |
| } else if (value == false && isAmpAmp) { |
| // Report error on "if" block: false && !e! |
| _errorReporter.reportErrorForNode( |
| HintCode.DEAD_CODE, node.rightOperand); |
| // Only visit the LHS: |
| lhsCondition?.accept(this); |
| return; |
| } |
| } |
| } |
| // 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: |
| // rhsCondition?.accept(this); |
| // 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: |
| // rhsCondition?.accept(this); |
| // return null; |
| // } |
| // } |
| } else if (isQuestionQuestion && _isNonNullableUnit) { |
| _checkForDeadNullCoalesce(node.leftOperand.staticType, node.rightOperand); |
| } |
| 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.) |
| @override |
| void visitBlock(Block node) { |
| NodeList<Statement> statements = node.statements; |
| _checkForDeadStatementsInNodeList(statements); |
| } |
| |
| @override |
| void visitBreakStatement(BreakStatement node) { |
| labelTracker?.recordUsage(node.label?.name); |
| } |
| |
| @override |
| void visitConditionalExpression(ConditionalExpression node) { |
| Expression conditionExpression = node.condition; |
| conditionExpression?.accept(this); |
| if (!_isDebugConstant(conditionExpression)) { |
| EvaluationResultImpl result = |
| _getConstantBooleanValue(conditionExpression); |
| if (result != null) { |
| if (result.value.toBoolValue() == true) { |
| // Report error on "else" block: true ? 1 : !2! |
| _errorReporter.reportErrorForNode( |
| HintCode.DEAD_CODE, node.elseExpression); |
| node.thenExpression?.accept(this); |
| return; |
| } else { |
| // Report error on "if" block: false ? !1! : 2 |
| _errorReporter.reportErrorForNode( |
| HintCode.DEAD_CODE, node.thenExpression); |
| node.elseExpression?.accept(this); |
| return; |
| } |
| } |
| } |
| super.visitConditionalExpression(node); |
| } |
| |
| @override |
| void visitContinueStatement(ContinueStatement node) { |
| labelTracker?.recordUsage(node.label?.name); |
| } |
| |
| @override |
| void visitExportDirective(ExportDirective node) { |
| ExportElement exportElement = node.element; |
| if (exportElement != null) { |
| // The element is null when the URI is invalid. |
| LibraryElement library = exportElement.exportedLibrary; |
| if (library != null && !library.isSynthetic) { |
| for (Combinator combinator in node.combinators) { |
| _checkCombinator(library, combinator); |
| } |
| } |
| } |
| super.visitExportDirective(node); |
| } |
| |
| @override |
| void visitIfElement(IfElement node) { |
| Expression conditionExpression = node.condition; |
| conditionExpression?.accept(this); |
| if (!_isDebugConstant(conditionExpression)) { |
| EvaluationResultImpl result = |
| _getConstantBooleanValue(conditionExpression); |
| if (result != null) { |
| if (result.value.toBoolValue() == true) { |
| // Report error on else block: if(true) {} else {!} |
| CollectionElement elseElement = node.elseElement; |
| if (elseElement != null) { |
| _errorReporter.reportErrorForNode(HintCode.DEAD_CODE, elseElement); |
| node.thenElement?.accept(this); |
| return; |
| } |
| } else { |
| // Report error on if block: if (false) {!} else {} |
| _errorReporter.reportErrorForNode( |
| HintCode.DEAD_CODE, node.thenElement); |
| node.elseElement?.accept(this); |
| return; |
| } |
| } |
| } |
| super.visitIfElement(node); |
| } |
| |
| @override |
| void visitIfStatement(IfStatement node) { |
| Expression conditionExpression = node.condition; |
| conditionExpression?.accept(this); |
| if (!_isDebugConstant(conditionExpression)) { |
| EvaluationResultImpl result = |
| _getConstantBooleanValue(conditionExpression); |
| if (result != null) { |
| if (result.value.toBoolValue() == true) { |
| // Report error on else block: if(true) {} else {!} |
| Statement elseStatement = node.elseStatement; |
| if (elseStatement != null) { |
| _errorReporter.reportErrorForNode( |
| HintCode.DEAD_CODE, elseStatement); |
| node.thenStatement?.accept(this); |
| return; |
| } |
| } else { |
| // Report error on if block: if (false) {!} else {} |
| _errorReporter.reportErrorForNode( |
| HintCode.DEAD_CODE, node.thenStatement); |
| node.elseStatement?.accept(this); |
| return; |
| } |
| } |
| } |
| super.visitIfStatement(node); |
| } |
| |
| @override |
| void visitImportDirective(ImportDirective node) { |
| ImportElement importElement = node.element; |
| if (importElement != null) { |
| // The element is null when the URI is invalid, but not when the URI is |
| // valid but refers to a non-existent file. |
| LibraryElement library = importElement.importedLibrary; |
| if (library != null && !library.isSynthetic) { |
| for (Combinator combinator in node.combinators) { |
| _checkCombinator(library, combinator); |
| } |
| } |
| } |
| super.visitImportDirective(node); |
| } |
| |
| @override |
| void visitLabeledStatement(LabeledStatement node) { |
| _pushLabels(node.labels); |
| try { |
| super.visitLabeledStatement(node); |
| } finally { |
| _popLabels(); |
| } |
| } |
| |
| @override |
| void visitSwitchCase(SwitchCase node) { |
| _checkForDeadStatementsInNodeList(node.statements, allowMandated: true); |
| super.visitSwitchCase(node); |
| } |
| |
| @override |
| void visitSwitchDefault(SwitchDefault node) { |
| _checkForDeadStatementsInNodeList(node.statements, allowMandated: true); |
| super.visitSwitchDefault(node); |
| } |
| |
| @override |
| void visitSwitchStatement(SwitchStatement node) { |
| List<Label> labels = <Label>[]; |
| for (SwitchMember member in node.members) { |
| labels.addAll(member.labels); |
| } |
| _pushLabels(labels); |
| try { |
| super.visitSwitchStatement(node); |
| } finally { |
| _popLabels(); |
| } |
| } |
| |
| @override |
| void visitTryStatement(TryStatement node) { |
| node.body?.accept(this); |
| node.finallyBlock?.accept(this); |
| NodeList<CatchClause> catchClauses = node.catchClauses; |
| int numOfCatchClauses = catchClauses.length; |
| List<DartType> visitedTypes = new List<DartType>(); |
| for (int i = 0; i < numOfCatchClauses; i++) { |
| CatchClause catchClause = catchClauses[i]; |
| if (catchClause.onKeyword != null) { |
| // An on-catch clause was found;Â verify that the exception type is not a |
| // subtype of a previous on-catch exception type. |
| DartType currentType = catchClause.exceptionType?.type; |
| if (currentType != null) { |
| 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). |
| catchClause?.accept(this); |
| 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; |
| } |
| } |
| int length = visitedTypes.length; |
| for (int j = 0; j < length; j++) { |
| DartType type = visitedTypes[j]; |
| if (_typeSystem.isSubtypeOf(currentType, 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; |
| } |
| } |
| visitedTypes.add(currentType); |
| } |
| catchClause?.accept(this); |
| } 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). |
| catchClause?.accept(this); |
| 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; |
| } |
| } |
| } |
| } |
| |
| @override |
| void visitWhileStatement(WhileStatement node) { |
| Expression conditionExpression = node.condition; |
| conditionExpression?.accept(this); |
| if (!_isDebugConstant(conditionExpression)) { |
| EvaluationResultImpl result = |
| _getConstantBooleanValue(conditionExpression); |
| if (result != null) { |
| if (result.value.toBoolValue() == false) { |
| // Report error on while block: while (false) {!} |
| _errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node.body); |
| return; |
| } |
| } |
| } |
| node.body?.accept(this); |
| } |
| |
| /// Resolve the names in the given [combinator] in the scope of the given |
| /// [library]. |
| void _checkCombinator(LibraryElement library, Combinator combinator) { |
| Namespace namespace = |
| new NamespaceBuilder().createExportNamespaceForLibrary(library); |
| NodeList<SimpleIdentifier> names; |
| ErrorCode hintCode; |
| if (combinator is HideCombinator) { |
| names = combinator.hiddenNames; |
| hintCode = HintCode.UNDEFINED_HIDDEN_NAME; |
| } else { |
| names = (combinator as ShowCombinator).shownNames; |
| hintCode = HintCode.UNDEFINED_SHOWN_NAME; |
| } |
| for (SimpleIdentifier name in names) { |
| String nameStr = name.name; |
| Element element = namespace.get(nameStr); |
| if (element == null) { |
| element = namespace.get("$nameStr="); |
| } |
| if (element == null) { |
| _errorReporter |
| .reportErrorForNode(hintCode, name, [library.identifier, nameStr]); |
| } |
| } |
| } |
| |
| void _checkForDeadNullCoalesce(TypeImpl lhsType, Expression rhs) { |
| if (_isNonNullableUnit && _typeSystem.isNonNullable(lhsType)) { |
| _errorReporter.reportErrorForNode(HintCode.DEAD_CODE, rhs, []); |
| } |
| } |
| |
| /// Given some list of [statements], loop through the list searching for dead |
| /// statements. If [allowMandated] is true, then allow dead statements that |
| /// are mandated by the language spec. This allows for a final break, |
| /// continue, return, or throw statement at the end of a switch case, that are |
| /// mandated by the language spec. |
| void _checkForDeadStatementsInNodeList(NodeList<Statement> statements, |
| {bool allowMandated: false}) { |
| bool statementExits(Statement statement) { |
| if (statement is BreakStatement) { |
| return statement.label == null; |
| } else if (statement is ContinueStatement) { |
| return statement.label == null; |
| } |
| return ExitDetector.exits(statement); |
| } |
| |
| int size = statements.length; |
| for (int i = 0; i < size; i++) { |
| Statement currentStatement = statements[i]; |
| currentStatement?.accept(this); |
| if (statementExits(currentStatement) && i != size - 1) { |
| Statement nextStatement = statements[i + 1]; |
| Statement lastStatement = statements[size - 1]; |
| // If mandated statements are allowed, and only the last statement is |
| // dead, and it's a BreakStatement, then assume it is a statement |
| // mandated by the language spec, there to avoid a |
| // CASE_BLOCK_NOT_TERMINATED error. |
| if (allowMandated && i == size - 2 && nextStatement is BreakStatement) { |
| return; |
| } |
| int offset = nextStatement.offset; |
| int length = lastStatement.end - offset; |
| _errorReporter.reportErrorForOffset(HintCode.DEAD_CODE, offset, length); |
| return; |
| } |
| } |
| } |
| |
| /// Given some [expression], 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( |
| new DartObjectImpl(null, BoolState.from(true))); |
| } else { |
| return new EvaluationResultImpl( |
| 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 the given [expression] is resolved to a constant |
| /// variable. |
| bool _isDebugConstant(Expression expression) { |
| Element element = null; |
| if (expression is Identifier) { |
| element = expression.staticElement; |
| } else if (expression is PropertyAccess) { |
| element = expression.propertyName.staticElement; |
| } |
| if (element is PropertyAccessorElement) { |
| PropertyInducingElement variable = element.variable; |
| return variable != null && variable.isConst; |
| } |
| return false; |
| } |
| |
| /// Exit the most recently entered label scope after reporting any labels that |
| /// were not referenced within that scope. |
| void _popLabels() { |
| for (Label label in labelTracker.unusedLabels()) { |
| _errorReporter |
| .reportErrorForNode(HintCode.UNUSED_LABEL, label, [label.label.name]); |
| } |
| labelTracker = labelTracker.outerTracker; |
| } |
| |
| /// Enter a new label scope in which the given [labels] are defined. |
| void _pushLabels(List<Label> labels) { |
| labelTracker = new _LabelTracker(labelTracker, labels); |
| } |
| } |
| |
| /// A visitor that resolves directives in an AST structure to already built |
| /// elements. |
| /// |
| /// The resulting AST must have everything resolved that would have been |
| /// resolved by a [DirectiveElementBuilder]. |
| class DirectiveResolver extends SimpleAstVisitor { |
| final Map<Source, int> sourceModificationTimeMap; |
| final Map<Source, SourceKind> importSourceKindMap; |
| final Map<Source, SourceKind> exportSourceKindMap; |
| final List<AnalysisError> errors = <AnalysisError>[]; |
| |
| LibraryElement _enclosingLibrary; |
| |
| DirectiveResolver(this.sourceModificationTimeMap, this.importSourceKindMap, |
| this.exportSourceKindMap); |
| |
| @override |
| void visitCompilationUnit(CompilationUnit node) { |
| _enclosingLibrary = |
| resolutionMap.elementDeclaredByCompilationUnit(node).library; |
| for (Directive directive in node.directives) { |
| directive.accept(this); |
| } |
| } |
| |
| @override |
| void visitExportDirective(ExportDirective node) { |
| int nodeOffset = node.offset; |
| node.element = null; |
| for (ExportElement element in _enclosingLibrary.exports) { |
| if (element.nameOffset == nodeOffset) { |
| node.element = element; |
| // Verify the exported source kind. |
| LibraryElement exportedLibrary = element.exportedLibrary; |
| if (exportedLibrary != null) { |
| Source exportedSource = exportedLibrary.source; |
| int exportedTime = sourceModificationTimeMap[exportedSource] ?? -1; |
| if (exportedTime >= 0 && |
| exportSourceKindMap[exportedSource] != SourceKind.LIBRARY) { |
| StringLiteral uriLiteral = node.uri; |
| errors.add(new AnalysisError( |
| _enclosingLibrary.source, |
| uriLiteral.offset, |
| uriLiteral.length, |
| CompileTimeErrorCode.EXPORT_OF_NON_LIBRARY, |
| [uriLiteral.toSource()])); |
| } |
| } |
| break; |
| } |
| } |
| } |
| |
| @override |
| void visitImportDirective(ImportDirective node) { |
| int nodeOffset = node.offset; |
| node.element = null; |
| for (ImportElement element in _enclosingLibrary.imports) { |
| if (element.nameOffset == nodeOffset) { |
| node.element = element; |
| // Verify the imported source kind. |
| LibraryElement importedLibrary = element.importedLibrary; |
| if (importedLibrary != null) { |
| Source importedSource = importedLibrary.source; |
| int importedTime = sourceModificationTimeMap[importedSource] ?? -1; |
| if (importedTime >= 0 && |
| importSourceKindMap[importedSource] != SourceKind.LIBRARY) { |
| StringLiteral uriLiteral = node.uri; |
| ErrorCode errorCode = element.isDeferred |
| ? StaticWarningCode.IMPORT_OF_NON_LIBRARY |
| : CompileTimeErrorCode.IMPORT_OF_NON_LIBRARY; |
| errors.add(new AnalysisError( |
| _enclosingLibrary.source, |
| uriLiteral.offset, |
| uriLiteral.length, |
| errorCode, |
| [uriLiteral.toSource()])); |
| } |
| } |
| break; |
| } |
| } |
| } |
| |
| @override |
| void visitLibraryDirective(LibraryDirective node) { |
| node.element = _enclosingLibrary; |
| } |
| } |
| |
| /// 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<PropertyAccessorElement> _accessors; |
| |
| List<ConstructorElement> _constructors; |
| |
| List<ClassElement> _enums; |
| |
| List<FieldElement> _fields; |
| |
| List<FunctionElement> _functions; |
| |
| List<LabelElement> _labels; |
| |
| List<LocalVariableElement> _localVariables; |
| |
| List<MethodElement> _methods; |
| |
| List<ClassElement> _mixins; |
| |
| List<ParameterElement> _parameters; |
| |
| List<TopLevelVariableElement> _topLevelVariables; |
| |
| List<ClassElement> _types; |
| |
| List<FunctionTypeAliasElement> _typeAliases; |
| |
| List<TypeParameterElement> _typeParameters; |
| |
| List<PropertyAccessorElement> get accessors { |
| if (_accessors == null) { |
| return const <PropertyAccessorElement>[]; |
| } |
| List<PropertyAccessorElement> result = _accessors; |
| _accessors = null; |
| return result; |
| } |
| |
| List<ConstructorElement> get constructors { |
| if (_constructors == null) { |
| return const <ConstructorElement>[]; |
| } |
| List<ConstructorElement> result = _constructors; |
| _constructors = null; |
| return result; |
| } |
| |
| List<ClassElement> get enums { |
| if (_enums == null) { |
| return const <ClassElement>[]; |
| } |
| List<ClassElement> result = _enums; |
| _enums = null; |
| return result; |
| } |
| |
| List<FieldElement> get fields { |
| if (_fields == null) { |
| return const <FieldElement>[]; |
| } |
| List<FieldElement> result = _fields; |
| _fields = null; |
| return result; |
| } |
| |
| List<FieldElement> get fieldsWithoutFlushing { |
| if (_fields == null) { |
| return const <FieldElement>[]; |
| } |
| List<FieldElement> result = _fields; |
| return result; |
| } |
| |
| List<FunctionElement> get functions { |
| if (_functions == null) { |
| return const <FunctionElement>[]; |
| } |
| List<FunctionElement> result = _functions; |
| _functions = null; |
| return result; |
| } |
| |
| List<LabelElement> get labels { |
| if (_labels == null) { |
| return const <LabelElement>[]; |
| } |
| List<LabelElement> result = _labels; |
| _labels = null; |
| return result; |
| } |
| |
| List<LocalVariableElement> get localVariables { |
| if (_localVariables == null) { |
| return const <LocalVariableElement>[]; |
| } |
| List<LocalVariableElement> result = _localVariables; |
| _localVariables = null; |
| return result; |
| } |
| |
| List<MethodElement> get methods { |
| if (_methods == null) { |
| return const <MethodElement>[]; |
| } |
| List<MethodElement> result = _methods; |
| _methods = null; |
| return result; |
| } |
| |
| List<ClassElement> get mixins { |
| if (_mixins == null) { |
| return const <ClassElement>[]; |
| } |
| List<ClassElement> result = _mixins; |
| _mixins = null; |
| return result; |
| } |
| |
| List<ParameterElement> get parameters { |
| if (_parameters == null) { |
| return const <ParameterElement>[]; |
| } |
| List<ParameterElement> result = _parameters; |
| _parameters = null; |
| return result; |
| } |
| |
| List<TopLevelVariableElement> get topLevelVariables { |
| if (_topLevelVariables == null) { |
| return const <TopLevelVariableElement>[]; |
| } |
| List<TopLevelVariableElement> result = _topLevelVariables; |
| _topLevelVariables = null; |
| return result; |
| } |
| |
| List<FunctionTypeAliasElement> get typeAliases { |
| if (_typeAliases == null) { |
| return const <FunctionTypeAliasElement>[]; |
| } |
| List<FunctionTypeAliasElement> result = _typeAliases; |
| _typeAliases = null; |
| return result; |
| } |
| |
| List<TypeParameterElement> get typeParameters { |
| if (_typeParameters == null) { |
| return const <TypeParameterElement>[]; |
| } |
| List<TypeParameterElement> result = _typeParameters; |
| _typeParameters = null; |
| return result; |
| } |
| |
| List<ClassElement> get types { |
| if (_types == null) { |
| return const <ClassElement>[]; |
| } |
| List<ClassElement> result = _types; |
| _types = null; |
| return result; |
| } |
| |
| void addAccessor(PropertyAccessorElement element) { |
| if (_accessors == null) { |
| _accessors = new List<PropertyAccessorElement>(); |
| } |
| _accessors.add(element); |
| } |
| |
| void addConstructor(ConstructorElement element) { |
| if (_constructors == null) { |
| _constructors = new List<ConstructorElement>(); |
| } |
| _constructors.add(element); |
| } |
| |
| void addEnum(ClassElement element) { |
| if (_enums == null) { |
| _enums = new List<ClassElement>(); |
| } |
| _enums.add(element); |
| } |
| |
| void addField(FieldElement element) { |
| if (_fields == null) { |
| _fields = new List<FieldElement>(); |
| } |
| _fields.add(element); |
| } |
| |
| void addFunction(FunctionElement element) { |
| if (_functions == null) { |
| _functions = new List<FunctionElement>(); |
| } |
| _functions.add(element); |
| } |
| |
| void addLabel(LabelElement element) { |
| if (_labels == null) { |
| _labels = new List<LabelElement>(); |
| } |
| _labels.add(element); |
| } |
| |
| void addLocalVariable(LocalVariableElement element) { |
| if (_localVariables == null) { |
| _localVariables = new List<LocalVariableElement>(); |
| } |
| _localVariables.add(element); |
| } |
| |
| void addMethod(MethodElement element) { |
| if (_methods == null) { |
| _methods = new List<MethodElement>(); |
| } |
| _methods.add(element); |
| } |
| |
| void addMixin(ClassElement element) { |
| if (_mixins == null) { |
| _mixins = new List<ClassElement>(); |
| } |
| _mixins.add(element); |
| } |
| |
| void addParameter(ParameterElement element) { |
| if (_parameters == null) { |
| _parameters = new List<ParameterElement>(); |
| } |
| _parameters.add(element); |
| } |
| |
| void addTopLevelVariable(TopLevelVariableElement element) { |
| if (_topLevelVariables == null) { |
| _topLevelVariables = new List<TopLevelVariableElement>(); |
| } |
| _topLevelVariables.add(element); |
| } |
| |
| void addType(ClassElement element) { |
| if (_types == null) { |
| _types = new List<ClassElement>(); |
| } |
| _types.add(element); |
| } |
| |
| void addTypeAlias(FunctionTypeAliasElement element) { |
| if (_typeAliases == null) { |
| _typeAliases = new List<FunctionTypeAliasElement>(); |
| } |
| _typeAliases.add(element); |
| } |
| |
| void addTypeParameter(TypeParameterElement element) { |
| if (_typeParameters == null) { |
| _typeParameters = new List<TypeParameterElement>(); |
| } |
| _typeParameters.add(element); |
| } |
| |
| FieldElement getField(String fieldName, {bool synthetic: false}) { |
| if (_fields == null) { |
| return null; |
| } |
| int length = _fields.length; |
| for (int i = 0; i < length; i++) { |
| FieldElement field = _fields[i]; |
| if (field.name == fieldName && field.isSynthetic == synthetic) { |
| return field; |
| } |
| } |
| return null; |
| } |
| |
| TopLevelVariableElement getTopLevelVariable(String variableName) { |
| if (_topLevelVariables == null) { |
| return null; |
| } |
| int length = _topLevelVariables.length; |
| for (int i = 0; i < length; i++) { |
| TopLevelVariableElement variable = _topLevelVariables[i]; |
| 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 `EnumMemberBuilder` build the members in enum |
| /// declarations. |
| class EnumMemberBuilder extends RecursiveAstVisitor<void> { |
| /// 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 |
| void visitEnumDeclaration(EnumDeclaration node) { |
| // |
| // Finish building the enum. |
| // |
| EnumElementImpl enumElement = node.name.staticElement as EnumElementImpl; |
| InterfaceType enumType = enumElement.type; |
| // |
| // Populate the fields. |
| // |
| List<FieldElement> fields = new List<FieldElement>(); |
| List<PropertyAccessorElement> getters = new List<PropertyAccessorElement>(); |
| InterfaceType intType = _typeProvider.intType; |
| String indexFieldName = "index"; |
| FieldElementImpl indexField = new FieldElementImpl(indexFieldName, -1); |
| indexField.isFinal = true; |
| indexField.isSynthetic = true; |
| indexField.type = intType; |
| fields.add(indexField); |
| getters.add(_createGetter(indexField)); |
| ConstFieldElementImpl valuesField = new ConstFieldElementImpl("values", -1); |
| valuesField.isStatic = true; |
| valuesField.isConst = true; |
| valuesField.isSynthetic = true; |
| valuesField.type = _typeProvider.listType.instantiate(<DartType>[enumType]); |
| fields.add(valuesField); |
| getters.add(_createGetter(valuesField)); |
| // |
| // Build the enum constants. |
| // |
| NodeList<EnumConstantDeclaration> constants = node.constants; |
| List<DartObjectImpl> constantValues = new List<DartObjectImpl>(); |
| int constantCount = constants.length; |
| for (int i = 0; i < constantCount; i++) { |
| EnumConstantDeclaration constant = constants[i]; |
| FieldElementImpl constantField = constant.name.staticElement; |
| // |
| // Create a value for the constant. |
| // |
| Map<String, DartObjectImpl> fieldMap = |
| new HashMap<String, DartObjectImpl>(); |
| fieldMap[indexFieldName] = new DartObjectImpl(intType, new IntState(i)); |
| DartObjectImpl value = |
| new DartObjectImpl(enumType, new GenericState(fieldMap)); |
| constantValues.add(value); |
| constantField.evaluationResult = new EvaluationResultImpl(value); |
| fields.add(constantField); |
| getters.add(constantField.getter); |
| } |
| // |
| // Build the value of the 'values' field. |
| // |
| valuesField.evaluationResult = new EvaluationResultImpl( |
| new DartObjectImpl(valuesField.type, new ListState(constantValues))); |
| // Update toString() return type. |
| { |
| MethodElementImpl toStringMethod = enumElement.methods[0]; |
| toStringMethod.returnType = _typeProvider.stringType; |
| toStringMethod.type = new FunctionTypeImpl(toStringMethod); |
| } |
| // |
| // 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. |
| super.visitEnumDeclaration(node); |
| } |
| |
| /// Create a getter that corresponds to the given [field]. |
| PropertyAccessorElement _createGetter(FieldElementImpl field) { |
| return new PropertyAccessorElementImpl_ImplicitGetter(field); |
| } |
| } |
| |
| /// 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) { |
| _visitDirective(node); |
| } |
| |
| @override |
| void visitImportDirective(ImportDirective node) { |
| _visitDirective(node); |
| } |
| |
| @override |
| void visitLibraryDirective(LibraryDirective node) { |
| _visitDirective(node); |
| } |
| |
| @override |
| void visitSimpleIdentifier(SimpleIdentifier node) { |
| _visitIdentifier(node, node.staticElement); |
| } |
| |
| /// If the given [identifier] is prefixed with a [PrefixElement], fill the |
| /// corresponding `UsedImportedElements.prefixMap` entry and return `true`. |
| bool _recordPrefixMap(SimpleIdentifier identifier, Element element) { |
| bool recordIfTargetIsPrefixElement(Expression target) { |
| if (target is SimpleIdentifier && target.staticElement is PrefixElement) { |
| List<Element> prefixedElements = usedElements.prefixMap |
| .putIfAbsent(target.staticElement, () => <Element>[]); |
| prefixedElements.add(element); |
| return true; |
| } |
| return false; |
| } |
| |
| AstNode parent = identifier.parent; |
| if (parent is MethodInvocation && parent.methodName == identifier) { |
| return recordIfTargetIsPrefixElement(parent.target); |
| } |
| if (parent is PrefixedIdentifier && parent.identifier == identifier) { |
| return recordIfTargetIsPrefixElement(parent.prefix); |
| } |
| return false; |
| } |
| |
| /// Visit identifiers used by the given [directive]. |
| void _visitDirective(Directive directive) { |
| directive.documentationComment?.accept(this); |
| directive.metadata.accept(this); |
| } |
| |
| void _visitIdentifier(SimpleIdentifier identifier, Element element) { |
| 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) { |
| List<Element> conflictingElements = element.conflictingElements; |
| int length = conflictingElements.length; |
| for (int i = 0; i < length; i++) { |
| Element elt = conflictingElements[i]; |
| _visitIdentifier(identifier, elt); |
| } |
| return; |
| } |
| |
| // Record `importPrefix.identifier` into 'prefixMap'. |
| if (_recordPrefixMap(identifier, element)) { |
| return; |
| } |
| |
| if (element is PrefixElement) { |
| usedElements.prefixMap.putIfAbsent(element, () => <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); |
| } |
| } |
| |
| /// 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.declaredElement; |
| super.visitClassDeclaration(node); |
| } finally { |
| _enclosingClass = enclosingClassOld; |
| } |
| } |
| |
| @override |
| visitFunctionDeclaration(FunctionDeclaration node) { |
| ExecutableElement enclosingExecOld = _enclosingExec; |
| try { |
| _enclosingExec = node.declaredElement; |
| super.visitFunctionDeclaration(node); |
| } finally { |
| _enclosingExec = enclosingExecOld; |
| } |
| } |
| |
| @override |
| visitFunctionExpression(FunctionExpression node) { |
| if (node.parent is! FunctionDeclaration) { |
| usedElements.addElement(node.declaredElement); |
| } |
| super.visitFunctionExpression(node); |
| } |
| |
| @override |
| visitMethodDeclaration(MethodDeclaration node) { |
| ExecutableElement enclosingExecOld = _enclosingExec; |
| try { |
| _enclosingExec = node.declaredElement; |
| 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 PropertyAccessorElement && |
| element.isSynthetic && |
| isIdentifierRead && |
| element.variable is TopLevelVariableElement) { |
| usedElements.addElement(element.variable); |
| } else if (element is LocalVariableElement) { |
| if (isIdentifierRead) { |
| usedElements.addElement(element); |
| } |
| } else { |
| _useIdentifierElement(node); |
| if (element == null || |
| element.enclosingElement is ClassElement && |
| !identical(element, _enclosingExec)) { |
| usedElements.members.add(node.name); |
| if (isIdentifierRead) { |
| usedElements.readMembers.add(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) { |
| if (element is ClassElement) { |
| AstNode parent2 = node.parent.parent; |
| if (parent2 is IsExpression) { |
| return; |
| } |
| if (parent2 is VariableDeclarationList) { |
| // If it's a field's type, it still counts as used. |
| if (parent2.parent is! FieldDeclaration) { |
| 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) { |
| if (parent is PrefixExpression || parent is PostfixExpression) { |
| // v++; |
| // ++v; |
| return false; |
| } |
| if (parent is AssignmentExpression && parent.leftHandSide == node) { |
| // v ??= doSomething(); |
| // vs. |
| // v += 2; |
| TokenType operatorType = parent.operator?.type; |
| return operatorType == TokenType.QUESTION_QUESTION_EQ; |
| } |
| } |
| // OK |
| return true; |
| } |
| } |
| |
| /// 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] hint is generated with |
| /// [generateUnusedImportHints]. |
| /// |
| /// Additionally, [generateDuplicateImportHints] generates |
| /// [HintCode.DUPLICATE_IMPORT] hints and [HintCode.UNUSED_SHOWN_NAME] hints. |
| /// |
| /// 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 { |
| /// All [ImportDirective]s of the current library. |
| final List<ImportDirective> _allImports = <ImportDirective>[]; |
| |
| /// A list of [ImportDirective]s that the current library imports, but does |
| /// not use. |
| /// |
| /// 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<ImportDirective> _unusedImports = <ImportDirective>[]; |
| |
| /// 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<ImportDirective> _duplicateImports = <ImportDirective>[]; |
| |
| /// The cache of [Namespace]s for [ImportDirective]s. |
| final HashMap<ImportDirective, Namespace> _namespaceMap = |
| new HashMap<ImportDirective, Namespace>(); |
| |
| /// 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<PrefixElement, List<ImportDirective>> _prefixElementMap = |
| new HashMap<PrefixElement, List<ImportDirective>>(); |
| |
| /// A map of identifiers that the current library's imports show, but that the |
| /// library does not use. |
| /// |
| /// Each import directive maps to a list of the identifiers that are imported |
| /// via the "show" keyword. |
| /// |
| /// As each identifier is visited by this visitor, it is identified as being |
| /// used by the library, and the identifier is removed from this map (under |
| /// the import that imported it). After all the sources in the library have |
| /// been evaluated, each list in this map's values present the set of unused |
| /// shown elements. |
| /// |
| /// See [ImportsVerifier.generateUnusedShownNameHints]. |
| final HashMap<ImportDirective, List<SimpleIdentifier>> _unusedShownNamesMap = |
| new HashMap<ImportDirective, List<SimpleIdentifier>>(); |
| |
| /// A map of names that are hidden more than once. |
| final HashMap<NamespaceDirective, List<SimpleIdentifier>> |
| _duplicateHiddenNamesMap = |
| new HashMap<NamespaceDirective, List<SimpleIdentifier>>(); |
| |
| /// A map of names that are shown more than once. |
| final HashMap<NamespaceDirective, List<SimpleIdentifier>> |
| _duplicateShownNamesMap = |
| new HashMap<NamespaceDirective, List<SimpleIdentifier>>(); |
| |
| void addImports(CompilationUnit node) { |
| for (Directive directive in node.directives) { |
| if (directive is ImportDirective) { |
| LibraryElement libraryElement = directive.uriElement; |
| if (libraryElement == null) { |
| continue; |
| } |
| _allImports.add(directive); |
| _unusedImports.add(directive); |
| // |
| // Initialize prefixElementMap |
| // |
| if (directive.asKeyword != null) { |
| SimpleIdentifier prefixIdentifier = directive.prefix; |
| if (prefixIdentifier != null) { |
| Element element = prefixIdentifier.staticElement; |
| if (element is PrefixElement) { |
| List<ImportDirective> list = _prefixElementMap[element]; |
| if (list == null) { |
| list = new List<ImportDirective>(); |
| _prefixElementMap[element] = list; |
| } |
| list.add(directive); |
| } |
| // TODO (jwren) Can the element ever not be a PrefixElement? |
| } |
| } |
| _addShownNames(directive); |
| } |
| if (directive is NamespaceDirective) { |
| _addDuplicateShownHiddenNames(directive); |
| } |
| } |
| if (_unusedImports.length > 1) { |
| // order the list of unusedImports to find duplicates in faster than |
| // O(n^2) time |
| List<ImportDirective> importDirectiveArray = |
| new List<ImportDirective>.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) { |
| int length = _duplicateImports.length; |
| for (int i = 0; i < length; i++) { |
| errorReporter.reportErrorForNode( |
| HintCode.DUPLICATE_IMPORT, _duplicateImports[i].uri); |
| } |
| } |
| |
| /// Report a [HintCode.DUPLICATE_SHOWN_HIDDEN_NAME] hint for each duplicate |
| /// shown or hidden name. |
| /// |
| /// Only call this method after all of the compilation units have been visited |
| /// by this visitor. |
| /// |
| /// @param errorReporter the error reporter used to report the set of |
| /// [HintCode.UNUSED_SHOWN_NAME] hints |
| void generateDuplicateShownHiddenNameHints(ErrorReporter reporter) { |
| _duplicateHiddenNamesMap.forEach( |
| (NamespaceDirective directive, List<SimpleIdentifier> identifiers) { |
| int length = identifiers.length; |
| for (int i = 0; i < length; i++) { |
| Identifier identifier = identifiers[i]; |
| reporter.reportErrorForNode( |
| HintCode.DUPLICATE_HIDDEN_NAME, identifier, [identifier.name]); |
| } |
| }); |
| _duplicateShownNamesMap.forEach( |
| (NamespaceDirective directive, List<SimpleIdentifier> identifiers) { |
| int length = identifiers.length; |
| for (int i = 0; i < length; i++) { |
| Identifier identifier = identifiers[i]; |
| reporter.reportErrorForNode( |
| HintCode.DUPLICATE_SHOWN_NAME, identifier, [identifier.name]); |
| } |
| }); |
| } |
| |
| /// Report an [HintCode.UNUSED_IMPORT] hint for each unused import. |
| /// |
| /// Only call this method after all of the compilation units have been visited |
| /// by this visitor. |
| /// |
| /// @param errorReporter the error reporter used to report the set of |
| /// [HintCode.UNUSED_IMPORT] hints |
| void generateUnusedImportHints(ErrorReporter errorReporter) { |
| int length = _unusedImports.length; |
| for (int i = 0; i < length; i++) { |
| ImportDirective unusedImport = _unusedImports[i]; |
| // Check that the imported URI exists and isn't dart:core |
| ImportElement importElement = unusedImport.element; |
| if (importElement != null) { |
| LibraryElement libraryElement = importElement.importedLibrary; |
| if (libraryElement == null || |
| libraryElement.isDartCore || |
| libraryElement.isSynthetic) { |
| continue; |
| } |
| } |
| StringLiteral uri = unusedImport.uri; |
| errorReporter |
| .reportErrorForNode(HintCode.UNUSED_IMPORT, uri, [uri.stringValue]); |
| } |
| } |
| |
| /// Use the error [reporter] to report an [HintCode.UNUSED_SHOWN_NAME] hint |
| /// for each unused shown name. |
| /// |
| /// This method should only be invoked after all of the compilation units have |
| /// been visited by this visitor. |
| void generateUnusedShownNameHints(ErrorReporter reporter) { |
| _unusedShownNamesMap.forEach( |
| (ImportDirective importDirective, List<SimpleIdentifier> identifiers) { |
|