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

Commit eb5412c

Browse files
authored
feat: introduce check-unused-l10n command (#467)
* feat: inroduce check-unused-l10n command * chore: rename files * test: add tests * chore: add exports * fix: fix readme * test: update tests * chore: review fixes and reporter refactoring * test: update tests * chore: update changelog
1 parent 72fb79b commit eb5412c

27 files changed

+993
-31
lines changed

CHANGELOG.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
## Unreleased
44

5-
* feat: Add static code diagnostic `prefer-const-border-radius`.
6-
* Improve static code diagnostic `prefer-extracting-callbacks`, don't trigger on empty function blocks.
7-
* Improve unused files check, add support for `vm:entry-point` annotation.
8-
* feat: ignore Flutter builder functions in `prefer-extracting-callbacks` rule.
9-
* fix: compute NumberOfParametersMetric only for functions and methods
5+
* feat: introduce `check-unused-l10n` command.
6+
* feat: add static code diagnostic `prefer-const-border-radius`.
7+
* feat: improve static code diagnostic `prefer-extracting-callbacks`: don't trigger on empty function blocks and ignore Flutter builder functions.
8+
* feat: improve unused files check, add support for `vm:entry-point` annotation.
9+
* fix: compute NumberOfParametersMetric only for functions and methods.
1010

1111
## 4.3.3
1212

lib/src/analyzers/lint_analyzer/lint_analyzer.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class LintAnalyzer {
4545
String rootFolder,
4646
) {
4747
if (!isExcluded(result.path, config.globalExcludes)) {
48-
return _runAnalysisForFile(
48+
return _analyzeFile(
4949
result,
5050
config,
5151
rootFolder,
@@ -95,7 +95,7 @@ class LintAnalyzer {
9595
for (final filePath in analyzedFiles) {
9696
final unit = await context.currentSession.getResolvedUnit(filePath);
9797
if (unit is ResolvedUnitResult) {
98-
final result = _runAnalysisForFile(
98+
final result = _analyzeFile(
9999
unit,
100100
lintAnalysisConfig,
101101
rootFolder,
@@ -112,7 +112,7 @@ class LintAnalyzer {
112112
return analyzerResult;
113113
}
114114

115-
LintFileReport? _runAnalysisForFile(
115+
LintFileReport? _analyzeFile(
116116
ResolvedUnitResult result,
117117
LintAnalysisConfig config,
118118
String rootFolder, {

lib/src/analyzers/lint_analyzer/reporters/reporters_list/json/lint_json_reporter.dart

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,9 @@ class LintJsonReporter extends JsonReporter<LintFileReport> {
2222
return;
2323
}
2424

25-
final nowTime = DateTime.now();
26-
final reportTime = DateTime(
27-
nowTime.year,
28-
nowTime.month,
29-
nowTime.day,
30-
nowTime.hour,
31-
nowTime.minute,
32-
nowTime.second,
33-
);
34-
3525
final encodedReport = json.encode({
3626
'formatVersion': formatVersion,
37-
'timestamp': reportTime.toString(),
27+
'timestamp': getTimestamp(),
3828
'records': records.map(_lintFileReportToJson).toList(),
3929
});
4030

lib/src/analyzers/unused_files_analyzer/reporters/reporters_list/json/unused_files_json_reporter.dart

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,9 @@ class UnusedFilesJsonReporter extends JsonReporter<UnusedFilesFileReport> {
1616
return;
1717
}
1818

19-
final nowTime = DateTime.now();
20-
final reportTime = DateTime(
21-
nowTime.year,
22-
nowTime.month,
23-
nowTime.day,
24-
nowTime.hour,
25-
nowTime.minute,
26-
nowTime.second,
27-
);
28-
2919
final encodedReport = json.encode({
3020
'formatVersion': formatVersion,
31-
'timestamp': reportTime.toString(),
21+
'timestamp': getTimestamp(),
3222
'unusedFiles': records.map(_analysisRecordToJson).toList(),
3323
});
3424

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import '../../../reporters/models/file_report.dart';
2+
import 'unused_l10n_issue.dart';
3+
4+
class UnusedL10nFileReport implements FileReport {
5+
/// The path to the target file.
6+
@override
7+
final String path;
8+
9+
/// The path to the target file relative to the package root.
10+
@override
11+
final String relativePath;
12+
13+
/// The issues detected in the target file.
14+
final Iterable<UnusedL10nIssue> issues;
15+
16+
/// The name of a class with issues.
17+
final String className;
18+
19+
const UnusedL10nFileReport({
20+
required this.path,
21+
required this.relativePath,
22+
required this.issues,
23+
required this.className,
24+
});
25+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'package:meta/meta.dart';
2+
import 'package:source_span/source_span.dart';
3+
4+
/// Represents an issue detected by the unused localization check.
5+
@immutable
6+
class UnusedL10nIssue {
7+
/// The name of a class member, which is unused
8+
final String memberName;
9+
10+
/// The source location associated with this issue.
11+
final SourceLocation location;
12+
13+
/// Initialize a newly created [UnusedL10nIssue].
14+
///
15+
/// The issue is associated with the given [location]. Used for
16+
/// creating an unused localization report.
17+
const UnusedL10nIssue({
18+
required this.memberName,
19+
required this.location,
20+
});
21+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import 'dart:io';
2+
3+
import '../../../reporters/models/console_reporter.dart';
4+
import '../../../reporters/models/json_reporter.dart';
5+
import '../../../reporters/models/reporter.dart';
6+
import 'reporters_list/console/unused_l10n_console_reporter.dart';
7+
import 'reporters_list/json/unused_l10n_json_reporter.dart';
8+
9+
// ignore: avoid_private_typedef_functions
10+
typedef _ReportersFactory = Reporter Function(IOSink output);
11+
12+
final _implementedReports = <String, _ReportersFactory>{
13+
ConsoleReporter.id: (output) => UnusedL10nConsoleReporter(output),
14+
JsonReporter.id: (output) => UnusedL10nJsonReporter(output),
15+
};
16+
17+
Reporter? reporter({
18+
required String name,
19+
required IOSink output,
20+
}) {
21+
final constructor = _implementedReports[name];
22+
23+
return constructor != null ? constructor(output) : null;
24+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import 'dart:io';
2+
3+
import 'package:ansicolor/ansicolor.dart';
4+
5+
import '../../../../../reporters/models/console_reporter.dart';
6+
import '../../../models/unused_l10n_file_report.dart';
7+
8+
class UnusedL10nConsoleReporter extends ConsoleReporter<UnusedL10nFileReport> {
9+
final _errorColor = AnsiPen()..red(bold: true);
10+
final _warningColor = AnsiPen()..yellow(bold: true);
11+
final _successColor = AnsiPen()..green();
12+
13+
UnusedL10nConsoleReporter(IOSink output) : super(output);
14+
15+
@override
16+
Future<void> report(Iterable<UnusedL10nFileReport> records) async {
17+
if (records.isEmpty) {
18+
output.writeln('${_successColor('✔')} no unused localization found!');
19+
20+
return;
21+
}
22+
23+
final sortedRecords = records.toList()
24+
..sort((a, b) => a.relativePath.compareTo(b.relativePath));
25+
26+
var warnings = 0;
27+
28+
for (final analysisRecord in sortedRecords) {
29+
output.writeln('class ${analysisRecord.className}');
30+
31+
for (final issue in analysisRecord.issues) {
32+
final line = issue.location.line;
33+
final column = issue.location.column;
34+
final path = analysisRecord.relativePath;
35+
36+
final offset = ''.padRight(3);
37+
final pathOffset = offset.padRight(5);
38+
39+
output
40+
..writeln('$offset ${_warningColor('⚠')} unused ${issue.memberName}')
41+
..writeln('$pathOffset at $path:$line:$column');
42+
}
43+
44+
warnings += analysisRecord.issues.length;
45+
46+
output.writeln('');
47+
}
48+
49+
output.writeln(
50+
'${_errorColor('✖')} total unused localization class fields, getters and methods - ${_errorColor(warnings)}',
51+
);
52+
}
53+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
4+
import 'package:meta/meta.dart';
5+
6+
import '../../../../../reporters/models/json_reporter.dart';
7+
import '../../../models/unused_l10n_file_report.dart';
8+
import '../../../models/unused_l10n_issue.dart';
9+
10+
@immutable
11+
class UnusedL10nJsonReporter extends JsonReporter<UnusedL10nFileReport> {
12+
const UnusedL10nJsonReporter(IOSink output) : super(output, 2);
13+
14+
@override
15+
Future<void> report(Iterable<UnusedL10nFileReport> records) async {
16+
if (records.isEmpty) {
17+
return;
18+
}
19+
20+
final encodedReport = json.encode({
21+
'formatVersion': formatVersion,
22+
'timestamp': getTimestamp(),
23+
'unusedLocalizations': records.map(_unusedL10nFileReportToJson).toList(),
24+
});
25+
26+
output.write(encodedReport);
27+
}
28+
29+
Map<String, Object> _unusedL10nFileReportToJson(
30+
UnusedL10nFileReport report,
31+
) =>
32+
{
33+
'path': report.relativePath,
34+
'className': report.className,
35+
'issues': report.issues.map(_issueToJson).toList(),
36+
};
37+
38+
Map<String, Object> _issueToJson(UnusedL10nIssue issue) => {
39+
'memberName': issue.memberName,
40+
'column': issue.location.column,
41+
'line': issue.location.line,
42+
'offset': issue.location.offset,
43+
};
44+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import 'package:glob/glob.dart';
2+
3+
/// Represents converted unused localization config,
4+
/// which contains parsed entities.
5+
class UnusedL10nAnalysisConfig {
6+
final Iterable<Glob> globalExcludes;
7+
final Iterable<Glob> analyzerExcludedPatterns;
8+
final RegExp classPattern;
9+
10+
UnusedL10nAnalysisConfig(
11+
this.globalExcludes,
12+
this.analyzerExcludedPatterns,
13+
String? classPattern,
14+
) : classPattern = RegExp(classPattern ?? r'I18n$');
15+
}

0 commit comments

Comments
 (0)