diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index a6d676591fd4..5e50f1a4b5f2 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1645,6 +1645,17 @@ describe('Parsing theme values from CSS', () => { `) }) + test('normalizes single-digit hexadecimal escapes in theme variable names', async () => { + let compiler = await compile(css` + @theme { + --color-\0: red; + } + @tailwind utilities; + `) + + expect(compiler.build([String.raw`bg-(--color-\0)`])).toContain('--color-\uFFFD: red;') + }) + // https://github.com/tailwindlabs/tailwindcss/issues/19786 test('out-of-range escaped CSS variable candidates do not crash the build', async () => { // Shouldn't crash diff --git a/packages/tailwindcss/src/utils/escape.test.ts b/packages/tailwindcss/src/utils/escape.test.ts index f4c8e1192862..562af75ef722 100644 --- a/packages/tailwindcss/src/utils/escape.test.ts +++ b/packages/tailwindcss/src/utils/escape.test.ts @@ -8,6 +8,32 @@ describe('escape', () => { }) describe('unescape', () => { + test.each([ + [String.raw`\0`, '\uFFFD'], + [String.raw`\1`, '\u0001'], + [String.raw`\9`, '\t'], + [String.raw`\a`, '\n'], + [String.raw`\A`, '\n'], + [String.raw`\c`, '\f'], + [String.raw`\C`, '\f'], + [String.raw`\f`, '\u000f'], + [String.raw`\F`, '\u000f'], + ])('decodes a single hexadecimal digit in %s', (input, expected) => { + expect(unescape(input)).toBe(expected) + }) + + test.each([ + [String.raw`\a b`, '\nb'], + [String.raw`\00000a`, '\n'], + [String.raw`\41`, 'A'], + [String.raw`\0 `, '\uFFFD'], + [String.raw`\!`, '!'], + [String.raw`\g`, 'g'], + [String.raw`\\`, '\\'], + ])('preserves escape handling in %s', (input, expected) => { + expect(unescape(input)).toBe(expected) + }) + test('removes backslashes', () => { expect(unescape(String.raw`red-1\/2`)).toMatchInlineSnapshot(`"red-1/2"`) }) diff --git a/packages/tailwindcss/src/utils/escape.ts b/packages/tailwindcss/src/utils/escape.ts index c430e4ad42bf..7c34f183b0f3 100644 --- a/packages/tailwindcss/src/utils/escape.ts +++ b/packages/tailwindcss/src/utils/escape.ts @@ -73,12 +73,12 @@ export function escape(value: string) { } export function unescape(escaped: string) { - return escaped.replace(/\\([\dA-Fa-f]{1,6}[\t\n\f\r ]?|[\S\s])/g, (match) => { - if (match.length <= 2) { - return match[1] + return escaped.replace(/\\(?:([\dA-Fa-f]{1,6})[\t\n\f\r ]?|([\S\s]))/g, (_, hex, char) => { + if (!hex) { + return char } - let codePoint = Number.parseInt(match.slice(1).trim(), 16) + let codePoint = Number.parseInt(hex, 16) if ( // Invalid codepoint: https://infra.spec.whatwg.org/#code-point