@genesislcap/mock-server 15.60.0 → 15.62.0
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/README.md +287 -40
- package/dist/db/criteria.d.ts +28 -9
- package/dist/db/criteria.js +856 -258
- package/dist/db/criteriaDialect.d.ts +6 -0
- package/dist/db/criteriaDialect.js +441 -0
- package/dist/db/criteriaTokens.d.ts +12 -0
- package/dist/db/criteriaTokens.js +53 -0
- package/dist/db/groovySyntax.d.ts +26 -0
- package/dist/db/groovySyntax.js +481 -0
- package/dist/db/groovyValidator.d.ts +11 -0
- package/dist/db/groovyValidator.js +523 -0
- package/dist/db/gsfExpr.d.ts +21 -0
- package/dist/db/gsfExpr.js +333 -0
- package/dist/db/identity.d.ts +2 -1
- package/dist/db/identity.js +24 -3
- package/dist/db/legacyCriteria.d.ts +7 -0
- package/dist/db/legacyCriteria.js +280 -0
- package/dist/db/ordering.d.ts +2 -0
- package/dist/db/ordering.js +24 -3
- package/dist/db/schema.d.ts +2 -0
- package/dist/db/schema.js +34 -3
- package/dist/db/store.d.ts +7 -0
- package/dist/db/store.js +55 -7
- package/dist/db/values.d.ts +19 -0
- package/dist/db/values.js +243 -0
- package/dist/handlers/commitEvent.js +3 -2
- package/dist/handlers/dataLogon.d.ts +2 -1
- package/dist/handlers/dataLogon.js +108 -51
- package/dist/handlers/eventValidation.d.ts +2 -1
- package/dist/handlers/eventValidation.js +33 -6
- package/dist/handlers/requestReply.js +43 -16
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/protocol/connection.d.ts +1 -1
- package/dist/server.js +15 -8
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
- package/src/db/criteria.ts +979 -290
- package/src/db/criteriaDialect.ts +487 -0
- package/src/db/criteriaTokens.ts +72 -0
- package/src/db/groovySyntax.ts +500 -0
- package/src/db/groovyValidator.ts +546 -0
- package/src/db/gsfExpr.ts +392 -0
- package/src/db/identity.ts +22 -4
- package/src/db/legacyCriteria.ts +301 -0
- package/src/db/ordering.ts +27 -3
- package/src/db/schema.ts +43 -8
- package/src/db/store.ts +65 -7
- package/src/db/values.ts +247 -0
- package/src/handlers/commitEvent.ts +3 -2
- package/src/handlers/dataLogon.ts +134 -56
- package/src/handlers/eventValidation.ts +40 -13
- package/src/handlers/requestReply.ts +47 -14
- package/src/index.ts +6 -1
- package/src/protocol/connection.ts +3 -4
- package/src/server.ts +16 -15
- package/src/types.ts +7 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
// Which CRITERIA_MATCH strings real GSF refuses to compile, and the text it
|
|
2
|
+
// refuses them with.
|
|
3
|
+
//
|
|
4
|
+
// GSF parses a criteria as Groovy before anything else
|
|
5
|
+
// (CriteriaFilterBuilder.validateAndParseCriteriaOptions:
|
|
6
|
+
// AstBuilder().buildFromString(criteria), GSF v8.15.29, Groovy 3.0.25), and a
|
|
7
|
+
// parse failure becomes INVALID_CRITERIA with the compiler's message. This
|
|
8
|
+
// engine's criteria grammar (criteria.ts) is much smaller than Groovy, so its
|
|
9
|
+
// own parse failures can't be taken for Groovy's: `ENABLED`, `NAME in ['A']`
|
|
10
|
+
// or arithmetic are valid Groovy it doesn't model. This check answers only
|
|
11
|
+
// for the mistakes it can pin down as Groovy does — a dangling operator, an
|
|
12
|
+
// unclosed or unopened parenthesis, an operator where an operand must start,
|
|
13
|
+
// an unterminated string — and gives up (undefined) on anything else it
|
|
14
|
+
// doesn't understand, so a criteria is refused only when Groovy would refuse
|
|
15
|
+
// it too. The messages and positions follow what Groovy 3.0.25's AstBuilder
|
|
16
|
+
// reports for those cases (checked by running it; test/groovySyntax.test.ts pins
|
|
17
|
+
// them), though ANTLR names a different token in a few of them.
|
|
18
|
+
// Thrown when the criteria is outside what this check understands: no verdict.
|
|
19
|
+
class GiveUp extends Error {
|
|
20
|
+
}
|
|
21
|
+
// Thrown for a syntax error Groovy reports the same way.
|
|
22
|
+
class Refused extends Error {
|
|
23
|
+
text;
|
|
24
|
+
offset;
|
|
25
|
+
kind;
|
|
26
|
+
constructor(text, offset, kind = 'input') {
|
|
27
|
+
super(text);
|
|
28
|
+
this.text = text;
|
|
29
|
+
this.offset = offset;
|
|
30
|
+
this.kind = kind;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Groovy's operators, longest first so the scanner takes `===` before `==`.
|
|
34
|
+
const OPERATORS = [
|
|
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
|
+
// Operators that end a postfix chain with a member name.
|
|
90
|
+
const MEMBER_OPERATORS = new Set(['.', '?.', '*.', '.&', '.@', '::']);
|
|
91
|
+
// Operators an operand may start with.
|
|
92
|
+
const PREFIX_OPERATORS = new Set(['!', '~', '+', '-', '++', '--']);
|
|
93
|
+
// Operators that can never start an operand: one of these where an operand
|
|
94
|
+
// must start is the error Groovy reports at that operator (recorded for ==,
|
|
95
|
+
// &&, ||, > and &).
|
|
96
|
+
const NEVER_LEADING = new Set([
|
|
97
|
+
'==',
|
|
98
|
+
'!=',
|
|
99
|
+
'===',
|
|
100
|
+
'!==',
|
|
101
|
+
'<=',
|
|
102
|
+
'>=',
|
|
103
|
+
'<',
|
|
104
|
+
'>',
|
|
105
|
+
'&&',
|
|
106
|
+
'||',
|
|
107
|
+
'=',
|
|
108
|
+
'&',
|
|
109
|
+
'|',
|
|
110
|
+
'^',
|
|
111
|
+
'=~',
|
|
112
|
+
'==~',
|
|
113
|
+
'<=>',
|
|
114
|
+
]);
|
|
115
|
+
// Binary operators; this engine's AND / OR keywords count too, so a criteria
|
|
116
|
+
// in its own dialect gets no syntax verdict here: criteriaDialect.ts gives
|
|
117
|
+
// GSF's.
|
|
118
|
+
const BINARY_KEYWORDS = new Set(['in', 'instanceof', 'as']);
|
|
119
|
+
const DIALECT_BINARY = new Set(['AND', 'OR']);
|
|
120
|
+
const NAME_START = /[A-Za-z_$]/;
|
|
121
|
+
const NAME_PART = /[A-Za-z0-9_$]/;
|
|
122
|
+
const NUMBER = /^\d+(?:\.\d+)?(?:[eE][+-]?\d+)?[GgLlDdFfIi]?/;
|
|
123
|
+
// Characters Groovy can't start any token with.
|
|
124
|
+
const NEVER_TOKENS = new Set(['#', '`']);
|
|
125
|
+
const TRIPLE_QUOTE = 3;
|
|
126
|
+
function scan(source) {
|
|
127
|
+
const tokens = [];
|
|
128
|
+
let at = 0;
|
|
129
|
+
while (at < source.length) {
|
|
130
|
+
const char = source[at];
|
|
131
|
+
if (char === ' ' || char === '\t') {
|
|
132
|
+
at += 1;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
// A line break can end a statement in Groovy: leave those to Groovy.
|
|
136
|
+
if (char === '\n' || char === '\r')
|
|
137
|
+
throw new GiveUp();
|
|
138
|
+
if (char === "'" || char === '"') {
|
|
139
|
+
// Triple-quoted strings and interpolated GStrings: not modelled.
|
|
140
|
+
if (source.startsWith(char.repeat(TRIPLE_QUOTE), at))
|
|
141
|
+
throw new GiveUp();
|
|
142
|
+
let end = at + 1;
|
|
143
|
+
while (end < source.length && source[end] !== char)
|
|
144
|
+
end += source[end] === '\\' ? 2 : 1;
|
|
145
|
+
if (end >= source.length)
|
|
146
|
+
throw new Refused(char, at, 'character');
|
|
147
|
+
const text = source.slice(at, end + 1);
|
|
148
|
+
if (char === '"' && text.includes('${'))
|
|
149
|
+
throw new GiveUp();
|
|
150
|
+
tokens.push({ kind: 'literal', text, offset: at });
|
|
151
|
+
at = end + 1;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const number = NUMBER.exec(source.slice(at));
|
|
155
|
+
if (number) {
|
|
156
|
+
tokens.push({ kind: 'literal', text: number[0], offset: at });
|
|
157
|
+
at += number[0].length;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
// A slash may open a slashy string (/O'Brien/) or a comment, inside
|
|
161
|
+
// which nothing above applies: no verdict.
|
|
162
|
+
if (char === '/')
|
|
163
|
+
throw new GiveUp();
|
|
164
|
+
if (NAME_START.test(char)) {
|
|
165
|
+
let end = at + 1;
|
|
166
|
+
while (end < source.length && NAME_PART.test(source[end]))
|
|
167
|
+
end += 1;
|
|
168
|
+
tokens.push({ kind: 'name', text: source.slice(at, end), offset: at });
|
|
169
|
+
at = end;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if ('([{'.includes(char)) {
|
|
173
|
+
tokens.push({ kind: 'open', text: char, offset: at });
|
|
174
|
+
at += 1;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (')]}'.includes(char)) {
|
|
178
|
+
tokens.push({ kind: 'close', text: char, offset: at });
|
|
179
|
+
at += 1;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (char === ',' || char === ';') {
|
|
183
|
+
tokens.push({ kind: char === ',' ? 'comma' : 'semicolon', text: char, offset: at });
|
|
184
|
+
at += 1;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const operator = OPERATORS.find((candidate) => source.startsWith(candidate, at));
|
|
188
|
+
if (operator) {
|
|
189
|
+
tokens.push({ kind: 'operator', text: operator, offset: at });
|
|
190
|
+
at += operator.length;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (NEVER_TOKENS.has(char))
|
|
194
|
+
throw new Refused(char, at, 'character');
|
|
195
|
+
throw new GiveUp();
|
|
196
|
+
}
|
|
197
|
+
return tokens;
|
|
198
|
+
}
|
|
199
|
+
class Checker {
|
|
200
|
+
at = 0;
|
|
201
|
+
tokens;
|
|
202
|
+
// The offset of the end of the input, where Groovy reports '<EOF>'.
|
|
203
|
+
end;
|
|
204
|
+
constructor(tokens, end) {
|
|
205
|
+
this.tokens = tokens;
|
|
206
|
+
this.end = end;
|
|
207
|
+
}
|
|
208
|
+
peek(ahead = 0) {
|
|
209
|
+
return this.tokens[this.at + ahead];
|
|
210
|
+
}
|
|
211
|
+
next() {
|
|
212
|
+
const token = this.tokens[this.at];
|
|
213
|
+
this.at += 1;
|
|
214
|
+
return token;
|
|
215
|
+
}
|
|
216
|
+
isOperator(token, ...texts) {
|
|
217
|
+
return token?.kind === 'operator' && texts.includes(token.text);
|
|
218
|
+
}
|
|
219
|
+
startsOperand(token) {
|
|
220
|
+
if (!token)
|
|
221
|
+
return false;
|
|
222
|
+
if (token.kind === 'name' || token.kind === 'literal')
|
|
223
|
+
return true;
|
|
224
|
+
if (token.kind === 'open')
|
|
225
|
+
return true;
|
|
226
|
+
return token.kind === 'operator' && PREFIX_OPERATORS.has(token.text);
|
|
227
|
+
}
|
|
228
|
+
isBinary(token) {
|
|
229
|
+
if (!token)
|
|
230
|
+
return false;
|
|
231
|
+
if (token.kind === 'name') {
|
|
232
|
+
return BINARY_KEYWORDS.has(token.text) || DIALECT_BINARY.has(token.text.toUpperCase());
|
|
233
|
+
}
|
|
234
|
+
return (token.kind === 'operator' &&
|
|
235
|
+
!MEMBER_OPERATORS.has(token.text) &&
|
|
236
|
+
!['!', '~', '++', '--', '?', '?:', ':', '->'].includes(token.text));
|
|
237
|
+
}
|
|
238
|
+
check() {
|
|
239
|
+
for (;;) {
|
|
240
|
+
this.expression();
|
|
241
|
+
const token = this.next();
|
|
242
|
+
if (!token)
|
|
243
|
+
return;
|
|
244
|
+
if (token.kind === 'semicolon') {
|
|
245
|
+
if (!this.peek())
|
|
246
|
+
return;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
// A parenthesis closing nothing (recorded: `NAME == 'x')`).
|
|
250
|
+
if (token.kind === 'close' && token.text === ')')
|
|
251
|
+
throw new Refused(')', token.offset);
|
|
252
|
+
throw new GiveUp();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
expression() {
|
|
256
|
+
this.binary();
|
|
257
|
+
const token = this.peek();
|
|
258
|
+
if (this.isOperator(token, '?')) {
|
|
259
|
+
this.next();
|
|
260
|
+
this.expression();
|
|
261
|
+
const colon = this.next();
|
|
262
|
+
if (!colon)
|
|
263
|
+
throw new Refused('<EOF>', this.end);
|
|
264
|
+
if (!this.isOperator(colon, ':'))
|
|
265
|
+
throw new GiveUp();
|
|
266
|
+
this.expression();
|
|
267
|
+
}
|
|
268
|
+
else if (this.isOperator(token, '?:')) {
|
|
269
|
+
this.next();
|
|
270
|
+
this.expression();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
binary() {
|
|
274
|
+
this.unary();
|
|
275
|
+
while (this.isBinary(this.peek())) {
|
|
276
|
+
const operator = this.next();
|
|
277
|
+
// A trailing + or - (which could also start an operand) is reported at
|
|
278
|
+
// itself (recorded: `QUANTITY > 1 +`); any other at the end of input.
|
|
279
|
+
if (!this.peek() && PREFIX_OPERATORS.has(operator.text)) {
|
|
280
|
+
throw new Refused(operator.text, operator.offset);
|
|
281
|
+
}
|
|
282
|
+
this.unary();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
unary() {
|
|
286
|
+
for (;;) {
|
|
287
|
+
const token = this.peek();
|
|
288
|
+
if (token?.kind === 'operator' && PREFIX_OPERATORS.has(token.text)) {
|
|
289
|
+
this.next();
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
// This engine's NOT keyword, when an operand follows it.
|
|
293
|
+
if (token?.kind === 'name' &&
|
|
294
|
+
token.text.toUpperCase() === 'NOT' &&
|
|
295
|
+
this.startsOperand(this.peek(1))) {
|
|
296
|
+
this.next();
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
this.postfix();
|
|
302
|
+
}
|
|
303
|
+
postfix() {
|
|
304
|
+
this.primary();
|
|
305
|
+
for (;;) {
|
|
306
|
+
const token = this.peek();
|
|
307
|
+
if (token?.kind === 'operator' && MEMBER_OPERATORS.has(token.text)) {
|
|
308
|
+
this.next();
|
|
309
|
+
const member = this.next();
|
|
310
|
+
if (!member)
|
|
311
|
+
throw new Refused('<EOF>', this.end);
|
|
312
|
+
// A name, or a quoted one (obj.'name'); not a number (1.5.5).
|
|
313
|
+
const quoted = member.kind === 'literal' && /^['"]/.test(member.text);
|
|
314
|
+
if (member.kind !== 'name' && !quoted)
|
|
315
|
+
throw new GiveUp();
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (this.isOperator(token, '++', '--')) {
|
|
319
|
+
this.next();
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (token?.kind === 'open' && token.text === '(') {
|
|
323
|
+
this.next();
|
|
324
|
+
this.callArguments(token);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (token?.kind === 'open' && (token.text === '[' || token.text === '{')) {
|
|
328
|
+
this.next();
|
|
329
|
+
this.bracketed(token);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
primary() {
|
|
336
|
+
const token = this.next();
|
|
337
|
+
if (!token)
|
|
338
|
+
throw new Refused('<EOF>', this.end);
|
|
339
|
+
if (token.kind === 'name' || token.kind === 'literal')
|
|
340
|
+
return;
|
|
341
|
+
if (token.kind === 'open' && token.text === '(') {
|
|
342
|
+
const following = this.peek();
|
|
343
|
+
if (!following)
|
|
344
|
+
throw new Refused('(', this.end); // recorded: `NAME == 'x' && (`
|
|
345
|
+
// `()` is no expression; recorded only at the end of the input.
|
|
346
|
+
if (following.kind === 'close' && following.text === ')') {
|
|
347
|
+
this.next();
|
|
348
|
+
if (!this.peek())
|
|
349
|
+
throw new Refused('<EOF>', this.end);
|
|
350
|
+
throw new GiveUp();
|
|
351
|
+
}
|
|
352
|
+
this.expression();
|
|
353
|
+
const close = this.next();
|
|
354
|
+
if (!close)
|
|
355
|
+
throw new Refused('<EOF>', this.end);
|
|
356
|
+
if (close.kind !== 'close' || close.text !== ')')
|
|
357
|
+
throw new GiveUp();
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (token.kind === 'open') {
|
|
361
|
+
this.bracketed(token);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (token.kind === 'close' && token.text === ')')
|
|
365
|
+
throw new Refused(')', token.offset);
|
|
366
|
+
if (token.kind === 'operator' && NEVER_LEADING.has(token.text)) {
|
|
367
|
+
throw new Refused(token.text, token.offset);
|
|
368
|
+
}
|
|
369
|
+
throw new GiveUp();
|
|
370
|
+
}
|
|
371
|
+
// A call's arguments, after its '('. Running out of input inside them is
|
|
372
|
+
// reported at that '(' (recorded: `Expr.contains(NAME, 'x'`).
|
|
373
|
+
callArguments(open) {
|
|
374
|
+
try {
|
|
375
|
+
this.list(')');
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
if (error instanceof Refused && error.offset === this.end && error.kind === 'input') {
|
|
379
|
+
throw new Refused('(', open.offset);
|
|
380
|
+
}
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// A list literal or index after '[', or a closure after '{'. Closures
|
|
385
|
+
// aren't modelled: their contents only have to balance.
|
|
386
|
+
bracketed(open) {
|
|
387
|
+
if (open.text === '{') {
|
|
388
|
+
let depth = 1;
|
|
389
|
+
while (depth > 0) {
|
|
390
|
+
const token = this.next();
|
|
391
|
+
if (!token)
|
|
392
|
+
throw new GiveUp();
|
|
393
|
+
if (token.kind === 'open')
|
|
394
|
+
depth += 1;
|
|
395
|
+
if (token.kind === 'close')
|
|
396
|
+
depth -= 1;
|
|
397
|
+
}
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
this.list(']');
|
|
401
|
+
}
|
|
402
|
+
// Comma-separated expressions up to `close`; a trailing comma is allowed.
|
|
403
|
+
list(close) {
|
|
404
|
+
for (;;) {
|
|
405
|
+
const token = this.peek();
|
|
406
|
+
if (token?.kind === 'close' && token.text === close) {
|
|
407
|
+
this.next();
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (token?.kind === 'comma')
|
|
411
|
+
throw new GiveUp();
|
|
412
|
+
this.expression();
|
|
413
|
+
const after = this.next();
|
|
414
|
+
if (!after) {
|
|
415
|
+
if (close === ')')
|
|
416
|
+
throw new Refused('<EOF>', this.end);
|
|
417
|
+
throw new GiveUp();
|
|
418
|
+
}
|
|
419
|
+
if (after.kind === 'close' && after.text === close)
|
|
420
|
+
return;
|
|
421
|
+
if (after.kind !== 'comma')
|
|
422
|
+
throw new GiveUp();
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
// The criteria's Groovy tokens, or undefined where this scanner gives up (a
|
|
427
|
+
// line break, a slash, a triple-quoted or interpolated string) or Groovy's
|
|
428
|
+
// lexer refuses it.
|
|
429
|
+
export function groovyTokens(criteria) {
|
|
430
|
+
try {
|
|
431
|
+
return scan(criteria);
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
if (error instanceof GiveUp || error instanceof Refused)
|
|
435
|
+
return undefined;
|
|
436
|
+
throw error;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// The syntax error Groovy would report for this criteria, or undefined when
|
|
440
|
+
// it has none or this check can't tell (see the header).
|
|
441
|
+
export function groovySyntaxError(criteria) {
|
|
442
|
+
const verdict = checkGroovySyntax(criteria);
|
|
443
|
+
return verdict.kind === 'refused' ? verdict.error : undefined;
|
|
444
|
+
}
|
|
445
|
+
export function checkGroovySyntax(criteria) {
|
|
446
|
+
if (criteria.trim() === '')
|
|
447
|
+
return { kind: 'valid' };
|
|
448
|
+
try {
|
|
449
|
+
new Checker(scan(criteria), criteria.length).check();
|
|
450
|
+
return { kind: 'valid' };
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
if (error instanceof GiveUp || error instanceof RangeError)
|
|
454
|
+
return { kind: 'unknown' };
|
|
455
|
+
if (!(error instanceof Refused))
|
|
456
|
+
throw error;
|
|
457
|
+
const quoted = error.text === "'" ? "'\\''" : `'${error.text}'`;
|
|
458
|
+
return {
|
|
459
|
+
kind: 'refused',
|
|
460
|
+
error: { message: `Unexpected ${error.kind}: ${quoted}`, line: 1, column: error.offset + 1 },
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
let scripts = 0;
|
|
465
|
+
// The INVALID_CRITERIA TEXT GSF sends for it: the MultipleCompilationErrors
|
|
466
|
+
// message, prefixed with the exception's simple name
|
|
467
|
+
// (GenericCriteriaValidationException("${e.javaClass.simpleName} :
|
|
468
|
+
// ${e.message}")), naming a generated script class.
|
|
469
|
+
// `ownLine`: the position goes on a line of its own, as Groovy prints a
|
|
470
|
+
// semantic error ("unable to resolve class NOT\n @ line 1, column 5.")
|
|
471
|
+
// rather than a syntax one.
|
|
472
|
+
export function groovyCompileErrorText(criteria, error, { ownLine = false } = {}) {
|
|
473
|
+
scripts += 1;
|
|
474
|
+
const script = `Script${Date.now()}${scripts}.groovy`;
|
|
475
|
+
const sourceLine = criteria.split('\n')[error.line - 1] ?? '';
|
|
476
|
+
return ('MultipleCompilationErrorsException : startup failed:\n' +
|
|
477
|
+
`${script}: ${error.line}: ${error.message}${ownLine ? '\n' : ''} @ line ${error.line}, column ${error.column}.\n` +
|
|
478
|
+
` ${sourceLine}\n` +
|
|
479
|
+
` ${' '.repeat(error.column - 1)}^\n` +
|
|
480
|
+
'\n1 error\n');
|
|
481
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const DEFAULT_IMPORT_CLASSES: Record<string, string>;
|
|
2
|
+
export declare const isClassName: (name: string) => boolean;
|
|
3
|
+
export declare const EXPR_METHODS: Set<string>;
|
|
4
|
+
export declare const FIELD_METHODS: Set<string>;
|
|
5
|
+
export type GroovyValidation = {
|
|
6
|
+
kind: 'refused';
|
|
7
|
+
text: string;
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'passed';
|
|
10
|
+
};
|
|
11
|
+
export declare function groovyValidation(criteria: string, allowed?: ReadonlySet<string>): GroovyValidation | undefined;
|