Skip to main content

cccc_core/
ir.rs

1//! The language-agnostic intermediate representation (IR) that the complexity
2//! engine scores.
3//!
4//! A language adapter (e.g. `cccc-typescript`) lowers its native AST into a
5//! `Vec<Node>` describing only the constructs that affect Cognitive / Cyclomatic
6//! Complexity — branches, loops, switches, exception handlers, logical-operator
7//! sequences, function boundaries, and calls. Everything else collapses into
8//! [`Node::Group`], which is a transparent container the engine simply recurses
9//! into. All scoring rules live in [`crate::engine`]; the IR carries no scores.
10//!
11//! The top level handed to the engine is itself a `&[Node]`, representing
12//! module-level code; the engine scores it under an implicit module frame.
13
14/// A logical operator, normalized across languages.
15///
16/// The adapter is responsible for folding a run of like operators into a single
17/// [`Node::Logical`] (e.g. `a && b && c` is one `Logical` with three operands);
18/// the engine counts one cognitive point per `Logical` node.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum LogicalOp {
21    And,
22    Or,
23    /// Nullish coalescing (`??`).
24    Coalesce,
25}
26
27/// One arm of a [`Node::Switch`].
28#[derive(Debug, Clone)]
29pub struct SwitchCase {
30    /// `true` for the `default` arm, which is not a cyclomatic decision point.
31    pub is_default: bool,
32    pub body: Vec<Node>,
33}
34
35/// A node of the normalized complexity IR.
36///
37/// Fields that hold sub-expressions or sub-statements are `Vec<Node>` so the
38/// adapter can drop irrelevant detail and the engine can recurse uniformly.
39#[derive(Debug, Clone)]
40pub enum Node {
41    /// A function-like unit (function, method, arrow, accessor, …). The engine
42    /// scores each one independently — nesting resets to 0 at this boundary —
43    /// and reports it as a child of the enclosing unit. `kind`/`name` are
44    /// opaque, adapter-chosen labels (the engine never interprets them).
45    Function {
46        name: String,
47        kind: String,
48        /// 1-based line where the unit starts.
49        line: u32,
50        body: Vec<Node>,
51    },
52
53    /// An `if` (with optional `else` / `else if`). `then` is scored with a
54    /// nesting bonus; `alternate` (a nested `Branch` for `else if`, or any other
55    /// node for a plain `else`) is scored flat — one cognitive point, no bonus.
56    Branch {
57        test: Vec<Node>,
58        then: Vec<Node>,
59        alternate: Option<Box<Node>>,
60    },
61
62    /// A ternary `?:`. Scored like a branch: +1 plus nesting bonus.
63    Conditional {
64        test: Vec<Node>,
65        then: Vec<Node>,
66        alternate: Vec<Node>,
67    },
68
69    /// Any loop (`for` / `for-in` / `for-of` / `while` / `do-while`), normalized
70    /// to one shape. +1 plus nesting bonus, and a cyclomatic point.
71    Loop { body: Vec<Node> },
72
73    /// A `switch`. +1 plus nesting bonus (but the switch itself is not a McCabe
74    /// decision point — each non-default `case` is, scored via [`SwitchCase`]).
75    Switch { cases: Vec<SwitchCase> },
76
77    /// A `catch` clause. +1 plus nesting bonus, and a cyclomatic point.
78    Catch { body: Vec<Node> },
79
80    /// A `break` / `continue`. Only a *labelled* jump adds one cognitive point.
81    Jump { labeled: bool },
82
83    /// A run of like logical operators. One cognitive point per node; the
84    /// adapter folds `a && b && c` into a single node with three operands.
85    Logical { op: LogicalOp, operands: Vec<Node> },
86
87    /// A function call. If `callee` names the nearest enclosing function, the
88    /// engine counts it as recursion (one cognitive point).
89    Call { callee: Option<String> },
90
91    /// A transparent container for any construct that holds children but carries
92    /// no score of its own (statements, expressions, blocks). The engine simply
93    /// recurses into it.
94    Group(Vec<Node>),
95}