// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package astutil_test // This file defines tests of PathEnclosingInterval. // TODO(adonovan): exhaustive tests that run over the whole input // tree, not just handcrafted examples. import ( "bytes" "fmt" "go/ast" "go/parser" "go/token" "strings" "testing" "golang.org/x/tools/go/ast/astutil" ) // pathToString returns a string containing the concrete types of the // nodes in path. func pathToString(path []ast.Node) string { var buf bytes.Buffer fmt.Fprint(&buf, "[") for i, n := range path { if i > 0 { fmt.Fprint(&buf, " ") } fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast.")) } fmt.Fprint(&buf, "]") return buf.String() } // findInterval parses input and returns the [start, end) positions of // the first occurrence of substr in input. f==nil indicates failure; // an error has already been reported in that case. func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) { f, err := parser.ParseFile(fset, "", input, 0) if err != nil { t.Errorf("parse error: %s", err) return } i := strings.Index(input, substr) if i < 0 { t.Errorf("%q is not a substring of input", substr) f = nil return } filePos := fset.File(f.Package) return f, filePos.Pos(i), filePos.Pos(i + len(substr)) } // Common input for following tests. const input = ` // Hello. package main import "fmt" func f() {} func main() { z := (x + y) // add them f() // NB: ExprStmt and its CallExpr have same Pos/End } func g[A any, P interface{ctype1| ~ctype2}](a1 A, p1 P) {} type PT[T constraint] struct{ t T } var v GT[targ1] var h = g[ targ2, targ3] ` func TestPathEnclosingInterval_Exact(t *testing.T) { type testCase struct { substr string // first occurrence of this string indicates interval node string // complete text of expected containing node } dup := func(s string) testCase { return testCase{s, s} } // For the exact tests, we check that a substring is mapped to // the canonical string for the node it denotes. tests := []testCase{ {"package", input[11 : len(input)-1]}, {"\npack", input[11 : len(input)-1]}, dup("main"), {"import", "import \"fmt\""}, dup("\"fmt\""), {"\nfunc f() {}\n", "func f() {}"}, {"x ", "x"}, {" y", "y"}, dup("z"), {" + ", "x + y"}, {" :=", "z := (x + y)"}, dup("x + y"), dup("(x + y)"), {" (x + y) ", "(x + y)"}, {" (x + y) // add", "(x + y)"}, {"func", "func f() {}"}, dup("func f() {}"), {"\nfun", "func f() {}"}, {" f", "f"}, dup("[A any, P interface{ctype1| ~ctype2}]"), {"[", "[A any, P interface{ctype1| ~ctype2}]"}, dup("A"), {" any", "any"}, dup("ctype1"), {"|", "ctype1| ~ctype2"}, dup("ctype2"), {"~", "~ctype2"}, dup("~ctype2"), {" ~ctype2", "~ctype2"}, {"]", "[A any, P interface{ctype1| ~ctype2}]"}, dup("a1"), dup("a1 A"), dup("(a1 A, p1 P)"), dup("type PT[T constraint] struct{ t T }"), dup("PT"), dup("[T constraint]"), dup("constraint"), dup("targ1"), {" targ2", "targ2"}, dup("g[ targ2, targ3]"), } for _, test := range tests { f, start, end := findInterval(t, new(token.FileSet), input, test.substr) if f == nil { continue } path, exact := astutil.PathEnclosingInterval(f, start, end) if !exact { t.Errorf("PathEnclosingInterval(%q) not exact", test.substr) continue } if len(path) == 0 { if test.node != "" { t.Errorf("PathEnclosingInterval(%q).path: got [], want %q", test.substr, test.node) } continue } if got := input[path[0].Pos():path[0].End()]; got != test.node { t.Errorf("PathEnclosingInterval(%q): got %q, want %q (path was %s)", test.substr, got, test.node, pathToString(path)) continue } } } func TestPathEnclosingInterval_Paths(t *testing.T) { type testCase struct { substr string // first occurrence of this string indicates interval path string // the pathToString(),exact of the expected path } // For these tests, we check only the path of the enclosing // node, but not its complete text because it's often quite // large when !exact. tests := []testCase{ {"// add", "[BlockStmt FuncDecl File],false"}, {"(x + y", "[ParenExpr AssignStmt BlockStmt FuncDecl File],false"}, {"x +", "[BinaryExpr ParenExpr AssignStmt BlockStmt FuncDecl File],false"}, {"z := (x", "[AssignStmt BlockStmt FuncDecl File],false"}, {"func f", "[FuncDecl File],false"}, {"func f()", "[FuncDecl File],false"}, {" f()", "[FuncDecl File],false"}, {"() {}", "[FuncDecl File],false"}, {"// Hello", "[File],false"}, {" f", "[Ident FuncDecl File],true"}, {"func ", "[FuncDecl File],true"}, {"mai", "[Ident File],true"}, {"f() // NB", "[CallExpr ExprStmt BlockStmt FuncDecl File],true"}, {" any", "[Ident Field FieldList FuncDecl File],true"}, {"|", "[BinaryExpr Field FieldList InterfaceType Field FieldList FuncDecl File],true"}, {"ctype2", "[Ident UnaryExpr BinaryExpr Field FieldList InterfaceType Field FieldList FuncDecl File],true"}, {"a1", "[Ident Field FieldList FuncDecl File],true"}, {"PT[T constraint]", "[TypeSpec GenDecl File],false"}, {"[T constraint]", "[FieldList TypeSpec GenDecl File],true"}, {"targ2", "[Ident IndexListExpr ValueSpec GenDecl File],true"}, } for _, test := range tests { f, start, end := findInterval(t, new(token.FileSet), input, test.substr) if f == nil { continue } path, exact := astutil.PathEnclosingInterval(f, start, end) if got := fmt.Sprintf("%s,%v", pathToString(path), exact); got != test.path { t.Errorf("PathEnclosingInterval(%q): got %q, want %q", test.substr, got, test.path) continue } } }