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