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 }