wizard-ql 2.0.1 → 2.1.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/dist/errors.d.ts +8 -2
- package/dist/index.js +120 -87
- package/dist/parse.d.ts +1 -1
- package/dist/spec.d.ts +5 -0
- package/dist/stringify.d.ts +1 -1
- package/package.json +1 -1
package/dist/errors.d.ts
CHANGED
|
@@ -14,15 +14,18 @@ export declare class ParseError extends Error {
|
|
|
14
14
|
readonly startIndex: number | undefined;
|
|
15
15
|
/** The end token index */
|
|
16
16
|
readonly endIndex: number | undefined;
|
|
17
|
+
/** The tokens being parsed */
|
|
18
|
+
readonly tokens: Token[] | undefined;
|
|
17
19
|
/**
|
|
18
20
|
* Construct a Parsing Error
|
|
19
21
|
* @param message The error message
|
|
22
|
+
* @param tokens The tokens being parsed
|
|
20
23
|
* @param startToken The first token this error pertains to
|
|
21
24
|
* @param startIndex The start token index
|
|
22
25
|
* @param endToken The last token this error pertains to
|
|
23
26
|
* @param endIndex The end token index
|
|
24
27
|
*/
|
|
25
|
-
constructor(message: string, startToken: Token | undefined, startIndex: number | undefined, endToken?: Token | undefined, endIndex?: number | undefined);
|
|
28
|
+
constructor(message: string, tokens: Token[] | undefined, startToken: Token | undefined, startIndex: number | undefined, endToken?: Token | undefined, endIndex?: number | undefined);
|
|
26
29
|
}
|
|
27
30
|
/**
|
|
28
31
|
* A WizardQL string expression parsing constraint error
|
|
@@ -39,13 +42,16 @@ export declare class ConstraintError extends Error {
|
|
|
39
42
|
readonly startIndex: number | undefined;
|
|
40
43
|
/** The end token index */
|
|
41
44
|
readonly endIndex: number | undefined;
|
|
45
|
+
/** The tokens being parsed */
|
|
46
|
+
readonly tokens: Token[] | undefined;
|
|
42
47
|
/**
|
|
43
48
|
* Construct a Constraint Error
|
|
44
49
|
* @param message The error message
|
|
50
|
+
* @param tokens The tokens being parsed
|
|
45
51
|
* @param startToken The first token this error pertains to
|
|
46
52
|
* @param startIndex The start token index
|
|
47
53
|
* @param endToken The last token this error pertains to
|
|
48
54
|
* @param endIndex The end token index
|
|
49
55
|
*/
|
|
50
|
-
constructor(message: string, startToken: Token | undefined, startIndex: number | undefined, endToken?: Token | undefined, endIndex?: number | undefined);
|
|
56
|
+
constructor(message: string, tokens: Token[] | undefined, startToken: Token | undefined, startIndex: number | undefined, endToken?: Token | undefined, endIndex?: number | undefined);
|
|
51
57
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
// src/spec.ts
|
|
2
|
+
var QUOTES = ["'", '"', "`"];
|
|
3
|
+
var BRACKETS = [
|
|
4
|
+
["[", "]"],
|
|
5
|
+
["{", "}"]
|
|
6
|
+
];
|
|
7
|
+
var PARENS = [
|
|
8
|
+
["(", ")"]
|
|
9
|
+
];
|
|
10
|
+
var NEGATORS = ["!"];
|
|
11
|
+
var ARRAY_DELIMITERS = [","];
|
|
2
12
|
var OPERATION_ALIAS_DICTIONARY = {
|
|
3
13
|
AND: "AND",
|
|
4
14
|
"&&": "AND",
|
|
@@ -72,7 +82,6 @@ var COMPARISON_TYPE_DICTIONARY = {
|
|
|
72
82
|
var TYPE_PRIORITY = ["boolean", "date", "number", "string"];
|
|
73
83
|
// src/regex.ts
|
|
74
84
|
var ESCAPE_REGEX = "(?<=(?<!\\\\)(?:\\\\\\\\)*)";
|
|
75
|
-
var QUOTES = ["'", '"', "`"];
|
|
76
85
|
var QUOTE_REGEX_STR = `${ESCAPE_REGEX}(?<quote>${QUOTES.map((q) => RegExp.escape(q)).join("|")})(?<quotecontent>.*?)${ESCAPE_REGEX}\\k<quote>`;
|
|
77
86
|
|
|
78
87
|
// src/errors.ts
|
|
@@ -106,9 +115,11 @@ class ParseError extends Error {
|
|
|
106
115
|
endToken;
|
|
107
116
|
startIndex;
|
|
108
117
|
endIndex;
|
|
109
|
-
|
|
118
|
+
tokens;
|
|
119
|
+
constructor(message, tokens, startToken, startIndex, endToken = startToken, endIndex = startIndex) {
|
|
110
120
|
const header = constructHeader(startToken, startIndex, endToken, endIndex);
|
|
111
121
|
super(header + message);
|
|
122
|
+
this.tokens = tokens;
|
|
112
123
|
this.rawMessage = message;
|
|
113
124
|
this.startToken = startToken;
|
|
114
125
|
this.endToken = endToken;
|
|
@@ -124,9 +135,11 @@ class ConstraintError extends Error {
|
|
|
124
135
|
endToken;
|
|
125
136
|
startIndex;
|
|
126
137
|
endIndex;
|
|
127
|
-
|
|
138
|
+
tokens;
|
|
139
|
+
constructor(message, tokens, startToken, startIndex, endToken = startToken, endIndex = startIndex) {
|
|
128
140
|
const header = constructHeader(startToken, startIndex, endToken, endIndex);
|
|
129
141
|
super(header + message);
|
|
142
|
+
this.tokens = tokens;
|
|
130
143
|
this.rawMessage = message;
|
|
131
144
|
this.startToken = startToken;
|
|
132
145
|
this.endToken = endToken;
|
|
@@ -136,7 +149,7 @@ class ConstraintError extends Error {
|
|
|
136
149
|
}
|
|
137
150
|
|
|
138
151
|
// src/parse.ts
|
|
139
|
-
var TOKEN_REGEX = new RegExp("(?<=(?<!\\\\)(?:\\\\\\\\)*)(?<quote>\\x27|\\x22|\\x60)(?<quotecontent>.*?)(?<=(?<!\\\\)(?:\\\\\\\\)*)\\k<quote>|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x41ND(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x26\\x26|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x26|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\^|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4fR(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\|\\||(?<=(?<!\\\\)(?:\\\\\\\\)*)\\||(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x56(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x47EQ(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3e\\x3d|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3d\\x3e|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4cEQ(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3c\\x3d|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3d\\x3c|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4eOTEQUALS(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4eOTEQUAL(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4eEQ(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x49SNT(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x21\\x3d\\x3d|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x21\\x3d|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x45QUALS(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x45QUAL(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x45Q(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x49S(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3d\\x3d|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3d|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4cESS(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3c|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x47REATER(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3e|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4dORE(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4eOTIN(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x21\\x3a|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x49N(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3a|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4eOTMATCHES(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4eOTMATCH(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x21\\x7e|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4dATCHES(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4dATCH(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x7e|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\(|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\[|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\]|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\{|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\}|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\
|
|
152
|
+
var TOKEN_REGEX = new RegExp("(?<=(?<!\\\\)(?:\\\\\\\\)*)(?<quote>\\x27|\\x22|\\x60)(?<quotecontent>.*?)(?<=(?<!\\\\)(?:\\\\\\\\)*)\\k<quote>|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x41ND(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x26\\x26|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x26|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\^|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4fR(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\|\\||(?<=(?<!\\\\)(?:\\\\\\\\)*)\\||(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x56(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x47EQ(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3e\\x3d|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3d\\x3e|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4cEQ(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3c\\x3d|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3d\\x3c|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4eOTEQUALS(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4eOTEQUAL(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4eEQ(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x49SNT(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x21\\x3d\\x3d|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x21\\x3d|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x45QUALS(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x45QUAL(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x45Q(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x49S(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3d\\x3d|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3d|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4cESS(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3c|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x47REATER(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3e|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4dORE(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4eOTIN(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x21\\x3a|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x49N(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x3a|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4eOTMATCHES(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4eOTMATCH(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x21\\x7e|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4dATCHES(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|^)\\x4dATCH(?=(?<=(?<!\\\\)(?:\\\\\\\\)*)\\s|$)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x7e|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\(|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\[|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\]|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\{|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\}|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x21|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\x2c", "g");
|
|
140
153
|
var QUOTE_REGEX = new RegExp("(?<=(?<!\\\\)(?:\\\\\\\\)*)(?<quote>\\x27|\\x22|\\x60)(?<quotecontent>.*?)(?<=(?<!\\\\)(?:\\\\\\\\)*)\\k<quote>");
|
|
141
154
|
var QUOTE_EDGE_REGEX = new RegExp(`^${"(?<=(?<!\\\\)(?:\\\\\\\\)*)(?<quote>\\x27|\\x22|\\x60)(?<quotecontent>.*?)(?<=(?<!\\\\)(?:\\\\\\\\)*)\\k<quote>"}$`);
|
|
142
155
|
function pushSanitized(array, item, index) {
|
|
@@ -156,20 +169,27 @@ function _tokenize(expression, pattern) {
|
|
|
156
169
|
let lastMatchEnd = null;
|
|
157
170
|
for (const match of matches) {
|
|
158
171
|
pushSanitized(tokens, expression.slice(lastMatchEnd ?? 0, match.index), lastMatchEnd === null ? 0 : lastMatchEnd);
|
|
159
|
-
if ([
|
|
172
|
+
if (BRACKETS.some(([o]) => match[0] === o)) {
|
|
160
173
|
const startToken = {
|
|
161
174
|
content: match[0],
|
|
162
175
|
index: match.index
|
|
163
176
|
};
|
|
164
177
|
tokens.push(startToken);
|
|
178
|
+
let subopenings = 0;
|
|
165
179
|
let endToken;
|
|
166
180
|
for (const submatch of matches) {
|
|
167
|
-
if (match[0] ===
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
181
|
+
if (match[0] === submatch[0] || match[0] === submatch[0])
|
|
182
|
+
++subopenings;
|
|
183
|
+
if (BRACKETS.some(([o, c]) => match[0] === o && submatch[0] === c)) {
|
|
184
|
+
if (subopenings)
|
|
185
|
+
--subopenings;
|
|
186
|
+
else {
|
|
187
|
+
endToken = {
|
|
188
|
+
content: submatch[0],
|
|
189
|
+
index: submatch.index
|
|
190
|
+
};
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
173
193
|
}
|
|
174
194
|
}
|
|
175
195
|
const subtokens = endToken ? _tokenize(expression.slice(startToken.index + startToken.content.length, endToken.index), new RegExp(`${"(?<=(?<!\\\\)(?:\\\\\\\\)*)(?<quote>\\x27|\\x22|\\x60)(?<quotecontent>.*?)(?<=(?<!\\\\)(?:\\\\\\\\)*)\\k<quote>"}|${ESCAPE_REGEX},`, "g")) : _tokenize(expression.slice(startToken.index + startToken.content.length), TOKEN_REGEX);
|
|
@@ -200,6 +220,7 @@ function processToken(token) {
|
|
|
200
220
|
const escaped = unquoted ?? token;
|
|
201
221
|
const unescaped = escaped.replaceAll(new RegExp(`(?<!${ESCAPE_REGEX}\\\\)\\\\`, "g"), "");
|
|
202
222
|
return {
|
|
223
|
+
raw: token,
|
|
203
224
|
unquoted,
|
|
204
225
|
escaped,
|
|
205
226
|
unescaped
|
|
@@ -270,8 +291,8 @@ function complementExpression(expression) {
|
|
|
270
291
|
break;
|
|
271
292
|
}
|
|
272
293
|
}
|
|
273
|
-
function coerceType(
|
|
274
|
-
const { unescaped } =
|
|
294
|
+
function coerceType(processedToken, fieldTypes, operatorTypes, dateInterpreter) {
|
|
295
|
+
const { raw, unescaped } = processedToken;
|
|
275
296
|
let operatorHits = 0;
|
|
276
297
|
let fieldHits = 0;
|
|
277
298
|
for (const type of TYPE_PRIORITY) {
|
|
@@ -285,13 +306,13 @@ function coerceType(value, fieldTypes, operatorTypes, dateInterpreter) {
|
|
|
285
306
|
continue;
|
|
286
307
|
switch (type) {
|
|
287
308
|
case "boolean":
|
|
288
|
-
if (
|
|
309
|
+
if (raw === "true")
|
|
289
310
|
return true;
|
|
290
|
-
else if (
|
|
311
|
+
else if (raw === "false")
|
|
291
312
|
return false;
|
|
292
313
|
break;
|
|
293
314
|
case "number": {
|
|
294
|
-
const num = Number(
|
|
315
|
+
const num = Number(raw);
|
|
295
316
|
if (isNaN(num))
|
|
296
317
|
break;
|
|
297
318
|
return num;
|
|
@@ -299,7 +320,7 @@ function coerceType(value, fieldTypes, operatorTypes, dateInterpreter) {
|
|
|
299
320
|
case "string":
|
|
300
321
|
return unescaped;
|
|
301
322
|
case "date": {
|
|
302
|
-
const num = Number(
|
|
323
|
+
const num = Number(raw);
|
|
303
324
|
const date = dateInterpreter(isNaN(num) ? unescaped : num);
|
|
304
325
|
if (isNaN(+date))
|
|
305
326
|
break;
|
|
@@ -319,46 +340,47 @@ function coerceType(value, fieldTypes, operatorTypes, dateInterpreter) {
|
|
|
319
340
|
}
|
|
320
341
|
function validateCondition(condition, constraints, valueIsImplicit, ctx) {
|
|
321
342
|
let validated = false;
|
|
343
|
+
condition.field = processToken(condition.field).unescaped;
|
|
322
344
|
const field = constraints?.caseInsensitive ? [...Object.keys(constraints.types ?? {}), ...Object.keys(constraints.restricted ?? {})].find((k) => k.toLowerCase() === condition.field.toLowerCase()) ?? condition.field : condition.field;
|
|
323
345
|
const restriction = constraints?.restricted?.[field];
|
|
324
346
|
const type = constraints?.types?.[field];
|
|
325
347
|
if (constraints?.disallowUnvalidated && restriction === undefined && type === undefined)
|
|
326
|
-
throw new ConstraintError(`Unknown field "${condition.field}"`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
348
|
+
throw new ConstraintError(`Unknown field "${condition.field}"`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
327
349
|
const operationType = COMPARISON_TYPE_DICTIONARY[condition.operation];
|
|
328
350
|
const types = type && (Array.isArray(type) ? type : [type]);
|
|
329
|
-
const
|
|
351
|
+
const processedValues = (Array.isArray(condition.value) ? condition.value : [condition.value]).map((v) => processToken(v));
|
|
330
352
|
if (restriction === true)
|
|
331
|
-
throw new ConstraintError(`Field "${condition.field}" is restricted`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
353
|
+
throw new ConstraintError(`Field "${condition.field}" is restricted`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
332
354
|
else if (Array.isArray(restriction)) {
|
|
333
355
|
const [philosophy, checks] = restriction;
|
|
334
356
|
if (philosophy === "deny") {
|
|
335
357
|
for (const check of checks) {
|
|
336
358
|
if (check instanceof RegExp) {
|
|
337
|
-
if (
|
|
338
|
-
throw new ConstraintError(`Value for field "${condition.field}" violates prohibitive pattern constraint "${check.toString()}". Prohibited values/patterns: Allowed values/patterns: ${checks.join(", ")}`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
359
|
+
if (processedValues.some(({ unescaped }) => check.test(unescaped))) {
|
|
360
|
+
throw new ConstraintError(`Value for field "${condition.field}" violates prohibitive pattern constraint "${check.toString()}". Prohibited values/patterns: Allowed values/patterns: ${checks.join(", ")}`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
339
361
|
}
|
|
340
362
|
} else {
|
|
341
|
-
if (
|
|
342
|
-
throw new ConstraintError(`Forbidden value "${check}" for field "${condition.field}". Prohibited values/patterns: ${checks.join(", ")}`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
363
|
+
if (processedValues.some(({ unescaped }) => unescaped === check)) {
|
|
364
|
+
throw new ConstraintError(`Forbidden value "${check}" for field "${condition.field}". Prohibited values/patterns: ${checks.join(", ")}`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
343
365
|
}
|
|
344
366
|
}
|
|
345
367
|
}
|
|
346
368
|
} else {
|
|
347
|
-
for (const
|
|
348
|
-
if (!checks.some((c) => c instanceof RegExp
|
|
349
|
-
throw new ConstraintError(`Value for field "${condition.field}" does not meet any allowed value/pattern. Allowed values/patterns: ${checks.join(", ")}`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
369
|
+
for (const { unescaped } of processedValues) {
|
|
370
|
+
if (!checks.some((c) => c instanceof RegExp ? c.test(unescaped) : c === unescaped)) {
|
|
371
|
+
throw new ConstraintError(`Value for field "${condition.field}" does not meet any allowed value/pattern. Allowed values/patterns: ${checks.join(", ")}`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
350
372
|
}
|
|
351
373
|
}
|
|
352
374
|
}
|
|
353
375
|
validated = true;
|
|
354
376
|
}
|
|
355
377
|
if (operationType === "array" && !Array.isArray(condition.value))
|
|
356
|
-
throw new ConstraintError(`Value "${condition.value.toString()}" is not permitted for "${condition.operation}" which expects an array`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
378
|
+
throw new ConstraintError(`Value "${condition.value.toString()}" is not permitted for "${condition.operation}" which expects an array`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
357
379
|
else if (operationType !== "array" && Array.isArray(condition.value))
|
|
358
|
-
throw new ConstraintError(`Value "${condition.value.toString()}" is not permitted for "${condition.operation}" which expects a non-array value`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
380
|
+
throw new ConstraintError(`Value "${condition.value.toString()}" is not permitted for "${condition.operation}" which expects a non-array value`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
359
381
|
const dateInterpreter = constraints?.dateInterpreter ?? ((v) => new Date(v));
|
|
360
|
-
for (let i = 0;i <
|
|
361
|
-
const v =
|
|
382
|
+
for (let i = 0;i < processedValues.length; ++i) {
|
|
383
|
+
const v = processedValues[i];
|
|
362
384
|
let opTypes;
|
|
363
385
|
if (valueIsImplicit)
|
|
364
386
|
opTypes = ["boolean"];
|
|
@@ -382,15 +404,16 @@ function validateCondition(condition, constraints, valueIsImplicit, ctx) {
|
|
|
382
404
|
}
|
|
383
405
|
try {
|
|
384
406
|
const value = coerceType(v, types ?? opTypes, opTypes, dateInterpreter);
|
|
385
|
-
|
|
386
|
-
|
|
407
|
+
if (Array.isArray(condition.value))
|
|
408
|
+
condition.value[i] = value;
|
|
409
|
+
else
|
|
387
410
|
condition.value = value;
|
|
388
411
|
} catch (err) {
|
|
389
412
|
switch (err.message) {
|
|
390
413
|
case "operator":
|
|
391
|
-
throw new ConstraintError(`Value "${condition.value.toString()}" not allowed for operation "${condition.operation}" which only allows for "${operationType}" type`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
414
|
+
throw new ConstraintError(`Value "${condition.value.toString()}" not allowed for operation "${condition.operation}" which only allows for "${operationType}" type`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
392
415
|
case "field":
|
|
393
|
-
throw new ConstraintError(`Value "${condition.value.toString()}" includes a type not permitted for field "${condition.field}". Allowed types: ${(types ?? opTypes).join(", ")}`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
416
|
+
throw new ConstraintError(`Value "${condition.value.toString()}" includes a type not permitted for field "${condition.field}". Allowed types: ${(types ?? opTypes).join(", ")}`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
394
417
|
}
|
|
395
418
|
}
|
|
396
419
|
}
|
|
@@ -413,15 +436,16 @@ function _parse(tokens, _offset, constraints) {
|
|
|
413
436
|
if (inConjunction) {
|
|
414
437
|
const prior = expressions.at(-1);
|
|
415
438
|
if (!prior)
|
|
416
|
-
throw new ParseError("Unexpected: Expression list empty when parser is meant to append to an AND group", ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
439
|
+
throw new ParseError("Unexpected: Expression list empty when parser is meant to append to an AND group", ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
417
440
|
if (prior.type !== "group" || prior.operation !== "AND")
|
|
418
|
-
throw new ParseError("Unexpected: Last expression is not an AND group yet parser thinks it's appending to one", ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
441
|
+
throw new ParseError("Unexpected: Last expression is not an AND group yet parser thinks it's appending to one", ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
419
442
|
return prior.constituents;
|
|
420
443
|
}
|
|
421
444
|
return expressions;
|
|
422
445
|
}
|
|
423
446
|
function resolveCondition(ctx) {
|
|
424
447
|
const baseCtx = {
|
|
448
|
+
tokens,
|
|
425
449
|
startToken: field?.token ?? comparisonOperation?.token ?? value?.token,
|
|
426
450
|
startIndex: field?.index ?? comparisonOperation?.index ?? value?.index,
|
|
427
451
|
endToken: value?.token ?? comparisonOperation?.token ?? field?.token,
|
|
@@ -432,7 +456,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
432
456
|
const group = getExpressionGroup(ctx);
|
|
433
457
|
if (field && comparisonOperation && value) {
|
|
434
458
|
if (!expectingExpression)
|
|
435
|
-
throw new ParseError("Unexpected expression resolution before junctive operator", ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
459
|
+
throw new ParseError("Unexpected expression resolution before junctive operator", ctx.tokens, ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
436
460
|
group.push(validateCondition({
|
|
437
461
|
type: "condition",
|
|
438
462
|
field: field.content,
|
|
@@ -443,7 +467,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
443
467
|
expectingExpression = false;
|
|
444
468
|
} else if (field && !comparisonOperation && !value) {
|
|
445
469
|
if (!expectingExpression)
|
|
446
|
-
throw new ParseError("Unexpected expression resolution before junctive operator", ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
470
|
+
throw new ParseError("Unexpected expression resolution before junctive operator", ctx.tokens, ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
447
471
|
group.push(validateCondition({
|
|
448
472
|
type: "condition",
|
|
449
473
|
field: field.content,
|
|
@@ -453,30 +477,31 @@ function _parse(tokens, _offset, constraints) {
|
|
|
453
477
|
inConjunction = false;
|
|
454
478
|
expectingExpression = false;
|
|
455
479
|
} else if (field || comparisonOperation || value !== undefined)
|
|
456
|
-
throw new ParseError("Failed to resolve condition; missing operand or operator", ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
480
|
+
throw new ParseError("Failed to resolve condition; missing operand or operator", ctx.tokens, ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
457
481
|
field = undefined;
|
|
458
482
|
comparisonOperation = undefined;
|
|
459
483
|
value = undefined;
|
|
460
484
|
}
|
|
461
485
|
for (let t = 0;t < tokens.length; ++t) {
|
|
462
486
|
const token = tokens[t];
|
|
463
|
-
if (token.content ===
|
|
464
|
-
throw new ParseError("Unexpected closing parenthesis", token, _offset + t);
|
|
465
|
-
if ([
|
|
466
|
-
throw new ParseError("Unexpected closing bracket/brace", token, _offset + t);
|
|
467
|
-
|
|
487
|
+
if (PARENS.some(([, c]) => token.content === c))
|
|
488
|
+
throw new ParseError("Unexpected closing parenthesis", tokens, token, _offset + t);
|
|
489
|
+
if (BRACKETS.some(([, c]) => token.content === c))
|
|
490
|
+
throw new ParseError("Unexpected closing bracket/brace", tokens, token, _offset + t);
|
|
491
|
+
const paren = PARENS.find(([o]) => token.content === o);
|
|
492
|
+
if (paren) {
|
|
468
493
|
if (field || comparisonOperation || value)
|
|
469
|
-
throw new ParseError("Tried to open a group during an operation", token, _offset + t);
|
|
470
|
-
const closingIndex = getClosingIndex(tokens, t,
|
|
494
|
+
throw new ParseError("Tried to open a group during an operation", tokens, token, _offset + t);
|
|
495
|
+
const closingIndex = getClosingIndex(tokens, t, paren[0], paren[1]);
|
|
471
496
|
if (closingIndex === -1)
|
|
472
|
-
throw new ParseError("Missing closing parenthesis for group", token, _offset + t);
|
|
497
|
+
throw new ParseError("Missing closing parenthesis for group", tokens, token, _offset + t);
|
|
473
498
|
++t;
|
|
474
499
|
const subExpression = _parse(tokens.slice(t, closingIndex), _offset + t, constraints);
|
|
475
500
|
if (subExpression) {
|
|
476
501
|
if (subExpression.type === "group" && subExpression.operation === groupOperation)
|
|
477
502
|
expressions.push(...subExpression.constituents);
|
|
478
503
|
else {
|
|
479
|
-
const group = getExpressionGroup({ startToken: token, startIndex: _offset + t });
|
|
504
|
+
const group = getExpressionGroup({ tokens, startToken: token, startIndex: _offset + t });
|
|
480
505
|
group.push(subExpression);
|
|
481
506
|
inConjunction = false;
|
|
482
507
|
}
|
|
@@ -487,6 +512,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
487
512
|
const op = OPERATION_ALIAS_DICTIONARY[token.content];
|
|
488
513
|
if (op && OPERATION_PURPOSE_DICTIONARY[op] === "junction") {
|
|
489
514
|
resolveCondition({
|
|
515
|
+
tokens,
|
|
490
516
|
startToken: field?.token ?? token,
|
|
491
517
|
startIndex: field?.index ?? _offset + t,
|
|
492
518
|
endToken: token,
|
|
@@ -494,16 +520,16 @@ function _parse(tokens, _offset, constraints) {
|
|
|
494
520
|
});
|
|
495
521
|
const prior = expressions.at(-1);
|
|
496
522
|
if (!prior)
|
|
497
|
-
throw new ParseError("Unexpected junction operator with no preceding expression", token, _offset + t);
|
|
523
|
+
throw new ParseError("Unexpected junction operator with no preceding expression", tokens, token, _offset + t);
|
|
498
524
|
expectingExpression = true;
|
|
499
525
|
if (groupOperation && groupOperation !== op) {
|
|
500
526
|
if (expressions.length < 2)
|
|
501
|
-
throw new ParseError("Unexpected junction operator with no preceding expression", token, _offset + t);
|
|
527
|
+
throw new ParseError("Unexpected junction operator with no preceding expression", tokens, token, _offset + t);
|
|
502
528
|
switch (groupOperation) {
|
|
503
529
|
case "AND": {
|
|
504
530
|
const futureSubgroup = _parse(tokens.slice(t + 1), _offset + t, constraints);
|
|
505
531
|
if (futureSubgroup === null)
|
|
506
|
-
throw new ParseError("Dangling junction operator", token, _offset + t);
|
|
532
|
+
throw new ParseError("Dangling junction operator", tokens, token, _offset + t);
|
|
507
533
|
return {
|
|
508
534
|
type: "group",
|
|
509
535
|
operation: "OR",
|
|
@@ -540,19 +566,21 @@ function _parse(tokens, _offset, constraints) {
|
|
|
540
566
|
}
|
|
541
567
|
continue;
|
|
542
568
|
}
|
|
543
|
-
if (token.content
|
|
569
|
+
if (NEGATORS.includes(token.content)) {
|
|
544
570
|
const nextToken = tokens[t + 1];
|
|
545
|
-
|
|
571
|
+
const nextParen = PARENS.find(([o]) => nextToken?.content === o);
|
|
572
|
+
if (nextParen) {
|
|
546
573
|
resolveCondition({
|
|
574
|
+
tokens,
|
|
547
575
|
startToken: field?.token ?? token,
|
|
548
576
|
startIndex: field?.index ?? _offset + t,
|
|
549
577
|
endToken: value?.token ?? comparisonOperation?.token ?? field?.token,
|
|
550
578
|
endIndex: value?.index ?? comparisonOperation?.index ?? field?.index
|
|
551
579
|
});
|
|
552
580
|
++t;
|
|
553
|
-
const closingIndex = getClosingIndex(tokens, t,
|
|
581
|
+
const closingIndex = getClosingIndex(tokens, t, nextParen[0], nextParen[1]);
|
|
554
582
|
if (closingIndex === -1)
|
|
555
|
-
throw new ParseError("Missing closing parenthesis for group", token, _offset + t);
|
|
583
|
+
throw new ParseError("Missing closing parenthesis for group", tokens, token, _offset + t);
|
|
556
584
|
++t;
|
|
557
585
|
const futureSubExpression = _parse(tokens.slice(t, closingIndex), _offset + t, constraints);
|
|
558
586
|
if (futureSubExpression) {
|
|
@@ -561,6 +589,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
561
589
|
expressions.push(...futureSubExpression.constituents);
|
|
562
590
|
else {
|
|
563
591
|
const group = getExpressionGroup({
|
|
592
|
+
tokens,
|
|
564
593
|
startToken: token,
|
|
565
594
|
startIndex: _offset + t,
|
|
566
595
|
endToken: tokens[closingIndex],
|
|
@@ -577,7 +606,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
577
606
|
if (!comparisonOperation || op && OPERATION_PURPOSE_DICTIONARY[op] === "comparison") {
|
|
578
607
|
if (op && OPERATION_PURPOSE_DICTIONARY[op] === "comparison") {
|
|
579
608
|
if (comparisonOperation || !field)
|
|
580
|
-
throw new ParseError("Unexpected comparison operator", field?.token ?? token, field?.index ?? _offset + t, token, _offset + t);
|
|
609
|
+
throw new ParseError("Unexpected comparison operator", tokens, field?.token ?? token, field?.index ?? _offset + t, token, _offset + t);
|
|
581
610
|
comparisonOperation = {
|
|
582
611
|
content: op,
|
|
583
612
|
token,
|
|
@@ -585,14 +614,15 @@ function _parse(tokens, _offset, constraints) {
|
|
|
585
614
|
};
|
|
586
615
|
continue;
|
|
587
616
|
} else if (field)
|
|
588
|
-
throw new ParseError("Expected a comparison operator", field.token, field.index, token, _offset + t);
|
|
617
|
+
throw new ParseError("Expected a comparison operator", tokens, field.token, field.index, token, _offset + t);
|
|
589
618
|
}
|
|
590
619
|
if (!field) {
|
|
591
|
-
if (token.content
|
|
620
|
+
if (NEGATORS.includes(token.content)) {
|
|
592
621
|
const nextToken = tokens[t + 1];
|
|
593
622
|
if (!nextToken)
|
|
594
|
-
throw new ParseError('Unexpected "!"', token, _offset + t);
|
|
623
|
+
throw new ParseError('Unexpected "!"', tokens, token, _offset + t);
|
|
595
624
|
resolveCondition({
|
|
625
|
+
tokens,
|
|
596
626
|
startToken: token,
|
|
597
627
|
startIndex: _offset + t,
|
|
598
628
|
endToken: nextToken,
|
|
@@ -600,7 +630,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
600
630
|
});
|
|
601
631
|
++t;
|
|
602
632
|
field = {
|
|
603
|
-
content:
|
|
633
|
+
content: nextToken.content,
|
|
604
634
|
token,
|
|
605
635
|
index: _offset + t
|
|
606
636
|
};
|
|
@@ -612,6 +642,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
612
642
|
implicit: true
|
|
613
643
|
};
|
|
614
644
|
resolveCondition({
|
|
645
|
+
tokens,
|
|
615
646
|
startToken: field.token,
|
|
616
647
|
startIndex: field.index,
|
|
617
648
|
endToken: nextToken,
|
|
@@ -619,7 +650,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
619
650
|
});
|
|
620
651
|
} else {
|
|
621
652
|
field = {
|
|
622
|
-
content:
|
|
653
|
+
content: token.content,
|
|
623
654
|
token,
|
|
624
655
|
index: _offset + t
|
|
625
656
|
};
|
|
@@ -627,17 +658,13 @@ function _parse(tokens, _offset, constraints) {
|
|
|
627
658
|
continue;
|
|
628
659
|
}
|
|
629
660
|
if (!value) {
|
|
630
|
-
|
|
661
|
+
const bracket = BRACKETS.find(([o]) => token.content === o);
|
|
662
|
+
if (bracket) {
|
|
631
663
|
let resolveEntry = function(subtoken, subindex) {
|
|
632
664
|
if (workingEntry) {
|
|
633
|
-
const {
|
|
634
|
-
unquoted: unquotedWorkingEntry
|
|
635
|
-
} = processToken(workingEntry);
|
|
636
665
|
const subquotes = workingEntry.match(QUOTE_REGEX);
|
|
637
666
|
if (subquotes && (subquotes.index !== 0 || subquotes[0].length !== workingEntry.length))
|
|
638
|
-
throw new ParseError("Quotes must surround entire values in arrays", firstEntryToken ?? subtoken, firstEntryTokenIndex ?? subindex, lastEntryToken ?? subtoken, lastEntryTokenIndex ?? subindex);
|
|
639
|
-
if (!unquotedWorkingEntry && (token.content === "[" && new RegExp(`${ESCAPE_REGEX}(?:\\[|\\])`).test(workingEntry) || token.content === "{" && new RegExp(`${ESCAPE_REGEX}(?:\\{|\\})`).test(workingEntry)))
|
|
640
|
-
throw new ParseError("Unescaped bracket in an array value", firstEntryToken ?? subtoken, firstEntryTokenIndex ?? subindex, lastEntryToken ?? subtoken, lastEntryTokenIndex ?? subindex);
|
|
667
|
+
throw new ParseError("Quotes must surround entire values in arrays", tokens, firstEntryToken ?? subtoken, firstEntryTokenIndex ?? subindex, lastEntryToken ?? subtoken, lastEntryTokenIndex ?? subindex);
|
|
641
668
|
arr.push(workingEntry);
|
|
642
669
|
workingEntry = "";
|
|
643
670
|
firstEntryToken = undefined;
|
|
@@ -646,9 +673,9 @@ function _parse(tokens, _offset, constraints) {
|
|
|
646
673
|
lastEntryTokenIndex = undefined;
|
|
647
674
|
}
|
|
648
675
|
};
|
|
649
|
-
const closingIndex = getClosingIndex(tokens, t,
|
|
676
|
+
const closingIndex = getClosingIndex(tokens, t, bracket[0], bracket[1]);
|
|
650
677
|
if (closingIndex === -1)
|
|
651
|
-
throw new ParseError("Missing closing bracket/brace for array value", token, _offset + t);
|
|
678
|
+
throw new ParseError("Missing closing bracket/brace for array value", tokens, token, _offset + t);
|
|
652
679
|
++t;
|
|
653
680
|
const arr = [];
|
|
654
681
|
value = {
|
|
@@ -664,9 +691,9 @@ function _parse(tokens, _offset, constraints) {
|
|
|
664
691
|
let lastEntryTokenIndex;
|
|
665
692
|
for (let ct = 0;ct < arrayContents.length; ++ct) {
|
|
666
693
|
const contentToken = arrayContents[ct];
|
|
667
|
-
if (contentToken.content
|
|
694
|
+
if (ARRAY_DELIMITERS.includes(contentToken.content)) {
|
|
668
695
|
if (!workingEntry)
|
|
669
|
-
throw new ParseError("Unexpected blank entry in array", contentToken, _offset + t + ct);
|
|
696
|
+
throw new ParseError("Unexpected blank entry in array", tokens, contentToken, _offset + t + ct);
|
|
670
697
|
resolveEntry(contentToken, _offset + t + ct);
|
|
671
698
|
} else {
|
|
672
699
|
lastEntryToken = contentToken;
|
|
@@ -680,7 +707,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
680
707
|
}
|
|
681
708
|
resolveEntry(arrayContents.at(-1), _offset + t + arrayContents.length - 1);
|
|
682
709
|
if (!arr.length) {
|
|
683
|
-
throw new ParseError("Empty array provided as value", token, _offset + t - 1, tokens[closingIndex], _offset + closingIndex);
|
|
710
|
+
throw new ParseError("Empty array provided as value", tokens, token, _offset + t - 1, tokens[closingIndex], _offset + closingIndex);
|
|
684
711
|
}
|
|
685
712
|
t = closingIndex;
|
|
686
713
|
} else {
|
|
@@ -691,6 +718,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
691
718
|
};
|
|
692
719
|
}
|
|
693
720
|
resolveCondition({
|
|
721
|
+
tokens,
|
|
694
722
|
startToken: field.token,
|
|
695
723
|
startIndex: field.index,
|
|
696
724
|
endToken: value.token ?? comparisonOperation?.token ?? field.token,
|
|
@@ -699,23 +727,24 @@ function _parse(tokens, _offset, constraints) {
|
|
|
699
727
|
}
|
|
700
728
|
}
|
|
701
729
|
resolveCondition({
|
|
730
|
+
tokens,
|
|
702
731
|
startToken: field?.token,
|
|
703
732
|
startIndex: field?.index,
|
|
704
733
|
endToken: value?.token ?? comparisonOperation?.token ?? field?.token,
|
|
705
734
|
endIndex: value?.index ?? comparisonOperation?.index ?? field?.index
|
|
706
735
|
});
|
|
707
736
|
if (inConjunction)
|
|
708
|
-
throw new ParseError("Dangling junction operator", tokens.at(-1), _offset + tokens.length - 1);
|
|
737
|
+
throw new ParseError("Dangling junction operator", tokens, tokens.at(-1), _offset + tokens.length - 1);
|
|
709
738
|
if (groupOperation) {
|
|
710
739
|
if (expressions.length === 1)
|
|
711
|
-
throw new ParseError("Dangling junction operator", tokens.at(-1), _offset + tokens.length - 1);
|
|
740
|
+
throw new ParseError("Dangling junction operator", tokens, tokens.at(-1), _offset + tokens.length - 1);
|
|
712
741
|
return {
|
|
713
742
|
type: "group",
|
|
714
743
|
operation: groupOperation,
|
|
715
744
|
constituents: expressions
|
|
716
745
|
};
|
|
717
746
|
} else if (expressions.length > 1)
|
|
718
|
-
throw new ParseError("Group possesses multiple conditions without disjunctive operators", tokens[0], _offset);
|
|
747
|
+
throw new ParseError("Group possesses multiple conditions without disjunctive operators", tokens, tokens[0], _offset);
|
|
719
748
|
else
|
|
720
749
|
return expressions[0] ?? null;
|
|
721
750
|
}
|
|
@@ -815,6 +844,10 @@ function summarize(expressions) {
|
|
|
815
844
|
return summary;
|
|
816
845
|
}
|
|
817
846
|
// src/stringify.ts
|
|
847
|
+
var DEFAULT_BRACKET = BRACKETS[0];
|
|
848
|
+
var DEFAULT_PAREN = PARENS[0];
|
|
849
|
+
var DEFAULT_NEGATOR = NEGATORS[0];
|
|
850
|
+
var DEFAULT_DELIMITER = ARRAY_DELIMITERS[0];
|
|
818
851
|
var formats = {
|
|
819
852
|
programmatic: {
|
|
820
853
|
AND: "&",
|
|
@@ -883,16 +916,16 @@ function stringify(expression, opts = {}) {
|
|
|
883
916
|
string += " ";
|
|
884
917
|
}
|
|
885
918
|
if (alwaysParenthesize && constituent.type === "group" || expression.operation === "AND" && constituent.operation === "OR")
|
|
886
|
-
string +=
|
|
919
|
+
string += DEFAULT_PAREN[0];
|
|
887
920
|
string += stringify(constituent, opts);
|
|
888
921
|
if (alwaysParenthesize && constituent.type === "group" || expression.operation === "AND" && constituent.operation === "OR")
|
|
889
|
-
string +=
|
|
922
|
+
string += DEFAULT_PAREN[1];
|
|
890
923
|
}
|
|
891
924
|
break;
|
|
892
925
|
case "condition":
|
|
893
926
|
if (condenseBooleans && ["EQUAL", "NOTEQUAL"].includes(expression.operation) && typeof expression.value === "boolean") {
|
|
894
927
|
const negative = expression.operation === "EQUAL" && !expression.value || expression.operation === "NOTEQUAL" && expression.value;
|
|
895
|
-
string += `${negative ?
|
|
928
|
+
string += `${negative ? DEFAULT_NEGATOR : ""}${expression.field}`;
|
|
896
929
|
} else {
|
|
897
930
|
string += expression.field;
|
|
898
931
|
if (!compact || comparisonNotation === "linguistic")
|
|
@@ -901,8 +934,8 @@ function stringify(expression, opts = {}) {
|
|
|
901
934
|
if (!compact || comparisonNotation === "linguistic")
|
|
902
935
|
string += " ";
|
|
903
936
|
if (Array.isArray(expression.value)) {
|
|
904
|
-
const join = expression.value.map(addQuotesIfNecessary).join(compact ?
|
|
905
|
-
string +=
|
|
937
|
+
const join = expression.value.map(addQuotesIfNecessary).join(compact ? DEFAULT_DELIMITER : DEFAULT_DELIMITER + " ");
|
|
938
|
+
string += `${DEFAULT_BRACKET[0]}${join}${DEFAULT_BRACKET[1]}`;
|
|
906
939
|
} else
|
|
907
940
|
string += addQuotesIfNecessary(expression.value);
|
|
908
941
|
}
|
|
@@ -982,17 +1015,17 @@ function createDOMInput({ input, constraints, onUpdate, parseOnInitialize }) {
|
|
|
982
1015
|
element.toggleAttribute("data-quoted", true);
|
|
983
1016
|
if (!isNaN(Number(token.content)))
|
|
984
1017
|
element.toggleAttribute("data-number", true);
|
|
985
|
-
if (
|
|
986
|
-
if (inArray
|
|
1018
|
+
if (PARENS.concat(BRACKETS).some((entry) => entry.includes(token.content))) {
|
|
1019
|
+
if (inArray && BRACKETS.some(([o, c]) => inArray === o && token.content === c))
|
|
987
1020
|
inArray = undefined;
|
|
988
1021
|
if (!inArray)
|
|
989
1022
|
element.setAttribute("data-bracket", token.content);
|
|
990
|
-
if (!inArray && [
|
|
1023
|
+
if (!inArray && BRACKETS.some(([o]) => o === token.content))
|
|
991
1024
|
inArray = token.content;
|
|
992
1025
|
}
|
|
993
|
-
if (inArray && token.content
|
|
1026
|
+
if (inArray && ARRAY_DELIMITERS.includes(token.content))
|
|
994
1027
|
element.toggleAttribute("data-delimiter", true);
|
|
995
|
-
if (!inArray && token.content
|
|
1028
|
+
if (!inArray && NEGATORS.includes(token.content))
|
|
996
1029
|
element.toggleAttribute("data-negation", true);
|
|
997
1030
|
if (!inArray && token.content in OPERATION_ALIAS_DICTIONARY) {
|
|
998
1031
|
element.setAttribute("data-operation", OPERATION_PURPOSE_DICTIONARY[OPERATION_ALIAS_DICTIONARY[token.content]]);
|
package/dist/parse.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface ExpressionConstraints<T extends TypeRecord, V extends boolean>
|
|
|
17
17
|
* By default allow any value and restrict a collection of values by passing ['deny', VALUES[]]\
|
|
18
18
|
* By default restrict any value and allow a collection of values by passing ['allow', VALUES[]]\
|
|
19
19
|
* If the query value is of array type, it will check all entries of the array and ensure they're all allowed.\
|
|
20
|
-
* This check runs before type
|
|
20
|
+
* This check runs before type coercion so the value checked will always be a string. However, quotes and escapes WILL be removed
|
|
21
21
|
*/
|
|
22
22
|
restricted?: Partial<Record<keyof T | (string & {}), boolean | ['allow' | 'deny', Array<string | RegExp>]>>;
|
|
23
23
|
/**
|
package/dist/spec.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export declare const QUOTES: string[];
|
|
2
|
+
export declare const BRACKETS: Array<[open: string, close: string]>;
|
|
3
|
+
export declare const PARENS: Array<[open: string, close: string]>;
|
|
4
|
+
export declare const NEGATORS: string[];
|
|
5
|
+
export declare const ARRAY_DELIMITERS: string[];
|
|
1
6
|
/** All available operation aliases (alias -> operation) */
|
|
2
7
|
export declare const OPERATION_ALIAS_DICTIONARY: {
|
|
3
8
|
readonly AND: "AND";
|
package/dist/stringify.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"homepage": "https://github.com/guidance-analytics/wizard-ql",
|
|
8
8
|
"description": "WizardQL is a natural-language-like query language for constructing data queries for resources that meet conditions.",
|
|
9
|
-
"version": "2.0
|
|
9
|
+
"version": "2.1.0",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "git+https://github.com/guidance-analytics/wizard-ql.git"
|