fix(01-design-tokens-rams-foundation): WR-02 repair familiesFromShorthand + flag literal font: shorthands
This commit is contained in:
+38
-8
@@ -439,8 +439,19 @@ function checkDeclaration(prop, value, tokens) {
|
|||||||
violations.push(...literalHexViolations(value, tokens));
|
violations.push(...literalHexViolations(value, tokens));
|
||||||
if (prop.startsWith('--')) return violations; // custom property: color literals above are the checkable surface
|
if (prop.startsWith('--')) return violations; // custom property: color literals above are the checkable surface
|
||||||
violations.push(...shadowViolations(prop, value));
|
violations.push(...shadowViolations(prop, value));
|
||||||
if (prop === 'font-family' || prop === 'font') {
|
if (prop === 'font') {
|
||||||
if (!/var\(/i.test(value)) violations.push(...fontViolations(prop === 'font' ? familiesFromShorthand(value) : value, tokens));
|
/* WR-02: a literal font: shorthand embeds unvalidated weight/
|
||||||
|
size/line-height numerics — flag it outright (package convention:
|
||||||
|
font: var(--type-*)), naming the extracted families. */
|
||||||
|
if (!/var\(/i.test(value)) {
|
||||||
|
const families = familiesFromShorthand(value);
|
||||||
|
violations.push(
|
||||||
|
'literal font: shorthand — use font: var(--type-*) (Rams typography tokens)'
|
||||||
|
+ (families ? `; families: ${families}` : ''),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (prop === 'font-family' && !/var\(/i.test(value)) {
|
||||||
|
violations.push(...fontViolations(value, tokens));
|
||||||
}
|
}
|
||||||
if (RADIUS_PROPS.test(prop) && !radiusAllowed(value)) {
|
if (RADIUS_PROPS.test(prop) && !radiusAllowed(value)) {
|
||||||
violations.push('non-zero border-radius (Rams Shapes: absolute zero roundedness)');
|
violations.push('non-zero border-radius (Rams Shapes: absolute zero roundedness)');
|
||||||
@@ -448,13 +459,32 @@ function checkDeclaration(prop, value, tokens) {
|
|||||||
return violations;
|
return violations;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* font: shorthand — families are the comma groups without sizes/weights. */
|
/* font: shorthand — families are the comma groups after the leading
|
||||||
|
style/variant/weight keyword run and the size[/line-height]
|
||||||
|
component: `700 14px/18px Arial, sans-serif` → `Arial, sans-serif`.
|
||||||
|
(WR-02: the old single-strip regex removed only the first token,
|
||||||
|
leaving "14px/18px Arial", which the numeric filter then dropped —
|
||||||
|
so no font: shorthand could ever be flagged.) */
|
||||||
function familiesFromShorthand(value) {
|
function familiesFromShorthand(value) {
|
||||||
return value
|
const families = [];
|
||||||
.split(',')
|
for (const group of splitTopLevel(value, ',')) {
|
||||||
.map((g) => g.trim().replace(/^[0-9./]+\S*\s+/g, '').trim())
|
let s = group.trim();
|
||||||
.filter((g) => !/^[0-9.]/.test(g) && g.length > 0)
|
if (!s) continue;
|
||||||
.join(',');
|
/* strip the leading style/variant/weight run (any order per the
|
||||||
|
CSS grammar, incl. bare weights and an oblique angle) */
|
||||||
|
let m;
|
||||||
|
while ((m = /^(?:normal|italic|oblique(?:\s+[0-9.]+(?:deg|grad|rad|turn))?|small-caps|bold|bolder|lighter|[1-9][0-9]{0,2})\s+/i.exec(s))) {
|
||||||
|
s = s.slice(m[0].length);
|
||||||
|
}
|
||||||
|
/* strip the mandatory size (optionally /line-height) once */
|
||||||
|
s = s.replace(
|
||||||
|
/^(?:calc\([^()]*\)|[0-9]*\.?[0-9]+(?:[a-z%]+)?)\s*\/\s*(?:calc\([^()]*\)|[0-9]*\.?[0-9]+(?:[a-z%]+)?)\s+/i,
|
||||||
|
'',
|
||||||
|
).replace(/^(?:calc\([^()]*\)|[0-9]*\.?[0-9]+(?:[a-z%]+)?)\s+/i, '');
|
||||||
|
const family = stripFamily(s);
|
||||||
|
if (family) families.push(family);
|
||||||
|
}
|
||||||
|
return families.join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ————— audit driver ————— */
|
/* ————— audit driver ————— */
|
||||||
|
|||||||
@@ -33,6 +33,10 @@
|
|||||||
* bare `rounded` → var(--radius) = 0px (controlled
|
* bare `rounded` → var(--radius) = 0px (controlled
|
||||||
* build); non-zero bare key → exit 2; the inert
|
* build); non-zero bare key → exit 2; the inert
|
||||||
* v3-style DEFAULT sub-key must not reappear
|
* v3-style DEFAULT sub-key must not reappear
|
||||||
|
* T10 literal font: shorthands flagged regardless of
|
||||||
|
* family (convention: font: var(--type-*)); var()
|
||||||
|
* shorthands pass; familiesFromShorthand extracts
|
||||||
|
* the family for the message → flagged (exit 1)
|
||||||
*/
|
*/
|
||||||
import { spawnSync } from 'node:child_process';
|
import { spawnSync } from 'node:child_process';
|
||||||
import { existsSync, mkdirSync, readFileSync, symlinkSync, writeFileSync } from 'node:fs';
|
import { existsSync, mkdirSync, readFileSync, symlinkSync, writeFileSync } from 'node:fs';
|
||||||
@@ -164,6 +168,17 @@ writeFileSync(styleAttrPage, `<!DOCTYPE html>
|
|||||||
</body></html>
|
</body></html>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
/* T10 — literal font: shorthands (WR-02: the familiesFromShorthand
|
||||||
|
* strip bug left "14px/18px Arial" behind, which the numeric filter
|
||||||
|
* then dropped — no font: shorthand could ever be flagged). */
|
||||||
|
const fontShorthand = join(FIX, 'font-shorthand.css');
|
||||||
|
writeFileSync(fontShorthand, `/* literal font: shorthand — flagged regardless of family (package
|
||||||
|
convention: font: var(--type-*)); var() shorthands pass */
|
||||||
|
.a-font-shorthand { font: 700 14px/18px Arial; }
|
||||||
|
.b-font-shorthand { font: italic 700 12px/16px "Space Grotesk", sans-serif; }
|
||||||
|
.c-font-shorthand-var { font: var(--type-body-md); }
|
||||||
|
`);
|
||||||
|
|
||||||
/* ————— tests ————— */
|
/* ————— tests ————— */
|
||||||
|
|
||||||
// T1 — clean stylesheet exits 0
|
// T1 — clean stylesheet exits 0
|
||||||
@@ -289,6 +304,21 @@ writeFileSync(styleAttrPage, `<!DOCTYPE html>
|
|||||||
'bare rounded still compiles to the v3 .25rem default');
|
'bare rounded still compiles to the v3 .25rem default');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// T10 — literal font: shorthands are flagged; var() shorthands pass
|
||||||
|
{
|
||||||
|
const r = runAudit([fontShorthand]);
|
||||||
|
check('T10 literal font: shorthand → exit 1', r.status === 1,
|
||||||
|
`status=${r.status} error=${r.error} out=${r.out.slice(0, 300)}`);
|
||||||
|
check('T10 off-token shorthand family extracted (Arial)',
|
||||||
|
/font: 700 14px\/18px Arial/.test(r.out) && /Arial/.test(r.out),
|
||||||
|
`out=${r.out.slice(0, 300)}`);
|
||||||
|
check('T10 sanctioned-family shorthand still flagged (convention: font: var(--type-*))',
|
||||||
|
/font: italic 700 12px\/16px "Space Grotesk", sans-serif/.test(r.out),
|
||||||
|
`out=${r.out.slice(0, 300)}`);
|
||||||
|
check('T10 var() shorthand passes', !/var\(--type-body-md\)/.test(r.out),
|
||||||
|
`out=${r.out.slice(0, 300)}`);
|
||||||
|
}
|
||||||
|
|
||||||
/* ————— report ————— */
|
/* ————— report ————— */
|
||||||
console.log(`audit.test: ${passes.length} passed, ${failures.length} failed`);
|
console.log(`audit.test: ${passes.length} passed, ${failures.length} failed`);
|
||||||
passes.forEach((p) => console.log(` PASS ${p}`));
|
passes.forEach((p) => console.log(` PASS ${p}`));
|
||||||
|
|||||||
Reference in New Issue
Block a user