Fix walk skipping children of nodes displaced by an insertion - #1790
Draft
TwitchBronBron wants to merge 1 commit into
Draft
Fix walk skipping children of nodes displaced by an insertion#1790TwitchBronBron wants to merge 1 commit into
TwitchBronBron wants to merge 1 commit into
Conversation
When a visitor inserted a statement above the node being walked, `walk` re-read `owner[key]`, saw a different node, and assumed the original had been replaced. It then descended into the newly-inserted node instead, and `walkArray` marked the original as processed — so the displaced node's children were never walked. `walk` observes only the before/after state of a single slot, which cannot distinguish replacement from displacement from deletion. Split those three cases apart explicitly via array membership and length. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
When a visitor inserts a statement above the node currently being walked, that node's children are silently never walked. Injecting a
printabove anifmeans theifbody never gets visited.walkre-readsowner[key]after the visitor runs and treats any change as "this node was replaced". But three different mutations all produce that same observation, and only one of them is a replacement:owner[key]changed?Insert-above was being misread as replacement, so
walkdescended into the newly-inserted node andwalkArraymarked the original processed — skipping it forever.What changed
walkSlot, using array membership (indexOf) plus length change to tell them apart. Publicwalk()keeps its exact signature.walkArraymarks a node processed before walking it, so one visit per node regardless of how the visitor mutates the array afterwards.walkArraynow checks the cancellation token in its loop; previously a cancelled walk kept spinning the array, only bailing inside eachwalkcall.Verified
npm test— 3088 passing, 0 failing. Reverting justvisitors.tswhile keeping the new tests fails 4 of them, so they do pin the bug rather than describe the implementation.Not fixed — needs a design decision
Two mutation shapes are still wrong, added as
it.skipwith the actual-vs-expected recorded:splice(key, 1, x, y)(delete one, insert several) —xis never visited. Length is unchanged-or-larger, so "replaced by x" and "deleted, then x and y inserted" are indistinguishable after the fact.These aren't fixable by better inference, because the right answer depends on what the plugin intends and that isn't in the AST. Two legitimate, mutually exclusive expectations exist today:
Suggested follow-up: a
WalkOptions.injectedNodes?: 'skip' | 'visit'flag plus amaxPassessafety valve, implemented by snapshotting array membership before the loop instead of inferring from index math. That approach also dissolves both gaps above and removes thei = -1O(n²) restart. Default should stay'visit'for now to avoid a breaking change, with a flip to'skip'considered for the next major.Worth noting
AstEditoralready records every mutation with exact indices — if the walker consumed that change log it would know rather than infer, but only when an editor is passed, and much of the codebase splices arrays raw.Drafting rather than opening for review since the follow-up design is the more important half of this.