1// Copyright 2019 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 5package cmd 6 7import ( 8 "fmt" 9 "go/token" 10 "path/filepath" 11 "regexp" 12 "strconv" 13 14 "golang.org/x/tools/internal/lsp/source" 15) 16 17type Location struct { 18 Filename string `json:"file"` 19 Start Position `json:"start"` 20 End Position `json:"end"` 21} 22 23type Position struct { 24 Line int `json:"line"` 25 Column int `json:"column"` 26 Offset int `json:"offset"` 27} 28 29func newLocation(fset *token.FileSet, r source.Range) Location { 30 start := fset.Position(r.Start) 31 end := fset.Position(r.End) 32 // it should not be possible the following line to fail 33 filename, _ := source.ToURI(start.Filename).Filename() 34 return Location{ 35 Filename: filename, 36 Start: Position{ 37 Line: start.Line, 38 Column: start.Column, 39 Offset: fset.File(r.Start).Offset(r.Start), 40 }, 41 End: Position{ 42 Line: end.Line, 43 Column: end.Column, 44 Offset: fset.File(r.End).Offset(r.End), 45 }, 46 } 47} 48 49var posRe = regexp.MustCompile( 50 `(?P<file>.*):(?P<start>(?P<sline>\d+):(?P<scol>\d)+|#(?P<soff>\d+))(?P<end>:(?P<eline>\d+):(?P<ecol>\d+)|#(?P<eoff>\d+))?$`) 51 52const ( 53 posReAll = iota 54 posReFile 55 posReStart 56 posReSLine 57 posReSCol 58 posReSOff 59 posReEnd 60 posReELine 61 posReECol 62 posReEOff 63) 64 65func init() { 66 names := posRe.SubexpNames() 67 // verify all our submatch offsets are correct 68 for name, index := range map[string]int{ 69 "file": posReFile, 70 "start": posReStart, 71 "sline": posReSLine, 72 "scol": posReSCol, 73 "soff": posReSOff, 74 "end": posReEnd, 75 "eline": posReELine, 76 "ecol": posReECol, 77 "eoff": posReEOff, 78 } { 79 if names[index] == name { 80 continue 81 } 82 // try to find it 83 for test := range names { 84 if names[test] == name { 85 panic(fmt.Errorf("Index for %s incorrect, wanted %v have %v", name, index, test)) 86 } 87 } 88 panic(fmt.Errorf("Subexp %s does not exist", name)) 89 } 90} 91 92// parseLocation parses a string of the form "file:pos" or 93// file:start,end" where pos, start, end match either a byte offset in the 94// form #%d or a line and column in the form %d,%d. 95func parseLocation(value string) (Location, error) { 96 var loc Location 97 m := posRe.FindStringSubmatch(value) 98 if m == nil { 99 return loc, fmt.Errorf("bad location syntax %q", value) 100 } 101 loc.Filename = m[posReFile] 102 if !filepath.IsAbs(loc.Filename) { 103 loc.Filename, _ = filepath.Abs(loc.Filename) // ignore error 104 } 105 if m[posReSLine] != "" { 106 v, err := strconv.ParseInt(m[posReSLine], 10, 32) 107 if err != nil { 108 return loc, err 109 } 110 loc.Start.Line = int(v) 111 v, err = strconv.ParseInt(m[posReSCol], 10, 32) 112 if err != nil { 113 return loc, err 114 } 115 loc.Start.Column = int(v) 116 } else { 117 v, err := strconv.ParseInt(m[posReSOff], 10, 32) 118 if err != nil { 119 return loc, err 120 } 121 loc.Start.Offset = int(v) 122 } 123 if m[posReEnd] == "" { 124 loc.End = loc.Start 125 } else { 126 if m[posReELine] != "" { 127 v, err := strconv.ParseInt(m[posReELine], 10, 32) 128 if err != nil { 129 return loc, err 130 } 131 loc.End.Line = int(v) 132 v, err = strconv.ParseInt(m[posReECol], 10, 32) 133 if err != nil { 134 return loc, err 135 } 136 loc.End.Column = int(v) 137 } else { 138 v, err := strconv.ParseInt(m[posReEOff], 10, 32) 139 if err != nil { 140 return loc, err 141 } 142 loc.End.Offset = int(v) 143 } 144 } 145 return loc, nil 146} 147 148func (l Location) Format(f fmt.State, c rune) { 149 // we should always have a filename 150 fmt.Fprint(f, l.Filename) 151 // are we in line:column format or #offset format 152 fmt.Fprintf(f, ":%v", l.Start) 153 if l.End != l.Start { 154 fmt.Fprintf(f, ",%v", l.End) 155 } 156} 157 158func (p Position) Format(f fmt.State, c rune) { 159 // are we in line:column format or #offset format 160 if p.Line > 0 { 161 fmt.Fprintf(f, "%d:%d", p.Line, p.Column) 162 return 163 } 164 fmt.Fprintf(f, "#%d", p.Offset) 165} 166