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
16 changes: 0 additions & 16 deletions internal/engine/bottom-up.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,22 +183,6 @@ func candidate(
return best
}

func getKeyLabel(n *treesitter.ASTNode) string {
if n == nil || len(n.Children) == 0 {
return ""
}
k := n.Children[0]
if k.Label != "" {
return k.Label
}
for _, desc := range k.Descendants() {
if desc.Label != "" {
return desc.Label
}
}
return ""
}

// labelOverlap returns the number of shared leaf labels in t2's subtree.
func labelOverlap(t1Labels map[string]int, t2 *treesitter.ASTNode) int {
count := 0
Expand Down
72 changes: 72 additions & 0 deletions internal/engine/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func Match(t1, t2 *treesitter.ASTNode, srcA, srcB []byte) *MatchResult {
// Match AST nodes top-down, by declaration, and bottom-up using line partitioning.
TopDown(t1, t2, minHeight, mappings, part)
matchDeclarations(t1, t2, mappings)
matchPairValues(t1, t2, mappings)
BottomUp(t1, t2, mappings, minDice)
ContestContainers(t1, t2, mappings)

Expand Down Expand Up @@ -346,3 +347,74 @@ func getReceiverTypeName(n *treesitter.ASTNode) string {
}
return ""
}

func getParentPairKey(n *treesitter.ASTNode) string {
for curr := n.Parent; curr != nil; curr = curr.Parent {
if label := getKeyLabel(curr.Parent); label != "" {
return label
}
}
return ""
}

func matchPairValues(t1, t2 *treesitter.ASTNode, m *Mapping) {
if t1 == nil || t1.Language == "" {
return
}
r := treesitter.GetRules(t1.Language)
if r == nil || len(r.Pairs) == 0 {
return
}

for _, n1 := range t1.PostOrder() {
if !slices.Contains(r.Pairs, n1.Type) {
continue
}

var n2 *treesitter.ASTNode
if m.Has(n1) {
n2 = m.Src()[n1]
if n2 == nil || !slices.Contains(r.Pairs, n2.Type) {
continue
}
} else {
key1 := getKeyLabel(n1)
if key1 == "" {
continue
}
anc1 := NearestMatchedAncestor(n1, m, false)
pKey1 := getParentPairKey(n1)

for _, cand := range t2.PostOrder() {
if !slices.Contains(r.Pairs, cand.Type) || m.HasDst(cand) {
continue
}
if getKeyLabel(cand) != key1 {
continue
}
anc2 := NearestMatchedAncestor(cand, m, true)
if !areAncestorsMatched(anc1, anc2, m) {
continue
}

pKey2 := getParentPairKey(cand)
if pKey1 != pKey2 {
continue
}

n2 = cand
m.Add(n1, n2)
m.Add(n1.Children[0], n2.Children[0])
break
}
}

if n2 != nil && len(n1.Children) >= 2 && len(n2.Children) >= 2 {
val1 := n1.Children[len(n1.Children)-1]
val2 := n2.Children[len(n2.Children)-1]
if !m.Has(val1) && !m.HasDst(val2) && len(val1.Children) > 0 && len(val2.Children) > 0 && val1.Type == val2.Type {
m.Add(val1, val2)
}
}
}
}
51 changes: 51 additions & 0 deletions internal/engine/matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,54 @@ func TestMatchUnmatchedLeavesIgnoresKeywords(t *testing.T) {
t.Errorf("MatchContainerKeywords should map kw1 to kw2 under mapped parents, got %v", m.Src()[kw1])
}
}

func TestMatchPairValues(t *testing.T) {
k1 := testutil.Leaf("string", "\"priority\"")
val1 := testutil.Node("object", "", testutil.Leaf("string", "\"a\""))
p1 := testutil.Node("key_value_pair", "", k1, val1)
srcRoot := testutil.Node("root", "", p1)
srcRoot.Language = "go"

k2 := testutil.Leaf("string", "\"priority\"")
val2 := testutil.Node("object", "", testutil.Leaf("string", "\"b\""))
p2 := testutil.Node("key_value_pair", "", k2, val2)
dstRoot := testutil.Node("root", "", p2)

m := NewMapping()
m.Add(p1, p2)

matchPairValues(srcRoot, dstRoot, m)

if !m.Has(val1) || m.Src()[val1] != val2 {
t.Errorf("matchPairValues should map val1 to val2, got %v", m.Src()[val1])
}
}

