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  }