From 798a7bf9053b2aeb52beb399d1f1c7ebdcbcc093 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 14 Jul 2025 11:44:31 -0400 Subject: [PATCH] Ignore consecutive semicolons in the CSS parser (#18532) Fixes #18523 I swear I'd already landed this but apparently not. --- CHANGELOG.md | 1 + packages/tailwindcss/src/css-parser.test.ts | 48 ++++++++++++++------- packages/tailwindcss/src/css-parser.ts | 2 +- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a8156b74..5cf1ede90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Detect classes inside Elixir charlist, word list, and string sigils ([#18432](https://github.com/tailwindlabs/tailwindcss/pull/18432)) - Track source locations through `@plugin` and `@config` ([#18345](https://github.com/tailwindlabs/tailwindcss/pull/18345)) - Handle when `process.env.DEBUG` is a boolean in `@tailwindcss/node` ([#18485](https://github.com/tailwindlabs/tailwindcss/pull/18485)) +- Ignore consecutive semicolons in the CSS parser ([#18532](https://github.com/tailwindlabs/tailwindcss/pull/18532)) ## [4.1.11] - 2025-06-26 diff --git a/packages/tailwindcss/src/css-parser.test.ts b/packages/tailwindcss/src/css-parser.test.ts index b477758ae..11c9b33c6 100644 --- a/packages/tailwindcss/src/css-parser.test.ts +++ b/packages/tailwindcss/src/css-parser.test.ts @@ -1078,6 +1078,38 @@ describe.each(['Unix', 'Windows'])('Line endings: %s', (lineEndings) => { }, ]) }) + + it('should ignore consecutive semicolons', () => { + expect(parse(';;;')).toEqual([]) + }) + + it('should ignore semicolons after an at-rule with a body', () => { + expect(parse('@plugin "foo" {} ;')).toEqual([ + { + kind: 'at-rule', + name: '@plugin', + params: '"foo"', + nodes: [], + }, + ]) + }) + + it('should ignore consecutive semicolons a declaration', () => { + expect(parse('.foo { color: red;;; }')).toEqual([ + { + kind: 'rule', + selector: '.foo', + nodes: [ + { + kind: 'declaration', + property: 'color', + value: 'red', + important: false, + }, + ], + }, + ]) + }) }) describe('errors', () => { @@ -1163,22 +1195,6 @@ describe.each(['Unix', 'Windows'])('Line endings: %s', (lineEndings) => { `[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]`, - ) - }) }) it('ignores BOM at the beginning of a file', () => { diff --git a/packages/tailwindcss/src/css-parser.ts b/packages/tailwindcss/src/css-parser.ts index a59e34721..e38f64b33 100644 --- a/packages/tailwindcss/src/css-parser.ts +++ b/packages/tailwindcss/src/css-parser.ts @@ -333,7 +333,7 @@ export function parse(input: string, opts?: ParseOptions) { ) { let declaration = parseDeclaration(buffer) if (!declaration) { - if (buffer.length === 0) throw new Error('Unexpected semicolon') + if (buffer.length === 0) continue throw new Error(`Invalid declaration: \`${buffer.trim()}\``) }