fallow_types/complexity_contribution.rs
1//! Per-construct complexity increments that explain the cyclomatic and
2//! cognitive scores of a function.
3
4/// Which complexity metric a [`ComplexityContribution`] adds to.
5#[derive(
6 Debug,
7 Clone,
8 Copy,
9 PartialEq,
10 Eq,
11 serde::Serialize,
12 serde::Deserialize,
13 bitcode::Encode,
14 bitcode::Decode,
15)]
16#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
17#[serde(rename_all = "kebab-case")]
18pub enum ComplexityMetric {
19 /// `McCabe` cyclomatic complexity (independent execution paths).
20 Cyclomatic,
21 /// `SonarSource` cognitive complexity (structural + nesting penalty).
22 Cognitive,
23}
24
25/// The syntactic construct that produced a single complexity increment.
26///
27/// Mirrors `SonarSource` cognitive-complexity vocabulary where it overlaps.
28/// `Case` means a `case` label carrying a test; a bare `default` adds nothing
29/// to cyclomatic complexity and so produces no contribution.
30#[derive(
31 Debug,
32 Clone,
33 Copy,
34 PartialEq,
35 Eq,
36 serde::Serialize,
37 serde::Deserialize,
38 bitcode::Encode,
39 bitcode::Decode,
40)]
41#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
42#[serde(rename_all = "kebab-case")]
43#[non_exhaustive]
44pub enum ComplexityContributionKind {
45 /// An `if` condition.
46 If,
47 /// A bare `else` branch (cognitive only).
48 Else,
49 /// An `else if` continuation (both metrics: cyclomatic +1, cognitive flat
50 /// +1 with no nesting penalty).
51 ElseIf,
52 /// A `?:` conditional (ternary) expression.
53 Ternary,
54 /// A logical `&&` operator.
55 LogicalAnd,
56 /// A logical `||` operator.
57 LogicalOr,
58 /// A `??` nullish-coalescing operator.
59 NullishCoalescing,
60 /// A logical assignment operator (`&&=`, `||=`, `??=`); cyclomatic only.
61 LogicalAssignment,
62 /// An optional-chaining link (`?.`); cyclomatic only.
63 OptionalChain,
64 /// A `for` loop.
65 For,
66 /// A `for...in` loop.
67 ForIn,
68 /// A `for...of` loop.
69 ForOf,
70 /// A `while` loop.
71 While,
72 /// A `do...while` loop.
73 DoWhile,
74 /// A `switch` statement (cognitive only; each `case` adds cyclomatic).
75 Switch,
76 /// A `case` label carrying a test (cyclomatic only).
77 Case,
78 /// A `catch` clause.
79 Catch,
80 /// A labeled `break` (cognitive only).
81 LabeledBreak,
82 /// A labeled `continue` (cognitive only).
83 LabeledContinue,
84 /// Legacy JSX-depth contribution kind kept for schema compatibility. Current
85 /// extraction records JSX nesting as descriptive `react_jsx_max_depth`
86 /// context and does not emit this kind for layout depth.
87 JsxDepth,
88 /// React hook density (cognitive only). One contribution per hook call in a
89 /// component body (`useState` / `useEffect` / `useMemo` / `useCallback` /
90 /// custom `use*`); a hook-heavy component accrues cognitive load the same way
91 /// branching does.
92 HookDensity,
93 /// React prop count past the comfortable floor (cognitive only). A component
94 /// destructuring many props is doing many things; the props beyond the floor
95 /// fold into cognitive so a wide-interface component surfaces as a hotspot.
96 PropCount,
97 /// A Svelte `{#await}` block.
98 Await,
99 /// A Svelte `{:then}` continuation.
100 Then,
101}
102
103/// A single complexity increment, located at its source line/column.
104///
105/// `weight` is the amount this construct added to `metric`; for nested
106/// cognitive increments `weight == 1 + nesting`. Consumers that render inline
107/// (the VS Code editor breakdown) group contributions by `line` and sum the
108/// weights, deferring the per-kind list to a hover.
109#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, bitcode::Encode, bitcode::Decode)]
110#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
111pub struct ComplexityContribution {
112 /// 1-based line number where the construct begins.
113 pub line: u32,
114 /// 0-based byte column where the construct begins.
115 pub col: u32,
116 /// Which metric this increment contributes to.
117 pub metric: ComplexityMetric,
118 /// The syntactic construct responsible for the increment.
119 pub kind: ComplexityContributionKind,
120 /// The amount added to `metric` at this site (`1 + nesting` for nested
121 /// cognitive increments, otherwise `1`).
122 pub weight: u16,
123 /// The nesting depth at the increment site (`0` when not nested). Lets a
124 /// consumer explain a cognitive `+3` as "+1 base, +2 nesting".
125 pub nesting: u16,
126 /// This cyclomatic `||` increment is part of a literal membership chain:
127 /// strict equality checks of one identifier against primitive literals
128 /// (`kind === "a" || kind === "b"`). The `literal-membership` value of
129 /// `health.cyclomaticPolicy` removes these increments from the effective
130 /// cyclomatic value. Omitted when `false`, and always `false` under the
131 /// default `standard` policy.
132 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
133 pub literal_membership: bool,
134}