Skip to content
This repository was archived by the owner on Jul 16, 2023. It is now read-only.
Closed
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 @@ -3,6 +3,7 @@
## Unreleased

* refactor: make violations field of summary lint report record as integer
* feat: introduce file metrics

## 4.6.0

Expand Down
12 changes: 12 additions & 0 deletions lib/src/analyzers/lint_analyzer/lint_analyzer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ class LintAnalyzer {
return LintFileReport(
path: filePath,
relativePath: relativePath,
file: Report(
location:
nodeLocation(node: internalResult.unit, source: internalResult),
metrics: const [],
declaration: internalResult.unit,
),
classes: Map.unmodifiable(classMetrics
.map<String, Report>((key, value) => MapEntry(key.name, value))),
functions: Map.unmodifiable(functionMetrics.map<String, Report>(
Expand All @@ -221,6 +227,12 @@ class LintAnalyzer {
return LintFileReport(
path: filePath,
relativePath: relativePath,
file: Report(
location:
nodeLocation(node: internalResult.unit, source: internalResult),
metrics: const [],
declaration: internalResult.unit,
),
classes: const {},
functions: const {},
issues: issues,
Expand Down
43 changes: 43 additions & 0 deletions lib/src/analyzers/lint_analyzer/metrics/models/file_metric.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'package:analyzer/dart/ast/ast.dart';

import '../../models/internal_resolved_unit_result.dart';
import '../../models/scoped_class_declaration.dart';
import '../../models/scoped_function_declaration.dart';
import 'metric.dart';
import 'metric_documentation.dart';
import 'metric_value.dart';
import 'metric_value_level.dart';

/// An interface for metrics that compute a value for a file aka compilation unit node.
abstract class FileMetric<T extends num> extends Metric<T> {
/// Initialize a newly created [FileMetric].
const FileMetric({
required String id,
required MetricDocumentation documentation,
required T? threshold,
required MetricValueLevel Function(T, T?) levelComputer,
}) : super(
id: id,
documentation: documentation,
threshold: threshold,
levelComputer: levelComputer,
);

@override
bool supports(
AstNode node,
Iterable<ScopedClassDeclaration> classDeclarations,
Iterable<ScopedFunctionDeclaration> functionDeclarations,
InternalResolvedUnitResult source,
Iterable<MetricValue> otherMetricsValues,
) =>
node is CompilationUnit;

@override
String? nodeType(
AstNode node,
Iterable<ScopedClassDeclaration> classDeclarations,
Iterable<ScopedFunctionDeclaration> functionDeclarations,
) =>
null;
}
5 changes: 4 additions & 1 deletion lib/src/analyzers/lint_analyzer/models/entity_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ class EntityType {
/// The entity representing a class, mixin or extension.
static const classEntity = EntityType._('class');

/// The entity representing a whole file.
static const fileEntity = EntityType._('file');

/// The entity representing a class method or constructor, function, getter or setter.
static const methodEntity = EntityType._('method');

/// A list containing all of the enum values that are defined.
static const values = [classEntity, methodEntity];
static const values = [classEntity, fileEntity, methodEntity];

final String _value;

Expand Down
4 changes: 4 additions & 0 deletions lib/src/analyzers/lint_analyzer/models/lint_file_report.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class LintFileReport implements FileReport {
@override
final String relativePath;

/// The metrics report about the target file.
final Report file;

/// The all classes reports in the target file.
final Map<String, Report> classes;

Expand All @@ -30,6 +33,7 @@ class LintFileReport implements FileReport {
const LintFileReport({
required this.path,
required this.relativePath,
required this.file,
required this.classes,
required this.functions,
required this.issues,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class LintCodeClimateReporter
record.relativePath,
),
..._reportsToCodeClimate(
[...record.functions.values, ...record.classes.values],
[record.file, ...record.classes.values, ...record.functions.values],
record.relativePath,
),
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ class LintConsoleReporter

for (final file in records) {
final lines = [
..._reportMetrics('', file.file),
..._reportIssues([...file.issues, ...file.antiPatternCases]),
..._reportMetrics({...file.classes, ...file.functions}),
..._reportEntityMetrics({...file.classes, ...file.functions}),
];

if (lines.isNotEmpty) {
Expand All @@ -50,29 +51,28 @@ class LintConsoleReporter
a.location.start.offset.compareTo(b.location.start.offset)))
.map(_helper.getIssueMessage);

Iterable<String> _reportMetrics(Map<String, Report> reports) =>
Iterable<String> _reportEntityMetrics(Map<String, Report> reports) =>
(reports.entries.toList()
..sort((a, b) => a.value.location.start.offset
.compareTo(b.value.location.start.offset)))
.expand((entry) {
final source = entry.key;
final report = entry.value;
.expand((entry) => _reportMetrics(entry.key, entry.value));

final reportLevel = report.metricsLevel;
if (reportAll || isReportLevel(reportLevel)) {
final violations = [
for (final metric in report.metrics)
if (reportAll || _isNeedToReport(metric))
_helper.getMetricReport(metric),
];
Iterable<String> _reportMetrics(String source, Report report) {
final reportLevel = report.metricsLevel;
if (reportAll || isReportLevel(reportLevel)) {
final violations = [
for (final metric in report.metrics)
if (reportAll || _isNeedToReport(metric))
_helper.getMetricReport(metric),
];

return [
_helper.getMetricMessage(reportLevel, source, violations),
];
}
return [
_helper.getMetricMessage(reportLevel, source, violations),
];
}

return [];
});
return [];
}

bool _isNeedToReport(MetricValue metric) =>
metric.level > MetricValueLevel.none;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ class LintConsoleReporterHelper {
) {
final color = _colorPens[violationLevel];
if (color != null) {
final normalizedLabel = _normalize(
final normalizedLabel = color(_normalize(
violationLevel != MetricValueLevel.none
? violationLevel.toString().capitalize()
: '',
);
));

return '${color(normalizedLabel)}$source - ${violations.join(', ')}';
return '$normalizedLabel${source.isNotEmpty ? '$source - ' : ''}${violations.join(', ')}';
}

throw StateError('Unexpected violation level.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class LintJsonReporter

Map<String, Object> _lintFileReportToJson(LintFileReport report) => {
'path': report.relativePath,
'fileMetrics': _metricValuesToJson(report.file.metrics),
'classes': _reportToJson(report.classes),
'functions': _reportToJson(report.functions),
'issues': _issueToJson(report.issues),
Expand Down
4 changes: 4 additions & 0 deletions test/src/analyzers/lint_analyzer/lint_analyzer_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/report.dart
import 'package:path/path.dart' as p;
import 'package:test/test.dart';

import '../../../stubs_builders.dart';

void main() {
group(
'LintAnalyzer',
Expand Down Expand Up @@ -170,6 +172,7 @@ void main() {
LintFileReport(
path: '/home/dev/project/bin/example.dart',
relativePath: 'bin/example.dart',
file: buildReportStub(),
classes: Map.unmodifiable(<String, Report>{}),
functions: Map.unmodifiable(<String, Report>{}),
issues: const [],
Expand All @@ -178,6 +181,7 @@ void main() {
LintFileReport(
path: '/home/dev/project/lib/example.dart',
relativePath: 'lib/example.dart',
file: buildReportStub(),
classes: Map.unmodifiable(<String, Report>{}),
functions: Map.unmodifiable(<String, Report>{}),
issues: const [],
Expand Down
2 changes: 1 addition & 1 deletion test/src/analyzers/lint_analyzer/models/report_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ void main() {
late Report report;

setUp(() {
report = buildRecordStub(metrics: [
report = buildReportStub(metrics: [
buildMetricValueStub<int>(
id: metric1Id,
value: 10,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import 'dart:io';

import 'package:dart_code_metrics/src/analyzers/lint_analyzer/reporters/reporters_list/code_climate/lint_code_climate_reporter.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

import '../report_example.dart';

class IOSinkMock extends Mock implements IOSink {}

void main() {
group('LintCodeClimateReporter.report report about', () {
late IOSinkMock output; // ignore: close_sinks
late IOSinkMock gitlabOutput; // ignore: close_sinks

late LintCodeClimateReporter _reporter;
late LintCodeClimateReporter _gitlabReporter;

setUp(() {
output = IOSinkMock();
gitlabOutput = IOSinkMock();

_reporter = LintCodeClimateReporter(output);
_gitlabReporter =
LintCodeClimateReporter(gitlabOutput, gitlabCompatible: true);
});

test('empty report', () async {
await _reporter.report([]);
await _gitlabReporter.report([]);

verifyNever(() => output.writeln(any()));
verifyNever(() => gitlabOutput.writeln(any()));
});

test('complex report', () async {
await _reporter.report(testReport);
await _gitlabReporter.report(testReport);

final captured = verify(
() => output.writeln(captureAny()),
).captured.cast<String>();
final gitlabCaptured = verify(
() => gitlabOutput.writeln(captureAny()),
).captured.cast<String>();

expect(
captured,
equals(
[
'{"type":"issue","check_name":"file-metric-id","description":"metric comment","categories":["Complexity"],"location":{"path":"test/resources/abstract_class.dart","positions":{"begin":{"line":0,"column":0},"end":{"line":0,"column":12}}},"severity":"info","fingerprint":"7b9ff7e7828edd7e91c3e9ef91e6dcc6"}\x00',
'{"type":"issue","check_name":"id","description":"metric comment","categories":["Complexity"],"location":{"path":"test/resources/abstract_class.dart","positions":{"begin":{"line":0,"column":0},"end":{"line":0,"column":16}}},"severity":"info","fingerprint":"661469706d480a46dfeea856182f339f"}\x00',
'{"type":"issue","check_name":"id","description":"simple message","categories":["Bug Risk"],"location":{"path":"test/resources/class_with_factory_constructors.dart","positions":{"begin":{"line":0,"column":0},"end":{"line":0,"column":20}}},"severity":"critical","fingerprint":"f25e877a3578c5d4433dfe131b9f98d0"}\x00',
'{"type":"issue","check_name":"designId","description":"simple design message","categories":["Style"],"location":{"path":"test/resources/class_with_factory_constructors.dart","positions":{"begin":{"line":0,"column":0},"end":{"line":0,"column":20}}},"severity":"minor","fingerprint":"4eb25898669ab2a1c20db5d70e2edfb8"}\x00',
'{"type":"issue","check_name":"id","description":"metric comment","categories":["Complexity"],"location":{"path":"test/resources/class_with_factory_constructors.dart","positions":{"begin":{"line":0,"column":0},"end":{"line":0,"column":20}}},"severity":"info","fingerprint":"b1abfce3f198adb690f6d40fc2aea6a5"}\x00',
],
),
);

// ignore_for_file: missing_whitespace_between_adjacent_strings
expect(
gitlabCaptured.single,
equals(
'['
'{"type":"issue","check_name":"file-metric-id","description":"metric comment","categories":["Complexity"],"location":{"path":"test/resources/abstract_class.dart","positions":{"begin":{"line":0,"column":0},"end":{"line":0,"column":12}}},"severity":"info","fingerprint":"7b9ff7e7828edd7e91c3e9ef91e6dcc6"},'
'{"type":"issue","check_name":"id","description":"metric comment","categories":["Complexity"],"location":{"path":"test/resources/abstract_class.dart","positions":{"begin":{"line":0,"column":0},"end":{"line":0,"column":16}}},"severity":"info","fingerprint":"661469706d480a46dfeea856182f339f"},'
'{"type":"issue","check_name":"id","description":"simple message","categories":["Bug Risk"],"location":{"path":"test/resources/class_with_factory_constructors.dart","positions":{"begin":{"line":0,"column":0},"end":{"line":0,"column":20}}},"severity":"critical","fingerprint":"f25e877a3578c5d4433dfe131b9f98d0"},'
'{"type":"issue","check_name":"designId","description":"simple design message","categories":["Style"],"location":{"path":"test/resources/class_with_factory_constructors.dart","positions":{"begin":{"line":0,"column":0},"end":{"line":0,"column":20}}},"severity":"minor","fingerprint":"4eb25898669ab2a1c20db5d70e2edfb8"},'
'{"type":"issue","check_name":"id","description":"metric comment","categories":["Complexity"],"location":{"path":"test/resources/class_with_factory_constructors.dart","positions":{"begin":{"line":0,"column":0},"end":{"line":0,"column":20}}},"severity":"info","fingerprint":"b1abfce3f198adb690f6d40fc2aea6a5"}'
']',
),
);
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import 'package:dart_code_metrics/src/analyzers/lint_analyzer/reporters/reporter
import 'package:source_span/source_span.dart';
import 'package:test/test.dart';

import '../../../../../stubs_builders.dart';
import '../../../../../../stubs_builders.dart';

void main() {
group('LintConsoleReporterHelper', () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:dart_code_metrics/src/analyzers/lint_analyzer/reporters/reporter
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

import 'report_example.dart';
import '../report_example.dart';

class IOSinkMock extends Mock implements IOSink {}

Expand Down Expand Up @@ -52,6 +52,7 @@ void main() {
equals(
[
'test/resources/abstract_class.dart:',
'\x1B[38;5;11mWarning \x1B[0mmetric1: \x1B[38;5;11m100\x1B[0m',
'\x1B[38;5;9mAlarm \x1B[0mclass.constructor - metric2: \x1B[38;5;9m10\x1B[0m',
'',
'test/resources/class_with_factory_constructors.dart:',
Expand All @@ -67,11 +68,13 @@ void main() {
equals(
[
'test/resources/abstract_class.dart:',
'\x1B[38;5;11mWarning \x1B[0mmetric1: \x1B[38;5;11m100\x1B[0m',
'\x1B[38;5;7m \x1B[0mclass - metric1: \x1B[38;5;7m0\x1B[0m',
'\x1B[38;5;9mAlarm \x1B[0mclass.constructor - metric2: \x1B[38;5;9m10\x1B[0m',
'\x1B[38;5;7m \x1B[0mclass.method - metric3: \x1B[38;5;7m1\x1B[0m',
'',
'test/resources/class_with_factory_constructors.dart:',
'\x1B[38;5;7m \x1B[0mmetric1: \x1B[38;5;7m0\x1B[0m, metric2: \x1B[38;5;7m1\x1B[0m',
'\x1B[38;5;11mWarning \x1B[0msimple message : 0:0 : id',
'\x1B[38;5;4mStyle \x1B[0msimple design message : 0:0 : designId',
'\x1B[38;5;11mWarning \x1B[0mfunction - metric4: \x1B[38;5;11m5\x1B[0m',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ void main() {
() {
expect(
renderDetailsTooltip(
buildRecordStub(
buildReportStub(
metrics: [
buildMetricValueStub(
id: 'metric',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ void main() {

final recordFirst = (report['records'] as Iterable).first as Map;
expect(recordFirst, containsPair('path', src1Path));
expect(
recordFirst['fileMetrics'],
equals([
{
'metricsId': 'file-metric-id',
'value': 100,
'level': 'warning',
'comment': 'metric comment',
'context': <String>[],
},
]),
);

final recordLast = (report['records'] as Iterable).last as Map;
expect(recordLast, containsPair('path', src2Path));
Expand Down
Loading