-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpython_parser_test.go
More file actions
126 lines (122 loc) · 4.81 KB
/
Copy pathpython_parser_test.go
File metadata and controls
126 lines (122 loc) · 4.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package codegraph
import (
"context"
"reflect"
"strings"
"testing"
)
// +link=https://github.com/odvcencio/gotreesitter/issues/1274
// +case=`Python splat suffixes remain inside the unpacked expression, while calls and references retain their owner`
func TestPythonSplatSuffixFactsAndGraph(t *testing.T) {
object := Expression{Kind: "identifier", Text: "items"}
index := Expression{Kind: "integer", Text: "0"}
attribute := Expression{Kind: "attribute", Text: "values", Children: []Expression{object}}
subscript := Expression{Kind: "subscript", Children: []Expression{object, index}}
for _, tc := range []struct {
name, expression string
want Expression
}{
{"attribute", "items.values", attribute},
{"subscript", "items[0]", subscript},
{"attribute_subscript", "items.values[0]", Expression{Kind: "subscript", Children: []Expression{attribute, index}}},
{"subscript_attribute", "items[0].values", Expression{Kind: "attribute", Text: "values", Children: []Expression{subscript}}},
} {
t.Run(tc.name, func(t *testing.T) {
source := "def sink(*args):\n pass\ndef target():\n pass\ndef entry(items):\n sink(*" + tc.expression + ", target)\n"
g, err := New("splat", Options{})
if err != nil {
t.Fatal(err)
}
doc := Document{Path: "app.py", Content: []byte(source)}
facts, err := g.Extract(context.Background(), doc)
if err != nil {
t.Fatal(err)
}
if len(facts.Statements) != 3 || len(facts.Statements[2].Body) != 1 {
t.Fatalf("statements = %+v", facts.Statements)
}
call := facts.Statements[2].Body[0].Value
want := Expression{Kind: "call", Children: []Expression{
{Kind: "identifier", Text: "sink"},
{Kind: "list_splat", Children: []Expression{tc.want}},
{Kind: "identifier", Text: "target"},
}}
if !reflect.DeepEqual(call, want) {
t.Fatalf("call expression = %#v, want %#v", call, want)
}
if err := g.AddDocuments(context.Background(), doc); err != nil {
t.Fatal(err)
}
if _, err := g.Wait(context.Background()); err != nil {
t.Fatal(err)
}
assertPythonParserGraph(t, g, source, "sink", "sink(*"+tc.expression+", target)")
})
}
}
// +link=https://github.com/odvcencio/gotreesitter/issues/1275
// +case=`Escaped multiline strings preserve surrounding declarations and edges without treating string contents as references`
func TestPythonEscapedStringGraph(t *testing.T) {
for _, tc := range []struct{ name, literal string }{
{"triple_two_slashes", "\"\"\"target()" + `\\` + "\n\"\"\""},
{"triple_four_slashes", "\"\"\"target()" + `\\\\` + "\n\"\"\""},
{"single_continuation", "\"target()" + `\` + "\ntext\""},
{"raw_triple", "r\"\"\"target()" + `\\` + "\n\"\"\""},
{"bytes_triple", "b\"\"\"target()" + `\\` + "\n\"\"\""},
} {
t.Run(tc.name, func(t *testing.T) {
source := "def sink():\n pass\ndef target():\n pass\ndef entry():\n text = " + tc.literal + "\n sink()\n return target\n"
g, report, err := Build(context.Background(), "escapes", []Document{{Path: "app.py", Content: []byte(source)}}, Options{})
if err != nil || len(report.Diagnostics) != 0 {
t.Fatalf("build: %v, diagnostics: %+v", err, report.Diagnostics)
}
assertPythonParserGraph(t, g, source, "sink", "sink()")
})
}
}
// Check consumer-visible nodes, edge ownership, confidence and source spans.
// In particular, mentioning target in a string must not create an extra edge.
func assertPythonParserGraph(t *testing.T, g *Graph, source, callee, callText string) {
t.Helper()
for _, name := range []string{"entry", "target", callee} {
nodes := g.Find("app.py", Function, name)
if len(nodes) != 1 {
t.Fatalf("%s nodes = %+v", name, nodes)
}
n := nodes[0]
if !strings.HasPrefix(source[n.Location.StartByte:n.Location.EndByte], "def "+name+"(") {
t.Fatalf("wrong declaration span: %+v", n)
}
}
entry := g.Find("app.py", Function, "entry")[0]
sink := g.Find("app.py", Function, callee)[0]
target := g.Find("app.py", Function, "target")[0]
calls := g.RelationsFrom(entry.ID, Calls)
if len(calls) != 1 || calls[0].Target != sink.ID || calls[0].Confidence != Exact {
t.Fatalf("entry calls = %+v", calls)
}
if got := source[calls[0].Location.StartByte:calls[0].Location.EndByte]; got != callText {
t.Fatalf("call span = %q, want %q", got, callText)
}
refs := g.RelationsFrom(entry.ID, References)
if len(refs) != 2 {
t.Fatalf("entry references = %+v", refs)
}
counts := map[string]int{}
for _, ref := range refs {
counts[ref.Target]++
if ref.Confidence != Exact {
t.Fatalf("reference confidence = %+v", ref)
}
name := callee
if ref.Target == target.ID {
name = "target"
}
if got := source[ref.Location.StartByte:ref.Location.EndByte]; got != name {
t.Fatalf("reference span = %q, want %q", got, name)
}
}
if counts[sink.ID] != 1 || counts[target.ID] != 1 {
t.Fatalf("reference targets = %v", counts)
}
}