• 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
15package span
16
17import (
18	"fmt"
19	"go/token"
20)
21
22// Range represents a source code range in token.Pos form.
23// It also carries the FileSet that produced the positions, so that it is
24// self contained.
25type Range struct {
26	FileSet *token.FileSet
27	Start   token.Pos
28	End     token.Pos
29}
30
31// TokenConverter is a Converter backed by a token file set and file.
32// It uses the file set methods to work out the conversions, which
33// makes it fast and does not require the file contents.
34type TokenConverter struct {
35	fset *token.FileSet
36	file *token.File
37}
38
39// NewRange creates a new Range from a FileSet and two positions.
40// To represent a point pass a 0 as the end pos.
41func NewRange(fset *token.FileSet, start, end token.Pos) Range {
42	return Range{
43		FileSet: fset,
44		Start:   start,
45		End:     end,
46	}
47}
48
49// NewTokenConverter returns an implementation of Converter backed by a
50// token.File.
51func NewTokenConverter(fset *token.FileSet, f *token.File) *TokenConverter {
52	return &TokenConverter{fset: fset, file: f}
53}
54
55// NewContentConverter returns an implementation of Converter for the
56// given file content.
57func NewContentConverter(filename string, content []byte) *TokenConverter {
58	fset := token.NewFileSet()
59	f := fset.AddFile(filename, -1, len(content))
60	f.SetLinesForContent(content)
61	return &TokenConverter{fset: fset, file: f}
62}
63
64// IsPoint returns true if the range represents a single point.
65func (r Range) IsPoint() bool {
66	return r.Start == r.End
67}
68
69// Span converts a Range to a Span that represents the Range.
70// It will fill in all the members of the Span, calculating the line and column
71// information.
72func (r Range) Span() (Span, error) {
73	f := r.FileSet.File(r.Start)
74	if f == nil {
75		return Span{}, fmt.Errorf("file not found in FileSet")
76	}
77	s := Span{v: span{URI: FileURI(f.Name())}}
78	var err error
79	s.v.Start.Offset, err = offset(f, r.Start)
80	if err != nil {
81		return Span{}, err
82	}
83	if r.End.IsValid() {
84		s.v.End.Offset, err = offset(f, r.End)
85		if err != nil {
86			return Span{}, err
87		}
88	}
89	s.v.Start.clean()
90	s.v.End.clean()
91	s.v.clean()
92	converter := NewTokenConverter(r.FileSet, f)
93	return s.WithPosition(converter)
94}
95
96// offset is a copy of the Offset function in go/token, but with the adjustment
97// that it does not panic on invalid positions.
98func offset(f *token.File, pos token.Pos) (int, error) {
99	if int(pos) < f.Base() || int(pos) > f.Base()+f.Size() {
100		return 0, fmt.Errorf("invalid pos")
101	}
102	return int(pos) - f.Base(), nil
103}
104
105// Range converts a Span to a Range that represents the Span for the supplied
106// File.
107func (s Span) Range(converter *TokenConverter) (Range, error) {
108	s, err := s.WithOffset(converter)
109	if err != nil {
110		return Range{}, err
111	}
112	// go/token will panic if the offset is larger than the file's size,
113	// so check here to avoid panicking.
114	if s.Start().Offset() > converter.file.Size() {
115		return Range{}, fmt.Errorf("start offset %v is past the end of the file %v", s.Start(), converter.file.Size())
116	}
117	if s.End().Offset() > converter.file.Size() {
118		return Range{}, fmt.Errorf("end offset %v is past the end of the file %v", s.End(), converter.file.Size())
119	}
120	return Range{
121		FileSet: converter.fset,
122		Start:   converter.file.Pos(s.Start().Offset()),
123		End:     converter.file.Pos(s.End().Offset()),
124	}, nil
125}
126
127func (l *TokenConverter) ToPosition(offset int) (int, int, error) {
128	if offset > l.file.Size() {
129		return 0, 0, fmt.Errorf("offset %v is past the end of the file %v", offset, l.file.Size())
130	}
131	pos := l.file.Pos(offset)
132	p := l.fset.Position(pos)
133	if offset == l.file.Size() {
134		return p.Line + 1, 1, nil
135	}
136	return p.Line, p.Column, nil
137}
138
139func (l *TokenConverter) ToOffset(line, col int) (int, error) {
140	if line < 0 {
141		return -1, fmt.Errorf("line is not valid")
142	}
143	lineMax := l.file.LineCount() + 1
144	if line > lineMax {
145		return -1, fmt.Errorf("line is beyond end of file %v", lineMax)
146	} else if line == lineMax {
147		if col > 1 {
148			return -1, fmt.Errorf("column is beyond end of file")
149		}
150		// at the end of the file, allowing for a trailing eol
151		return l.file.Size(), nil
152	}
153	pos := lineStart(l.file, line)
154	if !pos.IsValid() {
155		return -1, fmt.Errorf("line is not in file")
156	}
157	// we assume that column is in bytes here, and that the first byte of a
158	// line is at column 1
159	pos += token.Pos(col - 1)
160	return offset(l.file, pos)
161}
162