Skip to content

Commit 7bde331

Browse files
bwilkersoncommit-bot@chromium.org
authored andcommitted
Add support for extensions to unused import detection
Change-Id: I211c769765293a35f371bd2b403b38ac5dcb3b14 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/112280 Commit-Queue: Brian Wilkerson <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]> Reviewed-by: Phil Quitslund <[email protected]>
1 parent b12e2ce commit 7bde331

File tree

3 files changed

+304
-37
lines changed

3 files changed

+304
-37
lines changed

pkg/analyzer/lib/src/error/imports_verifier.dart

Lines changed: 91 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,56 @@ class GatherUsedImportedElementsVisitor extends RecursiveAstVisitor {
1818

1919
GatherUsedImportedElementsVisitor(this.library);
2020

21+
@override
22+
void visitBinaryExpression(BinaryExpression node) {
23+
_recordIfExtensionMember(node.staticElement);
24+
return super.visitBinaryExpression(node);
25+
}
26+
2127
@override
2228
void visitExportDirective(ExportDirective node) {
2329
_visitDirective(node);
2430
}
2531

32+
@override
33+
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
34+
_recordIfExtensionMember(node.staticElement);
35+
return super.visitFunctionExpressionInvocation(node);
36+
}
37+
2638
@override
2739
void visitImportDirective(ImportDirective node) {
2840
_visitDirective(node);
2941
}
3042

43+
@override
44+
void visitIndexExpression(IndexExpression node) {
45+
_recordIfExtensionMember(node.staticElement);
46+
return super.visitIndexExpression(node);
47+
}
48+
3149
@override
3250
void visitLibraryDirective(LibraryDirective node) {
3351
_visitDirective(node);
3452
}
3553

54+
@override
55+
void visitPrefixExpression(PrefixExpression node) {
56+
_recordIfExtensionMember(node.staticElement);
57+
return super.visitPrefixExpression(node);
58+
}
59+
3660
@override
3761
void visitSimpleIdentifier(SimpleIdentifier node) {
3862
_visitIdentifier(node, node.staticElement);
3963
}
4064

