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.
79 lines
2.8 KiB
79 lines
2.8 KiB
/**
|
|
* Lint an eidos component CSS against its morfo contract.
|
|
*
|
|
* Usage: npx tsx scripts/eidos-lint.ts <component-kebab>
|
|
* e.g. npx tsx scripts/eidos-lint.ts toggle
|
|
*
|
|
* Reads `src/uix/morfo/components/{name}.ts` +
|
|
* `src/uix/eidos/components/{name}/{name}.css` (or legacy flat
|
|
* `components/{name}.css`), compiles the morfo, and reports which
|
|
* selectors are morfo-backed, eidos-only DOM signals/wrapper attrs, or
|
|
* invalid (value not in declared enum).
|
|
*
|
|
* Also validates the `data-event*` VALUES against the morfo catalogue and
|
|
* the sema canon — see `scripts/eidos-event-vocabulary.ts`.
|
|
*/
|
|
|
|
import { readFileSync, existsSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
|
|
import { compileMorfo } from '../src/uix/morfo/compile';
|
|
import { formatLintReport, lintEidosCss } from '../src/uix/eidos/lint';
|
|
import type { Morfo } from '../src/uix/morfo/types';
|
|
import {
|
|
formatEventFindings,
|
|
lintEventValues,
|
|
loadEventVocabulary
|
|
} from './eidos-event-vocabulary';
|
|
|
|
async function main() {
|
|
const name = process.argv[2];
|
|
if (!name) {
|
|
console.error('Usage: tsx scripts/eidos-lint.ts <component-kebab>');
|
|
process.exit(2);
|
|
}
|
|
|
|
const morfoPath = resolve(`src/uix/morfo/components/${name}.ts`);
|
|
const subfolderCssPath = resolve(`src/uix/eidos/components/${name}/${name}.css`);
|
|
const flatCssPath = resolve(`src/uix/eidos/components/${name}.css`);
|
|
const cssPath = existsSync(subfolderCssPath) ? subfolderCssPath : flatCssPath;
|
|
if (!existsSync(morfoPath)) {
|
|
console.error(`No morfo file at ${morfoPath}`);
|
|
process.exit(2);
|
|
}
|
|
if (!existsSync(cssPath)) {
|
|
console.error(`No eidos CSS at ${subfolderCssPath} or ${flatCssPath}`);
|
|
process.exit(2);
|
|
}
|
|
|
|
const mod = (await import(`../src/uix/morfo/components/${name}`)) as Record<string, unknown>;
|
|
const morfoExportName = `${name.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())}Morfo`;
|
|
const morfo = mod[morfoExportName] as Morfo | undefined;
|
|
if (!morfo) {
|
|
console.error(`Module exports ${Object.keys(mod).join(', ')}; expected ${morfoExportName}.`);
|
|
process.exit(2);
|
|
}
|
|
|
|
const compiled = compileMorfo(morfo);
|
|
const css = readFileSync(cssPath, 'utf-8');
|
|
const report = lintEidosCss(css, compiled);
|
|
console.log(formatLintReport(report));
|
|
|
|
// The structural report allowlists `data-event*` as eidos-only and never
|
|
// reads its value — the vocabulary is checked separately.
|
|
const eventFindings = lintEventValues(css, await loadEventVocabulary(), name);
|
|
if (eventFindings.length) {
|
|
console.log('');
|
|
console.log(' data-event* vocabulary (value must exist in a morfo / the canon):');
|
|
console.log(formatEventFindings(eventFindings, ' '));
|
|
}
|
|
|
|
const deadEvents = eventFindings.filter((f) => f.level === 'error').length;
|
|
if (report.counts.invalid > 0 || deadEvents > 0) process.exit(1);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(2);
|
|
});
|