diff --git a/packages/lint/src/rules/fonts.test.ts b/packages/lint/src/rules/fonts.test.ts
index 6afa663bbe2..fdbabb76696 100644
--- a/packages/lint/src/rules/fonts.test.ts
+++ b/packages/lint/src/rules/fonts.test.ts
@@ -14,6 +14,33 @@ async function findAliasFindings(html: string) {
describe("font rules", () => {
describe("system_font_will_alias", () => {
+ it("reports an aliased family reached through a referenced custom property", async () => {
+ const findings = await findAliasFindings(`
@@ -99,6 +126,99 @@ describe("font rules", () => {
});
describe("font_family_without_font_face", () => {
+ it.each([
+ ':root { --font-family: "Brand Heading"; } h1 { font-family: var(--font-family); }',
+ ':root { --family: "Brand Heading"; --heading: var(--family); } h1 { font-family: var(--heading); }',
+ ':root { --heading: "Brand Heading"; } h1 { font-family: serif, var(--heading), sans-serif; }',
+ ':root { --heading: "Brand Heading", sans-serif; } h1 { font-family: var(--heading); }',
+ ':root { --Heading: "Brand Heading"; --heading: Inter; } h1 { font-family: var(--Heading); }',
+ ':root { --heading: "Brand Heading"; } h1 { font-family: var(--heading, "Unused Fallback"); }',
+ ])("reports the missing family in a referenced custom property: %s", async (css) => {
+ const findings = await findByCode(
+ `
`,
+ "font_family_without_font_face",
+ );
+ expect(findings).toHaveLength(1);
+ expect(findings[0]?.message).toContain("brand heading");
+ expect(findings[0]?.message).not.toContain("unused fallback");
+ });
+
+ it("resolves referenced font tokens across style blocks and locates their use", async () => {
+ const findings = await findByCode(
+ `
+
`,
+ "font_family_without_font_face",
+ );
+ expect(findings).toHaveLength(1);
+ expect(findings[0]).toMatchObject({ line: 3, column: 6 });
+ });
+
+ it.each([
+ '@font-face { font-family: "Brand Heading"; src: local("Arial"); } :root { --heading: "Brand Heading"; } h1 { font-family: var(--heading); }',
+ ":root { --heading: var(--other); --other: var(--heading); } h1 { font-family: var(--heading); }",
+ ':root { --heading: "Brand Heading"; } h1 { font-family: var(--unresolved); }',
+ ':root { --heading: "Brand Heading"; } h1 { font-family: "var(--heading)"; }',
+ ])("keeps declared or unresolved token references quiet: %s", async (css) => {
+ const findings = await findByCode(
+ `
`,
+ "font_family_without_font_face",
+ );
+ expect(findings).toHaveLength(0);
+ });
+
+ it.each([
+ ':root { --font-family: "Brand Heading"; }',
+ ':root { --brand-font-family: "Brand Heading"; }',
+ 'p::before { content: "font-family: Brand Heading;"; }',
+ ])("ignores family names outside font declarations: %s", async (css) => {
+ const findings = await findByCode(
+ `
`,
+ "font_family_without_font_face",
+ );
+ expect(findings).toHaveLength(0);
+ });
+
+ it.each([
+ 'p::before { content: "@font-face { font-family: Brand Heading; }"; }',
+ '@font-face { --font-family: "Brand Heading"; src: local("Arial"); }',
+ ])("requires a real font-face descriptor: %s", async (css) => {
+ const findings = await findByCode(
+ `
`,
+ "font_family_without_font_face",
+ );
+ expect(findings).toHaveLength(1);
+ expect(findings[0]!.message).toContain("brand heading");
+ });
+
+ it("reports only the real missing family and locates its declaration", async () => {
+ const findings = await findByCode(
+ `
`,
+ "font_family_without_font_face",
+ );
+ expect(findings).toHaveLength(1);
+ expect(findings[0]).toMatchObject({ line: 4, column: 5 });
+ expect(findings[0]!.message).toContain("real brand");
+ expect(findings[0]!.message).not.toContain("unused brand");
+ expect(findings[0]!.message).not.toContain("another brand");
+ });
+
+ it("still finds a real font face inside a conditional rule", async () => {
+ const findings = await findByCode(
+ `
`,
+ "font_family_without_font_face",
+ );
+ expect(findings).toHaveLength(0);
+ });
+
it("flags font-family used without @font-face", async () => {
const html = `
diff --git a/packages/lint/src/rules/fonts.ts b/packages/lint/src/rules/fonts.ts
index cb9bb7df59a..f8fdd74d786 100644
--- a/packages/lint/src/rules/fonts.ts
+++ b/packages/lint/src/rules/fonts.ts
@@ -30,40 +30,36 @@ const GENERIC_FAMILIES = new Set([
"revert",
]);
-// A CSS comment can contain a `}` (e.g. `@font-face { /* 400 } regular */
-// font-family: 'X'; ... }`), which truncates the naive `@font-face\s*\{[^}]*\}`
-// block match at the comment's brace — so the rule never sees the real
-// `font-family` and reports a false-positive font_family_without_font_face.
-// Large/"framework" stylesheets hit this far more often than minimal ones,
-// which is why a simple