• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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