github.com/v2fly/tools@v0.100.0/internal/lsp/source/identifier_test.go (about)

     1  // Copyright 2020 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 source
     6  
     7  import (
     8  	"bytes"
     9  	"go/ast"
    10  	"go/parser"
    11  	"go/token"
    12  	"go/types"
    13  	"testing"
    14  )
    15  
    16  func TestSearchForEnclosing(t *testing.T) {
    17  	tests := []struct {
    18  		desc string
    19  		// For convenience, consider the first occurrence of the identifier "X" in
    20  		// src.
    21  		src string
    22  		// By convention, "" means no type found.
    23  		wantTypeName string
    24  	}{
    25  		{
    26  			desc:         "self enclosing",
    27  			src:          `package a; type X struct {}`,
    28  			wantTypeName: "X",
    29  		},
    30  		{
    31  			// TODO(rFindley): is this correct, or do we want to resolve I2 here?
    32  			desc:         "embedded interface in interface",
    33  			src:          `package a; var y = i1.X; type i1 interface {I2}; type I2 interface{X()}`,
    34  			wantTypeName: "",
    35  		},
    36  		{
    37  			desc:         "embedded interface in struct",
    38  			src:          `package a; var y = t.X; type t struct {I}; type I interface{X()}`,
    39  			wantTypeName: "I",
    40  		},
    41  		{
    42  			desc:         "double embedding",
    43  			src:          `package a; var y = t1.X; type t1 struct {t2}; type t2 struct {I}; type I interface{X()}`,
    44  			wantTypeName: "I",
    45  		},
    46  		{
    47  			desc:         "struct field",
    48  			src:          `package a; type T struct { X int }`,
    49  			wantTypeName: "T",
    50  		},
    51  		{
    52  			desc:         "nested struct field",
    53  			src:          `package a; type T struct { E struct { X int } }`,
    54  			wantTypeName: "T",
    55  		},
    56  		{
    57  			desc:         "slice entry",
    58  			src:          `package a; type T []int; var S = T{X}; var X int = 2`,
    59  			wantTypeName: "T",
    60  		},
    61  		{
    62  			desc:         "struct pointer literal",
    63  			src:          `package a; type T struct {i int}; var L = &T{X}; const X = 2`,
    64  			wantTypeName: "T",
    65  		},
    66  	}
    67  
    68  	for _, test := range tests {
    69  		test := test
    70  		t.Run(test.desc, func(t *testing.T) {
    71  			fset := token.NewFileSet()
    72  			file, err := parser.ParseFile(fset, "a.go", test.src, parser.AllErrors)
    73  			if err != nil {
    74  				t.Fatal(err)
    75  			}
    76  			column := 1 + bytes.IndexRune([]byte(test.src), 'X')
    77  			pos := posAt(1, column, fset, "a.go")
    78  			path := pathEnclosingObjNode(file, pos)
    79  			if path == nil {
    80  				t.Fatalf("no ident found at (1, %d)", column)
    81  			}
    82  			info := newInfo()
    83  			if _, err = (*types.Config)(nil).Check("p", fset, []*ast.File{file}, info); err != nil {
    84  				t.Fatal(err)
    85  			}
    86  			typ := searchForEnclosing(info, path)
    87  			if typ == nil {
    88  				if test.wantTypeName != "" {
    89  					t.Errorf("searchForEnclosing(...) = <nil>, want %q", test.wantTypeName)
    90  				}
    91  				return
    92  			}
    93  			if got := typ.(*types.Named).Obj().Name(); got != test.wantTypeName {
    94  				t.Errorf("searchForEnclosing(...) = %q, want %q", got, test.wantTypeName)
    95  			}
    96  		})
    97  	}
    98  }
    99  
   100  // posAt returns the token.Pos corresponding to the 1-based (line, column)
   101  // coordinates in the file fname of fset.
   102  func posAt(line, column int, fset *token.FileSet, fname string) token.Pos {
   103  	var tok *token.File
   104  	fset.Iterate(func(f *token.File) bool {
   105  		if f.Name() == fname {
   106  			tok = f
   107  			return false
   108  		}
   109  		return true
   110  	})
   111  	if tok == nil {
   112  		return token.NoPos
   113  	}
   114  	start := tok.LineStart(line)
   115  	return start + token.Pos(column-1)
   116  }
   117  
   118  // newInfo returns a types.Info with all maps populated.
   119  func newInfo() *types.Info {
   120  	return &types.Info{
   121  		Types:      make(map[ast.Expr]types.TypeAndValue),
   122  		Defs:       make(map[*ast.Ident]types.Object),
   123  		Uses:       make(map[*ast.Ident]types.Object),
   124  		Implicits:  make(map[ast.Node]types.Object),
   125  		Selections: make(map[*ast.SelectorExpr]*types.Selection),
   126  		Scopes:     make(map[ast.Node]*types.Scope),
   127  	}
   128  }