Don't treat \ as an escape inside CSS comments (#20508)
<!-- 👋 Hey, thanks for your interest in contributing to Tailwind! **Please ask first before starting work on any significant new features.** It's never a fun experience to have your pull request declined after investing a lot of time and effort into a new feature. To avoid this from happening, we request that contributors create a discussion to first discuss any significant new features. For more info, check out the contributing guide: https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md --> ## Summary <!-- Provide a summary of the issue and the changes you're making. How does your change solve the problem? --> The CSS parser treated `\` inside comments as an escape and skipped the character after it. Because of that, a comment ending in `\*/` was never closed, and the CSS that followed was swallowed or turned into a broken rule: ```css /* C:\temp\*/ .a { color: red } ``` Before this change the `.a` rule disappeared entirely, and `/* \*/ .a { color: red }` produced the selector `* \*/ .a`. The same thing happened to comments inside declaration values, where the comment ran on until the next `*/` it could find, pulling following declarations into the value. Per [CSS Syntax Level 3 §4.3.2](https://www.w3.org/TR/css-syntax-3/#consume-comment), a comment ends at the first `*/` and escapes aren't processed inside comments. This removes the backslash handling from both comment-scanning loops in `css-parser.ts` (top-level and inside declaration values). The existing test `/*Hello, \*\/ world!*/` keeps passing, since that input contains no `*/` before the final one. ## Test plan <!-- Explain how you tested your changes. Include the exact commands that you used to verify the change works and include screenshots/screen recordings of the update behavior in the browser if applicable. --> Added two tests to `packages/tailwindcss/src/css-parser.test.ts` (both run with Unix and Windows line endings): - a top-level comment ending in `\*/` followed by a rule - a comment ending in `\*/` inside a custom property value, followed by another declaration Both fail without the change to `css-parser.ts` and pass with it. ```sh pnpm vitest run packages/tailwindcss/src/css-parser.test.ts pnpm vitest run --project tailwindcss pnpm run lint ``` --------- Co-authored-by: Robin Malfait <robin.malfait@shopify.com> Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This commit is contained in:
parent
86f3daf912
commit
722acdfebf
3 changed files with 54 additions and 13 deletions
|
|
@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- 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))
|
||||
- Sort `min-*`, `max-*`, and container query variants with decimal values numerically (e.g. `min-[40.25rem]` before `min-[40.5rem]`) ([#20512](https://github.com/tailwindlabs/tailwindcss/pull/20512))
|
||||
- Ensure CSS comments ending with `\*/` are closed correctly instead of swallowing the CSS that follows (e.g. `/* C:\temp\*/`) ([#20508](https://github.com/tailwindlabs/tailwindcss/pull/20508))
|
||||
|
||||
## [4.3.3] - 2026-07-16
|
||||
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -128,19 +128,24 @@ export function parse(input: string, opts?: ParseOptions) {
|
|||
// ^^^^^^^^^^^^^
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// The escape character `\` in comments are ignored, this means that a
|
||||
// the end of a comment preceded by `\` does _not_ mean that the end of the
|
||||
// comment is escaped and therefore we have to keep parsing.
|
||||
//
|
||||
// This is a valid comment:
|
||||
// ```
|
||||
// /*C:\*/
|
||||
// ```
|
||||
// See: https://www.w3.org/TR/css-syntax-3/#consume-comment
|
||||
else if (currentChar === SLASH && input.charCodeAt(i + 1) === ASTERISK) {
|
||||
let start = i
|
||||
|
||||
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.
|
||||
if (peekChar === ASTERISK && input.charCodeAt(j + 1) === SLASH) {
|
||||
i = j + 1
|
||||
break
|
||||
}
|
||||
|
|
@ -224,13 +229,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.
|
||||
if (peekChar === ASTERISK && input.charCodeAt(k + 1) === SLASH) {
|
||||
j = k + 1
|
||||
break
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue