Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions crates/codegraph-core/src/complexity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,14 +323,7 @@ mod tests {
let m = compute_js(
"function f(x) { if (x > 0) { return 1; } else if (x < 0) { return -1; } else { return 0; } }",
);
assert_eq!(m.cognitive, 4); // +1 if, +1 else-if, +1 else (from else-if's else_clause), +1 else-if cognitive
// Wait, let me recalculate:
// if: cognitive +1 (nesting 0), cyclomatic +1
// else-if: cognitive +1, cyclomatic +1 (no nesting)
// else: cognitive +1
// Total cognitive = 3, cyclomatic = 1 + 1 + 1 = 3
// Hmm, the else clause wrapping the else-if doesn't add anything (it's detected as else-if wrapper)
// So: if (+1 cog, +1 cyc), else-if (+1 cog, +1 cyc), plain else (+1 cog)
// if (+1 cog, +1 cyc), else-if (+1 cog, +1 cyc), plain else (+1 cog)
// cognitive = 3, cyclomatic = 3
assert_eq!(m.cognitive, 3);
assert_eq!(m.cyclomatic, 3);
Expand Down
2 changes: 1 addition & 1 deletion crates/codegraph-core/src/extractors/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
line: start_line(&member),
end_line: Some(end_line(&member)),
decorators: None,
complexity: None,
complexity: None,
});
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/codegraph-core/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
pub struct ComplexityMetrics {
pub cognitive: u32,
pub cyclomatic: u32,
#[napi(js_name = "maxNesting")]
pub max_nesting: u32,
}

Expand Down
16 changes: 15 additions & 1 deletion src/complexity.js
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,21 @@ export async function buildComplexityMetrics(db, fileSymbols, rootDir, _engineOp
row.id,
def.complexity.cognitive,
def.complexity.cyclomatic,
def.complexity.maxNesting ?? def.complexity.max_nesting ?? 0,
def.complexity.maxNesting ?? 0,
0,
0,
0, // loc, sloc, commentLines
0,
0,
0,
0, // halstead n1, n2, bigN1, bigN2
0,
0,
0, // vocabulary, length, volume
0,
0,
0, // difficulty, effort, bugs
0, // maintainabilityIndex
);
analyzed++;
continue;
Expand Down
6 changes: 3 additions & 3 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ export const DEFAULTS = {
ci: { failOnCycles: false, impactThreshold: null },
manifesto: {
rules: {
cognitive: { warn: 15, fail: null },
cyclomatic: { warn: 10, fail: null },
maxNesting: { warn: 4, fail: null },
cognitive: { warn: 15 },
cyclomatic: { warn: 10 },
maxNesting: { warn: 4 },
maintainabilityIndex: { warn: 20, fail: null },
importCount: { warn: null, fail: null },
exportCount: { warn: null, fail: null },
Expand Down
12 changes: 10 additions & 2 deletions src/manifesto.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,26 @@ import { debug } from './logger.js';
* defaults: { warn, fail } — null means disabled
*/
export const RULE_DEFS = [
{ name: 'cognitive', level: 'function', metric: 'cognitive', defaults: { warn: 15, fail: null } },
{
name: 'cognitive',
level: 'function',
metric: 'cognitive',
defaults: { warn: 15, fail: null },
reportOnly: true,
},
{
name: 'cyclomatic',
level: 'function',
metric: 'cyclomatic',
defaults: { warn: 10, fail: null },
reportOnly: true,
},
{
name: 'maxNesting',
level: 'function',
metric: 'max_nesting',
defaults: { warn: 4, fail: null },
reportOnly: true,
},
{
name: 'importCount',
Expand Down Expand Up @@ -67,7 +75,7 @@ function resolveRules(userRules) {
const user = userRules?.[def.name];
resolved[def.name] = {
warn: user?.warn !== undefined ? user.warn : def.defaults.warn,
fail: user?.fail !== undefined ? user.fail : def.defaults.fail,
fail: def.reportOnly ? null : user?.fail !== undefined ? user.fail : def.defaults.fail,
};
}
return resolved;
Expand Down
2 changes: 1 addition & 1 deletion src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ function normalizeNativeSymbols(result) {
? {
cognitive: d.complexity.cognitive,
cyclomatic: d.complexity.cyclomatic,
maxNesting: d.complexity.maxNesting ?? d.complexity.max_nesting,
maxNesting: d.complexity.maxNesting,
}
: null,
})),
Expand Down
48 changes: 48 additions & 0 deletions tests/integration/manifesto.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,54 @@ describe('manifestoData', () => {
}
});

test('reportOnly complexity rules ignore user-configured fail thresholds', () => {
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-manifesto-reportonly-'));
fs.mkdirSync(path.join(configDir, '.codegraph'));
const roDbPath = path.join(configDir, '.codegraph', 'graph.db');

fs.copyFileSync(dbPath, roDbPath);

// User attempts to set fail thresholds on complexity rules
fs.writeFileSync(
path.join(configDir, '.codegraphrc.json'),
JSON.stringify({
manifesto: {
rules: {
cognitive: { warn: 15, fail: 5 },
cyclomatic: { warn: 10, fail: 3 },
maxNesting: { warn: 4, fail: 2 },
},
},
}),
);

const origCwd = process.cwd();
try {
process.chdir(configDir);
const data = manifestoData(roDbPath);

// fail thresholds should be forced to null (reportOnly)
for (const name of ['cognitive', 'cyclomatic', 'maxNesting']) {
const rule = data.rules.find((r) => r.name === name);
expect(rule.thresholds.fail).toBeNull();
}

// All violations should be warn-only, never fail
const complexityViolations = data.violations.filter(
(v) => v.rule === 'cognitive' || v.rule === 'cyclomatic' || v.rule === 'maxNesting',
);
for (const v of complexityViolations) {
expect(v.level).toBe('warn');
}

// Overall result should pass (no fail-level violations)
expect(data.passed).toBe(true);
} finally {
process.chdir(origCwd);
fs.rmSync(configDir, { recursive: true, force: true });
}
});

test('noCycles rule with fail threshold sets passed=false', () => {
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-manifesto-fail-'));
fs.mkdirSync(path.join(configDir, '.codegraph'));
Expand Down