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 }