Skip to content
Open
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: 0 additions & 1 deletion lib/analysis_options_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ custom_lint:
# Since we're not using the source-lines-of-code rule, `main()` function in test can
# have high cyclomatic complexity.
# For rationale against splitting up `main()` in tests, see `source-lines-of-code` comments.
# Also, there is a bug in metric calculation: https://github.com/dart-code-checker/dart-code-metrics/issues/663
- cyclomatic_complexity: false

# Late keyword is allowed in tests in order to enable the use of custom mocks and
Expand Down
6 changes: 6 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import 'package:solid_lints/src/lints/avoid_returning_widgets/avoid_returning_wi
import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart';
import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/fixes/avoid_unnecessary_type_assertions_fix.dart';
import 'package:solid_lints/src/lints/avoid_unused_parameters/avoid_unused_parameters_rule.dart';
import 'package:solid_lints/src/lints/cyclomatic_complexity/cyclomatic_complexity_rule.dart';
import 'package:solid_lints/src/lints/cyclomatic_complexity/models/cyclomatic_complexity_parameters.dart';
import 'package:solid_lints/src/lints/double_literal_format/double_literal_format_rule.dart';
import 'package:solid_lints/src/lints/double_literal_format/fixes/double_literal_format_fix.dart';
import 'package:solid_lints/src/lints/proper_super_calls/proper_super_calls_rule.dart';
Expand Down Expand Up @@ -49,6 +51,10 @@ class SolidLintsPlugin extends Plugin {
AvoidUnusedParametersRule(
analysisOptionsLoader: analysisLoader,
),
CyclomaticComplexityRule(
analysisOptionsLoader: analysisLoader,
parametersParser: CyclomaticComplexityParameters.fromJson,
),
];