func TestMatchPairKeyNameAffinity(t *testing.T) {
// Old pair: "priority": { ... }
kOld := testutil.Leaf("string", "\"priority\"")
valOld := testutil.Node("object", "")
pairOld := testutil.Node("pair", "", kOld, valOld)
srcObj := testutil.Node("object", "", pairOld)

// New pair 1: "priority": { ... }
kNew1 := testutil.Leaf("string", "\"priority\"")
valNew1 := testutil.Node("object", "")
pairNew1 := testutil.Node("pair", "", kNew1, valNew1)

// New pair 2: "oneOf": [ ... ]
kNew2 := testutil.Leaf("string", "\"oneOf\"")
valNew2 := testutil.Node("array", "")
pairNew2 := testutil.Node("pair", "", kNew2, valNew2)

dstObj := testutil.Node("object", "", pairNew1, pairNew2)

r := Match(srcObj, dstObj, nil, nil)
if r == nil || r.Mappings == nil {
t.Fatal("Match returned nil")
}

if r.Mappings.Src()[pairOld] != pairNew1 {
t.Errorf("pairOld ('priority') should match pairNew1 ('priority'), got %v", r.Mappings.Src()[pairOld])
}
}
16 changes: 16 additions & 0 deletions internal/engine/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ func descendantSet(n *treesitter.ASTNode) map[*treesitter.ASTNode]struct{} {
return s
}

func getKeyLabel(n *treesitter.ASTNode) string {
if n == nil || len(n.Children) == 0 {
return ""
}
k := n.Children[0]
if k.Label != "" {
return k.Label
}
for _, desc := range k.Descendants() {
if desc.Label != "" {
return desc.Label
}
}
return ""
}

func commonMappedDescendants(t1, t2 *treesitter.ASTNode, m map[*treesitter.ASTNode]*treesitter.ASTNode) (common, lenS1, lenS2 int) {
s1 := t1.Descendants()
s2 := descendantSet(t2)
Expand Down
4 changes: 3 additions & 1 deletion internal/treesitter/c/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,6 @@ declarations:
- parameter_declaration
- field_declaration


pairs:
- field_designator
- initializer_pair
4 changes: 3 additions & 1 deletion internal/treesitter/cpp/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,6 @@ declarations:
- parameter_declaration
- field_declaration


pairs:
- field_designator
- initializer_pair
4 changes: 3 additions & 1 deletion internal/treesitter/go/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,7 @@ declarations:
unordered:
- interface_type


pairs:
- key_value_pair
- keyed_element

3 changes: 2 additions & 1 deletion internal/treesitter/java/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,5 +190,6 @@ declarations:
unordered:
- annotation


pairs:
- element_value_pair

6 changes: 4 additions & 2 deletions internal/treesitter/javascript/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ unordered:
- object
- object_pattern



pairs:
- pair
- property_assignment
- pair_pattern

3 changes: 3 additions & 0 deletions internal/treesitter/json/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ keywords:
- "true"
- "false"
- "null"

pairs:
- pair
2 changes: 2 additions & 0 deletions internal/treesitter/lua/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,5 @@ declarations:
- function_definition
- variable_declaration

pairs:
- field
4 changes: 4 additions & 0 deletions internal/treesitter/php/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,7 @@ unordered:
- attribute_group
- attribute_list

pairs:
- pair
- array_element_initializer

4 changes: 3 additions & 1 deletion internal/treesitter/python/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,7 @@ unordered:
- dictionary
- set


pairs:
- pair
- keyword_argument

3 changes: 2 additions & 1 deletion internal/treesitter/ruby/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,6 @@ unordered:
- hash
- hash_pattern


pairs:
- pair

1 change: 1 addition & 0 deletions internal/treesitter/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Rules struct {
Scaffolding []string `yaml:"scaffolding"`
Keywords []string `yaml:"keywords"`
Declarations []string `yaml:"declarations"`
Pairs []string `yaml:"pairs"`
Unordered []string `yaml:"unordered"`
}

Expand Down
12 changes: 12 additions & 0 deletions internal/treesitter/rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,15 @@ func TestRulesUnordered(t *testing.T) {
t.Errorf("unexpected unordered entries: %v", r.Unordered)
}
}

func TestRulesPairs(t *testing.T) {
r := &Rules{
Pairs: []string{"pair", "key_value_pair"},
}
if len(r.Pairs) != 2 {
t.Fatalf("expected 2 pairs entries, got %d", len(r.Pairs))
}
if r.Pairs[0] != "pair" || r.Pairs[1] != "key_value_pair" {
t.Errorf("unexpected pairs entries: %v", r.Pairs)
}
}
4 changes: 3 additions & 1 deletion internal/treesitter/rust/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,5 +154,7 @@ declarations:
unordered:
- use_declaration


pairs:
- field_initializer
- struct_pattern_field

8 changes: 6 additions & 2 deletions internal/treesitter/tsx/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ unordered:
- jsx_self_closing_element
- object_pattern



pairs:
- pair
- property_assignment
- pair_pattern
- property_signature
- jsx_attribute

7 changes: 5 additions & 2 deletions internal/treesitter/typescript/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ unordered:
- object_type
- object_pattern



pairs:
- pair
- property_assignment
- pair_pattern
- property_signature

4 changes: 3 additions & 1 deletion internal/treesitter/zig/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,6 @@ declarations:
- test_declaration
- using_namespace_declaration


pairs:
- initializer_pair
- field_initializer

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading