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

Commit c80810d

Browse files
authored
refactor: maintability index (#452)
* refactor: remove unused code * feat: provide otherMetricsValues in computeImplementation * refactor: implement Maintainability Index metric * chore: setup mi metric
1 parent f3a0cf9 commit c80810d

21 files changed

+242
-153
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ Usage: metrics [arguments...] <directories>
157157
--number-of-parameters=<4> Number of Parameters threshold.
158158
--source-lines-of-code=<50> Source lines of Code threshold.
159159
--weight-of-class=<0.33> Weight Of a Class threshold.
160+
--maintainability-index=<50> Maintainability Index threshold.
160161
161162
162163
--root-folder=<./> Root folder.

analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ dart_code_metrics:
1717
- long-parameter-list
1818
metrics:
1919
cyclomatic-complexity: 20
20+
maintainability-index: 50
2021
maximum-nesting: 5
2122
number-of-parameters: 5
2223
metrics-exclude:

lib/src/analyzers/lint_analyzer/lint_analyzer.dart

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import 'dart:io';
2-
import 'dart:math';
32

43
import 'package:analyzer/dart/analysis/results.dart';
54
import 'package:analyzer/dart/ast/ast.dart';
6-
import 'package:collection/collection.dart';
75
import 'package:path/path.dart';
86

97
import '../../config_builder/config_builder.dart';
@@ -15,14 +13,8 @@ import '../../utils/file_utils.dart';
1513
import '../../utils/node_utils.dart';
1614
import 'lint_analysis_config.dart';
1715
import 'lint_config.dart';
18-
import 'metrics/metrics_list/cyclomatic_complexity/cyclomatic_complexity_metric.dart';
19-
import 'metrics/metrics_list/halstead_volume/halstead_volume_metric.dart';
20-
import 'metrics/metrics_list/source_lines_of_code/source_lines_of_code_metric.dart';
21-
import 'metrics/models/metric_documentation.dart';
2216
import 'metrics/models/metric_value.dart';
23-
import 'metrics/models/metric_value_level.dart';
2417
import 'metrics/scope_visitor.dart';
25-
import 'models/entity_type.dart';
2618
import 'models/internal_resolved_unit_result.dart';
2719
import 'models/issue.dart';
2820
import 'models/lint_file_report.dart';
@@ -322,50 +314,6 @@ class LintAnalyzer {
322314
}
323315
}
324316

325-
final cyclomatic = metrics.firstWhereOrNull(
326-
(value) => value.metricsId == CyclomaticComplexityMetric.metricId,
327-
);
328-
329-
final halsteadVolume = metrics.firstWhereOrNull(
330-
(value) => value.metricsId == HalsteadVolumeMetric.metricId,
331-
);
332-
333-
final sourceLinesOfCode = metrics.firstWhereOrNull(
334-
(value) => value.metricsId == SourceLinesOfCodeMetric.metricId,
335-
);
336-
337-
if (cyclomatic != null &&
338-
halsteadVolume != null &&
339-
sourceLinesOfCode != null) {
340-
final maintainabilityIndex = max(
341-
0,
342-
(171 -
343-
5.2 * log(max(1, halsteadVolume.value)) -
344-
cyclomatic.value * 0.23 -
345-
16.2 * log(max(1, sourceLinesOfCode.value))) *
346-
100 /
347-
171,
348-
).toDouble();
349-
350-
metrics.add(
351-
MetricValue<double>(
352-
metricsId: 'maintainability-index',
353-
documentation: const MetricDocumentation(
354-
name: 'Maintainability index',
355-
shortName: '',
356-
brief: '',
357-
measuredType: EntityType.classEntity,
358-
recomendedThreshold: 0,
359-
),
360-
value: maintainabilityIndex,
361-
level: _maintainabilityIndexViolationLevel(
362-
maintainabilityIndex,
363-
),
364-
comment: '',
365-
),
366-
);
367-
}
368-
369317
functionRecords[function] = Report(
370318
location: nodeLocation(node: function.declaration, source: source),
371319
declaration: function.declaration,
@@ -376,18 +324,6 @@ class LintAnalyzer {
376324
return functionRecords;
377325
}
378326

379-
MetricValueLevel _maintainabilityIndexViolationLevel(double index) {
380-
if (index < 10) {
381-
return MetricValueLevel.alarm;
382-
} else if (index < 20) {
383-
return MetricValueLevel.warning;
384-
} else if (index < 40) {
385-
return MetricValueLevel.noted;
386-
}
387-
388-
return MetricValueLevel.none;
389-
}
390-
391327
bool _isSupported(AnalysisResult result) =>
392328
result.path.endsWith('.dart') && !result.path.endsWith('.g.dart');
393329
}

