mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 07:26:30 +00:00
ci(client): fail the count ratchets on an entry above the tree
lint:size, lint:layers, lint:rtl (both baselines), lint:skips and lint:warnings only failed a count that passed its entry. A count that fell below its entry was logged and nothing more, so a file that shrank could grow back to its old entry unseen, and the entry of a deleted file stayed behind for the next file at that path. lint:format and lint:offline already fail on such a stale entry, and the docs said these counts may only shrink. Each of them now fails on an entry that lowerCounts would lower or drop, naming its count or saying the file is gone, and tells to run --update, which still only lowers. staleCounts and reportStale in lib/ratchet.mjs hold the shared part. Every baseline matches the tree today, so none needed lowering. The client CLAUDE.md says so, and that CI lints through lint:warnings rather than lint:check.
This commit is contained in:
@@ -4,7 +4,17 @@
|
||||
import { ESLint } from 'eslint';
|
||||
import { join } from 'node:path';
|
||||
import ts from 'typescript';
|
||||
import { countMap, lowerCounts, readBaseline, readText, TEST_FILE, toKey, writeBaseline } from './ratchet.mjs';
|
||||
import {
|
||||
countMap,
|
||||
lowerCounts,
|
||||
readBaseline,
|
||||
readText,
|
||||
reportStale,
|
||||
staleCounts,
|
||||
TEST_FILE,
|
||||
toKey,
|
||||
writeBaseline,
|
||||
} from './ratchet.mjs';
|
||||
|
||||
/**
|
||||
* Where a warning is counted: in the app code, in the tests, or, for a
|
||||
@@ -151,7 +161,7 @@ export async function check({
|
||||
|
||||
for (const line of errors) error(`FAIL ${line}`);
|
||||
let grown = 0;
|
||||
let lowerable = 0;
|
||||
let stale = 0;
|
||||
for (const area of AREAS) {
|
||||
for (const [rule, n] of Object.entries(counts[area])) {
|
||||
const allowed = baseline[area][rule] ?? 0;
|
||||
@@ -163,7 +173,14 @@ export async function check({
|
||||
.map(([key, k]) => `${key} (${k})`);
|
||||
error(`FAIL ${area}: ${n} ${rule} warning(s), baseline ${allowed}. Most in: ${top.join(', ')}`);
|
||||
}
|
||||
for (const [rule, allowed] of Object.entries(baseline[area])) if ((counts[area][rule] ?? 0) < allowed) lowerable++;
|
||||
const fallen = staleCounts(baseline[area], counts[area]);
|
||||
reportStale(fallen, {
|
||||
file: 'eslint-baseline.json',
|
||||
command: 'lint:warnings',
|
||||
label: (rule) => `${area}: ${rule}`,
|
||||
error,
|
||||
});
|
||||
stale += fallen.length;
|
||||
}
|
||||
if (grown) {
|
||||
error(
|
||||
@@ -171,9 +188,8 @@ export async function check({
|
||||
'Run npx eslint <file> to see each one.'
|
||||
);
|
||||
}
|
||||
if (lowerable && !update) log(`${lowerable} rule count(s) fell below the baseline: run with --update to lower it.`);
|
||||
log(
|
||||
`eslint: ${errors.length} error(s); warnings ${AREAS.map((area) => `${area} ${sum(counts[area])}/${sum(baseline[area])}`).join(', ')} (now/baseline)`
|
||||
);
|
||||
return errors.length || grown ? 1 : 0;
|
||||
return errors.length || grown || stale ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import { join } from 'node:path';
|
||||
import { sourceGraph } from './imports.mjs';
|
||||
import { countMap, lowerCounts, readBaseline, writeBaseline } from './ratchet.mjs';
|
||||
import { countMap, lowerCounts, readBaseline, reportStale, staleCounts, writeBaseline } from './ratchet.mjs';
|
||||
|
||||
const VIEW = ['components', 'mobile', 'pages', 'hooks'];
|
||||
|
||||
@@ -82,7 +82,7 @@ export function check({
|
||||
}
|
||||
|
||||
const grown = Object.entries(counts).filter(([key, n]) => n > (baseline[key] ?? 0));
|
||||
const lowerable = Object.entries(baseline).filter(([key, n]) => (counts[key] ?? 0) < n);
|
||||
const stale = staleCounts(baseline, counts);
|
||||
|
||||
for (const [key, n] of grown) {
|
||||
error(`FAIL ${key}: ${n} import(s) against the layering, baseline ${baseline[key] ?? 0}:`);
|
||||
@@ -94,12 +94,10 @@ export function check({
|
||||
'a hook under hooks/, a type under types/) instead of importing upwards.'
|
||||
);
|
||||
}
|
||||
if (lowerable.length && !update) {
|
||||
log(`${lowerable.length} file(s) hold fewer of these imports than the baseline: run with --update to lower it.`);
|
||||
}
|
||||
reportStale(stale, { file: 'layers-baseline.json', command: 'lint:layers', root, error });
|
||||
const sum = (map) => Object.values(map).reduce((a, b) => a + b, 0);
|
||||
log(
|
||||
`layers: ${sum(counts)} import(s) against the layering in ${Object.keys(counts).length} file(s), baseline allows ${sum(baseline)}`
|
||||
);
|
||||
return grown.length ? 1 : 0;
|
||||
return grown.length || stale.length ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -126,6 +126,50 @@ export function lowerCounts(baseline, counts, floor = 0) {
|
||||
return lowered;
|
||||
}
|
||||
|
||||
/**
|
||||
* The entries of a count baseline that allow more than is measured now: a
|
||||
* count that fell below its entry, one back at floor or under it, or a file
|
||||
* that is gone. Exactly the entries lowerCounts would lower or drop. Each one
|
||||
* fails its check until --update lowers it, or the count could grow back to
|
||||
* the old entry unseen, and a deleted file would hand its allowance to the
|
||||
* next file at its path.
|
||||
*
|
||||
* @param {Record<string, number>} baseline
|
||||
* @param {Record<string, number>} counts
|
||||
* @param {number | ((key: string) => number)} [floor]
|
||||
* @returns {{ key: string, entry: number, now: number }[]}
|
||||
*/
|
||||
export function staleCounts(baseline, counts, floor = 0) {
|
||||
const lowered = lowerCounts(baseline, counts, floor);
|
||||
return Object.entries(baseline)
|
||||
.filter(([key, entry]) => lowered[key] !== entry)
|
||||
.map(([key, entry]) => ({ key, entry, now: counts[key] ?? 0 }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a FAIL line for each stale entry (see staleCounts) of the baseline
|
||||
* scripts/<file>, then how to lower them. With root, an entry whose file is
|
||||
* not there is called gone; label names an entry that is not a file path.
|
||||
*
|
||||
* @param {{ key: string, entry: number, now: number }[]} stale
|
||||
* @param {{ file: string, command: string, root?: string, label?: (key: string) => string, error: (line: string) => void }} options
|
||||
*/
|
||||
export function reportStale(stale, { file, command, root, label = (key) => key, error }) {
|
||||
for (const { key, entry, now } of stale) {
|
||||
const gone = root !== undefined && !existsSync(join(root, key));
|
||||
error(
|
||||
`FAIL ${label(key)} is held at ${entry} in scripts/${file}, ` +
|
||||
(gone ? 'but the file is gone.' : `but there are ${now} now.`)
|
||||
);
|
||||
}
|
||||
if (stale.length) {
|
||||
error(
|
||||
`Run npm run ${command} -- --update to lower the baseline with the change that made it smaller: ` +
|
||||
'an entry above the count lets it grow back unseen.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** The baseline entries that are still offenders: a list only ever loses files. */
|
||||
export function lowerList(baseline, offenders) {
|
||||
const now = new Set(offenders);
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
lowerCounts,
|
||||
readBaseline,
|
||||
readText,
|
||||
reportStale,
|
||||
staleCounts,
|
||||
TEST_FILE,
|
||||
toKey,
|
||||
writeBaseline,
|
||||
@@ -163,9 +165,8 @@ export function check({
|
||||
|
||||
const grown = Object.entries(counts).filter(([file, n]) => n > (baseline[file] ?? 0));
|
||||
const marked = Object.entries(markers).filter(([file, n]) => n > (allowedMarkers[file] ?? 0));
|
||||
const lowerable =
|
||||
Object.entries(baseline).filter(([file, n]) => (counts[file] ?? 0) < n).length +
|
||||
Object.entries(allowedMarkers).filter(([file, n]) => (markers[file] ?? 0) < n).length;
|
||||
const stale = staleCounts(baseline, counts);
|
||||
const staleMarkers = staleCounts(allowedMarkers, markers);
|
||||
|
||||
for (const [file, n] of grown) {
|
||||
error(
|
||||
@@ -182,12 +183,11 @@ export function check({
|
||||
'(a map, a measured position, a time axis) needs a reviewer to raise the entry by hand.'
|
||||
);
|
||||
}
|
||||
if (lowerable && !update) {
|
||||
log(`${lowerable} entr(ies) are above what the files hold now: run with --update to lower them.`);
|
||||
}
|
||||
reportStale(stale, { file: 'rtl-baseline.json', command: 'lint:rtl', root: join(root, 'src'), error });
|
||||
reportStale(staleMarkers, { file: 'rtl-disable-baseline.json', command: 'lint:rtl', root: join(root, 'src'), error });
|
||||
log(
|
||||
`rtl: ${sum(counts)} physical side(s) in ${Object.keys(counts).length} file(s), baseline allows ${sum(baseline)}; ` +
|
||||
`${sum(markers)} line(s) marked ${DISABLE}, baseline allows ${sum(allowedMarkers)}`
|
||||
);
|
||||
return grown.length || marked.length ? 1 : 0;
|
||||
return grown.length || marked.length || stale.length || staleMarkers.length ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
lowerCounts,
|
||||
readBaseline,
|
||||
readText,
|
||||
reportStale,
|
||||
staleCounts,
|
||||
TEST_FILE,
|
||||
toKey,
|
||||
writeBaseline,
|
||||
@@ -68,7 +70,9 @@ const limitOf = (key) => groupOf(key)?.limit ?? 0;
|
||||
|
||||
/**
|
||||
* Runs the check against the baseline at baselinePath. With update, the
|
||||
* baseline is lowered first. Returns the exit code.
|
||||
* baseline is lowered first. An entry above the file's size now, or for a
|
||||
* file that is gone, fails as well until --update lowers it. Returns the
|
||||
* exit code.
|
||||
*/
|
||||
export function check({
|
||||
root,
|
||||
@@ -86,7 +90,7 @@ export function check({
|
||||
}
|
||||
|
||||
const grown = Object.entries(sizes).filter(([key, n]) => n > Math.max(baseline[key] ?? 0, limitOf(key)));
|
||||
const lowerable = Object.entries(baseline).filter(([key, n]) => (sizes[key] ?? 0) < n);
|
||||
const stale = staleCounts(baseline, sizes, limitOf);
|
||||
|
||||
for (const [key, n] of grown) {
|
||||
const entry = baseline[key];
|
||||
@@ -96,11 +100,9 @@ export function check({
|
||||
'Move a concern into a module of its own instead of growing the file or packing its lines.'
|
||||
);
|
||||
}
|
||||
if (lowerable.length && !update) {
|
||||
log(`${lowerable.length} file(s) are smaller than their baseline now: run with --update to lower it.`);
|
||||
}
|
||||
reportStale(stale, { file: 'size-baseline.json', command: 'lint:size', root, error });
|
||||
log(
|
||||
`size: ${Object.keys(sizes).length} file(s), ${Object.keys(baseline).length} past their limit held at their baseline`
|
||||
);
|
||||
return grown.length ? 1 : 0;
|
||||
return grown.length || stale.length ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
lowerCounts,
|
||||
readBaseline,
|
||||
readText,
|
||||
reportStale,
|
||||
staleCounts,
|
||||
TEST_FILE,
|
||||
toKey,
|
||||
writeBaseline,
|
||||
@@ -139,12 +141,11 @@ export function check({
|
||||
if (grown.length) {
|
||||
error('Fix the test, delete it if its feature is gone, or use skipIf/runIf when it depends on the environment.');
|
||||
}
|
||||
const lowerable = Object.entries(baseline).filter(([key, n]) => (counts[key] ?? 0) < n);
|
||||
if (lowerable.length && !update)
|
||||
log(`${lowerable.length} file(s) skip fewer tests than the baseline: run with --update to lower it.`);
|
||||
const stale = staleCounts(baseline, counts);
|
||||
reportStale(stale, { file: 'skip-baseline.json', command: 'lint:skips', root, error });
|
||||
const sum = (map) => Object.values(map).reduce((a, b) => a + b, 0);
|
||||
log(
|
||||
`skips: ${sum(counts)} skipped or todo test(s), baseline allows ${sum(baseline)}; ${Object.keys(only).length} file(s) with .only`
|
||||
);
|
||||
return grown.length || Object.keys(only).length ? 1 : 0;
|
||||
return grown.length || stale.length || Object.keys(only).length ? 1 : 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user