| 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