mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(lint): stop font_family_without_font_face flagging system-ui stacks and var() (#1796)
The font_family_without_font_face rule raised a blocking error on two legitimate, very common cases: 1. The `-apple-system, BlinkMacSystemFont` system-ui stack. These two tokens are the cross-browser incantation for the platform UI font (synonyms of `system-ui`); they name no font file, so demanding an @font-face for them is wrong. They appear in almost every CSS reset. 2. `font-family: var(--x)` indirection. The shared family extractor took the literal `var(--heading)` as a font name. The linter cannot statically resolve a custom property, so it must not flag it. Fix at the root: add the two system-ui synonyms to GENERIC_FAMILIES, and skip any parenthesised (function) token in the shared family extractor so both this rule and system_font_will_alias stop misreading var(). A real undeclared font sitting in the same stack is still flagged.
This commit is contained in:
@@ -291,5 +291,39 @@ describe("font rules", () => {
|
||||
const findings = await findByCode(html, "font_family_without_font_face");
|
||||
expect(findings).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("does not flag the -apple-system / BlinkMacSystemFont system-ui stack", async () => {
|
||||
const html = `<div data-composition-id="test" data-width="1920" data-height="1080">
|
||||
<style>body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }</style>
|
||||
</div>`;
|
||||
const findings = await findByCode(html, "font_family_without_font_face");
|
||||
expect(findings).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("does not flag a var() font-family indirection it cannot resolve", async () => {
|
||||
const html = `<div data-composition-id="test" data-width="1920" data-height="1080">
|
||||
<style>:root { --heading: 'Inter'; } h1 { font-family: var(--heading); }</style>
|
||||
</div>`;
|
||||
const findings = await findByCode(html, "font_family_without_font_face");
|
||||
expect(findings).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("does not flag a var() with a quoted fallback font", async () => {
|
||||
const html = `<div data-composition-id="test" data-width="1920" data-height="1080">
|
||||
<style>h1 { font-family: var(--heading, 'Geist'), sans-serif; }</style>
|
||||
</div>`;
|
||||
const findings = await findByCode(html, "font_family_without_font_face");
|
||||
expect(findings).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("still flags a real undeclared font sitting next to a system stack", async () => {
|
||||
const html = `<div data-composition-id="test" data-width="1920" data-height="1080">
|
||||
<style>body { font-family: 'Aeonik', -apple-system, BlinkMacSystemFont, sans-serif; }</style>
|
||||
</div>`;
|
||||
const findings = await findByCode(html, "font_family_without_font_face");
|
||||
expect(findings).toHaveLength(1);
|
||||
expect(findings[0]!.message).toContain("aeonik");
|
||||
expect(findings[0]!.message).not.toContain("apple-system");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,6 +56,22 @@ function extractFontFaceFamilies(styles: Array<{ content: string }>): Set<string
|
||||
return families;
|
||||
}
|
||||
|
||||
// Normalize one comma-separated font-family entry to a lowercase family name,
|
||||
// or null if it carries no resolvable name. `var(--heading)` (or any function
|
||||
// token) is an indirection the linter cannot statically resolve, so the literal
|
||||
// `var(...)` is not a font name and flagging it is a false positive. Comma-split
|
||||
// fallbacks like `var(--x, 'Inter')` also leave a dangling `)` on the fallback
|
||||
// part, so skip anything bearing parentheses.
|
||||
function normalizeUsedFontName(part: string): string | null {
|
||||
const name = part
|
||||
.trim()
|
||||
.replace(/^['"]|['"]$/g, "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (!name || name.includes("(") || name.includes(")")) return null;
|
||||
return name;
|
||||
}
|
||||
|
||||
function extractUsedFontFamilies(styles: Array<{ content: string }>): string[] {
|
||||
const used: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
@@ -64,13 +80,8 @@ function extractUsedFontFamilies(styles: Array<{ content: string }>): string[] {
|
||||
const withoutFontFace = stripCssComments(style.content).replace(/@font-face\s*\{[^}]*\}/gi, "");
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = propRe.exec(withoutFontFace)) !== null) {
|
||||
const stack = match[1]!;
|
||||
for (const part of stack.split(",")) {
|
||||
const name = part
|
||||
.trim()
|
||||
.replace(/^['"]|['"]$/g, "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
for (const part of match[1]!.split(",")) {
|
||||
const name = normalizeUsedFontName(part);
|
||||
if (name && !GENERIC_FAMILIES.has(name) && !seen.has(name)) {
|
||||
seen.add(name);
|
||||
used.push(name);
|
||||
|
||||
Reference in New Issue
Block a user