From 8fc1bb848e065857e7e6d93190dece0db7b26e2c Mon Sep 17 00:00:00 2001 From: koreahghg Date: Wed, 23 Sep 2026 08:28:41 +0900 Subject: [PATCH] Don't treat `\` as an escape inside CSS comments Per the CSS Syntax spec, a comment ends at the first `*/`; escapes are not processed inside comments. The parser skipped the character after a `\`, so a comment like `/* C:\temp\*/` was never closed and swallowed (or corrupted) the CSS that followed it. --- CHANGELOG.md | 1 + packages/tailwindcss/src/css-parser.test.ts | 39 +++++++++++++++++++++ packages/tailwindcss/src/css-parser.ts | 18 +++------- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 338a78eb9..05b29b3f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Only normalize top-level `and`, `or`, and `not` keywords in `supports-[…]` variants (e.g. `selector(a: not (.foo))` → `selector(a:not(.foo))`) ([#20420](https://github.com/tailwindlabs/tailwindcss/pull/20420)) - Don't warn about Angular's `::ng-deep` and `:host-context()` when optimizing CSS ([#20434](https://github.com/tailwindlabs/tailwindcss/pull/20434)) - Don't generate CSS for candidates containing an empty additional modifier (e.g. `bg-red-500/50/` and `group-hover/foo//bar:flex`) ([#20466](https://github.com/tailwindlabs/tailwindcss/pull/20466)) +- Ensure CSS comments ending with `\*/` are closed correctly instead of swallowing the CSS that follows (e.g. `/* C:\temp\*/`) ## [4.3.3] - 2026-07-16 diff --git a/packages/tailwindcss/src/css-parser.test.ts b/packages/tailwindcss/src/css-parser.test.ts index 8df19f96c..0dcbc0a41 100644 --- a/packages/tailwindcss/src/css-parser.test.ts +++ b/packages/tailwindcss/src/css-parser.test.ts @@ -31,6 +31,23 @@ describe.each(['Unix', 'Windows'])('Line endings: %s', (lineEndings) => { ).toEqual([]) }) + it('should end a comment at `*/` even when it is preceded by a `\\`', () => { + expect( + parse(css` + /* C:\temp\*/ + .foo { + color: red; + } + `), + ).toEqual([ + { + kind: 'rule', + selector: '.foo', + nodes: [{ kind: 'declaration', property: 'color', value: 'red', important: false }], + }, + ]) + }) + it('should parse a comment inside of a selector and ignore it', () => { expect( parse(css` @@ -448,6 +465,28 @@ describe.each(['Unix', 'Windows'])('Line endings: %s', (lineEndings) => { ]) }) + it('should end a comment in a custom property at `*/` even when it is preceded by a `\\`', () => { + expect( + parse(css` + --foo: /* C:\temp\*/ bar; + --bar: /* baz */ qux; + `), + ).toEqual([ + { + kind: 'declaration', + property: '--foo', + value: '/* C:\\temp\\*/ bar', + important: false, + }, + { + kind: 'declaration', + property: '--bar', + value: '/* baz */ qux', + important: false, + }, + ]) + }) + it('should parse empty custom properties', () => { expect( parse(css` diff --git a/packages/tailwindcss/src/css-parser.ts b/packages/tailwindcss/src/css-parser.ts index 3dd5e6ffe..d7b797bb1 100644 --- a/packages/tailwindcss/src/css-parser.ts +++ b/packages/tailwindcss/src/css-parser.ts @@ -134,13 +134,9 @@ export function parse(input: string, opts?: ParseOptions) { for (let j = i + 2; j < input.length; j++) { peekChar = input.charCodeAt(j) - // Current character is a `\` therefore the next character is escaped. - if (peekChar === BACKSLASH) { - j += 1 - } - - // End of the comment - else if (peekChar === ASTERISK && input.charCodeAt(j + 1) === SLASH) { + // End of the comment. Escapes are not processed inside of comments, + // so a `\` right before the closing `*/` does not escape it. + if (peekChar === ASTERISK && input.charCodeAt(j + 1) === SLASH) { i = j + 1 break } @@ -224,13 +220,9 @@ export function parse(input: string, opts?: ParseOptions) { else if (peekChar === SLASH && input.charCodeAt(j + 1) === ASTERISK) { for (let k = j + 2; k < input.length; k++) { peekChar = input.charCodeAt(k) - // Current character is a `\` therefore the next character is escaped. - if (peekChar === BACKSLASH) { - k += 1 - } - // End of the comment - else if (peekChar === ASTERISK && input.charCodeAt(k + 1) === SLASH) { + // End of the comment. Escapes are not processed inside of comments. + if (peekChar === ASTERISK && input.charCodeAt(k + 1) === SLASH) { j = k + 1 break }