You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
82 lines
3.1 KiB
82 lines
3.1 KiB
/**
|
|
* rtl-check — the mechanical guard of the build contract's RTL row
|
|
* (docs/guides/component-guide.md). Until now that row's guard was
|
|
* "rule (LIVE)", i.e. human review, and it is how the Slider defect of
|
|
* `013ceac57` reached production: three vertical rules paired a logical
|
|
* `inset-inline-start` with a physical `translateX`, and nothing in CI looked
|
|
* at CSS declarations — `eidos-lint` classifies selectors.
|
|
*
|
|
* RTL-1 a physical inline-axis displacement (`transform: translateX/
|
|
* translate/translate3d`, or the independent `translate` property) in
|
|
* a block that also anchors on `inset-inline*` / `margin-inline*`.
|
|
* The anchor flips with the writing direction, the displacement does
|
|
* not: correct in LTR, a full element-width off-axis in RTL.
|
|
*
|
|
* RTL-2 inside a `:dir()` block, one logical face turned off and the other
|
|
* repainted. The property had ALREADY mirrored, so relocating it
|
|
* cancels the mirror and parks it opposite the siblings left alone.
|
|
* This is the shape that survived the `[dir='rtl']` → `:dir(rtl)`
|
|
* migration untouched, because that pass rewrote selectors and never
|
|
* read a body — a mechanical migration inherits what it translates.
|
|
*
|
|
* Both rules, their deliberate exclusions and their escape hatches
|
|
* (`rtl-physical:` / `rtl-mirror:`) live in `src/uix/eidos/rtl-lint.ts`.
|
|
*/
|
|
|
|
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
import { join, relative } from 'node:path';
|
|
|
|
import { lintRtlGeometry, lintRtlMirror } from '../src/uix/eidos/rtl-lint';
|
|
|
|
const REPO = process.cwd();
|
|
const EIDOS_DIR = join(REPO, 'src', 'uix', 'eidos');
|
|
|
|
function walk(dir: string, out: string[] = []): string[] {
|
|
for (const entry of readdirSync(dir)) {
|
|
const path = join(dir, entry);
|
|
if (statSync(path).isDirectory()) walk(path, out);
|
|
else if (entry.endsWith('.css')) out.push(path);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function main(): void {
|
|
const files = walk(EIDOS_DIR);
|
|
let errors = 0;
|
|
|
|
for (const file of files) {
|
|
const rel = relative(REPO, file).replace(/\\/g, '/');
|
|
const css = readFileSync(file, 'utf8');
|
|
|
|
for (const f of lintRtlGeometry(css)) {
|
|
errors++;
|
|
console.error(
|
|
`ERROR [RTL-1] ${rel}:${f.line} — physical "${f.translate}" against logical ` +
|
|
`${f.anchors.map((a) => `"${a}"`).join(' + ')} in ${f.selector}`
|
|
);
|
|
}
|
|
|
|
for (const f of lintRtlMirror(css)) {
|
|
errors++;
|
|
console.error(
|
|
`ERROR [RTL-2] ${rel}:${f.line} — double flip: "${f.neutral}" turns a face off and ` +
|
|
`"${f.payload}" repaints the other, but ${f.family} had already mirrored ` +
|
|
`in ${f.selector}`
|
|
);
|
|
}
|
|
}
|
|
|
|
if (errors > 0) {
|
|
console.error(
|
|
`\nrtl-check: ${errors} error(s) across ${files.length} files\n` +
|
|
'RTL-1: express the offset logically (margin-inline-start: calc(…)), go fully\n' +
|
|
'physical (left/right), or mark the line /* rtl-physical: <reason> */ if meant.\n' +
|
|
'RTL-2: DELETE the rule and check the base declaration — it had already mirrored.'
|
|
);
|
|
process.exit(1);
|
|
}
|
|
console.log(`rtl-check: 0 error(s) across ${files.length} files`);
|
|
}
|
|
|
|
main();
|