lib/src/analyzers/lint_analyzer/metrics/metrics_factory.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import '../models/entity_type.dart';
22
import 'metrics_list/cyclomatic_complexity/cyclomatic_complexity_metric.dart';
33
import 'metrics_list/halstead_volume/halstead_volume_metric.dart';
44
import 'metrics_list/lines_of_code_metric.dart';
5+
import 'metrics_list/maintainability_index_metric.dart';
56
import 'metrics_list/maximum_nesting_level/maximum_nesting_level_metric.dart';
67
import 'metrics_list/number_of_methods_metric.dart';
78
import 'metrics_list/number_of_parameters_metric.dart';
@@ -24,6 +25,10 @@ final _implementedMetrics = <String, Metric Function(Map<String, Object>)>{
2425
SourceLinesOfCodeMetric.metricId: (config) =>
2526
SourceLinesOfCodeMetric(config: config),
2627
WeightOfClassMetric.metricId: (config) => WeightOfClassMetric(config: config),
28+
// Complex metrics:
29+
// Depend on CyclomaticComplexityMetric, HalsteadVolumeMetric and SourceLinesOfCodeMetric metrics
30+
MaintainabilityIndexMetric.metricId: (config) =>
31+
MaintainabilityIndexMetric(config: config),
2732
};
2833

