pi-lens
Version:
Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo
73 lines (72 loc) • 3.53 kB
JavaScript
import { isTestSuiteOrganizer } from "./framework-call-noise.js";
// Defaults match the historical hardcoded values so behavior is unchanged for
// projects without a config. Project-specific overrides are read from the
// per-dispatch context; these mutable fallbacks exist for tests/legacy direct
// rule evaluation only.
export const DEFAULT_HIGH_COMPLEXITY_THRESHOLD = 15;
export const DEFAULT_HIGH_COMPLEXITY_DEPTH_THRESHOLD = 6;
let ccThreshold = DEFAULT_HIGH_COMPLEXITY_THRESHOLD;
let depthThreshold = DEFAULT_HIGH_COMPLEXITY_DEPTH_THRESHOLD;
function isPositiveFiniteThreshold(value) {
return Number.isFinite(value) && value > 0;
}
/** Override fallback thresholds for tests/legacy direct rule evaluation. */
export function setHighComplexityThresholds(cc, depth) {
// Non-positive thresholds make every function violate the rule; treat them as
// invalid config/test input rather than turning the rule into noise.
if (isPositiveFiniteThreshold(cc))
ccThreshold = cc;
if (isPositiveFiniteThreshold(depth))
depthThreshold = depth;
}
/** Test helper: restore compile-time defaults. */
export function resetHighComplexityThresholds() {
ccThreshold = DEFAULT_HIGH_COMPLEXITY_THRESHOLD;
depthThreshold = DEFAULT_HIGH_COMPLEXITY_DEPTH_THRESHOLD;
}
export const highComplexityRule = {
id: "high-complexity",
requires: ["file.functionSummaries"],
appliesTo(ctx) {
return /\.tsx?$/.test(ctx.filePath);
},
evaluate(ctx, store) {
const fns = store.getFileFact(ctx.filePath, "file.functionSummaries") ?? [];
const diagnostics = [];
const configuredCcThreshold = ctx.projectConfig?.rules["high-complexity"]?.threshold;
const activeCcThreshold = configuredCcThreshold ?? ccThreshold;
for (const f of fns) {
// A describe()/it()/test() wrapper's own complexity/nesting-depth
// aggregates branches from ALL of its nested test bodies (#577) — e.g. a
// `for` loop inside each of several `it()`s sums into the enclosing
// `describe()`'s cyclomatic complexity, even though no single test is
// complex. Unlike high-fan-out's call-name filter, there's no per-call
// signal to exclude for branch counting, so the equivalent fix here is to
// skip the organizer function itself. Genuinely complex test HELPER
// functions don't call it/describe/test themselves, so they're unaffected.
if (isTestSuiteOrganizer(f.outgoingCalls))
continue;
const ccBreached = f.cyclomaticComplexity >= activeCcThreshold;
const depthBreached = f.maxNestingDepth >= depthThreshold;
if (!ccBreached && !depthBreached)
continue;
const parts = [];
if (ccBreached)
parts.push(`cyclomatic complexity ${f.cyclomaticComplexity}`);
if (depthBreached)
parts.push(`nesting depth ${f.maxNestingDepth}`);
diagnostics.push({
id: `high-complexity:${ctx.filePath}:${f.line}`,
tool: "fact-rules",
rule: "high-complexity",
filePath: ctx.filePath,
line: f.line,
column: f.column,
severity: "warning",
semantic: "warning",
message: `'${f.name}' has ${parts.join(" and ")} — consider breaking it up`,
});
}
return diagnostics;
},
};