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  }