/* * The comparison behind lint:format (scripts/format-lint.mjs): which files * Prettier would rewrite, against the list of files that were unformatted * when the check came in. */ import { join } from 'node:path'; import * as prettier from 'prettier'; import { fileList, listFiles, lowerList, RatchetError, readBaseline, readText, toKey, writeBaseline, } from './ratchet.mjs'; export const DIRS = ['src', 'tests']; export const accepts = (key) => /\.(?:tsx?|css)$/.test(key); /** * Whether Prettier leaves the file as it is, with the config that applies to * it. The text is read with LF line ends, so a Windows checkout agrees with CI * about endOfLine. A file Prettier cannot parse is not formatted. */ export async function isFormatted(path) { const options = await prettier.resolveConfig(path); if (!options) throw new RatchetError(`no Prettier config applies to ${path}`); try { return await prettier.check(readText(path), { ...options, filepath: path }); } catch { return false; } } /** The files under root that Prettier would rewrite, keyed by their path from root. */ export async function unformatted(root) { const found = []; for (const path of listFiles(root, DIRS, accepts)) if (!(await isFormatted(path))) found.push(toKey(root, path)); return found; } /** * Runs the check against the list at baselinePath. A file outside the list * must be formatted. A listed file that is formatted now, or gone, fails the * check as well until --update takes it out: the list only shrinks, and a * stale entry would let the next file at that path in unformatted. Returns * the exit code. */ export async function check({ root, baselinePath = join(root, 'scripts/format-baseline.json'), update = false, list = false, log = console.log, error = console.error, }) { const found = await unformatted(root); let baseline = readBaseline(baselinePath, fileList); if (update) { baseline = lowerList(baseline, found); writeBaseline(baselinePath, baseline); } const allowed = new Set(baseline); const fresh = found.filter((key) => !allowed.has(key)); const now = new Set(found); const stale = baseline.filter((key) => !now.has(key)); if (list) for (const key of found) log(`${allowed.has(key) ? 'listed' : 'new '} ${key}`); for (const key of fresh) { error(`FAIL ${key} is not formatted. Run: npx prettier --write ${key}`); } for (const key of stale) { error(`FAIL ${key} is listed in scripts/format-baseline.json but is formatted now or gone.`); } if (stale.length) error('Run npm run lint:format -- --update to take the formatted files off the list.'); log(`format: ${found.length} unformatted file(s), ${baseline.length} listed in the baseline`); return fresh.length || stale.length ? 1 : 0; }