mirror of
https://github.com/golang/go
synced 2024-11-18 10:24:42 -07:00
b40df0fb21
This implements a standard way of describing source locations along with printing and parsing of those locations and conversion to and from the token.Pos forms. Change-Id: Ibb3df0a282bc2effb0fa8bd3a51bb0d281b2ffb1 Reviewed-on: https://go-review.googlesource.com/c/tools/+/163778 Run-TryBot: Ian Cottrell <iancottrell@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
203 lines
5.3 KiB
Go
203 lines
5.3 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package span
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
// Span represents a source code range in standardized form.
|
|
type Span struct {
|
|
URI URI `json:"uri"`
|
|
Start Point `json:"start"`
|
|
End Point `json:"end"`
|
|
}
|
|
|
|
// Point represents a single point within a file.
|
|
// In general this should only be used as part of a Span, as on its own it
|
|
// does not carry enough information.
|
|
type Point struct {
|
|
Line int `json:"line"`
|
|
Column int `json:"column"`
|
|
Offset int `json:"offset"`
|
|
}
|
|
|
|
// Offsets is the interface to an object that can convert to offset
|
|
// from line:column forms for a single file.
|
|
type Offsets interface {
|
|
ToOffset(line, col int) int
|
|
}
|
|
|
|
// Coords is the interface to an object that can convert to line:column
|
|
// from offset forms for a single file.
|
|
type Coords interface {
|
|
ToCoord(offset int) (int, int)
|
|
}
|
|
|
|
// Converter is the interface to an object that can convert between line:column
|
|
// and offset forms for a single file.
|
|
type Converter interface {
|
|
Offsets
|
|
Coords
|
|
}
|
|
|
|
// Format implements fmt.Formatter to print the Location in a standard form.
|
|
// The format produced is one that can be read back in using Parse.
|
|
func (s Span) Format(f fmt.State, c rune) {
|
|
fullForm := f.Flag('+')
|
|
preferOffset := f.Flag('#')
|
|
// we should always have a uri, simplify if it is file format
|
|
//TODO: make sure the end of the uri is unambiguous
|
|
uri := string(s.URI)
|
|
if !fullForm {
|
|
if filename, err := s.URI.Filename(); err == nil {
|
|
uri = filename
|
|
}
|
|
}
|
|
fmt.Fprint(f, uri)
|
|
// see which bits of start to write
|
|
printOffset := fullForm || (s.Start.Offset > 0 && (preferOffset || s.Start.Line <= 0))
|
|
printLine := fullForm || (s.Start.Line > 0 && !printOffset)
|
|
printColumn := fullForm || (printLine && (s.Start.Column > 1 || s.End.Column > 1))
|
|
if !printLine && !printColumn && !printOffset {
|
|
return
|
|
}
|
|
fmt.Fprint(f, ":")
|
|
if printLine {
|
|
fmt.Fprintf(f, "%d", clamp(s.Start.Line))
|
|
}
|
|
if printColumn {
|
|
fmt.Fprintf(f, ":%d", clamp(s.Start.Column))
|
|
}
|
|
if printOffset {
|
|
fmt.Fprintf(f, "#%d", clamp(s.Start.Offset))
|
|
}
|
|
// start is written, do we need end?
|
|
printLine = fullForm || (printLine && s.End.Line > s.Start.Line)
|
|
isPoint := s.End.Line == s.Start.Line && s.End.Column == s.Start.Column
|
|
printColumn = fullForm || (printColumn && s.End.Column > 0 && !isPoint)
|
|
printOffset = fullForm || (printOffset && s.End.Offset > s.Start.Offset)
|
|
if !printLine && !printColumn && !printOffset {
|
|
return
|
|
}
|
|
fmt.Fprint(f, "-")
|
|
if printLine {
|
|
fmt.Fprintf(f, "%d", clamp(s.End.Line))
|
|
}
|
|
if printColumn {
|
|
if printLine {
|
|
fmt.Fprint(f, ":")
|
|
}
|
|
fmt.Fprintf(f, "%d", clamp(s.End.Column))
|
|
}
|
|
if printOffset {
|
|
fmt.Fprintf(f, "#%d", clamp(s.End.Offset))
|
|
}
|
|
}
|
|
|
|
// CleanOffset returns a copy of the Span with the Offset field updated.
|
|
// If the field is missing and Offsets is supplied it will be used to
|
|
// calculate it from the line and column.
|
|
// The value will then be adjusted to the canonical form.
|
|
func (s Span) CleanOffset(offsets Offsets) Span {
|
|
if offsets != nil {
|
|
if (s.Start.Line > 1 || s.Start.Column > 1) && s.Start.Offset == 0 {
|
|
s.Start.updateOffset(offsets)
|
|
}
|
|
if (s.End.Line > 1 || s.End.Column > 1) && s.End.Offset == 0 {
|
|
s.End.updateOffset(offsets)
|
|
}
|
|
}
|
|
if s.Start.Offset < 0 {
|
|
s.Start.Offset = 0
|
|
}
|
|
if s.End.Offset <= s.Start.Offset {
|
|
s.End.Offset = s.Start.Offset
|
|
}
|
|
return s
|
|
}
|
|
|
|
// CleanCoords returns a copy of the Span with the Line and Column fields
|
|
// cleaned.
|
|
// If the fields are missing and Coords is supplied it will be used to
|
|
// calculate them from the offset.
|
|
// The values will then be adjusted to the canonical form.
|
|
func (s Span) CleanCoords(coords Coords) Span {
|
|
if coords != nil {
|
|
if s.Start.Line == 0 && s.Start.Offset > 0 {
|
|
s.Start.Line, s.Start.Column = coords.ToCoord(s.Start.Offset)
|
|
}
|
|
if s.End.Line == 0 && s.End.Offset > 0 {
|
|
s.End.Line, s.End.Column = coords.ToCoord(s.End.Offset)
|
|
}
|
|
}
|
|
if s.Start.Line <= 0 {
|
|
s.Start.Line = 0
|
|
}
|
|
if s.Start.Line == 0 {
|
|
s.Start.Column = 0
|
|
} else if s.Start.Column <= 0 {
|
|
s.Start.Column = 0
|
|
}
|
|
if s.End.Line < s.Start.Line {
|
|
s.End.Line = s.Start.Line
|
|
}
|
|
if s.End.Column < s.Start.Column {
|
|
s.End.Column = s.Start.Column
|
|
}
|
|
if s.Start.Column <= 1 && s.End.Column <= 1 {
|
|
s.Start.Column = 0
|
|
s.End.Column = 0
|
|
}
|
|
if s.Start.Line <= 1 && s.End.Line <= 1 && s.Start.Column <= 1 && s.End.Column <= 1 {
|
|
s.Start.Line = 0
|
|
s.End.Line = 0
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Clean returns a copy of the Span fully normalized.
|
|
// If passed a converter, it will use it to fill in any missing fields by
|
|
// converting between offset and line column fields.
|
|
// It does not attempt to validate that already filled fields have consistent
|
|
// values.
|
|
func (s Span) Clean(converter Converter) Span {
|
|
s = s.CleanOffset(converter)
|
|
s = s.CleanCoords(converter)
|
|
if s.End.Offset == 0 {
|
|
// in case CleanCoords adjusted the end position
|
|
s.End.Offset = s.Start.Offset
|
|
}
|
|
return s
|
|
}
|
|
|
|
// IsPoint returns true if the span represents a single point.
|
|
// It is only valid on spans that are "clean".
|
|
func (s Span) IsPoint() bool {
|
|
return s.Start == s.End
|
|
}
|
|
|
|
func (p *Point) updateOffset(offsets Offsets) {
|
|
p.Offset = 0
|
|
if p.Line <= 0 {
|
|
return
|
|
}
|
|
c := p.Column
|
|
if c < 1 {
|
|
c = 1
|
|
}
|
|
if p.Line == 1 && c == 1 {
|
|
return
|
|
}
|
|
p.Offset = offsets.ToOffset(p.Line, c)
|
|
}
|
|
|
|
func clamp(v int) int {
|
|
if v < 0 {
|
|
return 0
|
|
}
|
|
return v
|
|
}
|