golang.org/x/tools/gopls@v0.15.3/internal/util/safetoken/safetoken.go (about) 1 // Copyright 2022 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 provides wrappers around methods in go/token, 6 // that return errors rather than panicking. 7 // 8 // It also provides a central place for workarounds in the underlying 9 // packages. The use of this package's functions instead of methods of 10 // token.File (such as Offset, Position, and PositionFor) is mandatory 11 // throughout the gopls codebase and enforced by a static check. 12 package safetoken 13 14 import ( 15 "fmt" 16 "go/token" 17 ) 18 19 // Offset returns f.Offset(pos), but first checks that the file 20 // contains the pos. 21 // 22 // The definition of "contains" here differs from that of token.File 23 // in order to work around a bug in the parser (issue #57490): during 24 // error recovery, the parser may create syntax nodes whose computed 25 // End position is 1 byte beyond EOF, which would cause 26 // token.File.Offset to panic. The workaround is that this function 27 // accepts a Pos that is exactly 1 byte beyond EOF and maps it to the 28 // EOF offset. 29 func Offset(f *token.File, pos token.Pos) (int, error) { 30 if !inRange(f, pos) { 31 // Accept a Pos that is 1 byte beyond EOF, 32 // and map it to the EOF offset. 33 // (Workaround for #57490.) 34 if int(pos) == f.Base()+f.Size()+1 { 35 return f.Size(), nil 36 } 37 38 return -1, fmt.Errorf("pos %d is not in range [%d:%d] of file %s", 39 pos, f.Base(), f.Base()+f.Size(), f.Name()) 40 } 41 return int(pos) - f.Base(), nil 42 } 43 44 // Offsets returns Offset(start) and Offset(end). 45 func Offsets(f *token.File, start, end token.Pos) (int, int, error) { 46 startOffset, err := Offset(f, start) 47 if err != nil { 48 return 0, 0, fmt.Errorf("start: %v", err) 49 } 50 endOffset, err := Offset(f, end) 51 if err != nil { 52 return 0, 0, fmt.Errorf("end: %v", err) 53 } 54 return startOffset, endOffset, nil 55 } 56 57 // Pos returns f.Pos(offset), but first checks that the offset is 58 // non-negative and not larger than the size of the file. 59 func Pos(f *token.File, offset int) (token.Pos, error) { 60 if !(0 <= offset && offset <= f.Size()) { 61 return token.NoPos, fmt.Errorf("offset %d is not in range for file %s of size %d", offset, f.Name(), f.Size()) 62 } 63 return token.Pos(f.Base() + offset), nil 64 } 65 66 // inRange reports whether file f contains position pos, 67 // according to the invariants of token.File. 68 // 69 // This function is not public because of the ambiguity it would 70 // create w.r.t. the definition of "contains". Use Offset instead. 71 func inRange(f *token.File, pos token.Pos) bool { 72 return token.Pos(f.Base()) <= pos && pos <= token.Pos(f.Base()+f.Size()) 73 } 74 75 // Position returns the Position for the pos value in the given file. 76 // 77 // p must be NoPos, a valid Pos in the range of f, or exactly 1 byte 78 // beyond the end of f. (See [Offset] for explanation.) 79 // Any other value causes a panic. 80 // 81 // Line directives (//line comments) are ignored. 82 func Position(f *token.File, pos token.Pos) token.Position { 83 // Work around issue #57490. 84 if int(pos) == f.Base()+f.Size()+1 { 85 pos-- 86 } 87 88 // TODO(adonovan): centralize the workaround for 89 // golang/go#41029 (newline at EOF) here too. 90 91 return f.PositionFor(pos, false) 92 } 93 94 // Line returns the line number for the given offset in the given file. 95 func Line(f *token.File, pos token.Pos) int { 96 return Position(f, pos).Line 97 } 98 99 // StartPosition converts a start Pos in the FileSet into a Position. 100 // 101 // Call this function only if start represents the start of a token or 102 // parse tree, such as the result of Node.Pos(). If start is the end of 103 // an interval, such as Node.End(), call EndPosition instead, as it 104 // may need the correction described at [Position]. 105 func StartPosition(fset *token.FileSet, start token.Pos) (_ token.Position) { 106 if f := fset.File(start); f != nil { 107 return Position(f, start) 108 } 109 return 110 } 111 112 // EndPosition converts an end Pos in the FileSet into a Position. 113 // 114 // Call this function only if pos represents the end of 115 // a non-empty interval, such as the result of Node.End(). 116 func EndPosition(fset *token.FileSet, end token.Pos) (_ token.Position) { 117 if f := fset.File(end); f != nil && int(end) > f.Base() { 118 return Position(f, end) 119 } 120 121 // Work around issue #57490. 122 if f := fset.File(end - 1); f != nil { 123 return Position(f, end) 124 } 125 126 return 127 }