github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/ast/astutil/enclosing_test.go (about) 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 }