Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions internal/postprocess/normalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,21 +122,12 @@ func computeMoveRetention(src, dst *treesitter.ASTNode, ms *engine.Mapping, r *r
return effectiveSurviving / float64(nMax)
}

// isStationaryMove reports whether a Move stayed in place across transparent
// wrappers (such as template_declaration or friend_declaration) or expression shifts.
// isStationaryMove reports whether a move stayed in place, accounting for transparent
// wrappers, expression shifts, and container provenance.
func isStationaryMove(node, dstNode *treesitter.ASTNode, ms *engine.Mapping, r *rules.Rules) bool {
if node == nil || dstNode == nil || node.Parent == nil || dstNode.Parent == nil {
return false
}
hasPos := (node.EndByte > 0 || node.StartRow > 0 || node.EndRow > 0) &&
(dstNode.EndByte > 0 || dstNode.StartRow > 0 || dstNode.EndRow > 0)
if hasPos && node.StartRow == dstNode.StartRow && node.EndRow == dstNode.EndRow &&
node.StartCol == dstNode.StartCol && node.EndCol == dstNode.EndCol {
if sameScopeDeclaration(node, dstNode, ms, r) {
return true
}
}

srcParent := node.Parent
dstParent := dstNode.Parent

Expand All @@ -149,6 +140,23 @@ func isStationaryMove(node, dstNode *treesitter.ASTNode, ms *engine.Mapping, r *
return true
}

hasPos := (node.EndByte > 0 || node.StartRow > 0 || node.EndRow > 0) &&
(dstNode.EndByte > 0 || dstNode.StartRow > 0 || dstNode.EndRow > 0)
if hasPos && node.StartRow == dstNode.StartRow && node.EndRow == dstNode.EndRow &&
node.StartCol == dstNode.StartCol && node.EndCol == dstNode.EndCol {
if sameScopeDeclaration(node, dstNode, ms, r) {
// Not stationary if moved across distinct containers, like when dstParent
// came from an old container that never held this node.
if mappedDstParent := ms.Dst()[dstParent]; mappedDstParent != nil && !mappedDstParent.Contains(node) {
return false
}
if mappedSrcParent := ms.Src()[srcParent]; mappedSrcParent != nil && !mappedSrcParent.Contains(dstNode) {
return false
}
return true
}
}

sameLine := node.StartRow == dstNode.StartRow

// Walk up past transparent wrappers to find the enclosing container.
Expand Down
47 changes: 47 additions & 0 deletions internal/postprocess/normalize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3252,3 +3252,50 @@ func isDeclarationHeader(n *treesitter.ASTNode, r *rules.Rules) bool {
t.Errorf("expected binary_expression move to be demoted after descendants were evicted, but found an active Move")
}
}

func TestNormalizeStationaryMove_CrossContainerCoordinateCollisionPreservesMove(t *testing.T) {
// Don't drop a move as stationary if coordinates match only because an item moved
// into an adjacent container that landed on the item's old row and column.
src := []byte(`package rules

var rules = &Rules{
TagElements: []string{
"jsx_element",
},
Tags: []string{
"jsx_opening_element",
"jsx_closing_element",
},
}
`)
dst := []byte(`package rules

var rules = &Rules{
Tags: []string{
"jsx_element",
"jsx_opening_element",
"jsx_closing_element",
},
}
`)

srcAST, err := treesitter.Parse(src, "rules.go")
if err != nil {
t.Fatalf("failed to parse src: %v", err)
}
dstAST, err := treesitter.Parse(dst, "rules.go")
if err != nil {
t.Fatalf("failed to parse dst: %v", err)
}

matchResult := engine.Match(srcAST, dstAST, src, dst, nil)
script := actions.GenerateEditScript(srcAST, dstAST, matchResult.Mappings)
normalized := Run(script, matchResult.Mappings, srcAST, dstAST)

hasMove := slices.ContainsFunc(normalized.Actions(), func(a actions.Action) bool {
return a.Type == actions.Move && a.Node != nil && (a.Node.Label == `"jsx_element"` || (len(a.Node.Children) > 0 && a.Node.Children[0].Label == `"jsx_element"`))
})
if !hasMove {
t.Errorf("expected Move action on \"jsx_element\" to be preserved across containers despite coordinate collision, but none found")
}
}
Loading