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  }