The device renders in Chrome 113, which predates relaxed CSS nesting, so
a nested rule whose selector starts with an element name is not a parse
error anyone would notice -- the rule simply does not exist, there and
nowhere else. Three were live in `index.css`, and the one that mattered
was the `text-overflow: ellipsis` on the bottom bar's title and artist,
which had therefore never truncated on the device. No tier here can see
the class at all: the component tier, the e2e tier and `make ui-visual`
all run a current engine, where the rule applies normally.
So `make css-check` carries a second script. It reads `index.css` and
the `css` literals in `src/**/*.ts` alike, since a shadow-root
stylesheet is parsed by the same engine, and it names the file, the line
and the fix -- a leading `&`, which is valid in both syntaxes.
The detection walks blocks rather than matching lines, and both things
it has to get right fall out of one rule: a rule is nested when a
*style* rule is somewhere above it, not when its immediate parent is a
block. That leaves `@media (...) { bottom-nav { ... } }` at the top
level alone, which is the majority of what a regex over the file would
report, and still flags the same rule inside an at-rule that is itself
inside a style rule. Strings and comments are read through, so a brace
in a `url()` is not a block.
The tree has no violation left, so the check would pass just as happily
over an empty glob: it refuses one, and `test/utils/css-nesting.test.ts`
pins the semantics that make the sweep mean something. The literal
scanner the two checks share is lifted into `css-literals.mjs`
unchanged, except that a `${}` substitution is now blanked keeping its
newlines so a line number survives it.
Closes #154
63 lines
2.3 KiB
JavaScript
63 lines
2.3 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* A backtick inside a comment in a `css` tagged template literal ends
|
|
* the literal.
|
|
*
|
|
* This has cost four sessions across three plans. It is written down in
|
|
* CLAUDE.md, in the yellowjacket-dev skill and in NOTES.md, and it was
|
|
* read twice in the session it then cost a cycle in — so it is a check
|
|
* now rather than a fourth paragraph. Knowledge that has been ignored
|
|
* three times is not a knowledge problem.
|
|
*
|
|
* What makes it expensive is not the mistake but the *report*. The
|
|
* literal ends early, the rest of the CSS is parsed as JavaScript, and
|
|
* what comes back is `Expected "]" but found "inline"` pointing at a
|
|
* line of prose — or, when it happens in a shared module like
|
|
* `tokens.css.ts`, every test in the suite failing to import and an
|
|
* output that reads like a broken test runner. And `make dev-headless`
|
|
* leaves the dev server serving the last good bundle, so the page still
|
|
* works and still shows the old behaviour.
|
|
*
|
|
* The detection is exact rather than heuristic. If a backtick inside a
|
|
* comment closed the literal early, then the text the parser *did* take
|
|
* as the literal contains an unterminated `/*`. Nothing else produces
|
|
* that, and a legitimate literal cannot contain one.
|
|
*/
|
|
import { globSync, readFileSync } from 'node:fs';
|
|
|
|
import { taggedLiterals } from './css-literals.mjs';
|
|
|
|
const TAGS = ['css', 'html', 'svg'];
|
|
|
|
const files = globSync('src/**/*.ts', { cwd: process.cwd() });
|
|
const problems = [];
|
|
|
|
for (const file of files) {
|
|
const src = readFileSync(file, 'utf8');
|
|
|
|
for (const { tag, body, line } of taggedLiterals(src, TAGS)) {
|
|
const opens = (body.match(/\/\*/g) ?? []).length;
|
|
const closes = (body.match(/\*\//g) ?? []).length;
|
|
|
|
if (opens > closes) problems.push({ file, line, tag });
|
|
}
|
|
}
|
|
|
|
if (problems.length > 0) {
|
|
for (const p of problems) {
|
|
console.error(
|
|
`${p.file}:${p.line}: unterminated /* inside a ${p.tag}\`\` literal — ` +
|
|
'a backtick in a comment ends the literal early',
|
|
);
|
|
}
|
|
|
|
console.error(
|
|
`\ncss-literal-check: ${problems.length} problem(s). ` +
|
|
'Remove the backticks from the comment; markdown quoting does not ' +
|
|
'survive a tagged template.',
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`css-literal-check: ${files.length} files, no broken literals`);
|