| // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'package:analyzer/dart/ast/token.dart'; | |
| import 'package:analyzer/dart/element/element.dart'; | |
| import 'package:analyzer/src/dart/ast/ast.dart'; | |
| import 'package:analyzer/src/dart/element/element.dart'; | |
| import 'package:analyzer/src/dart/element/extensions.dart'; | |
| import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag; | |
| import 'package:analyzer/src/diagnostic/diagnostic_factory.dart'; | |
| import 'package:analyzer/src/error/codes.dart'; | |
| import 'package:analyzer/src/error/listener.dart'; | |
| import 'package:analyzer/src/utilities/extensions/element.dart'; | |
| class DuplicateDefinitionVerifier { | |
| final LibraryElementImpl _currentLibrary; | |
| final DiagnosticReporter _diagnosticReporter; | |
| final DiagnosticFactory _diagnosticFactory = DiagnosticFactory(); | |
| final Set<Token> _reportedTokens = Set.identity(); | |
| DuplicateDefinitionVerifier(this._currentLibrary, this._diagnosticReporter); | |
| /// Check that the exception and stack trace parameters have different names. | |
| void checkCatchClause(CatchClause node) { | |
| var exceptionParameter = node.exceptionParameter; | |
| var stackTraceParameter = node.stackTraceParameter; | |
| if (exceptionParameter != null && stackTraceParameter != null) { | |
| var element = exceptionParameter.declaredFragment?.element; | |
| if (element != null && element.isWildcardVariable) return; | |
| String exceptionName = exceptionParameter.name.lexeme; | |
| if (exceptionName == stackTraceParameter.name.lexeme) { | |
| _diagnosticReporter.report( | |
| _diagnosticFactory.duplicateDefinitionForNodes( | |
| _diagnosticReporter.source, | |
| diag.duplicateDefinition.withArguments(name: exceptionName), | |
| stackTraceParameter, | |
| exceptionParameter, | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| /// Check that the given list of variable declarations does not define | |
| /// multiple variables of the same name. | |
| void checkForVariables(VariableDeclarationListImpl node) { | |
| var scope = _DuplicateIdentifierScope(this); | |
| for (var variable in node.variables) { | |
| scope.add(variable.name, node: variable); | |
| } | |
| } | |
| /// Check that all of the parameters have unique names. | |
| void checkParameters(FormalParameterListImpl node) { | |
| var scope = _FormalParameterDuplicateIdentifierScope(this); | |
| for (var parameter in node.allFormalParameters) { | |
| // The identifier can be null if this is a parameter list for a generic | |
| // function type. | |
| var identifier = parameter.name; | |
| if (identifier == null) { | |
| continue; | |
| } | |
| scope.add(identifier, node: parameter); | |
| } | |
| // For private named parameters, also look for collisions with their public | |
| // name and other parameters. | |
| for (var parameter in node.allFormalParameters) { | |
| if (parameter.declaredFragment | |
| case FieldFormalParameterFragment fragment) { | |
| if (fragment.privateName != null) { | |
| scope.checkPublicName( | |
| privateName: parameter.name!, | |
| publicName: fragment.name!, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| /// Check that all of the variables have unique names. | |
| void checkStatements(List<StatementImpl> statements) { | |
| var scope = _DuplicateIdentifierScope(this); | |
| for (var statement in statements) { | |
| if (statement is VariableDeclarationStatementImpl) { | |
| for (var variable in statement.variables.variables) { | |
| scope.add(variable.name, node: variable); | |
| } | |
| } else if (statement is FunctionDeclarationStatementImpl) { | |
| if (!_isWildCardFunction(statement)) { | |
| scope.add( | |
| statement.functionDeclaration.name, | |
| node: statement.functionDeclaration, | |
| ); | |
| } | |
| } else if (statement is PatternVariableDeclarationStatementImpl) { | |
| for (var variable in statement.declaration.elements) { | |
| scope.add(variable.node.name, node: variable.node); | |
| } | |
| } | |
| } | |
| } | |
| /// Check that all of the parameters have unique names. | |
| void checkTypeParameters(TypeParameterListImpl node) { | |
| var scope = _DuplicateIdentifierScope(this); | |
| for (var parameter in node.typeParameters) { | |
| scope.add(parameter.name, node: parameter); | |
| } | |
| } | |
| /// Check that there are no members with the same name. | |
| void checkUnit(CompilationUnitImpl node) { | |
| var fragment = node.declaredFragment!; | |
| var definedGetters = <String, ElementImpl>{}; | |
| var definedSetters = <String, ElementImpl>{}; | |
| void addWithoutChecking(LibraryFragmentImpl libraryFragment) { | |
| for (var fragment in libraryFragment.getters) { | |
| var element = fragment.element; | |
| if (element.lookupName case var name?) { | |
| definedGetters[name] = element; | |
| } | |
| } | |
| for (var fragment in libraryFragment.setters) { | |
| var element = fragment.element; | |
| if (element.lookupName case var name?) { | |
| definedSetters[name] = element; | |
| } | |
| } | |
| for (var fragment in libraryFragment.classes) { | |
| var element = fragment.element; | |
| if (element.lookupName case var name?) { | |
| definedGetters[name] = element; | |
| } | |
| } | |
| for (var fragment in libraryFragment.enums) { | |
| var element = fragment.element; | |
| if (element.lookupName case var name?) { | |
| definedGetters[name] = element; | |
| } | |
| } | |
| for (var fragment in libraryFragment.extensions) { | |
| var element = fragment.element; | |
| if (element.lookupName case var name?) { | |
| definedGetters[name] = element; | |
| } | |
| } | |
| for (var fragment in libraryFragment.extensionTypes) { | |
| var element = fragment.element; | |
| if (element.lookupName case var name?) { | |
| definedGetters[name] = element; | |
| } | |
| } | |
| for (var fragment in libraryFragment.functions) { | |
| var element = fragment.element; | |
| if (element.lookupName case var name?) { | |
| definedGetters[name] = element; | |
| } | |
| } | |
| for (var fragment in libraryFragment.mixins) { | |
| var element = fragment.element; | |
| if (element.lookupName case var name?) { | |
| definedGetters[name] = element; | |
| } | |
| } | |
| for (var fragment in libraryFragment.typeAliases) { | |
| var element = fragment.element; | |
| if (element.lookupName case var name?) { | |
| definedGetters[name] = element; | |
| } | |
| } | |
| } | |
| var libraryDeclarations = _currentLibrary.libraryDeclarations; | |
| for (var importPrefix in fragment.prefixes) { | |
| var name = importPrefix.name; | |
| if (name != null) { | |
| if (libraryDeclarations.withName(name) case var existing?) { | |
| _diagnosticReporter.report( | |
| _diagnosticFactory.duplicateDefinition( | |
| diag.prefixCollidesWithTopLevelMember.withArguments(name: name), | |
| importPrefix.firstFragment, | |
| existing as ElementImpl, | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| // TODO(scheglov): carry across resolved units | |
| var currentLibraryFragment = node.declaredFragment!; | |
| for (var libraryFragment in _currentLibrary.fragments) { | |
| if (libraryFragment == currentLibraryFragment) { | |
| break; | |
| } | |
| addWithoutChecking(libraryFragment); | |
| } | |
| for (var member in node.declarations2) { | |
| switch (member) { | |
| case ClassDeclarationImpl(): | |
| var declaredFragment = member.declaredFragment!; | |
| if (!declaredFragment.isAugmentation) { | |
| _checkDuplicateFragmentIdentifier( | |
| definedGetters, | |
| member.namePart.typeName, | |
| fragment: declaredFragment, | |
| ); | |
| } | |
| case EnumDeclarationImpl(): | |
| var declaredFragment = member.declaredFragment!; | |
| if (!declaredFragment.isAugmentation) { | |
| _checkDuplicateFragmentIdentifier( | |
| definedGetters, | |
| member.namePart.typeName, | |
| fragment: declaredFragment, | |
| ); | |
| } | |
| case ExtensionDeclarationImpl(): | |
| var identifier = member.name; | |
| if (identifier != null) { | |
| var declaredFragment = member.declaredFragment!; | |
| if (!declaredFragment.isAugmentation) { | |
| _checkDuplicateFragmentIdentifier( | |
| definedGetters, | |
| identifier, | |
| fragment: declaredFragment, | |
| ); | |
| } | |
| } | |
| case ExtensionTypeDeclarationImpl(): | |
| var declaredFragment = member.declaredFragment!; | |
| if (!declaredFragment.isAugmentation) { | |
| _checkDuplicateFragmentIdentifier( | |
| definedGetters, | |
| member.namePart.typeName, | |
| fragment: declaredFragment, | |
| ); | |
| } | |
| case FunctionDeclarationImpl(): | |
| var declaredFragment = member.declaredFragment!; | |
| if (!declaredFragment.isAugmentation) { | |
| if (declaredFragment is SetterFragment) { | |
| _checkDuplicateFragmentIdentifier( | |
| definedSetters, | |
| member.name, | |
| fragment: declaredFragment, | |
| ); | |
| } else { | |
| _checkDuplicateFragmentIdentifier( | |
| definedGetters, | |
| member.name, | |
| fragment: declaredFragment, | |
| ); | |
| } | |
| } | |
| case TopLevelGetterDeclarationImpl(): | |
| var declaredFragment = member.declaredFragment!; | |
| if (!declaredFragment.isAugmentation) { | |
| _checkDuplicateFragmentIdentifier( | |
| definedGetters, | |
| member.name, | |
| fragment: declaredFragment, | |
| ); | |
| } | |
| case MixinDeclarationImpl(): | |
| var declaredFragment = member.declaredFragment!; | |
| if (!declaredFragment.isAugmentation) { | |
| _checkDuplicateFragmentIdentifier( | |
| definedGetters, | |
| member.name, | |
| fragment: declaredFragment, | |
| ); | |
| } | |
| case TopLevelVariableDeclarationImpl(): | |
| for (var variable in member.variables.variables) { | |
| var declaredFragment = variable.declaredFragment; | |
| declaredFragment as TopLevelVariableFragmentImpl; | |
| if (!declaredFragment.isAugmentation) { | |
| var declaredElement = declaredFragment.element; | |
| if (declaredElement.getter?.firstFragment case var getter?) { | |
| _checkDuplicateFragmentIdentifier( | |
| definedGetters, | |
| variable.name, | |
| originFragment: declaredFragment, | |
| fragment: getter, | |
| ); | |
| } | |
| if (declaredElement.definesSetter) { | |
| if (declaredElement.setter?.firstFragment case var setter?) { | |
| _checkDuplicateFragmentIdentifier( | |
| definedSetters, | |
| variable.name, | |
| originFragment: declaredFragment, | |
| fragment: setter, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| case TypeAliasImpl(): | |
| var declaredFragment = member.declaredFragment!; | |
| if (!declaredFragment.isAugmentation) { | |
| _checkDuplicateFragmentIdentifier( | |
| definedGetters, | |
| member.name, | |
| fragment: declaredFragment, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| /// Check whether the given [fragment] defined by the [identifier] is already | |
| /// in [scope], and produce an error if it is. | |
| void _checkDuplicateFragmentIdentifier( | |
| Map<String, ElementImpl> scope, | |
| Token identifier, { | |
| FragmentImpl? originFragment, | |
| required FragmentImpl fragment, | |
| }) { | |
| if (identifier.isSynthetic) { | |
| return; | |
| } | |
| var lookupName = fragment.element.lookupName; | |
| if (lookupName == null) { | |
| return; | |
| } | |
| if (_reportedTokens.contains(identifier)) { | |
| return; | |
| } | |
| if (scope[lookupName] case var previous?) { | |
| _reportedTokens.add(identifier); | |
| _diagnosticReporter.report( | |
| _diagnosticFactory.duplicateDefinition( | |
| diag.duplicateDefinition.withArguments(name: lookupName), | |
| originFragment ?? fragment, | |
| previous, | |
| ), | |
| ); | |
| } else { | |
| scope[lookupName] = fragment.element; | |
| } | |
| } | |
| bool _isWildCardFunction(FunctionDeclarationStatement statement) => | |
| statement.functionDeclaration.name.lexeme == '_' && | |
| _currentLibrary.hasWildcardVariablesFeatureEnabled; | |
| } | |
| /// A single scope where colliding definitions with the same name is an error. | |
| class _DuplicateIdentifierScope<T extends AstNode> { | |
| final DuplicateDefinitionVerifier _verifier; | |
| final Map<String, (T, Token)> _scope = {}; | |
| _DuplicateIdentifierScope(this._verifier); | |
| /// Reports an error if there is already a node in this scope with the given | |
| /// [identifier] name. | |
| /// | |
| /// Otherwise, adds [node] to the scope. | |
| void add(Token identifier, {required T node}) { | |
| if (identifier.isSynthetic) { | |
| return; | |
| } | |
| if (_verifier._reportedTokens.contains(identifier)) { | |
| return; | |
| } | |
| // Wildcards do not collide. | |
| if (isWildcard(identifier, node)) { | |
| return; | |
| } | |
| if (_scope[identifier.lexeme] case (var previousNode, var previousToken)) { | |
| _verifier._reportedTokens.add(identifier); | |
| _verifier._diagnosticReporter.report( | |
| _verifier._diagnosticFactory.duplicateDefinitionForNodes( | |
| _verifier._diagnosticReporter.source, | |
| getDiagnostic( | |
| previousNode, | |
| node, | |
| ).withArguments(name: identifier.lexeme), | |
| identifier, | |
| previousToken, | |
| ), | |
| ); | |
| } else { | |
| _scope[identifier.lexeme] = (node, identifier); | |
| } | |
| } | |
| /// The diagnostic code to use when [previous] and [current] have the same | |
| /// name. | |
| DiagnosticWithArguments<LocatableDiagnostic Function({required String name})> | |
| getDiagnostic(T previous, T current) => diag.duplicateDefinition; | |
| /// Whether [node] named [identifier] acts as a wildcard. | |
| bool isWildcard(Token identifier, T node) { | |
| return identifier.lexeme == '_' && | |
| _verifier._currentLibrary.hasWildcardVariablesFeatureEnabled; | |
| } | |
| } | |
| /// A [_DuplicateIdentifierScope] for formal parameters. | |
| /// | |
| /// Handles private named parameters and initializing formals. | |
| class _FormalParameterDuplicateIdentifierScope | |
| extends _DuplicateIdentifierScope<FormalParameter> { | |
| _FormalParameterDuplicateIdentifierScope(super.verifier); | |
| /// Given a private named parameter with [privateName] and corresponding | |
| /// [publicName], checks that the public name doesn't collide with any other | |
| /// parameter. | |
| void checkPublicName({ | |
| required Token privateName, | |
| required String publicName, | |
| }) { | |
| if (_scope[publicName] case (var _, var previousToken)) { | |
| _verifier._diagnosticReporter.report( | |
| _verifier._diagnosticFactory.duplicateDefinitionForNodes( | |
| _verifier._diagnosticReporter.source, | |
| diag.privateNamedParameterDuplicatePublicName.withArguments( | |
| name: publicName, | |
| ), | |
| privateName, | |
| previousToken, | |
| ), | |
| ); | |
| } | |
| } | |
| @override | |
| DiagnosticWithArguments<LocatableDiagnostic Function({required String name})> | |
| getDiagnostic(FormalParameter previous, FormalParameter current) { | |
| // When two initializing formals collide, tell the user they can't | |
| // initialize the same field twice. | |
| if (previous is FieldFormalParameter && current is FieldFormalParameter) { | |
| return diag.duplicateFieldFormalParameter; | |
| } | |
| return diag.duplicateDefinition; | |
| } | |
| @override | |
| bool isWildcard(Token identifier, FormalParameter node) { | |
| if (!super.isWildcard(identifier, node)) { | |
| return false; | |
| } | |
| // Since fields can be named `_`, initializing formals are not | |
| // considered wildcards. | |
| var element = node.declaredFragment!.element; | |
| if (element is FieldFormalParameterElement) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| } |