mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Discard invalid declarations when parsing CSS (#16093)
I discovered this when triaging an error someone had on Tailwind Play. 1. When we see a `;` we often assume a valid declaration precedes it but that may not be the case 2. When we see the name of a custom property we assume everything that follows will be a valid declaration but that is not necessarily the case 3. A bare identifier inside of a rule is treated as a declaration which is not the case This PR fixes all three of these by ignoring these invalid cases. Though some should probably be turned into errors. --------- Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This commit is contained in:
parent
95722020fe
commit
35a5e8cb64
@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Vite: Transform `<style>` blocks in HTML files ([#16069](https://github.com/tailwindlabs/tailwindcss/pull/16069))
|
||||
- Prevent camelCasing CSS custom properties added by JavaScript plugins ([#16103](https://github.com/tailwindlabs/tailwindcss/pull/16103))
|
||||
- Do not emit `@keyframes` in `@theme reference` ([#16120](https://github.com/tailwindlabs/tailwindcss/pull/16120))
|
||||
- Discard invalid declarations when parsing CSS ([#16093](https://github.com/tailwindlabs/tailwindcss/pull/16093))
|
||||
|
||||
## [4.0.1] - 2025-01-29
|
||||
|
||||
|
||||
@ -329,6 +329,28 @@ describe.each(['Unix', 'Windows'])('Line endings: %s', (lineEndings) => {
|
||||
])
|
||||
})
|
||||
|
||||
it('should parse a custom property with an empty value', () => {
|
||||
expect(parse('--foo:;')).toEqual([
|
||||
{
|
||||
kind: 'declaration',
|
||||
property: '--foo',
|
||||
value: '',
|
||||
important: false,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should parse a custom property with a space value', () => {
|
||||
expect(parse('--foo: ;')).toEqual([
|
||||
{
|
||||
kind: 'declaration',
|
||||
property: '--foo',
|
||||
value: '',
|
||||
important: false,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should parse a custom property with a block including nested "css"', () => {
|
||||
expect(
|
||||
parse(css`
|
||||
@ -1097,5 +1119,39 @@ describe.each(['Unix', 'Windows'])('Line endings: %s', (lineEndings) => {
|
||||
`),
|
||||
).toThrowErrorMatchingInlineSnapshot(`[Error: Unterminated string: "Hello world!;"]`)
|
||||
})
|
||||
|
||||
it('should error when incomplete custom properties are used', () => {
|
||||
expect(() => parse('--foo')).toThrowErrorMatchingInlineSnapshot(
|
||||
`[Error: Invalid custom property, expected a value]`,
|
||||
)
|
||||
})
|
||||
|
||||
it('should error when incomplete custom properties are used inside rules', () => {
|
||||
expect(() => parse('.foo { --bar }')).toThrowErrorMatchingInlineSnapshot(
|
||||
`[Error: Invalid custom property, expected a value]`,
|
||||
)
|
||||
})
|
||||
|
||||
it('should error when a declaration is incomplete', () => {
|
||||
expect(() => parse('.foo { bar }')).toThrowErrorMatchingInlineSnapshot(
|
||||
`[Error: Invalid declaration: \`bar\`]`,
|
||||
)
|
||||
})
|
||||
|
||||
it('should error when a semicolon exists after an at-rule with a body', () => {
|
||||
expect(() => parse('@plugin "foo" {} ;')).toThrowErrorMatchingInlineSnapshot(
|
||||
`[Error: Unexpected semicolon]`,
|
||||
)
|
||||
})
|
||||
|
||||
it('should error when consecutive semicolons exist', () => {
|
||||
expect(() => parse(';;;')).toThrowErrorMatchingInlineSnapshot(`[Error: Unexpected semicolon]`)
|
||||
})
|
||||
|
||||
it('should error when consecutive semicolons exist after a declaration', () => {
|
||||
expect(() => parse('.foo { color: red;;; }')).toThrowErrorMatchingInlineSnapshot(
|
||||
`[Error: Unexpected semicolon]`,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -286,6 +286,8 @@ export function parse(input: string) {
|
||||
}
|
||||
|
||||
let declaration = parseDeclaration(buffer, colonIdx)
|
||||
if (!declaration) throw new Error(`Invalid custom property, expected a value`)
|
||||
|
||||
if (parent) {
|
||||
parent.nodes.push(declaration)
|
||||
} else {
|
||||
@ -337,6 +339,11 @@ export function parse(input: string) {
|
||||
closingBracketStack[closingBracketStack.length - 1] !== ')'
|
||||
) {
|
||||
let declaration = parseDeclaration(buffer)
|
||||
if (!declaration) {
|
||||
if (buffer.length === 0) throw new Error('Unexpected semicolon')
|
||||
throw new Error(`Invalid declaration: \`${buffer.trim()}\``)
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
parent.nodes.push(declaration)
|
||||
} else {
|
||||
@ -435,7 +442,10 @@ export function parse(input: string) {
|
||||
|
||||
// Attach the declaration to the parent.
|
||||
if (parent) {
|
||||
parent.nodes.push(parseDeclaration(buffer, colonIdx))
|
||||
let node = parseDeclaration(buffer, colonIdx)
|
||||
if (!node) throw new Error(`Invalid declaration: \`${buffer.trim()}\``)
|
||||
|
||||
parent.nodes.push(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -543,7 +553,11 @@ export function parseAtRule(buffer: string, nodes: AstNode[] = []): AtRule {
|
||||
return atRule(buffer.trim(), '', nodes)
|
||||
}
|
||||
|
||||
function parseDeclaration(buffer: string, colonIdx: number = buffer.indexOf(':')): Declaration {
|
||||
function parseDeclaration(
|
||||
buffer: string,
|
||||
colonIdx: number = buffer.indexOf(':'),
|
||||
): Declaration | null {
|
||||
if (colonIdx === -1) return null
|
||||
let importantIdx = buffer.indexOf('!important', colonIdx + 1)
|
||||
return decl(
|
||||
buffer.slice(0, colonIdx).trim(),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user