Skip to content

Commit dff1be1

Browse files
Komoszekclaude
andauthored
[leancode_lint] Add cognitive_complexity (#581)
* [leancode_lint] Add cognitive_complexity Reports functions, methods, constructors and closures whose cognitive complexity is over LeanCodeLintConfig.cognitiveComplexity.maximum (15 by default), scored like Biome's noExcessiveCognitiveComplexity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1sawdgkJ3Snt4Ug7QGZEr * [leancode_lint] Make cognitive_complexity opt-in Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1sawdgkJ3Snt4Ug7QGZEr * [leancode_lint] Nest only the first level of functions in cognitive_complexity As SonarJS does, so a test closure inside group inside main is not penalized for the test framework's structure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1sawdgkJ3Snt4Ug7QGZEr * [leancode_lint] Let a collection if cost nothing in cognitive_complexity A conditional child in a widget list reads like JSX's short-circuit, which SonarJS ignores. Also pin that null-aware operators cost nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1sawdgkJ3Snt4Ug7QGZEr * [leancode_lint] Tighten cognitive_complexity's messages, docs and tests The message names what was detected and the correction says what to do, the README entry is shorter, and the tests use range markers like the others. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1sawdgkJ3Snt4Ug7QGZEr * [leancode_lint] Document how cognitive_complexity scores The rule's doc comment holds the full scoring table and why each deviation from SonarSource's paper exists; the README links to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1sawdgkJ3Snt4Ug7QGZEr * [leancode_lint] Unit-test cognitive_complexity's scoring Scoring is split from reporting so it can be tested without a lint rule, and a constructor's initializer list is no longer added to its body's score. Also fix the scoring table row the alignment broke. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1sawdgkJ3Snt4Ug7QGZEr * [leancode_lint] Report a closure's cognitive complexity at its parameters Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1sawdgkJ3Snt4Ug7QGZEr * [leancode_lint] Keep cognitive_complexity on analyzer 13 ConstructorDeclaration.errorRange is newer than analyzer 13.0.0, the lowest version allowed, so pana's downgrade analysis failed; the range is now built from the constructor's name. Also make ScoredFunction a class and realign the scoring table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1sawdgkJ3Snt4Ug7QGZEr * [leancode_lint] Score primary constructors in cognitive_complexity A primary constructor's body is a PrimaryConstructorBody, not a ConstructorDeclaration, so it was never scored. It is now reported at its `this`, without its initializers, like other constructors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1sawdgkJ3Snt4Ug7QGZEr * [leancode_lint] Report a constructor without its type name at its keyword `new named`, `new` and `factory make` were reported at the name alone, or at the parameters when there was no name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1sawdgkJ3Snt4Ug7QGZEr * [leancode_lint] Use errorRange for constructors in cognitive_complexity ConstructorDeclaration.errorRange covers a constructor without its type name too, and exists since analyzer 13.1.0, so require that instead of rebuilding the range by hand for 13.0.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1sawdgkJ3Snt4Ug7QGZEr * [leancode_lint] Use shorthand range markers in single-diagnostic tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1sawdgkJ3Snt4Ug7QGZEr --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5403466 commit dff1be1

8 files changed

Lines changed: 680 additions & 1 deletion

File tree

‎packages/leancode_lint/CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Unreleased
22

3+
- Add [`cognitive_complexity`](https://github.com/leancodepl/flutter_corelibrary/tree/master/packages/leancode_lint#cognitive_complexity), which reports functions whose cognitive complexity is over a configurable maximum (15 by default). It is disabled by default.
4+
- Require `analyzer: '>=13.1.0 <15.0.0'`
35
- Fix [`use_design_system_item`](https://github.com/leancodepl/flutter_corelibrary/tree/master/packages/leancode_lint#use_design_system_item) not reporting forbidden items used via dot shorthands (e.g. `.new()`, `.fromARGB()`, `.staticMember`).
46

57
# 27.0.0

‎packages/leancode_lint/README.md‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,53 @@ final plugin = LeanCodeLintPlugin(
595595

596596
</details>
597597

598+
<details>
599+
<summary><code>cognitive_complexity</code></summary>
600+
601+
### `cognitive_complexity`
602+
603+
**AVOID** functions that are hard to follow.
604+
605+
Reports functions, methods, constructors and closures whose
606+
[Cognitive Complexity](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)
607+
is over the maximum (15 by default). Each `if`, `switch`, `?:`, loop and `catch`
608+
costs 1, plus 1 for each level it is nested at; each `else` and each run of `&&`
609+
or `||` costs 1. Unlike the original metric:
610+
611+
- a closure is scored on its own, and only the first level of nested functions
612+
adds a nesting level, so a `test` isn't penalized for sitting in a `group`
613+
- a collection `if` and null-aware operators (`?.`, `??`, `!`, ...) cost nothing
614+
615+
The full scoring table and the reasons for each deviation are in the
616+
[rule's documentation](./lib/src/lints/cognitive_complexity.dart).
617+
618+
#### Configuration
619+
620+
Disabled by default. Enable it in `analysis_options.yaml`:
621+
622+
```yaml
623+
plugins:
624+
leancode_lint:
625+
version: ^27.1.0
626+
diagnostics:
627+
cognitive_complexity: true
628+
```
629+
630+
The maximum is configured via `LeanCodeLintConfig.cognitiveComplexity`:
631+
632+
```dart
633+
import 'package:leancode_lint/plugin.dart';
634+
635+
final plugin = LeanCodeLintPlugin(
636+
name: 'my_lints',
637+
config: LeanCodeLintConfig(
638+
cognitiveComplexity: CognitiveComplexityConfig(maximum: 30),
639+
),
640+
);
641+
```
642+
643+
</details>
644+
598645
<details>
599646
<summary><code>hook_widget_does_not_use_hooks</code></summary>
600647

‎packages/leancode_lint/lib/config.dart‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ final class const LeanCodeLintConfig({
1717

1818
/// Configuration for the `bloc_related_class_naming` rule.
1919
final BlocRelatedClassNamingConfig blocRelatedClassNaming = const .new(),
20+
21+
/// Configuration for the `cognitive_complexity` rule.
22+
final CognitiveComplexityConfig cognitiveComplexity = const .new(),
2023
});
2124

2225
/// Configuration for the `bloc_related_class_naming` rule.
@@ -34,6 +37,12 @@ class const BlocRelatedClassNamingConfig({
3437
final String presentationEventSuffix = 'PresentationEvent',
3538
});
3639

40+
/// Configuration for the `cognitive_complexity` rule.
41+
///
42+
/// [maximum] is the highest cognitive complexity a function may have. The
43+
/// default, 15, is SonarSource's and Biome's.
44+
class const CognitiveComplexityConfig({final int maximum = 15});
45+
3746
class const CatchParameterNamesConfig({
3847
final String exception = 'err',
3948
final String stackTrace = 'st',

‎packages/leancode_lint/lib/plugin.dart‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'package:leancode_lint/src/lints/avoid_single_child_in_multi_child_widget
1414
import 'package:leancode_lint/src/lints/bloc_related_class_naming.dart';
1515
import 'package:leancode_lint/src/lints/bloc_subclasses_naming.dart';
1616
import 'package:leancode_lint/src/lints/catch_parameter_names.dart';
17+
import 'package:leancode_lint/src/lints/cognitive_complexity.dart';
1718
import 'package:leancode_lint/src/lints/constructor_parameters_and_fields_should_have_the_same_order.dart';
1819
import 'package:leancode_lint/src/lints/hook_widget_does_not_use_hooks.dart';
1920
import 'package:leancode_lint/src/lints/missing_equatable_props.dart';
@@ -62,6 +63,9 @@ final class LeanCodeLintPlugin({
6263
CatchParameterNames(config: config.catchParameterNames),
6364
)
6465
..registerWarningRule(AvoidCatchError())
66+
..registerLintRule(
67+
CognitiveComplexity(config: config.cognitiveComplexity),
68+
)
6569
..registerWarningRule(AvoidBuildContextInBlocs())
6670
..registerWarningRule(AvoidConditionalHooks())
6771
..registerWarningRule(AvoidDirectCollectionEqualityChecks())

0 commit comments

Comments
 (0)