diff --git a/CHANGELOG.md b/CHANGELOG.md index c914039970..386db367cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # Changelog +## 1.1.0 +- Added support for CodeClimate + ## 1.0.0 - Initial release diff --git a/bin/metrics.dart b/bin/metrics.dart index 2929114310..0d63431e61 100644 --- a/bin/metrics.dart +++ b/bin/metrics.dart @@ -22,7 +22,7 @@ void main(List args) { abbr: 'r', help: 'The format of the output of the analysis', valueHelp: 'console', - allowed: ['console', 'json', 'html'], + allowed: ['console', 'json', 'html', 'codeclimate'], defaultsTo: 'console') ..addOption(cyclomaticComplexityThreshold, help: 'Cyclomatic complexity threshold', valueHelp: '20', defaultsTo: '20') @@ -82,6 +82,9 @@ void main(List args) { case 'html': HtmlReporter(reportConfig: config).report(runner.results()); break; + case 'codeclimate': + CodeClimateReporter(reportConfig: config).report(runner.results()); + break; default: throw ArgumentError.value(arguments[reporterOptionName], reporterOptionName); } diff --git a/lib/reporters.dart b/lib/reporters.dart index cdb3322d88..91d6540c44 100644 --- a/lib/reporters.dart +++ b/lib/reporters.dart @@ -1,4 +1,5 @@ export 'package:metrics/src/reporters/console_reporter.dart'; +export 'package:metrics/src/reporters/code_climate/code_climate_reporter.dart'; export 'package:metrics/src/reporters/html_reporter.dart'; export 'package:metrics/src/reporters/json_reporter.dart'; export 'package:metrics/src/reporters/reporter.dart'; diff --git a/lib/src/reporters/code_climate/code_climate_issue.dart b/lib/src/reporters/code_climate/code_climate_issue.dart new file mode 100644 index 0000000000..22d1eb34ee --- /dev/null +++ b/lib/src/reporters/code_climate/code_climate_issue.dart @@ -0,0 +1,103 @@ +class CodeClimateIssue { + static const String type = 'issue'; + static const Iterable categories = ['Complexity']; + static const int remediation_points = 50000; + + final String check_name; + final String description; + final CodeClimateLocation location; + + CodeClimateIssue._(this.check_name, this.description, this.location); + + factory CodeClimateIssue._create(String name, String desc, int startLine, int endLine, String fileName) { + final position = + CodeClimatePosition(CodeClimateLineColumnPosition(startLine), CodeClimateLineColumnPosition(endLine)); + final location = CodeClimateLocation(fileName, position); + return CodeClimateIssue._(name, desc, location); + } + + factory CodeClimateIssue.linesOfCode( + int startLine, int endLine, String fileName, String functionName, int threshold) { + final desc = + 'Function `$functionName` has ${endLine - startLine} lines of code (exceeds $threshold allowed). Consider refactoring.'; + return CodeClimateIssue._create('linesOfCode', desc, startLine, endLine, fileName); + } + + factory CodeClimateIssue.cyclomaticComplexity( + int startLine, int endLine, int value, String fileName, String functionName, int threshold) { + final desc = + 'Function `$functionName` has a Cyclomatic Complexity of $value (exceeds $threshold allowed). Consider refactoring.'; + return CodeClimateIssue._create('cyclomaticComplexity', desc, startLine, endLine, fileName); + } + + factory CodeClimateIssue.maintainabilityIndex( + int startLine, int endLine, double value, String fileName, String functionName) { + final desc = + 'Function `$functionName` has a Maintainability Index of $value (min 40 allowed). Consider refactoring.'; + return CodeClimateIssue._create('maintainabilityIndex', desc, startLine, endLine, fileName); + } + + Map toJson() { + return { + 'type': type, + 'check_name': check_name, + 'categories': categories, + 'remediation_points': remediation_points, + 'description': description, + 'location': location.toJson(), + }; + } +} + +class CodeClimateIssueContent { + final String body; + + CodeClimateIssueContent(this.body); + + Map toJson() { + return { + 'body': body, + }; + } +} + +class CodeClimateLocation { + final String path; + final CodeClimatePosition positions; + + CodeClimateLocation(this.path, this.positions); + + Map toJson() { + return { + 'path': path, + 'positions': positions.toJson(), + }; + } +} + +class CodeClimatePosition { + final CodeClimateLineColumnPosition begin; + final CodeClimateLineColumnPosition end; + + CodeClimatePosition(this.begin, this.end); + + Map toJson() { + return { + 'begin': begin.toJson(), + 'end': end.toJson(), + }; + } +} + +class CodeClimateLineColumnPosition { + final num line; + + CodeClimateLineColumnPosition(this.line); + + Map toJson() { + return { + 'line': line, + 'column': 1, + }; + } +} diff --git a/lib/src/reporters/code_climate/code_climate_reporter.dart b/lib/src/reporters/code_climate/code_climate_reporter.dart new file mode 100644 index 0000000000..a217360871 --- /dev/null +++ b/lib/src/reporters/code_climate/code_climate_reporter.dart @@ -0,0 +1,50 @@ +import 'dart:convert'; + +import 'package:meta/meta.dart'; +import 'package:metrics/src/models/component_record.dart'; +import 'package:metrics/src/models/config.dart'; +import 'package:metrics/src/models/violation_level.dart'; +import 'package:metrics/src/reporters/code_climate/code_climate_issue.dart'; +import 'package:metrics/src/reporters/reporter.dart'; +import 'package:metrics/src/reporters/utility_selector.dart'; + +class CodeClimateReporter implements Reporter { + final Config reportConfig; + static const String _nullCharacter = '\u0000'; + CodeClimateReporter({@required this.reportConfig}); + + @override + void report(Iterable records) { + if (records?.isEmpty ?? true) { + return; + } + + final data = records.map(_toIssues).expand((r) => r).toList(growable: false); + for (final issue in data) { + print(json.encode(issue) + _nullCharacter); + } + } + + bool _isIssueLevel(ViolationLevel level) => level == ViolationLevel.warning || level == ViolationLevel.alarm; + + List _toIssues(ComponentRecord record) { + final result = []; + for (final key in record.records.keys) { + final func = record.records[key]; + final report = UtilitySelector.functionReport(func, reportConfig); + if (_isIssueLevel(report.linesOfCodeViolationLevel)) { + result.add(CodeClimateIssue.linesOfCode( + func.firstLine, func.lastLine, record.relativePath, key, reportConfig.linesOfCodeWarningLevel)); + } + if (_isIssueLevel(report.cyclomaticComplexityViolationLevel)) { + result.add(CodeClimateIssue.cyclomaticComplexity(func.firstLine, func.lastLine, report.cyclomaticComplexity, + record.relativePath, key, reportConfig.linesOfCodeWarningLevel)); + } + if (_isIssueLevel(report.maintainabilityIndexViolationLevel)) { + result.add(CodeClimateIssue.maintainabilityIndex( + func.firstLine, func.lastLine, report.maintainabilityIndex, record.relativePath, key)); + } + } + return result; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 18192dbd23..b2fd0b7509 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: metrics description: Command line tool which helps to improve code quality -version: 1.0.0 +version: 1.1.0 homepage: https://github.com/wrike/metrics author: Dmitry Krutskih