diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart index 56797526ea3..a47d7a0865b 100644 --- a/pkg/analyzer/lib/error/error.dart +++ b/pkg/analyzer/lib/error/error.dart @@ -257,6 +257,8 @@ const List errorCodeValues = const [ HintCode.DEPRECATED_MIXIN_FUNCTION, HintCode.DIVISION_OPTIMIZATION, HintCode.DUPLICATE_IMPORT, + HintCode.DUPLICATE_HIDDEN_NAME, + HintCode.DUPLICATE_SHOWN_NAME, HintCode.FILE_IMPORT_INSIDE_LIB_REFERENCES_FILE_OUTSIDE, HintCode.FILE_IMPORT_OUTSIDE_LIB_REFERENCES_FILE_INSIDE, HintCode.GENERIC_METHOD_COMMENT, diff --git a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart index b3b523056f1..0e6d519a660 100644 --- a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart +++ b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart @@ -334,6 +334,7 @@ class LibraryAnalyzer { verifier.addImports(unit); _usedImportedElementsList.forEach(verifier.removeUsedElements); verifier.generateDuplicateImportHints(errorReporter); + verifier.generateDuplicateShownHiddenNameHints(errorReporter); verifier.generateUnusedImportHints(errorReporter); verifier.generateUnusedShownNameHints(errorReporter); } diff --git a/pkg/analyzer/lib/src/dart/error/hint_codes.dart b/pkg/analyzer/lib/src/dart/error/hint_codes.dart index 8d2ab0f6c1b..560a1e522ba 100644 --- a/pkg/analyzer/lib/src/dart/error/hint_codes.dart +++ b/pkg/analyzer/lib/src/dart/error/hint_codes.dart @@ -131,6 +131,22 @@ class HintCode extends ErrorCode { 'DUPLICATE_IMPORT', "Duplicate import.", correction: "Try removing all but one import of the library."); + /** + * Duplicate hidden names. + */ + static const HintCode DUPLICATE_HIDDEN_NAME = + const HintCode('DUPLICATE_HIDDEN_NAME', "Duplicate hidden name.", + correction: "Try removing the repeated name from the list of hidden " + "members."); + + /** + * Duplicate shown names. + */ + static const HintCode DUPLICATE_SHOWN_NAME = + const HintCode('DUPLICATE_SHOWN_NAME', "Duplicate shown name.", + correction: "Try removing the repeated name from the list of shown " + "members."); + /** * It is a bad practice for a source file in a package "lib" directory * hierarchy to traverse outside that directory hierarchy. For example, a diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart index 6fdac9541a2..858e83f09a3 100644 --- a/pkg/analyzer/lib/src/generated/resolver.dart +++ b/pkg/analyzer/lib/src/generated/resolver.dart @@ -4076,6 +4076,20 @@ class ImportsVerifier { final HashMap> _unusedShownNamesMap = new HashMap>(); + /** + * A map of names that are hidden more than once. + */ + final HashMap> + _duplicateHiddenNamesMap = + new HashMap>(); + + /** + * A map of names that are shown more than once. + */ + final HashMap> + _duplicateShownNamesMap = + new HashMap>(); + void addImports(CompilationUnit node) { for (Directive directive in node.directives) { if (directive is ImportDirective) { @@ -4105,6 +4119,9 @@ class ImportsVerifier { } _addShownNames(directive); } + if (directive is NamespaceDirective) { + _addDuplicateShownHiddenNames(directive); + } } if (_unusedImports.length > 1) { // order the list of unusedImports to find duplicates in faster than @@ -4199,6 +4216,37 @@ class ImportsVerifier { }); } + /** + * 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 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 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]); + } + }); + } + /** * Remove elements from [_unusedImports] using the given [usedElements]. */ @@ -4262,6 +4310,43 @@ class ImportsVerifier { } } + /** + * Add duplicate shown and hidden names from [directive] into + * [_duplicateHiddenNamesMap] and [_duplicateShownNamesMap]. + */ + void _addDuplicateShownHiddenNames(NamespaceDirective directive) { + if (directive.combinators == null) { + return; + } + for (Combinator combinator in directive.combinators) { + // Use a Set to find duplicates in faster than O(n^2) time. + Set identifiers = new Set(); + if (combinator is HideCombinator) { + for (SimpleIdentifier name in combinator.hiddenNames) { + if (name.staticElement != null) { + if (!identifiers.add(name.staticElement)) { + // [name] is a duplicate. + List duplicateNames = _duplicateHiddenNamesMap + .putIfAbsent(directive, () => new List()); + duplicateNames.add(name); + } + } + } + } else if (combinator is ShowCombinator) { + for (SimpleIdentifier name in combinator.shownNames) { + if (name.staticElement != null) { + if (!identifiers.add(name.staticElement)) { + // [name] is a duplicate. + List duplicateNames = _duplicateShownNamesMap + .putIfAbsent(directive, () => new List()); + duplicateNames.add(name); + } + } + } + } + } + } + /** * Lookup and return the [Namespace] from the [_namespaceMap]. * diff --git a/pkg/analyzer/lib/src/task/dart.dart b/pkg/analyzer/lib/src/task/dart.dart index d83a9bcbd7d..a1c723247bc 100644 --- a/pkg/analyzer/lib/src/task/dart.dart +++ b/pkg/analyzer/lib/src/task/dart.dart @@ -2836,6 +2836,7 @@ class GenerateHintsTask extends SourceBasedAnalysisTask { verifier.addImports(unit); usedImportedElementsList.forEach(verifier.removeUsedElements); verifier.generateDuplicateImportHints(errorReporter); + verifier.generateDuplicateShownHiddenNameHints(errorReporter); verifier.generateUnusedImportHints(errorReporter); verifier.generateUnusedShownNameHints(errorReporter); } diff --git a/pkg/analyzer/test/generated/hint_code_test.dart b/pkg/analyzer/test/generated/hint_code_test.dart index f93161b12d3..f37baf0398c 100644 --- a/pkg/analyzer/test/generated/hint_code_test.dart +++ b/pkg/analyzer/test/generated/hint_code_test.dart @@ -1241,6 +1241,32 @@ class B {}'''); verify([source]); } + test_duplicateShownHiddenName_hidden() async { + Source source = addSource(r''' +library L; +export 'lib1.dart' hide A, B, A;'''); + addNamedSource("/lib1.dart", r''' +library lib1; +class A {} +class B {}'''); + await computeAnalysisResult(source); + assertErrors(source, [HintCode.DUPLICATE_HIDDEN_NAME]); + verify([source]); + } + + test_duplicateShownHiddenName_shown() async { + Source source = addSource(r''' +library L; +export 'lib1.dart' show A, B, A;'''); + addNamedSource("/lib1.dart", r''' +library lib1; +class A {} +class B {}'''); + await computeAnalysisResult(source); + assertErrors(source, [HintCode.DUPLICATE_SHOWN_NAME]); + verify([source]); + } + test_factory__expr_return_null_OK() async { Source source = addSource(r''' import 'package:meta/meta.dart';