github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/oracle/pos.go (about)

     1  package oracle
     2  
     3  // This file defines utilities for working with file positions.
     4  
     5  import (
     6  	"fmt"
     7  	"go/parser"
     8  	"go/token"
     9  	"os"
    10  	"path/filepath"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"golang.org/x/tools/go/ast/astutil"
    15  )
    16  
    17  // parseOctothorpDecimal returns the numeric value if s matches "#%d",
    18  // otherwise -1.
    19  func parseOctothorpDecimal(s string) int {
    20  	if s != "" && s[0] == '#' {
    21  		if s, err := strconv.ParseInt(s[1:], 10, 32); err == nil {
    22  			return int(s)
    23  		}
    24  	}
    25  	return -1
    26  }
    27  
    28  // parsePosFlag parses a string of the form "file:pos" or
    29  // file:start,end" where pos, start, end match #%d and represent byte
    30  // offsets, and returns its components.
    31  //
    32  // (Numbers without a '#' prefix are reserved for future use,
    33  // e.g. to indicate line/column positions.)
    34  //
    35  func parsePosFlag(posFlag string) (filename string, startOffset, endOffset int, err error) {
    36  	if posFlag == "" {
    37  		err = fmt.Errorf("no source position specified (-pos flag)")
    38  		return
    39  	}
    40  
    41  	colon := strings.LastIndex(posFlag, ":")
    42  	if colon < 0 {
    43  		err = fmt.Errorf("invalid source position -pos=%q", posFlag)
    44  		return
    45  	}
    46  	filename, offset := posFlag[:colon], posFlag[colon+1:]
    47  	startOffset = -1
    48  	endOffset = -1
    49  	if hyphen := strings.Index(offset, ","); hyphen < 0 {
    50  		// e.g. "foo.go:#123"
    51  		startOffset = parseOctothorpDecimal(offset)
    52  		endOffset = startOffset
    53  	} else {
    54  		// e.g. "foo.go:#123,#456"
    55  		startOffset = parseOctothorpDecimal(offset[:hyphen])
    56  		endOffset = parseOctothorpDecimal(offset[hyphen+1:])
    57  	}
    58  	if startOffset < 0 || endOffset < 0 {
    59  		err = fmt.Errorf("invalid -pos offset %q", offset)
    60  		return
    61  	}
    62  	return
    63  }
    64  
    65  // findQueryPos searches fset for filename and translates the
    66  // specified file-relative byte offsets into token.Pos form.  It
    67  // returns an error if the file was not found or the offsets were out
    68  // of bounds.
    69  //
    70  func findQueryPos(fset *token.FileSet, filename string, startOffset, endOffset int) (start, end token.Pos, err error) {
    71  	var file *token.File
    72  	fset.Iterate(func(f *token.File) bool {
    73  		if sameFile(filename, f.Name()) {
    74  			// (f.Name() is absolute)
    75  			file = f
    76  			return false // done
    77  		}
    78  		return true // continue
    79  	})
    80  	if file == nil {
    81  		err = fmt.Errorf("couldn't find file containing position")
    82  		return
    83  	}
    84  
    85  	// Range check [start..end], inclusive of both end-points.
    86  
    87  	if 0 <= startOffset && startOffset <= file.Size() {
    88  		start = file.Pos(int(startOffset))
    89  	} else {
    90  		err = fmt.Errorf("start position is beyond end of file")
    91  		return
    92  	}
    93  
    94  	if 0 <= endOffset && endOffset <= file.Size() {
    95  		end = file.Pos(int(endOffset))
    96  	} else {
    97  		err = fmt.Errorf("end position is beyond end of file")
    98  		return
    99  	}
   100  
   101  	return
   102  }
   103  
   104  // sameFile returns true if x and y have the same basename and denote
   105  // the same file.
   106  //
   107  func sameFile(x, y string) bool {
   108  	if filepath.Base(x) == filepath.Base(y) { // (optimisation)
   109  		if xi, err := os.Stat(x); err == nil {
   110  			if yi, err := os.Stat(y); err == nil {
   111  				return os.SameFile(xi, yi)
   112  			}
   113  		}
   114  	}
   115  	return false
   116  }
   117  
   118  // fastQueryPos parses the -pos flag and returns a QueryPos.
   119  // It parses only a single file, and does not run the type checker.
   120  func fastQueryPos(posFlag string) (*queryPos, error) {
   121  	filename, startOffset, endOffset, err := parsePosFlag(posFlag)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	fset := token.NewFileSet()
   127  	f, err := parser.ParseFile(fset, filename, nil, 0)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	start, end, err := findQueryPos(fset, filename, startOffset, endOffset)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	path, exact := astutil.PathEnclosingInterval(f, start, end)
   138  	if path == nil {
   139  		return nil, fmt.Errorf("no syntax here")
   140  	}
   141  
   142  	return &queryPos{fset, start, end, path, exact, nil}, nil
   143  }