/** * Block the presence-only `toBeDefined()` matcher in the test tree. * * The test-quality gate fails on a weak assertion the branch adds. This * check holds every test file to its recorded `toBeDefined()` count in * `registry.json`, and the count only falls (see "Test Quality Standards" * in AGENTS.md). The exact matcher `toBeUndefined()` stays allowed where * the code documents an absent value, so the pattern names `toBeDefined` * alone. */ import type { Counts, PerFileFinding } from "#scripts/check-runner.ts"; import { findWeakPattern, type TestQualityFinding, } from "#scripts/test-quality-audit-model.ts"; /** The matcher this check counts, in comment-and-string-blanked code. */ export const PRESENCE_PATTERN = /\.toBeDefined\s*\(\s*\)/g; const RULE = "presence-only"; const PROBLEM = "presence-only assertion: toBeDefined() checks presence alone"; const FIX = "assert the value, shape, or invariant instead"; const UPDATE_COMMAND = "deno task check:presence-assertions --update"; /** Every `toBeDefined()` call in one test file, with its position. */ export const presenceFindings = ( path: string, content: string, ): TestQualityFinding[] => findWeakPattern(path, content, { message: RULE, pattern: PRESENCE_PATTERN }); /** One file's findings against its recorded count: the calls past the count * fail, a fallen count asks for its record, and a file with no call left but * an entry asks for the entry's deletion. */ export const findIssues = ( file: string, content: string, accepted: Counts, ): PerFileFinding[] => { const findings = presenceFindings(file, content); const recorded = accepted[file] ?? 0; if (findings.length === 0) { return file in accepted ? [ { fix: "delete the entry in scripts/check-presence-assertions/registry.json", line: 1, problem: "holds no presence-only assertion", rule: "stale-entry", }, ] : []; } if (findings.length < recorded) { return [ { fix: `run \`${UPDATE_COMMAND}\` to record the count`, line: 1, problem: `holds ${findings.length} presence-only assertions, fewer than the recorded ${recorded}`, rule: "improved", }, ]; } return findings.slice(recorded).map((finding) => ({ fix: FIX, line: finding.line, problem: PROBLEM, rule: RULE, })); };