Skip to content

Commit b8c8ca7

Browse files
perf: eliminate WASM re-parse for native complexity + build optimizations (#140)
* fix: native complexity upsert supplies all 18 Halstead/MI columns The native complexity shortcut was passing only 4 params to the upsert statement which now expects 18 (added by the Halstead/MI migration). This crashed at runtime for natively-parsed functions. Fill Halstead/LOC/MI columns with zeros since native engine intentionally skips WASM AST. Also adds #[napi(js_name = "maxNesting")] to ComplexityMetrics so Rust exports camelCase directly, removing the snake_case fallback in parser.js and complexity.js. Cleans up a duplicate assertion and fixes indentation in go.rs. Impact: 4 functions changed, 4 affected * fix: make complexity rules report-only in manifesto engine Complexity rules (cognitive, cyclomatic, maxNesting) now always report warnings but never cause process.exit(1). This keeps codegraph focused on dependency graph analysis rather than acting as a CI linter gate. - Add reportOnly flag to function-level complexity RULE_DEFS - Force fail: null in resolveRules() when reportOnly is set - Remove fail key from complexity defaults in config.js - Add integration test verifying user fail thresholds are ignored Impact: 1 functions changed, 2 affected * style: format manifesto.js per biome rules Impact: 1 functions changed, 2 affected --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 8f12f66 commit b8c8ca7

8 files changed

Lines changed: 80 additions & 16 deletions

File tree

crates/codegraph-core/src/complexity.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -323,14 +323,7 @@ mod tests {
323323
let m = compute_js(
324324
"function f(x) { if (x > 0) { return 1; } else if (x < 0) { return -1; } else { return 0; } }",
325325
);
326-
assert_eq!(m.cognitive, 4); // +1 if, +1 else-if, +1 else (from else-if's else_clause), +1 else-if cognitive
327-
// Wait, let me recalculate:
328-
// if: cognitive +1 (nesting 0), cyclomatic +1
329-
// else-if: cognitive +1, cyclomatic +1 (no nesting)
330-
// else: cognitive +1
331-
// Total cognitive = 3, cyclomatic = 1 + 1 + 1 = 3
332-
// Hmm, the else clause wrapping the else-if doesn't add anything (it's detected as else-if wrapper)
333-
// So: if (+1 cog, +1 cyc), else-if (+1 cog, +1 cyc), plain else (+1 cog)
326+
// if (+1 cog, +1 cyc), else-if (+1 cog, +1 cyc), plain else (+1 cog)
334327
// cognitive = 3, cyclomatic = 3
335328
assert_eq!(m.cognitive, 3);
336329
assert_eq!(m.cyclomatic, 3);

crates/codegraph-core/src/extractors/go.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
111111
line: start_line(&member),
112112
end_line: Some(end_line(&member)),
113113
decorators: None,
114-
complexity: None,
114+
complexity: None,
115115
});
116116
}
117117
}

crates/codegraph-core/src/types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
66
pub struct ComplexityMetrics {
77
pub cognitive: u32,
88
pub cyclomatic: u32,
9+
#[napi(js_name = "maxNesting")]
910
pub max_nesting: u32,
1011
}
1112

src/complexity.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,21 @@ export async function buildComplexityMetrics(db, fileSymbols, rootDir, _engineOp
581581
row.id,
582582
def.complexity.cognitive,
583583
def.complexity.cyclomatic,
584-
def.complexity.maxNesting ?? def.complexity.max_nesting ?? 0,
584+
def.complexity.maxNesting ?? 0,
585+
0,
586+
0,
587+
0, // loc, sloc, commentLines
588+
0,
589+
0,
590+
0,
591+
0, // halstead n1, n2, bigN1, bigN2
592+
0,
593+
0,
594+
0, // vocabulary, length, volume
595+
0,
596+
0,
597+
0, // difficulty, effort, bugs
598+
0, // maintainabilityIndex
585599
);
586600
analyzed++;
587601
continue;

src/config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ export const DEFAULTS = {
2626
ci: { failOnCycles: false, impactThreshold: null },
2727
manifesto: {
2828
rules: {
29-
cognitive: { warn: 15, fail: null },
30-
cyclomatic: { warn: 10, fail: null },
31-
maxNesting: { warn: 4, fail: null },
29+
cognitive: { warn: 15 },
30+
cyclomatic: { warn: 10 },
31+
maxNesting: { warn: 4 },
3232
maintainabilityIndex: { warn: 20, fail: null },
3333
importCount: { warn: null, fail: null },
3434
exportCount: { warn: null, fail: null },

src/manifesto.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,26 @@ import { debug } from './logger.js';
1212
* defaults: { warn, fail } — null means disabled
1313
*/
1414
export const RULE_DEFS = [
15-
{ name: 'cognitive', level: 'function', metric: 'cognitive', defaults: { warn: 15, fail: null } },
15+
{
16+
name: 'cognitive',
17+
level: 'function',
18+
metric: 'cognitive',
19+
defaults: { warn: 15, fail: null },
20+
reportOnly: true,
21+
},
1622
{
1723
name: 'cyclomatic',
1824
level: 'function',
1925
metric: 'cyclomatic',
2026
defaults: { warn: 10, fail: null },
27+
reportOnly: true,
2128
},
2229
{
2330
name: 'maxNesting',
2431
level: 'function',
2532
metric: 'max_nesting',
2633
defaults: { warn: 4, fail: null },
34+
reportOnly: true,
2735
},
2836
{
2937
name: 'importCount',
@@ -67,7 +75,7 @@ function resolveRules(userRules) {
6775
const user = userRules?.[def.name];
6876
resolved[def.name] = {
6977
warn: user?.warn !== undefined ? user.warn : def.defaults.warn,
70-
fail: user?.fail !== undefined ? user.fail : def.defaults.fail,
78+
fail: def.reportOnly ? null : user?.fail !== undefined ? user.fail : def.defaults.fail,
7179
};
7280
}
7381
return resolved;

src/parser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ function normalizeNativeSymbols(result) {
136136
? {
137137
cognitive: d.complexity.cognitive,
138138
cyclomatic: d.complexity.cyclomatic,
139-
maxNesting: d.complexity.maxNesting ?? d.complexity.max_nesting,
139+
maxNesting: d.complexity.maxNesting,
140140
}
141141
: null,
142142
})),

tests/integration/manifesto.test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,54 @@ describe('manifestoData', () => {
291291
}
292292
});
293293

294+
test('reportOnly complexity rules ignore user-configured fail thresholds', () => {
295+
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-manifesto-reportonly-'));
296+
fs.mkdirSync(path.join(configDir, '.codegraph'));
297+
const roDbPath = path.join(configDir, '.codegraph', 'graph.db');
298+
299+
fs.copyFileSync(dbPath, roDbPath);
300+
301+
// User attempts to set fail thresholds on complexity rules
302+
fs.writeFileSync(
303+
path.join(configDir, '.codegraphrc.json'),
304+
JSON.stringify({
305+
manifesto: {
306+
rules: {
307+
cognitive: { warn: 15, fail: 5 },
308+
cyclomatic: { warn: 10, fail: 3 },
309+
maxNesting: { warn: 4, fail: 2 },
310+
},
311+
},
312+
}),
313+
);
314+
315+
const origCwd = process.cwd();
316+
try {
317+
process.chdir(configDir);
318+
const data = manifestoData(roDbPath);
319+
320+
// fail thresholds should be forced to null (reportOnly)
321+
for (const name of ['cognitive', 'cyclomatic', 'maxNesting']) {
322+
const rule = data.rules.find((r) => r.name === name);
323+
expect(rule.thresholds.fail).toBeNull();
324+
}
325+
326+
// All violations should be warn-only, never fail
327+
const complexityViolations = data.violations.filter(
328+
(v) => v.rule === 'cognitive' || v.rule === 'cyclomatic' || v.rule === 'maxNesting',
329+
);
330+
for (const v of complexityViolations) {
331+
expect(v.level).toBe('warn');
332+
}
333+
334+
// Overall result should pass (no fail-level violations)
335+
expect(data.passed).toBe(true);
336+
} finally {
337+
process.chdir(origCwd);
338+
fs.rmSync(configDir, { recursive: true, force: true });
339+
}
340+
});
341+
294342
test('noCycles rule with fail threshold sets passed=false', () => {
295343
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-manifesto-fail-'));
296344
fs.mkdirSync(path.join(configDir, '.codegraph'));

0 commit comments

Comments
 (0)