golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/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 ) 23 24 // pathToString returns a string containing the concrete types of the 25 // nodes in path. 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 // findInterval parses input and returns the [start, end) positions of 40 // the first occurrence of substr in input. f==nil indicates failure; 41 // an error has already been reported in that case. 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 // Common input for following tests. 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 // first occurrence of this string indicates interval 83 node string // complete text of expected containing node 84 } 85 86 dup := func(s string) testCase { return testCase{s, s} } 87 // For the exact tests, we check that a substring is mapped to 88 // the canonical string for the node it denotes. 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 // first occurrence of this string indicates interval 175 path string // the pathToString(),exact of the expected path 176 } 177 // For these tests, we check only the path of the enclosing 178 // node, but not its complete text because it's often quite 179 // large when !exact. 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 }