65+
void _recordIfExtensionMember(Element element) {
66+
if (element != null && element.enclosingElement is ExtensionElement) {
67+
_recordUsedExtension(element.enclosingElement);
68+
}
69+
}
70+
4171
/// If the given [identifier] is prefixed with a [PrefixElement], fill the
4272
/// corresponding `UsedImportedElements.prefixMap` entry and return `true`.
4373
bool _recordPrefixMap(SimpleIdentifier identifier, Element element) {
@@ -61,6 +91,29 @@ class GatherUsedImportedElementsVisitor extends RecursiveAstVisitor {
6191
return false;
6292
}
6393

94+
void _recordUsedElement(Element element) {
95+
// Ignore if an unknown library.
96+
LibraryElement containingLibrary = element.library;
97+
if (containingLibrary == null) {
98+
return;
99+
}
100+
// Ignore if a local element.
101+
if (library == containingLibrary) {
102+
return;
103+
}
104+
// Remember the element.
105+
usedElements.elements.add(element);
106+
}
107+
108+
void _recordUsedExtension(ExtensionElement extension) {
109+
// Ignore if a local element.
110+
if (library == extension.library) {
111+
return;
112+
}
113+
// Remember the element.
114+
usedElements.usedExtensions.add(extension);
115+
}
116+
64117
/// Visit identifiers used by the given [directive].
65118
void _visitDirective(Directive directive) {
66119
directive.documentationComment?.accept(this);
@@ -71,44 +124,28 @@ class GatherUsedImportedElementsVisitor extends RecursiveAstVisitor {
71124
if (element == null) {
72125
return;
73126
}
74-
// If the element is multiply defined then call this method recursively for
75-
// each of the conflicting elements.
76-
if (element is MultiplyDefinedElement) {
127+
// Record `importPrefix.identifier` into 'prefixMap'.
128+
if (_recordPrefixMap(identifier, element)) {
129+
return;
130+
}
131+
Element enclosingElement = element.enclosingElement;
132+
if (enclosingElement is CompilationUnitElement) {
133+
_recordUsedElement(element);
134+
} else if (enclosingElement is ExtensionElement) {
135+
_recordUsedExtension(enclosingElement);
136+
return;
137+
} else if (element is PrefixElement) {
138+
usedElements.prefixMap.putIfAbsent(element, () => <Element>[]);
139+
} else if (element is MultiplyDefinedElement) {
140+
// If the element is multiply defined then call this method recursively
141+
// for each of the conflicting elements.
77142
List<Element> conflictingElements = element.conflictingElements;
78143
int length = conflictingElements.length;
79144
for (int i = 0; i < length; i++) {
80145
Element elt = conflictingElements[i];
81146
_visitIdentifier(identifier, elt);
82147
}
83-
return;
84-
}
85-
86-
// Record `importPrefix.identifier` into 'prefixMap'.
87-
if (_recordPrefixMap(identifier, element)) {
88-
return;
89-
}
90-
91-
if (element is PrefixElement) {
92-
usedElements.prefixMap.putIfAbsent(element, () => <Element>[]);
93-
return;
94-
} else if (element.enclosingElement is! CompilationUnitElement) {
95-
// Identifiers that aren't a prefix element and whose enclosing element
96-
// isn't a CompilationUnit are ignored- this covers the case the
97-
// identifier is a relative-reference, a reference to an identifier not
98-
// imported by this library.
99-
return;
100-
}
101-
// Ignore if an unknown library.
102-
LibraryElement containingLibrary = element.library;
103-
if (containingLibrary == null) {
104-
return;
105148
}
106-
// Ignore if a local element.
107-
if (library == containingLibrary) {
108-
return;
109-
}
110-
// Remember the element.
111-
usedElements.elements.add(element);
112149
}
113150
}
114151

@@ -381,6 +418,22 @@ class ImportsVerifier {
381418
}
382419
}
383420
}
421+
// Process extension elements.
422+
for (ExtensionElement extensionElement in usedElements.usedExtensions) {
423+
// Stop if all the imports and shown names are known to be used.
424+
if (_unusedImports.isEmpty && _unusedShownNamesMap.isEmpty) {
425+
return;
426+
}
427+
// Find import directives using namespaces.
428+
String name = extensionElement.name;
429+
for (ImportDirective importDirective in _allImports) {
430+
Namespace namespace = _computeNamespace(importDirective);
431+
if (namespace?.get(name) == extensionElement) {
432+
_unusedImports.remove(importDirective);
433+
_removeFromUnusedShownNamesMap(extensionElement, importDirective);
434+
}
435+
}
436+
}
384437
}
385438

