Skip to content

JavaScript: a non-optional list slot reads as empty when a peer sends none - #8798

Merged
knutwannheden merged 1 commit into
mainfrom
js-printer-crashes-on-absent-propertyassignment.modifiers
Sep 8, 2026
Merged

knutwannheden merged 1 commit into
mainfrom
js-printer-crashes-on-absent-propertyassignment.modifiers

Conversation

@knutwannheden

@knutwannheden knutwannheden commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Printing a JavaScript or TypeScript source file whose LST predates #8720 fails with a TypeError from the printer. Observed while running recipes over a corpus of open-source repositories: 879 failed prints across 293 files in 7 repositories, reproduced on two consecutive days and on two different bundled @openrewrite/rewrite versions (8.91.3 and 8.91.5).

TypeError: propertyAssignment.modifiers is not iterable
    at JavaScriptPrinter.visitPropertyAssignment (.../src/javascript/print.ts:1423:44)
    at async JavaScriptPrinter.visitBlock (.../src/javascript/print.ts:556:9)
    at async JavaScriptPrinter.visitNewClass (.../src/javascript/print.ts:1311:26)
  org.openrewrite.rpc.RewriteRpc.print(RewriteRpc.java:689),
  org.openrewrite.SourceFile.printAll(SourceFile.java:123),
  org.openrewrite.text.PlainTextParser.convert(PlainTextParser.java:58),
  org.openrewrite.text.FindAndReplace$1.visit(FindAndReplace.java:118)

Per repository: Netflix/pollyjs 411, spring-guides/tut-spring-security-and-angular-js 339, Netflix/conductor 69, spring-projects/spring-security 33, Netflix/metaflow 21, spring-projects/spring-restdocs 3, spring-projects/spring-session 3. The failure does not mark the source file as errored, so it is easy to miss. #8799 hit the same absent slot from the auto-formatter.

Mechanism

JS.PropertyAssignment.modifiers was added in #8720, so an LST serialized before it deserializes into the current Java type with modifiers == null. RpcSendQueue.sendList sends nothing for a null list against a null before-value, and the receiver leaves the slot at its undefined default. A freshly parsed tree is unaffected — the TypeScript parser populates the slot, so both peers hold [].

Fix

The receiver already has a helper for this. Non-optional list slots take receiveListDefined, optional ones take receiveList — 19 slots against 1 in javascript/rpc.ts, and in java/rpc.ts the bare calls end in || []. #8720 added the one slot that reads a non-optional list with the un-coerced call (JSX.Tag.children, the only other bare call, is genuinely optional: its self-closing union branch declares children?: undefined).

But receiveListDefined was (await this.receiveList(...))! — a non-null assertion the compiler erases, so it promised the type checker something it never enforced at runtime. It now coerces with ?? [], which makes its name true and closes the same gap for all 53 of its call sites. modifiers then uses it.

This deliberately leaves JS.PropertyAssignment alone. A null list is already harmless on the Java side — ListUtils.map(null, …) returns null and the wither is a no-op — so the model needs no nullable-slot accommodation.

Superseding #8799

#8799 guarded one consumer, JavaScriptVisitor.visitPropertyAssignment, with ?? []. That did not cover this crash, because JavaScriptPrinter overrides that method — print.ts has 15 more for (… of x.modifiers) loops, and every other one is safe only because its slot is received through receiveListDefined. Guarding the receiver instead is what keeps all 16 safe, so the guard is reverted here and visitor.ts is back to treating all 10 of its modifiers slots alike.

Its test goes with it. It cast past the model's own readonly modifiers: J.Modifier[] to build a tree the type forbids, then asserted a consumer tolerated it — so it passed on main while the crash above still shipped. The replacement asserts the state cannot reach a consumer in the first place.

Tests

absent-list-slot.test.ts round-trips a parsed compilation unit whose modifiers slot is absent through RpcSendQueue/RpcReceiveQueue, then asserts the received tree holds [] and prints back to its source. Both halves are load-bearing under mutation: restoring the ! in receiveListDefined fails it, and so does putting modifiers back on the bare receiveList.

@github-project-automation github-project-automation Bot moved this to In Progress in OpenRewrite Sep 7, 2026
@knutwannheden
knutwannheden marked this pull request as draft September 7, 2026 12:59
@knutwannheden
knutwannheden force-pushed the js-printer-crashes-on-absent-propertyassignment.modifiers branch from 16cc277 to a144dbd Compare September 7, 2026 13:25
@knutwannheden knutwannheden changed the title JavaScript: an absent PropertyAssignment.modifiers prints as empty JavaScript: a non-optional list slot reads as empty when a peer sends none Sep 7, 2026
@knutwannheden
knutwannheden force-pushed the js-printer-crashes-on-absent-propertyassignment.modifiers branch from a144dbd to 4f4d1dc Compare September 8, 2026 00:54
… none

`JS.PropertyAssignment.modifiers` was added in #8720, so an LST serialized before that
deserializes with the field null. `JavaScriptSender` sends nothing for a null list, and the
TypeScript receiver was the one call site taking the bare `receiveList` for a slot the model
declares non-optional, so `modifiers` stayed undefined. `JavaScriptPrinter` then threw
`TypeError: propertyAssignment.modifiers is not iterable`, failing the print of every source
file containing an object literal, and `NormalizeWhitespaceVisitor` threw from `mapAsync`.

`receiveListDefined` now reads an absent list as empty rather than asserting non-null to the
type checker alone, and `modifiers` uses it, as the other 19 non-optional list slots do. That
supersedes the consumer-side guard added in #8799, reverted here along with its test: the
state it tolerated can no longer reach a consumer.
@knutwannheden
knutwannheden force-pushed the js-printer-crashes-on-absent-propertyassignment.modifiers branch from 4f4d1dc to 2793307 Compare September 8, 2026 01:16
@knutwannheden
knutwannheden marked this pull request as ready for review September 8, 2026 01:35
@knutwannheden
knutwannheden merged commit cdb10b3 into main Sep 8, 2026
1 check passed
@knutwannheden
knutwannheden deleted the js-printer-crashes-on-absent-propertyassignment.modifiers branch September 8, 2026 01:38
@github-project-automation github-project-automation Bot moved this from In Progress to Done in OpenRewrite Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

1 participant