#!/usr/bin/env node /** * The receipt checker. * * Every demo quotes the code that computes its reward, verbatim, next to the * number that code produced. That quote is the page's whole claim to being * evidence rather than marketing, so the thing it quotes has to exist and the * region it quotes has to resolve. A receipt panel that silently renders empty, * or renders the wrong forty lines because someone inserted a function above * the marker, is worse than no receipt at all. * * ── MARKER SYNTAX ────────────────────────────────────────────────────────── * * `RewardSpec.source` is `{ path, code, marker? }`. Without a `marker` the * whole file is the receipt. With one, the receipt is the region delimited by * a matched pair of comment lines in the source file itself: * * # region: pig-demo/ * ... the quoted lines ... * # endregion: pig-demo/ * * Rules, all enforced here: * * · The marker lines are EXCLUSIVE — neither appears in the quoted region. * · EXACTLY ONE pair per file per marker. Zero is a broken receipt; two or * more is ambiguous, and an ambiguous receipt silently quotes whichever * region the reader's implementation happened to find first. Both are * fatal. * · `region` must come before `endregion`, and the region must be non-empty * once the marker lines are removed. * · The comment token is `#` (Python) or `//` (TypeScript), then optional * whitespace. Everything else on the line is ignored, so * `# region: pig-demo/consistency (see PR #41)` is legal. * · The `pig-demo/` prefix is required. It is what makes these greppable and * stops an editor's own `#region` folding markers from being read as * receipts. * * Marker names are `[a-z0-9][a-z0-9-]*`: they end up in a grep, a CI message * and a code comment, and mixed case in all three is a bug factory. */ import path from 'node:path'; import { Report, abs, allDemoDirs, demoFiles, evalLiteral, exists, isUnresolved, literalsAfter, read, rel, } from './_lib.mjs'; const report = new Report('check-receipts'); const RULE_PATH = 'reward.source.path'; const RULE_MARKER = 'reward.source.marker'; const MARKER_NAME = /^[a-z0-9][a-z0-9-]*$/; const markerLine = (kind, marker) => new RegExp(`^[ \\t]*(?:#|//)[ \\t]*${kind}:[ \\t]*pig-demo/${marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![\\w-])`, 'm'); /** Every `source: { ... }` literal in a demo, wherever the demo chose to put it. */ function receipts(slug) { const out = []; for (const file of demoFiles(slug)) { const src = read(file); for (const literal of literalsAfter(src, /\bsource\s*:\s*/, '{')) { // `code` is legitimately an identifier here — the Python arrives through // a Vite `?raw` import, which cannot resolve in plain Node — so the // literal is read leniently and only `path` and `marker` are trusted. const evaluated = evalLiteral(literal.text, 'RewardSpec.source', { lenient: true }); if (!evaluated.ok) { out.push({ file, error: evaluated.error }); continue; } const value = evaluated.value; if (!value || typeof value !== 'object' || !('path' in value)) continue; // some other `source:` key const line = src.slice(0, literal.start).split('\n').length; out.push({ file, line, value }); } } return out; } const slugs = allDemoDirs().filter((s) => !s.startsWith('_')); if (slugs.length === 0) { console.log('check-receipts: no demos to check.'); process.exit(0); } for (const slug of slugs) { const found = receipts(slug); if (found.length === 0) { report.fail( rel(path.join(abs('src', 'demos'), slug)), RULE_PATH, 'no `reward.source` found. Every demo quotes the code that computes its reward; without a source ' + 'the receipt panel has nothing to show and the page is asserting its numbers rather than proving them.', ); continue; } for (const receipt of found) { const where = `${rel(receipt.file)}${receipt.line ? `:${receipt.line}` : ''}`; if (receipt.error) { report.fail(where, RULE_PATH, receipt.error); continue; } const { path: sourcePath, marker } = receipt.value; if (typeof sourcePath !== 'string' || sourcePath.trim() === '') { report.fail( where, RULE_PATH, `\`path\` is ${isUnresolved(sourcePath) ? 'an imported constant' : JSON.stringify(sourcePath)}. ` + 'It must be a literal repo-relative path, e.g. "envs/wordle_five/wordle_five/rubric.py", ' + 'so a reader can open the same file this page quotes.', ); continue; } // Repo-relative by contract. A leading slash would mean the site root, // which is not where the Python lives, and an absolute disk path would // only work on the machine that wrote it. const clean = sourcePath.replace(/^\.\//, ''); if (path.isAbsolute(clean)) { report.fail(where, RULE_PATH, `\`path\` is absolute ("${sourcePath}"). Use a repo-relative path.`); continue; } const onDisk = abs(clean); if (!report.check(exists(onDisk), where, RULE_PATH, `\`path\` is "${sourcePath}", and ${rel(onDisk)} does not exist.`)) { continue; } if (marker === undefined || marker === null) continue; // whole file is the receipt if (typeof marker !== 'string' || !MARKER_NAME.test(marker)) { report.fail( where, RULE_MARKER, `\`marker\` is ${JSON.stringify(marker)}. Markers are lower-kebab-case (${MARKER_NAME}) because they ` + 'appear in a grep, a CI message and a code comment, and mixed case in all three is a bug factory.', ); continue; } const body = read(onDisk); const lines = body.split('\n'); const opens = []; const closes = []; lines.forEach((line, i) => { if (markerLine('region', marker).test(line)) opens.push(i); if (markerLine('endregion', marker).test(line)) closes.push(i); }); const syntax = `Expected exactly one "# region: pig-demo/${marker}" and one "# endregion: pig-demo/${marker}".`; if (opens.length !== 1 || closes.length !== 1) { report.fail( rel(onDisk), RULE_MARKER, `marker "${marker}" (referenced from ${where}) resolves to ${opens.length} region marker(s) and ` + `${closes.length} endregion marker(s). ${syntax} ` + (opens.length > 1 || closes.length > 1 ? 'More than one pair is ambiguous: the receipt would quote whichever region the reader found first.' : 'A missing marker renders the receipt panel empty, which reads as the code not existing.'), ); continue; } const [open] = opens; const [close] = closes; if (close <= open) { report.fail( rel(onDisk), RULE_MARKER, `marker "${marker}": the endregion is on line ${close + 1}, at or before the region on line ${open + 1}.`, ); continue; } const region = lines.slice(open + 1, close); report.check( region.some((line) => line.trim() !== ''), rel(onDisk), RULE_MARKER, `marker "${marker}" delimits an empty region between lines ${open + 1} and ${close + 1}. The markers are ` + 'exclusive, so a pair on adjacent lines quotes nothing.', ); } } report.finish();