cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
e622a47c6fc350caabb3fe75c25cc3bfbd6feab6

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/parser/source/position.go

210lines · modecode

1package source
2
3import (
4 "strings"
5
6 "github.com/prometheus/prometheus/model/labels"
7 promParser "github.com/prometheus/prometheus/promql/parser"
8 "github.com/prometheus/prometheus/promql/parser/posrange"
9)
10
11func GetQueryFragment(expr string, pos posrange.PositionRange) string {
12 return expr[pos.Start:pos.End]
13}
14
15func isOutside(pos posrange.PositionRange, outside []posrange.PositionRange) bool {
16 for _, out := range outside {
17 if pos.Start >= out.Start && pos.End <= out.End {
18 return false
19 }
20 }
21 return true
22}
23
24// FindFuncNamePosition returns the source position of a function name within
25// the provided fragment.
26//
27// It matches fn case-insensitively, but only when the matched text is
28// followed by optional whitespace and then `(`. This allows `by (...)` to
29// match `by` while skipping the same text inside longer words such as
30// `bytes`.
31//
32// The returned range covers only the function name. When converting it to a
33// diagnostic, use:
34//
35// - FirstColumn = pos.Start + 1
36// - LastColumn = pos.End.
37func FindFuncNamePosition(expr string, within posrange.PositionRange, fn string) posrange.PositionRange {
38 fragment := GetQueryFragment(expr, within)
39 lower := strings.ToLower(fragment)
40 fnLower := strings.ToLower(fn)
41 offset := 0
42 for {
43 // Find next case-insensitive occurrence of fn.
44 idx := strings.Index(lower[offset:], fnLower)
45 if idx < 0 {
46 return within
47 }
48 idx += offset
49 end := idx + len(fn)
50 // Check that fn is followed by optional whitespace then '('.
51 // This skips false positives like "by" inside "bytes".
52 for i := end; i < len(fragment); i++ {
53 c := fragment[i]
54 if c == '(' {
55 return posrange.PositionRange{
56 Start: within.Start + posrange.Pos(idx),
57 End: within.Start + posrange.Pos(end),
58 }
59 }
60 if c != ' ' && c != '\n' && c != '\t' {
61 break // Not whitespace, not '(' — this occurrence is inside a word.
62 }
63 }
64 offset = idx + 1 // Try next occurrence.
65 }
66}
67
68// FindFuncPosition returns the position of the whole `fn(...)` call inside the
69// within fragment, from fn up to its closing ')'. When within spans more than
70// one call, outside lists ranges the match must fall within, which lets callers
71// pick the right `fn(...)` when the same keyword appears on both sides.
72func FindFuncPosition(expr string, within posrange.PositionRange, fn string, outside []posrange.PositionRange) posrange.PositionRange {
73 fragment := GetQueryFragment(expr, within)
74 lower := strings.ToLower(fragment)
75 fnLower := strings.ToLower(fn)
76 for offset := 0; ; {
77 idx := strings.Index(lower[offset:], fnLower)
78 if idx < 0 {
79 return within
80 }
81 idx += offset
82 offset = idx + 1
83
84 // fn must be followed by optional whitespace and then '(', otherwise
85 // this is just fn appearing inside a longer word like "bytes".
86 open := -1
87 for i := idx + len(fn); i < len(fragment); i++ {
88 c := fragment[i]
89 if c == '(' {
90 open = i
91 break
92 }
93 if c != ' ' && c != '\n' && c != '\t' {
94 break
95 }
96 }
97 if open < 0 {
98 continue
99 }
100
101 // The call ends at the first ')' after the opening paren.
102 closeRel := strings.IndexByte(fragment[open:], ')')
103 if closeRel < 0 {
104 continue
105 }
106
107 pos := posrange.PositionRange{
108 Start: within.Start + posrange.Pos(idx),
109 End: within.Start + posrange.Pos(open+closeRel+1),
110 }
111 if isOutside(pos, outside) {
112 return pos
113 }
114 return within
115 }
116}
117
118// findArgumentPosition looks for name inside the expr substring selected by
119// within and returns the matching argument token position in the original expr.
120//
121// For example, if expr is `sum(foo) by(job, instance) > 0` and within selects
122// `by(job, instance)`, then name="instance" returns the position of
123// `instance` in the full expr.
124//
125// If expr is `foo * on(job) group_left(cluster) bar` and within selects
126// `on(job)`, then name="job" returns the position of `job` in the full expr.
127//
128// If expr is `sum(foo) without(notify)` and within selects `without(notify)`,
129// then name="notify" returns the position of `notify` in the full expr.
130func findArgumentPosition(expr string, within posrange.PositionRange, name string) posrange.PositionRange {
131 fragment := GetQueryFragment(expr, within)
132
133 isSpace := func(b byte) bool {
134 return b == ' ' || b == '\n' || b == '\t'
135 }
136 // Match the right-most name that sits as a complete argument, i.e. one
137 // bordered by '(' or ',' on the left and ',' or ')' on the right.
138 best := -1
139 for off := 0; ; {
140 i := strings.Index(fragment[off:], name)
141 if i < 0 {
142 break
143 }
144 i += off
145 off = i + 1
146
147 // The byte opening the argument must be '(' or a ',' separator.
148 p := i - 1
149 for p >= 0 && isSpace(fragment[p]) {
150 p--
151 }
152 if p < 0 || (fragment[p] != '(' && fragment[p] != ',') {
153 continue
154 }
155
156 // The byte closing the argument must be ')' or a ',' separator.
157 q := i + len(name)
158 for q < len(fragment) && isSpace(fragment[q]) {
159 q++
160 }
161 if q >= len(fragment) || (fragment[q] != ')' && fragment[q] != ',') {
162 continue
163 }
164
165 best = i
166 }
167 if best < 0 {
168 return within
169 }
170 return posrange.PositionRange{
171 Start: within.Start + posrange.Pos(best),
172 End: within.Start + posrange.Pos(best+len(name)),
173 }
174}
175
176// findBinOpsOperatorPosition returns the position of the op operator between the
177// two sides of a binary expression, searching only the gap between them so a
178// matching op inside either side is ignored.
179func findBinOpsOperatorPosition(expr string, n *promParser.BinaryExpr, op string) posrange.PositionRange {
180 within := posrange.PositionRange{
181 Start: n.LHS.PositionRange().End + 1,
182 End: n.RHS.PositionRange().Start,
183 }
184 idx := strings.Index(GetQueryFragment(expr, within), op)
185 if idx < 0 {
186 return within
187 }
188 return posrange.PositionRange{
189 Start: within.Start + posrange.Pos(idx),
190 End: within.Start + posrange.Pos(idx+len(op)),
191 }
192}
193
194// FindMatcherPos locates 'name op "value"' (e.g. job=~"foo") in the expression
195// fragment and returns the position spanning from "name" through the closing
196// quote, with End exclusive (one past the closing quote) to match the other
197// position functions and GetQueryFragment.
198func FindMatcherPos(expr string, within posrange.PositionRange, m *labels.Matcher) posrange.PositionRange {
199 fragment := GetQueryFragment(expr, within)
200 // Build the full pattern: name + op + "value" (e.g. job=~"foo").
201 pattern := m.Name + m.Type.String() + `"` + m.Value + `"`
202 idx := strings.Index(fragment, pattern)
203 if idx < 0 {
204 return within
205 }
206 return posrange.PositionRange{
207 Start: within.Start + posrange.Pos(idx),
208 End: within.Start + posrange.Pos(idx+len(pattern)),
209 }
210}
211