Skip to content

fix: parse nth-child of syntax without whitespace after of - #18611

Merged
dummdidumm merged 3 commits into
sveltejs:mainfrom
kdelay:fix/nth-of-without-trailing-whitespace
Aug 20, 2026
Merged

dummdidumm merged 3 commits into
sveltejs:mainfrom
kdelay:fix/nth-of-without-trailing-whitespace

Conversation

@kdelay

@kdelay kdelay commented Aug 5, 2026

Copy link
Copy Markdown

Fixes #18609

Problem

REGEX_NTH_OF matched the <an+b> of <selector> form only with whitespace on both sides of of:

/(even|odd|...)((?=\s*[,)])|\s+of\s+)/y

CSS minifiers (cssnano, lightningcss) legally collapse 2n of .foo to 2n of.foo, because the . delim token already terminates the of identifier. Browsers accept the minified form, but the parser threw css_expected_identifier on it.

This is not only a parse-a-string problem: SvelteKit's inlineStyleThreshold re-parses the built (minified) CSS, so a component using :nth-child(... of .selector) compiles fine on its own and then fails the production build the moment its CSS file falls under the inline threshold.

Change

of still has to be preceded by whitespace (2nof lexes as a single dimension token), but it no longer has to be followed by whitespace when the next token is one that ends an identifier and can start a selector (., #, [, *, :).

Test plan

Added packages/svelte/tests/parser-modern/samples/css-nth-of-minified, covering 2n of.important, even of#first, -n+3 of[data-active], 2n of*, 2n of:not(.excluded) and 2n of.a, .b. It fails on main (the parser throws) and passes with this change; verified by reverting only the style.js hunk.

Boundary cases checked against parse(..., { modern: true }) on this branch, so the fix does not make the parser more lenient than CSS:

input before after
2n of .x ok ok
2n of.x throws ok
2n of#id / 2n of[data-a] / 2n of* / 2n of:not(.a) throws ok
2nof .x throws throws
2n of-foo throws throws
2n ofx throws throws
2n of) / 2n of, throws throws