2934
Iterable<Metric> getMetrics({

lib/src/analyzers/lint_analyzer/metrics/metrics_list/cyclomatic_complexity/cyclomatic_complexity_metric.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import '../../metric_utils.dart';
1313
import '../../models/function_metric.dart';
1414
import '../../models/metric_computation_result.dart';
1515
import '../../models/metric_documentation.dart';
16+
import '../../models/metric_value.dart';
1617
import 'cyclomatic_complexity_flow_visitor.dart';
1718

1819
const _documentation = MetricDocumentation(
@@ -45,6 +46,7 @@ class CyclomaticComplexityMetric extends FunctionMetric<int> {
4546
Iterable<ScopedClassDeclaration> classDeclarations,
4647
Iterable<ScopedFunctionDeclaration> functionDeclarations,
4748
InternalResolvedUnitResult source,
49+
Iterable<MetricValue<num>> otherMetricsValues,
4850
) {
4951
final visitor = CyclomaticComplexityFlowVisitor();
5052
node.visitChildren(visitor);

lib/src/analyzers/lint_analyzer/metrics/metrics_list/halstead_volume/halstead_volume_metric.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import '../../metric_utils.dart';
1010
import '../../models/function_metric.dart';
1111
import '../../models/metric_computation_result.dart';
1212
import '../../models/metric_documentation.dart';
13+
import '../../models/metric_value.dart';
1314
import 'halstead_volume_ast_visitor.dart';
1415

1516
const _documentation = MetricDocumentation(
@@ -46,6 +47,7 @@ class HalsteadVolumeMetric extends FunctionMetric<double> {
4647
Iterable<ScopedClassDeclaration> classDeclarations,
4748
Iterable<ScopedFunctionDeclaration> functionDeclarations,
4849
InternalResolvedUnitResult source,
50+
Iterable<MetricValue<num>> otherMetricsValues,
4951
) {
5052
final visitor = HalsteadVolumeAstVisitor();
5153
node.visitChildren(visitor);

lib/src/analyzers/lint_analyzer/metrics/metrics_list/lines_of_code_metric.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import '../metric_utils.dart';
88
import '../models/function_metric.dart';
99
import '../models/metric_computation_result.dart';
1010
import '../models/metric_documentation.dart';
11+
import '../models/metric_value.dart';
1112

1213
const _documentation = MetricDocumentation(
1314
name: 'Lines of Code',
@@ -40,6 +41,7 @@ class LinesOfCodeMetric extends FunctionMetric<int> {
4041
Iterable<ScopedClassDeclaration> classDeclarations,
4142
Iterable<ScopedFunctionDeclaration> functionDeclarations,
4243
InternalResolvedUnitResult source,
44+
Iterable<MetricValue<num>> otherMetricsValues,
4345
) =>
4446
MetricComputationResult(
4547
value: 1 +
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import 'dart:math';
2+
3+
import 'package:analyzer/dart/ast/ast.dart';
4+
5+
import '../../models/entity_type.dart';
6+
import '../../models/internal_resolved_unit_result.dart';
7+
import '../../models/scoped_class_declaration.dart';
8+
import '../../models/scoped_function_declaration.dart';
9+
import '../metric_utils.dart';
10+
import '../models/function_metric.dart';
11+
import '../models/metric_computation_result.dart';
12+
import '../models/metric_documentation.dart';
13+
import '../models/metric_value.dart';
14+
import 'cyclomatic_complexity/cyclomatic_complexity_metric.dart';
15+
import 'halstead_volume/halstead_volume_metric.dart';
16+
import 'source_lines_of_code/source_lines_of_code_metric.dart';
17+
18+
const _documentation = MetricDocumentation(
19+
name: 'Maintainability Index',
20+
shortName: 'MI',
21+
brief:
22+
'Maintainability Index is a software metric which measures how maintainable (easy to support and change) the source code is.',
23+
measuredType: EntityType.methodEntity,
24+
recomendedThreshold: 50,
25+
);
26+
27+
/// Maintainability Index (MI)
28+
///
29+
/// Maintainability Index is a software metric which measures how maintainable
30+
/// (easy to support and change) the source code is.
31+
class MaintainabilityIndexMetric extends FunctionMetric<int> {
32+
static const String metricId = 'maintainability-index';
33+
34+
MaintainabilityIndexMetric({Map<String, Object> config = const {}})
35+
: super(
36+
id: metricId,
37+
documentation: _documentation,
38+
threshold: readNullableThreshold<int>(config, metricId),
39+
levelComputer: invertValueLevel,
40+
);
41+
42+
@override
43+
bool supports(
44+
Declaration node,
45+
Iterable<ScopedClassDeclaration> classDeclarations,
46+
Iterable<ScopedFunctionDeclaration> functionDeclarations,
47+
InternalResolvedUnitResult source,
48+
Iterable<MetricValue<num>> otherMetricsValues,
49+
) =>
50+
super.supports(
51+
node,
52+
classDeclarations,
53+
functionDeclarations,
54+
source,
55+
otherMetricsValues,
56+
) &&
57+
[
58+
CyclomaticComplexityMetric.metricId,
59+
HalsteadVolumeMetric.metricId,
60+
SourceLinesOfCodeMetric.metricId,
61+
].every((metricId) =>
62+
otherMetricsValues.any((value) => value.metricsId == metricId));
63+
64+
@override
65+
MetricComputationResult<int> computeImplementation(
66+
Declaration node,
67+
Iterable<ScopedClassDeclaration> classDeclarations,
68+
Iterable<ScopedFunctionDeclaration> functionDeclarations,
69+
InternalResolvedUnitResult source,
70+
Iterable<MetricValue<num>> otherMetricsValues,
71+
) {
72+
final halVol = otherMetricsValues.firstWhere(
73+
(value) => value.metricsId == HalsteadVolumeMetric.metricId,
74+
);
75+
76+
final cyclomatic = otherMetricsValues.firstWhere(
77+
(value) => value.metricsId == CyclomaticComplexityMetric.metricId,
78+
);
79+
80+
final sloc = otherMetricsValues.firstWhere(
81+
(value) => value.metricsId == SourceLinesOfCodeMetric.metricId,
82+
);
83+
84+
final halVolScale = log(max(1, halVol.value));
85+
final cycloScale = cyclomatic.value;
86+
final slocScale = log(max(1, sloc.value));
87+
88+
final maintainabilityIndex =
89+
(171 - halVolScale * 5.2 - cycloScale * 0.23 - slocScale * 16.2) / 171;
90+
91+
return MetricComputationResult(
92+
value: (maintainabilityIndex * 100).clamp(0, 100).ceil(),
93+
);
94+
}
95+
96+
@override
97+
String commentMessage(String nodeType, int value, int? threshold) {
98+
final exceeds = threshold != null && value > threshold
99+
? ', exceeds the minimum of $threshold allowed'
100+
: '';
101+
final index = '$value maintability index';
102+
103+
return 'This $nodeType has $index$exceeds.';
104+
}
105+
}

lib/src/analyzers/lint_analyzer/metrics/metrics_list/maximum_nesting_level/maximum_nesting_level_metric.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import '../../metric_utils.dart';
1111
import '../../models/function_metric.dart';
1212
import '../../models/metric_computation_result.dart';
1313
import '../../models/metric_documentation.dart';
14+
import '../../models/metric_value.dart';
1415
import 'nesting_level_visitor.dart';
1516

1617
const _documentation = MetricDocumentation(
@@ -44,6 +45,7 @@ class MaximumNestingLevelMetric extends FunctionMetric<int> {
4445
Iterable<ScopedClassDeclaration> classDeclarations,
4546
Iterable<ScopedFunctionDeclaration> functionDeclarations,
4647
InternalResolvedUnitResult source,
48+
Iterable<MetricValue<num>> otherMetricsValues,
4749
) {
4850
final visitor = NestingLevelVisitor(node);
4951
node.visitChildren(visitor);

lib/src/analyzers/lint_analyzer/metrics/metrics_list/number_of_methods_metric.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import '../metric_utils.dart';
1010
import '../models/class_metric.dart';
1111
import '../models/metric_computation_result.dart';
1212
import '../models/metric_documentation.dart';
13+
import '../models/metric_value.dart';
1314
import '../scope_utils.dart';
1415

1516
const _documentation = MetricDocumentation(
@@ -41,6 +42,7 @@ class NumberOfMethodsMetric extends ClassMetric<int> {
4142
Iterable<ScopedClassDeclaration> classDeclarations,
4243
Iterable<ScopedFunctionDeclaration> functionDeclarations,
4344
InternalResolvedUnitResult source,
45+
Iterable<MetricValue<num>> otherMetricsValues,
4446
) {
4547
final methods = classMethods(node, functionDeclarations);
4648

0 commit comments

Comments
 (0)