Parts. Report UNNECESSARY_IMPORT using the new tracking.
Change-Id: Ide8f003bdfb1c80f1e342d3742b538edfda6ad8b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/382901
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/end_to_end_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/end_to_end_test.dart
index c75f6cd..02ecc4e 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/end_to_end_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/end_to_end_test.dart
@@ -161,7 +161,7 @@
import '$importUri';
void f(New o) {}
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void> test_replacedBy() async {
@@ -193,6 +193,6 @@
int f() {
return FileMode.read;
}
-''', errorFilter: ignoreUnusedImport);
+''');
}
}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart
index 5b32f16..3755483 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart
@@ -95,7 +95,7 @@
void f() {
CupertinoAlertDialog(content: 'x');
}
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void>
@@ -171,7 +171,7 @@
void f() {
CupertinoPopupSurface(child: 'x');
}
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void>
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_test.dart
index 0b9e999..d7cde53 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_test.dart
@@ -125,7 +125,7 @@
void f() {
New.c();
}
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void> test_constructor_unnamed_deprecated() async {
@@ -175,7 +175,7 @@
void f() {
New();
}
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void> test_constructor_unnamed_removed_prefixed() async {
@@ -234,7 +234,7 @@
import '$importUri';
class C extends New {}
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void> test_inImplements_deprecated() async {
@@ -270,7 +270,7 @@
import '$importUri';
class C implements New {}
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void> test_inOn_deprecated() async {
@@ -306,7 +306,7 @@
import '$importUri';
extension E on New {}
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void> test_inTypeAnnotation_deprecated() async {
@@ -342,7 +342,7 @@
import '$importUri';
void f(New o) {}
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void> test_inTypeArgument_deprecated() async {
@@ -430,7 +430,7 @@
import '$importUri';
class C with New {}
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void> test_staticField_deprecated() async {
@@ -472,7 +472,7 @@
import '$importUri';
var s = New.empty;
-''', errorFilter: ignoreUnusedImport);
+''');
}
}
@@ -693,7 +693,7 @@
import '$importUri';
var l = New('a').double;
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void> test_staticField_deprecated() async {
@@ -735,7 +735,7 @@
import '$importUri';
var s = New.empty;
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void> test_staticField_removed_prefixed() async {
@@ -1176,7 +1176,7 @@
void f() {
b;
}
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void> test_topLevel_reference_removed_prefixed() async {
@@ -1417,7 +1417,7 @@
import '$importUri';
class C with New {}
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void> test_inWith_removed_prefixed() async {
@@ -1515,7 +1515,7 @@
void f() {
b();
}
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void> test_removed_prefixed() async {
@@ -1661,7 +1661,7 @@
import '$importUri';
void f(New o) {}
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void> test_removed_prefixed() async {
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/replaced_by_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/replaced_by_test.dart
index 1c3f3be..9ff7300 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/replaced_by_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/replaced_by_test.dart
@@ -1068,7 +1068,7 @@
f() {
expect(true, true);
}
-''', errorFilter: ignoreUnusedImport);
+''');
}
Future<void> test_new_element_uris_single() async {
@@ -1102,7 +1102,7 @@
main() {
expect(true, true);
}
-''', errorFilter: ignoreUnusedImport);
+''');
}
}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/sdk_fix_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/sdk_fix_test.dart
index fb75558..76c6fdb 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/sdk_fix_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/sdk_fix_test.dart
@@ -49,7 +49,7 @@
import '$importUri';
void f(Bar o) {}
-''', errorFilter: ignoreUnusedImport);
+''');
}
}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_use_case_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_use_case_test.dart
index 506bab3..11e3f25 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_use_case_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_use_case_test.dart
@@ -96,6 +96,6 @@
main() {
expect(true, true);
}
-''', errorFilter: ignoreUnusedImport);
+''');
}
}
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_analysis.dart b/pkg/analyzer/lib/src/dart/analysis/file_analysis.dart
index ec94826..3f82f5f 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_analysis.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_analysis.dart
@@ -6,6 +6,7 @@
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/scope.dart';
import 'package:analyzer/src/ignore_comments/ignore_info.dart';
/// Information about a file being analyzed.
@@ -16,6 +17,7 @@
final CompilationUnitImpl unit;
final CompilationUnitElementImpl element;
final IgnoreInfo ignoreInfo;
+ late ImportsTracking importsTracking;
FileAnalysis({
required this.file,
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
index 7ffa613..83ba5e6 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
@@ -182,11 +182,18 @@
_testingData?.recordFlowAnalysisDataForTesting(
file.uri, flowAnalysisHelper.dataForTesting!);
- var resolverVisitor = ResolverVisitor(_inheritance, _libraryElement,
- libraryResolutionContext, file.source, _typeProvider, errorListener,
- featureSet: _libraryElement.featureSet,
- analysisOptions: _library.file.analysisOptions,
- flowAnalysisHelper: flowAnalysisHelper);
+ var resolverVisitor = ResolverVisitor(
+ _inheritance,
+ _libraryElement,
+ libraryResolutionContext,
+ file.source,
+ _typeProvider,
+ errorListener,
+ featureSet: _libraryElement.featureSet,
+ analysisOptions: _library.file.analysisOptions,
+ flowAnalysisHelper: flowAnalysisHelper,
+ libraryFragment: unitElement,
+ );
_testingData?.recordTypeConstraintGenerationDataForTesting(
file.uri, resolverVisitor.inferenceHelper.dataForTesting!);
@@ -497,8 +504,10 @@
LanguageVersionOverrideVerifier(errorReporter).verify(unit);
// Verify imports.
- {
- ImportsVerifier verifier = ImportsVerifier();
+ if (!_hasDiagnosticReportedThatPreventsImportWarnings()) {
+ var verifier = ImportsVerifier(
+ fileAnalysis: fileAnalysis,
+ );
verifier.addImports(unit);
usedImportedElements.forEach(verifier.removeUsedElements);
verifier.generateDuplicateExportWarnings(errorReporter);
@@ -586,6 +595,35 @@
];
}
+ bool _hasDiagnosticReportedThatPreventsImportWarnings() {
+ var errorCodes = _libraryFiles.values.map((analysis) {
+ return analysis.errorListener.errors.map((e) => e.errorCode);
+ }).flattenedToSet;
+
+ for (var errorCode in errorCodes) {
+ if (const {
+ CompileTimeErrorCode.AMBIGUOUS_IMPORT,
+ CompileTimeErrorCode.CONST_WITH_NON_TYPE,
+ CompileTimeErrorCode.EXTENDS_NON_CLASS,
+ CompileTimeErrorCode.IMPLEMENTS_NON_CLASS,
+ CompileTimeErrorCode.MIXIN_OF_NON_CLASS,
+ CompileTimeErrorCode.NEW_WITH_NON_TYPE,
+ CompileTimeErrorCode.NOT_A_TYPE,
+ CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT,
+ CompileTimeErrorCode.UNDEFINED_ANNOTATION,
+ CompileTimeErrorCode.UNDEFINED_CLASS,
+ CompileTimeErrorCode.UNDEFINED_FUNCTION,
+ CompileTimeErrorCode.UNDEFINED_IDENTIFIER,
+ CompileTimeErrorCode.UNDEFINED_PREFIXED_NAME,
+ WarningCode.DEPRECATED_EXPORT_USE,
+ }.contains(errorCode)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/// Return a new parsed unresolved [CompilationUnit].
FileAnalysis _parse({
required FileState file,
@@ -618,10 +656,24 @@
fileElement: _libraryElement.definingCompilationUnit,
);
+ // Configure scopes for all files to track imports usages.
+ // Associate tracking objects with file objects.
+ for (var fileAnalysis in _libraryFiles.values) {
+ var scope = fileAnalysis.element.scope;
+ var tracking = scope.importsTrackingInit();
+ fileAnalysis.importsTracking = tracking;
+ }
+
for (var fileAnalysis in _libraryFiles.values) {
_resolveFile(fileAnalysis);
}
+ // Stop tracking usages by scopes.
+ for (var fileAnalysis in _libraryFiles.values) {
+ var scope = fileAnalysis.element.scope;
+ scope.importsTrackingDestroy();
+ }
+
_computeConstants();
}
@@ -911,11 +963,18 @@
_testingData?.recordFlowAnalysisDataForTesting(
fileAnalysis.file.uri, flowAnalysisHelper.dataForTesting!);
- var resolver = ResolverVisitor(_inheritance, _libraryElement,
- libraryResolutionContext, source, _typeProvider, errorListener,
- analysisOptions: _library.file.analysisOptions,
- featureSet: unit.featureSet,
- flowAnalysisHelper: flowAnalysisHelper);
+ var resolver = ResolverVisitor(
+ _inheritance,
+ _libraryElement,
+ libraryResolutionContext,
+ source,
+ _typeProvider,
+ errorListener,
+ analysisOptions: _library.file.analysisOptions,
+ featureSet: unit.featureSet,
+ flowAnalysisHelper: flowAnalysisHelper,
+ libraryFragment: unitElement,
+ );
unit.accept(resolver);
_testingData?.recordTypeConstraintGenerationDataForTesting(
fileAnalysis.file.uri, resolver.inferenceHelper.dataForTesting!);
diff --git a/pkg/analyzer/lib/src/dart/element/scope.dart b/pkg/analyzer/lib/src/dart/element/scope.dart
index c9d4229..c668cc1 100644
--- a/pkg/analyzer/lib/src/dart/element/scope.dart
+++ b/pkg/analyzer/lib/src/dart/element/scope.dart
@@ -109,6 +109,141 @@
}
}
+/// Tracking information for all import in [CompilationUnitElementImpl].
+class ImportsTracking {
+ /// Tracking information for each import prefix.
+ final Map<PrefixElementImpl?, ImportsTrackingOfPrefix> map;
+
+ ImportsTracking({
+ required this.map,
+ });
+
+ /// The elements that are used from [import].
+ Set<Element> elementsOf(LibraryImportElementImpl import) {
+ return trackerOf(import).importToUsedElements[import] ?? {};
+ }
+
+ void notifyExtensionUsed(ExtensionElement element) {
+ for (var tracking in map.values) {
+ tracking.notifyExtensionUsed(element);
+ }
+ }
+
+ ImportsTrackingOfPrefix trackerOf(LibraryImportElementImpl import) {
+ var prefix = import.prefix?.element;
+ return map[prefix]!;
+ }
+}
+
+class ImportsTrackingOfPrefix {
+ final PrefixScope scope;
+ final Map<Element, List<LibraryImportElementImpl>> elementImports;
+
+ /// Key: an import.
+ /// Value: used elements imported from the import.
+ final Map<LibraryImportElementImpl, Set<Element>> importToUsedElements = {};
+
+ /// Key: an import.
+ /// Value: used elements imported from the import.
+ /// Excludes elements from deprecated exports.
+ final Map<LibraryImportElementImpl, Set<Element>> importToAccessedElements2 =
+ {};
+
+ /// Usually it is an error to use an import prefix without `.identifier`
+ /// after it, but we allow this in comment references. This makes the
+ /// corresponding group of imports "used".
+ bool hasPrefixUsedInCommentReference = false;
+
+ /// We set it temporarily to `false` while resolving combinators.
+ bool active = true;
+
+ ImportsTrackingOfPrefix({
+ required this.scope,
+ required this.elementImports,
+ });
+
+ /// The elements that are used from [import].
+ Set<Element> elementsOf(LibraryImportElementImpl import) {
+ return importToUsedElements[import] ?? {};
+ }
+
+ /// The subset of [elementsOf], excludes elements that are from deprecated
+ /// exports inside the imported library.
+ Set<Element> elementsOf2(LibraryImportElementImpl import) {
+ var result = importToAccessedElements2[import];
+ if (result != null) {
+ return result;
+ }
+
+ var accessedElements = elementsOf(import);
+
+ // SAFETY: the scope adds only imports with libraries.
+ var importedLibrary = import.importedLibrary!;
+ var elementFactory = importedLibrary.session.elementFactory;
+
+ for (var exportedReference in importedLibrary.exportedReferences) {
+ var reference = exportedReference.reference;
+ var element = elementFactory.elementOfReference(reference)!;
+
+ // Check only accessed elements.
+ if (!accessedElements.contains(element)) {
+ continue;
+ }
+
+ // We want to exclude only deprecated exports.
+ if (!importedLibrary.isFromDeprecatedExport(exportedReference)) {
+ continue;
+ }
+
+ // OK, we have to clone the set, and remove the element.
+ result ??= accessedElements.toSet();
+ result.remove(element);
+ }
+
+ result ??= accessedElements;
+ return importToAccessedElements2[import] = result;
+ }
+
+ void lookupResult(Element? element) {
+ if (!active) {
+ return;
+ }
+
+ if (element == null) {
+ return;
+ }
+
+ if (element is MultiplyDefinedElement) {
+ return;
+ }
+
+ // SAFETY: if we have `element`, it is from a local import.
+ var imports = elementImports[element]!;
+ for (var import in imports) {
+ (importToUsedElements[import] ??= {}).add(element);
+ }
+ }
+
+ void notifyExtensionUsed(ExtensionElement element) {
+ var imports = elementImports[element];
+ if (imports != null) {
+ for (var import in imports) {
+ (importToUsedElements[import] ??= {}).add(element);
+ }
+ } else {
+ // We include into `accessibleExtensions` elements from parents.
+ // So, it is possible that the element is not from this scope.
+ // In this case we notify the parent tracker.
+ var parentTracking = scope.parent?._importsTracking;
+ parentTracking?.notifyExtensionUsed(element);
+ }
+ }
+
+ void notifyPrefixUsedInCommentReference() {
+ hasPrefixUsedInCommentReference = true;
+ }
+}
+
/// The scope defined by an instance element.
class InstanceScope extends EnclosedScope {
InstanceScope(super.parent, InstanceElement element) {
@@ -177,13 +312,22 @@
/// The cached result for [accessibleExtensions].
List<ExtensionElement>? _extensions;
+ /// This field is set temporarily while resolving all files of a library.
+ /// So, we can track which elements were actually returned, and which imports
+ /// in which file (including enclosing files) provided these elements.
+ ///
+ /// When we are done, we remove the tracker, so that it does not use memory
+ /// when we are not resolving files of this library.
+ ImportsTracking? _importsTracking;
+
factory LibraryFragmentScope(CompilationUnitElementImpl fragment) {
+ var parent = fragment.enclosingElement3?.scope;
return LibraryFragmentScope._(
- parent: fragment.enclosingElement3?.scope,
+ parent: parent,
fragment: fragment,
noPrefixScope: PrefixScope(
libraryElement: fragment.library,
- parent: null,
+ parent: parent?.noPrefixScope,
libraryImports: fragment.libraryImports,
prefix: null,
),
@@ -217,6 +361,34 @@
}.toFixedList();
}
+ // TODO(scheglov): this is kludge.
+ // We should not use the fragment scope for resolving combinators.
+ // We should use the export scope of the imported library.
+ void importsTrackingActive(bool value) {
+ if (_importsTracking case var importsTracking?) {
+ for (var tracking in importsTracking.map.values) {
+ tracking.active = value;
+ }
+ }
+ }
+
+ void importsTrackingDestroy() {
+ noPrefixScope.importsTrackingDestroy();
+ for (var prefixElement in _prefixElements.values) {
+ prefixElement.scope.importsTrackingDestroy();
+ }
+ }
+
+ ImportsTracking importsTrackingInit() {
+ return _importsTracking = ImportsTracking(
+ map: {
+ null: noPrefixScope.importsTrackingInit(),
+ for (var prefixElement in _prefixElements.values)
+ prefixElement: prefixElement.scope.importsTrackingInit(),
+ },
+ );
+ }
+
@override
ScopeLookupResult lookup(String id) {
// Try declarations of the whole library.
@@ -230,10 +402,14 @@
return importResult;
}
- // No parent, no result.
+ // No result.
return ScopeLookupResultImpl(null, null);
}
+ void notifyExtensionUsed(ExtensionElement element) {
+ _importsTracking?.notifyExtensionUsed(element);
+ }
+
PrefixScope? _getParentPrefixScope(PrefixElementImpl prefix) {
var isDeferred = prefix.imports.any((import) {
return import.prefix is DeferredImportElementPrefix;
@@ -266,12 +442,7 @@
}
// Try the parent's combined import scope.
- var parentResult = parent?._lookupCombined(id);
- if (parentResult != null) {
- return parentResult;
- }
-
- return null;
+ return parent?._lookupCombined(id);
}
ScopeLookupResult? _lookupLibrary(String id) {
@@ -310,6 +481,9 @@
final LibraryElementImpl libraryElement;
final PrefixScope? parent;
+ final List<LibraryImportElementImpl> _importElements = [];
+ final Map<Element, List<LibraryImportElementImpl>> _elementImports = {};
+
final Map<String, Element> _getters = {};
final Map<String, Element> _setters = {};
Set<String>? _settersFromDeprecatedExport;
@@ -317,40 +491,53 @@
final Set<ExtensionElement> _extensions = {};
LibraryElement? _deferredLibrary;
+ ImportsTrackingOfPrefix? _importsTracking;
+
PrefixScope({
required this.libraryElement,
required this.parent,
- required List<LibraryImportElement> libraryImports,
+ required List<LibraryImportElementImpl> libraryImports,
required PrefixElement? prefix,
}) {
var elementFactory = libraryElement.session.elementFactory;
for (var import in libraryImports) {
var importedUri = import.uri;
- if (importedUri is DirectiveUriWithLibrary &&
+ if (importedUri is DirectiveUriWithLibraryImpl &&
import.prefix?.element == prefix) {
+ _importElements.add(import);
var importedLibrary = importedUri.library;
- if (importedLibrary is LibraryElementImpl) {
- var combinators = import.combinators.build();
- for (var exportedReference in importedLibrary.exportedReferences) {
- var reference = exportedReference.reference;
- if (combinators.allows(reference.name)) {
- var element = elementFactory.elementOfReference(reference)!;
- if (_shouldAdd(importedLibrary, element)) {
- _add(
- element,
- importedLibrary.isFromDeprecatedExport(exportedReference),
- );
- }
+ var combinators = import.combinators.build();
+ for (var exportedReference in importedLibrary.exportedReferences) {
+ var reference = exportedReference.reference;
+ if (combinators.allows(reference.name)) {
+ var element = elementFactory.elementOfReference(reference)!;
+ if (_shouldAdd(importedLibrary, element)) {
+ (_elementImports[element] ??= []).add(import);
+ _add(
+ element,
+ importedLibrary.isFromDeprecatedExport(exportedReference),
+ );
}
}
- if (import.prefix is DeferredImportElementPrefix) {
- _deferredLibrary ??= importedLibrary;
- }
+ }
+ if (import.prefix is DeferredImportElementPrefix) {
+ _deferredLibrary ??= importedLibrary;
}
}
}
}
+ void importsTrackingDestroy() {
+ _importsTracking = null;
+ }
+
+ ImportsTrackingOfPrefix importsTrackingInit() {
+ return _importsTracking = ImportsTrackingOfPrefix(
+ scope: this,
+ elementImports: _elementImports,
+ );
+ }
+
@override
ScopeLookupResult lookup(String id) {
var deferredLibrary = _deferredLibrary;
@@ -361,6 +548,8 @@
var getter = _getters[id];
var setter = _setters[id];
if (getter != null || setter != null) {
+ _importsTracking?.lookupResult(getter);
+ _importsTracking?.lookupResult(setter);
return PrefixScopeLookupResult(
getter,
setter,
@@ -376,6 +565,11 @@
return ScopeLookupResultImpl(null, null);
}
+ /// Usually this is an error, but we allow it in comment references.
+ void notifyPrefixUsedInCommentReference() {
+ _importsTracking?.notifyPrefixUsedInCommentReference();
+ }
+
void _add(Element element, bool isFromDeprecatedExport) {
if (element is PropertyAccessorElement && element.isSetter) {
_addTo(
diff --git a/pkg/analyzer/lib/src/dart/resolver/comment_reference_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/comment_reference_resolver.dart
index 5092d92..cdb1c50 100644
--- a/pkg/analyzer/lib/src/dart/resolver/comment_reference_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/comment_reference_resolver.dart
@@ -5,6 +5,7 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
+import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
@@ -143,6 +144,12 @@
var lookupResult = identifier.scopeLookupResult!;
var element = lookupResult.getter ?? lookupResult.setter;
+ // Usually referencing just an import prefix is an error.
+ // But we allow this in documentation comments.
+ if (element is PrefixElementImpl) {
+ element.scope.notifyPrefixUsedInCommentReference();
+ }
+
if (element == null) {
InterfaceType enclosingType;
var enclosingClass = _resolver.enclosingClass;
diff --git a/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
index bcf7695..124c1be 100644
--- a/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
@@ -96,12 +96,20 @@
}
if (extensions.length == 1) {
- return extensions[0].asResolutionResult;
+ var instantiated = extensions[0];
+ _resolver.libraryFragment.scope.notifyExtensionUsed(
+ instantiated.extension,
+ );
+ return instantiated.asResolutionResult;
}
var mostSpecific = _chooseMostSpecific(extensions);
if (mostSpecific.length == 1) {
- return mostSpecific.first.asResolutionResult;
+ var instantiated = mostSpecific.first;
+ _resolver.libraryFragment.scope.notifyExtensionUsed(
+ instantiated.extension,
+ );
+ return instantiated.asResolutionResult;
}
// The most specific extension is ambiguous.
diff --git a/pkg/analyzer/lib/src/error/imports_verifier.dart b/pkg/analyzer/lib/src/error/imports_verifier.dart
index 80d63b7..b1ce0d4f 100644
--- a/pkg/analyzer/lib/src/error/imports_verifier.dart
+++ b/pkg/analyzer/lib/src/error/imports_verifier.dart
@@ -5,10 +5,11 @@
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/listener.dart';
+import 'package:analyzer/src/dart/analysis/file_analysis.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
+import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/resolver/scope.dart';
import 'package:analyzer/src/error/codes.dart';
-import 'package:analyzer/src/summary2/combinator.dart';
/// A visitor that visits ASTs and fills [UsedImportedElements].
class GatherUsedImportedElementsVisitor extends RecursiveAstVisitor<void> {
@@ -241,6 +242,8 @@
/// this logic built up in this class could be used for such an action in the
/// future.
class ImportsVerifier {
+ final FileAnalysis fileAnalysis;
+
/// All [ImportDirective]s of the current library.
final List<ImportDirectiveImpl> _allImports = [];
@@ -296,6 +299,10 @@
final Map<NamespaceDirective, List<SimpleIdentifier>>
_duplicateShownNamesMap = {};
+ ImportsVerifier({
+ required this.fileAnalysis,
+ });
+
void addImports(CompilationUnit node) {
var importsWithLibraries = <_NamespaceDirective>[];
var exportsWithLibraries = <_NamespaceDirective>[];
@@ -418,50 +425,103 @@
});
}
- /// Report an [HintCode.UNNECESSARY_IMPORT] hint for each unnecessary import.
+ /// Report import directives that are unnecessary.
///
- /// Only call this method after unused imports have been determined by
- /// [removeUsedElements].
+ /// In a given library, every import directive has a set of "used elements",
+ /// the subset of elements provided by the import which are used in the
+ /// library. In a given library, an import directive is "unnecessary" if
+ /// there exists at least one other import directive with the same prefix
+ /// as the first import directive, and a "used elements" set which is a
+ /// proper superset of the first import directive's "used elements" set.
void generateUnnecessaryImportHints(ErrorReporter errorReporter,
List<UsedImportedElements> usedImportedElementsList) {
+ var importsTracking = fileAnalysis.importsTracking;
var usedImports = {..._allImports}..removeAll(_unusedImports);
- var verifier = _UnnecessaryImportsVerifier(usedImports);
- verifier.processUsedElements(usedImportedElementsList);
- verifier.reportImports(errorReporter);
- }
+ for (var firstDirective in usedImports) {
+ var firstElement = firstDirective.element!;
+ var tracker = importsTracking.trackerOf(firstElement);
- /// 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
- var importElement = unusedImport.element;
- if (importElement != null) {
- var libraryElement = importElement.importedLibrary;
- if (libraryElement == null ||
- libraryElement.isDartCore ||
- libraryElement.isSynthetic) {
+ for (var secondDirective in usedImports) {
+ if (secondDirective == firstDirective) {
continue;
}
+
+ var secondElement = secondDirective.element!;
+
+ // Must be the same import prefix, so the same tracker.
+ var secondTracker = importsTracking.trackerOf(secondElement);
+ if (secondTracker != tracker) {
+ continue;
+ }
+
+ var firstSet = tracker.elementsOf2(firstElement);
+ var secondSet = tracker.elementsOf2(secondElement);
+
+ // The second must provide all elements of the first.
+ if (!secondSet.containsAll(firstSet)) {
+ continue;
+ }
+
+ // The second must provide strictly more than the first.
+ if (!(secondSet.length > firstSet.length)) {
+ continue;
+ }
+
+ var firstElementUri = firstElement.uri;
+ var secondElementUri = secondElement.uri;
+ if (firstElementUri is DirectiveUriWithLibraryImpl &&
+ secondElementUri is DirectiveUriWithLibraryImpl) {
+ errorReporter.atNode(
+ firstDirective.uri,
+ HintCode.UNNECESSARY_IMPORT,
+ arguments: [
+ firstElementUri.relativeUriString,
+ secondElementUri.relativeUriString,
+ ],
+ );
+ // Now that we reported on the first, so we are done.
+ break;
+ }
}
- StringLiteral uri = unusedImport.uri;
- // We can safely assume that `uri.stringValue` is non-`null`, because the
- // only way for it to be `null` is if the import contains a string
- // interpolation, in which case the import wouldn't have resolved and
- // would not have been included in [_unusedImports].
- errorReporter.atNode(
- uri,
- WarningCode.UNUSED_IMPORT,
- arguments: [uri.stringValue!],
- );
+ }
+ }
+
+ /// Report [WarningCode.UNUSED_IMPORT] for each unused import.
+ void generateUnusedImportHints(ErrorReporter errorReporter) {
+ var importsTracking = fileAnalysis.importsTracking;
+ for (var importDirective in fileAnalysis.unit.directives) {
+ if (importDirective is ImportDirectiveImpl) {
+ var importElement = importDirective.element!;
+ var prefixElement = importElement.prefix?.element;
+ var tracking = importsTracking.map[prefixElement]!;
+
+ // Ignore the group of imports with a prefix in a comment reference.
+ if (tracking.hasPrefixUsedInCommentReference) {
+ continue;
+ }
+
+ if (importElement.uri case DirectiveUriWithLibraryImpl uri) {
+ // Ignore explicit dart:core import.
+ if (uri.library.isDartCore) {
+ continue;
+ }
+
+ // The URI target does not exist, reported this elsewhere.
+ if (uri.library.isSynthetic) {
+ continue;
+ }
+
+ var isUsed = tracking.importToUsedElements.containsKey(importElement);
+ if (!isUsed) {
+ errorReporter.atNode(
+ importDirective.uri,
+ WarningCode.UNUSED_IMPORT,
+ arguments: [uri.relativeUriString],
+ );
+ }
+ }
+ }
}
}
@@ -729,97 +789,6 @@
String get libraryUriStr => '${library.source.uri}';
}
-/// A class which verifies (and reports) whether any import directives are
-/// unnecessary.
-///
-/// In a given library, every import directive has a set of "used elements," the
-/// subset of elements provided by the import which are used in the library. In
-/// a given library, an import directive is "unnecessary" if there exists at
-/// least one other import directive with the same prefix as the aforementioned
-/// import directive, and a "used elements" set which is a proper superset of
-/// the aforementioned import directive's "used elements" set.
-class _UnnecessaryImportsVerifier {
- /// The set of imports which provide at least one element used in the library.
- final Set<ImportDirectiveImpl> _usedImports;
-
- /// The mapping of each import to its "used elements" set.
- ///
- /// This is computed in [processUsedElements].
- final Map<ImportDirective, Set<Element>> _usedElementSets = {};
-
- _UnnecessaryImportsVerifier(this._usedImports);
-
- /// Determines the "used elements" set for each import directive in
- /// [_usedImports].
- void processUsedElements(
- List<UsedImportedElements> usedImportedElementsList,
- ) {
- assert(_usedElementSets.isEmpty);
-
- var allUsedElements = <Element>{};
- for (var usedElements in usedImportedElementsList) {
- allUsedElements.addAll(usedElements.elements);
- allUsedElements.addAll(usedElements.usedExtensions);
- for (var elements in usedElements.prefixMap.values) {
- allUsedElements.addAll(elements);
- }
- }
-
- for (var importDirective in _usedImports) {
- var importElement = importDirective.element;
- if (importElement == null) continue;
-
- var importedLibrary = importElement.importedLibrary;
- if (importedLibrary == null) continue;
-
- var combinators = importElement.combinators.build();
- for (var exportedReference in importedLibrary.exportedReferences) {
- var reference = exportedReference.reference;
- var element = reference.element;
- if (combinators.allows(reference.name) && element != null) {
- if (allUsedElements.contains(element)) {
- if (!importedLibrary.isFromDeprecatedExport(exportedReference)) {
- (_usedElementSets[importDirective] ??= {}).add(element);
- }
- }
- }
- }
- }
- }
-
- /// Reports the import directives which are unnecessary.
- void reportImports(ErrorReporter errorReporter) {
- for (var importDirective in _usedImports) {
- if (!_usedElementSets.containsKey(importDirective)) continue;
- for (var otherImport in _usedImports) {
- if (otherImport == importDirective) continue;
- if (importDirective.prefix?.name != otherImport.prefix?.name) continue;
- if (!_usedElementSets.containsKey(otherImport)) continue;
- var importElementSet = _usedElementSets[importDirective]!;
- var otherElementSet = _usedElementSets[otherImport]!;
- if (otherElementSet.containsAll(importElementSet)) {
- if (otherElementSet.length > importElementSet.length) {
- StringLiteral uri = importDirective.uri;
- // The only way an import URI's `stringValue` can be `null` is if
- // the string contained interpolations, in which case the import
- // would have failed to resolve, and we would never reach here. So
- // it is safe to assume that `uri.stringValue` and
- // `otherImport.uri.stringValue` are both non-`null`.
- errorReporter.atNode(
- uri,
- HintCode.UNNECESSARY_IMPORT,
- arguments: [uri.stringValue!, otherImport.uri.stringValue!],
- );
- // Break out of the loop of "other imports" to prevent reporting
- // UNNECESSARY_IMPORT on [importDirective] multiple times.
- break;
- }
- }
- }
- }
- }
-}
-
extension on Map<ImportDirective, Namespace> {
/// Lookup and return the [Namespace] in this Map.
///
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 581e9dd..b67dc19 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -86,6 +86,7 @@
import 'package:analyzer/src/generated/variable_type_provider.dart';
import 'package:analyzer/src/task/inference_error.dart';
import 'package:analyzer/src/util/ast_data_extractor.dart';
+import 'package:analyzer/src/utilities/extensions/object.dart';
/// Function determining which source files should have inference logging
/// enabled.
@@ -134,6 +135,9 @@
/// The element for the library containing the compilation unit being visited.
final LibraryElementImpl definingLibrary;
+ /// The library fragment being visited.
+ final CompilationUnitElementImpl libraryFragment;
+
/// The context shared between different units of the same library.
final LibraryResolutionContext libraryResolutionContext;
@@ -304,39 +308,43 @@
// TODO(paulberry): make [featureSet] a required parameter (this will be a
// breaking change).
ResolverVisitor(
- InheritanceManager3 inheritanceManager,
- LibraryElementImpl definingLibrary,
- LibraryResolutionContext libraryResolutionContext,
- Source source,
- TypeProvider typeProvider,
- AnalysisErrorListener errorListener,
- {required FeatureSet featureSet,
- required AnalysisOptionsImpl analysisOptions,
- required FlowAnalysisHelper flowAnalysisHelper})
- : this._(
- inheritanceManager,
- definingLibrary,
- libraryResolutionContext,
- source,
- definingLibrary.typeSystem,
- typeProvider as TypeProviderImpl,
- errorListener,
- featureSet,
- analysisOptions,
- flowAnalysisHelper);
+ InheritanceManager3 inheritanceManager,
+ LibraryElementImpl definingLibrary,
+ LibraryResolutionContext libraryResolutionContext,
+ Source source,
+ TypeProvider typeProvider,
+ AnalysisErrorListener errorListener, {
+ required CompilationUnitElementImpl libraryFragment,
+ required FeatureSet featureSet,
+ required AnalysisOptionsImpl analysisOptions,
+ required FlowAnalysisHelper flowAnalysisHelper,
+ }) : this._(
+ inheritanceManager,
+ definingLibrary,
+ libraryResolutionContext,
+ source,
+ definingLibrary.typeSystem,
+ typeProvider as TypeProviderImpl,
+ errorListener,
+ featureSet,
+ analysisOptions,
+ flowAnalysisHelper,
+ libraryFragment: libraryFragment,
+ );
ResolverVisitor._(
- this.inheritance,
- this.definingLibrary,
- this.libraryResolutionContext,
- this.source,
- this.typeSystem,
- this.typeProvider,
- AnalysisErrorListener errorListener,
- FeatureSet featureSet,
- this.analysisOptions,
- this.flowAnalysis)
- : errorReporter = ErrorReporter(errorListener, source),
+ this.inheritance,
+ this.definingLibrary,
+ this.libraryResolutionContext,
+ this.source,
+ this.typeSystem,
+ this.typeProvider,
+ AnalysisErrorListener errorListener,
+ FeatureSet featureSet,
+ this.analysisOptions,
+ this.flowAnalysis, {
+ required this.libraryFragment,
+ }) : errorReporter = ErrorReporter(errorListener, source),
_featureSet = featureSet,
genericMetadataIsEnabled =
definingLibrary.featureSet.isEnabled(Feature.generic_metadata),
@@ -4984,6 +4992,17 @@
}
@override
+ void visitHideCombinator(HideCombinator node) {
+ var scope = nameScope.ifTypeOrNull<LibraryFragmentScope>();
+ scope?.importsTrackingActive(false);
+ try {
+ super.visitHideCombinator(node);
+ } finally {
+ scope?.importsTrackingActive(true);
+ }
+ }
+
+ @override
void visitIfElement(covariant IfElementImpl node) {
_visitIf(node);
}
@@ -5010,6 +5029,9 @@
}
@override
+ void visitLibraryIdentifier(LibraryIdentifier node) {}
+
+ @override
void visitMethodDeclaration(covariant MethodDeclarationImpl node) {
node.body.localVariableInfo = _localVariableInfo;
node.metadata.accept(this);
@@ -5102,6 +5124,17 @@
}
@override
+ void visitShowCombinator(ShowCombinator node) {
+ var scope = nameScope.ifTypeOrNull<LibraryFragmentScope>();
+ scope?.importsTrackingActive(false);
+ try {
+ super.visitShowCombinator(node);
+ } finally {
+ scope?.importsTrackingActive(true);
+ }
+ }
+
+ @override
void visitSimpleIdentifier(covariant SimpleIdentifierImpl node) {
// Ignore if already resolved - declaration or type.
if (node.inDeclarationContext()) {
@@ -5109,6 +5142,12 @@
}
// Ignore if qualified.
var parent = node.parent;
+ if (parent is ConstructorName && parent.name == node) {
+ return;
+ }
+ if (parent is Label && parent.parent is NamedExpression) {
+ return;
+ }
var scopeLookupResult = nameScope.lookup(node.name);
node.scopeLookupResult = scopeLookupResult;
// Ignore if it cannot be a reference to a local variable.
@@ -5120,9 +5159,6 @@
parent.fieldName == node) {
return;
}
- if (parent is ConstructorName) {
- return;
- }
if (parent is Label) {
return;
}
diff --git a/pkg/analyzer/lib/src/summary2/ast_resolver.dart b/pkg/analyzer/lib/src/summary2/ast_resolver.dart
index cc8dbbf..aab408f 100644
--- a/pkg/analyzer/lib/src/summary2/ast_resolver.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_resolver.dart
@@ -58,6 +58,7 @@
featureSet: _featureSet,
analysisOptions: analysisOptions,
flowAnalysisHelper: _flowAnalysis,
+ libraryFragment: _unitElement,
);
AstResolver(
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
index c4089ba..a4af3fa 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
@@ -2237,7 +2237,6 @@
flags: exists isLibrary
errors
25 +1 UNDEFINED_CLASS
- 7 +8 UNUSED_IMPORT
[stream]
ResolvedUnitResult #1
[status] idle
diff --git a/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart b/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
index 175ba7b..b2cc427 100644
--- a/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
@@ -2112,7 +2112,6 @@
math?.loadLibrary();
}
''', [
- error(WarningCode.UNUSED_IMPORT, 7, 11),
error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 49, 4),
]);
diff --git a/pkg/analyzer/test/src/diagnostics/deprecated_export_use_test.dart b/pkg/analyzer/test/src/diagnostics/deprecated_export_use_test.dart
index e7e84cd..b45f790 100644
--- a/pkg/analyzer/test/src/diagnostics/deprecated_export_use_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/deprecated_export_use_test.dart
@@ -336,17 +336,19 @@
''');
newFile('$testPackageLibPath/b.dart', r'''
-library b;
+library;
@deprecated
export 'a.dart';
+
+class B {}
''');
await assertNoErrorsInCode('''
import 'a.dart';
import 'b.dart';
-void f(A a) {}
+void f(A a, B b) {}
''');
}
diff --git a/pkg/analyzer/test/src/diagnostics/prefix_collides_with_top_level_member_test.dart b/pkg/analyzer/test/src/diagnostics/prefix_collides_with_top_level_member_test.dart
index 20b55fd..f8c9324 100644
--- a/pkg/analyzer/test/src/diagnostics/prefix_collides_with_top_level_member_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/prefix_collides_with_top_level_member_test.dart
@@ -25,7 +25,6 @@
typedef p();
p.A a = p.A();
''', [
- error(WarningCode.UNUSED_IMPORT, 7, 10),
error(CompileTimeErrorCode.PREFIX_COLLIDES_WITH_TOP_LEVEL_MEMBER, 32, 1,
contextMessages: [message(testFile, 21, 1)]),
error(CompileTimeErrorCode.NOT_A_TYPE, 37, 3),
@@ -92,7 +91,6 @@
class p {}
p.A a = p.A();
''', [
- error(WarningCode.UNUSED_IMPORT, 7, 10),
error(CompileTimeErrorCode.PREFIX_COLLIDES_WITH_TOP_LEVEL_MEMBER, 30, 1,
contextMessages: [message(testFile, 21, 1)]),
error(CompileTimeErrorCode.NOT_A_TYPE, 35, 3),
diff --git a/pkg/analyzer/test/src/diagnostics/prefix_identifier_not_followed_by_dot_test.dart b/pkg/analyzer/test/src/diagnostics/prefix_identifier_not_followed_by_dot_test.dart
index 40bcb1b..68813dd 100644
--- a/pkg/analyzer/test/src/diagnostics/prefix_identifier_not_followed_by_dot_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/prefix_identifier_not_followed_by_dot_test.dart
@@ -132,7 +132,6 @@
p?.loadLibrary();
}
''', [
- error(WarningCode.UNUSED_IMPORT, 7, 10),
error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 41, 1),
]);
}
diff --git a/pkg/analyzer/test/src/diagnostics/unnecessary_import_test.dart b/pkg/analyzer/test/src/diagnostics/unnecessary_import_test.dart
index 427aa8d..8d771aa 100644
--- a/pkg/analyzer/test/src/diagnostics/unnecessary_import_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/unnecessary_import_test.dart
@@ -171,25 +171,6 @@
]);
}
- test_class_deprecatedExport() async {
- newFile('$testPackageLibPath/a.dart', '''
-class A {}
-''');
-
- newFile('$testPackageLibPath/b.dart', '''
-library;
-@deprecated
-export 'a.dart';
-class B {}
-''');
-
- await assertNoErrorsInCode('''
-import 'a.dart';
-import 'b.dart';
-void f(A a, B b) {}
-''');
- }
-
test_duplicateImport_differentPrefix() async {
newFile('$testPackageLibPath/lib1.dart', '''
class A {}
@@ -271,6 +252,91 @@
]);
}
+ test_hasDeprecatedExport_hasNotDeprecatedImport_hasOtherClass() async {
+ newFile('$testPackageLibPath/a.dart', r'''
+class A {}
+''');
+
+ newFile('$testPackageLibPath/b.dart', r'''
+library;
+
+@deprecated
+export 'a.dart';
+
+class B {}
+''');
+
+ // `import b` is not reported because provides used `B`.
+ // `A` is from both `a.dart` and `b.dart`, so not reported.
+ await assertNoErrorsInCode('''
+import 'a.dart';
+import 'b.dart';
+
+void f(A _, B _) {}
+''');
+ }
+
+ test_hasDeprecatedExport_hasNotDeprecatedImport_noOtherClass() async {
+ newFile('$testPackageLibPath/a.dart', r'''
+class A {}
+''');
+
+ newFile('$testPackageLibPath/b.dart', r'''
+class B {}
+''');
+
+ newFile('$testPackageLibPath/c.dart', r'''
+library;
+
+@deprecated
+export 'b.dart';
+
+class C {}
+''');
+
+ // `import c` is unnecessary because we use only `B` from it.
+ // But the export of `B` from `c.dart` is deprecated.
+ // We can get `B` from `import b`, in a not deprecated way.
+ // It also declares `C`, but we don't use it.
+ await assertErrorsInCode('''
+import 'a.dart';
+import 'b.dart';
+import 'c.dart';
+
+void f(A _, B _) {}
+''', [
+ error(HintCode.UNNECESSARY_IMPORT, 41, 8),
+ ]);
+ }
+
+ test_hasDeprecatedExport_noNotDeprecatedImport() async {
+ newFile('$testPackageLibPath/a.dart', r'''
+class A {}
+''');
+
+ newFile('$testPackageLibPath/b.dart', r'''
+class B {}
+''');
+
+ newFile('$testPackageLibPath/c.dart', r'''
+library;
+
+@deprecated
+export 'b.dart';
+''');
+
+ // `import c` is not marked as unnecessary because of there is
+ // `DEPRECATED_EXPORT_USE` already reported.
+ await assertErrorsInCode('''
+import 'a.dart';
+import 'c.dart';
+
+void f(A _, B _) {}
+''', [
+ error(WarningCode.DEPRECATED_EXPORT_USE, 47, 1),
+ ]);
+ }
+
test_hide() async {
newFile('$testPackageLibPath/lib1.dart', '''
class A {}
@@ -297,6 +363,25 @@
''');
}
+ test_unnecessary_hasError() async {
+ newFile('$testPackageLibPath/a.dart', '''
+class A {}
+''');
+
+ newFile('$testPackageLibPath/b.dart', '''
+export 'a.dart';
+class B {}
+''');
+
+ await assertErrorsInCode('''
+import 'a.dart';
+import 'b.dart';
+void f(A _, B _, C _) {}
+''', [
+ error(CompileTimeErrorCode.UNDEFINED_CLASS, 51, 1),
+ ]);
+ }
+
test_unnecessaryImport() async {
newFile('$testPackageLibPath/lib1.dart', '''
class A {}
diff --git a/pkg/analyzer/test/src/diagnostics/unused_import_test.dart b/pkg/analyzer/test/src/diagnostics/unused_import_test.dart
index 9e45b31..dc1e091 100644
--- a/pkg/analyzer/test/src/diagnostics/unused_import_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/unused_import_test.dart
@@ -315,6 +315,30 @@
''');
}
+ test_extension_instance_method_inPart() async {
+ newFile('$testPackageLibPath/a.dart', r'''
+extension E on int {
+ void foo() {}
+}
+''');
+
+ var b = newFile('$testPackageLibPath/b.dart', r'''
+import 'a.dart';
+part 'c.dart';
+''');
+
+ var c = newFile('$testPackageLibPath/c.dart', r'''
+part of 'b.dart';
+
+void f() {
+ 0.foo();
+}
+''');
+
+ await assertErrorsInFile2(b, []);
+ await assertErrorsInFile2(c, []);
+ }
+
test_extension_instance_operator_binary() async {
newFile('$testPackageLibPath/lib1.dart', r'''
extension E on String {
@@ -475,6 +499,52 @@
]);
}
+ test_noPrefix_constructorName_name() async {
+ await assertErrorsInCode(r'''
+import 'dart:async';
+
+class A {
+ A.foo();
+}
+
+void f() {
+ A.foo();
+}
+''', [
+ error(WarningCode.UNUSED_IMPORT, 7, 12),
+ ]);
+ }
+
+ test_noPrefix_named_argument() async {
+ await assertErrorsInCode(r'''
+import 'dart:math';
+
+void f() {
+ Duration(seconds: 0);
+}
+''', [
+ error(WarningCode.UNUSED_IMPORT, 7, 11),
+ ]);
+ }
+
+ test_prefixed_commentReference_prefix() async {
+ await assertNoErrorsInCode(r'''
+import 'dart:math' as math;
+
+/// [math]
+void f() {}
+''');
+ }
+
+ test_prefixed_commentReference_prefixClass() async {
+ await assertNoErrorsInCode(r'''
+import 'dart:math' as math;
+
+/// [math.Random]
+void f() {}
+''');
+ }
+
test_show() async {
newFile('$testPackageLibPath/lib1.dart', r'''
class A {}
diff --git a/pkg/linter/test/rules/use_key_in_widget_constructors_test.dart b/pkg/linter/test/rules/use_key_in_widget_constructors_test.dart
index cd6446b..44ee669 100644
--- a/pkg/linter/test/rules/use_key_in_widget_constructors_test.dart
+++ b/pkg/linter/test/rules/use_key_in_widget_constructors_test.dart
@@ -36,8 +36,6 @@
await assertNoDiagnostics(r'''
part of 'a.dart';
-import 'package:flutter/widgets.dart';
-
augment class W {
augment const W();
}
@@ -96,13 +94,11 @@
await assertDiagnostics(r'''
part of 'a.dart';
-import 'package:flutter/widgets.dart';
-
augment class W {
const W();
}
''', [
- lint(85, 1),
+ lint(45, 1),
]);
}