for (final lintRule in lintRules) {
Expand Down
79 changes: 38 additions & 41 deletions lib/src/lints/cyclomatic_complexity/cyclomatic_complexity_rule.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:analyzer/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.dart';
import 'package:analyzer/error/error.dart';
import 'package:solid_lints/src/lints/cyclomatic_complexity/models/cyclomatic_complexity_parameters.dart';
import 'package:solid_lints/src/lints/cyclomatic_complexity/visitors/cyclomatic_complexity_flow_visitor.dart';
import 'package:solid_lints/src/models/rule_config.dart';
import 'package:solid_lints/src/lints/cyclomatic_complexity/visitors/cyclomatic_complexity_visitor.dart';
import 'package:solid_lints/src/models/solid_lint_rule.dart';
Comment thread
solid-illiaaihistov marked this conversation as resolved.

/// Limit for the number of linearly independent paths through a program's
Expand All @@ -17,54 +17,51 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart';
/// triggering a warning.
///
/// ```yaml
/// custom_lint:
/// rules:
/// - cyclomatic_complexity:
/// max_complexity: 10
/// plugins:
/// solid_lints:
/// diagnostics:
/// cyclomatic_complexity:
/// max_complexity: 10
/// ```
class CyclomaticComplexityRule
extends SolidLintRule<CyclomaticComplexityParameters> {
/// This lint rule represents the error if complexity
/// reaches maximum value.
/// Name of the lint.
static const lintName = 'cyclomatic_complexity';

CyclomaticComplexityRule._(super.rule);
static const _code = LintCode(
lintName,
'The maximum allowed complexity of a function is {0}. Please decrease it.',
);

/// Creates a new instance of [CyclomaticComplexityRule]
/// based on the lint configuration.
factory CyclomaticComplexityRule.createRule(CustomLintConfigs configs) {
final rule = RuleConfig(
configs: configs,
name: lintName,
paramsParser: CyclomaticComplexityParameters.fromJson,
problemMessage: (value) =>
'The maximum allowed complexity of a function is '
'${value.maxComplexity}. Please decrease it.',
);
@override
DiagnosticCode get diagnosticCode => _code;

return CyclomaticComplexityRule._(rule);
}
/// Creates a new instance of [CyclomaticComplexityRule].
CyclomaticComplexityRule({
required super.analysisOptionsLoader,
required super.parametersParser,
}) : super.withParameters(
name: lintName,
description:
'Limit for the number of linearly independent paths '
"through a program's source code.",
);

@override
void run(
CustomLintResolver resolver,
DiagnosticReporter reporter,
CustomLintContext context,
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
context.registry.addBlockFunctionBody((node) {
context.registry.addDeclaration((declarationNode) {
final isIgnored =
config.parameters.exclude.shouldIgnore(declarationNode);
if (isIgnored) return;
super.registerNodeProcessors(registry, context);

final parameters =
getParametersForContext(context) ??
CyclomaticComplexityParameters.empty();

final visitor = CyclomaticComplexityFlowVisitor();
node.visitChildren(visitor);
final visitor = CyclomaticComplexityVisitor(this, parameters);

if (visitor.complexityEntities.length + 1 >
config.parameters.maxComplexity) {
reporter.atNode(node, code);
}
});
});
registry.addFunctionDeclaration(this, visitor);
registry.addConstructorDeclaration(this, visitor);
registry.addMethodDeclaration(this, visitor);
}
Comment thread
solid-illiaaihistov marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ class CyclomaticComplexityParameters {
required this.exclude,
});

/// Empty [CyclomaticComplexityParameters] model.
factory CyclomaticComplexityParameters.empty() =>
CyclomaticComplexityParameters(
maxComplexity: _defaultMaxComplexity,
exclude: ExcludedIdentifiersListParameter(exclude: []),
);

/// Method for creating from json data
factory CyclomaticComplexityParameters.fromJson(Map<String, Object?> json) =>
CyclomaticComplexityParameters(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,6 @@ import 'package:analyzer/dart/ast/visitor.dart';
/// The AST visitor that will collect cyclomatic complexity of visit nodes in an
/// AST structure.
class CyclomaticComplexityFlowVisitor extends RecursiveAstVisitor<void> {
static const _complexityTokenTypes = [
TokenType.AMPERSAND_AMPERSAND,
TokenType.BAR_BAR,
TokenType.QUESTION_PERIOD,
TokenType.QUESTION_QUESTION,
TokenType.QUESTION_QUESTION_EQ,
];

final _complexityEntities = <SyntacticEntity>{};

/// Returns an array of entities that increase cyclomatic complexity.
Expand All @@ -52,13 +44,46 @@ class CyclomaticComplexityFlowVisitor extends RecursiveAstVisitor<void> {
}

@override
void visitBlockFunctionBody(BlockFunctionBody node) {
_visitBlock(
node.block.leftBracket.next,
node.block.rightBracket,
);
void visitBinaryExpression(BinaryExpression node) {
final type = node.operator.type;
if (type == TokenType.AMPERSAND_AMPERSAND ||
type == TokenType.BAR_BAR ||
type == TokenType.QUESTION_QUESTION) {
_increaseComplexity(node);
}
super.visitBinaryExpression(node);
}

@override
void visitAssignmentExpression(AssignmentExpression node) {
if (node.operator.type == TokenType.QUESTION_QUESTION_EQ) {
_increaseComplexity(node);
}
super.visitAssignmentExpression(node);
}

@override
void visitPropertyAccess(PropertyAccess node) {
if (node.operator.type == TokenType.QUESTION_PERIOD) {
_increaseComplexity(node);
}
super.visitPropertyAccess(node);
}

super.visitBlockFunctionBody(node);
@override
void visitMethodInvocation(MethodInvocation node) {
if (node.operator?.type == TokenType.QUESTION_PERIOD) {
_increaseComplexity(node);
}
super.visitMethodInvocation(node);
}

@override
Comment thread
solid-illiaaihistov marked this conversation as resolved.
void visitIndexExpression(IndexExpression node) {
if (node.question != null) {
_increaseComplexity(node);
}
super.visitIndexExpression(node);
}

@override
Expand All @@ -76,13 +101,10 @@ class CyclomaticComplexityFlowVisitor extends RecursiveAstVisitor<void> {
}

@override
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
_visitBlock(
node.expression.beginToken.previous,
node.expression.endToken.next,
);
void visitDoStatement(DoStatement node) {
_increaseComplexity(node);

super.visitExpressionFunctionBody(node);
super.visitDoStatement(node);
}

@override
Expand Down Expand Up @@ -113,6 +135,79 @@ class CyclomaticComplexityFlowVisitor extends RecursiveAstVisitor<void> {
super.visitSwitchDefault(node);
}

@override
void visitSwitchExpressionCase(SwitchExpressionCase node) {
_increaseComplexity(node);

super.visitSwitchExpressionCase(node);
}

@override
void visitSwitchPatternCase(SwitchPatternCase node) {
_increaseComplexity(node);

super.visitSwitchPatternCase(node);
}

@override
void visitWhenClause(WhenClause node) {
_increaseComplexity(node);

super.visitWhenClause(node);
}
Comment thread
solid-illiaaihistov marked this conversation as resolved.
Comment thread
solid-illiaaihistov marked this conversation as resolved.

@override
void visitIfElement(IfElement node) {
_increaseComplexity(node);
super.visitIfElement(node);
}

@override
void visitForElement(ForElement node) {
_increaseComplexity(node);
super.visitForElement(node);
}
Comment thread
solid-illiaaihistov marked this conversation as resolved.

@override
void visitLogicalAndPattern(LogicalAndPattern node) {
_increaseComplexity(node);
super.visitLogicalAndPattern(node);
}

@override
void visitLogicalOrPattern(LogicalOrPattern node) {
_increaseComplexity(node);
super.visitLogicalOrPattern(node);
}
Comment thread
solid-illiaaihistov marked this conversation as resolved.

@override
void visitNullCheckPattern(NullCheckPattern node) {
_increaseComplexity(node);
super.visitNullCheckPattern(node);
}

@override
void visitNullAssertPattern(NullAssertPattern node) {
_increaseComplexity(node);
super.visitNullAssertPattern(node);
}

@override
void visitCascadeExpression(CascadeExpression node) {
if (node.isNullAware) {
_increaseComplexity(node);
}
super.visitCascadeExpression(node);
}

@override
void visitSpreadElement(SpreadElement node) {
if (node.isNullAware) {
_increaseComplexity(node);
}
super.visitSpreadElement(node);
}

@override
void visitWhileStatement(WhileStatement node) {
_increaseComplexity(node);
Expand All @@ -127,15 +222,19 @@ class CyclomaticComplexityFlowVisitor extends RecursiveAstVisitor<void> {
super.visitYieldStatement(node);
}

void _visitBlock(Token? firstToken, Token? lastToken) {
var token = firstToken;
while (token != lastToken && token != null) {
if (token.matchesAny(_complexityTokenTypes)) {
_increaseComplexity(token);
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
// Stop recursion into nested function declarations.
}

token = token.next;
}
@override
void visitMethodDeclaration(MethodDeclaration node) {
// Stop recursion into nested method declarations.
}

@override
void visitFunctionExpression(FunctionExpression node) {
// Stop recursion into nested function expressions (closures).
}

void _increaseComplexity(SyntacticEntity entity) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:solid_lints/src/lints/cyclomatic_complexity/cyclomatic_complexity_rule.dart';
import 'package:solid_lints/src/lints/cyclomatic_complexity/models/cyclomatic_complexity_parameters.dart';
import 'package:solid_lints/src/lints/cyclomatic_complexity/visitors/cyclomatic_complexity_flow_visitor.dart';

/// Visitor that runs [CyclomaticComplexityFlowVisitor] on function,
/// constructor, and method bodies to count their cyclomatic complexity.
class CyclomaticComplexityVisitor extends SimpleAstVisitor<void> {
final CyclomaticComplexityRule _rule;
final CyclomaticComplexityParameters _parameters;

/// Creates a new instance of [CyclomaticComplexityVisitor].
CyclomaticComplexityVisitor(this._rule, this._parameters);

void _checkBody(Declaration declaration, FunctionBody? body) {
if (body == null) return;

final isIgnored = _parameters.exclude.shouldIgnore(declaration);
if (isIgnored) return;

final visitor = CyclomaticComplexityFlowVisitor();
body.accept(visitor);

if (visitor.complexityEntities.length + 1 > _parameters.maxComplexity) {
_rule.reportAtNode(
body,
arguments: [_parameters.maxComplexity.toString()],
);
}
}

@override
void visitFunctionDeclaration(FunctionDeclaration node) {
_checkBody(node, node.functionExpression.body);
}

@override
void visitConstructorDeclaration(ConstructorDeclaration node) {
_checkBody(node, node.body);
}

@override
void visitMethodDeclaration(MethodDeclaration node) {
_checkBody(node, node.body);
}
}
Loading
Loading