Skip to main content

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}