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