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

fix: correctly support conditional imports for check-unused-code #1097

Merged
merged 2 commits into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* feat: add static code diagnostic [`prefer-using-list-view`](https://dartcodemetrics.dev/docs/rules/flutter/prefer-using-list-view).
* feat: add static code diagnostic [`avoid-unnecessary-conditionals`](https://dartcodemetrics.dev/docs/rules/common/avoid-unnecessary-conditionals).
* feat: support boolean literals removal for [`prefer-conditional-expressions`](https://dartcodemetrics.dev/docs/rules/common/prefer-conditional-expressions) auto-fix.
* fix: correctly support conditional imports for [`check-unused-code`](https://dartcodemetrics.dev/docs/cli/check-unused-code).

## 5.1.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ class FileElementsUsage {

final Set<String> exports = {};

final Map<Set<String>, Set<Element>> conditionalElements = {};

void merge(FileElementsUsage other) {
prefixMap.addAll(other.prefixMap);
elements.addAll(other.elements);
usedExtensions.addAll(other.usedExtensions);
exports.addAll(other.exports);
conditionalElements.addAll(other.conditionalElements);
}
}
34 changes: 17 additions & 17 deletions lib/src/analyzers/unused_code_analyzer/unused_code_analyzer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -210,25 +210,25 @@ class UnusedCodeAnalyzer {
.contains(declaredSource.fullName);
}

bool _isUnused(
FileElementsUsage codeUsages,
String path,
Element element,
) =>
!codeUsages.elements.any(
(usedElement) => _isUsed(usedElement, element),
) &&
!codeUsages.usedExtensions.any(
(usedElement) => _isUsed(usedElement, element),
) &&
!codeUsages.prefixMap.values.any(
(usage) =>
usage.paths.contains(path) &&
usage.elements.any((usedElement) =>
bool _isUnused(FileElementsUsage codeUsages, String path, Element element) =>
!codeUsages.conditionalElements.entries.any((entry) =>
entry.key.contains(path) &&
entry.value.any((usedElement) =>
_isUsed(usedElement, element) ||
(usedElement.name == element.name &&
usedElement.kind == element.kind))) &&
!codeUsages.elements
.any((usedElement) => _isUsed(usedElement, element)) &&
!codeUsages.usedExtensions
.any((usedElement) => _isUsed(usedElement, element)) &&
!codeUsages.prefixMap.values.any((usage) =>
usage.paths.contains(path) &&
usage.elements.any(
(usedElement) =>
_isUsed(usedElement, element) ||
(usedElement.name == element.name &&
usedElement.kind == element.kind)),
);
usedElement.kind == element.kind),
));

UnusedCodeIssue _createUnusedCodeIssue(
ElementImpl element,
Expand Down
51 changes: 50 additions & 1 deletion lib/src/analyzers/unused_code_analyzer/used_code_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:collection/collection.dart';

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

UsedCodeVisitor();

@override
void visitImportDirective(ImportDirective node) {
if (node.configurations.isNotEmpty) {
final paths = node.configurations.map((config) {
final uri = config.resolvedUri;

return (uri is DirectiveUriWithSource) ? uri.source.fullName : null;
}).whereNotNull();
// ignore: deprecated_member_use
final mainImport = node.element2?.importedLibrary?.source.fullName;

final allPaths = {if (mainImport != null) mainImport, ...paths};

fileElementsUsage.conditionalElements.update(
allPaths,
(conditionalElements) => conditionalElements,
ifAbsent: () => {},
);
}

super.visitImportDirective(node);
}

@override
void visitExportDirective(ExportDirective node) {
super.visitExportDirective(node);
Expand Down Expand Up @@ -130,6 +156,24 @@ class UsedCodeVisitor extends RecursiveAstVisitor<void> {
return false;
}

bool _recordConditionalElement(Element element) {
final elementPath = element.source?.fullName;
if (elementPath == null) {
return false;
}

final entries = fileElementsUsage.conditionalElements.entries;
for (final conditionalElement in entries) {
if (conditionalElement.key.contains(elementPath)) {
conditionalElement.value.add(element);

return true;
}
}

return false;
}

/// Records use of a not prefixed [element].
void _recordUsedElement(Element element) {
// Ignore if an unknown library.
Expand Down Expand Up @@ -157,11 +201,16 @@ class UsedCodeVisitor extends RecursiveAstVisitor<void> {
return;
}

// Usage in State<WidgetClassName> is not a sing of usage.
// Usage in State<WidgetClassName> is not a sign of usage.
if (_isUsedAsNamedTypeForWidgetState(identifier)) {
return;
}

// Record elements that are imported with conditional imports
if (_recordConditionalElement(element)) {
return;
}

// Record `importPrefix.identifier` into 'prefixMap'.
if (_recordPrefixMap(identifier, element)) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
int calculate() => 1;

bool helloWorld() => false; // LINT
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int calculate() => 2;
9 changes: 7 additions & 2 deletions test/resources/unused_code_analyzer/unused_code_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import 'public_members.dart';
import 'unconditional_file.dart'
if (dart.library.html) 'conditional_file.dart'
if (dart.library.io) 'conditional_file.dart' as config;
if (dart.library.io) 'conditional_file.dart';
import 'unconditional_prefixed_file.dart'
if (dart.library.html) 'conditional_prefixed_file.dart'
if (dart.library.io) 'conditional_prefixed_file.dart' as config;

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

SomeEnum.hello;

config.calculateResults();
calculateResults();

config.calculate();

setPadding();
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ void main() {
);
});

test('should report 4 files and not report excluded file', () {
expect(result, hasLength(4));
test('should report 5 files and not report excluded file', () {
expect(result, hasLength(5));
});

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

test('should analyze conditional prefixed import files', () async {
final unconditionalReport = result.firstWhereOrNull(
(report) =>
report.path.endsWith('unconditional_prefixed_file.dart'),
);

expect(unconditionalReport, null);

final report = result.firstWhere(
(report) => report.path.endsWith('conditional_prefixed_file.dart'),
);

expect(report.issues, hasLength(1));

final firstIssue = report.issues.first;
expect(firstIssue.declarationName, 'helloWorld');
expect(firstIssue.declarationType, 'function');
expect(firstIssue.location.line, 3);
expect(firstIssue.location.column, 1);
});

test('should analyze files', () async {
final report = result.firstWhere(
(report) => report.path.endsWith('public_members.dart'),
Expand Down