diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index 01ab124e..ac39f012 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -10,3 +10,5 @@ custom_lint: max_complexity: 4 - number_of_parameters: max_parameters: 2 + - function_lines_of_code: + max_lines: 50 diff --git a/example/test/lines_of_code_test.dart b/example/test/lines_of_code_test.dart new file mode 100644 index 00000000..5f5cfbde --- /dev/null +++ b/example/test/lines_of_code_test.dart @@ -0,0 +1,59 @@ +import 'package:test/test.dart'; + +/// Check number of lines fail +/// +/// `function_lines_of_code: max_lines` +/// expect_lint: function_lines_of_code +void linesOfCode() { + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); + test("addition", () { + expect(1 + 1, equals(2)); + }); +} diff --git a/lib/cyclomatic_complexity/cyclomatic_complexity_metric.dart b/lib/lints/cyclomatic_complexity/cyclomatic_complexity_metric.dart similarity index 84% rename from lib/cyclomatic_complexity/cyclomatic_complexity_metric.dart rename to lib/lints/cyclomatic_complexity/cyclomatic_complexity_metric.dart index c6837a0b..5fa352e4 100644 --- a/lib/cyclomatic_complexity/cyclomatic_complexity_metric.dart +++ b/lib/lints/cyclomatic_complexity/cyclomatic_complexity_metric.dart @@ -1,7 +1,7 @@ import 'package:analyzer/error/listener.dart'; import 'package:custom_lint_builder/custom_lint_builder.dart'; -import 'package:solid_lints/cyclomatic_complexity/models/cyclomatic_complexity_parameters.dart'; -import 'package:solid_lints/cyclomatic_complexity/visitor/cyclomatic_complexity_flow_visitor.dart'; +import 'package:solid_lints/lints/cyclomatic_complexity/models/cyclomatic_complexity_parameters.dart'; +import 'package:solid_lints/lints/cyclomatic_complexity/visitor/cyclomatic_complexity_flow_visitor.dart'; import 'package:solid_lints/models/metric_rule.dart'; /// A Complexity metric checks content of block and detects more easier solution diff --git a/lib/cyclomatic_complexity/models/cyclomatic_complexity_parameters.dart b/lib/lints/cyclomatic_complexity/models/cyclomatic_complexity_parameters.dart similarity index 100% rename from lib/cyclomatic_complexity/models/cyclomatic_complexity_parameters.dart rename to lib/lints/cyclomatic_complexity/models/cyclomatic_complexity_parameters.dart diff --git a/lib/cyclomatic_complexity/visitor/cyclomatic_complexity_flow_visitor.dart b/lib/lints/cyclomatic_complexity/visitor/cyclomatic_complexity_flow_visitor.dart similarity index 99% rename from lib/cyclomatic_complexity/visitor/cyclomatic_complexity_flow_visitor.dart rename to lib/lints/cyclomatic_complexity/visitor/cyclomatic_complexity_flow_visitor.dart index 9eb45463..c101ced4 100644 --- a/lib/cyclomatic_complexity/visitor/cyclomatic_complexity_flow_visitor.dart +++ b/lib/lints/cyclomatic_complexity/visitor/cyclomatic_complexity_flow_visitor.dart @@ -1,4 +1,4 @@ -//MIT License +// MIT License // // Copyright (c) 2020-2021 Dart Code Checker team // diff --git a/lib/lints/function_lines_of_code/function_lines_of_code_metric.dart b/lib/lints/function_lines_of_code/function_lines_of_code_metric.dart new file mode 100644 index 00000000..fc5083ae --- /dev/null +++ b/lib/lints/function_lines_of_code/function_lines_of_code_metric.dart @@ -0,0 +1,40 @@ +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:solid_lints/lints/function_lines_of_code/models/function_lines_of_code_parameters.dart'; +import 'package:solid_lints/lints/function_lines_of_code/visitor/function_lines_of_code_visitor.dart'; +import 'package:solid_lints/models/metric_rule.dart'; + +/// A number of lines metric which checks whether we didn't exceed +/// the maximum allowed number of lines for a file +class FunctionLinesOfCodeMetric extends DartLintRule { + /// The [LintCode] of this lint rule that represents the error if number of + /// parameters reaches the maximum value. + static const lintName = 'function_lines_of_code'; + + /// Configuration for number of parameters metric rule. + final MetricRule config; + + /// Creates a new instance of [FunctionLinesOfCodeMetric]. + FunctionLinesOfCodeMetric(this.config) : super(code: config.lintCode); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + final visitor = FunctionLinesOfCodeVisitor(resolver.lineInfo); + + context.registry.addDeclaration((node) { + node.visitChildren(visitor); + + if (visitor.linesWithCode.length > config.parameters.maxLines) { + reporter.reportErrorForOffset( + code, + node.firstTokenAfterCommentAndMetadata.offset, + node.end, + ); + } + }); + } +} diff --git a/lib/lints/function_lines_of_code/models/function_lines_of_code_parameters.dart b/lib/lints/function_lines_of_code/models/function_lines_of_code_parameters.dart new file mode 100644 index 00000000..d02ce997 --- /dev/null +++ b/lib/lints/function_lines_of_code/models/function_lines_of_code_parameters.dart @@ -0,0 +1,19 @@ +/// A data model class that represents the "source lines of code" input +/// parameters. +class FunctionLinesOfCodeParameters { + /// Maximum number of lines + final int maxLines; + + static const _defaultMaxLines = 200; + + /// Constructor for [FunctionLinesOfCodeParameters] model + const FunctionLinesOfCodeParameters({ + required this.maxLines, + }); + + /// Method for creating from json data + factory FunctionLinesOfCodeParameters.fromJson(Map json) => + FunctionLinesOfCodeParameters( + maxLines: json['max_lines'] as int? ?? _defaultMaxLines, + ); +} diff --git a/lib/lints/function_lines_of_code/visitor/function_lines_of_code_visitor.dart b/lib/lints/function_lines_of_code/visitor/function_lines_of_code_visitor.dart new file mode 100644 index 00000000..d3596c3f --- /dev/null +++ b/lib/lints/function_lines_of_code/visitor/function_lines_of_code_visitor.dart @@ -0,0 +1,69 @@ +// MIT License +// +// Copyright (c) 2020-2021 Dart Code Checker team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:analyzer/source/line_info.dart'; + +/// The AST visitor that will find lines with code. +class FunctionLinesOfCodeVisitor extends RecursiveAstVisitor { + final LineInfo _lineInfo; + + final _linesWithCode = {}; + + /// Returns the array with indices of lines with code. + Iterable get linesWithCode => _linesWithCode; + + /// Creates a new instance of [FunctionLinesOfCodeVisitor]. + FunctionLinesOfCodeVisitor(this._lineInfo); + + @override + void visitBlockFunctionBody(BlockFunctionBody node) { + _collectFunctionBodyData( + node.block.leftBracket.next, + node.block.rightBracket, + ); + super.visitBlockFunctionBody(node); + } + + @override + void visitExpressionFunctionBody(ExpressionFunctionBody node) { + _collectFunctionBodyData( + node.expression.beginToken.previous, + node.expression.endToken.next, + ); + super.visitExpressionFunctionBody(node); + } + + void _collectFunctionBodyData(Token? firstToken, Token? lastToken) { + var token = firstToken; + while (token != lastToken && token != null) { + if (!token.isSynthetic) { + _linesWithCode.add(_lineInfo.getLocation(token.offset).lineNumber); + } + + token = token.next; + } + } +} diff --git a/lib/number_of_parameters/models/number_of_parameters_parameters.dart b/lib/lints/number_of_parameters/models/number_of_parameters_parameters.dart similarity index 100% rename from lib/number_of_parameters/models/number_of_parameters_parameters.dart rename to lib/lints/number_of_parameters/models/number_of_parameters_parameters.dart diff --git a/lib/number_of_parameters/number_of_parameters_metric.dart b/lib/lints/number_of_parameters/number_of_parameters_metric.dart similarity index 93% rename from lib/number_of_parameters/number_of_parameters_metric.dart rename to lib/lints/number_of_parameters/number_of_parameters_metric.dart index 0db538e1..5dc20834 100644 --- a/lib/number_of_parameters/number_of_parameters_metric.dart +++ b/lib/lints/number_of_parameters/number_of_parameters_metric.dart @@ -1,8 +1,8 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/error/listener.dart'; import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:solid_lints/lints/number_of_parameters/models/number_of_parameters_parameters.dart'; import 'package:solid_lints/models/metric_rule.dart'; -import 'package:solid_lints/number_of_parameters/models/number_of_parameters_parameters.dart'; /// A number of parameters metric which checks whether we didn't exceed /// the maximum allowed number of parameters for a function or a method diff --git a/lib/solid_lints.dart b/lib/solid_lints.dart index e3669657..edcc2d66 100644 --- a/lib/solid_lints.dart +++ b/lib/solid_lints.dart @@ -1,11 +1,13 @@ library solid_metrics; import 'package:custom_lint_builder/custom_lint_builder.dart'; -import 'package:solid_lints/cyclomatic_complexity/cyclomatic_complexity_metric.dart'; -import 'package:solid_lints/cyclomatic_complexity/models/cyclomatic_complexity_parameters.dart'; +import 'package:solid_lints/lints/cyclomatic_complexity/cyclomatic_complexity_metric.dart'; +import 'package:solid_lints/lints/cyclomatic_complexity/models/cyclomatic_complexity_parameters.dart'; +import 'package:solid_lints/lints/function_lines_of_code/function_lines_of_code_metric.dart'; +import 'package:solid_lints/lints/function_lines_of_code/models/function_lines_of_code_parameters.dart'; +import 'package:solid_lints/lints/number_of_parameters/models/number_of_parameters_parameters.dart'; +import 'package:solid_lints/lints/number_of_parameters/number_of_parameters_metric.dart'; import 'package:solid_lints/models/metric_rule.dart'; -import 'package:solid_lints/number_of_parameters/models/number_of_parameters_parameters.dart'; -import 'package:solid_lints/number_of_parameters/number_of_parameters_metric.dart'; /// creates plugin PluginBase createPlugin() => _SolidLints(); @@ -42,6 +44,19 @@ class _SolidLints extends PluginBase { rules.add(NumberOfParametersMetric(numberOfParameters)); } + final functionLinesOfCode = MetricRule( + configs: configs, + name: FunctionLinesOfCodeMetric.lintName, + factory: FunctionLinesOfCodeParameters.fromJson, + problemMessage: (value) => '' + 'The maximum allowed number of lines is ${value.maxLines}. ' + 'Try splitting this function into smaller parts.', + ); + + if (functionLinesOfCode.enabled) { + rules.add(FunctionLinesOfCodeMetric(functionLinesOfCode)); + } + return rules; } }