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

Commit c9d0eb2

Browse files
authored
fix: support excludes and conditional imports for check-unused-code (#654)
* fix: support excludes and conditional imports for check-unused-code * chore: update changelog * fix: track usage even for excluded files
1 parent 12e34a6 commit c9d0eb2

File tree

13 files changed

+242
-146
lines changed

13 files changed

+242
-146
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
* fix: support excludes and conditional imports for `check-unused-code` command.
6+
37
## 4.10.0-dev.1
48

59
* feat: add check unused code command.

analysis_options.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ analyzer:
55
- test/resources/*
66
- test/resources/unused_files_analyzer/**
77
- test/resources/unused_l10n_analyzer/**
8-
- test/resources/unused_code_analyzer/**
98
- test/**/examples/**
109
language:
1110
strict-inference: true

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import 'package:analyzer/dart/element/element.dart';
22

3+
import 'prefix_element_usage.dart';
4+
35
/// A container with information about used imports prefixes and used imported
46
/// elements.
57
class FileElementsUsage {
68
/// The map of referenced prefix elements and the elements that they prefix.
7-
final Map<PrefixElement, List<Element>> prefixMap = {};
9+
final Map<PrefixElement, PrefixElementUsage> prefixMap = {};
810

911
/// The set of referenced top-level elements.
1012
final Set<Element> elements = {};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'package:analyzer/dart/element/element.dart';
2+
3+
/// A container with information about used imports prefixes.
4+
class PrefixElementUsage {
5+
/// The paths to imported files.
6+
/// Used for conditional imports to track all conditional paths.
7+
final Iterable<String> paths;
8+
9+
/// The set of referenced elements.
10+
final Set<Element> elements;
11+
12+
const PrefixElementUsage(this.paths, this.elements);
13+
14+
void add(Element element) {
15+
elements.add(element);
16+
}
17+
}

lib/src/analyzers/unused_code_analyzer/unused_code_analyzer.dart

Lines changed: 47 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:analyzer/dart/analysis/utilities.dart';
66
import 'package:analyzer/dart/element/element.dart';
77
// ignore: implementation_imports
88
import 'package:analyzer/src/dart/element/element.dart';
9+
import 'package:glob/glob.dart';
910
import 'package:path/path.dart';
1011
import 'package:source_span/source_span.dart';
1112

@@ -57,28 +58,34 @@ class UnusedCodeAnalyzer {
5758
final unusedCodeAnalysisConfig =
5859
await _getAnalysisConfig(context, rootFolder, config);
5960

60-
final filePaths =
61-
_getFilePaths(folders, context, rootFolder, unusedCodeAnalysisConfig);
61+
final excludes = unusedCodeAnalysisConfig.globalExcludes
62+
.followedBy(unusedCodeAnalysisConfig.analyzerExcludedPatterns);
63+
final filePaths = _getFilePaths(folders, context, rootFolder, excludes);
6264

6365
final analyzedFiles =
6466
filePaths.intersection(context.contextRoot.analyzedFiles().toSet());
65-
await _analyseFiles(
66-
analyzedFiles,
67-
context.currentSession.getResolvedUnit,
68-
codeUsages,
69-
publicCode,
70-
);
67+
for (final filePath in analyzedFiles) {
68+
final unit = await context.currentSession.getResolvedUnit(filePath);
69+
70+
final codeUsage = _analyzeFileCodeUsages(unit);
71+
if (codeUsage != null) {
72+
codeUsages.merge(codeUsage);
73+
}
74+
75+
publicCode[filePath] = _analyzeFilePublicCode(unit);
76+
}
7177

7278
final notAnalyzedFiles = filePaths.difference(analyzedFiles);
73-
await _analyseFiles(
74-
notAnalyzedFiles,
75-
(filePath) => resolveFile2(path: filePath),
76-
codeUsages,
77-
publicCode,
78-
shouldAnalyse: (filePath) => unusedCodeAnalysisConfig
79-
.analyzerExcludedPatterns
80-
.any((pattern) => pattern.matches(filePath)),
81-
);
79+
for (final filePath in notAnalyzedFiles) {
80+
if (excludes.any((pattern) => pattern.matches(filePath))) {
81+
final unit = await resolveFile2(path: filePath);
82+
83+
final codeUsage = _analyzeFileCodeUsages(unit);
84+
if (codeUsage != null) {
85+
codeUsages.merge(codeUsage);
86+
}
87+
}
88+
}
8289
}
8390

8491
codeUsages.exports.forEach(publicCode.remove);
@@ -105,18 +112,14 @@ class UnusedCodeAnalyzer {
105112
Iterable<String> folders,
106113
AnalysisContext context,
107114
String rootFolder,
108-
UnusedCodeAnalysisConfig unusedCodeAnalysisConfig,
115+
Iterable<Glob> excludes,
109116
) {
110117
final contextFolders = folders
111118
.where((path) => normalize(join(rootFolder, path))
112119
.startsWith(context.contextRoot.root.path))
113120
.toList();
114121

115-
return extractDartFilesFromFolders(
116-
contextFolders,
117-
rootFolder,
118-
unusedCodeAnalysisConfig.globalExcludes,
119-
);
122+
return extractDartFilesFromFolders(contextFolders, rootFolder, excludes);
120123
}
121124

122125
FileElementsUsage? _analyzeFileCodeUsages(SomeResolvedUnitResult unit) {
@@ -141,27 +144,6 @@ class UnusedCodeAnalyzer {
141144
return {};
142145
}
143146

144-
Future<void> _analyseFiles(
145-
Set<String> files,
146-
Future<SomeResolvedUnitResult> Function(String) unitExtractor,
147-
FileElementsUsage codeUsages,
148-
Map<String, Set<Element>> publicCode, {
149-
bool Function(String)? shouldAnalyse,
150-
}) async {
151-
for (final filePath in files) {
152-
if (shouldAnalyse == null || shouldAnalyse(filePath)) {
153-
final unit = await unitExtractor(filePath);
154-
155-
final codeUsage = _analyzeFileCodeUsages(unit);
156-
if (codeUsage != null) {
157-
codeUsages.merge(codeUsage);
158-
}
159-
160-
publicCode[filePath] = _analyzeFilePublicCode(unit);
161-
}
162-
}
163-
}
164-
165147
Iterable<UnusedCodeFileReport> _getReports(
166148
FileElementsUsage codeUsages,
167149
Map<String, Set<Element>> publicCodeElements,
@@ -173,10 +155,7 @@ class UnusedCodeAnalyzer {
173155
final issues = <UnusedCodeIssue>[];
174156

175157
for (final element in elements) {
176-
if (!codeUsages.elements
177-
.any((usedElement) => _isUsed(usedElement, element)) &&
178-
!codeUsages.usedExtensions
179-
.any((usedElement) => _isUsed(usedElement, element))) {
158+
if (_isUnused(codeUsages, path, element)) {
180159
final unit = element.thisOrAncestorOfType<CompilationUnitElement>();
181160
if (unit != null) {
182161
issues.add(_createUnusedCodeIssue(element as ElementImpl, unit));
@@ -202,6 +181,26 @@ class UnusedCodeAnalyzer {
202181
element == usedElement ||
203182
element is PropertyInducingElement && element.getter == usedElement;
204183

184+
bool _isUnused(
185+
FileElementsUsage codeUsages,
186+
String path,
187+
Element element,
188+
) =>
189+
!codeUsages.elements.any(
190+
(usedElement) => _isUsed(usedElement, element),
191+
) &&
192+
!codeUsages.usedExtensions.any(
193+
(usedElement) => _isUsed(usedElement, element),
194+
) &&
195+
!codeUsages.prefixMap.values.any(
196+
(usage) =>
197+
usage.paths.contains(path) &&
198+
usage.elements.any((usedElement) =>
199+
_isUsed(usedElement, element) ||
200+
(usedElement.name == element.name &&
201+
usedElement.kind == element.kind)),
202+
);
203+
205204
UnusedCodeIssue _createUnusedCodeIssue(
206205
ElementImpl element,
207206
CompilationUnitElement unit,

lib/src/analyzers/unused_code_analyzer/used_code_visitor.dart

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
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+
67
import 'models/file_elements_usage.dart';
8+
import 'models/prefix_element_usage.dart';
79

810
// Copied from https://github.com/dart-lang/sdk/blob/main/pkg/analyzer/lib/src/error/imports_verifier.dart#L15
911

@@ -100,8 +102,10 @@ class UsedCodeVisitor extends RecursiveAstVisitor<void> {
100102
if (target is SimpleIdentifier) {
101103
final targetElement = target.staticElement;
102104
if (targetElement is PrefixElement) {
103-
(fileElementsUsage.prefixMap
104-
.putIfAbsent(targetElement, () => <Element>[])).add(element);
105+
(fileElementsUsage.prefixMap.putIfAbsent(
106+
targetElement,
107+
() => PrefixElementUsage(_getPrefixUsagePaths(target), {}),
108+
)).add(element);
105109

106110
return true;
107111
}
@@ -162,7 +166,10 @@ class UsedCodeVisitor extends RecursiveAstVisitor<void> {
162166

163167
return;
164168
} else if (element is PrefixElement) {
165-
fileElementsUsage.prefixMap.putIfAbsent(element, () => <Element>[]);
169+
fileElementsUsage.prefixMap.putIfAbsent(
170+
element,
171+
() => PrefixElementUsage(_getPrefixUsagePaths(identifier), {}),
172+
);
166173
} else if (element is MultiplyDefinedElement) {
167174
// If the element is multiply defined then call this method recursively
168175
// for each of the conflicting elements.
@@ -174,4 +181,25 @@ class UsedCodeVisitor extends RecursiveAstVisitor<void> {
174181
}
175182
}
176183
}
184+
185+
Iterable<String> _getPrefixUsagePaths(SimpleIdentifier target) {
186+
final root = target.root;
187+
188+
if (root is! CompilationUnit) {
189+
return [];
190+
}
191+
192+
return root.directives.fold<List<String>>([], (previousValue, directive) {
193+
if (directive is ImportDirective &&
194+
directive.prefix?.name == target.name) {
195+
previousValue.add(directive.uriSource.toString());
196+
197+
for (final config in directive.configurations) {
198+
previousValue.add(config.uriSource.toString());
199+
}
200+
}
201+
202+
return previousValue;
203+
});
204+
}
177205
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// ignore: prefer_expression_function_bodies
2+
String calculateResults() {
3+
return 'conditional results';
4+
}
5+
6+
bool hello() => false; // LINT
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
void freeze() {
2+
print('brr');
3+
}

test/resources/unused_code_analyzer/not_used.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ class NotUsed {} // LINT
22

33
const int value = 15; // LINT
44

5+
// ignore: no-empty-block
56
void someFunction() {} // LINT

test/resources/unused_code_analyzer/public_members.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// ignore_for_file: avoid_positional_boolean_parameters, library_private_types_in_public_api, override_on_non_overriding_member, unused_local_variable, no-empty-block
2+
13
// LINT
24
void printInteger(int aNumber) {
35
print('The number is $aNumber.'); // Print to console.
@@ -37,7 +39,7 @@ class _MyWidgetState extends BaseState<MyWidget> {
3739

3840
void myMethod() {
3941
setState(() {
40-
myString = "Hello";
42+
myString = 'Hello';
4143
});
4244
}
4345

@@ -152,7 +154,7 @@ enum SomeOtherEnum {
152154
world,
153155
}
154156

155-
class Mixin {}
157+
mixin Mixin {}
156158

157159
typedef Hello = String;
158160

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// ignore: prefer_expression_function_bodies
2+
String calculateResults() {
3+
return 'unconditional results';
4+
}

test/resources/unused_code_analyzer/unused_code_example.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
// ignore_for_file: unnecessary_statements, cascade_invocations, prefer_const_declarations, omit_local_variable_types
2+
13
import 'public_members.dart';
4+
import 'unconditional_file.dart'
5+
if (dart.library.html) 'conditional_file.dart'
6+
if (dart.library.io) 'conditional_file.dart' as config;
27

38
void main() {
49
final widget = MyWidget('hello');
@@ -17,4 +22,6 @@ void main() {
1722
str.doNothing();
1823

1924
SomeEnum.hello;
25+
26+
config.calculateResults();
2027
}

0 commit comments

Comments
 (0)