mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-14 12:36:30 +00:00
286 lines
7.2 KiB
Go
286 lines
7.2 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 contains support for representing with positions and ranges in
|
||
|
// text files.
|
||
|
package span
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"path"
|
||
|
)
|
||
|
|
||
|
// Span represents a source code range in standardized form.
|
||
|
type Span struct {
|
||
|
v span
|
||
|
}
|
||
|
|
||
|
// 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 {
|
||
|
v point
|
||
|
}
|
||
|
|
||
|
type span struct {
|
||
|
URI URI `json:"uri"`
|
||
|
Start point `json:"start"`
|
||
|
End point `json:"end"`
|
||
|
}
|
||
|
|
||
|
type point struct {
|
||
|
Line int `json:"line"`
|
||
|
Column int `json:"column"`
|
||
|
Offset int `json:"offset"`
|
||
|
}
|
||
|
|
||
|
// Invalid is a span that reports false from IsValid
|
||
|
var Invalid = Span{v: span{Start: invalidPoint.v, End: invalidPoint.v}}
|
||
|
|
||
|
var invalidPoint = Point{v: point{Line: 0, Column: 0, Offset: -1}}
|
||
|
|
||
|
// Converter is the interface to an object that can convert between line:column
|
||
|
// and offset forms for a single file.
|
||
|
type Converter interface {
|
||
|
//ToPosition converts from an offset to a line:column pair.
|
||
|
ToPosition(offset int) (int, int, error)
|
||
|
//ToOffset converts from a line:column pair to an offset.
|
||
|
ToOffset(line, col int) (int, error)
|
||
|
}
|
||
|
|
||
|
func New(uri URI, start Point, end Point) Span {
|
||
|
s := Span{v: span{URI: uri, Start: start.v, End: end.v}}
|
||
|
s.v.clean()
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
func NewPoint(line, col, offset int) Point {
|
||
|
p := Point{v: point{Line: line, Column: col, Offset: offset}}
|
||
|
p.v.clean()
|
||
|
return p
|
||
|
}
|
||
|
|
||
|
func Compare(a, b Span) int {
|
||
|
if r := CompareURI(a.URI(), b.URI()); r != 0 {
|
||
|
return r
|
||
|
}
|
||
|
if r := comparePoint(a.v.Start, b.v.Start); r != 0 {
|
||
|
return r
|
||
|
}
|
||
|
return comparePoint(a.v.End, b.v.End)
|
||
|
}
|
||
|
|
||
|
func ComparePoint(a, b Point) int {
|
||
|
return comparePoint(a.v, b.v)
|
||
|
}
|
||
|
|
||
|
func comparePoint(a, b point) int {
|
||
|
if !a.hasPosition() {
|
||
|
if a.Offset < b.Offset {
|
||
|
return -1
|
||
|
}
|
||
|
if a.Offset > b.Offset {
|
||
|
return 1
|
||
|
}
|
||
|
return 0
|
||
|
}
|
||
|
if a.Line < b.Line {
|
||
|
return -1
|
||
|
}
|
||
|
if a.Line > b.Line {
|
||
|
return 1
|
||
|
}
|
||
|
if a.Column < b.Column {
|
||
|
return -1
|
||
|
}
|
||
|
if a.Column > b.Column {
|
||
|
return 1
|
||
|
}
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func (s Span) HasPosition() bool { return s.v.Start.hasPosition() }
|
||
|
func (s Span) HasOffset() bool { return s.v.Start.hasOffset() }
|
||
|
func (s Span) IsValid() bool { return s.v.Start.isValid() }
|
||
|
func (s Span) IsPoint() bool { return s.v.Start == s.v.End }
|
||
|
func (s Span) URI() URI { return s.v.URI }
|
||
|
func (s Span) Start() Point { return Point{s.v.Start} }
|
||
|
func (s Span) End() Point { return Point{s.v.End} }
|
||
|
func (s *Span) MarshalJSON() ([]byte, error) { return json.Marshal(&s.v) }
|
||
|
func (s *Span) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &s.v) }
|
||
|
|
||
|
func (p Point) HasPosition() bool { return p.v.hasPosition() }
|
||
|
func (p Point) HasOffset() bool { return p.v.hasOffset() }
|
||
|
func (p Point) IsValid() bool { return p.v.isValid() }
|
||
|
func (p *Point) MarshalJSON() ([]byte, error) { return json.Marshal(&p.v) }
|
||
|
func (p *Point) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &p.v) }
|
||
|
func (p Point) Line() int {
|
||
|
if !p.v.hasPosition() {
|
||
|
panic(fmt.Errorf("position not set in %v", p.v))
|
||
|
}
|
||
|
return p.v.Line
|
||
|
}
|
||
|
func (p Point) Column() int {
|
||
|
if !p.v.hasPosition() {
|
||
|
panic(fmt.Errorf("position not set in %v", p.v))
|
||
|
}
|
||
|
return p.v.Column
|
||
|
}
|
||
|
func (p Point) Offset() int {
|
||
|
if !p.v.hasOffset() {
|
||
|
panic(fmt.Errorf("offset not set in %v", p.v))
|
||
|
}
|
||
|
return p.v.Offset
|
||
|
}
|
||
|
|
||
|
func (p point) hasPosition() bool { return p.Line > 0 }
|
||
|
func (p point) hasOffset() bool { return p.Offset >= 0 }
|
||
|
func (p point) isValid() bool { return p.hasPosition() || p.hasOffset() }
|
||
|
func (p point) isZero() bool {
|
||
|
return (p.Line == 1 && p.Column == 1) || (!p.hasPosition() && p.Offset == 0)
|
||
|
}
|
||
|
|
||
|
func (s *span) clean() {
|
||
|
//this presumes the points are already clean
|
||
|
if !s.End.isValid() || (s.End == point{}) {
|
||
|
s.End = s.Start
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (p *point) clean() {
|
||
|
if p.Line < 0 {
|
||
|
p.Line = 0
|
||
|
}
|
||
|
if p.Column <= 0 {
|
||
|
if p.Line > 0 {
|
||
|
p.Column = 1
|
||
|
} else {
|
||
|
p.Column = 0
|
||
|
}
|
||
|
}
|
||
|
if p.Offset == 0 && (p.Line > 1 || p.Column > 1) {
|
||
|
p.Offset = -1
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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.v.URI)
|
||
|
if c == 'f' {
|
||
|
uri = path.Base(uri)
|
||
|
} else if !fullForm {
|
||
|
uri = s.v.URI.Filename()
|
||
|
}
|
||
|
fmt.Fprint(f, uri)
|
||
|
if !s.IsValid() || (!fullForm && s.v.Start.isZero() && s.v.End.isZero()) {
|
||
|
return
|
||
|
}
|
||
|
// see which bits of start to write
|
||
|
printOffset := s.HasOffset() && (fullForm || preferOffset || !s.HasPosition())
|
||
|
printLine := s.HasPosition() && (fullForm || !printOffset)
|
||
|
printColumn := printLine && (fullForm || (s.v.Start.Column > 1 || s.v.End.Column > 1))
|
||
|
fmt.Fprint(f, ":")
|
||
|
if printLine {
|
||
|
fmt.Fprintf(f, "%d", s.v.Start.Line)
|
||
|
}
|
||
|
if printColumn {
|
||
|
fmt.Fprintf(f, ":%d", s.v.Start.Column)
|
||
|
}
|
||
|
if printOffset {
|
||
|
fmt.Fprintf(f, "#%d", s.v.Start.Offset)
|
||
|
}
|
||
|
// start is written, do we need end?
|
||
|
if s.IsPoint() {
|
||
|
return
|
||
|
}
|
||
|
// we don't print the line if it did not change
|
||
|
printLine = fullForm || (printLine && s.v.End.Line > s.v.Start.Line)
|
||
|
fmt.Fprint(f, "-")
|
||
|
if printLine {
|
||
|
fmt.Fprintf(f, "%d", s.v.End.Line)
|
||
|
}
|
||
|
if printColumn {
|
||
|
if printLine {
|
||
|
fmt.Fprint(f, ":")
|
||
|
}
|
||
|
fmt.Fprintf(f, "%d", s.v.End.Column)
|
||
|
}
|
||
|
if printOffset {
|
||
|
fmt.Fprintf(f, "#%d", s.v.End.Offset)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s Span) WithPosition(c Converter) (Span, error) {
|
||
|
if err := s.update(c, true, false); err != nil {
|
||
|
return Span{}, err
|
||
|
}
|
||
|
return s, nil
|
||
|
}
|
||
|
|
||
|
func (s Span) WithOffset(c Converter) (Span, error) {
|
||
|
if err := s.update(c, false, true); err != nil {
|
||
|
return Span{}, err
|
||
|
}
|
||
|
return s, nil
|
||
|
}
|
||
|
|
||
|
func (s Span) WithAll(c Converter) (Span, error) {
|
||
|
if err := s.update(c, true, true); err != nil {
|
||
|
return Span{}, err
|
||
|
}
|
||
|
return s, nil
|
||
|
}
|
||
|
|
||
|
func (s *Span) update(c Converter, withPos, withOffset bool) error {
|
||
|
if !s.IsValid() {
|
||
|
return fmt.Errorf("cannot add information to an invalid span")
|
||
|
}
|
||
|
if withPos && !s.HasPosition() {
|
||
|
if err := s.v.Start.updatePosition(c); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if s.v.End.Offset == s.v.Start.Offset {
|
||
|
s.v.End = s.v.Start
|
||
|
} else if err := s.v.End.updatePosition(c); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
if withOffset && (!s.HasOffset() || (s.v.End.hasPosition() && !s.v.End.hasOffset())) {
|
||
|
if err := s.v.Start.updateOffset(c); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if s.v.End.Line == s.v.Start.Line && s.v.End.Column == s.v.Start.Column {
|
||
|
s.v.End.Offset = s.v.Start.Offset
|
||
|
} else if err := s.v.End.updateOffset(c); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (p *point) updatePosition(c Converter) error {
|
||
|
line, col, err := c.ToPosition(p.Offset)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
p.Line = line
|
||
|
p.Column = col
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (p *point) updateOffset(c Converter) error {
|
||
|
offset, err := c.ToOffset(p.Line, p.Column)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
p.Offset = offset
|
||
|
return nil
|
||
|
}
|