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

Commit 824f90c

Browse files
authored
feat: add Checkstyle format reporter. (#738)
* feat: add Checkstyle format reporter. * chore: tune bitbucket pipeline * chore: update Changelog
1 parent b83c5f1 commit 824f90c

File tree

14 files changed

+213
-6
lines changed

14 files changed

+213
-6
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"backport",
77
"behaviour",
88
"bools",
9+
"Checkstyle",
910
"codeclimate",
1011
"codecov",
1112
"codequality",

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
* **Breaking Change:** cli arguments `--fatal-unused` and `--fatal-warnings` activate by default.
6+
* feat: add Checkstyle format reporter.
67
* chore: restrict `analyzer` version to `>=3.0.0 <3.4.0`.
78
* chore: restrict `analyzer_plugin` version to `>=0.9.0 <0.10.0`.
89
* feat: add [prefer-immediate-return](https://dartcodemetrics.dev/docs/rules/common/prefer-immediate-return) rule

bitbucket-pipelines.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ pipelines:
66
name: analyze
77
script:
88
- dart pub upgrade
9-
- dart run dart_code_metrics:metrics lib
9+
- dart run dart_code_metrics:metrics lib -r checkstyle > checkstyle-result.xml
10+
after-script:
11+
- pipe: atlassian/checkstyle-report:0.3.1

lib/src/analyzers/lint_analyzer/reporters/reporter_factory.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:io';
22

3+
import '../../../reporters/models/checkstyle_reporter.dart';
34
import '../../../reporters/models/code_climate_reporter.dart';
45
import '../../../reporters/models/console_reporter.dart';
56
import '../../../reporters/models/file_report.dart';
@@ -8,6 +9,7 @@ import '../../../reporters/models/html_reporter.dart';
89
import '../../../reporters/models/json_reporter.dart';
910
import '../../../reporters/models/reporter.dart';
1011
import 'lint_report_params.dart';
12+
import 'reporters_list/checkstyle/lint_checkstyle_reporter.dart';
1113
import 'reporters_list/code_climate/lint_code_climate_reporter.dart';
1214
import 'reporters_list/console/lint_console_reporter.dart';
1315
import 'reporters_list/github/lint_github_reporter.dart';
@@ -20,6 +22,7 @@ final _implementedReports = <
2022
IOSink output,
2123
String reportFolder,
2224
)>{
25+
CheckstyleReporter.id: (output, _) => LintCheckstyleReporter(output),
2326
ConsoleReporter.id: (output, _) => LintConsoleReporter(output),
2427
ConsoleReporter.verboseId: (output, _) =>
2528
LintConsoleReporter(output, reportAll: true),
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import 'dart:io';
2+
3+
import 'package:xml/xml.dart';
4+
5+
import '../../../../../reporters/models/checkstyle_reporter.dart';
6+
import '../../../models/lint_file_report.dart';
7+
import '../../../models/severity.dart';
8+
import '../../../models/summary_lint_report_record.dart';
9+
import '../../lint_report_params.dart';
10+
11+
/// Lint Checkstyle reporter.
12+
///
13+
/// Use it to create reports in Checkstyle format.
14+
class LintCheckstyleReporter extends CheckstyleReporter<LintFileReport,
15+
SummaryLintReportRecord<Object>, LintReportParams> {
16+
LintCheckstyleReporter(IOSink output) : super(output);
17+
18+
@override
19+
Future<void> report(
20+
Iterable<LintFileReport> records, {
21+
Iterable<SummaryLintReportRecord<Object>> summary = const [],
22+
LintReportParams? additionalParams,
23+
}) async {
24+
if (records.isEmpty) {
25+
return;
26+
}
27+
28+
final builder = XmlBuilder();
29+
30+
builder
31+
..processing('xml', 'version="1.0"')
32+
..element('checkstyle', attributes: {'version': '10.0'}, nest: () {
33+
for (final record in records) {
34+
if (!_needToReport(record)) {
35+
continue;
36+
}
37+
38+
builder.element(
39+
'file',
40+
attributes: {'name': record.relativePath},
41+
nest: () {
42+
final issues = [...record.issues, ...record.antiPatternCases];
43+
44+
for (final issue in issues) {
45+
builder.element(
46+
'error',
47+
attributes: {
48+
'line': '${issue.location.start.line}',
49+
if (issue.location.start.column > 0)
50+
'column': '${issue.location.start.column}',
51+
'severity': _severityMapping[issue.severity] ?? 'ignore',
52+
'message': issue.message,
53+
'source': issue.ruleId,
54+
},
55+
);
56+
}
57+
},
58+
);
59+
}
60+
});
61+
62+
output.writeln(builder.buildDocument().toXmlString(pretty: true));
63+
}
64+
65+
bool _needToReport(LintFileReport report) =>
66+
report.issues.isNotEmpty || report.antiPatternCases.isNotEmpty;
67+
}
68+
69+
const _severityMapping = {
70+
Severity.error: 'error',
71+
Severity.warning: 'warning',
72+
Severity.style: 'info',
73+
Severity.performance: 'warning',
74+
Severity.none: 'ignore',
75+
};

lib/src/cli/commands/analyze_command.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class AnalyzeCommand extends BaseCommand {
110110
allowed: [
111111
FlagNames.consoleReporter,
112112
FlagNames.consoleVerboseReporter,
113+
FlagNames.checkstyleReporter,
113114
FlagNames.codeClimateReporter,
114115
FlagNames.githubReporter,
115116
FlagNames.gitlabCodeClimateReporter,

lib/src/cli/models/flag_names.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import '../../reporters/models/checkstyle_reporter.dart';
12
import '../../reporters/models/code_climate_reporter.dart';
23
import '../../reporters/models/console_reporter.dart';
34
import '../../reporters/models/github_reporter.dart';
@@ -17,6 +18,7 @@ class FlagNames {
1718
static const consoleReporter = ConsoleReporter.id;
1819
static const consoleVerboseReporter = ConsoleReporter.verboseId;
1920
static const codeClimateReporter = CodeClimateReporter.id;
21+
static const checkstyleReporter = CheckstyleReporter.id;
2022
static const htmlReporter = HtmlReporter.id;
2123
static const jsonReporter = JsonReporter.id;
2224
static const githubReporter = GitHubReporter.id;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'dart:io';
2+
3+
import 'package:meta/meta.dart';
4+
5+
import 'file_report.dart';
6+
import 'reporter.dart';
7+
8+
/// Creates reports in Checkstyle format widely understood by various CI and analysis tools
9+
abstract class CheckstyleReporter<T extends FileReport, S, P>
10+
extends Reporter<T, S, P> {
11+
static const String id = 'checkstyle';
12+
13+
@protected
14+
final IOSink output;
15+
16+
const CheckstyleReporter(this.output);
17+
}

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ dependencies:
2222
meta: ^1.3.0
2323
path: ^1.8.0
2424
source_span: ^1.8.0
25+
xml: ^5.3.0
2526
yaml: ^3.1.0
2627

2728
dev_dependencies:
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import 'dart:io';
2+
3+
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/reporters/reporters_list/checkstyle/lint_checkstyle_reporter.dart';
4+
import 'package:mocktail/mocktail.dart';
5+
import 'package:test/test.dart';
6+
import 'package:xml/xml.dart';
7+
8+
import '../report_example.dart';
9+
10+
class IOSinkMock extends Mock implements IOSink {}
11+
12+
void main() {
13+
group('LintCheckstyleReporter reports in json format', () {
14+
// ignore: close_sinks
15+
late IOSinkMock output;
16+
17+
setUp(() {
18+
output = IOSinkMock();
19+
});
20+
21+
test('empty report', () {
22+
LintCheckstyleReporter(output).report([]);
23+
24+
verifyNever(() => output.write(captureAny()));
25+
});
26+
27+
test('complex report', () {
28+
LintCheckstyleReporter(output).report(testReport, summary: testSummary);
29+
30+
final captured = verify(
31+
() => output.writeln(captureAny()),
32+
).captured.first as String;
33+
final report = XmlDocument.parse(captured);
34+
35+
final file = report.findAllElements('file');
36+
expect(
37+
file.first.getAttribute('name'),
38+
equals('test/resources/class_with_factory_constructors.dart'),
39+
);
40+
41+
final errors = report.findAllElements('error');
42+
expect(errors.first.getAttribute('line'), equals('0'));
43+
expect(errors.first.getAttribute('severity'), equals('warning'));
44+
expect(errors.first.getAttribute('message'), equals('simple message'));
45+
expect(errors.first.getAttribute('source'), equals('id'));
46+
47+
expect(errors.last.getAttribute('line'), equals('0'));
48+
expect(errors.last.getAttribute('severity'), equals('info'));
49+
expect(
50+
errors.last.getAttribute('message'),
51+
equals('simple design message'),
52+
);
53+
expect(errors.last.getAttribute('source'), equals('designId'));
54+
});
55+
});
56+
}

test/src/cli/commands/analyze_command_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const _usage = 'Collect code metrics, rules and anti-patterns violations.\n'
99
'\n'
1010
'\n'
1111
'-r, --reporter=<console> The format of the output of the analysis.\n'
12-
' [console (default), console-verbose, codeclimate, github, gitlab, html, json]\n'
12+
' [console (default), console-verbose, checkstyle, codeclimate, github, gitlab, html, json]\n'
1313
'-o, --output-directory=<OUTPUT> Write HTML output to OUTPUT.\n'
1414
' (defaults to "metrics")\n'
1515
'\n'

website/docs/cli/analyze.md

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ Usage: metrics analyze [arguments...] <directories>
1818
1919
2020
-r, --reporter=<console> The format of the output of the analysis
21-
[console (default), console-verbose,
22-
codeclimate, github, gitlab, html, json]
21+
[console (default), console-verbose, checkstyle, codeclimate, github, gitlab, html, json]
2322
-o, --output-directory=<OUTPUT> Write HTML output to OUTPUT
2423
(defaults to "metrics/")
2524
@@ -356,7 +355,7 @@ jobs:
356355
run: flutter pub get
357356

358357
- name: Run Code Metrics
359-
run: flutter pub run dart_code_metrics:metrics --reporter=github lib
358+
run: flutter pub run dart_code_metrics:metrics --fatal-style --fatal-performance --fatal-warnings --reporter=github lib
360359
```
361360
362361
Example of a report in a PR:
@@ -375,7 +374,7 @@ code_quality:
375374
before_script:
376375
- dart pub global activate dart_code_metrics
377376
script:
378-
- dart pub global run dart_code_metrics:metrics analyze lib -r gitlab > code-quality-report.json
377+
- dart pub global run dart_code_metrics:metrics analyze --fatal-style --fatal-performance --fatal-warnings --reporter=gitlab lib > code-quality-report.json
379378
artifacts:
380379
reports:
381380
codequality: code-quality-report.json
@@ -388,3 +387,51 @@ Example of a Code Quality widget in a PR:
388387
Example of a Code Quality in a PR diff view:
389388

390389
![GitLab diff](../../static/img/gitlab-codequality-diff-view.jpg)
390+
391+
### Checkstyle {#checkstyle}
392+
393+
Reports about design and static code diagnostics issues. Use `--reporter=checkstyle` to enable this format.
394+
395+
```xml
396+
<?xml version="1.0"?>
397+
<checkstyle version="10.0">
398+
<file name="lib/src/analyzers/lint_analyzer/lint_analyzer.dart">
399+
<error line="168" column="3" severity="ignore" message="Long method. This method contains 63 lines with code." source="long-method"/>
400+
</file>
401+
<file name="lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_returning_widgets/visit_declaration.dart">
402+
<error line="3" column="1" severity="ignore" message="Long Parameter List. This function require 6 arguments." source="long-parameter-list"/>
403+
</file>
404+
<file name="lib/src/analyzers/lint_analyzer/reporters/utility_selector.dart">
405+
<error line="27" column="3" severity="ignore" message="Long method. This method contains 53 lines with code." source="long-method"/>
406+
</file>
407+
<file name="lib/src/analyzers/lint_analyzer/reporters/reporters_list/html/utility_functions.dart">
408+
<error line="45" column="1" severity="ignore" message="Long function. This function contains 56 lines with code." source="long-method"/>
409+
</file>
410+
<file name="lib/src/analyzers/lint_analyzer/reporters/reporters_list/html/lint_html_reporter.dart">
411+
<error line="74" column="3" severity="ignore" message="Long method. This method contains 136 lines with code." source="long-method"/>
412+
<error line="330" column="3" severity="ignore" message="Long method. This method contains 159 lines with code." source="long-method"/>
413+
</file>
414+
</checkstyle>
415+
```
416+
417+
#### Example Bitbucket pipeline configuration
418+
419+
- Define a pipeline in your [`bitbucket-pipelines.yml`](https://support.atlassian.com/bitbucket-cloud/docs/configure-bitbucket-pipelinesyml/) file that generates the Checkstyle report and [process them](https://bitbucket.org/product/features/pipelines/integrations?p=atlassian/checkstyle-report).
420+
421+
```yaml
422+
image: google/dart
423+
424+
pipelines:
425+
default:
426+
- step:
427+
name: analyze
428+
script:
429+
- dart pub global activate dart_code_metrics
430+
- dart pub global run dart_code_metrics:metrics analyze --fatal-style --fatal-performance --fatal-warnings --reporter=checkstyle lib > checkstyle-result.xml
431+
after-script:
432+
- pipe: atlassian/checkstyle-report:0.3.1
433+
```
434+
435+
Example of a report:
436+
437+
![Checkstyle report in Bitbucket ](../../static/img/bitbucket-pipeline-report.jpg)

website/docs/cli/overview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ It will produce a result in one of the supported formats:
2929

3030
- Console
3131
- GitHub
32+
- Checkstyle
3233
- Codeclimate
3334
- HTML
3435
- JSON
Loading

0 commit comments

Comments
 (0)