chore(scripts): remove dead references to a retired repository
Two comments in build-rollup-register.{mjs,test.mjs} used a since-deleted
repository as the example of a nested same-name git repo. Replaced with the
invented `example-repo/example-repo`; the test fixture (`outer/outer`) and
what it proves are unchanged.
Adds scripts/check-retired-refs.mjs (+ test): fails when a tracked text
file names a retired term in its content or path. The term list is
local-only (scripts/retired-terms.local.md, ignored via *.local.md) and
never tracked; a missing or empty list reports NOT CHECKED (exit 2), and
the repo-level test skips with that reason instead of passing silently.
Named exemption list, empty. Chosen *.local.md because the existing ignore
rule already covers it, so .gitignore is untouched.
Red on 69fb250 (112 files, 2 hits, exit 1) -> green (114 files, 0 hits).
Suite 217 -> 227 tests, 211 -> 221 pass; the same 6 check-okf-parity
failures before and after, caused by the worktree path breaking its
sibling-repo lookup (9/9 green in the main checkout). check-versions:
12/12 OK, 0 ERROR.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
74c27d0e8e
commit
4ecc6e2b6a
4 changed files with 260 additions and 2 deletions
146
scripts/check-retired-refs.test.mjs
Normal file
146
scripts/check-retired-refs.test.mjs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
// Tests for the retired-reference gate. The unit tests use synthetic terms; only the last
|
||||
// test reads the real, local-only term list, and it SKIPS loudly when that file is absent.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync, spawnSync } from 'node:child_process';
|
||||
import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import {
|
||||
DEFAULT_TERMS_FILE,
|
||||
EXEMPT_PATHS,
|
||||
loadTerms,
|
||||
buildPattern,
|
||||
scanEntries,
|
||||
runCheck,
|
||||
} from './check-retired-refs.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const REPO = join(HERE, '..');
|
||||
const CLI = join(HERE, 'check-retired-refs.mjs');
|
||||
const TERMS = ['retired-alpha', 'Beta.Tool', 'gamma'];
|
||||
const PATTERN = buildPattern(TERMS);
|
||||
|
||||
function tmpDir(prefix) {
|
||||
return mkdtempSync(join(tmpdir(), prefix));
|
||||
}
|
||||
|
||||
function termsFile(lines) {
|
||||
const file = join(tmpDir('retired-terms-'), 'terms.local.md');
|
||||
writeFileSync(file, lines.join('\n'));
|
||||
return file;
|
||||
}
|
||||
|
||||
test('loadTerms: one term per line, blanks and # lines ignored, missing file -> null', () => {
|
||||
const file = termsFile(['# comment', '', ' retired-alpha ', 'gamma', '']);
|
||||
assert.deepEqual(loadTerms(file), ['retired-alpha', 'gamma']);
|
||||
assert.equal(loadTerms(join(tmpDir('retired-none-'), 'absent.md')), null);
|
||||
});
|
||||
|
||||
test('buildPattern: case-insensitive plain substrings, regex metacharacters escaped', () => {
|
||||
assert.match('uses RETIRED-ALPHA here', PATTERN);
|
||||
assert.match('beta.tool', PATTERN);
|
||||
assert.doesNotMatch('betaXtool', PATTERN, '"." is literal, not a wildcard');
|
||||
assert.doesNotMatch('repo-mailbox, check-versions', PATTERN);
|
||||
assert.throws(() => buildPattern([]), /empty term list/);
|
||||
});
|
||||
|
||||
test('scanEntries reports content hits with line numbers and path hits', () => {
|
||||
const findings = scanEntries(
|
||||
[
|
||||
{ path: 'clean.md', content: 'nothing here\n' },
|
||||
{ path: 'a.md', content: 'line one\nuses retired-alpha/retired-alpha\n' },
|
||||
{ path: 'docs/gamma.md', content: 'clean body\n' },
|
||||
],
|
||||
{ pattern: PATTERN },
|
||||
);
|
||||
assert.deepEqual(
|
||||
findings.map((f) => [f.path, f.kind, f.line]),
|
||||
[
|
||||
['a.md', 'content', 2],
|
||||
['docs/gamma.md', 'path', 0],
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test('scanEntries skips binary content but still checks its path', () => {
|
||||
const findings = scanEntries(
|
||||
[
|
||||
{ path: 'img.png', content: null },
|
||||
{ path: 'gamma.bin', content: null },
|
||||
],
|
||||
{ pattern: PATTERN },
|
||||
);
|
||||
assert.deepEqual(findings.map((f) => f.path), ['gamma.bin']);
|
||||
});
|
||||
|
||||
test('scanEntries honours the named exemption list', () => {
|
||||
const entries = [{ path: 'keep.md', content: 'gamma' }];
|
||||
assert.equal(scanEntries(entries, { pattern: PATTERN }).length, 1);
|
||||
assert.equal(scanEntries(entries, { pattern: PATTERN, exempt: ['keep.md'] }).length, 0);
|
||||
assert.deepEqual(EXEMPT_PATHS, [], 'the shipped exemption list is empty');
|
||||
});
|
||||
|
||||
function tmpRepo(files) {
|
||||
const root = tmpDir('retired-refs-');
|
||||
execFileSync('git', ['init', '-q', root]);
|
||||
for (const [path, body] of Object.entries(files)) {
|
||||
mkdirSync(dirname(join(root, path)), { recursive: true });
|
||||
writeFileSync(join(root, path), body);
|
||||
}
|
||||
execFileSync('git', ['-C', root, 'add', '-A']);
|
||||
return root;
|
||||
}
|
||||
|
||||
test('runCheck reads the git index: a staged hit is found, an untracked one is not', () => {
|
||||
const root = tmpRepo({ 'README.md': 'points at gamma\n' });
|
||||
writeFileSync(join(root, 'untracked.md'), 'retired-alpha');
|
||||
const { checked, findings } = runCheck(root, TERMS);
|
||||
assert.equal(checked, 1);
|
||||
assert.deepEqual(findings.map((f) => `${f.path}:${f.line}`), ['README.md:1']);
|
||||
});
|
||||
|
||||
test('CLI exits 1 on a hit and 0 on a clean tree, always printing the denominator', () => {
|
||||
const terms = termsFile(TERMS);
|
||||
const run = (root) => spawnSync(process.execPath, [CLI, root, '--terms', terms], { encoding: 'utf8' });
|
||||
const dirty = run(tmpRepo({ 'x.md': 'retired-alpha' }));
|
||||
assert.equal(dirty.status, 1);
|
||||
assert.match(dirty.stdout, /\[ERROR\] retired reference — x\.md:1/);
|
||||
assert.match(dirty.stdout, /checked 1 tracked files against 3 terms — 1 hit/);
|
||||
const clean = run(tmpRepo({ 'x.md': 'clean\n' }));
|
||||
assert.equal(clean.status, 0);
|
||||
assert.match(clean.stdout, /checked 1 tracked files against 3 terms — 0 hit/);
|
||||
});
|
||||
|
||||
test('CLI fails on an empty index: 0 files checked verified nothing', () => {
|
||||
const empty = tmpDir('retired-refs-empty-');
|
||||
execFileSync('git', ['init', '-q', empty]);
|
||||
const r = spawnSync(process.execPath, [CLI, empty, '--terms', termsFile(TERMS)], { encoding: 'utf8' });
|
||||
assert.equal(r.status, 1);
|
||||
assert.match(r.stdout, /verified nothing/);
|
||||
});
|
||||
|
||||
test('CLI reports NOT CHECKED (exit 2) for a missing or empty term list, never a pass', () => {
|
||||
const root = tmpRepo({ 'x.md': 'retired-alpha' });
|
||||
const missing = spawnSync(process.execPath, [CLI, root, '--terms', join(root, 'absent.md')], { encoding: 'utf8' });
|
||||
assert.equal(missing.status, 2);
|
||||
assert.match(missing.stdout, /NOT CHECKED — term list missing/);
|
||||
const empty = spawnSync(process.execPath, [CLI, root, '--terms', termsFile(['# only a comment'])], { encoding: 'utf8' });
|
||||
assert.equal(empty.status, 2);
|
||||
assert.match(empty.stdout, /NOT CHECKED — term list empty/);
|
||||
});
|
||||
|
||||
// The FERDIG criterion: this repository's own tracked files carry zero retired references.
|
||||
// The real term list is local-only by design; without it this test cannot measure anything,
|
||||
// so it skips with the reason instead of passing silently.
|
||||
test('this repository: 0 retired references across its tracked files', (t) => {
|
||||
const terms = loadTerms();
|
||||
if (!terms || terms.length === 0) {
|
||||
t.skip(`NOT CHECKED — local term list missing or empty: ${DEFAULT_TERMS_FILE}`);
|
||||
return;
|
||||
}
|
||||
const { checked, findings } = runCheck(REPO, terms);
|
||||
assert.ok(checked > 0, 'checked 0 files — verified nothing');
|
||||
assert.deepEqual(findings.map((f) => `${f.path}:${f.line}`), []);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue