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

Commit

Permalink
refactor: maintability index (#452)
Browse files Browse the repository at this point in the history
* refactor: remove unused code

* feat: provide otherMetricsValues in computeImplementation

* refactor: implement Maintainability Index metric

* chore: setup mi metric
  • Loading branch information
dkrutskikh authored Sep 12, 2021
1 parent f3a0cf9 commit c80810d
Show file tree
Hide file tree
Showing 21 changed files with 242 additions and 153 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ Usage: metrics [arguments...] <directories>
--number-of-parameters=<4> Number of Parameters threshold.
--source-lines-of-code=<50> Source lines of Code threshold.
--weight-of-class=<0.33> Weight Of a Class threshold.
--maintainability-index=<50> Maintainability Index threshold.
--root-folder=<./> Root folder.
Expand Down
1 change: 1 addition & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dart_code_metrics:
- long-parameter-list
metrics:
cyclomatic-complexity: 20
maintainability-index: 50
maximum-nesting: 5
number-of-parameters: 5
metrics-exclude:
Expand Down
64 changes: 0 additions & 64 deletions lib/src/analyzers/lint_analyzer/lint_analyzer.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import 'dart:io';
import 'dart:math';

import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:collection/collection.dart';
import 'package:path/path.dart';

import '../../config_builder/config_builder.dart';
Expand All @@ -15,14 +13,8 @@ import '../../utils/file_utils.dart';
import '../../utils/node_utils.dart';
import 'lint_analysis_config.dart';
import 'lint_config.dart';
import 'metrics/metrics_list/cyclomatic_complexity/cyclomatic_complexity_metric.dart';
import 'metrics/metrics_list/halstead_volume/halstead_volume_metric.dart';
import 'metrics/metrics_list/source_lines_of_code/source_lines_of_code_metric.dart';
import 'metrics/models/metric_documentation.dart';
import 'metrics/models/metric_value.dart';
import 'metrics/models/metric_value_level.dart';
import 'metrics/scope_visitor.dart';
import 'models/entity_type.dart';
import 'models/internal_resolved_unit_result.dart';
import 'models/issue.dart';
import 'models/lint_file_report.dart';
Expand Down Expand Up @@ -322,50 +314,6 @@ class LintAnalyzer {
}
}

final cyclomatic = metrics.firstWhereOrNull(
(value) => value.metricsId == CyclomaticComplexityMetric.metricId,
);

final halsteadVolume = metrics.firstWhereOrNull(
(value) => value.metricsId == HalsteadVolumeMetric.metricId,
);

final sourceLinesOfCode = metrics.firstWhereOrNull(
(value) => value.metricsId == SourceLinesOfCodeMetric.metricId,
);

if (cyclomatic != null &&
halsteadVolume != null &&
sourceLinesOfCode != null) {
final maintainabilityIndex = max(
0,
(171 -
5.2 * log(max(1, halsteadVolume.value)) -
cyclomatic.value * 0.23 -
16.2 * log(max(1, sourceLinesOfCode.value))) *
100 /
171,
).toDouble();

metrics.add(
MetricValue<double>(
metricsId: 'maintainability-index',
documentation: const MetricDocumentation(
name: 'Maintainability index',
shortName: '',
brief: '',
measuredType: EntityType.classEntity,
recomendedThreshold: 0,
),
value: maintainabilityIndex,
level: _maintainabilityIndexViolationLevel(
maintainabilityIndex,
),
comment: '',
),
);
}

functionRecords[function] = Report(
location: nodeLocation(node: function.declaration, source: source),
declaration: function.declaration,
Expand All @@ -376,18 +324,6 @@ class LintAnalyzer {
return functionRecords;
}

MetricValueLevel _maintainabilityIndexViolationLevel(double index) {
if (index < 10) {
return MetricValueLevel.alarm;
} else if (index < 20) {
return MetricValueLevel.warning;
} else if (index < 40) {
return MetricValueLevel.noted;
}

return MetricValueLevel.none;
}

bool _isSupported(AnalysisResult result) =>
result.path.endsWith('.dart') && !result.path.endsWith('.g.dart');
}
5 changes: 5 additions & 0 deletions lib/src/analyzers/lint_analyzer/metrics/metrics_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import '../models/entity_type.dart';
import 'metrics_list/cyclomatic_complexity/cyclomatic_complexity_metric.dart';
import 'metrics_list/halstead_volume/halstead_volume_metric.dart';
import 'metrics_list/lines_of_code_metric.dart';
import 'metrics_list/maintainability_index_metric.dart';
import 'metrics_list/maximum_nesting_level/maximum_nesting_level_metric.dart';
import 'metrics_list/number_of_methods_metric.dart';
import 'metrics_list/number_of_parameters_metric.dart';
Expand All @@ -24,6 +25,10 @@ final _implementedMetrics = <String, Metric Function(Map<String, Object>)>{
SourceLinesOfCodeMetric.metricId: (config) =>
SourceLinesOfCodeMetric(config: config),
WeightOfClassMetric.metricId: (config) => WeightOfClassMetric(config: config),
// Complex metrics:
// Depend on CyclomaticComplexityMetric, HalsteadVolumeMetric and SourceLinesOfCodeMetric metrics
MaintainabilityIndexMetric.metricId: (config) =>
MaintainabilityIndexMetric(config: config),
};

Iterable<Metric> getMetrics({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import '../../metric_utils.dart';
import '../../models/function_metric.dart';
import '../../models/metric_computation_result.dart';
import '../../models/metric_documentation.dart';
import '../../models/metric_value.dart';
import 'cyclomatic_complexity_flow_visitor.dart';

const _documentation = MetricDocumentation(
Expand Down Expand Up @@ -45,6 +46,7 @@ class CyclomaticComplexityMetric extends FunctionMetric<int> {
Iterable<ScopedClassDeclaration> classDeclarations,
Iterable<ScopedFunctionDeclaration> functionDeclarations,
InternalResolvedUnitResult source,
Iterable<MetricValue<num>> otherMetricsValues,
) {
final visitor = CyclomaticComplexityFlowVisitor();
node.visitChildren(visitor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import '../../metric_utils.dart';
import '../../models/function_metric.dart';
import '../../models/metric_computation_result.dart';
import '../../models/metric_documentation.dart';
import '../../models/metric_value.dart';
import 'halstead_volume_ast_visitor.dart';

const _documentation = MetricDocumentation(
Expand Down Expand Up @@ -46,6 +47,7 @@ class HalsteadVolumeMetric extends FunctionMetric<double> {
Iterable<ScopedClassDeclaration> classDeclarations,
Iterable<ScopedFunctionDeclaration> functionDeclarations,
InternalResolvedUnitResult source,
Iterable<MetricValue<num>> otherMetricsValues,
) {
final visitor = HalsteadVolumeAstVisitor();
node.visitChildren(visitor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import '../metric_utils.dart';
import '../models/function_metric.dart';
import '../models/metric_computation_result.dart';
import '../models/metric_documentation.dart';
import '../models/metric_value.dart';

const _documentation = MetricDocumentation(
name: 'Lines of Code',
Expand Down Expand Up @@ -40,6 +41,7 @@ class LinesOfCodeMetric extends FunctionMetric<int> {
Iterable<ScopedClassDeclaration> classDeclarations,
Iterable<ScopedFunctionDeclaration> functionDeclarations,
InternalResolvedUnitResult source,
Iterable<MetricValue<num>> otherMetricsValues,
) =>
MetricComputationResult(
value: 1 +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import 'dart:math';

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

import '../../models/entity_type.dart';
import '../../models/internal_resolved_unit_result.dart';
import '../../models/scoped_class_declaration.dart';
import '../../models/scoped_function_declaration.dart';
import '../metric_utils.dart';
import '../models/function_metric.dart';
import '../models/metric_computation_result.dart';
import '../models/metric_documentation.dart';
import '../models/metric_value.dart';
import 'cyclomatic_complexity/cyclomatic_complexity_metric.dart';
import 'halstead_volume/halstead_volume_metric.dart';
import 'source_lines_of_code/source_lines_of_code_metric.dart';

const _documentation = MetricDocumentation(
name: 'Maintainability Index',
shortName: 'MI',
brief:
'Maintainability Index is a software metric which measures how maintainable (easy to support and change) the source code is.',
measuredType: EntityType.methodEntity,
recomendedThreshold: 50,
);

/// Maintainability Index (MI)
///
/// Maintainability Index is a software metric which measures how maintainable
/// (easy to support and change) the source code is.
class MaintainabilityIndexMetric extends FunctionMetric<int> {
static const String metricId = 'maintainability-index';

MaintainabilityIndexMetric({Map<String, Object> config = const {}})
: super(
id: metricId,
documentation: _documentation,
threshold: readNullableThreshold<int>(config, metricId),
levelComputer: invertValueLevel,
);

@override
bool supports(
Declaration node,
Iterable<ScopedClassDeclaration> classDeclarations,
Iterable<ScopedFunctionDeclaration> functionDeclarations,
InternalResolvedUnitResult source,
Iterable<MetricValue<num>> otherMetricsValues,
) =>
super.supports(
node,
classDeclarations,
functionDeclarations,
source,
otherMetricsValues,
) &&
[
CyclomaticComplexityMetric.metricId,
HalsteadVolumeMetric.metricId,
SourceLinesOfCodeMetric.metricId,
].every((metricId) =>
otherMetricsValues.any((value) => value.metricsId == metricId));

@override
MetricComputationResult<int> computeImplementation(
Declaration node,
Iterable<ScopedClassDeclaration> classDeclarations,
Iterable<ScopedFunctionDeclaration> functionDeclarations,
InternalResolvedUnitResult source,
Iterable<MetricValue<num>> otherMetricsValues,
) {
final halVol = otherMetricsValues.firstWhere(
(value) => value.metricsId == HalsteadVolumeMetric.metricId,
);

final cyclomatic = otherMetricsValues.firstWhere(
(value) => value.metricsId == CyclomaticComplexityMetric.metricId,
);

final sloc = otherMetricsValues.firstWhere(
(value) => value.metricsId == SourceLinesOfCodeMetric.metricId,
);

final halVolScale = log(max(1, halVol.value));
final cycloScale = cyclomatic.value;
final slocScale = log(max(1, sloc.value));

final maintainabilityIndex =
(171 - halVolScale * 5.2 - cycloScale * 0.23 - slocScale * 16.2) / 171;

return MetricComputationResult(
value: (maintainabilityIndex * 100).clamp(0, 100).ceil(),
);
}

@override
String commentMessage(String nodeType, int value, int? threshold) {
final exceeds = threshold != null && value > threshold
? ', exceeds the minimum of $threshold allowed'
: '';
final index = '$value maintability index';

return 'This $nodeType has $index$exceeds.';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import '../../metric_utils.dart';
import '../../models/function_metric.dart';
import '../../models/metric_computation_result.dart';
import '../../models/metric_documentation.dart';
import '../../models/metric_value.dart';
import 'nesting_level_visitor.dart';

const _documentation = MetricDocumentation(
Expand Down Expand Up @@ -44,6 +45,7 @@ class MaximumNestingLevelMetric extends FunctionMetric<int> {
Iterable<ScopedClassDeclaration> classDeclarations,
Iterable<ScopedFunctionDeclaration> functionDeclarations,
InternalResolvedUnitResult source,
Iterable<MetricValue<num>> otherMetricsValues,
) {
final visitor = NestingLevelVisitor(node);
node.visitChildren(visitor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import '../metric_utils.dart';
import '../models/class_metric.dart';
import '../models/metric_computation_result.dart';
import '../models/metric_documentation.dart';
import '../models/metric_value.dart';
import '../scope_utils.dart';

const _documentation = MetricDocumentation(
Expand Down Expand Up @@ -41,6 +42,7 @@ class NumberOfMethodsMetric extends ClassMetric<int> {
Iterable<ScopedClassDeclaration> classDeclarations,
Iterable<ScopedFunctionDeclaration> functionDeclarations,
InternalResolvedUnitResult source,
Iterable<MetricValue<num>> otherMetricsValues,
) {
final methods = classMethods(node, functionDeclarations);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import '../metric_utils.dart';
import '../models/function_metric.dart';
import '../models/metric_computation_result.dart';
import '../models/metric_documentation.dart';
import '../models/metric_value.dart';

const _documentation = MetricDocumentation(
name: 'Number of Parameters',
Expand Down Expand Up @@ -38,6 +39,7 @@ class NumberOfParametersMetric extends FunctionMetric<int> {
Iterable<ScopedClassDeclaration> classDeclarations,
Iterable<ScopedFunctionDeclaration> functionDeclarations,
InternalResolvedUnitResult source,
Iterable<MetricValue<num>> otherMetricsValues,
) {
int? parametersCount;
if (node is FunctionDeclaration) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import '../../metric_utils.dart';
import '../../models/function_metric.dart';
import '../../models/metric_computation_result.dart';
import '../../models/metric_documentation.dart';
import '../../models/metric_value.dart';
import 'source_code_visitor.dart';

const _documentation = MetricDocumentation(
Expand Down Expand Up @@ -45,6 +46,7 @@ class SourceLinesOfCodeMetric extends FunctionMetric<int> {
Iterable<ScopedClassDeclaration> classDeclarations,
Iterable<ScopedFunctionDeclaration> functionDeclarations,
InternalResolvedUnitResult source,
Iterable<MetricValue<num>> otherMetricsValues,
) {
final visitor = SourceCodeVisitor(source.lineInfo);
node.visitChildren(visitor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class WeightOfClassMetric extends ClassMetric<double> {
Iterable<ScopedClassDeclaration> classDeclarations,
Iterable<ScopedFunctionDeclaration> functionDeclarations,
InternalResolvedUnitResult source,
Iterable<MetricValue<num>> otherMetricsValues,
) {
final totalPublicMethods = classMethods(node, functionDeclarations)
.where(_isPublicMethod)
Expand Down
2 changes: 2 additions & 0 deletions lib/src/analyzers/lint_analyzer/metrics/models/metric.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ abstract class Metric<T extends num> {
classDeclarations,
functionDeclarations,
source,
otherMetricsValues,
);

final type = nodeType(node, classDeclarations, functionDeclarations) ?? '';
Expand All @@ -78,6 +79,7 @@ abstract class Metric<T extends num> {
Iterable<ScopedClassDeclaration> classDeclarations,
Iterable<ScopedFunctionDeclaration> functionDeclarations,
InternalResolvedUnitResult source,
Iterable<MetricValue<num>> otherMetricsValues,
);

/// Returns the message for the user containing information about the computed value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import '../../../../metrics/models/metric_value.dart';
class FunctionMetricsReport {
final MetricValue<int> cyclomaticComplexity;
final MetricValue<int> sourceLinesOfCode;
final MetricValue<double> maintainabilityIndex;
final MetricValue<int> maintainabilityIndex;
final MetricValue<int> argumentsCount;
final MetricValue<int> maximumNestingLevel;

Expand Down
Loading

0 comments on commit c80810d

Please sign in to comment.