1 | // Copyright 2013 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | package astutil_test |
6 | |
7 | // This file defines tests of PathEnclosingInterval. |
8 | |
9 | // TODO(adonovan): exhaustive tests that run over the whole input |
10 | // tree, not just handcrafted examples. |
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 | "golang.org/x/tools/internal/typeparams" |
23 | ) |
24 | |
25 | // pathToString returns a string containing the concrete types of the |
26 | // nodes in path. |
27 | func pathToString(path []ast.Node) string { |
28 | var buf bytes.Buffer |
29 | fmt.Fprint(&buf, "[") |
30 | for i, n := range path { |
31 | if i > 0 { |
32 | fmt.Fprint(&buf, " ") |
33 | } |
34 | fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast.")) |
35 | } |
36 | fmt.Fprint(&buf, "]") |
37 | return buf.String() |
38 | } |
39 | |
40 | // findInterval parses input and returns the [start, end) positions of |
41 | // the first occurrence of substr in input. f==nil indicates failure; |
42 | // an error has already been reported in that case. |
43 | func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) { |
44 | f, err := parser.ParseFile(fset, "<input>", input, 0) |
45 | if err != nil { |
46 | t.Errorf("parse error: %s", err) |
47 | return |
48 | } |
49 | |
50 | i := strings.Index(input, substr) |
51 | if i < 0 { |
52 | t.Errorf("%q is not a substring of input", substr) |
53 | f = nil |
54 | return |
55 | } |
56 | |
57 | filePos := fset.File(f.Package) |
58 | return f, filePos.Pos(i), filePos.Pos(i + len(substr)) |
59 | } |
60 | |
61 | // Common input for following tests. |
62 | var input = makeInput() |
63 | |
64 | func makeInput() string { |
65 | src := ` |
66 | // Hello. |
67 | package main |
68 | import "fmt" |
69 | func f() {} |
70 | func main() { |
71 | z := (x + y) // add them |
72 | f() // NB: ExprStmt and its CallExpr have same Pos/End |
73 | } |
74 | ` |
75 | |
76 | if typeparams.Enabled { |
77 | src += ` |
78 | func g[A any, P interface{ctype1| ~ctype2}](a1 A, p1 P) {} |
79 | |
80 | type PT[T constraint] struct{ t T } |
81 | |
82 | var v GT[targ1] |
83 | |
84 | var h = g[ targ2, targ3] |
85 | ` |
86 | } |
87 | return src |
88 | } |
89 | |
90 | func TestPathEnclosingInterval_Exact(t *testing.T) { |
91 | type testCase struct { |
92 | substr string // first occurrence of this string indicates interval |
93 | node string // complete text of expected containing node |
94 | } |
95 | |
96 | dup := func(s string) testCase { return testCase{s, s} } |
97 | // For the exact tests, we check that a substring is mapped to |
98 | // the canonical string for the node it denotes. |
99 | tests := []testCase{ |
100 | {"package", |
101 | input[11 : len(input)-1]}, |
102 | {"\npack", |
103 | input[11 : len(input)-1]}, |
104 | dup("main"), |
105 | {"import", |
106 | "import \"fmt\""}, |
107 | dup("\"fmt\""), |
108 | {"\nfunc f() {}\n", |
109 | "func f() {}"}, |
110 | {"x ", |
111 | "x"}, |
112 | {" y", |
113 | "y"}, |
114 | dup("z"), |
115 | {" + ", |
116 | "x + y"}, |
117 | {" :=", |
118 | "z := (x + y)"}, |
119 | dup("x + y"), |
120 | dup("(x + y)"), |
121 | {" (x + y) ", |
122 | "(x + y)"}, |
123 | {" (x + y) // add", |
124 | "(x + y)"}, |
125 | {"func", |
126 | "func f() {}"}, |
127 | dup("func f() {}"), |
128 | {"\nfun", |
129 | "func f() {}"}, |
130 | {" f", |
131 | "f"}, |
132 | } |
133 | if typeparams.Enabled { |
134 | tests = append(tests, []testCase{ |
135 | dup("[A any, P interface{ctype1| ~ctype2}]"), |
136 | {"[", "[A any, P interface{ctype1| ~ctype2}]"}, |
137 | dup("A"), |
138 | {" any", "any"}, |
139 | dup("ctype1"), |
140 | {"|", "ctype1| ~ctype2"}, |
141 | dup("ctype2"), |
142 | {"~", "~ctype2"}, |
143 | dup("~ctype2"), |
144 | {" ~ctype2", "~ctype2"}, |
145 | {"]", "[A any, P interface{ctype1| ~ctype2}]"}, |
146 | dup("a1"), |
147 | dup("a1 A"), |
148 | dup("(a1 A, p1 P)"), |
149 | dup("type PT[T constraint] struct{ t T }"), |
150 | dup("PT"), |
151 | dup("[T constraint]"), |
152 | dup("constraint"), |
153 | dup("targ1"), |
154 | {" targ2", "targ2"}, |
155 | dup("g[ targ2, targ3]"), |
156 | }...) |
157 | } |
158 | for _, test := range tests { |
159 | f, start, end := findInterval(t, new(token.FileSet), input, test.substr) |
160 | if f == nil { |
161 | continue |
162 | } |
163 | |
164 | path, exact := astutil.PathEnclosingInterval(f, start, end) |
165 | if !exact { |
166 | t.Errorf("PathEnclosingInterval(%q) not exact", test.substr) |
167 | continue |
168 | } |
169 | |
170 | if len(path) == 0 { |
171 | if test.node != "" { |
172 | t.Errorf("PathEnclosingInterval(%q).path: got [], want %q", |
173 | test.substr, test.node) |
174 | } |
175 | continue |
176 | } |
177 | |
178 | if got := input[path[0].Pos():path[0].End()]; got != test.node { |
179 | t.Errorf("PathEnclosingInterval(%q): got %q, want %q (path was %s)", |
180 | test.substr, got, test.node, pathToString(path)) |
181 | continue |
182 | } |
183 | } |
184 | } |
185 | |
186 | func TestPathEnclosingInterval_Paths(t *testing.T) { |
187 | type testCase struct { |
188 | substr string // first occurrence of this string indicates interval |
189 | path string // the pathToString(),exact of the expected path |
190 | } |
191 | // For these tests, we check only the path of the enclosing |
192 | // node, but not its complete text because it's often quite |
193 | // large when !exact. |
194 | tests := []testCase{ |
195 | {"// add", |
196 | "[BlockStmt FuncDecl File],false"}, |
197 | {"(x + y", |
198 | "[ParenExpr AssignStmt BlockStmt FuncDecl File],false"}, |
199 | {"x +", |
200 | "[BinaryExpr ParenExpr AssignStmt BlockStmt FuncDecl File],false"}, |
201 | {"z := (x", |
202 | "[AssignStmt BlockStmt FuncDecl File],false"}, |
203 | {"func f", |
204 | "[FuncDecl File],false"}, |
205 | {"func f()", |
206 | "[FuncDecl File],false"}, |
207 | {" f()", |
208 | "[FuncDecl File],false"}, |
209 | {"() {}", |
210 | "[FuncDecl File],false"}, |
211 | {"// Hello", |
212 | "[File],false"}, |
213 | {" f", |
214 | "[Ident FuncDecl File],true"}, |
215 | {"func ", |
216 | "[FuncDecl File],true"}, |
217 | {"mai", |
218 | "[Ident File],true"}, |
219 | {"f() // NB", |
220 | "[CallExpr ExprStmt BlockStmt FuncDecl File],true"}, |
221 | } |
222 | if typeparams.Enabled { |
223 | tests = append(tests, []testCase{ |
224 | {" any", "[Ident Field FieldList FuncDecl File],true"}, |
225 | {"|", "[BinaryExpr Field FieldList InterfaceType Field FieldList FuncDecl File],true"}, |
226 | {"ctype2", |
227 | "[Ident UnaryExpr BinaryExpr Field FieldList InterfaceType Field FieldList FuncDecl File],true"}, |
228 | {"a1", "[Ident Field FieldList FuncDecl File],true"}, |
229 | {"PT[T constraint]", "[TypeSpec GenDecl File],false"}, |
230 | {"[T constraint]", "[FieldList TypeSpec GenDecl File],true"}, |
231 | {"targ2", "[Ident IndexListExpr ValueSpec GenDecl File],true"}, |
232 | }...) |
233 | } |
234 | for _, test := range tests { |
235 | f, start, end := findInterval(t, new(token.FileSet), input, test.substr) |
236 | if f == nil { |
237 | continue |
238 | } |
239 | |
240 | path, exact := astutil.PathEnclosingInterval(f, start, end) |
241 | if got := fmt.Sprintf("%s,%v", pathToString(path), exact); got != test.path { |
242 | t.Errorf("PathEnclosingInterval(%q): got %q, want %q", |
243 | test.substr, got, test.path) |
244 | continue |
245 | } |
246 | } |
247 | } |
248 |
Members