Repository navigation
fix(core): read a type assertion in a for head, collect a rest parameter's default and a parameter after an arrow function's rest parameter, reject ? and type annotations outside parameters, and end an async rest parameter after its annotation (#723, #724, #725, #726, #727) - #777
Merged
Merged
Conversation
…meter's default and a parameter after an arrow function's rest parameter, reject `?` and type annotations outside parameters, and end an async rest parameter after its annotation (#723, #724, #725, #726, #727) - #723: a type assertion in a `for…in` or `for…of` head parses and is kept, as in an assignment target (`toAssignable`, sveltejs/acorn-typescript#148). - #724: a `?` or a type annotation after an item of a parenthesized expression or an expression list, or a type annotation after a spread, fails where the list isn't an arrow function's parameters (`parseParenItem`, `parseSpread`, sveltejs/acorn-typescript#149). - #725: an async arrow function's rest parameter covers its `?` and type annotation (`parseSpread`, sveltejs/acorn-typescript#150). - #726: a rest parameter's default records TS1048 when collecting, and the tree leaves it out, as typescript-estree does; tsrx-prettier-plugin refuses it. - #727: a parameter after an arrow function's rest parameter records TS1014 when collecting, and the arrow function keeps it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue. You can view the agent here.
Reviewed by Cursor Bugbot for commit 0d91fc9. Configure here.
1 task done
This was referenced Sep 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Closes #723
Closes #724
Closes #725
Closes #726
Closes #727
These are the follow-ups from #728: arrow function parameters, rest parameters, and
forheads that TSRX read differently from TypeScript's TSX parser. #723, #724, and #725 are caused in@sveltejs/acorn-typescript, and each fix is a narrow override inTSRXPluginmarkedUPSTREAM(…). #726 and #727 are checker-level mistakes (#415) where acorn is right about JavaScript:collectandloosemode now record TypeScript's error and keep parsing, and a strict parse throws it. Some inputs that parsed now throw, because TypeScript rejects them (see Behavior change).The bugs and the fixes
#723: a type assertion in a
for…inorfor…ofheadfor ((a as T) of x),for ([a as T] of x),for ({ a: b! } of x), andfor ((a!) in x)failed withUnexpected type cast in parameter position.in every mode. acorn converts a loop's head withtoAssignable(init, false), and acorn-typescript raises that error whenisBindingis false. Its flag means the opposite of @babel/parser'sisLHS, where the code comes from (sveltejs/acorn-typescript#148).toAssignableconverts a type assertion's expression without the error whenisBindingis false, and keeps the assertion as acorn-typescript does for an assignment target (withpreserveTypeScriptWrapper, or in place for a nested one).parseForStatementpassespreserveTypeScriptWrapper, so the loop's left side keeps the assertion and the parentheses around it (preserveParens), as an assignment target's does. acorn ignorestoAssignable's result there; TSRX's copy uses it.checkLValPattern, which raises the error for an assertion in a binding, is unchanged, so(a as T) => astill fails.#724: a
?or a type annotation outside an arrow function's parametersacorn-typescript's
parseParenItemreads a?and a type annotation after each item of a parenthesized expression and of every list thatparseExprListreads (call andnewarguments, array literals, decorator arguments), andparseExprListreads a type annotation after a spread. They're meant for an arrow function's parameters. Where the list wasn't one, the item kept them:(x: number)crashed the compile (Not implemented: TSTypeCastExpression), andf(x?)andf(...x: number[])were printed back (sveltejs/acorn-typescript#149).parseParenAndDistinguishExpressionandparseExprListnote the list being read (#expressionList), and whether it can be an arrow function's parameters: a parenthesized list that can be an arrow function (canBeArrow), or the arguments ofasync (…). No other list can.parseParenItemandparseSpreadpass what they read to#checkParameterSyntax. In a list that can't be an arrow function's parameters, it raises @babel/parser's error there:Did not expect a type annotation here.at the annotation's:, orUnexpected tokenat the?. Otherwise it notes the first one, and the list raises it when no arrow function follows (parseParenAndDistinguishExpression,#parseAsyncArguments).parseEffects.willSet).?after a spread among the arguments ofasync (…)goes through the same check, so#readingAsyncArgumentsand thequestionfield are gone. Its behavior is the same.This throws
Unexpected token (3:2)at the?, where TypeScript reports TS1109 at the:.#725: an async arrow function's rest parameter's range
The rest parameter of
async (...a: number[]) => aended before its type annotation, since acorn-typescript'sparseExprListsets it without moving the spread's end (sveltejs/acorn-typescript#150).tsrx-prettier-pluginplaces comments by these ranges, and moved the comment inasync (...a /* c */: number[]) => aafter the annotation.parseSpreadnow reads the annotation among the arguments ofasync (…)itself, beforeparseExprListdoes, and moves the spread's end past it, and past a?(#702), as a rest parameter's is elsewhere. The formatter keeps the comment where Prettier does:const f = async (...a /* c */ : number[]) => a;.#726: a rest parameter's default (TS1048)
function f(...a = []) {},(...a = []) => a,async (...a = []) => a, andtype F = (...a = []) => voidfailed in every mode. TypeScript's parser reads the default, and its checker reports TS1048A rest parameter cannot have an initializer.at the parameter's name.#readRestParameterDefaultreads the default after the rest element, from its=. ESTree's rest element has no place for it, and typescript-estree leaves it out of the tree while the parameter's range covers it; so does this. It keeps the rest element in aWeakSetfor the errors, so the tree gets no new field.parseBindingListItemraises TS1048 at the name and reads the default, in the lists acorn reads withparseBindingList(and TSRX's collect-mode copy).parseParenItemreads the default after a rest element in a parenthesized list that can be an arrow function's parameters,parseSpreadafter the?or type annotation of a spread among the arguments ofasync (…), andparseSubscriptAsyncArrowtakes it out of the spread's argument (...a = []is an assignment in a call).parseFunctionBodyraises TS1048 once the arrow function is read, with TS1047 ([Bug]: tsrx-core: An async arrow function's optional rest parameter fails to parse in every mode, where TypeScript reports TS1047 from its checker #702, [Bug]: tsrx-core: A syntax error in a generic arrow function is reported asUnexpected tokenat its type parameters #703). A generic async arrow function's parameters leave it toparseFunctionBodytoo, as for TS1047.tsParseBindingListForSignature. That method now reads the list throughparseBindingListin a strict parse too, so thatparseBindingListItemknows it's a parameter list. That changes nothing else there (see Verification).tsrx-prettier-pluginrefuses it (REJECTED_MISTAKES), since the tree doesn't keep the default. Prettier'stypescriptparser drops it (function f(...a) {}).#727: a parameter after an arrow function's rest parameter (TS1014)
(...a, b) => 1threwComma is not permitted after the rest elementin collect mode, which records it for a function, a signature, and an async arrow function. acorn'sparseParenAndDistinguishExpressionends a parenthesized list at its rest element and raises the error at the comma.#collectCheckerLevelError, at that comma, calls#readArrowParametersPastRestElement. In a list that can be an arrow function's parameters, at its top level, where the rest of the list reads as parameters and=>follows (#readsAsArrowParameters, the lookahead that#startsArrowParameterPropertyuses), it records the error at the comma and reads the items after it, as#parseBindingListPastRestElementdoes for a function's.parseParenArrowListadds them to the arrow function's parameters and checks their pattern errors. Otherwise the error is thrown, as before.#parseArrowParameterPropertyraised it after a rest element with modifiers,(x, public ...a, b) => b, in both kinds of list. It records it now, as#parseRestParameterPropertydoes for a function's, and the list goes on.Behavior change
TypeScript's parser rejects each of these (decision 23 in #596). They parsed in every mode, and now throw in every mode (#724):
(x: number),(a, b: T),f(x: number),f?.(x: number),f<T>(x: number),new F(x: number),[x: number],([...x: number[]]),({ a: (b: number) }),@dec(x: number) class A {},([x: number]) => 1,async(x: number),f(...x: number[]),async(...x: number[])Did not expect a type annotation here.at the:(TypeScript: TS1005'=>' expected.or',' expected.)(x?: number),(x, y?),f(x?),async(x?),([x?]) => 1Unexpected tokenat the?(TypeScript: TS1005 or TS1109)Inputs that threw and parse now: #723's
forheads in every mode, and #726's and #727's incollectandloosemode. Inputs that threw and throw a different error now:[Bug]: tsrx-core: Collect mode throws for a rest parameter with a default, which TypeScript reports from its checker (TS1048) #726, strict: TS1048 at the name instead of
Unexpected tokenat the=, orRest elements cannot have a default valuefor an async arrow function. In a strict parse,class A { constructor(public ...a = []) {} }still fails at the....(...a = [])with no=>fails at the...instead of the=, as(...a)does: the list reads the default, and acorn's check for a rest element in a parenthesized expression comes first.x || (...a = []) => a, whose list can't be an arrow function, still fails at the=.async(...a: T = [])with no=>fails at the:([Bug]: tsrx-core: A?or a type annotation in parentheses or call arguments parses when no=>follows, and the compile crashes or keeps it #724) instead of the=.This fails at the
:([Bug]: tsrx-core: A?or a type annotation in parentheses or call arguments parses when no=>follows, and the compile crashes or keeps it #724) instead of the=>, which the line break keeps from making it an arrow function:@tsrx/prettier-pluginparses strictly, so it throws for all of the #724 inputs too.Overrides
toAssignable, withparseForStatementisBindingis false, and keeps it.parseParenItem,parseSpread,#checkParameterSyntax, fromparseParenAndDistinguishExpression,parseExprList, and#parseAsyncArguments?or a type annotation where the list isn't an arrow function's parameters.parseSpreadasync (…)and moves its end.parseBindingListItem,parseParenItem,parseSpread,parseSubscriptAsyncArrow,#readRestParameterDefault,parseFunctionBody,tsParseBindingListForSignature#collectCheckerLevelError,#readArrowParametersPastRestElement,parseParenArrowList,#parseArrowParameterPropertyI checked sveltejs/acorn-typescript#110's head: it doesn't touch
toAssignable,parseParenItem,parseExprList, orparseParenAndDistinguishExpression, so it fixes none of these, as the upstream issues say.The formatters
tsrx-prettier-pluginparses withcollect: true:typescriptparser formats them: [Bug]: tsrx-core: A type assertion in afor…inorfor…ofhead fails to parse, although TypeScript accepts it #723'sforheads (for ((a as number) of x);givesfor (a as number of x);), [Bug]: tsrx-core, tsrx-prettier-plugin: An async arrow function's rest parameter ends before its type annotation, so the formatter moves a comment past the annotation #725's comment, and [Bug]: tsrx-core: Collect mode throws for a parameter after an arrow function's rest parameter, which TypeScript reports from its checker (TS1014) #727's parameters (const f = (...a, b) => [a, b];).A rest parameter cannot have an initializer., added toREJECTED_MISTAKES), and [Bug]: tsrx-core: A?or a type annotation in parentheses or call arguments parses when no=>follows, and the compile crashes or keeps it #724's inputs, which Prettier'stypescriptparser refuses with its own messages.prettier-known-failures.jsonis unchanged (empty).Language server
The language server compiles with
loose: true. I ran the new collect-mode parses throughcompile_to_volar_mappingsfor all five targets:(...a, b) => [a, b],(x, public ...a, b) => 1,function h(...a = []) {},(...a: number[] = []) => a,async (...a = []) => a,async (...a: number[] = [1]) => a,type H = (...a = []) => void,interface I { m(...a = []): void }, #723'sforheads, and both kinds inside a template's setup. None fails. The virtual code keeps a parameter after a rest parameter ((...a, b) => [a, b]), so TypeScript reports its own TS1014 there too, following #558's convention. It leaves out a rest parameter's default, as the tree does, so only TSRX reports TS1048. A strict compile prints #723's heads without the parentheses (for (a as string of x)), which TypeScript accepts.Downstream
No node changes shape for code that parses today. #725 moves the end of an async arrow function's rest parameter to where a rest parameter's end already is without
async, with the same fields. Everything else is code that threw and parses now, or parsed and throws now, with no other AST change. So I didn't check Ripple or Octane.Verification
Parse comparison with
main(9c2f24f8;e63c530a, merged in, changes no parser file). 19,898 sources, 18,924 distinct:.tsrxfiles (59 distinct);packages/tsrx-prettier-plugin/tests/prettier/(readSnapshotEntries/readFormatCase);Each was parsed strict, with
{ collect: true, errors: [], comments: [], preserveParens: true }, and with{ loose: true }, before and after. That's 59,694 parses in four worker threads, each killed if a parse took over 15 s. None hung. I compared every AST, comment list, and error list, or the thrown message and position.85 sources changed. No
.tsrxfile and no Prettier input or output changed. 72 are this PR's new test strings. The other 13 are existing test strings:?or annotation ([Bug]: tsrx-core, tsrx-prettier-plugin: An async arrow function's rest parameter ends before its type annotation, so the formatter moves a comment past the annotation #725), such asconst f = async (x, ...a?: number[]) => a;and fix(core): read an async arrow function's optional rest parameter, report a generic arrow function's errors where they are, collect a signature parameter's default, and rejectasync(a)(b) =>and type assertions in arrow parameters (#702, #703, #704, #705, #706) #728's [Bug]: tsrx-core: An async arrow function's optional rest parameter fails to parse in every mode, where TypeScript reports TS1047 from its checker #702 tests;m(@a ...rest = []) {}andm(...rest = []) {}), which record TS1048 now ([Bug]: tsrx-core: Collect mode throws for a rest parameter with a default, which TypeScript reports from its checker (TS1048) #726); the test compares the two and passes;(x: number)andfmt(x: string)insource-mappings.js,(bar: string, baz?: string): voidincompile.js, and a type inref-types.test.js, which throw now, or throw at the first:([Bug]: tsrx-core: A?or a type annotation in parentheses or call arguments parses when no=>follows, and the compile crashes or keeps it #724).By mode:
?or a type annotation in parentheses or call arguments parses when no=>follows, and the compile crashes or keeps it #724), 15 from throwing to parsing ([Bug]: tsrx-core: A type assertion in afor…inorfor…ofhead fails to parse, although TypeScript accepts it #723), 18 throw a different error ([Bug]: tsrx-core: Collect mode throws for a rest parameter with a default, which TypeScript reports from its checker (TS1048) #726, and [Bug]: tsrx-core: A?or a type annotation in parentheses or call arguments parses when no=>follows, and the compile crashes or keeps it #724 where they threw already), and 10 parse with a different AST ([Bug]: tsrx-core, tsrx-prettier-plugin: An async arrow function's rest parameter ends before its type annotation, so the formatter moves a comment past the annotation #725).?or a type annotation in parentheses or call arguments parses when no=>follows, and the compile crashes or keeps it #724), 36 from throwing to parsing ([Bug]: tsrx-core: A type assertion in afor…inorfor…ofhead fails to parse, although TypeScript accepts it #723, [Bug]: tsrx-core: Collect mode throws for a rest parameter with a default, which TypeScript reports from its checker (TS1048) #726, [Bug]: tsrx-core: Collect mode throws for a parameter after an arrow function's rest parameter, which TypeScript reports from its checker (TS1014) #727), 5 throw a different error, and 20 parse with a different AST, all of them [Bug]: tsrx-core, tsrx-prettier-plugin: An async arrow function's rest parameter ends before its type annotation, so the formatter moves a comment past the annotation #725's ranges, with the same errors.I also ran signature parameter lists, strict and collecting, before and after, since
tsParseBindingListForSignaturereads them throughparseBindingListin a strict parse now: optional patterns and rest parameters in function types, method, call, and construct signatures, ambient functions, and parameters after a rest parameter. None changed.I timed parsing the
.tsrxfiles and Prettier sources in alternating processes before and after. The machine was under heavy load (load average about 30), and the difference was within run-to-run noise.Tests.
upstream-parser-workarounds.test.js, new describes:for…inorfor…ofhead is rejected sveltejs/acorn-typescript#148: [Bug]: tsrx-core: A type assertion in afor…inorfor…ofhead fails to parse, although TypeScript accepts it #723's heads in every mode, includingsatisfies, a member expression,for await, and a template's setup, keeping the assertion, and the parentheses around it withpreserveParens.?in parentheses or call arguments is accepted when no=>follows sveltejs/acorn-typescript#149: the error at the first?or annotation in every mode, for parenthesized lists, calls,new, arrays, decorators, the arguments ofasync (…)with no=>, across lines, and in a template; and arrow functions' parameters and conditional expressions that still parse.async, including a comment before the annotation.parser.test.js,mistakes that TypeScript reports only from its checker: cases for [Bug]: tsrx-core: Collect mode throws for a rest parameter with a default, which TypeScript reports from its checker (TS1048) #726 (functions, pattern and annotated parameters, arrow functions, async and generic ones across lines, a template, a function type and an interface with TS2371, and a parameter property) and [Bug]: tsrx-core: Collect mode throws for a parameter after an arrow function's rest parameter, which TypeScript reports from its checker (TS1014) #727 (several parameters and rest parameters after a rest parameter, a return type, across lines in a template, and after a rest parameter with modifiers, async too). Each checks the collected messages at their positions, the tree, and the strict throw. Newits: [Bug]: tsrx-core: Collect mode throws for a rest parameter with a default, which TypeScript reports from its checker (TS1048) #726 and [Bug]: tsrx-core: Collect mode throws for a parameter after an arrow function's rest parameter, which TypeScript reports from its checker (TS1014) #727 inputs that still throw where no arrow function follows, andasync(...b = []), which is still a call with an assignment in the spread.tsrx-prettier-plugin/tests/tsrx.test.js: [Bug]: tsrx-core: A?or a type annotation in parentheses or call arguments parses when no=>follows, and the compile crashes or keeps it #724's errors, [Bug]: tsrx-core: Collect mode throws for a rest parameter with a default, which TypeScript reports from its checker (TS1048) #726's refusal, [Bug]: tsrx-core: Collect mode throws for a parameter after an arrow function's rest parameter, which TypeScript reports from its checker (TS1014) #727's parameters, and a new describe for [Bug]: tsrx-core, tsrx-prettier-plugin: An async arrow function's rest parameter ends before its type annotation, so the formatter moves a comment past the annotation #725's comments and [Bug]: tsrx-core: A type assertion in afor…inorfor…ofhead fails to parse, although TypeScript accepts it #723's heads.main's parser. The others guard behavior that already worked.Local runs. These pass:
tsrx-utils,tsrx-analyze,prettier-plugin,tsrx-prettier-plugin,tsrx-react,tsrx-preact,tsrx-solid,tsrx-vue, andtsrx-hono(--maxWorkers=4),prettier-pluginandtsrx-prettier-pluginagain after mergingmain.pnpm typecheck.tsrx-tscisn't linked after--ignore-scripts, so I built@tsrx/typescript-pluginand ranvite-plugin-hono'stypecheck:teststhroughpackages/typescript-plugin/dist/tsc.js, then the steps after it.pnpm format(no changes outside this PR's files),pnpm format:checkafter mergingmain, andpnpm changeset:check.A
patchchangeset for@tsrx/coreis included.tsrx-prettier-pluginis private.Findings
Filed here, each with a separate cause and not fixed in this PR:
for awaithead fails to parse, although TypeScript accepts it #769:for await (a as T of x)fails to parse in every mode, although TypeScript accepts it: acorn reads afor awaithead withparseExprSubscripts, which doesn't reach acorn-typescript'sasandsatisfies. With [Bug]: tsrx-core: A type assertion in afor…inorfor…ofhead fails to parse, although TypeScript accepts it #723 fixed,tsrx-prettier-pluginformatsfor await ((a as T) of x)to that, as Prettier does, which then fails to parse. Filed upstream as A type assertion in afor awaithead fails to parse sveltejs/acorn-typescript#157.const [...a = 1] = b,const { ...a = 1 } = b,[...a = 1] = b), which TypeScript reports from its checker (TS1186). It's [Bug]: tsrx-core: Collect mode throws for a rest parameter with a default, which TypeScript reports from its checker (TS1048) #726 for patterns, read in three other places.const { ...a, b } = c), which TypeScript reports from its checker (TS2462).parsePropertyraises at the comma, where#collectCheckerLevelErroronly takes a trailing comma.Same causes as the issues here, and not changed:
(...a? = []) => arecords TS1047 and TS1048, and(...a = [], b) => 1records TS1014 and TS1048.=>at the type parameters, as fix(core): read an async arrow function's optional rest parameter, report a generic arrow function's errors where they are, collect a signature parameter's default, and rejectasync(a)(b) =>and type assertions in arrow parameters (#702, #703, #704, #705, #706) #728 noted for [Bug]: tsrx-core: A syntax error in a generic arrow function is reported asUnexpected tokenat its type parameters #703:<T,>(...a, b) => 1throwsUnexpected token (1:10), andasync <T,>(...a, b) => 1throwsUnexpected tokenat its=>. Collecting reads both, with TS1014.🤖 Generated with Claude Code
Note
Medium Risk
Changes core parser behavior for common TypeScript edge cases; some inputs that previously parsed now throw, which can affect format-on-save and strict tooling paths.
Overview
This PR tightens
@tsrx/coreparsing so arrow parameters, rest parameters, andfor…in/for…ofheads match TypeScript more closely, with matching Prettier plugin rejections and tests.Now parses and keeps in the AST (collect/loose): type assertions in loop heads (
for ((a as T) of x)); async rest parameters whose source range includes?and type annotations (fixes comment placement in the formatter); arrow functions with parameters after a rest ((...a, b) => …), with TS1014 recorded at the comma.Checker-level mistakes in collect/loose: rest-parameter defaults (
...a = []) raiseA rest parameter cannot have an initializer., read past the=but omit the default from the tree (like typescript-estree); signatures still get TS2371 where applicable.tsrx-prettier-plugintreats that message like other rejected mistakes.Stricter errors (aligned with TypeScript): optional markers and type annotations in non-parameter lists—parentheses, call/
new/array/decorator arguments without=>—now fail withDid not expect a type annotation here.orUnexpected tokeninstead of crashing or printing invalid code.Implementation is mostly
TSRXPluginoverrides (toAssignable,parseParenItem,parseSpread,#readRestParameterDefault,#readArrowParametersPastRestElement,#checkParameterSyntax, etc.) plus expanded parser and Prettier tests.Reviewed by Cursor Bugbot for commit 5608aa4. Bugbot is set up for automated code reviews on this repo. Configure here.