@fc-components/monaco-editor 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +29 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -0
- package/dist/monaco-editor.cjs.development.js +2170 -0
- package/dist/monaco-editor.cjs.development.js.map +1 -0
- package/dist/monaco-editor.cjs.production.min.js +2 -0
- package/dist/monaco-editor.cjs.production.min.js.map +1 -0
- package/dist/monaco-editor.esm.js +2164 -0
- package/dist/monaco-editor.esm.js.map +1 -0
- package/dist/promql/completion/DataProvider.d.ts +44 -0
- package/dist/promql/completion/completions.d.ts +13 -0
- package/dist/promql/completion/getCompletionProvider.d.ts +6 -0
- package/dist/promql/completion/situation.d.ts +25 -0
- package/dist/promql/index.d.ts +14 -0
- package/dist/promql/promql.d.ts +60 -0
- package/dist/promql/types.d.ts +30 -0
- package/dist/promql/util.d.ts +6 -0
- package/dist/promql/validation.d.ts +11 -0
- package/package.json +56 -0
- package/src/index.tsx +3 -0
- package/src/promql/NOTICE.md +3 -0
- package/src/promql/completion/DataProvider.ts +252 -0
- package/src/promql/completion/completions.ts +188 -0
- package/src/promql/completion/getCompletionProvider.ts +96 -0
- package/src/promql/completion/situation.ts +491 -0
- package/src/promql/index.tsx +263 -0
- package/src/promql/promql.ts +912 -0
- package/src/promql/types.ts +35 -0
- package/src/promql/util.ts +29 -0
- package/src/promql/validation.ts +93 -0
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import type { SyntaxNode, Tree } from '@lezer/common';
|
|
2
|
+
import {
|
|
3
|
+
AggregateExpr,
|
|
4
|
+
AggregateModifier,
|
|
5
|
+
BinaryExpr,
|
|
6
|
+
EqlRegex,
|
|
7
|
+
EqlSingle,
|
|
8
|
+
FunctionCallBody,
|
|
9
|
+
GroupingLabels,
|
|
10
|
+
Identifier,
|
|
11
|
+
LabelMatchers,
|
|
12
|
+
LabelName,
|
|
13
|
+
MatchOp,
|
|
14
|
+
MatrixSelector,
|
|
15
|
+
Neq,
|
|
16
|
+
NeqRegex,
|
|
17
|
+
NumberDurationLiteralInDurationContext,
|
|
18
|
+
parser,
|
|
19
|
+
PromQL,
|
|
20
|
+
StringLiteral,
|
|
21
|
+
UnquotedLabelMatcher,
|
|
22
|
+
VectorSelector,
|
|
23
|
+
} from '@fc-components/lezer-metricsql';
|
|
24
|
+
|
|
25
|
+
import { NeverCaseError } from '../util';
|
|
26
|
+
import { LabelOperator, Label } from '../types';
|
|
27
|
+
|
|
28
|
+
type Direction = 'parent' | 'firstChild' | 'lastChild' | 'nextSibling';
|
|
29
|
+
|
|
30
|
+
type NodeTypeId =
|
|
31
|
+
| 0 // this is used as error-id
|
|
32
|
+
| typeof AggregateExpr
|
|
33
|
+
| typeof AggregateModifier
|
|
34
|
+
| typeof FunctionCallBody
|
|
35
|
+
| typeof GroupingLabels
|
|
36
|
+
| typeof Identifier
|
|
37
|
+
| typeof UnquotedLabelMatcher
|
|
38
|
+
| typeof LabelMatchers
|
|
39
|
+
| typeof LabelName
|
|
40
|
+
| typeof PromQL
|
|
41
|
+
| typeof StringLiteral
|
|
42
|
+
| typeof VectorSelector
|
|
43
|
+
| typeof MatrixSelector
|
|
44
|
+
| typeof MatchOp
|
|
45
|
+
| typeof EqlSingle
|
|
46
|
+
| typeof Neq
|
|
47
|
+
| typeof EqlRegex
|
|
48
|
+
| typeof NeqRegex;
|
|
49
|
+
|
|
50
|
+
type Path = Array<[Direction, NodeTypeId]>;
|
|
51
|
+
|
|
52
|
+
function move(node: SyntaxNode, direction: Direction): SyntaxNode | null {
|
|
53
|
+
switch (direction) {
|
|
54
|
+
case 'parent':
|
|
55
|
+
return node.parent;
|
|
56
|
+
case 'firstChild':
|
|
57
|
+
return node.firstChild;
|
|
58
|
+
case 'lastChild':
|
|
59
|
+
return node.lastChild;
|
|
60
|
+
case 'nextSibling':
|
|
61
|
+
return node.nextSibling;
|
|
62
|
+
default:
|
|
63
|
+
throw new NeverCaseError(direction);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function walk(node: SyntaxNode, path: Path): SyntaxNode | null {
|
|
68
|
+
let current: SyntaxNode | null = node;
|
|
69
|
+
for (const [direction, expectedType] of path) {
|
|
70
|
+
current = move(current, direction);
|
|
71
|
+
if (current === null) {
|
|
72
|
+
// we could not move in the direction, we stop
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
if (current.type.id !== expectedType) {
|
|
76
|
+
// the reached node has wrong type, we stop
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return current;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getNodeText(node: SyntaxNode, text: string): string {
|
|
84
|
+
return text.slice(node.from, node.to);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parsePromQLStringLiteral(text: string): string {
|
|
88
|
+
// if it is a string-literal, it is inside quotes of some kind
|
|
89
|
+
const inside = text.slice(1, text.length - 1);
|
|
90
|
+
|
|
91
|
+
// FIXME: support https://prometheus.io/docs/prometheus/latest/querying/basics/#string-literals
|
|
92
|
+
// FIXME: maybe check other promql code, if all is supported or not
|
|
93
|
+
|
|
94
|
+
// for now we do only some very simple un-escaping
|
|
95
|
+
|
|
96
|
+
// we start with double-quotes
|
|
97
|
+
if (text.startsWith('"') && text.endsWith('"')) {
|
|
98
|
+
// NOTE: this is not 100% perfect, we only unescape the double-quote,
|
|
99
|
+
// there might be other characters too
|
|
100
|
+
return inside.replace(/\\"/, '"');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// then single-quote
|
|
104
|
+
if (text.startsWith("'") && text.endsWith("'")) {
|
|
105
|
+
// NOTE: this is not 100% perfect, we only unescape the single-quote,
|
|
106
|
+
// there might be other characters too
|
|
107
|
+
return inside.replace(/\\'/, "'");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// then backticks
|
|
111
|
+
if (text.startsWith('`') && text.endsWith('`')) {
|
|
112
|
+
return inside;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
throw new Error('FIXME: invalid string literal');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export type Situation =
|
|
119
|
+
| {
|
|
120
|
+
type: 'IN_FUNCTION';
|
|
121
|
+
}
|
|
122
|
+
| {
|
|
123
|
+
type: 'AT_ROOT';
|
|
124
|
+
}
|
|
125
|
+
| {
|
|
126
|
+
type: 'EMPTY';
|
|
127
|
+
}
|
|
128
|
+
| {
|
|
129
|
+
type: 'IN_DURATION';
|
|
130
|
+
}
|
|
131
|
+
| {
|
|
132
|
+
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME';
|
|
133
|
+
metricName?: string;
|
|
134
|
+
otherLabels: Label[];
|
|
135
|
+
}
|
|
136
|
+
| {
|
|
137
|
+
type: 'IN_GROUPING';
|
|
138
|
+
metricName: string;
|
|
139
|
+
otherLabels: Label[];
|
|
140
|
+
}
|
|
141
|
+
| {
|
|
142
|
+
type: 'IN_LABEL_SELECTOR_WITH_LABEL_NAME';
|
|
143
|
+
metricName?: string;
|
|
144
|
+
labelName: string;
|
|
145
|
+
betweenQuotes: boolean;
|
|
146
|
+
otherLabels: Label[];
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
type Resolver = {
|
|
150
|
+
path: NodeTypeId[];
|
|
151
|
+
fun: (node: SyntaxNode, text: string, pos: number) => Situation | null;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
function isPathMatch(resolverPath: NodeTypeId[], cursorPath: number[]): boolean {
|
|
155
|
+
return resolverPath.every((item, index) => item === cursorPath[index]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const ERROR_NODE_NAME: NodeTypeId = 0; // this is used as error-id
|
|
159
|
+
|
|
160
|
+
const RESOLVERS: Resolver[] = [
|
|
161
|
+
{
|
|
162
|
+
path: [LabelMatchers, VectorSelector],
|
|
163
|
+
fun: resolveLabelKeysWithEquals,
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
path: [PromQL],
|
|
167
|
+
fun: resolveTopLevel,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
path: [FunctionCallBody],
|
|
171
|
+
fun: resolveInFunction,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
path: [StringLiteral, UnquotedLabelMatcher],
|
|
175
|
+
fun: resolveLabelMatcher,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
path: [ERROR_NODE_NAME, BinaryExpr, PromQL],
|
|
179
|
+
fun: resolveTopLevel,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
path: [ERROR_NODE_NAME, UnquotedLabelMatcher],
|
|
183
|
+
fun: resolveLabelMatcher,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
path: [ERROR_NODE_NAME, NumberDurationLiteralInDurationContext, MatrixSelector],
|
|
187
|
+
fun: resolveDurations,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
path: [GroupingLabels],
|
|
191
|
+
fun: resolveLabelsForGrouping,
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
const LABEL_OP_MAP = new Map<number, LabelOperator>([
|
|
196
|
+
[EqlSingle, '='],
|
|
197
|
+
[EqlRegex, '=~'],
|
|
198
|
+
[Neq, '!='],
|
|
199
|
+
[NeqRegex, '!~'],
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
function getLabelOp(opNode: SyntaxNode): LabelOperator | null {
|
|
203
|
+
const opChild = opNode.firstChild;
|
|
204
|
+
if (opChild === null) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return LABEL_OP_MAP.get(opChild.type.id) ?? null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getLabel(labelMatcherNode: SyntaxNode, text: string): Label | null {
|
|
212
|
+
if (labelMatcherNode.type.id !== UnquotedLabelMatcher) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const nameNode = walk(labelMatcherNode, [['firstChild', LabelName]]);
|
|
217
|
+
|
|
218
|
+
if (nameNode === null) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const opNode = walk(nameNode, [['nextSibling', MatchOp]]);
|
|
223
|
+
if (opNode === null) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const op = getLabelOp(opNode);
|
|
228
|
+
if (op === null) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const valueNode = walk(labelMatcherNode, [['lastChild', StringLiteral]]);
|
|
233
|
+
|
|
234
|
+
if (valueNode === null) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const name = getNodeText(nameNode, text);
|
|
239
|
+
const value = parsePromQLStringLiteral(getNodeText(valueNode, text));
|
|
240
|
+
|
|
241
|
+
return { name, value, op };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function getLabels(labelMatchersNode: SyntaxNode, text: string): Label[] {
|
|
245
|
+
if (labelMatchersNode.type.id !== LabelMatchers) {
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const labelNodes = labelMatchersNode.getChildren(UnquotedLabelMatcher);
|
|
250
|
+
return labelNodes.map((ln) => getLabel(ln, text)).filter(notEmpty);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function getNodeChildren(node: SyntaxNode): SyntaxNode[] {
|
|
254
|
+
let child: SyntaxNode | null = node.firstChild;
|
|
255
|
+
const children: SyntaxNode[] = [];
|
|
256
|
+
while (child !== null) {
|
|
257
|
+
children.push(child);
|
|
258
|
+
child = child.nextSibling;
|
|
259
|
+
}
|
|
260
|
+
return children;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function getNodeInSubtree(node: SyntaxNode, typeId: NodeTypeId): SyntaxNode | null {
|
|
264
|
+
// first we try the current node
|
|
265
|
+
if (node.type.id === typeId) {
|
|
266
|
+
return node;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// then we try the children
|
|
270
|
+
const children = getNodeChildren(node);
|
|
271
|
+
for (const child of children) {
|
|
272
|
+
const n = getNodeInSubtree(child, typeId);
|
|
273
|
+
if (n !== null) {
|
|
274
|
+
return n;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function resolveLabelsForGrouping(node: SyntaxNode, text: string, _pos: number): Situation | null {
|
|
282
|
+
const aggrExpNode = walk(node, [
|
|
283
|
+
['parent', AggregateModifier],
|
|
284
|
+
['parent', AggregateExpr],
|
|
285
|
+
]);
|
|
286
|
+
if (aggrExpNode === null) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
const bodyNode = aggrExpNode.getChild(FunctionCallBody);
|
|
290
|
+
if (bodyNode === null) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const metricIdNode = getNodeInSubtree(bodyNode, Identifier);
|
|
295
|
+
if (metricIdNode === null) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const metricName = getNodeText(metricIdNode, text);
|
|
300
|
+
return {
|
|
301
|
+
type: 'IN_GROUPING',
|
|
302
|
+
metricName,
|
|
303
|
+
otherLabels: [],
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function resolveLabelMatcher(node: SyntaxNode, text: string, _pos: number): Situation | null {
|
|
308
|
+
// we can arrive here in two situation. `node` is either:
|
|
309
|
+
// - a StringNode (like in `{job="^"}`)
|
|
310
|
+
// - or an error node (like in `{job=^}`)
|
|
311
|
+
const inStringNode = !node.type.isError;
|
|
312
|
+
|
|
313
|
+
const parent = walk(node, [['parent', UnquotedLabelMatcher]]);
|
|
314
|
+
if (parent === null) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const labelNameNode = walk(parent, [['firstChild', LabelName]]);
|
|
319
|
+
if (labelNameNode === null) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const labelName = getNodeText(labelNameNode, text);
|
|
324
|
+
|
|
325
|
+
const labelMatchersNode = walk(parent, [['parent', LabelMatchers]]);
|
|
326
|
+
if (labelMatchersNode === null) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// now we need to find the other names
|
|
331
|
+
const allLabels = getLabels(labelMatchersNode, text);
|
|
332
|
+
|
|
333
|
+
// we need to remove "our" label from all-labels, if it is in there
|
|
334
|
+
const otherLabels = allLabels.filter((label) => label.name !== labelName);
|
|
335
|
+
|
|
336
|
+
const metricNameNode = walk(labelMatchersNode, [
|
|
337
|
+
['parent', VectorSelector],
|
|
338
|
+
['firstChild', Identifier],
|
|
339
|
+
]);
|
|
340
|
+
|
|
341
|
+
if (metricNameNode === null) {
|
|
342
|
+
// we are probably in a situation without a metric name
|
|
343
|
+
return {
|
|
344
|
+
type: 'IN_LABEL_SELECTOR_WITH_LABEL_NAME',
|
|
345
|
+
labelName,
|
|
346
|
+
betweenQuotes: inStringNode,
|
|
347
|
+
otherLabels,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const metricName = getNodeText(metricNameNode, text);
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
type: 'IN_LABEL_SELECTOR_WITH_LABEL_NAME',
|
|
355
|
+
metricName,
|
|
356
|
+
labelName,
|
|
357
|
+
betweenQuotes: inStringNode,
|
|
358
|
+
otherLabels,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function resolveTopLevel(): Situation {
|
|
363
|
+
return {
|
|
364
|
+
type: 'AT_ROOT',
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function resolveInFunction(): Situation {
|
|
369
|
+
return {
|
|
370
|
+
type: 'IN_FUNCTION',
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function resolveDurations(): Situation {
|
|
375
|
+
return {
|
|
376
|
+
type: 'IN_DURATION',
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function resolveLabelKeysWithEquals(node: SyntaxNode, text: string, pos: number): Situation | null {
|
|
381
|
+
// next false positive:
|
|
382
|
+
// `something{a="1"^}`
|
|
383
|
+
const child = walk(node, [['firstChild', UnquotedLabelMatcher]]);
|
|
384
|
+
if (child !== null) {
|
|
385
|
+
// means the label-matching part contains at least one label already.
|
|
386
|
+
//
|
|
387
|
+
// in this case, we will need to have a `,` character at the end,
|
|
388
|
+
// to be able to suggest adding the next label.
|
|
389
|
+
// the area between the end-of-the-child-node and the cursor-pos
|
|
390
|
+
// must contain a `,` in this case.
|
|
391
|
+
const textToCheck = text.slice(child.to, pos);
|
|
392
|
+
|
|
393
|
+
if (!textToCheck.includes(',')) {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const metricNameNode = walk(node, [
|
|
399
|
+
['parent', VectorSelector],
|
|
400
|
+
['firstChild', Identifier],
|
|
401
|
+
]);
|
|
402
|
+
|
|
403
|
+
const otherLabels = getLabels(node, text);
|
|
404
|
+
|
|
405
|
+
if (metricNameNode === null) {
|
|
406
|
+
// we are probably in a situation without a metric name.
|
|
407
|
+
return {
|
|
408
|
+
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
|
|
409
|
+
otherLabels,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const metricName = getNodeText(metricNameNode, text);
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
|
|
417
|
+
metricName,
|
|
418
|
+
otherLabels,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// we find the first error-node in the tree that is at the cursor-position.
|
|
423
|
+
// NOTE: this might be too slow, might need to optimize it
|
|
424
|
+
// (ideas: we do not need to go into every subtree, based on from/to)
|
|
425
|
+
// also, only go to places that are in the sub-tree of the node found
|
|
426
|
+
// by default by lezer. problem is, `next()` will go upward too,
|
|
427
|
+
// and we do not want to go higher than our node
|
|
428
|
+
function getErrorNode(tree: Tree, pos: number): SyntaxNode | null {
|
|
429
|
+
const cur = tree.cursorAt(pos);
|
|
430
|
+
while (true) {
|
|
431
|
+
if (cur.from === pos && cur.to === pos) {
|
|
432
|
+
const { node } = cur;
|
|
433
|
+
if (node.type.isError) {
|
|
434
|
+
return node;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (!cur.next()) {
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export function getSituation(text: string, pos: number): Situation | null {
|
|
446
|
+
// there is a special-case when we are at the start of writing text,
|
|
447
|
+
// so we handle that case first
|
|
448
|
+
|
|
449
|
+
if (text === '') {
|
|
450
|
+
return {
|
|
451
|
+
type: 'EMPTY',
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
PromQL
|
|
457
|
+
Expr
|
|
458
|
+
VectorSelector
|
|
459
|
+
LabelMatchers
|
|
460
|
+
*/
|
|
461
|
+
const tree = parser.parse(text);
|
|
462
|
+
|
|
463
|
+
// if the tree contains error, it is very probable that
|
|
464
|
+
// our node is one of those error-nodes.
|
|
465
|
+
// also, if there are errors, the node lezer finds us,
|
|
466
|
+
// might not be the best node.
|
|
467
|
+
// so first we check if there is an error-node at the cursor-position
|
|
468
|
+
const maybeErrorNode = getErrorNode(tree, pos);
|
|
469
|
+
|
|
470
|
+
const cur = maybeErrorNode != null ? maybeErrorNode.cursor() : tree.cursorAt(pos);
|
|
471
|
+
const currentNode = cur.node;
|
|
472
|
+
|
|
473
|
+
const ids = [cur.type.id];
|
|
474
|
+
while (cur.parent()) {
|
|
475
|
+
ids.push(cur.type.id);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
for (let resolver of RESOLVERS) {
|
|
479
|
+
// i do not use a foreach because i want to stop as soon
|
|
480
|
+
// as i find something
|
|
481
|
+
if (isPathMatch(resolver.path, ids)) {
|
|
482
|
+
return resolver.fun(currentNode, text, pos);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
|
|
490
|
+
return value !== null && value !== undefined;
|
|
491
|
+
}
|