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.
This commit is contained in:
koreahghg 2026-09-23 08:28:41 +09:00
parent 41d9cae8e5
commit 8fc1bb848e
3 changed files with 45 additions and 13 deletions

View file

@ -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

View file

@ -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`

View file

@ -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
}