@cloudcare/codemirror-dql 0.0.57 → 0.0.58
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/dist/cjs/complete/dql.terms.d.ts +6 -0
- package/dist/cjs/complete/dql.terms.js +60 -4
- package/dist/cjs/complete/dql.terms.js.map +1 -1
- package/dist/cjs/complete/hybrid.d.ts +2 -1
- package/dist/cjs/complete/hybrid.js +42 -5
- package/dist/cjs/complete/hybrid.js.map +1 -1
- package/dist/cjs/parser/parser.d.ts +16 -0
- package/dist/cjs/parser/parser.js +618 -30
- package/dist/cjs/parser/parser.js.map +1 -1
- package/dist/esm/complete/dql.terms.d.ts +6 -0
- package/dist/esm/complete/dql.terms.js +22 -1
- package/dist/esm/complete/dql.terms.js.map +1 -1
- package/dist/esm/complete/hybrid.d.ts +2 -1
- package/dist/esm/complete/hybrid.js +44 -7
- package/dist/esm/complete/hybrid.js.map +1 -1
- package/dist/esm/parser/parser.d.ts +16 -0
- package/dist/esm/parser/parser.js +581 -28
- package/dist/esm/parser/parser.js.map +1 -1
- package/package.json +3 -3
|
@@ -14,6 +14,7 @@ const queryClauseLabels = new Map([
|
|
|
14
14
|
['TargetClause', 'projection'],
|
|
15
15
|
['FilterClause', 'WHERE'],
|
|
16
16
|
['TimeClause', 'time range'],
|
|
17
|
+
['ShiftClause', 'SHIFT'],
|
|
17
18
|
['ByClause', 'BY'],
|
|
18
19
|
['HavingClause', 'HAVING'],
|
|
19
20
|
['OrderClause', 'ORDER BY'],
|
|
@@ -23,8 +24,142 @@ const queryClauseLabels = new Map([
|
|
|
23
24
|
['SLimitClause', 'SLIMIT'],
|
|
24
25
|
['SOffsetClause', 'SOFFSET'],
|
|
25
26
|
]);
|
|
26
|
-
const expressionParents = new Set(['BinaryExpr', 'Expr', 'HavingExpr', 'ProjectionExpr']);
|
|
27
|
+
const expressionParents = new Set(['BinaryExpr', 'Expr', 'HavingExpr', 'ProjectionExpr', 'CaseArithmeticExpr']);
|
|
27
28
|
const commaContextBoundaries = new Set(['CaseExpr', 'Query']);
|
|
29
|
+
const documentedFunctionArities = new Map([
|
|
30
|
+
...[
|
|
31
|
+
'abs',
|
|
32
|
+
'any',
|
|
33
|
+
'avg',
|
|
34
|
+
'bool',
|
|
35
|
+
'ceil',
|
|
36
|
+
'count',
|
|
37
|
+
'count_distinct',
|
|
38
|
+
'count_series',
|
|
39
|
+
'deriv',
|
|
40
|
+
'derivative',
|
|
41
|
+
'difference',
|
|
42
|
+
'distinct',
|
|
43
|
+
'field_values',
|
|
44
|
+
'first',
|
|
45
|
+
'float',
|
|
46
|
+
'floor',
|
|
47
|
+
'histogram_auto',
|
|
48
|
+
'increase',
|
|
49
|
+
'int',
|
|
50
|
+
'irate',
|
|
51
|
+
'last',
|
|
52
|
+
'last_row',
|
|
53
|
+
'length',
|
|
54
|
+
'log',
|
|
55
|
+
'log2',
|
|
56
|
+
'log10',
|
|
57
|
+
'lower',
|
|
58
|
+
'ltrim',
|
|
59
|
+
'max',
|
|
60
|
+
'md5',
|
|
61
|
+
'median',
|
|
62
|
+
'min',
|
|
63
|
+
'mode',
|
|
64
|
+
'non_negative_derivative',
|
|
65
|
+
'non_negative_difference',
|
|
66
|
+
'rate',
|
|
67
|
+
'rate_over_count',
|
|
68
|
+
'rate_over_sum',
|
|
69
|
+
'rtrim',
|
|
70
|
+
'set',
|
|
71
|
+
'spread',
|
|
72
|
+
'stddev',
|
|
73
|
+
'string',
|
|
74
|
+
'sum',
|
|
75
|
+
'trim',
|
|
76
|
+
'uint',
|
|
77
|
+
'unwrap',
|
|
78
|
+
'upper',
|
|
79
|
+
].map((name) => [name, { min: 1, max: 1 }]),
|
|
80
|
+
['bottom', { min: 2, max: 2 }],
|
|
81
|
+
['cidr', { min: 1, max: 2 }],
|
|
82
|
+
['collect', { min: 1, max: 2 }],
|
|
83
|
+
['collect_distinct', { min: 1, max: 2 }],
|
|
84
|
+
['concat', { min: 1, max: Number.POSITIVE_INFINITY }],
|
|
85
|
+
['count_filter', { min: 2, max: 2 }],
|
|
86
|
+
['default', { min: 2, max: 2 }],
|
|
87
|
+
['distinct_by_collapse', { min: 1, max: 2 }],
|
|
88
|
+
['drain', { min: 2, max: 3 }],
|
|
89
|
+
['exists', { min: 0, max: 0 }],
|
|
90
|
+
['fill', { min: 2, max: 2 }],
|
|
91
|
+
['histogram', { min: 4, max: 5 }],
|
|
92
|
+
['histogram_quantile', { min: 2, max: 2 }],
|
|
93
|
+
['match', { min: 1, max: 2 }],
|
|
94
|
+
['now', { min: 0, max: 0 }],
|
|
95
|
+
['query_string', { min: 1, max: 2 }],
|
|
96
|
+
['querystring', { min: 1, max: 2 }],
|
|
97
|
+
['re', { min: 1, max: 2 }],
|
|
98
|
+
['regexp', { min: 1, max: 2 }],
|
|
99
|
+
['regex', { min: 1, max: 2 }],
|
|
100
|
+
['regexp_extract', { min: 2, max: 3 }],
|
|
101
|
+
['regexp_extract_all', { min: 2, max: 3 }],
|
|
102
|
+
['regexp_replace', { min: 3, max: 3 }],
|
|
103
|
+
['round', { min: 1, max: 2 }],
|
|
104
|
+
['search', { min: 1, max: 2 }],
|
|
105
|
+
['substr', { min: 2, max: 3 }],
|
|
106
|
+
['top', { min: 2, max: 2 }],
|
|
107
|
+
['wildcard', { min: 1, max: 2 }],
|
|
108
|
+
]);
|
|
109
|
+
const stableShiftAggregateFunctions = new Set([
|
|
110
|
+
'any',
|
|
111
|
+
'avg',
|
|
112
|
+
'change_score',
|
|
113
|
+
'collect',
|
|
114
|
+
'collect_distinct',
|
|
115
|
+
'corr',
|
|
116
|
+
'count',
|
|
117
|
+
'count_distinct',
|
|
118
|
+
'count_filter',
|
|
119
|
+
'count_series',
|
|
120
|
+
'deriv',
|
|
121
|
+
'derivative',
|
|
122
|
+
'difference',
|
|
123
|
+
'ewma',
|
|
124
|
+
'first',
|
|
125
|
+
'histogram_quantile',
|
|
126
|
+
'increase',
|
|
127
|
+
'irate',
|
|
128
|
+
'last',
|
|
129
|
+
'last_row',
|
|
130
|
+
'mad_score',
|
|
131
|
+
'max',
|
|
132
|
+
'median',
|
|
133
|
+
'min',
|
|
134
|
+
'mode',
|
|
135
|
+
'moving_average',
|
|
136
|
+
'non_negative_derivative',
|
|
137
|
+
'non_negative_difference',
|
|
138
|
+
'p50',
|
|
139
|
+
'p75',
|
|
140
|
+
'p90',
|
|
141
|
+
'p95',
|
|
142
|
+
'p99',
|
|
143
|
+
'percentile',
|
|
144
|
+
'rate',
|
|
145
|
+
'rate_over_count',
|
|
146
|
+
'rate_over_sum',
|
|
147
|
+
'slope',
|
|
148
|
+
'spread',
|
|
149
|
+
'stddev',
|
|
150
|
+
'sum',
|
|
151
|
+
'zscore',
|
|
152
|
+
]);
|
|
153
|
+
const unstableShiftAggregateFunctions = new Set([
|
|
154
|
+
'distinct',
|
|
155
|
+
'distinct_by_collapse',
|
|
156
|
+
'drain',
|
|
157
|
+
'field_values',
|
|
158
|
+
'histogram',
|
|
159
|
+
'histogram_auto',
|
|
160
|
+
'schema',
|
|
161
|
+
]);
|
|
162
|
+
const caseScalarFunctions = new Set(['int', 'float', 'string', 'md5', 'lower', 'upper', 'trim', 'ltrim', 'rtrim', 'regexp_replace', 'length']);
|
|
28
163
|
export const dqlSyntaxContexts = {
|
|
29
164
|
DQL: 'DQL query',
|
|
30
165
|
DQLConditions: 'DQL condition',
|
|
@@ -60,8 +195,15 @@ export const dqlSyntaxContexts = {
|
|
|
60
195
|
OuterArgItem: 'outer function argument',
|
|
61
196
|
Queted: 'quoted query delimiter',
|
|
62
197
|
EvalCallBody: 'eval function arguments',
|
|
198
|
+
EvalArgItem: 'eval expression argument',
|
|
199
|
+
EvalExpression: 'eval expression',
|
|
200
|
+
EvalAtom: 'eval expression value',
|
|
201
|
+
EvalFunctionCall: 'eval function call',
|
|
202
|
+
EvalFunctionCallBody: 'eval function arguments',
|
|
203
|
+
EvalFunctionArg: 'eval function argument',
|
|
63
204
|
FunctionCallBody: 'function arguments',
|
|
64
205
|
ArgItem: 'function argument',
|
|
206
|
+
EvalFieldReference: 'eval field reference',
|
|
65
207
|
InValue: 'argument value list',
|
|
66
208
|
SingleExpr: 'expression value',
|
|
67
209
|
Expr: 'expression',
|
|
@@ -78,6 +220,8 @@ export const dqlSyntaxContexts = {
|
|
|
78
220
|
ProjectionFieldFragment: 'projection field fragment',
|
|
79
221
|
ProjectionField: 'projection field',
|
|
80
222
|
SingleFieldExpr: 'projection expression value',
|
|
223
|
+
ShiftExpr: 'SHIFT expression',
|
|
224
|
+
ShiftOperand: 'SHIFT operand',
|
|
81
225
|
ProjectionExpr: 'projection expression',
|
|
82
226
|
AsExpr: 'alias',
|
|
83
227
|
FilterClause: 'filter clause',
|
|
@@ -91,10 +235,10 @@ export const dqlSyntaxContexts = {
|
|
|
91
235
|
CaseCondition: 'CASE condition',
|
|
92
236
|
CaseFilterExpr: 'CASE filter expression',
|
|
93
237
|
CasePredicate: 'CASE predicate',
|
|
238
|
+
CaseComparableValue: 'CASE comparison value',
|
|
94
239
|
CaseValue: 'CASE value',
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
CaseFunctionCallBody: 'CASE function arguments',
|
|
240
|
+
CaseArithmeticAtom: 'CASE arithmetic value',
|
|
241
|
+
CaseArithmeticExpr: 'CASE arithmetic expression',
|
|
98
242
|
FilterFragmentExpr: 'filter fragment',
|
|
99
243
|
ComparisonExpr: 'comparison expression',
|
|
100
244
|
LogicalFragmentExpr: 'logical filter expression',
|
|
@@ -105,6 +249,7 @@ export const dqlSyntaxContexts = {
|
|
|
105
249
|
FilterExpr: 'filter expression',
|
|
106
250
|
BinaryExpr: 'binary expression',
|
|
107
251
|
TimeClause: 'time range clause',
|
|
252
|
+
ShiftClause: 'SHIFT clause',
|
|
108
253
|
TimeRange: 'time range',
|
|
109
254
|
TimeExpr: 'time range value',
|
|
110
255
|
ByClause: 'BY clause',
|
|
@@ -124,6 +269,7 @@ export const dqlSyntaxContexts = {
|
|
|
124
269
|
};
|
|
125
270
|
const functionCallBodyParents = new Set([
|
|
126
271
|
'FunctionCallBody',
|
|
272
|
+
'EvalFunctionCallBody',
|
|
127
273
|
'EvalCallBody',
|
|
128
274
|
'OuterFunctionCallBody',
|
|
129
275
|
'RawOuterFunctionCallBody',
|
|
@@ -132,7 +278,6 @@ const functionCallBodyParents = new Set([
|
|
|
132
278
|
'FillOuterFunctionCallBody',
|
|
133
279
|
'VariadicOuterFunctionCallBody',
|
|
134
280
|
'RollupFunctionCallBody',
|
|
135
|
-
'CaseFunctionCallBody',
|
|
136
281
|
]);
|
|
137
282
|
export class Parser {
|
|
138
283
|
constructor(state, deadline = Date.now() + analysisBudgetMs) {
|
|
@@ -149,6 +294,7 @@ export class Parser {
|
|
|
149
294
|
this.quotedQueryDelimiterInfo = undefined;
|
|
150
295
|
this.caseResultDiagnostics = undefined;
|
|
151
296
|
this.caseResultRecoveryErrors = new Set();
|
|
297
|
+
this.caseShiftPresence = new Map();
|
|
152
298
|
}
|
|
153
299
|
getDiagnostics() {
|
|
154
300
|
const seen = new Set();
|
|
@@ -401,7 +547,7 @@ export class Parser {
|
|
|
401
547
|
this.appendDiagnostics(diagnostics);
|
|
402
548
|
}
|
|
403
549
|
describeError(node) {
|
|
404
|
-
var _a;
|
|
550
|
+
var _a, _b, _c;
|
|
405
551
|
const delimiterIssue = this.getDelimiterIssue();
|
|
406
552
|
if (delimiterIssue && delimiterIssue.kind !== 'unclosed' && this.isRelatedDelimiterError(node, delimiterIssue)) {
|
|
407
553
|
return null;
|
|
@@ -483,6 +629,26 @@ export class Parser {
|
|
|
483
629
|
}
|
|
484
630
|
return this.makeDiagnostic(node, 'expected "AS" before projection alias', group);
|
|
485
631
|
}
|
|
632
|
+
const projectionField = (_a = this.findAncestor(parent, 'ProjectionField')) !== null && _a !== void 0 ? _a : this.findAncestor(parent, 'ProjectionFieldFragment');
|
|
633
|
+
if (projectionField) {
|
|
634
|
+
const shiftError = this.errorDescendantsWithText(projectionField, 'shift').find((error) => error.from <= node.from);
|
|
635
|
+
if (shiftError && this.containsNode(projectionField, 'ShiftExpr')) {
|
|
636
|
+
return this.makeDiagnosticAt(shiftError.from, shiftError.to, 'SHIFT expressions cannot be nested', `shift-nested-syntax:${projectionField.from}`);
|
|
637
|
+
}
|
|
638
|
+
if (shiftError) {
|
|
639
|
+
const rawOperand = this.state.sliceDoc(projectionField.from, shiftError.from);
|
|
640
|
+
const leading = rawOperand.length - rawOperand.trimStart().length;
|
|
641
|
+
const trailing = rawOperand.length - rawOperand.trimEnd().length;
|
|
642
|
+
return this.makeDiagnosticAt(projectionField.from + leading, Math.max(projectionField.from + leading + 1, projectionField.from + rawOperand.length - trailing), 'SHIFT operand must be a stable aggregate or subquery measure', `shift-operand-syntax:${projectionField.from}`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
const invalidShiftContext = (_b = this.findAncestor(parent, 'FilterClause')) !== null && _b !== void 0 ? _b : this.findAncestor(parent, 'HavingClause');
|
|
646
|
+
if (invalidShiftContext) {
|
|
647
|
+
const shiftError = this.errorDescendantsWithText(invalidShiftContext, 'shift').find((error) => error.from <= node.from);
|
|
648
|
+
if (shiftError) {
|
|
649
|
+
return this.makeDiagnosticAt(shiftError.from, shiftError.to, 'expression-level SHIFT is only allowed in the projection', `shift-context-syntax:${invalidShiftContext.from}`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
486
652
|
if (parentName === 'OuterFunctionCall') {
|
|
487
653
|
return this.makeUnexpectedTokenDiagnostic(node, dqlSyntaxContexts.OuterFunctionCall, group);
|
|
488
654
|
}
|
|
@@ -507,7 +673,7 @@ export class Parser {
|
|
|
507
673
|
}
|
|
508
674
|
return this.makeUnexpectedTokenDiagnostic(node, 'function call', group);
|
|
509
675
|
}
|
|
510
|
-
if (parentName === 'ArgItem' && node.from === node.to) {
|
|
676
|
+
if ((parentName === 'ArgItem' || parentName === 'EvalFunctionArg') && node.from === node.to) {
|
|
511
677
|
if ((previous === null || previous === void 0 ? void 0 : previous.name) === 'EqlSingle') {
|
|
512
678
|
return this.makeDiagnostic(node, 'function argument value expected after "="', group);
|
|
513
679
|
}
|
|
@@ -553,19 +719,51 @@ export class Parser {
|
|
|
553
719
|
if (parentName === 'ByExpr' && errorText === ',') {
|
|
554
720
|
return this.makeDiagnostic(node, 'grouping field expected before ","', group);
|
|
555
721
|
}
|
|
556
|
-
if (expressionParents.has(parentName)) {
|
|
557
|
-
if (previous && this.isOperator(previous)) {
|
|
558
|
-
return this.makeDiagnostic(node, `right-hand expression expected after "${previousText}"`, group);
|
|
559
|
-
}
|
|
560
|
-
return this.makeUnexpectedTokenDiagnostic(node, 'expression', group);
|
|
561
|
-
}
|
|
562
722
|
const caseWhenClause = this.findAncestor(parent, 'CaseWhenClause');
|
|
563
723
|
if (caseWhenClause) {
|
|
724
|
+
const firstCaseError = this.firstErrorFrom(caseWhenClause);
|
|
564
725
|
const missingCondition = /^\s*when\s+(then)\b/i.exec(this.maskLineComments(this.nodeText(caseWhenClause)));
|
|
565
726
|
if (missingCondition) {
|
|
566
|
-
|
|
567
|
-
|
|
727
|
+
if (node.from === firstCaseError) {
|
|
728
|
+
const thenOffset = missingCondition.index + missingCondition[0].toLowerCase().lastIndexOf('then');
|
|
729
|
+
return this.makeDiagnosticAt(caseWhenClause.from + thenOffset, caseWhenClause.from + thenOffset + 4, 'CASE condition expected after "WHEN"', this.caseGroup(caseWhenClause));
|
|
730
|
+
}
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
if (!caseWhenClause.getChild('Then')) {
|
|
734
|
+
if (node.from === firstCaseError) {
|
|
735
|
+
return this.makeDiagnostic(node, 'expected "THEN" after CASE condition', this.caseGroup(caseWhenClause));
|
|
736
|
+
}
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
const missingResult = /\bthen\s+(else)\b/i.exec(this.maskLineComments(this.nodeText(caseWhenClause)));
|
|
740
|
+
if (missingResult) {
|
|
741
|
+
if (node.from === firstCaseError) {
|
|
742
|
+
const keyword = missingResult[1];
|
|
743
|
+
const keywordOffset = missingResult.index + missingResult[0].toLowerCase().lastIndexOf(keyword.toLowerCase());
|
|
744
|
+
return this.makeDiagnosticAt(caseWhenClause.from + keywordOffset, caseWhenClause.from + keywordOffset + keyword.length, 'CASE result expected after "THEN"', this.caseGroup(caseWhenClause));
|
|
745
|
+
}
|
|
746
|
+
return errorText ? this.makeUnexpectedTokenDiagnostic(node, dqlSyntaxContexts.CaseExpr, group) : null;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
if (parentName === 'EvalAtom' && errorText === '=' && (previous === null || previous === void 0 ? void 0 : previous.name) === 'Identifier') {
|
|
750
|
+
return this.makeDiagnostic(node, 'function argument value expected after "="', group);
|
|
751
|
+
}
|
|
752
|
+
if (parentName === 'EvalExpression' && node.from === node.to && (previous === null || previous === void 0 ? void 0 : previous.name) === 'EvalAtom' && (next === null || next === void 0 ? void 0 : next.name) === 'EvalAtom') {
|
|
753
|
+
return this.makeDiagnostic(node, 'expected "," between function arguments', group);
|
|
754
|
+
}
|
|
755
|
+
if (parentName === 'CaseArithmeticExpr' && errorText && !(previous === null || previous === void 0 ? void 0 : previous.type.isError)) {
|
|
756
|
+
return this.makeUnexpectedTokenDiagnostic(node, dqlSyntaxContexts.CaseExpr, group);
|
|
757
|
+
}
|
|
758
|
+
if (parentName === 'ShiftExpr' || parentName === 'ShiftClause') {
|
|
759
|
+
const message = errorText ? 'SHIFT duration must be a positive fixed duration' : 'duration expected after "SHIFT"';
|
|
760
|
+
return this.makeDiagnostic(node, message, `shift-duration:${parent.from}`);
|
|
761
|
+
}
|
|
762
|
+
if (expressionParents.has(parentName)) {
|
|
763
|
+
if (previous && this.isOperator(previous)) {
|
|
764
|
+
return this.makeDiagnostic(node, `right-hand expression expected after "${previousText}"`, group);
|
|
568
765
|
}
|
|
766
|
+
return this.makeUnexpectedTokenDiagnostic(node, 'expression', group);
|
|
569
767
|
}
|
|
570
768
|
if (parentName === 'CaseCondition') {
|
|
571
769
|
return this.makeDiagnostic(node, 'expected "THEN" after CASE condition', this.caseGroup(parent));
|
|
@@ -614,7 +812,7 @@ export class Parser {
|
|
|
614
812
|
}
|
|
615
813
|
if (parentName === 'RollupIdentifier') {
|
|
616
814
|
const timeClause = this.findAncestor(parent, 'TimeClause');
|
|
617
|
-
let child = (
|
|
815
|
+
let child = (_c = timeClause === null || timeClause === void 0 ? void 0 : timeClause.firstChild) !== null && _c !== void 0 ? _c : null;
|
|
618
816
|
while (child) {
|
|
619
817
|
if (child.type.isError)
|
|
620
818
|
return null;
|
|
@@ -1154,6 +1352,7 @@ export class Parser {
|
|
|
1154
1352
|
const contexts = {
|
|
1155
1353
|
Projection: 'projection item',
|
|
1156
1354
|
FunctionCallBody: 'function argument',
|
|
1355
|
+
EvalFunctionCallBody: 'function argument',
|
|
1157
1356
|
EvalCallBody: 'function argument',
|
|
1158
1357
|
OuterFunctionCallBody: 'function argument',
|
|
1159
1358
|
RawOuterFunctionCallBody: 'function argument',
|
|
@@ -1162,7 +1361,6 @@ export class Parser {
|
|
|
1162
1361
|
FillOuterFunctionCallBody: 'function argument',
|
|
1163
1362
|
VariadicOuterFunctionCallBody: 'function argument',
|
|
1164
1363
|
RollupFunctionCallBody: 'function argument',
|
|
1165
|
-
CaseFunctionCallBody: 'function argument',
|
|
1166
1364
|
InValue: 'argument value',
|
|
1167
1365
|
InFieldValue: 'filter value',
|
|
1168
1366
|
IndexExpr: 'namespace index',
|
|
@@ -1347,6 +1545,20 @@ export class Parser {
|
|
|
1347
1545
|
}
|
|
1348
1546
|
return false;
|
|
1349
1547
|
}
|
|
1548
|
+
firstErrorFrom(node) {
|
|
1549
|
+
let first = null;
|
|
1550
|
+
const visit = (current) => {
|
|
1551
|
+
let child = current.firstChild;
|
|
1552
|
+
while (child) {
|
|
1553
|
+
if (child.type.isError && (first === null || child.from < first))
|
|
1554
|
+
first = child.from;
|
|
1555
|
+
visit(child);
|
|
1556
|
+
child = child.nextSibling;
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
visit(node);
|
|
1560
|
+
return first;
|
|
1561
|
+
}
|
|
1350
1562
|
looksLikeProjectionAfterMissingColon(node) {
|
|
1351
1563
|
const querySource = node.getChild('QuerySource');
|
|
1352
1564
|
return Boolean(querySource && this.containsError(querySource) && !this.containsNode(querySource, 'DColon'));
|
|
@@ -2296,6 +2508,8 @@ export class Parser {
|
|
|
2296
2508
|
case 'Query':
|
|
2297
2509
|
case 'QueryFragment':
|
|
2298
2510
|
this.checkDuplicateQueryClauses(node);
|
|
2511
|
+
this.checkQueryShiftPlacement(node);
|
|
2512
|
+
this.checkExpressionShiftOffsets(node);
|
|
2299
2513
|
break;
|
|
2300
2514
|
case 'ShowExpr':
|
|
2301
2515
|
this.checkDuplicateShowClauses(node);
|
|
@@ -2304,6 +2518,19 @@ export class Parser {
|
|
|
2304
2518
|
case 'TimeRange':
|
|
2305
2519
|
this.checkTimeRangeShape(node);
|
|
2306
2520
|
break;
|
|
2521
|
+
case 'ShiftClause':
|
|
2522
|
+
this.checkShiftDuration(node);
|
|
2523
|
+
break;
|
|
2524
|
+
case 'ShiftExpr':
|
|
2525
|
+
this.checkShiftDuration(node);
|
|
2526
|
+
this.checkShiftExpression(node);
|
|
2527
|
+
break;
|
|
2528
|
+
case 'EvalExpr':
|
|
2529
|
+
this.checkEvalReferences(node);
|
|
2530
|
+
break;
|
|
2531
|
+
case 'EvalFieldReference':
|
|
2532
|
+
this.checkEvalFieldReferenceContext(node);
|
|
2533
|
+
break;
|
|
2307
2534
|
case 'RollupIdentifier':
|
|
2308
2535
|
this.checkBareRollup(node);
|
|
2309
2536
|
break;
|
|
@@ -2311,7 +2538,10 @@ export class Parser {
|
|
|
2311
2538
|
this.checkRollupFunctionCall(node);
|
|
2312
2539
|
break;
|
|
2313
2540
|
case 'FunctionCall':
|
|
2541
|
+
case 'EvalFunctionCall':
|
|
2314
2542
|
this.checkFunctionCall(node);
|
|
2543
|
+
if (node.name === 'FunctionCall')
|
|
2544
|
+
this.checkCaseFunction(node);
|
|
2315
2545
|
break;
|
|
2316
2546
|
case 'OuterFunctionCall':
|
|
2317
2547
|
this.checkOuterFunctionCall(node);
|
|
@@ -2331,9 +2561,6 @@ export class Parser {
|
|
|
2331
2561
|
case 'FieldName':
|
|
2332
2562
|
this.checkFieldName(node);
|
|
2333
2563
|
break;
|
|
2334
|
-
case 'CaseFunctionIdentifier':
|
|
2335
|
-
this.checkCaseFunction(node);
|
|
2336
|
-
break;
|
|
2337
2564
|
case 'CaseExpr':
|
|
2338
2565
|
this.checkCaseResults(node);
|
|
2339
2566
|
break;
|
|
@@ -2366,6 +2593,168 @@ export class Parser {
|
|
|
2366
2593
|
child = child.nextSibling;
|
|
2367
2594
|
}
|
|
2368
2595
|
}
|
|
2596
|
+
checkQueryShiftPlacement(query) {
|
|
2597
|
+
var _a;
|
|
2598
|
+
const clauses = [];
|
|
2599
|
+
let child = query.firstChild;
|
|
2600
|
+
while (child) {
|
|
2601
|
+
if (child.name === 'QueryClause' || child.name === 'QueryFragmentClause') {
|
|
2602
|
+
const clause = child.firstChild;
|
|
2603
|
+
if (clause)
|
|
2604
|
+
clauses.push(clause);
|
|
2605
|
+
}
|
|
2606
|
+
child = child.nextSibling;
|
|
2607
|
+
}
|
|
2608
|
+
const shifts = clauses.filter((clause) => clause.name === 'ShiftClause');
|
|
2609
|
+
const timeRanges = clauses.filter((clause) => clause.name === 'TimeClause');
|
|
2610
|
+
if (shifts.length !== 1 || timeRanges.length > 1)
|
|
2611
|
+
return;
|
|
2612
|
+
const shift = shifts[0];
|
|
2613
|
+
const shiftIndex = clauses.indexOf(shift);
|
|
2614
|
+
const laterTarget = clauses.slice(shiftIndex + 1).find((clause) => clause.name === 'TargetClause');
|
|
2615
|
+
if (laterTarget) {
|
|
2616
|
+
this.pushSemanticDiagnostic(shift, '"SHIFT" must appear after the projection', `shift-placement:${shift.from}`);
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
const laterFilter = clauses.slice(shiftIndex + 1).find((clause) => clause.name === 'FilterClause');
|
|
2620
|
+
if (laterFilter) {
|
|
2621
|
+
this.pushSemanticDiagnostic(shift, '"SHIFT" must appear after "WHERE"', `shift-placement:${shift.from}`);
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
if (timeRanges.length === 1) {
|
|
2625
|
+
const timeIndex = clauses.indexOf(timeRanges[0]);
|
|
2626
|
+
if (shiftIndex !== timeIndex + 1) {
|
|
2627
|
+
this.pushSemanticDiagnostic(shift, '"SHIFT" must immediately follow the time range', `shift-placement:${shift.from}`);
|
|
2628
|
+
}
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
const outputClause = clauses
|
|
2632
|
+
.slice(0, shiftIndex)
|
|
2633
|
+
.reverse()
|
|
2634
|
+
.find((clause) => ['ByClause', 'HavingClause', 'OrderClause', 'SOrderClause', 'LimitClause', 'OffsetClause', 'SLimitClause', 'SOffsetClause'].includes(clause.name));
|
|
2635
|
+
if (outputClause) {
|
|
2636
|
+
const label = (_a = queryClauseLabels.get(outputClause.name)) !== null && _a !== void 0 ? _a : outputClause.name;
|
|
2637
|
+
this.pushSemanticDiagnostic(shift, `"SHIFT" must appear before "${label}"`, `shift-placement:${shift.from}`);
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
checkExpressionShiftOffsets(query) {
|
|
2641
|
+
const offsets = new Set();
|
|
2642
|
+
let overflowReported = false;
|
|
2643
|
+
const visit = (node) => {
|
|
2644
|
+
if (overflowReported || Date.now() >= this.deadline)
|
|
2645
|
+
return;
|
|
2646
|
+
if (node !== query && node.name === 'Query')
|
|
2647
|
+
return;
|
|
2648
|
+
if (node.name === 'ShiftExpr') {
|
|
2649
|
+
const duration = node.getChild('Duration');
|
|
2650
|
+
if (duration) {
|
|
2651
|
+
const rawOffset = this.nodeText(duration).toLowerCase();
|
|
2652
|
+
const milliseconds = this.fixedDurationMilliseconds(rawOffset);
|
|
2653
|
+
const offset = milliseconds !== null && Number.isSafeInteger(milliseconds) ? `${milliseconds}ms` : rawOffset;
|
|
2654
|
+
if (!offsets.has(offset)) {
|
|
2655
|
+
offsets.add(offset);
|
|
2656
|
+
if (offsets.size > 16) {
|
|
2657
|
+
this.pushSemanticDiagnostic(duration, 'a SELECT can use at most 16 distinct SHIFT offsets', `shift-offsets:${query.from}`);
|
|
2658
|
+
overflowReported = true;
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
let child = node.firstChild;
|
|
2665
|
+
while (child) {
|
|
2666
|
+
visit(child);
|
|
2667
|
+
child = child.nextSibling;
|
|
2668
|
+
}
|
|
2669
|
+
};
|
|
2670
|
+
visit(query);
|
|
2671
|
+
}
|
|
2672
|
+
checkShiftDuration(shift) {
|
|
2673
|
+
const duration = shift.getChild('Duration');
|
|
2674
|
+
if (!duration || this.isPositiveFixedDuration(this.nodeText(duration)))
|
|
2675
|
+
return;
|
|
2676
|
+
this.pushSemanticDiagnostic(duration, 'SHIFT duration must be a positive fixed duration', `shift-duration:${shift.from}`);
|
|
2677
|
+
}
|
|
2678
|
+
checkShiftExpression(shift) {
|
|
2679
|
+
var _a, _b;
|
|
2680
|
+
const nested = this.findAncestor(shift.parent, 'ShiftExpr');
|
|
2681
|
+
if (nested) {
|
|
2682
|
+
this.pushSemanticDiagnostic(shift, 'SHIFT expressions cannot be nested', `shift-nested:${shift.from}`);
|
|
2683
|
+
return;
|
|
2684
|
+
}
|
|
2685
|
+
if (!this.findAncestor(shift.parent, 'TargetClause') && !this.findAncestor(shift.parent, 'ProjectionFragment')) {
|
|
2686
|
+
this.pushSemanticDiagnostic(shift, 'expression-level SHIFT is only allowed in the projection', `shift-context:${shift.from}`);
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
const operand = shift.getChild('ShiftOperand');
|
|
2690
|
+
if (!operand)
|
|
2691
|
+
return;
|
|
2692
|
+
const calls = this.shiftOperandFunctionCalls(operand);
|
|
2693
|
+
const unstable = calls.find((call) => {
|
|
2694
|
+
const name = this.functionCallName(call);
|
|
2695
|
+
return name !== null && unstableShiftAggregateFunctions.has(name);
|
|
2696
|
+
});
|
|
2697
|
+
if (unstable) {
|
|
2698
|
+
const name = (_a = this.functionCallName(unstable)) !== null && _a !== void 0 ? _a : 'function';
|
|
2699
|
+
const identifier = (_b = unstable.getChild('FunctionIdentifier')) !== null && _b !== void 0 ? _b : unstable;
|
|
2700
|
+
this.pushSemanticDiagnostic(identifier, `function "${name}" cannot be used as a SHIFT operand`, `shift-operand:${shift.from}`);
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
const hasStableAggregate = calls.some((call) => {
|
|
2704
|
+
const name = this.functionCallName(call);
|
|
2705
|
+
return name !== null && stableShiftAggregateFunctions.has(name);
|
|
2706
|
+
});
|
|
2707
|
+
if (!hasStableAggregate) {
|
|
2708
|
+
this.pushSemanticDiagnostic(operand, 'SHIFT operand must be a stable aggregate or subquery measure', `shift-operand:${shift.from}`);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
checkEvalFieldReferenceContext(reference) {
|
|
2712
|
+
if (this.findAncestor(reference.parent, 'EvalExpr'))
|
|
2713
|
+
return;
|
|
2714
|
+
this.pushSemanticDiagnostic(reference, 'qualified field references are only supported inside eval', `eval-field-context:${reference.from}`);
|
|
2715
|
+
}
|
|
2716
|
+
checkEvalReferences(evalExpr) {
|
|
2717
|
+
const body = evalExpr.getChild('EvalCallBody');
|
|
2718
|
+
if (!body)
|
|
2719
|
+
return;
|
|
2720
|
+
const aliases = new Set();
|
|
2721
|
+
for (const argument of this.directChildren(body, 'OuterArgItem')) {
|
|
2722
|
+
const identifier = argument.getChild('Identifier');
|
|
2723
|
+
if (!identifier || !argument.getChild('EqlSingle'))
|
|
2724
|
+
continue;
|
|
2725
|
+
if (!argument.getChild('Query') && !argument.getChild('OuterFunctionCall'))
|
|
2726
|
+
continue;
|
|
2727
|
+
const name = this.nodeText(identifier).toLowerCase();
|
|
2728
|
+
if (aliases.has(name)) {
|
|
2729
|
+
this.pushSemanticDiagnostic(identifier, `duplicate eval subquery "${name}"`, `eval-alias-duplicate:${identifier.from}`);
|
|
2730
|
+
continue;
|
|
2731
|
+
}
|
|
2732
|
+
aliases.add(name);
|
|
2733
|
+
}
|
|
2734
|
+
const expression = body.getChild('EvalArgItem');
|
|
2735
|
+
if (!expression)
|
|
2736
|
+
return;
|
|
2737
|
+
for (const reference of this.descendantsNamed(expression, 'EvalFieldReference')) {
|
|
2738
|
+
const alias = reference.getChild('Identifier');
|
|
2739
|
+
if (!alias)
|
|
2740
|
+
continue;
|
|
2741
|
+
const name = this.nodeText(alias).toLowerCase();
|
|
2742
|
+
if (!aliases.has(name)) {
|
|
2743
|
+
this.pushSemanticDiagnostic(alias, `unknown eval subquery "${name}" in field reference`, `eval-field:${reference.from}`);
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
if (aliases.size > 1) {
|
|
2747
|
+
for (const atom of this.descendantsNamed(expression, 'EvalAtom')) {
|
|
2748
|
+
const identifier = atom.getChild('Identifier');
|
|
2749
|
+
if (!identifier)
|
|
2750
|
+
continue;
|
|
2751
|
+
const name = this.nodeText(identifier).toLowerCase();
|
|
2752
|
+
if (!aliases.has(name)) {
|
|
2753
|
+
this.pushSemanticDiagnostic(identifier, `unknown eval subquery "${name}" in expression`, `eval-alias:${identifier.from}`);
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2369
2758
|
checkDuplicateShowClauses(show) {
|
|
2370
2759
|
const seen = new Set();
|
|
2371
2760
|
let child = show.firstChild;
|
|
@@ -2481,12 +2870,44 @@ export class Parser {
|
|
|
2481
2870
|
}
|
|
2482
2871
|
}
|
|
2483
2872
|
checkFunctionCall(call) {
|
|
2873
|
+
var _a;
|
|
2484
2874
|
const identifier = call.getChild('FunctionIdentifier');
|
|
2485
|
-
const body = call.getChild('FunctionCallBody');
|
|
2875
|
+
const body = (_a = call.getChild('FunctionCallBody')) !== null && _a !== void 0 ? _a : call.getChild('EvalFunctionCallBody');
|
|
2486
2876
|
if (!identifier || !body)
|
|
2487
2877
|
return;
|
|
2488
2878
|
const name = this.nodeText(identifier).toLowerCase();
|
|
2489
|
-
const
|
|
2879
|
+
const argumentName = body.name === 'EvalFunctionCallBody' ? 'EvalFunctionArg' : 'ArgItem';
|
|
2880
|
+
const args = this.directChildren(body, argumentName);
|
|
2881
|
+
const arity = documentedFunctionArities.get(name);
|
|
2882
|
+
if (arity && (args.length < arity.min || args.length > arity.max)) {
|
|
2883
|
+
this.pushSemanticDiagnostic(body, this.functionArityMessage(name, arity), `function-arity:${call.from}`);
|
|
2884
|
+
return;
|
|
2885
|
+
}
|
|
2886
|
+
if ((name === 'count_filter' || name === 'distinct_by_collapse') && args.length === 2 && !args[1].getChild('InValue')) {
|
|
2887
|
+
this.pushSemanticDiagnostic(args[1], `function "${name}" expects a value list as its second argument`, `function:${call.from}`);
|
|
2888
|
+
return;
|
|
2889
|
+
}
|
|
2890
|
+
if (name === 'histogram_quantile' && args.length === 2) {
|
|
2891
|
+
const quantile = this.numericValue(args[1]);
|
|
2892
|
+
if (quantile === null || quantile < 0 || quantile > 1) {
|
|
2893
|
+
this.pushSemanticDiagnostic(args[1], 'function "histogram_quantile" expects a quantile in range [0, 1]', `function:${call.from}`);
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
if (name === 'drain' && args.length >= 2) {
|
|
2898
|
+
const threshold = this.numericValue(args[1]);
|
|
2899
|
+
if (threshold === null || threshold <= 0 || threshold > 1) {
|
|
2900
|
+
this.pushSemanticDiagnostic(args[1], 'function "drain" expects a similarity threshold in range (0, 1]', `function:${call.from}`);
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
if (args.length === 3) {
|
|
2904
|
+
const maxClusters = this.numericValue(args[2]);
|
|
2905
|
+
if (maxClusters === null || !Number.isInteger(maxClusters) || maxClusters < 1 || maxClusters > 10000) {
|
|
2906
|
+
this.pushSemanticDiagnostic(args[2], 'function "drain" expects max_clusters to be an integer in range [1, 10000]', `function:${call.from}`);
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2490
2911
|
if (name === 'ewma') {
|
|
2491
2912
|
const alpha = args.length === 2 ? this.numericValue(args[1]) : null;
|
|
2492
2913
|
if (alpha === null || alpha <= 0 || alpha > 1) {
|
|
@@ -2515,6 +2936,17 @@ export class Parser {
|
|
|
2515
2936
|
this.pushSemanticDiagnostic(body, `function "${name}" expects exactly one input field`, `function:${call.from}`);
|
|
2516
2937
|
}
|
|
2517
2938
|
}
|
|
2939
|
+
functionArityMessage(name, arity) {
|
|
2940
|
+
if (arity.min === arity.max) {
|
|
2941
|
+
if (arity.min === 0)
|
|
2942
|
+
return `function "${name}" does not accept arguments`;
|
|
2943
|
+
return `function "${name}" expects exactly ${arity.min} ${arity.min === 1 ? 'argument' : 'arguments'}`;
|
|
2944
|
+
}
|
|
2945
|
+
if (!Number.isFinite(arity.max)) {
|
|
2946
|
+
return `function "${name}" expects at least ${arity.min} ${arity.min === 1 ? 'argument' : 'arguments'}`;
|
|
2947
|
+
}
|
|
2948
|
+
return `function "${name}" expects between ${arity.min} and ${arity.max} arguments`;
|
|
2949
|
+
}
|
|
2518
2950
|
checkOuterFunctionCall(call) {
|
|
2519
2951
|
const identifier = call.firstChild;
|
|
2520
2952
|
if (!identifier || identifier.name === 'FunctionIdentifier')
|
|
@@ -2613,11 +3045,36 @@ export class Parser {
|
|
|
2613
3045
|
this.pushSemanticDiagnostic(field, 'field names containing "-" must be enclosed in backticks', `field-name:${field.from}`);
|
|
2614
3046
|
}
|
|
2615
3047
|
}
|
|
2616
|
-
checkCaseFunction(
|
|
3048
|
+
checkCaseFunction(call) {
|
|
3049
|
+
const caseExpr = this.findAncestor(call.parent, 'CaseExpr');
|
|
3050
|
+
if (!caseExpr)
|
|
3051
|
+
return;
|
|
3052
|
+
const shiftExpr = this.findAncestor(call.parent, 'ShiftExpr');
|
|
3053
|
+
if (shiftExpr && shiftExpr.from >= caseExpr.from && shiftExpr.to <= caseExpr.to)
|
|
3054
|
+
return;
|
|
3055
|
+
const identifier = call.getChild('FunctionIdentifier');
|
|
3056
|
+
const body = call.getChild('FunctionCallBody');
|
|
3057
|
+
if (!identifier || !body)
|
|
3058
|
+
return;
|
|
2617
3059
|
const name = this.nodeText(identifier).toLowerCase();
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
3060
|
+
const args = this.directChildren(body, 'ArgItem');
|
|
3061
|
+
const arity = documentedFunctionArities.get(name);
|
|
3062
|
+
if (arity && (args.length < arity.min || args.length > arity.max))
|
|
3063
|
+
return;
|
|
3064
|
+
if (caseScalarFunctions.has(name))
|
|
3065
|
+
return;
|
|
3066
|
+
if (stableShiftAggregateFunctions.has(name) && this.caseContainsShift(caseExpr))
|
|
3067
|
+
return;
|
|
3068
|
+
this.pushSemanticDiagnostic(identifier, `function "${name}" is not supported inside CASE`, `case-function:${identifier.from}`);
|
|
3069
|
+
}
|
|
3070
|
+
caseContainsShift(caseExpr) {
|
|
3071
|
+
const key = this.nodeRangeKey(caseExpr);
|
|
3072
|
+
const cached = this.caseShiftPresence.get(key);
|
|
3073
|
+
if (cached !== undefined)
|
|
3074
|
+
return cached;
|
|
3075
|
+
const containsShift = this.containsNode(caseExpr, 'ShiftExpr');
|
|
3076
|
+
this.caseShiftPresence.set(key, containsShift);
|
|
3077
|
+
return containsShift;
|
|
2621
3078
|
}
|
|
2622
3079
|
checkCaseResults(caseExpr) {
|
|
2623
3080
|
let clause = caseExpr.firstChild;
|
|
@@ -2644,6 +3101,10 @@ export class Parser {
|
|
|
2644
3101
|
const hasFrom = /\bfrom\s*=/.test(bodyText.toLowerCase());
|
|
2645
3102
|
const filter = show.getChild('FilterClause');
|
|
2646
3103
|
const hasSourceFilter = Boolean(filter && /@__source__/.test(this.maskStringsAndLineComments(this.nodeText(filter))));
|
|
3104
|
+
if (/^show_[a-z0-9]+_index$/.test(name)) {
|
|
3105
|
+
this.pushSemanticDiagnostic(identifier, `"${name}" is not supported by the DQL parser`, `show:${show.from}:unsupported`);
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
2647
3108
|
if ((name === 'show_tag_value' || name === 'show_tag_value_cardinality' || name === 'show_series_count_by_tag_value') && !hasKeyin) {
|
|
2648
3109
|
this.pushSemanticDiagnostic(identifier, `"${name}" requires the named argument "keyin"`, `show:${show.from}:keyin`);
|
|
2649
3110
|
}
|
|
@@ -2669,13 +3130,105 @@ export class Parser {
|
|
|
2669
3130
|
}
|
|
2670
3131
|
return children;
|
|
2671
3132
|
}
|
|
3133
|
+
descendantsNamed(node, name) {
|
|
3134
|
+
const descendants = [];
|
|
3135
|
+
const visit = (current) => {
|
|
3136
|
+
let child = current.firstChild;
|
|
3137
|
+
while (child) {
|
|
3138
|
+
if (child.name === name)
|
|
3139
|
+
descendants.push(child);
|
|
3140
|
+
visit(child);
|
|
3141
|
+
child = child.nextSibling;
|
|
3142
|
+
}
|
|
3143
|
+
};
|
|
3144
|
+
visit(node);
|
|
3145
|
+
return descendants;
|
|
3146
|
+
}
|
|
3147
|
+
shiftOperandFunctionCalls(operand) {
|
|
3148
|
+
const querySource = operand.getChild('QuerySource');
|
|
3149
|
+
if (!querySource)
|
|
3150
|
+
return this.descendantsNamed(operand, 'FunctionCall');
|
|
3151
|
+
const query = querySource.getChild('Query');
|
|
3152
|
+
if (!query)
|
|
3153
|
+
return [];
|
|
3154
|
+
const calls = [];
|
|
3155
|
+
let clause = query.firstChild;
|
|
3156
|
+
while (clause) {
|
|
3157
|
+
if (clause.name === 'QueryClause') {
|
|
3158
|
+
const target = clause.getChild('TargetClause');
|
|
3159
|
+
if (target)
|
|
3160
|
+
calls.push(...this.descendantsNamed(target, 'FunctionCall'));
|
|
3161
|
+
}
|
|
3162
|
+
clause = clause.nextSibling;
|
|
3163
|
+
}
|
|
3164
|
+
return calls;
|
|
3165
|
+
}
|
|
3166
|
+
errorDescendantsWithText(node, text) {
|
|
3167
|
+
const errors = [];
|
|
3168
|
+
const expected = text.toLowerCase();
|
|
3169
|
+
const visit = (current) => {
|
|
3170
|
+
let child = current.firstChild;
|
|
3171
|
+
while (child) {
|
|
3172
|
+
if (child.type.isError && this.nodeText(child).toLowerCase() === expected)
|
|
3173
|
+
errors.push(child);
|
|
3174
|
+
visit(child);
|
|
3175
|
+
child = child.nextSibling;
|
|
3176
|
+
}
|
|
3177
|
+
};
|
|
3178
|
+
visit(node);
|
|
3179
|
+
return errors;
|
|
3180
|
+
}
|
|
3181
|
+
functionCallName(call) {
|
|
3182
|
+
const identifier = call.getChild('FunctionIdentifier');
|
|
3183
|
+
return identifier ? this.nodeText(identifier).toLowerCase() : null;
|
|
3184
|
+
}
|
|
3185
|
+
isPositiveFixedDuration(text) {
|
|
3186
|
+
const milliseconds = this.fixedDurationMilliseconds(text);
|
|
3187
|
+
return milliseconds !== null && milliseconds > 0;
|
|
3188
|
+
}
|
|
3189
|
+
fixedDurationMilliseconds(text) {
|
|
3190
|
+
const multipliers = {
|
|
3191
|
+
y: 365 * 24 * 60 * 60 * 1000,
|
|
3192
|
+
w: 7 * 24 * 60 * 60 * 1000,
|
|
3193
|
+
d: 24 * 60 * 60 * 1000,
|
|
3194
|
+
h: 60 * 60 * 1000,
|
|
3195
|
+
m: 60 * 1000,
|
|
3196
|
+
s: 1000,
|
|
3197
|
+
ms: 1,
|
|
3198
|
+
};
|
|
3199
|
+
const normalized = text.toLowerCase();
|
|
3200
|
+
const pattern = /(\d+)(ms|[ywdhms])/g;
|
|
3201
|
+
let total = 0;
|
|
3202
|
+
let consumed = 0;
|
|
3203
|
+
let part = pattern.exec(normalized);
|
|
3204
|
+
while (part) {
|
|
3205
|
+
if (part.index !== consumed)
|
|
3206
|
+
return null;
|
|
3207
|
+
const amount = Number(part[1]);
|
|
3208
|
+
const component = amount * multipliers[part[2]];
|
|
3209
|
+
if (!Number.isSafeInteger(amount) || !Number.isSafeInteger(component) || !Number.isSafeInteger(total + component))
|
|
3210
|
+
return null;
|
|
3211
|
+
total += component;
|
|
3212
|
+
consumed = pattern.lastIndex;
|
|
3213
|
+
part = pattern.exec(normalized);
|
|
3214
|
+
}
|
|
3215
|
+
return consumed === normalized.length ? total : null;
|
|
3216
|
+
}
|
|
2672
3217
|
numericValue(node) {
|
|
2673
3218
|
if (!node)
|
|
2674
3219
|
return null;
|
|
2675
|
-
|
|
3220
|
+
let valueNode = node;
|
|
3221
|
+
if (node.name === 'EvalFunctionArg') {
|
|
3222
|
+
const expression = node.getChild('EvalExpression');
|
|
3223
|
+
const atom = expression === null || expression === void 0 ? void 0 : expression.firstChild;
|
|
3224
|
+
if (!expression || (atom === null || atom === void 0 ? void 0 : atom.name) !== 'EvalAtom' || atom.nextSibling)
|
|
3225
|
+
return null;
|
|
3226
|
+
valueNode = atom;
|
|
3227
|
+
}
|
|
3228
|
+
const number = valueNode.name === 'Number' ? valueNode : valueNode.getChild('Number');
|
|
2676
3229
|
if (!number)
|
|
2677
3230
|
return null;
|
|
2678
|
-
const sign =
|
|
3231
|
+
const sign = valueNode.name !== 'Number' && valueNode.getChild('Sub') ? -1 : 1;
|
|
2679
3232
|
const value = sign * Number(this.nodeText(number).replace(/_/g, ''));
|
|
2680
3233
|
return Number.isFinite(value) ? value : null;
|
|
2681
3234
|
}
|