github.com/v2fly/tools@v0.100.0/internal/span/span.go (about)

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