/
location.go
132 lines (115 loc) · 3.61 KB
/
location.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Package location defines locations in Rego source code.
package location
import (
"bytes"
"encoding/json"
"errors"
"fmt"
astJSON "github.com/open-policy-agent/opa/ast/json"
)
// Location records a position in source code
type Location struct {
Text []byte `json:"-"` // The original text fragment from the source.
File string `json:"file"` // The name of the source file (which may be empty).
Row int `json:"row"` // The line in the source.
Col int `json:"col"` // The column in the row.
Offset int `json:"-"` // The byte offset for the location in the source.
// JSONOptions specifies options for marshaling and unmarshalling of locations
JSONOptions astJSON.Options
}
// NewLocation returns a new Location object.
func NewLocation(text []byte, file string, row int, col int) *Location {
return &Location{Text: text, File: file, Row: row, Col: col}
}
// Equal checks if two locations are equal to each other.
func (loc *Location) Equal(other *Location) bool {
return bytes.Equal(loc.Text, other.Text) &&
loc.File == other.File &&
loc.Row == other.Row &&
loc.Col == other.Col
}
// Errorf returns a new error value with a message formatted to include the location
// info (e.g., line, column, filename, etc.)
func (loc *Location) Errorf(f string, a ...interface{}) error {
return errors.New(loc.Format(f, a...))
}
// Wrapf returns a new error value that wraps an existing error with a message formatted
// to include the location info (e.g., line, column, filename, etc.)
func (loc *Location) Wrapf(err error, f string, a ...interface{}) error {
return fmt.Errorf(loc.Format(f, a...)+": %w", err)
}
// Format returns a formatted string prefixed with the location information.
func (loc *Location) Format(f string, a ...interface{}) string {
if len(loc.File) > 0 {
f = fmt.Sprintf("%v:%v: %v", loc.File, loc.Row, f)
} else {
f = fmt.Sprintf("%v:%v: %v", loc.Row, loc.Col, f)
}
return fmt.Sprintf(f, a...)
}
func (loc *Location) String() string {
if len(loc.File) > 0 {
return fmt.Sprintf("%v:%v", loc.File, loc.Row)
}
if len(loc.Text) > 0 {
return string(loc.Text)
}
return fmt.Sprintf("%v:%v", loc.Row, loc.Col)
}
// Compare returns -1, 0, or 1 to indicate if this loc is less than, equal to,
// or greater than the other. Comparison is performed on the file, row, and
// column of the Location (but not on the text.) Nil locations are greater than
// non-nil locations.
func (loc *Location) Compare(other *Location) int {
if loc == nil && other == nil {
return 0
} else if loc == nil {
return 1
} else if other == nil {
return -1
} else if loc.File < other.File {
return -1
} else if loc.File > other.File {
return 1
} else if loc.Row < other.Row {
return -1
} else if loc.Row > other.Row {
return 1
} else if loc.Col < other.Col {
return -1
} else if loc.Col > other.Col {
return 1
}
return 0
}
func (loc *Location) MarshalJSON() ([]byte, error) {
// structs are used here to preserve the field ordering of the original Location struct
if loc.JSONOptions.MarshalOptions.ExcludeLocationFile {
data := struct {
Row int `json:"row"`
Col int `json:"col"`
Text []byte `json:"text,omitempty"`
}{
Row: loc.Row,
Col: loc.Col,
}
if loc.JSONOptions.MarshalOptions.IncludeLocationText {
data.Text = loc.Text
}
return json.Marshal(data)
}
data := struct {
File string `json:"file"`
Row int `json:"row"`
Col int `json:"col"`
Text []byte `json:"text,omitempty"`
}{
Row: loc.Row,
Col: loc.Col,
File: loc.File,
}
if loc.JSONOptions.MarshalOptions.IncludeLocationText {
data.Text = loc.Text
}
return json.Marshal(data)
}