From 3239229a82a30fa8e90a409cae254077ad43763c Mon Sep 17 00:00:00 2001 From: Ben Word Date: Wed, 11 Mar 2026 13:41:16 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix=20font=20family=20parsing=20?= =?UTF-8?q?with=20namespace=20wildcard=20resets=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regex patterns for extracting CSS variables (--font-*, --color-*, --text-*, --radius-*) could match mid-value occurrences like var(--font-sans), producing garbled output. Add declaration boundary anchor (?:^|[;{}]) to all patterns so they only match at the start of a CSS declaration. Also filter out wildcard names (--font-*) and CSS-wide keywords (initial, inherit, unset, revert) from all extraction patterns. Co-Authored-By: Claude Opus 4.6 --- src/index.ts | 33 ++++++++++++++++++++++++++------- tests/index.test.ts | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5370dab..77c088a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1125,8 +1125,19 @@ export function wordpressThemeJson(config: ThemeJsonConfig = {}): VitePlugin { // the theme_file_path filter in Sage doesn't point to // a non-existent file (passes through the base theme.json) + /** CSS-wide keywords that represent resets, not actual values */ + const cssWideKeywords = [ + 'initial', + 'inherit', + 'unset', + 'revert', + 'revert-layer', + ]; + /** - * Helper to extract CSS variables using a regex pattern + * Helper to extract CSS variables using a regex pattern. + * Filters out wildcard namespace resets (e.g. --font-*: initial) + * and CSS-wide keywords. */ const extractVariables = ( regex: RegExp, @@ -1140,17 +1151,26 @@ export function wordpressThemeJson(config: ThemeJsonConfig = {}): VitePlugin { while ((match = regex.exec(content)) !== null) { const [, name, value] = match; - if (name && value) variables.push([name, value.trim()]); + if ( + name && + value && + !name.includes('*') && + !cssWideKeywords.includes(value.trim()) + ) + variables.push([name, value.trim()]); } return variables; }; const patterns = { - COLOR: /--color-([^:]+):\s*([^;}]+)[;}]?/g, - FONT_FAMILY: /--font-([^:]+):\s*([^;}]+)[;}]?/g, - FONT_SIZE: /--text-([^:]+):\s*([^;}]+)[;}]?/g, - BORDER_RADIUS: /--radius-([^:]+):\s*([^;}]+)[;}]?/g, + COLOR: /(?:^|[;{}])\s*--color-([^:]+):\s*([^;}]+)[;}]?/gm, + FONT_FAMILY: + /(?:^|[;{}])\s*--font-([^:]+):\s*([^;}]+)[;}]?/gm, + FONT_SIZE: + /(?:^|[;{}])\s*--text-([^:]+):\s*([^;}]+)[;}]?/gm, + BORDER_RADIUS: + /(?:^|[;{}])\s*--radius-([^:]+):\s*([^;}]+)[;}]?/gm, } as const; // Process colors from either @theme block or Tailwind config @@ -1158,7 +1178,6 @@ export function wordpressThemeJson(config: ThemeJsonConfig = {}): VitePlugin { ? [ // Process @theme block colors if available ...extractVariables(patterns.COLOR, themeContent) - .filter(([name]) => !name.endsWith('-*')) .map(([name, value]) => { const parts = name.split('-'); const colorName = parts[0]; diff --git a/tests/index.test.ts b/tests/index.test.ts index 5458805..ab8f2c6 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1752,4 +1752,49 @@ describe('wordpressThemeJson', () => { expect(slugs.indexOf('lg')).toBeLessThan(slugs.indexOf('relative')); expect(slugs.indexOf('lg')).toBeLessThan(slugs.indexOf('viewport')); }); + + it('should filter out wildcard namespace resets and CSS-wide keywords', () => { + const plugin = wordpressThemeJson({ + tailwindConfig: mockTailwindConfigPath, + }); + + const cssContent = ` + @theme { + --font-*: initial; + --font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif; + --color-*: initial; + --color-primary: #000000; + --text-*: inherit; + --text-lg: 1.125rem; + } + `; + + (plugin.transform as any)(cssContent, 'app.css'); + const emitFile = vi.fn(); + (plugin.generateBundle as any).call({ emitFile }); + + const themeJson = JSON.parse(emitFile.mock.calls[0][0].source); + + // Font families should only contain sans, not wildcard + const fontSlugs = themeJson.settings.typography.fontFamilies.map( + (f: { slug: string }) => f.slug + ); + expect(fontSlugs).toContain('sans'); + expect(fontSlugs).not.toContain('*'); + expect(fontSlugs).toHaveLength(1); + + // Colors should only contain primary, not wildcard + const colorSlugs = themeJson.settings.color.palette.map( + (c: { slug: string }) => c.slug + ); + expect(colorSlugs).toContain('primary'); + expect(colorSlugs).toHaveLength(1); + + // Font sizes should only contain lg, not wildcard + const sizeSlugs = themeJson.settings.typography.fontSizes.map( + (s: { slug: string }) => s.slug + ); + expect(sizeSlugs).toContain('lg'); + expect(sizeSlugs).toHaveLength(1); + }); });