Files
OmniRoute/tests/unit/build/new-code-mode.test.ts
Diego Rodrigues de Sa e Souza 8b7afc0eba feat(quality): new-code mode for the complexity and dead-code ratchets (Clean as You Code) (#12142)
A global ratchet ("total ≤ baseline") reds an innocent PR whenever the base
drifted, and lets a PR that adds 10 violations pass as long as someone else
removed 11 — both happened this week. On pull_request events quality.yml now
passes --base-ref <PR base SHA> to check:complexity-ratchets and check:dead-code
(file-size already had it); in that mode the gate compares HEAD with the
merge-base RESTRICTED to the files the PR touched:

- blocking: violations / dead exports the PR added in files it changed
  (complexityNewCode=, cognitiveComplexityNewCode=, deadExportsNewCode=)
- advisory: the global total vs the frozen baseline (re-frozen at release,
  watched by the nightly headroom job)

scripts/check/newCodeMode.mjs holds the git side (merge-base, changed files,
throwaway `git worktree` of the base with node_modules linked — no stash, no
checkout) and the pure comparison helpers (13 unit tests). ESLint runs only on
the changed files in both trees (~20 s); knip runs twice (~70 s).

Exercised locally against the last 8 merges: complexity flagged
src/lib/credentialHealth/scheduler.ts (2→3, cognitive 1→2) and dead-code flagged
src/lib/resilience/settings.ts:CredentialHealthCheckSettings — findings the
global totals were hiding under the relaxed baselines.

workflow_dispatch, the release-green sweep and the headroom job have no PR base
and keep the absolute comparison. Docs: QUALITY_GATES.md → "New-code mode".
2026-08-30 19:08:32 -03:00

113 lines
3.6 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import {
baseRefArg,
deadSymbolKeys,
diffNewCode,
filterScope,
newDeadSymbols,
perFileRuleCounts,
} from "../../../scripts/check/newCodeMode.mjs";
test("baseRefArg reads --base-ref <sha> and ignores a dangling flag", () => {
assert.equal(baseRefArg(["node", "x", "--base-ref", "abc123"]), "abc123");
assert.equal(baseRefArg(["node", "x", "--base-ref"]), null);
assert.equal(baseRefArg(["node", "x"]), null);
});
test("filterScope keeps only in-scope dirs/extensions, sorted and trimmed", () => {
const files = filterScope(
[
" src/a.ts",
"open-sse/b.tsx",
"tests/unit/c.test.ts",
"src/d.md",
"srcx/e.ts",
"",
"bin/f.mjs",
],
{ dirs: ["src", "open-sse", "bin"], exts: [".ts", ".tsx", ".mjs"] }
);
assert.deepEqual(files, ["bin/f.mjs", "open-sse/b.tsx", "src/a.ts"]);
});
test("perFileRuleCounts counts only the requested rules and relativizes absolute paths", () => {
const report = [
{
filePath: "/repo/src/a.ts",
messages: [
{ ruleId: "complexity" },
{ ruleId: "max-lines-per-function" },
{ ruleId: "no-unused-vars" },
],
},
{ filePath: "/repo/src/b.ts", messages: [{ ruleId: "sonarjs/cognitive-complexity" }] },
{ filePath: "src/c.ts", messages: [] },
];
const cyc = perFileRuleCounts(report, new Set(["complexity", "max-lines-per-function"]), "/repo");
assert.equal(cyc.get("src/a.ts"), 2);
assert.equal(cyc.get("src/b.ts"), 0);
assert.equal(cyc.get("src/c.ts"), 0);
const cog = perFileRuleCounts(report, new Set(["sonarjs/cognitive-complexity"]), "/repo");
assert.equal(cog.get("src/b.ts"), 1);
});
test("diffNewCode flags only changed files that grew; new files count from zero", () => {
const head = new Map([
["src/a.ts", 3],
["src/new.ts", 1],
["src/untouched.ts", 9],
]);
const base = new Map([
["src/a.ts", 3],
["src/untouched.ts", 2],
]);
const r = diffNewCode(head, base, ["src/a.ts", "src/new.ts"]);
assert.deepEqual(r.regressions, [{ file: "src/new.ts", base: 0, head: 1 }]);
assert.equal(r.head, 4);
assert.equal(r.base, 3);
assert.equal(r.delta, 1);
// a file that got better is not a regression
const better = diffNewCode(new Map([["src/a.ts", 1]]), new Map([["src/a.ts", 3]]), ["src/a.ts"]);
assert.deepEqual(better.regressions, []);
assert.equal(better.delta, -2);
});
test("deadSymbolKeys covers exports, types, namespace members and unused files", () => {
const keys = deadSymbolKeys({
issues: [
{
file: "src/a.ts",
exports: [{ name: "foo" }],
types: [{ name: "Bar" }],
nsExports: [{ name: "ns" }],
},
{ file: "src/dead.ts", files: ["src/dead.ts"] },
],
});
assert.deepEqual([...keys].sort(), [
"src/a.ts:Bar",
"src/a.ts:foo",
"src/a.ts:ns",
"src/dead.ts:<file>",
]);
assert.equal(deadSymbolKeys(null).size, 0);
});
test("newDeadSymbols reports only symbols that are new on HEAD, in touched files by default", () => {
const base = { issues: [{ file: "src/a.ts", exports: [{ name: "old" }] }] };
const head = {
issues: [
{ file: "src/a.ts", exports: [{ name: "old" }, { name: "fresh" }] },
{ file: "src/other.ts", exports: [{ name: "orphaned" }] },
],
};
assert.deepEqual(newDeadSymbols(head, base, ["src/a.ts"]), ["src/a.ts:fresh"]);
assert.deepEqual(newDeadSymbols(head, base, ["src/a.ts"], { includeUntouched: true }), [
"src/a.ts:fresh",
"src/other.ts:orphaned",
]);
assert.deepEqual(newDeadSymbols(base, head, ["src/a.ts"]), []);
});