diff --git a/auditor/checks/static.ts b/auditor/checks/static.ts new file mode 100644 index 0000000..954bfd5 --- /dev/null +++ b/auditor/checks/static.ts @@ -0,0 +1,147 @@ +// Static diff check — grep-style, no AST, no LLM. Looks for patterns +// that are high-signal evidence of placeholder code. +// +// Findings are severity-graded: +// block — explicit non-impl markers (unimplemented!, todo!, +// panic!("not implemented"), throw new Error("not implemented")) +// warn — TODO / FIXME / XXX / HACK comments on added lines, +// new struct fields with no read-site anywhere in the diff, +// suspiciously-empty function bodies ({ Ok(()) } / {} when +// the commit message claims the fn "implements" something) +// info — hardcoded "test" / "dummy" / "placeholder" strings in +// added lines (could be real, just flag for inspection) +// +// Consumes: raw unified diff text from Gitea. + +import type { Finding } from "../types.ts"; + +// Rust + TypeScript patterns that almost always indicate "this is +// not actually implemented yet." +const BLOCK_PATTERNS: Array<{ re: RegExp; why: string }> = [ + { re: /\bunimplemented!\s*\(/, why: "unimplemented!() macro call" }, + { re: /\btodo!\s*\(/, why: "todo!() macro call" }, + { re: /panic!\s*\(\s*"(?:not implemented|TODO|not yet|unimpl)/i, why: "panic! with not-implemented message" }, + { re: /throw\s+new\s+Error\s*\(\s*['"](?:not implemented|TODO|unimpl)/i, why: "throw Error 'not implemented'" }, +]; + +const WARN_COMMENT_PATTERNS: Array<{ re: RegExp; why: string }> = [ + { re: /^\+.*\/\/\s*(TODO|FIXME|XXX|HACK)\b/i, why: "TODO/FIXME/XXX/HACK comment added" }, + { re: /^\+.*#\s*(TODO|FIXME|XXX|HACK)\b/i, why: "TODO/FIXME/XXX/HACK comment added" }, +]; + +const INFO_HARDCODED_PATTERNS: Array<{ re: RegExp; why: string }> = [ + { re: /"(?:placeholder|dummy|foobar|xxx|replaceme|changeme)"/i, why: "suspicious hardcoded string" }, +]; + +export function runStaticCheck(diff: string): Finding[] { + const findings: Finding[] = []; + + // Per-file walk: only look at ADDED lines (prefix '+' but not '+++' + // which is the diff header). + const perFile = splitDiffByFile(diff); + + for (const [path, lines] of perFile) { + // Skip diff bookkeeping + pure-delete files + if (!lines.some(l => l.startsWith("+") && !l.startsWith("+++"))) continue; + + for (let idx = 0; idx < lines.length; idx++) { + const line = lines[idx]; + if (!line.startsWith("+") || line.startsWith("+++")) continue; + const added = line.slice(1); + + for (const { re, why } of BLOCK_PATTERNS) { + if (re.test(added)) { + findings.push({ + check: "static", + severity: "block", + summary: `${why} in ${path}`, + evidence: [`${path}:+${idx + 1}: ${added.trim().slice(0, 160)}`], + }); + } + } + for (const { re, why } of WARN_COMMENT_PATTERNS) { + if (re.test(line)) { + findings.push({ + check: "static", + severity: "warn", + summary: `${why} in ${path}`, + evidence: [`${path}:+${idx + 1}: ${added.trim().slice(0, 160)}`], + }); + } + } + for (const { re, why } of INFO_HARDCODED_PATTERNS) { + if (re.test(added)) { + findings.push({ + check: "static", + severity: "info", + summary: `${why} in ${path}`, + evidence: [`${path}:+${idx + 1}: ${added.trim().slice(0, 160)}`], + }); + } + } + } + + // "Field added but never read" heuristic — catches exactly the + // Phase 45 DocRef placeholder pattern. Limited to the diff itself: + // we're not doing a full-codebase grep here (too noisy; callers + // elsewhere might exist). The point is: if NEITHER this diff nor + // any other line in the diff reads the field, the PR is shipping + // state without a consumer. + const addedLines = lines.filter(l => l.startsWith("+") && !l.startsWith("+++")) + .map(l => l.slice(1)); + const newFields = extractNewFields(addedLines); + for (const field of newFields) { + const readPattern = new RegExp(`[\\.:]\\s*${escape(field)}\\b|\\b${escape(field)}\\s*:`); + // The definition line itself matches readPattern — filter it out + // by requiring at least TWO lines in the diff mention the field + // (one defines, one reads). + const hits = addedLines.filter(l => readPattern.test(l)); + if (hits.length < 2) { + findings.push({ + check: "static", + severity: "warn", + summary: `field '${field}' added in ${path} but no read-site in the diff — could be placeholder state without a consumer`, + evidence: [`${path}: added '${field}' with no reader; rest of diff has ${hits.length - 1} mentions`], + }); + } + } + } + + return findings; +} + +function splitDiffByFile(diff: string): Map { + const out = new Map(); + let current: string | null = null; + let buf: string[] = []; + for (const line of diff.split(/\r?\n/)) { + const m = line.match(/^diff --git a\/(\S+) b\/(\S+)/); + if (m) { + if (current) out.set(current, buf); + current = m[2]; + buf = []; + continue; + } + buf.push(line); + } + if (current) out.set(current, buf); + return out; +} + +// Extract new `pub name: Type,` fields from added lines. Rust syntax. +// Narrowly-scoped: only matches at the start of a trimmed line, +// requires `pub ` prefix, ignores `pub fn` / `pub struct` / etc. +function extractNewFields(addedLines: string[]): string[] { + const fields = new Set(); + for (const line of addedLines) { + const t = line.trim(); + // pub NAME: Type, + const m = t.match(/^pub\s+(?!fn\b|struct\b|enum\b|mod\b|use\b|trait\b|impl\b|const\b|static\b|type\b)(\w+)\s*:/); + if (m) fields.add(m[1]); + } + return Array.from(fields); +} + +function escape(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +}