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

feat: add check unused code command #649

Merged
merged 7 commits into from
Jan 17, 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 @@ -5,6 +5,7 @@
* fix: ignore private variables in `avoid-global-state` rule.
* feat: support excludes for a separate anti-pattern.
* chore: restrict `analyzer` version to `>=2.4.0 <3.2.0`.
* feat: add check unused code command.
* feat: ignore private members for `check-unused-l10n` command.

## 4.9.1
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,26 @@ Console report example:

![Console report example](https://raw.githubusercontent.com/dart-code-checker/dart-code-metrics/master/assets/unused-l10n-console-report.png)

#### Check unused code

Checks unused code in `*.dart` files. To execute the command, run

```sh
$ dart run dart_code_metrics:metrics check-unused-code lib

# or for a Flutter package
$ flutter pub run dart_code_metrics:metrics check-unused-code lib
```

It will produce a result in one of the format:

- Console
- JSON

Console report example:

![Console report example](https://raw.githubusercontent.com/dart-code-checker/dart-code-metrics/master/assets/unused-code-console-report.png)

## Troubleshooting

Please read [the following guide](./TROUBLESHOOTING.md) if the plugin is not working as you'd expect it to work.
Expand Down
1 change: 1 addition & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ analyzer:
- test/resources/*
- test/resources/unused_files_analyzer/**
- test/resources/unused_l10n_analyzer/**
- test/resources/unused_code_analyzer/**
- test/**/examples/**
language:
strict-inference: true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:analyzer/dart/element/element.dart';

/// A container with information about used imports prefixes and used imported
/// elements.
class FileElementsUsage {
/// The map of referenced prefix elements and the elements that they prefix.
final Map<PrefixElement, List<Element>> prefixMap = {};

/// The set of referenced top-level elements.
final Set<Element> elements = {};

/// The set of extensions defining members that are referenced.
final Set<ExtensionElement> usedExtensions = {};

final Set<String> exports = {};

void merge(FileElementsUsage other) {
prefixMap.addAll(other.prefixMap);
elements.addAll(other.elements);
usedExtensions.addAll(other.usedExtensions);
exports.addAll(other.exports);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import '../../../reporters/models/file_report.dart';
import 'unused_code_issue.dart';

/// Represents unused code report collected for a file
class UnusedCodeFileReport implements FileReport {
/// The path to the target file.
@override
final String path;

/// The path to the target file relative to the package root.
@override
final String relativePath;

/// The issues detected in the target file.
final Iterable<UnusedCodeIssue> issues;

const UnusedCodeFileReport({
required this.path,
required this.relativePath,
required this.issues,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:source_span/source_span.dart';

/// Represents an issue detected by the unused code check.
class UnusedCodeIssue {
/// The name of the unused declaration.
final String declarationName;

/// The type of the unused declaration.
final String declarationType;

/// The source location associated with this issue.
final SourceLocation location;

/// Initialize a newly created [UnusedCodeIssue].
///
/// The issue is associated with the given [location]. Used for
/// creating an unused code report.
const UnusedCodeIssue({
required this.declarationName,
required this.declarationType,
required this.location,
});
}
39 changes: 39 additions & 0 deletions lib/src/analyzers/unused_code_analyzer/public_code_visitor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// ignore_for_file: public_member_api_docs

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';

import '../../utils/node_utils.dart';

class PublicCodeVisitor extends GeneralizingAstVisitor<void> {
final Set<Element> topLevelElements = {};

@override
void visitCompilationUnitMember(CompilationUnitMember node) {
if (node is FunctionDeclaration) {
if (isEntrypoint(node.name.name, node.metadata)) {
return;
}
}

_getTopLevelElement(node);
}

@override
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
final variables = node.variables.variables;

if (variables.isNotEmpty) {
_getTopLevelElement(variables.first);
}
}

void _getTopLevelElement(Declaration node) {
final element = node.declaredElement;

if (element != null) {
topLevelElements.add(element);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'dart:io';

import '../../../../unused_code_analyzer.dart';
import '../../../reporters/models/console_reporter.dart';
import '../../../reporters/models/json_reporter.dart';
import '../../../reporters/models/reporter.dart';

final _implementedReports = <String,
Reporter<UnusedCodeFileReport, void, void> Function(IOSink output)>{
ConsoleReporter.id: (output) => UnusedCodeConsoleReporter(output),
JsonReporter.id: (output) => UnusedCodeJsonReporter(output),
};

Reporter<UnusedCodeFileReport, void, void>? reporter({
required String name,
required IOSink output,
}) {
final constructor = _implementedReports[name];

return constructor != null ? constructor(output) : null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'dart:io';

import '../../../../../reporters/models/console_reporter.dart';
import '../../../models/unused_code_file_report.dart';

/// Unused code console reporter.
///
/// Use it to create reports in console format.
class UnusedCodeConsoleReporter
extends ConsoleReporter<UnusedCodeFileReport, void, void> {
UnusedCodeConsoleReporter(IOSink output) : super(output);

@override
Future<void> report(
Iterable<UnusedCodeFileReport> records, {
Iterable<void> summary = const [],
void additionalParams,
}) async {
if (records.isEmpty) {
output.writeln('${okPen('✔')} no unused code found!');

return;
}

final sortedRecords = records.toList()
..sort((a, b) => a.relativePath.compareTo(b.relativePath));

var warnings = 0;

for (final analysisRecord in sortedRecords) {
output.writeln('${analysisRecord.relativePath}:');

for (final issue in analysisRecord.issues) {
final line = issue.location.line;
final column = issue.location.column;
final path = analysisRecord.relativePath;

final offset = ''.padRight(3);
final pathOffset = offset.padRight(5);

output
..writeln(
'$offset ${warningPen('⚠')} unused ${issue.declarationType} ${issue.declarationName}',
)
..writeln('$pathOffset at $path:$line:$column');
}

warnings += analysisRecord.issues.length;

output.writeln('');
}

output.writeln(
'${alarmPen('✖')} total unused code (classes, functions, variables, extensions, enums, mixins and type aliases) - ${alarmPen(warnings)}',
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'dart:convert';
import 'dart:io';

import '../../../../../reporters/models/json_reporter.dart';
import '../../../models/unused_code_file_report.dart';
import '../../../models/unused_code_issue.dart';

/// Unused code JSON reporter.
///
/// Use it to create reports in JSON format.
class UnusedCodeJsonReporter
extends JsonReporter<UnusedCodeFileReport, void, void> {
const UnusedCodeJsonReporter(IOSink output) : super(output, 2);

@override
Future<void> report(
Iterable<UnusedCodeFileReport> records, {
Iterable<void> summary = const [],
void additionalParams,
}) async {
if (records.isEmpty) {
return;
}

final encodedReport = json.encode({
'formatVersion': formatVersion,
'timestamp': getTimestamp(),
'unusedCode': records.map(_unusedCodeFileReportToJson).toList(),
});

output.write(encodedReport);
}

Map<String, Object> _unusedCodeFileReportToJson(
UnusedCodeFileReport report,
) =>
{
'path': report.relativePath,
'issues': report.issues.map(_issueToJson).toList(),
};

Map<String, Object> _issueToJson(UnusedCodeIssue issue) => {
'declarationType': issue.declarationType,
'declarationName': issue.declarationName,
'column': issue.location.column,
'line': issue.location.line,
'offset': issue.location.offset,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:glob/glob.dart';

/// Represents converted unused code config which contains parsed entities.
class UnusedCodeAnalysisConfig {
final Iterable<Glob> globalExcludes;
final Iterable<Glob> analyzerExcludedPatterns;

const UnusedCodeAnalysisConfig(
this.globalExcludes,
this.analyzerExcludedPatterns,
);
}
Loading