1// Copyright 2019 The Go Authors. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package span contains support for representing with positions and ranges in 16// text files. 17package span 18 19import ( 20 "encoding/json" 21 "fmt" 22 "path" 23) 24 25// Span represents a source code range in standardized form. 26type Span struct { 27 v span 28} 29 30// Point represents a single point within a file. 31// In general this should only be used as part of a Span, as on its own it 32// does not carry enough information. 33type Point struct { 34 v point 35} 36 37type span struct { 38 URI URI `json:"uri"` 39 Start point `json:"start"` 40 End point `json:"end"` 41} 42 43type point struct { 44 Line int `json:"line"` 45 Column int `json:"column"` 46 Offset int `json:"offset"` 47} 48 49// Invalid is a span that reports false from IsValid 50var Invalid = Span{v: span{Start: invalidPoint.v, End: invalidPoint.v}} 51 52var invalidPoint = Point{v: point{Line: 0, Column: 0, Offset: -1}} 53 54// Converter is the interface to an object that can convert between line:column 55// and offset forms for a single file. 56type Converter interface { 57 //ToPosition converts from an offset to a line:column pair. 58 ToPosition(offset int) (int, int, error) 59 //ToOffset converts from a line:column pair to an offset. 60 ToOffset(line, col int) (int, error) 61} 62 63func New(uri URI, start Point, end Point) Span { 64 s := Span{v: span{URI: uri, Start: start.v, End: end.v}} 65 s.v.clean() 66 return s 67} 68 69func NewPoint(line, col, offset int) Point { 70 p := Point{v: point{Line: line, Column: col, Offset: offset}} 71 p.v.clean() 72 return p 73} 74 75func Compare(a, b Span) int { 76 if r := CompareURI(a.URI(), b.URI()); r != 0 { 77 return r 78 } 79 if r := comparePoint(a.v.Start, b.v.Start); r != 0 { 80 return r 81 } 82 return comparePoint(a.v.End, b.v.End) 83} 84 85func ComparePoint(a, b Point) int { 86 return comparePoint(a.v, b.v) 87} 88 89func comparePoint(a, b point) int { 90 if !a.hasPosition() { 91 if a.Offset < b.Offset { 92 return -1 93 } 94 if a.Offset > b.Offset { 95 return 1 96 } 97 return 0 98 } 99 if a.Line < b.Line { 100 return -1 101 } 102 if a.Line > b.Line { 103 return 1 104 } 105 if a.Column < b.Column { 106 return -1 107 } 108 if a.Column > b.Column { 109 return 1 110 } 111 return 0 112} 113 114func (s Span) HasPosition() bool { return s.v.Start.hasPosition() } 115func (s Span) HasOffset() bool { return s.v.Start.hasOffset() } 116func (s Span) IsValid() bool { return s.v.Start.isValid() } 117func (s Span) IsPoint() bool { return s.v.Start == s.v.End } 118func (s Span) URI() URI { return s.v.URI } 119func (s Span) Start() Point { return Point{s.v.Start} } 120func (s Span) End() Point { return Point{s.v.End} } 121func (s *Span) MarshalJSON() ([]byte, error) { return json.Marshal(&s.v) } 122func (s *Span) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &s.v) } 123 124func (p Point) HasPosition() bool { return p.v.hasPosition() } 125func (p Point) HasOffset() bool { return p.v.hasOffset() } 126func (p Point) IsValid() bool { return p.v.isValid() } 127func (p *Point) MarshalJSON() ([]byte, error) { return json.Marshal(&p.v) } 128func (p *Point) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &p.v) } 129func (p Point) Line() int { 130 if !p.v.hasPosition() { 131 panic(fmt.Errorf("position not set in %v", p.v)) 132 } 133 return p.v.Line 134} 135func (p Point) Column() int { 136 if !p.v.hasPosition() { 137 panic(fmt.Errorf("position not set in %v", p.v)) 138 } 139 return p.v.Column 140} 141func (p Point) Offset() int { 142 if !p.v.hasOffset() { 143 panic(fmt.Errorf("offset not set in %v", p.v)) 144 } 145 return p.v.Offset 146} 147 148func (p point) hasPosition() bool { return p.Line > 0 } 149func (p point) hasOffset() bool { return p.Offset >= 0 } 150func (p point) isValid() bool { return p.hasPosition() || p.hasOffset() } 151func (p point) isZero() bool { 152 return (p.Line == 1 && p.Column == 1) || (!p.hasPosition() && p.Offset == 0) 153} 154 155func (s *span) clean() { 156 //this presumes the points are already clean 157 if !s.End.isValid() || (s.End == point{}) { 158 s.End = s.Start 159 } 160} 161 162func (p *point) clean() { 163 if p.Line < 0 { 164 p.Line = 0 165 } 166 if p.Column <= 0 { 167 if p.Line > 0 { 168 p.Column = 1 169 } else { 170 p.Column = 0 171 } 172 } 173 if p.Offset == 0 && (p.Line > 1 || p.Column > 1) { 174 p.Offset = -1 175 } 176} 177 178// Format implements fmt.Formatter to print the Location in a standard form. 179// The format produced is one that can be read back in using Parse. 180func (s Span) Format(f fmt.State, c rune) { 181 fullForm := f.Flag('+') 182 preferOffset := f.Flag('#') 183 // we should always have a uri, simplify if it is file format 184 //TODO: make sure the end of the uri is unambiguous 185 uri := string(s.v.URI) 186 if c == 'f' { 187 uri = path.Base(uri) 188 } else if !fullForm { 189 uri = s.v.URI.Filename() 190 } 191 fmt.Fprint(f, uri) 192 if !s.IsValid() || (!fullForm && s.v.Start.isZero() && s.v.End.isZero()) { 193 return 194 } 195 // see which bits of start to write 196 printOffset := s.HasOffset() && (fullForm || preferOffset || !s.HasPosition()) 197 printLine := s.HasPosition() && (fullForm || !printOffset) 198 printColumn := printLine && (fullForm || (s.v.Start.Column > 1 || s.v.End.Column > 1)) 199 fmt.Fprint(f, ":") 200 if printLine { 201 fmt.Fprintf(f, "%d", s.v.Start.Line) 202 } 203 if printColumn { 204 fmt.Fprintf(f, ":%d", s.v.Start.Column) 205 } 206 if printOffset { 207 fmt.Fprintf(f, "#%d", s.v.Start.Offset) 208 } 209 // start is written, do we need end? 210 if s.IsPoint() { 211 return 212 } 213 // we don't print the line if it did not change 214 printLine = fullForm || (printLine && s.v.End.Line > s.v.Start.Line) 215 fmt.Fprint(f, "-") 216 if printLine { 217 fmt.Fprintf(f, "%d", s.v.End.Line) 218 } 219 if printColumn { 220 if printLine { 221 fmt.Fprint(f, ":") 222 } 223 fmt.Fprintf(f, "%d", s.v.End.Column) 224 } 225 if printOffset { 226 fmt.Fprintf(f, "#%d", s.v.End.Offset) 227 } 228} 229 230func (s Span) WithPosition(c Converter) (Span, error) { 231 if err := s.update(c, true, false); err != nil { 232 return Span{}, err 233 } 234 return s, nil 235} 236 237func (s Span) WithOffset(c Converter) (Span, error) { 238 if err := s.update(c, false, true); err != nil { 239 return Span{}, err 240 } 241 return s, nil 242} 243 244func (s Span) WithAll(c Converter) (Span, error) { 245 if err := s.update(c, true, true); err != nil { 246 return Span{}, err 247 } 248 return s, nil 249} 250 251func (s *Span) update(c Converter, withPos, withOffset bool) error { 252 if !s.IsValid() { 253 return fmt.Errorf("cannot add information to an invalid span") 254 } 255 if withPos && !s.HasPosition() { 256 if err := s.v.Start.updatePosition(c); err != nil { 257 return err 258 } 259 if s.v.End.Offset == s.v.Start.Offset { 260 s.v.End = s.v.Start 261 } else if err := s.v.End.updatePosition(c); err != nil { 262 return err 263 } 264 } 265 if withOffset && (!s.HasOffset() || (s.v.End.hasPosition() && !s.v.End.hasOffset())) { 266 if err := s.v.Start.updateOffset(c); err != nil { 267 return err 268 } 269 if s.v.End.Line == s.v.Start.Line && s.v.End.Column == s.v.Start.Column { 270 s.v.End.Offset = s.v.Start.Offset 271 } else if err := s.v.End.updateOffset(c); err != nil { 272 return err 273 } 274 } 275 return nil 276} 277 278func (p *point) updatePosition(c Converter) error { 279 line, col, err := c.ToPosition(p.Offset) 280 if err != nil { 281 return err 282 } 283 p.Line = line 284 p.Column = col 285 return nil 286} 287 288func (p *point) updateOffset(c Converter) error { 289 offset, err := c.ToOffset(p.Line, p.Column) 290 if err != nil { 291 return err 292 } 293 p.Offset = offset 294 return nil 295} 296