fix: don't mistake type identifier expressions for TS type declarations in tags#18330
Conversation
…ations in tags
Detect TypeScript `type` declarations by actually parsing instead of by
character-class blacklists, so that expressions like `{type === 'all' ? a : b}`
or `{type instanceof Foo}` aren't misclassified as malformed declarations.
Closes #18328
🦋 Changeset detectedLatest commit: cca40b1 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
|
I also submitted a PR to improve the regular expression (#18329), but I realized that handling edge cases such as generics would be difficult with a regex-based approach alone. As a result, I changed the implementation to first perform a lightweight check using a regular expression and then use Acorn for a more accurate determination. |
|
dummdidumm
left a comment
There was a problem hiding this comment.
Goddammit I feared something would slip through. Left a few comments but overall solution makes sense
| {type / 2} | ||
| {type > 1} | ||
| {type instanceof Foo} | ||
| {type in foo} |
There was a problem hiding this comment.
These additions to this test should be enough we don't need all the other tests
There was a problem hiding this comment.
If we’re going with an AST-based solution, then the change at parser.root.comments.length = initial_comment_count; (https://github.com/sveltejs/svelte/pull/18330/changes#diff-07a9d6689d63c982b2e3a45ae4bd624240d27824e8c5bdce8a539b4d2a7c36f1R96) is necessary. Without it, the test at {type instanceof /* probe */ Object} (https://github.com/sveltejs/svelte/pull/18330/changes#diff-37911caa74a576888755175e439f6605c76c5c17695b44de73e2f028295ee3a2R1) fails.
But we can delete packages/svelte/tests/runtime-runes/samples/declaration-tag-type-identifier test. 9826361
| // discard probe comments so `read_expression` doesn't re-add them | ||
| parser.root.comments.length = initial_comment_count; | ||
| return null; | ||
| } |
There was a problem hiding this comment.
This feels overcomplicated. If you add type to the list of unsupported in the regex and then in the "if unsupported" condition check if it's a usage of type as a reference (using your new regex) then don't throw the error it should come down to the same result
There was a problem hiding this comment.
Are you saying that you think the approach in #18329 is preferable to using an AST-based solution?
I feel the AST-based approach is more robust. With the approach in #18329, we’d likely keep running into edge cases and need to add support for them individually—for example, patterns involving generics such as {type<string>('generic-call')}.
0b69c5d to
9826361
Compare
Coverage is already provided by the validator samples (declaration-tag-invalid-type-2 exercises the fall-through path; declaration-tag-invalid-type-unicode/-generic exercise the parse-confirmed type-alias error path).
9826361 to
b48ad46
Compare
| @@ -0,0 +1 @@ | |||
| {type instanceof /* probe */ Object} | |||
There was a problem hiding this comment.
Without this, comments end up being registered twice.
014cb4b to
b48ad46
Compare
There was a problem hiding this comment.
Pull request overview
Fixes #18328: expressions starting with type (e.g. {type === 'all' ? a : b}, {type instanceof Foo}) were being misclassified as malformed TypeScript type declarations because of a regex character-class blacklist that couldn't tell identifiers from declaration keywords. The fix replaces the blacklist with a "maybe" hint and confirms by actually parsing the contents, falling back to expression parsing when the result is an ExpressionStatement.
Changes:
- In
state/tag.js, splittypeout of the unsupported-declaration regex into aregex_maybe_type_declarationhint, then disambiguate viaparse_statement_at; if the result is anExpressionStatement, drop the speculative comments and fall through to expression parsing, otherwise (a TSTSTypeAliasDeclaration) raisedeclaration_tag_invalid_type. - In
utils/bracket.js, makefind_regex_endonly treat a/as the start of a regex literal when a closing/is found before the next newline, and avoid jumpingitoInfinityinfind_matching_bracketwhen no closing slash is present (fixes loose recovery for cases like{let x = a / }). - Added validator coverage for many
{type ...}expressions plus a TS type alias, a parser-modern fixture asserting comment handling on the speculative parse, and an extended loose-declaration-tag fixture.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/svelte/src/compiler/phases/1-parse/state/tag.js | Speculatively parse {type ...}, fall back to expression on ExpressionStatement, restore comment count |
| packages/svelte/src/compiler/phases/1-parse/utils/bracket.js | Only treat / as regex when closing slash precedes newline; guard against Infinity cursor advance |
| packages/svelte/tests/validator/samples/declaration-tag-invalid-type-2/input.svelte | Add <script lang="ts"> and many {type ...} expression cases |
| packages/svelte/tests/validator/samples/declaration-tag-invalid-type-2/errors.json | Update expected error location to the remaining real type-alias case |
| packages/svelte/tests/parser-modern/samples/tag-type-identifier-probe-comment/{input.svelte,output.json} | Snapshot showing comments aren't duplicated when speculative parse falls through |
| packages/svelte/tests/parser-modern/samples/loose-declaration-tag/{input.svelte,output.json} | Cover loose recovery for an unterminated division-like / |
| .changeset/fix-type-identifier-in-tag.md | Patch changeset entry |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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.1 ### Patch Changes - fix: error at compile time on duplicate snippet/declaration tag definitions ([#18351](#18351)) - fix: parse declaration tag contents more robustly ([#18353](#18353)) - fix: correctly transform references to earlier declarators in a declaration tag (e.g. `{let a = $state(0), b = $derived(a * 2)}`) ([#18348](#18348)) - fix: avoid spurious `state_referenced_locally` warnings for `$derived` declarations in declaration tags ([#18348](#18348)) - fix: tolerate whitespace before `let`/`const` in declaration tags ([#18348](#18348)) - fix: prevent infinite loop when a tag's expression ends with a trailing `/` at the end of the input ([#18350](#18350)) - fix: more robust parsing of declaration tags with regards to `type` ([#18330](#18330)) - fix: preserve newlines in spread input values when the `type` attribute is applied after `value` ([#18345](#18345)) - fix: update `SvelteURLSearchParams` when setting duplicate keys to the same joined value ([#18336](#18336)) - fix: check references for blockers on server, too ([#18352](#18352)) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.0` → `5.56.1`](https://renovatebot.com/diffs/npm/svelte/5.56.0/5.56.1) |  |  | --- ### Release Notes <details> <summary>sveltejs/svelte (svelte)</summary> ### [`v5.56.1`](https://github.com/sveltejs/svelte/blob/HEAD/packages/svelte/CHANGELOG.md#5561) [Compare Source](https://github.com/sveltejs/svelte/compare/svelte@5.56.0...svelte@5.56.1) ##### Patch Changes - fix: error at compile time on duplicate snippet/declaration tag definitions ([#​18351](sveltejs/svelte#18351)) - fix: parse declaration tag contents more robustly ([#​18353](sveltejs/svelte#18353)) - fix: correctly transform references to earlier declarators in a declaration tag (e.g. `{let a = $state(0), b = $derived(a * 2)}`) ([#​18348](sveltejs/svelte#18348)) - fix: avoid spurious `state_referenced_locally` warnings for `$derived` declarations in declaration tags ([#​18348](sveltejs/svelte#18348)) - fix: tolerate whitespace before `let`/`const` in declaration tags ([#​18348](sveltejs/svelte#18348)) - fix: prevent infinite loop when a tag's expression ends with a trailing `/` at the end of the input ([#​18350](sveltejs/svelte#18350)) - fix: more robust parsing of declaration tags with regards to `type` ([#​18330](sveltejs/svelte#18330)) - fix: preserve newlines in spread input values when the `type` attribute is applied after `value` ([#​18345](sveltejs/svelte#18345)) - fix: update `SvelteURLSearchParams` when setting duplicate keys to the same joined value ([#​18336](sveltejs/svelte#18336)) - fix: check references for blockers on server, too ([#​18352](sveltejs/svelte#18352)) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled because a matching PR was automerged previously. ♻ **Rebasing**: Whenever PR is behind base branch, 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](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTUuMCIsInVwZGF0ZWRJblZlciI6IjQzLjE5NS4wIiwidGFyZ2V0QnJhbmNoIjoiZGV2ZWxvcCIsImxhYmVscyI6W119--> Co-authored-by: huskas-2189 <huskas-2189@noreply.codeberg.org> Reviewed-on: https://codeberg.org/huskas-2189/Bookmark/pulls/82
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.0` → `5.56.1`](https://renovatebot.com/diffs/npm/svelte/5.56.0/5.56.1) |  |  | --- ### Release Notes <details> <summary>sveltejs/svelte (svelte)</summary> ### [`v5.56.1`](https://github.com/sveltejs/svelte/blob/HEAD/packages/svelte/CHANGELOG.md#5561) [Compare Source](https://github.com/sveltejs/svelte/compare/svelte@5.56.0...svelte@5.56.1) ##### Patch Changes - fix: error at compile time on duplicate snippet/declaration tag definitions ([#​18351](sveltejs/svelte#18351)) - fix: parse declaration tag contents more robustly ([#​18353](sveltejs/svelte#18353)) - fix: correctly transform references to earlier declarators in a declaration tag (e.g. `{let a = $state(0), b = $derived(a * 2)}`) ([#​18348](sveltejs/svelte#18348)) - fix: avoid spurious `state_referenced_locally` warnings for `$derived` declarations in declaration tags ([#​18348](sveltejs/svelte#18348)) - fix: tolerate whitespace before `let`/`const` in declaration tags ([#​18348](sveltejs/svelte#18348)) - fix: prevent infinite loop when a tag's expression ends with a trailing `/` at the end of the input ([#​18350](sveltejs/svelte#18350)) - fix: more robust parsing of declaration tags with regards to `type` ([#​18330](sveltejs/svelte#18330)) - fix: preserve newlines in spread input values when the `type` attribute is applied after `value` ([#​18345](sveltejs/svelte#18345)) - fix: update `SvelteURLSearchParams` when setting duplicate keys to the same joined value ([#​18336](sveltejs/svelte#18336)) - fix: check references for blockers on server, too ([#​18352](sveltejs/svelte#18352)) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled because a matching PR was automerged previously. ♻ **Rebasing**: Whenever PR is behind base branch, 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](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTUuMCIsInVwZGF0ZWRJblZlciI6IjQzLjE5NS4wIiwidGFyZ2V0QnJhbmNoIjoiZGV2ZWxvcCIsImxhYmVscyI6W119--> Co-authored-by: huskas-2189 <huskas-2189@noreply.codeberg.org> Reviewed-on: https://codeberg.org/huskas-2189/Bookmark/pulls/82
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.0` → `5.56.1`](https://renovatebot.com/diffs/npm/svelte/5.56.0/5.56.1) |  |  | --- ### Release Notes <details> <summary>sveltejs/svelte (svelte)</summary> ### [`v5.56.1`](https://github.com/sveltejs/svelte/blob/HEAD/packages/svelte/CHANGELOG.md#5561) [Compare Source](https://github.com/sveltejs/svelte/compare/svelte@5.56.0...svelte@5.56.1) ##### Patch Changes - fix: error at compile time on duplicate snippet/declaration tag definitions ([#​18351](sveltejs/svelte#18351)) - fix: parse declaration tag contents more robustly ([#​18353](sveltejs/svelte#18353)) - fix: correctly transform references to earlier declarators in a declaration tag (e.g. `{let a = $state(0), b = $derived(a * 2)}`) ([#​18348](sveltejs/svelte#18348)) - fix: avoid spurious `state_referenced_locally` warnings for `$derived` declarations in declaration tags ([#​18348](sveltejs/svelte#18348)) - fix: tolerate whitespace before `let`/`const` in declaration tags ([#​18348](sveltejs/svelte#18348)) - fix: prevent infinite loop when a tag's expression ends with a trailing `/` at the end of the input ([#​18350](sveltejs/svelte#18350)) - fix: more robust parsing of declaration tags with regards to `type` ([#​18330](sveltejs/svelte#18330)) - fix: preserve newlines in spread input values when the `type` attribute is applied after `value` ([#​18345](sveltejs/svelte#18345)) - fix: update `SvelteURLSearchParams` when setting duplicate keys to the same joined value ([#​18336](sveltejs/svelte#18336)) - fix: check references for blockers on server, too ([#​18352](sveltejs/svelte#18352)) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled because a matching PR was automerged previously. ♻ **Rebasing**: Whenever PR is behind base branch, 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](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTUuMCIsInVwZGF0ZWRJblZlciI6IjQzLjE5NS4wIiwidGFyZ2V0QnJhbmNoIjoiZGV2ZWxvcCIsImxhYmVscyI6W119--> Co-authored-by: huskas-2189 <huskas-2189@noreply.codeberg.org> Reviewed-on: https://codeberg.org/huskas-2189/Bookmark/pulls/82
Detect TypeScript
typedeclarations by actually parsing instead of by character-class blacklists, so that expressions like{type === 'all' ? a : b}or{type instanceof Foo}aren't misclassified as malformed declarations.Closes #18328
Before submitting the PR, please make sure you do the following
feat:,fix:,chore:, ordocs:.packages/svelte/src, add a changeset (npx changeset).Tests and linting
pnpm testand lint the project withpnpm lint