The resulting AST is the same shape as the whitespace form (Nth node followed by the of selector's own selectors); the only difference is the trailing space inside Nth.value, which is part of the matched source text. compile() output was checked too: li:nth-child(2n of.a) and li:nth-child(2n of .a) both scope to li.svelte-1lj1c1s:nth-child(...) with no warnings.

One side effect worth noting: :nth-child(even of.x) did not throw before, because even and of were read as type selectors, so the AST was silently wrong. It is now parsed as an Nth node.

Full suite on this branch: pnpm test 7668 passed / 69 skipped, 33 files, 0 failures. pnpm lint clean (after pnpm build, which prettier-plugin-svelte needs). Changeset added (patch).

@pkg-svelte-dev

pkg-svelte-dev Bot commented Aug 5, 2026 •

Copy link
Copy Markdown

Install the latest version of svelte from 91685af:

pnpm add https://pkg.svelte.dev/svelte/c/91685af27d648a87ff5ef7c9b5f61cd7df1798b0

Open in pkg.svelte.dev: https://pkg.svelte.dev/repos/svelte/pr/18611

Note

This PR is from a fork. A maintainer must approve approve each commit before it can be built and installed.

@changeset-bot

changeset-bot Bot commented Aug 5, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 91685af

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
svelte Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@kdelay

kdelay commented Aug 8, 2026

Copy link
Copy Markdown
Author

Pushed 7b565d9: the same gap applied to the nesting selector.

:nth-child(2n of &.foo) parsed fine, but the collapsed of&.foo still threw css_expected_identifier, even though & ends the of identifier exactly like ., #, [, * and : do, so a minifier can emit it. Added & to the lookahead and extended the parser-modern sample with a nested case; reverting only the style.js hunk makes that sample fail.

I also checked of |div (namespace prefix) for completeness: it throws today even with whitespace, so I left it alone rather than widen the fix past current parity.

Full suite on the branch: 7668 passed / 69 skipped, 33 files, 0 failures. pnpm lint clean.

kdelay added 3 commits August 14, 2026 17:11
REGEX_NTH_OF required whitespace on both sides of `of`, so the parser
threw `css_expected_identifier` on `:nth-child(2n of.foo)`. CSS minifiers
legally emit that form, because the `.` delim token already terminates
the `of` identifier, and SvelteKit's `inlineStyleThreshold` re-parses the
built (minified) CSS, which turns this into a production build failure.

Whitespace is still required before `of` (`2nof` lexes as a single
dimension token), and `of` still has to be followed by the start of a
selector, so `2n of-foo`, `2n ofx` and `2n of)` keep throwing.

Fixes sveltejs#18609
`&` is a valid start of a selector inside the `of` clause, and like `.`,
`#`, `[`, `*` and `:` it already ends the `of` identifier, so a minifier
can emit `:nth-child(2n of&.foo)`. The parser accepted the spaced form
but still threw `css_expected_identifier` on the collapsed one.

Extends the parser-modern sample with the nested case.
The stylesheet node gained a `comments` array in sveltejs#18637, so the
snapshot added by this branch needs the same field.
@kdelay
kdelay force-pushed the fix/nth-of-without-trailing-whitespace branch from 7b565d9 to 91685af Compare August 14, 2026 08:13

@dummdidumm dummdidumm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you!

@dummdidumm
dummdidumm merged commit 24130c1 into sveltejs:main Aug 20, 2026
11 of 12 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 20, 2026
Rich-Harris pushed a commit that referenced this pull request Aug 20, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## svelte@5.56.10

### Patch Changes

- fix: preserve CSS escape sequences when printing selectors
([#18667](#18667))

- fix: parse `:nth-child(2n of.foo)` where `of` is not followed by
whitespace ([#18611](#18611))

- fix: transform expressions inside labeled statements during server
compilation ([#18617](#18617))

- docs: clarify that context lookup includes the current component and
all ancestors ([#18581](#18581))

- fix: apply CSS custom properties with falsy values on components
([#18634](#18634))

- fix: correctly print `{#await ... catch x}` et al
([#18645](#18645))

- fix: ignore comments of Program node during migration script
([#18656](#18656))

- fix: reliably resolve append_style to its correct root
([#18614](#18614))

- fix: clean up removed capture event handlers from spread attributes
([#18618](#18618))

- fix: don't corrupt renderer type during SSR's legacy `bind:` retry
loop ([#18616](#18616))

- fix: treat concise arrow function bodies as implicit returns when
calculating blockers
([#18613](#18613))

- fix: give effect teardowns the value from before the first write in a
flush ([#18620](#18620))

- fix: avoid double-calling a derived reference when destructuring
`$derived` of another `$derived` during server-side rendering
([#18668](#18668))

- fix: preserve namespaces in CSS type selectors
([#18678](#18678))

- fix: increment private state fields through a non-`this` receiver
([#18622](#18622))

- chore: deduplicate client and server context helpers
([#18580](#18580))

- fix: release `last_propagated_event` after event propagation settles
so it no longer retains the last event's target subtree
([#18569](#18569))

- fix: allow custom elements to receive async values as props
([#18661](#18661))

- fix: strip comments from inline `style` values in linear time
([#18553](#18553))

- fix: prevent declaration comments from breaking server derived
references ([#18641](#18641))

- perf: make async blocker analysis scale linearly with the number of
top-level references
([#18549](#18549))

- fix: preserve short-circuiting for logical assignments to private
state fields ([#18594](#18594))

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
dadezzz pushed a commit to dadezzz/events-cash-register that referenced this pull request Aug 25, 2026
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [svelte](https://svelte.dev) ([source](https://github.com/sveltejs/svelte/tree/HEAD/packages/svelte)) | [`5.56.9` → `5.56.10`](https://renovatebot.com/diffs/npm/svelte/5.56.9/5.56.10) | ![age](https://developer.mend.io/api/mc/badges/age/npm/svelte/5.56.10?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/svelte/5.56.9/5.56.10?slim=true) |

---

### Release Notes

<details>
<summary>sveltejs/svelte (svelte)</summary>

### [`v5.56.10`](https://github.com/sveltejs/svelte/blob/HEAD/packages/svelte/CHANGELOG.md#55610)

[Compare Source](https://github.com/sveltejs/svelte/compare/svelte@5.56.9...svelte@5.56.10)

##### Patch Changes

- fix: preserve CSS escape sequences when printing selectors ([#&#8203;18667](sveltejs/svelte#18667))

- fix: parse `:nth-child(2n of.foo)` where `of` is not followed by whitespace ([#&#8203;18611](sveltejs/svelte#18611))

- fix: transform expressions inside labeled statements during server compilation ([#&#8203;18617](sveltejs/svelte#18617))

- docs: clarify that context lookup includes the current component and all ancestors ([#&#8203;18581](sveltejs/svelte#18581))

- fix: apply CSS custom properties with falsy values on components ([#&#8203;18634](sveltejs/svelte#18634))

- fix: correctly print `{#await ... catch x}` et al ([#&#8203;18645](sveltejs/svelte#18645))

- fix: ignore comments of Program node during migration script ([#&#8203;18656](sveltejs/svelte#18656))

- fix: reliably resolve append\_style to its correct root ([#&#8203;18614](sveltejs/svelte#18614))

- fix: clean up removed capture event handlers from spread attributes ([#&#8203;18618](sveltejs/svelte#18618))

- fix: don't corrupt renderer type during SSR's legacy `bind:` retry loop ([#&#8203;18616](sveltejs/svelte#18616))

- fix: treat concise arrow function bodies as implicit returns when calculating blockers ([#&#8203;18613](sveltejs/svelte#18613))

- fix: give effect teardowns the value from before the first write in a flush ([#&#8203;18620](sveltejs/svelte#18620))

- fix: avoid double-calling a derived reference when destructuring `$derived` of another `$derived` during server-side rendering ([#&#8203;18668](sveltejs/svelte#18668))

- fix: preserve namespaces in CSS type selectors ([#&#8203;18678](sveltejs/svelte#18678))

- fix: increment private state fields through a non-`this` receiver ([#&#8203;18622](sveltejs/svelte#18622))

- chore: deduplicate client and server context helpers ([#&#8203;18580](sveltejs/svelte#18580))

- fix: release `last_propagated_event` after event propagation settles so it no longer retains the last event's target subtree ([#&#8203;18569](sveltejs/svelte#18569))

- fix: allow custom elements to receive async values as props ([#&#8203;18661](sveltejs/svelte#18661))

- fix: strip comments from inline `style` values in linear time ([#&#8203;18553](sveltejs/svelte#18553))

- fix: prevent declaration comments from breaking server derived references ([#&#8203;18641](sveltejs/svelte#18641))

- perf: make async blocker analysis scale linearly with the number of top-level references ([#&#8203;18549](sveltejs/svelte#18549))

- fix: preserve short-circuiting for logical assignments to private state fields ([#&#8203;18594](sveltejs/svelte#18594))

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zMS4wIiwidXBkYXRlZEluVmVyIjoiNDQuMzEuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
renovate Bot added a commit to cigaleapp/cigale that referenced this pull request Aug 30, 2026
##### [v5.56.10](https://github.com/sveltejs/svelte/blob/HEAD/packages/svelte/CHANGELOG.md#55610)

##### Patch Changes

- fix: preserve CSS escape sequences when printing selectors ([#18667](sveltejs/svelte#18667))

- fix: parse `:nth-child(2n of.foo)` where `of` is not followed by whitespace ([#18611](sveltejs/svelte#18611))

- fix: transform expressions inside labeled statements during server compilation ([#18617](sveltejs/svelte#18617))

- docs: clarify that context lookup includes the current component and all ancestors ([#18581](sveltejs/svelte#18581))

- fix: apply CSS custom properties with falsy values on components ([#18634](sveltejs/svelte#18634))

- fix: correctly print `{#await ... catch x}` et al ([#18645](sveltejs/svelte#18645))

- fix: ignore comments of Program node during migration script ([#18656](sveltejs/svelte#18656))

- fix: reliably resolve append\_style to its correct root ([#18614](sveltejs/svelte#18614))

- fix: clean up removed capture event handlers from spread attributes ([#18618](sveltejs/svelte#18618))

- fix: don't corrupt renderer type during SSR's legacy `bind:` retry loop ([#18616](sveltejs/svelte#18616))

- fix: treat concise arrow function bodies as implicit returns when calculating blockers ([#18613](sveltejs/svelte#18613))

- fix: give effect teardowns the value from before the first write in a flush ([#18620](sveltejs/svelte#18620))

- fix: avoid double-calling a derived reference when destructuring `$derived` of another `$derived` during server-side rendering ([#18668](sveltejs/svelte#18668))

- fix: preserve namespaces in CSS type selectors ([#18678](sveltejs/svelte#18678))

- fix: increment private state fields through a non-`this` receiver ([#18622](sveltejs/svelte#18622))

- chore: deduplicate client and server context helpers ([#18580](sveltejs/svelte#18580))

- fix: release `last_propagated_event` after event propagation settles so it no longer retains the last event's target subtree ([#18569](sveltejs/svelte#18569))

- fix: allow custom elements to receive async values as props ([#18661](sveltejs/svelte#18661))

- fix: strip comments from inline `style` values in linear time ([#18553](sveltejs/svelte#18553))

- fix: prevent declaration comments from breaking server derived references ([#18641](sveltejs/svelte#18641))

- perf: make async blocker analysis scale linearly with the number of top-level references ([#18549](sveltejs/svelte#18549))

- fix: preserve short-circuiting for logical assignments to private state fields ([#18594](sveltejs/svelte#18594))
renovate Bot added a commit to cigaleapp/cigale that referenced this pull request Aug 30, 2026
##### [v5.56.10](https://github.com/sveltejs/svelte/blob/HEAD/packages/svelte/CHANGELOG.md#55610)

##### Patch Changes

- fix: preserve CSS escape sequences when printing selectors ([#18667](sveltejs/svelte#18667))

- fix: parse `:nth-child(2n of.foo)` where `of` is not followed by whitespace ([#18611](sveltejs/svelte#18611))

- fix: transform expressions inside labeled statements during server compilation ([#18617](sveltejs/svelte#18617))

- docs: clarify that context lookup includes the current component and all ancestors ([#18581](sveltejs/svelte#18581))

- fix: apply CSS custom properties with falsy values on components ([#18634](sveltejs/svelte#18634))

- fix: correctly print `{#await ... catch x}` et al ([#18645](sveltejs/svelte#18645))

- fix: ignore comments of Program node during migration script ([#18656](sveltejs/svelte#18656))

- fix: reliably resolve append\_style to its correct root ([#18614](sveltejs/svelte#18614))

- fix: clean up removed capture event handlers from spread attributes ([#18618](sveltejs/svelte#18618))

- fix: don't corrupt renderer type during SSR's legacy `bind:` retry loop ([#18616](sveltejs/svelte#18616))

- fix: treat concise arrow function bodies as implicit returns when calculating blockers ([#18613](sveltejs/svelte#18613))

- fix: give effect teardowns the value from before the first write in a flush ([#18620](sveltejs/svelte#18620))

- fix: avoid double-calling a derived reference when destructuring `$derived` of another `$derived` during server-side rendering ([#18668](sveltejs/svelte#18668))

- fix: preserve namespaces in CSS type selectors ([#18678](sveltejs/svelte#18678))

- fix: increment private state fields through a non-`this` receiver ([#18622](sveltejs/svelte#18622))

- chore: deduplicate client and server context helpers ([#18580](sveltejs/svelte#18580))

- fix: release `last_propagated_event` after event propagation settles so it no longer retains the last event's target subtree ([#18569](sveltejs/svelte#18569))

- fix: allow custom elements to receive async values as props ([#18661](sveltejs/svelte#18661))

- fix: strip comments from inline `style` values in linear time ([#18553](sveltejs/svelte#18553))

- fix: prevent declaration comments from breaking server derived references ([#18641](sveltejs/svelte#18641))

- perf: make async blocker analysis scale linearly with the number of top-level references ([#18549](sveltejs/svelte#18549))

- fix: preserve short-circuiting for logical assignments to private state fields ([#18594](sveltejs/svelte#18594))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CSS parser rejects minified :nth-child of syntax (2n of.foo) — REGEX_NTH_OF requires whitespace on both sides of of

2 participants