golang.org/x/tools/gopls@v0.15.3/internal/util/safetoken/safetoken_test.go (about) 1 // Copyright 2021 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 safetoken_test 6 7 import ( 8 "fmt" 9 "go/parser" 10 "go/token" 11 "go/types" 12 "os" 13 "testing" 14 15 "golang.org/x/tools/go/packages" 16 "golang.org/x/tools/gopls/internal/util/safetoken" 17 "golang.org/x/tools/internal/testenv" 18 ) 19 20 func TestWorkaroundIssue57490(t *testing.T) { 21 // During error recovery the parser synthesizes various close 22 // tokens at EOF, causing the End position of incomplete 23 // syntax nodes, computed as Rbrace+len("}"), to be beyond EOF. 24 src := `package p; func f() { var x struct` 25 fset := token.NewFileSet() 26 file, _ := parser.ParseFile(fset, "a.go", src, 0) 27 tf := fset.File(file.Pos()) 28 29 // Add another file to the FileSet. 30 file2, _ := parser.ParseFile(fset, "b.go", "package q", 0) 31 32 // This is the ambiguity of #57490... 33 if file.End() != file2.Pos() { 34 t.Errorf("file.End() %d != %d file2.Pos()", file.End(), file2.Pos()) 35 } 36 // ...which causes these statements to panic. 37 if false { 38 tf.Offset(file.End()) // panic: invalid Pos value 36 (should be in [1, 35]) 39 tf.Position(file.End()) // panic: invalid Pos value 36 (should be in [1, 35]) 40 } 41 42 // The offset of the EOF position is the file size. 43 offset, err := safetoken.Offset(tf, file.End()-1) 44 if err != nil || offset != tf.Size() { 45 t.Errorf("Offset(EOF) = (%d, %v), want token.File.Size %d", offset, err, tf.Size()) 46 } 47 48 // The offset of the file.End() position, 1 byte beyond EOF, 49 // is also the size of the file. 50 offset, err = safetoken.Offset(tf, file.End()) 51 if err != nil || offset != tf.Size() { 52 t.Errorf("Offset(ast.File.End()) = (%d, %v), want token.File.Size %d", offset, err, tf.Size()) 53 } 54 55 if got, want := safetoken.Position(tf, file.End()).String(), "a.go:1:35"; got != want { 56 t.Errorf("Position(ast.File.End()) = %s, want %s", got, want) 57 } 58 59 if got, want := safetoken.EndPosition(fset, file.End()).String(), "a.go:1:35"; got != want { 60 t.Errorf("EndPosition(ast.File.End()) = %s, want %s", got, want) 61 } 62 63 // Note that calling StartPosition on an end may yield the wrong file: 64 if got, want := safetoken.StartPosition(fset, file.End()).String(), "b.go:1:1"; got != want { 65 t.Errorf("StartPosition(ast.File.End()) = %s, want %s", got, want) 66 } 67 } 68 69 // To reduce the risk of panic, or bugs for which this package 70 // provides a workaround, this test statically reports references to 71 // forbidden methods of token.File or FileSet throughout gopls and 72 // suggests alternatives. 73 func TestGoplsSourceDoesNotCallTokenFileMethods(t *testing.T) { 74 testenv.NeedsGoPackages(t) 75 testenv.NeedsLocalXTools(t) 76 77 cfg := &packages.Config{ 78 Mode: packages.NeedName | packages.NeedModule | packages.NeedCompiledGoFiles | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedImports | packages.NeedDeps, 79 } 80 cfg.Env = os.Environ() 81 cfg.Env = append(cfg.Env, 82 "GOPACKAGESDRIVER=off", 83 "GOWORK=off", // necessary for -mod=mod below 84 "GOFLAGS=-mod=mod", 85 ) 86 87 pkgs, err := packages.Load(cfg, "go/token", "golang.org/x/tools/gopls/...") 88 if err != nil { 89 t.Fatal(err) 90 } 91 var tokenPkg *packages.Package 92 for _, pkg := range pkgs { 93 if pkg.PkgPath == "go/token" { 94 tokenPkg = pkg 95 break 96 } 97 } 98 if tokenPkg == nil { 99 t.Fatal("missing package go/token") 100 } 101 102 File := tokenPkg.Types.Scope().Lookup("File") 103 FileSet := tokenPkg.Types.Scope().Lookup("FileSet") 104 105 alternative := make(map[types.Object]string) 106 setAlternative := func(recv types.Object, old, new string) { 107 oldMethod, _, _ := types.LookupFieldOrMethod(recv.Type(), true, recv.Pkg(), old) 108 alternative[oldMethod] = new 109 } 110 setAlternative(File, "Line", "safetoken.Line") 111 setAlternative(File, "Offset", "safetoken.Offset") 112 setAlternative(File, "Position", "safetoken.Position") 113 setAlternative(File, "PositionFor", "safetoken.Position") 114 setAlternative(FileSet, "Position", "safetoken.StartPosition or EndPosition") 115 setAlternative(FileSet, "PositionFor", "safetoken.StartPosition or EndPosition") 116 117 for _, pkg := range pkgs { 118 switch pkg.PkgPath { 119 case "go/token", "golang.org/x/tools/gopls/internal/util/safetoken": 120 continue // allow calls within these packages 121 } 122 123 for ident, obj := range pkg.TypesInfo.Uses { 124 if alt, ok := alternative[obj]; ok { 125 posn := safetoken.StartPosition(pkg.Fset, ident.Pos()) 126 fmt.Fprintf(os.Stderr, "%s: forbidden use of %v; use %s instead.\n", posn, obj, alt) 127 t.Fail() 128 } 129 } 130 } 131 }