386439
/// Add duplicate shown and hidden names from [directive] into
@@ -492,11 +545,12 @@ class ImportsVerifier {
492545
/// A container with information about used imports prefixes and used imported
493546
/// elements.
494547
class UsedImportedElements {
495-
/// The map of referenced [PrefixElement]s and the [Element]s that they
496-
/// prefix.
497-
final Map<PrefixElement, List<Element>> prefixMap =
498-
new HashMap<PrefixElement, List<Element>>();
548+
/// The map of referenced prefix elements and the elements that they prefix.
549+
final Map<PrefixElement, List<Element>> prefixMap = {};
550+
551+
/// The set of referenced top-level elements.
552+
final Set<Element> elements = {};
499553

500-
/// The set of referenced top-level [Element]s.
501-
final Set<Element> elements = new HashSet<Element>();
554+
/// The set of extensions defining members that are referenced.
555+
final Set<ExtensionElement> usedExtensions = {};
502556
}

pkg/analyzer/test/src/diagnostics/unused_import_test.dart

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:analyzer/dart/analysis/features.dart';
56
import 'package:analyzer/src/error/codes.dart';
7+
import 'package:analyzer/src/generated/engine.dart';
68
import 'package:test_reflective_loader/test_reflective_loader.dart';
79

810
import '../dart/resolution/driver_resolution.dart';
911

1012
main() {
1113
defineReflectiveSuite(() {
1214
defineReflectiveTests(UnusedImportTest);
15+
defineReflectiveTests(UnusedImportWithExtensionMethodsTest);
1316
});
1417
}
1518

@@ -224,3 +227,169 @@ import 'lib1.dart';
224227
]);
225228
}
226229
}
230+
231+
@reflectiveTest
232+
class UnusedImportWithExtensionMethodsTest extends UnusedImportTest {
233+
@override
234+
AnalysisOptionsImpl get analysisOptions => AnalysisOptionsImpl()
235+
..contextFeatures = new FeatureSet.forTesting(
236+
sdkVersion: '2.3.0', additionalFeatures: [Feature.extension_methods]);
237+
238+
test_instance_call() async {
239+
newFile('/test/lib/lib1.dart', content: r'''
240+
extension E on int {
241+
int call(int x) => 0;
242+
}
243+
''');
244+
await assertNoErrorsInCode('''
245+
import 'lib1.dart';
246+
247+
f() {
248+
7(9);
249+
}
250+
''');
251+
}
252+
253+
test_instance_getter() async {
254+
newFile('/test/lib/lib1.dart', content: r'''
255+
extension E on String {
256+
String get empty => '';
257+
}
258+
''');
259+
await assertNoErrorsInCode('''
260+
import 'lib1.dart';
261+
262+
f() {
263+
''.empty;
264+
}
265+
''');
266+
}
267+
268+
test_instance_method() async {
269+
newFile('/test/lib/lib1.dart', content: r'''
270+
extension E on String {
271+
String empty() => '';
272+
}
273+
''');
274+
await assertNoErrorsInCode('''
275+
import 'lib1.dart';
276+
277+
f() {
278+
''.empty();
279+
}
280+
''');
281+
}
282+
283+
test_instance_operator_binary() async {
284+
newFile('/test/lib/lib1.dart', content: r'''
285+
extension E on String {
286+
String operator -(String s) => this;
287+
}
288+
''');
289+
await assertNoErrorsInCode('''
290+
import 'lib1.dart';
291+
292+
f() {
293+
'abc' - 'c';
294+
}
295+
''');
296+
}
297+
298+
test_instance_operator_index() async {
299+
newFile('/test/lib/lib1.dart', content: r'''
300+
extension E on int {
301+
int operator [](int i) => 0;
302+
}
303+
''');
304+
await assertNoErrorsInCode('''
305+
import 'lib1.dart';
306+
307+
f() {
308+
9[7];
309+
}
310+
''');
311+
}
312+
313+
test_instance_operator_unary() async {
314+
newFile('/test/lib/lib1.dart', content: r'''
315+
extension E on String {
316+
void operator -() {}
317+
}
318+
''');
319+
await assertNoErrorsInCode('''
320+
import 'lib1.dart';
321+
322+
f() {
323+
-'abc';
324+
}
325+
''');
326+
}
327+
328+
test_instance_setter() async {
329+
newFile('/test/lib/lib1.dart', content: r'''
330+
extension E on String {
331+
void set length(int i) {}
332+
}
333+
''');
334+
await assertNoErrorsInCode('''
335+
import 'lib1.dart';
336+
337+
f() {
338+
'abc'.length = 2;
339+
}
340+
''');
341+
}
342+
343+
test_multipleExtensions() async {
344+
newFile('/test/lib/lib1.dart', content: r'''
345+
extension E on String {
346+
String a() => '';
347+
}
348+
''');
349+
newFile('/test/lib/lib2.dart', content: r'''
350+
extension E on String {
351+
String b() => '';
352+
}
353+
''');
354+
await assertErrorsInCode('''
355+
import 'lib1.dart';
356+
import 'lib2.dart';
357+
358+
f() {
359+
''.b();
360+
}
361+
''', [
362+
error(HintCode.UNUSED_IMPORT, 7, 11),
363+
]);
364+
}
365+
366+
test_override_getter() async {
367+
newFile('/test/lib/lib1.dart', content: r'''
368+
extension E on String {
369+
String get empty => '';
370+
}
371+
''');
372+
await assertNoErrorsInCode('''
373+
import 'lib1.dart';
374+
375+
f() {
376+
E('').empty;
377+
}
378+
''');
379+
}
380+
381+
test_static_field() async {
382+
newFile('/test/lib/lib1.dart', content: r'''
383+
extension E on String {
384+
static const String empty = '';
385+
}
386+
''');
387+
await assertNoErrorsInCode('''
388+
import 'lib1.dart';
389+
390+
f() {
391+
E.empty;
392+
}
393+
''');
394+
}
395+
}

0 commit comments

Comments
 (0)