Skip to content
This repository was archived by the owner on Jul 16, 2023. It is now read-only.

Commit f395fd5

Browse files
authored
fix: correctly support conditional imports for check-unused-code (#1097)
* fix: correctly support conditional imports for check-unused-code * chore: ignore deprecation message
1 parent 9b1efee commit f395fd5

File tree

8 files changed

+105
-22
lines changed

8 files changed

+105
-22
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* feat: add static code diagnostic [`prefer-using-list-view`](https://dartcodemetrics.dev/docs/rules/flutter/prefer-using-list-view).
88
* feat: add static code diagnostic [`avoid-unnecessary-conditionals`](https://dartcodemetrics.dev/docs/rules/common/avoid-unnecessary-conditionals).
99
* feat: support boolean literals removal for [`prefer-conditional-expressions`](https://dartcodemetrics.dev/docs/rules/common/prefer-conditional-expressions) auto-fix.
10+
* fix: correctly support conditional imports for [`check-unused-code`](https://dartcodemetrics.dev/docs/cli/check-unused-code).
1011

1112
## 5.1.0
1213

lib/src/analyzers/unused_code_analyzer/models/file_elements_usage.dart

+3
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@ class FileElementsUsage {
1616

1717
final Set<String> exports = {};
1818

19+
final Map<Set<String>, Set<Element>> conditionalElements = {};
20+
1921
void merge(FileElementsUsage other) {
2022
prefixMap.addAll(other.prefixMap);
2123
elements.addAll(other.elements);
2224
usedExtensions.addAll(other.usedExtensions);
2325
exports.addAll(other.exports);
26+
conditionalElements.addAll(other.conditionalElements);
2427
}
2528
}

lib/src/analyzers/unused_code_analyzer/unused_code_analyzer.dart

+17-17
Original file line numberDiff line numberDiff line change
@@ -210,25 +210,25 @@ class UnusedCodeAnalyzer {
210210
.contains(declaredSource.fullName);
211211
}
212212

213-
bool _isUnused(
214-
FileElementsUsage codeUsages,
215-
String path,
216-
Element element,
217-
) =>
218-
!codeUsages.elements.any(
219-
(usedElement) => _isUsed(usedElement, element),
220-
) &&
221-
!codeUsages.usedExtensions.any(
222-
(usedElement) => _isUsed(usedElement, element),
223-
) &&
224-
!codeUsages.prefixMap.values.any(
225-
(usage) =>
226-
usage.paths.contains(path) &&
227-
usage.elements.any((usedElement) =>
213+
bool _isUnused(FileElementsUsage codeUsages, String path, Element element) =>
214+
!codeUsages.conditionalElements.entries.any((entry) =>
215+
entry.key.contains(path) &&
216+
entry.value.any((usedElement) =>
217+
_isUsed(usedElement, element) ||
218+
(usedElement.name == element.name &&
219+
usedElement.kind == element.kind))) &&
220+
!codeUsages.elements
221+
.any((usedElement) => _isUsed(usedElement, element)) &&
222+
!codeUsages.usedExtensions
223+
.any((usedElement) => _isUsed(usedElement, element)) &&
224+
!codeUsages.prefixMap.values.any((usage) =>
225+
usage.paths.contains(path) &&
226+
usage.elements.any(
227+
(usedElement) =>
228228
_isUsed(usedElement, element) ||
229229
(usedElement.name == element.name &&
230-
usedElement.kind == element.kind)),
231-
);
230+
usedElement.kind == element.kind),
231+
));
232232

233233
UnusedCodeIssue _createUnusedCodeIssue(
234234
ElementImpl element,

lib/src/analyzers/unused_code_analyzer/used_code_visitor.dart

+50-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import 'package:analyzer/dart/ast/ast.dart';
44
import 'package:analyzer/dart/ast/visitor.dart';
55
import 'package:analyzer/dart/element/element.dart';
6+
import 'package:collection/collection.dart';
67

78
import '../../utils/flutter_types_utils.dart';
89
import 'models/file_elements_usage.dart';
@@ -13,6 +14,31 @@ import 'models/prefix_element_usage.dart';
1314
class UsedCodeVisitor extends RecursiveAstVisitor<void> {
1415
final fileElementsUsage = FileElementsUsage();
1516

17+
UsedCodeVisitor();
18+
19+
@override
20+
void visitImportDirective(ImportDirective node) {
21+
if (node.configurations.isNotEmpty) {
22+
final paths = node.configurations.map((config) {
23+
final uri = config.resolvedUri;
24+
25+
return (uri is DirectiveUriWithSource) ? uri.source.fullName : null;
26+
}).whereNotNull();
27+
// ignore: deprecated_member_use
28+
final mainImport = node.element2?.importedLibrary?.source.fullName;
29+
30+
final allPaths = {if (mainImport != null) mainImport, ...paths};
31+
32+
fileElementsUsage.conditionalElements.update(
33+
allPaths,
34+
(conditionalElements) => conditionalElements,
35+
ifAbsent: () => {},
36+
);
37+
}
38+
39+
super.visitImportDirective(node);
40+
}
41+
1642
@override
1743
void visitExportDirective(ExportDirective node) {
1844
super.visitExportDirective(node);
@@ -130,6 +156,24 @@ class UsedCodeVisitor extends RecursiveAstVisitor<void> {
130156
return false;
131157
}
132158

159+
bool _recordConditionalElement(Element element) {
160+
final elementPath = element.source?.fullName;
161+
if (elementPath == null) {
162+
return false;
163+
}
164+
165+
final entries = fileElementsUsage.conditionalElements.entries;
166+
for (final conditionalElement in entries) {
167+
if (conditionalElement.key.contains(elementPath)) {
168+
conditionalElement.value.add(element);
169+
170+
return true;
171+
}
172+
}
173+
174+
return false;
175+
}
176+
133177
/// Records use of a not prefixed [element].
134178
void _recordUsedElement(Element element) {
135179
// Ignore if an unknown library.
@@ -157,11 +201,16 @@ class UsedCodeVisitor extends RecursiveAstVisitor<void> {
157201
return;
158202
}
159203

160-
// Usage in State<WidgetClassName> is not a sing of usage.
204+
// Usage in State<WidgetClassName> is not a sign of usage.
161205
if (_isUsedAsNamedTypeForWidgetState(identifier)) {
162206
return;
163207
}
164208

209+
// Record elements that are imported with conditional imports
210+
if (_recordConditionalElement(element)) {
211+
return;
212+
}
213+
165214
// Record `importPrefix.identifier` into 'prefixMap'.
166215
if (_recordPrefixMap(identifier, element)) {
167216
return;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
int calculate() => 1;
2+
3+
bool helloWorld() => false; // LINT
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int calculate() => 2;

test/resources/unused_code_analyzer/unused_code_example.dart

+7-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import 'public_members.dart';
44
import 'unconditional_file.dart'
55
if (dart.library.html) 'conditional_file.dart'
6-
if (dart.library.io) 'conditional_file.dart' as config;
6+
if (dart.library.io) 'conditional_file.dart';
7+
import 'unconditional_prefixed_file.dart'
8+
if (dart.library.html) 'conditional_prefixed_file.dart'
9+
if (dart.library.io) 'conditional_prefixed_file.dart' as config;
710

811
void main() {
912
final widget = MyWidget('hello');
@@ -23,7 +26,9 @@ void main() {
2326

2427
SomeEnum.hello;
2528

26-
config.calculateResults();
29+
calculateResults();
30+
31+
config.calculate();
2732

2833
setPadding();
2934
}

test/src/analyzers/unused_code_analyzer/unused_code_analyzer_test.dart

+23-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ void main() {
3535
);
3636
});
3737

38-
test('should report 4 files and not report excluded file', () {
39-
expect(result, hasLength(4));
38+
test('should report 5 files and not report excluded file', () {
39+
expect(result, hasLength(5));
4040
});
4141

4242
test('should analyze not used files', () async {
@@ -84,6 +84,27 @@ void main() {
8484
expect(firstIssue.location.column, 1);
8585
});
8686

87+
test('should analyze conditional prefixed import files', () async {
88+
final unconditionalReport = result.firstWhereOrNull(
89+
(report) =>
90+
report.path.endsWith('unconditional_prefixed_file.dart'),
91+
);
92+
93+
expect(unconditionalReport, null);
94+
95+
final report = result.firstWhere(
96+
(report) => report.path.endsWith('conditional_prefixed_file.dart'),
97+
);
98+
99+
expect(report.issues, hasLength(1));
100+
101+
final firstIssue = report.issues.first;
102+
expect(firstIssue.declarationName, 'helloWorld');
103+
expect(firstIssue.declarationType, 'function');
104+
expect(firstIssue.location.line, 3);
105+
expect(firstIssue.location.column, 1);
106+
});
107+
87108
test('should analyze files', () async {
88109
final report = result.firstWhere(
89110
(report) => report.path.endsWith('public_members.dart'),

0 commit comments

Comments
 (0)