1
2
3
4
5 package astutil_test
6
7
8
9
10
11
12 import (
13 "bytes"
14 "fmt"
15 "go/ast"
16 "go/parser"
17 "go/token"
18 "strings"
19 "testing"
20
21 "golang.org/x/tools/go/ast/astutil"
22 )
23
24
25
26 func pathToString(path []ast.Node) string {
27 var buf bytes.Buffer
28 fmt.Fprint(&buf, "[")
29 for i, n := range path {
30 if i > 0 {
31 fmt.Fprint(&buf, " ")
32 }
33 fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast."))
34 }
35 fmt.Fprint(&buf, "]")
36 return buf.String()
37 }
38
39
40
41
42 func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) {
43 f, err := parser.ParseFile(fset, "<input>", input, 0)
44 if err != nil {
45 t.Errorf("parse error: %s", err)
46 return
47 }
48
49 i := strings.Index(input, substr)
50 if i < 0 {
51 t.Errorf("%q is not a substring of input", substr)
52 f = nil
53 return
54 }
55
56 filePos := fset.File(f.Package)
57 return f, filePos.Pos(i), filePos.Pos(i + len(substr))
58 }
59
60
61 const input = `
62 // Hello.
63 package main
64 import "fmt"
65 func f() {}
66 func main() {
67 z := (x + y) // add them
68 f() // NB: ExprStmt and its CallExpr have same Pos/End
69 }
70
71 func g[A any, P interface{ctype1| ~ctype2}](a1 A, p1 P) {}
72
73 type PT[T constraint] struct{ t T }
74
75 var v GT[targ1]
76
77 var h = g[ targ2, targ3]
78 `
79
80 func TestPathEnclosingInterval_Exact(t *testing.T) {
81 type testCase struct {
82 substr string
83 node string
84 }
85
86 dup := func(s string) testCase { return testCase{s, s} }
87
88
89 tests := []testCase{
90 {"package",
91 input[11 : len(input)-1]},
92 {"\npack",
93 input[11 : len(input)-1]},
94 dup("main"),
95 {"import",
96 "import \"fmt\""},
97 dup("\"fmt\""),
98 {"\nfunc f() {}\n",
99 "func f() {}"},
100 {"x ",
101 "x"},
102 {" y",
103 "y"},
104 dup("z"),
105 {" + ",
106 "x + y"},
107 {" :=",
108 "z := (x + y)"},
109 dup("x + y"),
110 dup("(x + y)"),
111 {" (x + y) ",
112 "(x + y)"},
113 {" (x + y) // add",
114 "(x + y)"},
115 {"func",
116 "func f() {}"},
117 dup("func f() {}"),
118 {"\nfun",
119 "func f() {}"},
120 {" f",
121 "f"},
122 dup("[A any, P interface{ctype1| ~ctype2}]"),
123 {"[", "[A any, P interface{ctype1| ~ctype2}]"},
124 dup("A"),
125 {" any", "any"},
126 dup("ctype1"),
127 {"|", "ctype1| ~ctype2"},
128 dup("ctype2"),
129 {"~", "~ctype2"},
130 dup("~ctype2"),
131 {" ~ctype2", "~ctype2"},
132 {"]", "[A any, P interface{ctype1| ~ctype2}]"},
133 dup("a1"),
134 dup("a1 A"),
135 dup("(a1 A, p1 P)"),
136 dup("type PT[T constraint] struct{ t T }"),
137 dup("PT"),
138 dup("[T constraint]"),
139 dup("constraint"),
140 dup("targ1"),
141 {" targ2", "targ2"},
142 dup("g[ targ2, targ3]"),
143 }
144 for _, test := range tests {
145 f, start, end := findInterval(t, new(token.FileSet), input, test.substr)
146 if f == nil {
147 continue
148 }
149
150 path, exact := astutil.PathEnclosingInterval(f, start, end)
151 if !exact {
152 t.Errorf("PathEnclosingInterval(%q) not exact", test.substr)
153 continue
154 }
155
156 if len(path) == 0 {
157 if test.node != "" {
158 t.Errorf("PathEnclosingInterval(%q).path: got [], want %q",
159 test.substr, test.node)
160 }
161 continue
162 }
163
164 if got := input[path[0].Pos():path[0].End()]; got != test.node {
165 t.Errorf("PathEnclosingInterval(%q): got %q, want %q (path was %s)",
166 test.substr, got, test.node, pathToString(path))
167 continue
168 }
169 }
170 }
171
172 func TestPathEnclosingInterval_Paths(t *testing.T) {
173 type testCase struct {
174 substr string
175 path string
176 }
177
178
179
180 tests := []testCase{
181 {"// add",
182 "[BlockStmt FuncDecl File],false"},
183 {"(x + y",
184 "[ParenExpr AssignStmt BlockStmt FuncDecl File],false"},
185 {"x +",
186 "[BinaryExpr ParenExpr AssignStmt BlockStmt FuncDecl File],false"},
187 {"z := (x",
188 "[AssignStmt BlockStmt FuncDecl File],false"},
189 {"func f",
190 "[FuncDecl File],false"},
191 {"func f()",
192 "[FuncDecl File],false"},
193 {" f()",
194 "[FuncDecl File],false"},
195 {"() {}",
196 "[FuncDecl File],false"},
197 {"// Hello",
198 "[File],false"},
199 {" f",
200 "[Ident FuncDecl File],true"},
201 {"func ",
202 "[FuncDecl File],true"},
203 {"mai",
204 "[Ident File],true"},
205 {"f() // NB",
206 "[CallExpr ExprStmt BlockStmt FuncDecl File],true"},
207 {" any", "[Ident Field FieldList FuncDecl File],true"},
208 {"|", "[BinaryExpr Field FieldList InterfaceType Field FieldList FuncDecl File],true"},
209 {"ctype2",
210 "[Ident UnaryExpr BinaryExpr Field FieldList InterfaceType Field FieldList FuncDecl File],true"},
211 {"a1", "[Ident Field FieldList FuncDecl File],true"},
212 {"PT[T constraint]", "[TypeSpec GenDecl File],false"},
213 {"[T constraint]", "[FieldList TypeSpec GenDecl File],true"},
214 {"targ2", "[Ident IndexListExpr ValueSpec GenDecl File],true"},
215 }
216 for _, test := range tests {
217 f, start, end := findInterval(t, new(token.FileSet), input, test.substr)
218 if f == nil {
219 continue
220 }
221
222 path, exact := astutil.PathEnclosingInterval(f, start, end)
223 if got := fmt.Sprintf("%s,%v", pathToString(path), exact); got != test.path {
224 t.Errorf("PathEnclosingInterval(%q): got %q, want %q",
225 test.substr, got, test.path)
226 continue
227 }
228 }
229 }
230
View as plain text