[analyzer] add `HintCode.DUPLICATE_EXPORT`
Fixes #49439
Change-Id: I511205c6b0960f6b19a2ac45211bc1348568f52c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/260703
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index b9eb8aa..c662700 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -1293,6 +1293,10 @@
status: hasFix
HintCode.DIVISION_OPTIMIZATION:
status: hasFix
+HintCode.DUPLICATE_EXPORT:
+ status: needsFix
+ notes: |-
+ One fix is to remove the duplicated export.
HintCode.DUPLICATE_HIDDEN_NAME:
status: hasFix
HintCode.DUPLICATE_IGNORE:
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index 8aab96d..cfaaff0 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -571,6 +571,7 @@
HintCode.DEPRECATED_MIXIN_FUNCTION,
HintCode.DEPRECATED_NEW_IN_COMMENT_REFERENCE,
HintCode.DIVISION_OPTIMIZATION,
+ HintCode.DUPLICATE_EXPORT,
HintCode.DUPLICATE_HIDDEN_NAME,
HintCode.DUPLICATE_IGNORE,
HintCode.DUPLICATE_IMPORT,
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
index 6bf363c..8a626d9 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
@@ -371,6 +371,7 @@
ImportsVerifier verifier = ImportsVerifier();
verifier.addImports(unit);
usedImportedElements.forEach(verifier.removeUsedElements);
+ verifier.generateDuplicateExportHints(errorReporter);
verifier.generateDuplicateImportHints(errorReporter);
verifier.generateDuplicateShownHiddenNameHints(errorReporter);
verifier.generateUnusedImportHints(errorReporter);
diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart
index 4968e0a..13e53dd 100644
--- a/pkg/analyzer/lib/src/dart/ast/ast.dart
+++ b/pkg/analyzer/lib/src/dart/ast/ast.dart
@@ -7018,10 +7018,12 @@
/// to the same absolute URI, so to the same library, regardless of the used
/// syntax (absolute, relative, not normalized).
static bool areSyntacticallyIdenticalExceptUri(
- ImportDirective node1,
- ImportDirective node2,
+ NamespaceDirective node1,
+ NamespaceDirective node2,
) {
- if (node1.prefix?.name != node2.prefix?.name) {
+ if (node1 is ImportDirective &&
+ node2 is ImportDirective &&
+ node1.prefix?.name != node2.prefix?.name) {
return false;
}
diff --git a/pkg/analyzer/lib/src/dart/error/hint_codes.g.dart b/pkg/analyzer/lib/src/dart/error/hint_codes.g.dart
index e0b35ac..0e03dc8 100644
--- a/pkg/analyzer/lib/src/dart/error/hint_codes.g.dart
+++ b/pkg/analyzer/lib/src/dart/error/hint_codes.g.dart
@@ -205,6 +205,15 @@
hasPublishedDocs: true,
);
+ /// Duplicate exports.
+ ///
+ /// No parameters.
+ static const HintCode DUPLICATE_EXPORT = HintCode(
+ 'DUPLICATE_EXPORT',
+ "Duplicate export.",
+ correctionMessage: "Try removing all but one export of the library.",
+ );
+
/// No parameters.
static const HintCode DUPLICATE_HIDDEN_NAME = HintCode(
'DUPLICATE_HIDDEN_NAME',
diff --git a/pkg/analyzer/lib/src/error/imports_verifier.dart b/pkg/analyzer/lib/src/error/imports_verifier.dart
index de7ce08..319cda1 100644
--- a/pkg/analyzer/lib/src/error/imports_verifier.dart
+++ b/pkg/analyzer/lib/src/error/imports_verifier.dart
@@ -2,8 +2,6 @@
// 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/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
@@ -199,7 +197,7 @@
/// future.
class ImportsVerifier {
/// All [ImportDirective]s of the current library.
- final List<ImportDirective> _allImports = <ImportDirective>[];
+ final List<ImportDirective> _allImports = [];
/// A list of [ImportDirective]s that the current library imports, but does
/// not use.
@@ -210,23 +208,25 @@
/// this list represents the set of unused imports.
///
/// See [ImportsVerifier.generateUnusedImportErrors].
- final List<ImportDirective> _unusedImports = <ImportDirective>[];
+ final List<ImportDirective> _unusedImports = [];
/// After the list of [unusedImports] has been computed, this list is a proper
/// subset of the unused imports that are listed more than once.
- final List<ImportDirective> _duplicateImports = <ImportDirective>[];
+ final List<ImportDirective> _duplicateImports = [];
+
+ /// This list is a proper subset of the unused exports that are listed more
+ /// than once.
+ final List<ExportDirective> _duplicateExports = [];
/// The cache of [Namespace]s for [ImportDirective]s.
- final HashMap<ImportDirective, Namespace> _namespaceMap =
- HashMap<ImportDirective, Namespace>();
+ final Map<ImportDirective, Namespace> _namespaceMap = {};
/// 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.
- final HashMap<PrefixElement, List<ImportDirective>> _prefixElementMap =
- HashMap<PrefixElement, List<ImportDirective>>();
+ final Map<PrefixElement, List<ImportDirective>> _prefixElementMap = {};
/// A map of identifiers that the current library's imports show, but that the
/// library does not use.
@@ -241,22 +241,20 @@
/// shown elements.
///
/// See [ImportsVerifier.generateUnusedShownNameHints].
- final HashMap<ImportDirective, List<SimpleIdentifier>> _unusedShownNamesMap =
- HashMap<ImportDirective, List<SimpleIdentifier>>();
+ final Map<ImportDirective, List<SimpleIdentifier>> _unusedShownNamesMap = {};
/// A map of names that are hidden more than once.
- final HashMap<NamespaceDirective, List<SimpleIdentifier>>
- _duplicateHiddenNamesMap =
- HashMap<NamespaceDirective, List<SimpleIdentifier>>();
+ final Map<NamespaceDirective, List<SimpleIdentifier>>
+ _duplicateHiddenNamesMap = {};
/// A map of names that are shown more than once.
- final HashMap<NamespaceDirective, List<SimpleIdentifier>>
- _duplicateShownNamesMap =
- HashMap<NamespaceDirective, List<SimpleIdentifier>>();
+ final Map<NamespaceDirective, List<SimpleIdentifier>>
+ _duplicateShownNamesMap = {};
void addImports(CompilationUnit node) {
- final importsWithLibraries = <_ImportDirective>[];
- for (Directive directive in node.directives) {
+ final importsWithLibraries = <_NamespaceDirective>[];
+ final exportsWithLibraries = <_NamespaceDirective>[];
+ for (var directive in node.directives) {
if (directive is ImportDirective) {
var libraryElement = directive.element?.importedLibrary;
if (libraryElement == null) {
@@ -265,9 +263,9 @@
_allImports.add(directive);
_unusedImports.add(directive);
importsWithLibraries.add(
- _ImportDirective(
+ _NamespaceDirective(
node: directive,
- importedLibrary: libraryElement,
+ library: libraryElement,
),
);
//
@@ -289,56 +287,66 @@
}
}
_addShownNames(directive);
+ } else if (directive is ExportDirective) {
+ var libraryElement = directive.element?.exportedLibrary;
+ if (libraryElement == null) {
+ continue;
+ }
+ exportsWithLibraries.add(
+ _NamespaceDirective(
+ node: directive,
+ library: libraryElement,
+ ),
+ );
}
+
if (directive is NamespaceDirective) {
_addDuplicateShownHiddenNames(directive);
}
}
- if (importsWithLibraries.length > 1) {
- // order the list of unusedImports to find duplicates in faster than
- // O(n^2) time
- importsWithLibraries.sort((import1, import2) {
- return import1.libraryUriStr.compareTo(import2.libraryUriStr);
- });
- var currentDirective = importsWithLibraries[0];
- for (var i = 1; i < importsWithLibraries.length; i++) {
- final nextDirective = importsWithLibraries[i];
- if (currentDirective.libraryUriStr == nextDirective.libraryUriStr &&
- ImportDirectiveImpl.areSyntacticallyIdenticalExceptUri(
- currentDirective.node,
- nextDirective.node,
- )) {
- // 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.node.offset < nextDirective.node.offset) {
- _duplicateImports.add(nextDirective.node);
- } else {
- _duplicateImports.add(currentDirective.node);
- }
- }
- currentDirective = nextDirective;
- }
+ var importDuplicates = _duplicates(importsWithLibraries);
+ for (var duplicate in importDuplicates) {
+ _duplicateImports.add(duplicate as ImportDirective);
+ }
+ var exportDuplicates = _duplicates(exportsWithLibraries);
+ for (var duplicate in exportDuplicates) {
+ _duplicateExports.add(duplicate as ExportDirective);
+ }
+ }
+
+ /// Any time after the defining compilation unit has been visited by this
+ /// visitor, this method can be called to report an
+ /// [HintCode.DUPLICATE_EXPORT] hint for each of the export directives in the
+ /// [_duplicateExports] list.
+ ///
+ /// @param errorReporter the error reporter to report the set of
+ /// [HintCode.DUPLICATE_EXPORT] hints to
+ void generateDuplicateExportHints(ErrorReporter errorReporter) {
+ var length = _duplicateExports.length;
+ for (var i = 0; i < length; i++) {
+ errorReporter.reportErrorForNode(
+ HintCode.DUPLICATE_EXPORT, _duplicateExports[i].uri);
}
}
/// 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.
+ /// [_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++) {
+ var length = _duplicateImports.length;
+ for (var 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.
+ /// Report a [HintCode.DUPLICATE_SHOWN_NAME] and
+ /// [HintCode.DUPLICATE_HIDDEN_NAME] hints for each duplicate shown or hidden
+ /// name.
///
/// Only call this method after all of the compilation units have been visited
/// by this visitor.
@@ -526,29 +534,29 @@
/// Add duplicate shown and hidden names from [directive] into
/// [_duplicateHiddenNamesMap] and [_duplicateShownNamesMap].
void _addDuplicateShownHiddenNames(NamespaceDirective directive) {
- for (Combinator combinator in directive.combinators) {
+ for (var combinator in directive.combinators) {
// Use a Set to find duplicates in faster than O(n^2) time.
- Set<Element> identifiers = <Element>{};
+ var identifiers = <Element>{};
if (combinator is HideCombinator) {
- for (SimpleIdentifier name in combinator.hiddenNames) {
+ for (var name in combinator.hiddenNames) {
var element = name.staticElement;
if (element != null) {
if (!identifiers.add(element)) {
// [name] is a duplicate.
- List<SimpleIdentifier> duplicateNames = _duplicateHiddenNamesMap
- .putIfAbsent(directive, () => <SimpleIdentifier>[]);
+ List<SimpleIdentifier> duplicateNames =
+ _duplicateHiddenNamesMap.putIfAbsent(directive, () => []);
duplicateNames.add(name);
}
}
}
} else if (combinator is ShowCombinator) {
- for (SimpleIdentifier name in combinator.shownNames) {
+ for (var name in combinator.shownNames) {
var element = name.staticElement;
if (element != null) {
if (!identifiers.add(element)) {
// [name] is a duplicate.
- List<SimpleIdentifier> duplicateNames = _duplicateShownNamesMap
- .putIfAbsent(directive, () => <SimpleIdentifier>[]);
+ List<SimpleIdentifier> duplicateNames =
+ _duplicateShownNamesMap.putIfAbsent(directive, () => []);
duplicateNames.add(name);
}
}
@@ -572,6 +580,38 @@
}
}
+ /// Return the duplicates in [directives].
+ List<NamespaceDirective> _duplicates(List<_NamespaceDirective> directives) {
+ var duplicates = <NamespaceDirective>[];
+ if (directives.length > 1) {
+ // order the list of directives to find duplicates in faster than
+ // O(n^2) time
+ directives.sort((import1, import2) {
+ return import1.libraryUriStr.compareTo(import2.libraryUriStr);
+ });
+ var currentDirective = directives[0];
+ for (var i = 1; i < directives.length; i++) {
+ final nextDirective = directives[i];
+ if (currentDirective.libraryUriStr == nextDirective.libraryUriStr &&
+ ImportDirectiveImpl.areSyntacticallyIdenticalExceptUri(
+ currentDirective.node,
+ nextDirective.node,
+ )) {
+ // 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.node.offset < nextDirective.node.offset) {
+ duplicates.add(nextDirective.node);
+ } else {
+ duplicates.add(currentDirective.node);
+ }
+ }
+ currentDirective = nextDirective;
+ }
+ }
+ return duplicates;
+ }
+
/// Remove [element] from the list of names shown by [importDirective].
void _removeFromUnusedShownNamesMap(
Element element, ImportDirective importDirective) {
@@ -623,18 +663,18 @@
final Set<ExtensionElement> usedExtensions = {};
}
-/// [ImportDirective] with non-null imported [LibraryElement].
-class _ImportDirective {
- final ImportDirective node;
- final LibraryElement importedLibrary;
+/// [NamespaceDirective] with non-null imported or exported [LibraryElement].
+class _NamespaceDirective {
+ final NamespaceDirective node;
+ final LibraryElement library;
- _ImportDirective({
+ _NamespaceDirective({
required this.node,
- required this.importedLibrary,
+ required this.library,
});
- /// Returns the absolute URI of the imported library.
- String get libraryUriStr => '${importedLibrary.source.uri}';
+ /// Returns the absolute URI of the library.
+ String get libraryUriStr => '${library.source.uri}';
}
/// A class which verifies (and reports) whether any import directives are
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index 6d0d882..4168ce7 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -18025,6 +18025,14 @@
var x = pi;
```
+ DUPLICATE_EXPORT:
+ problemMessage: Duplicate export.
+ correctionMessage: Try removing all but one export of the library.
+ hasPublishedDocs: false
+ comment: |-
+ Duplicate exports.
+
+ No parameters.
DUPLICATE_IGNORE:
problemMessage: "The diagnostic '{0}' doesn't need to be ignored here because it's already being ignored."
correctionMessage: Try removing the name from the list, or removing the whole comment if this is the only name in the list.
diff --git a/pkg/analyzer/test/generated/non_error_resolver_test.dart b/pkg/analyzer/test/generated/non_error_resolver_test.dart
index 8503da8..65622a4 100644
--- a/pkg/analyzer/test/generated/non_error_resolver_test.dart
+++ b/pkg/analyzer/test/generated/non_error_resolver_test.dart
@@ -206,11 +206,13 @@
library lib;
class N {}
''');
- await assertNoErrorsInCode(r'''
+ await assertErrorsInCode(r'''
library L;
export 'lib.dart';
export 'lib.dart';
-''');
+''', [
+ error(HintCode.DUPLICATE_EXPORT, 37, 10),
+ ]);
}
test_ambiguousImport_dart_implicitHide() async {
diff --git a/pkg/analyzer/test/src/diagnostics/duplicate_import_test.dart b/pkg/analyzer/test/src/diagnostics/duplicate_import_test.dart
index 494f159..baa86ad 100644
--- a/pkg/analyzer/test/src/diagnostics/duplicate_import_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/duplicate_import_test.dart
@@ -10,11 +10,52 @@
main() {
defineReflectiveSuite(() {
+ defineReflectiveTests(DuplicateExportTest);
defineReflectiveTests(DuplicateImportTest);
});
}
@reflectiveTest
+class DuplicateExportTest extends PubPackageResolutionTest {
+ test_duplicateExport() async {
+ newFile('$testPackageLibPath/lib1.dart', r'''
+class A {}
+class B {}
+''');
+ await assertErrorsInCode('''
+export 'lib1.dart';
+export 'lib1.dart';
+''', [
+ error(HintCode.DUPLICATE_EXPORT, 27, 11),
+ ]);
+ }
+
+ test_duplicateExport_differentShow() async {
+ newFile('$testPackageLibPath/lib1.dart', r'''
+class A {}
+class B {}
+''');
+ await assertNoErrorsInCode('''
+export 'lib1.dart' show A;
+export 'lib1.dart' show B;
+''');
+ }
+
+ test_duplicateExport_sameShow() async {
+ newFile('$testPackageLibPath/lib1.dart', r'''
+class A {}
+class B {}
+''');
+ await assertErrorsInCode('''
+export 'lib1.dart' show A;
+export 'lib1.dart' show A;
+''', [
+ error(HintCode.DUPLICATE_EXPORT, 34, 11),
+ ]);
+ }
+}
+
+@reflectiveTest
class DuplicateImportTest extends PubPackageResolutionTest {
test_duplicateImport_absolute_absolute() async {
newFile('$testPackageLibPath/a.dart', r'''
diff --git a/pkg/analyzer/tool/diagnostics/generate.dart b/pkg/analyzer/tool/diagnostics/generate.dart
index 7299840..7c29e74 100644
--- a/pkg/analyzer/tool/diagnostics/generate.dart
+++ b/pkg/analyzer/tool/diagnostics/generate.dart
@@ -12,7 +12,7 @@
/// Generate the file `diagnostics.md` based on the documentation associated
/// with the declarations of the error codes.
-void main() async {
+Future<void> main() async {
var sink = File(computeOutputPath()).openWrite();
var generator = DocumentationGenerator();
generator.writeDocumentation(sink);
diff --git a/pkg/analyzer/tool/messages/generate.dart b/pkg/analyzer/tool/messages/generate.dart
index 77931f3..8d07ca3 100644
--- a/pkg/analyzer/tool/messages/generate.dart
+++ b/pkg/analyzer/tool/messages/generate.dart
@@ -24,7 +24,7 @@
import 'error_code_info.dart';
-void main() async {
+Future<void> main() async {
await GeneratedContent.generateAll(analyzerPkgPath, allTargets);
_SyntacticErrorGenerator()
diff --git a/pkg/analyzer/tool/messages/generate_test.dart b/pkg/analyzer/tool/messages/generate_test.dart
index ffba861..2b37eec 100644
--- a/pkg/analyzer/tool/messages/generate_test.dart
+++ b/pkg/analyzer/tool/messages/generate_test.dart
@@ -11,7 +11,7 @@
import 'error_code_info.dart';
import 'generate.dart';
-void main() async {
+Future<void> main() async {
await GeneratedContent.checkAll(analyzerPkgPath,
join(analyzerPkgPath, 'tool', 'messages', 'generate.dart'), allTargets);
}