| name | cyclomatic-complexity |
| description | Cyclomatic complexity measurement for JavaScript/TypeScript. Function complexity scoring, threshold enforcement, refactor detection, and per-file complexity reports. Sources: marcondescruza/node-complexity (MIT). |
/cyclomatic-complexity
When to Use
- Detect functions that are too complex to safely modify (complexity > 10 = refactor candidate)
- CI gate: fail builds when new code exceeds complexity threshold
- Tech-debt triage: rank functions by complexity to prioritize refactoring
- Pair with [[eslint-rule-engine]] for inline complexity warnings during coding
Do NOT use for
- Code correctness analysis (complexity ≠ correctness)
- Python complexity measurement (use radon:
radon cc -s src/)
Cyclomatic complexity formula
CC = Edges − Nodes + 2 * ConnectedComponents
= 1 + (number of decision points)
Decision points: if, else if, ?:, &&, ||, switch case, while, for, for-in, for-of, catch
Thresholds:
- 1–5: simple, easy to test
- 6–10: moderate, test all paths
- 11–20: complex, refactor soon
- 21+: untestable, must split
Measure with ESLint complexity rule
export default [
{
rules: {
complexity: ['error', { max: 10 }],
},
files: ['src/**/*.{js,ts}'],
},
]
Manual AST-based complexity scorer
import { parse } from 'acorn'
const DECISION_NODES = new Set([
'IfStatement', 'ConditionalExpression', 'SwitchCase',
'WhileStatement', 'DoWhileStatement',
'ForStatement', 'ForInStatement', 'ForOfStatement',
'CatchClause',
])
const LOGICAL_OPS = new Set(['&&', '||', '??'])
function measureComplexity(fnSource: string): number {
const ast = parse(`(${fnSource})`, { ecmaVersion: 2022 })
let cc = 1
function walk(node: any) {
if (!node || typeof node !== 'object') return
if (DECISION_NODES.has(node.type)) cc++
if (node.type === 'LogicalExpression' && LOGICAL_OPS.has(node.operator)) cc++
for ( key .(node)) {
child = node[key]
(.(child)) child.(walk)
(child?.) (child)
}
}
(ast)
cc
}
.(())
File-level complexity report
#!/usr/bin/env bash
THRESHOLD=${1:-10}
npx eslint --format json src/**/*.{js,ts} 2>/dev/null \
| jq --argjson t "$THRESHOLD" '
[.[] | .messages[]
| select(.ruleId == "complexity" and .severity == 2)
| { file: .filePath? // "?", line: .line, message }
] | sort_by(.line)
'
Cognitive complexity (human readability score)
export default [
{
plugins: { '@typescript-eslint': tseslint },
rules: {
'@typescript-eslint/cognitive-complexity': ['warn', 15],
},
},
]
Anti-Fake-Pass Checklist
❌ Measuring CC on minified code → all complexity reported as 1 (single "function")
❌ Ignoring ?? and ?. operators → modern JS complexity undercounted
❌ Threshold too low (≤5) for utility functions → alert fatigue from false positives
❌ Only measuring per-file, not per-function → complex helper buried in a simple file
❌ CC = 1 doesn't mean easy to test — 1 path through deeply nested callbacks is still hard
❌ Skipping CatchClause in walker → error-handling branches missed from score