wizard-ql 2.0.1 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/errors.d.ts +8 -2
- package/dist/index.js +119 -88
- 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,13 @@
|
|
|
1
1
|
// src/spec.ts
|
|
2
|
+
var BRACKETS = [
|
|
3
|
+
["[", "]"],
|
|
4
|
+
["{", "}"]
|
|
5
|
+
];
|
|
6
|
+
var PARENS = [
|
|
7
|
+
["(", ")"]
|
|
8
|
+
];
|
|
9
|
+
var NEGATORS = ["!"];
|
|
10
|
+
var ARRAY_DELIMITERS = [","];
|
|
2
11
|
var OPERATION_ALIAS_DICTIONARY = {
|
|
3
12
|
AND: "AND",
|
|
4
13
|
"&&": "AND",
|
|
@@ -72,8 +81,6 @@ var COMPARISON_TYPE_DICTIONARY = {
|
|
|
72
81
|
var TYPE_PRIORITY = ["boolean", "date", "number", "string"];
|
|
73
82
|
// src/regex.ts
|
|
74
83
|
var ESCAPE_REGEX = "(?<=(?<!\\\\)(?:\\\\\\\\)*)";
|
|
75
|
-
var QUOTES = ["'", '"', "`"];
|
|
76
|
-
var QUOTE_REGEX_STR = `${ESCAPE_REGEX}(?<quote>${QUOTES.map((q) => RegExp.escape(q)).join("|")})(?<quotecontent>.*?)${ESCAPE_REGEX}\\k<quote>`;
|
|
77
84
|
|
|
78
85
|
// src/errors.ts
|
|
79
86
|
function constructHeader(startToken, startIndex, endToken, endIndex) {
|
|
@@ -106,9 +113,11 @@ class ParseError extends Error {
|
|
|
106
113
|
endToken;
|
|
107
114
|
startIndex;
|
|
108
115
|
endIndex;
|
|
109
|
-
|
|
116
|
+
tokens;
|
|
117
|
+
constructor(message, tokens, startToken, startIndex, endToken = startToken, endIndex = startIndex) {
|
|
110
118
|
const header = constructHeader(startToken, startIndex, endToken, endIndex);
|
|
111
119
|
super(header + message);
|
|
120
|
+
this.tokens = tokens;
|
|
112
121
|
this.rawMessage = message;
|
|
113
122
|
this.startToken = startToken;
|
|
114
123
|
this.endToken = endToken;
|
|
@@ -124,9 +133,11 @@ class ConstraintError extends Error {
|
|
|
124
133
|
endToken;
|
|
125
134
|
startIndex;
|
|
126
135
|
endIndex;
|
|
127
|
-
|
|
136
|
+
tokens;
|
|
137
|
+
constructor(message, tokens, startToken, startIndex, endToken = startToken, endIndex = startIndex) {
|
|
128
138
|
const header = constructHeader(startToken, startIndex, endToken, endIndex);
|
|
129
139
|
super(header + message);
|
|
140
|
+
this.tokens = tokens;
|
|
130
141
|
this.rawMessage = message;
|
|
131
142
|
this.startToken = startToken;
|
|
132
143
|
this.endToken = endToken;
|
|
@@ -136,7 +147,7 @@ class ConstraintError extends Error {
|
|
|
136
147
|
}
|
|
137
148
|
|
|
138
149
|
// 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|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\(|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\)|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\[|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\]|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\{|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\}|(?<=(?<!\\\\)(?:\\\\\\\\)*)\\
|
|
150
|
+
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
151
|
var QUOTE_REGEX = new RegExp("(?<=(?<!\\\\)(?:\\\\\\\\)*)(?<quote>\\x27|\\x22|\\x60)(?<quotecontent>.*?)(?<=(?<!\\\\)(?:\\\\\\\\)*)\\k<quote>");
|
|
141
152
|
var QUOTE_EDGE_REGEX = new RegExp(`^${"(?<=(?<!\\\\)(?:\\\\\\\\)*)(?<quote>\\x27|\\x22|\\x60)(?<quotecontent>.*?)(?<=(?<!\\\\)(?:\\\\\\\\)*)\\k<quote>"}$`);
|
|
142
153
|
function pushSanitized(array, item, index) {
|
|
@@ -156,20 +167,27 @@ function _tokenize(expression, pattern) {
|
|
|
156
167
|
let lastMatchEnd = null;
|
|
157
168
|
for (const match of matches) {
|
|
158
169
|
pushSanitized(tokens, expression.slice(lastMatchEnd ?? 0, match.index), lastMatchEnd === null ? 0 : lastMatchEnd);
|
|
159
|
-
if ([
|
|
170
|
+
if (BRACKETS.some(([o]) => match[0] === o)) {
|
|
160
171
|
const startToken = {
|
|
161
172
|
content: match[0],
|
|
162
173
|
index: match.index
|
|
163
174
|
};
|
|
164
175
|
tokens.push(startToken);
|
|
176
|
+
let subopenings = 0;
|
|
165
177
|
let endToken;
|
|
166
178
|
for (const submatch of matches) {
|
|
167
|
-
if (match[0] ===
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
179
|
+
if (match[0] === submatch[0] || match[0] === submatch[0])
|
|
180
|
+
++subopenings;
|
|
181
|
+
if (BRACKETS.some(([o, c]) => match[0] === o && submatch[0] === c)) {
|
|
182
|
+
if (subopenings)
|
|
183
|
+
--subopenings;
|
|
184
|
+
else {
|
|
185
|
+
endToken = {
|
|
186
|
+
content: submatch[0],
|
|
187
|
+
index: submatch.index
|
|
188
|
+
};
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
173
191
|
}
|
|
174
192
|
}
|
|
175
193
|
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 +218,7 @@ function processToken(token) {
|
|
|
200
218
|
const escaped = unquoted ?? token;
|
|
201
219
|
const unescaped = escaped.replaceAll(new RegExp(`(?<!${ESCAPE_REGEX}\\\\)\\\\`, "g"), "");
|
|
202
220
|
return {
|
|
221
|
+
raw: token,
|
|
203
222
|
unquoted,
|
|
204
223
|
escaped,
|
|
205
224
|
unescaped
|
|
@@ -270,8 +289,8 @@ function complementExpression(expression) {
|
|
|
270
289
|
break;
|
|
271
290
|
}
|
|
272
291
|
}
|
|
273
|
-
function coerceType(
|
|
274
|
-
const { unescaped } =
|
|
292
|
+
function coerceType(processedToken, fieldTypes, operatorTypes, dateInterpreter) {
|
|
293
|
+
const { raw, unescaped } = processedToken;
|
|
275
294
|
let operatorHits = 0;
|
|
276
295
|
let fieldHits = 0;
|
|
277
296
|
for (const type of TYPE_PRIORITY) {
|
|
@@ -285,13 +304,13 @@ function coerceType(value, fieldTypes, operatorTypes, dateInterpreter) {
|
|
|
285
304
|
continue;
|
|
286
305
|
switch (type) {
|
|
287
306
|
case "boolean":
|
|
288
|
-
if (
|
|
307
|
+
if (raw === "true")
|
|
289
308
|
return true;
|
|
290
|
-
else if (
|
|
309
|
+
else if (raw === "false")
|
|
291
310
|
return false;
|
|
292
311
|
break;
|
|
293
312
|
case "number": {
|
|
294
|
-
const num = Number(
|
|
313
|
+
const num = Number(raw);
|
|
295
314
|
if (isNaN(num))
|
|
296
315
|
break;
|
|
297
316
|
return num;
|
|
@@ -299,7 +318,7 @@ function coerceType(value, fieldTypes, operatorTypes, dateInterpreter) {
|
|
|
299
318
|
case "string":
|
|
300
319
|
return unescaped;
|
|
301
320
|
case "date": {
|
|
302
|
-
const num = Number(
|
|
321
|
+
const num = Number(raw);
|
|
303
322
|
const date = dateInterpreter(isNaN(num) ? unescaped : num);
|
|
304
323
|
if (isNaN(+date))
|
|
305
324
|
break;
|
|
@@ -319,46 +338,47 @@ function coerceType(value, fieldTypes, operatorTypes, dateInterpreter) {
|
|
|
319
338
|
}
|
|
320
339
|
function validateCondition(condition, constraints, valueIsImplicit, ctx) {
|
|
321
340
|
let validated = false;
|
|
341
|
+
condition.field = processToken(condition.field).unescaped;
|
|
322
342
|
const field = constraints?.caseInsensitive ? [...Object.keys(constraints.types ?? {}), ...Object.keys(constraints.restricted ?? {})].find((k) => k.toLowerCase() === condition.field.toLowerCase()) ?? condition.field : condition.field;
|
|
323
343
|
const restriction = constraints?.restricted?.[field];
|
|
324
344
|
const type = constraints?.types?.[field];
|
|
325
345
|
if (constraints?.disallowUnvalidated && restriction === undefined && type === undefined)
|
|
326
|
-
throw new ConstraintError(`Unknown field "${condition.field}"`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
346
|
+
throw new ConstraintError(`Unknown field "${condition.field}"`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
327
347
|
const operationType = COMPARISON_TYPE_DICTIONARY[condition.operation];
|
|
328
348
|
const types = type && (Array.isArray(type) ? type : [type]);
|
|
329
|
-
const
|
|
349
|
+
const processedValues = (Array.isArray(condition.value) ? condition.value : [condition.value]).map((v) => processToken(v));
|
|
330
350
|
if (restriction === true)
|
|
331
|
-
throw new ConstraintError(`Field "${condition.field}" is restricted`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
351
|
+
throw new ConstraintError(`Field "${condition.field}" is restricted`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
332
352
|
else if (Array.isArray(restriction)) {
|
|
333
353
|
const [philosophy, checks] = restriction;
|
|
334
354
|
if (philosophy === "deny") {
|
|
335
355
|
for (const check of checks) {
|
|
336
356
|
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);
|
|
357
|
+
if (processedValues.some(({ unescaped }) => check.test(unescaped))) {
|
|
358
|
+
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
359
|
}
|
|
340
360
|
} 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);
|
|
361
|
+
if (processedValues.some(({ unescaped }) => unescaped === check)) {
|
|
362
|
+
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
363
|
}
|
|
344
364
|
}
|
|
345
365
|
}
|
|
346
366
|
} 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);
|
|
367
|
+
for (const { unescaped } of processedValues) {
|
|
368
|
+
if (!checks.some((c) => c instanceof RegExp ? c.test(unescaped) : c === unescaped)) {
|
|
369
|
+
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
370
|
}
|
|
351
371
|
}
|
|
352
372
|
}
|
|
353
373
|
validated = true;
|
|
354
374
|
}
|
|
355
375
|
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);
|
|
376
|
+
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
377
|
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);
|
|
378
|
+
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
379
|
const dateInterpreter = constraints?.dateInterpreter ?? ((v) => new Date(v));
|
|
360
|
-
for (let i = 0;i <
|
|
361
|
-
const v =
|
|
380
|
+
for (let i = 0;i < processedValues.length; ++i) {
|
|
381
|
+
const v = processedValues[i];
|
|
362
382
|
let opTypes;
|
|
363
383
|
if (valueIsImplicit)
|
|
364
384
|
opTypes = ["boolean"];
|
|
@@ -382,15 +402,16 @@ function validateCondition(condition, constraints, valueIsImplicit, ctx) {
|
|
|
382
402
|
}
|
|
383
403
|
try {
|
|
384
404
|
const value = coerceType(v, types ?? opTypes, opTypes, dateInterpreter);
|
|
385
|
-
|
|
386
|
-
|
|
405
|
+
if (Array.isArray(condition.value))
|
|
406
|
+
condition.value[i] = value;
|
|
407
|
+
else
|
|
387
408
|
condition.value = value;
|
|
388
409
|
} catch (err) {
|
|
389
410
|
switch (err.message) {
|
|
390
411
|
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);
|
|
412
|
+
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
413
|
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);
|
|
414
|
+
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
415
|
}
|
|
395
416
|
}
|
|
396
417
|
}
|
|
@@ -413,15 +434,16 @@ function _parse(tokens, _offset, constraints) {
|
|
|
413
434
|
if (inConjunction) {
|
|
414
435
|
const prior = expressions.at(-1);
|
|
415
436
|
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);
|
|
437
|
+
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
438
|
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);
|
|
439
|
+
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
440
|
return prior.constituents;
|
|
420
441
|
}
|
|
421
442
|
return expressions;
|
|
422
443
|
}
|
|
423
444
|
function resolveCondition(ctx) {
|
|
424
445
|
const baseCtx = {
|
|
446
|
+
tokens,
|
|
425
447
|
startToken: field?.token ?? comparisonOperation?.token ?? value?.token,
|
|
426
448
|
startIndex: field?.index ?? comparisonOperation?.index ?? value?.index,
|
|
427
449
|
endToken: value?.token ?? comparisonOperation?.token ?? field?.token,
|
|
@@ -432,7 +454,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
432
454
|
const group = getExpressionGroup(ctx);
|
|
433
455
|
if (field && comparisonOperation && value) {
|
|
434
456
|
if (!expectingExpression)
|
|
435
|
-
throw new ParseError("Unexpected expression resolution before junctive operator", ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
457
|
+
throw new ParseError("Unexpected expression resolution before junctive operator", ctx.tokens, ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
436
458
|
group.push(validateCondition({
|
|
437
459
|
type: "condition",
|
|
438
460
|
field: field.content,
|
|
@@ -443,7 +465,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
443
465
|
expectingExpression = false;
|
|
444
466
|
} else if (field && !comparisonOperation && !value) {
|
|
445
467
|
if (!expectingExpression)
|
|
446
|
-
throw new ParseError("Unexpected expression resolution before junctive operator", ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
468
|
+
throw new ParseError("Unexpected expression resolution before junctive operator", ctx.tokens, ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
447
469
|
group.push(validateCondition({
|
|
448
470
|
type: "condition",
|
|
449
471
|
field: field.content,
|
|
@@ -453,30 +475,31 @@ function _parse(tokens, _offset, constraints) {
|
|
|
453
475
|
inConjunction = false;
|
|
454
476
|
expectingExpression = false;
|
|
455
477
|
} 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);
|
|
478
|
+
throw new ParseError("Failed to resolve condition; missing operand or operator", ctx.tokens, ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
457
479
|
field = undefined;
|
|
458
480
|
comparisonOperation = undefined;
|
|
459
481
|
value = undefined;
|
|
460
482
|
}
|
|
461
483
|
for (let t = 0;t < tokens.length; ++t) {
|
|
462
484
|
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
|
-
|
|
485
|
+
if (PARENS.some(([, c]) => token.content === c))
|
|
486
|
+
throw new ParseError("Unexpected closing parenthesis", tokens, token, _offset + t);
|
|
487
|
+
if (BRACKETS.some(([, c]) => token.content === c))
|
|
488
|
+
throw new ParseError("Unexpected closing bracket/brace", tokens, token, _offset + t);
|
|
489
|
+
const paren = PARENS.find(([o]) => token.content === o);
|
|
490
|
+
if (paren) {
|
|
468
491
|
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,
|
|
492
|
+
throw new ParseError("Tried to open a group during an operation", tokens, token, _offset + t);
|
|
493
|
+
const closingIndex = getClosingIndex(tokens, t, paren[0], paren[1]);
|
|
471
494
|
if (closingIndex === -1)
|
|
472
|
-
throw new ParseError("Missing closing parenthesis for group", token, _offset + t);
|
|
495
|
+
throw new ParseError("Missing closing parenthesis for group", tokens, token, _offset + t);
|
|
473
496
|
++t;
|
|
474
497
|
const subExpression = _parse(tokens.slice(t, closingIndex), _offset + t, constraints);
|
|
475
498
|
if (subExpression) {
|
|
476
499
|
if (subExpression.type === "group" && subExpression.operation === groupOperation)
|
|
477
500
|
expressions.push(...subExpression.constituents);
|
|
478
501
|
else {
|
|
479
|
-
const group = getExpressionGroup({ startToken: token, startIndex: _offset + t });
|
|
502
|
+
const group = getExpressionGroup({ tokens, startToken: token, startIndex: _offset + t });
|
|
480
503
|
group.push(subExpression);
|
|
481
504
|
inConjunction = false;
|
|
482
505
|
}
|
|
@@ -487,6 +510,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
487
510
|
const op = OPERATION_ALIAS_DICTIONARY[token.content];
|
|
488
511
|
if (op && OPERATION_PURPOSE_DICTIONARY[op] === "junction") {
|
|
489
512
|
resolveCondition({
|
|
513
|
+
tokens,
|
|
490
514
|
startToken: field?.token ?? token,
|
|
491
515
|
startIndex: field?.index ?? _offset + t,
|
|
492
516
|
endToken: token,
|
|
@@ -494,16 +518,16 @@ function _parse(tokens, _offset, constraints) {
|
|
|
494
518
|
});
|
|
495
519
|
const prior = expressions.at(-1);
|
|
496
520
|
if (!prior)
|
|
497
|
-
throw new ParseError("Unexpected junction operator with no preceding expression", token, _offset + t);
|
|
521
|
+
throw new ParseError("Unexpected junction operator with no preceding expression", tokens, token, _offset + t);
|
|
498
522
|
expectingExpression = true;
|
|
499
523
|
if (groupOperation && groupOperation !== op) {
|
|
500
524
|
if (expressions.length < 2)
|
|
501
|
-
throw new ParseError("Unexpected junction operator with no preceding expression", token, _offset + t);
|
|
525
|
+
throw new ParseError("Unexpected junction operator with no preceding expression", tokens, token, _offset + t);
|
|
502
526
|
switch (groupOperation) {
|
|
503
527
|
case "AND": {
|
|
504
528
|
const futureSubgroup = _parse(tokens.slice(t + 1), _offset + t, constraints);
|
|
505
529
|
if (futureSubgroup === null)
|
|
506
|
-
throw new ParseError("Dangling junction operator", token, _offset + t);
|
|
530
|
+
throw new ParseError("Dangling junction operator", tokens, token, _offset + t);
|
|
507
531
|
return {
|
|
508
532
|
type: "group",
|
|
509
533
|
operation: "OR",
|
|
@@ -540,19 +564,21 @@ function _parse(tokens, _offset, constraints) {
|
|
|
540
564
|
}
|
|
541
565
|
continue;
|
|
542
566
|
}
|
|
543
|
-
if (token.content
|
|
567
|
+
if (NEGATORS.includes(token.content)) {
|
|
544
568
|
const nextToken = tokens[t + 1];
|
|
545
|
-
|
|
569
|
+
const nextParen = PARENS.find(([o]) => nextToken?.content === o);
|
|
570
|
+
if (nextParen) {
|
|
546
571
|
resolveCondition({
|
|
572
|
+
tokens,
|
|
547
573
|
startToken: field?.token ?? token,
|
|
548
574
|
startIndex: field?.index ?? _offset + t,
|
|
549
575
|
endToken: value?.token ?? comparisonOperation?.token ?? field?.token,
|
|
550
576
|
endIndex: value?.index ?? comparisonOperation?.index ?? field?.index
|
|
551
577
|
});
|
|
552
578
|
++t;
|
|
553
|
-
const closingIndex = getClosingIndex(tokens, t,
|
|
579
|
+
const closingIndex = getClosingIndex(tokens, t, nextParen[0], nextParen[1]);
|
|
554
580
|
if (closingIndex === -1)
|
|
555
|
-
throw new ParseError("Missing closing parenthesis for group", token, _offset + t);
|
|
581
|
+
throw new ParseError("Missing closing parenthesis for group", tokens, token, _offset + t);
|
|
556
582
|
++t;
|
|
557
583
|
const futureSubExpression = _parse(tokens.slice(t, closingIndex), _offset + t, constraints);
|
|
558
584
|
if (futureSubExpression) {
|
|
@@ -561,6 +587,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
561
587
|
expressions.push(...futureSubExpression.constituents);
|
|
562
588
|
else {
|
|
563
589
|
const group = getExpressionGroup({
|
|
590
|
+
tokens,
|
|
564
591
|
startToken: token,
|
|
565
592
|
startIndex: _offset + t,
|
|
566
593
|
endToken: tokens[closingIndex],
|
|
@@ -577,7 +604,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
577
604
|
if (!comparisonOperation || op && OPERATION_PURPOSE_DICTIONARY[op] === "comparison") {
|
|
578
605
|
if (op && OPERATION_PURPOSE_DICTIONARY[op] === "comparison") {
|
|
579
606
|
if (comparisonOperation || !field)
|
|
580
|
-
throw new ParseError("Unexpected comparison operator", field?.token ?? token, field?.index ?? _offset + t, token, _offset + t);
|
|
607
|
+
throw new ParseError("Unexpected comparison operator", tokens, field?.token ?? token, field?.index ?? _offset + t, token, _offset + t);
|
|
581
608
|
comparisonOperation = {
|
|
582
609
|
content: op,
|
|
583
610
|
token,
|
|
@@ -585,14 +612,15 @@ function _parse(tokens, _offset, constraints) {
|
|
|
585
612
|
};
|
|
586
613
|
continue;
|
|
587
614
|
} else if (field)
|
|
588
|
-
throw new ParseError("Expected a comparison operator", field.token, field.index, token, _offset + t);
|
|
615
|
+
throw new ParseError("Expected a comparison operator", tokens, field.token, field.index, token, _offset + t);
|
|
589
616
|
}
|
|
590
617
|
if (!field) {
|
|
591
|
-
if (token.content
|
|
618
|
+
if (NEGATORS.includes(token.content)) {
|
|
592
619
|
const nextToken = tokens[t + 1];
|
|
593
620
|
if (!nextToken)
|
|
594
|
-
throw new ParseError('Unexpected "!"', token, _offset + t);
|
|
621
|
+
throw new ParseError('Unexpected "!"', tokens, token, _offset + t);
|
|
595
622
|
resolveCondition({
|
|
623
|
+
tokens,
|
|
596
624
|
startToken: token,
|
|
597
625
|
startIndex: _offset + t,
|
|
598
626
|
endToken: nextToken,
|
|
@@ -600,7 +628,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
600
628
|
});
|
|
601
629
|
++t;
|
|
602
630
|
field = {
|
|
603
|
-
content:
|
|
631
|
+
content: nextToken.content,
|
|
604
632
|
token,
|
|
605
633
|
index: _offset + t
|
|
606
634
|
};
|
|
@@ -612,6 +640,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
612
640
|
implicit: true
|
|
613
641
|
};
|
|
614
642
|
resolveCondition({
|
|
643
|
+
tokens,
|
|
615
644
|
startToken: field.token,
|
|
616
645
|
startIndex: field.index,
|
|
617
646
|
endToken: nextToken,
|
|
@@ -619,7 +648,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
619
648
|
});
|
|
620
649
|
} else {
|
|
621
650
|
field = {
|
|
622
|
-
content:
|
|
651
|
+
content: token.content,
|
|
623
652
|
token,
|
|
624
653
|
index: _offset + t
|
|
625
654
|
};
|
|
@@ -627,17 +656,13 @@ function _parse(tokens, _offset, constraints) {
|
|
|
627
656
|
continue;
|
|
628
657
|
}
|
|
629
658
|
if (!value) {
|
|
630
|
-
|
|
659
|
+
const bracket = BRACKETS.find(([o]) => token.content === o);
|
|
660
|
+
if (bracket) {
|
|
631
661
|
let resolveEntry = function(subtoken, subindex) {
|
|
632
662
|
if (workingEntry) {
|
|
633
|
-
const {
|
|
634
|
-
unquoted: unquotedWorkingEntry
|
|
635
|
-
} = processToken(workingEntry);
|
|
636
663
|
const subquotes = workingEntry.match(QUOTE_REGEX);
|
|
637
664
|
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);
|
|
665
|
+
throw new ParseError("Quotes must surround entire values in arrays", tokens, firstEntryToken ?? subtoken, firstEntryTokenIndex ?? subindex, lastEntryToken ?? subtoken, lastEntryTokenIndex ?? subindex);
|
|
641
666
|
arr.push(workingEntry);
|
|
642
667
|
workingEntry = "";
|
|
643
668
|
firstEntryToken = undefined;
|
|
@@ -646,9 +671,9 @@ function _parse(tokens, _offset, constraints) {
|
|
|
646
671
|
lastEntryTokenIndex = undefined;
|
|
647
672
|
}
|
|
648
673
|
};
|
|
649
|
-
const closingIndex = getClosingIndex(tokens, t,
|
|
674
|
+
const closingIndex = getClosingIndex(tokens, t, bracket[0], bracket[1]);
|
|
650
675
|
if (closingIndex === -1)
|
|
651
|
-
throw new ParseError("Missing closing bracket/brace for array value", token, _offset + t);
|
|
676
|
+
throw new ParseError("Missing closing bracket/brace for array value", tokens, token, _offset + t);
|
|
652
677
|
++t;
|
|
653
678
|
const arr = [];
|
|
654
679
|
value = {
|
|
@@ -664,9 +689,9 @@ function _parse(tokens, _offset, constraints) {
|
|
|
664
689
|
let lastEntryTokenIndex;
|
|
665
690
|
for (let ct = 0;ct < arrayContents.length; ++ct) {
|
|
666
691
|
const contentToken = arrayContents[ct];
|
|
667
|
-
if (contentToken.content
|
|
692
|
+
if (ARRAY_DELIMITERS.includes(contentToken.content)) {
|
|
668
693
|
if (!workingEntry)
|
|
669
|
-
throw new ParseError("Unexpected blank entry in array", contentToken, _offset + t + ct);
|
|
694
|
+
throw new ParseError("Unexpected blank entry in array", tokens, contentToken, _offset + t + ct);
|
|
670
695
|
resolveEntry(contentToken, _offset + t + ct);
|
|
671
696
|
} else {
|
|
672
697
|
lastEntryToken = contentToken;
|
|
@@ -680,7 +705,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
680
705
|
}
|
|
681
706
|
resolveEntry(arrayContents.at(-1), _offset + t + arrayContents.length - 1);
|
|
682
707
|
if (!arr.length) {
|
|
683
|
-
throw new ParseError("Empty array provided as value", token, _offset + t - 1, tokens[closingIndex], _offset + closingIndex);
|
|
708
|
+
throw new ParseError("Empty array provided as value", tokens, token, _offset + t - 1, tokens[closingIndex], _offset + closingIndex);
|
|
684
709
|
}
|
|
685
710
|
t = closingIndex;
|
|
686
711
|
} else {
|
|
@@ -691,6 +716,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
691
716
|
};
|
|
692
717
|
}
|
|
693
718
|
resolveCondition({
|
|
719
|
+
tokens,
|
|
694
720
|
startToken: field.token,
|
|
695
721
|
startIndex: field.index,
|
|
696
722
|
endToken: value.token ?? comparisonOperation?.token ?? field.token,
|
|
@@ -699,23 +725,24 @@ function _parse(tokens, _offset, constraints) {
|
|
|
699
725
|
}
|
|
700
726
|
}
|
|
701
727
|
resolveCondition({
|
|
728
|
+
tokens,
|
|
702
729
|
startToken: field?.token,
|
|
703
730
|
startIndex: field?.index,
|
|
704
731
|
endToken: value?.token ?? comparisonOperation?.token ?? field?.token,
|
|
705
732
|
endIndex: value?.index ?? comparisonOperation?.index ?? field?.index
|
|
706
733
|
});
|
|
707
734
|
if (inConjunction)
|
|
708
|
-
throw new ParseError("Dangling junction operator", tokens.at(-1), _offset + tokens.length - 1);
|
|
735
|
+
throw new ParseError("Dangling junction operator", tokens, tokens.at(-1), _offset + tokens.length - 1);
|
|
709
736
|
if (groupOperation) {
|
|
710
737
|
if (expressions.length === 1)
|
|
711
|
-
throw new ParseError("Dangling junction operator", tokens.at(-1), _offset + tokens.length - 1);
|
|
738
|
+
throw new ParseError("Dangling junction operator", tokens, tokens.at(-1), _offset + tokens.length - 1);
|
|
712
739
|
return {
|
|
713
740
|
type: "group",
|
|
714
741
|
operation: groupOperation,
|
|
715
742
|
constituents: expressions
|
|
716
743
|
};
|
|
717
744
|
} else if (expressions.length > 1)
|
|
718
|
-
throw new ParseError("Group possesses multiple conditions without disjunctive operators", tokens[0], _offset);
|
|
745
|
+
throw new ParseError("Group possesses multiple conditions without disjunctive operators", tokens, tokens[0], _offset);
|
|
719
746
|
else
|
|
720
747
|
return expressions[0] ?? null;
|
|
721
748
|
}
|
|
@@ -815,6 +842,10 @@ function summarize(expressions) {
|
|
|
815
842
|
return summary;
|
|
816
843
|
}
|
|
817
844
|
// src/stringify.ts
|
|
845
|
+
var DEFAULT_BRACKET = BRACKETS[0];
|
|
846
|
+
var DEFAULT_PAREN = PARENS[0];
|
|
847
|
+
var DEFAULT_NEGATOR = NEGATORS[0];
|
|
848
|
+
var DEFAULT_DELIMITER = ARRAY_DELIMITERS[0];
|
|
818
849
|
var formats = {
|
|
819
850
|
programmatic: {
|
|
820
851
|
AND: "&",
|
|
@@ -883,16 +914,16 @@ function stringify(expression, opts = {}) {
|
|
|
883
914
|
string += " ";
|
|
884
915
|
}
|
|
885
916
|
if (alwaysParenthesize && constituent.type === "group" || expression.operation === "AND" && constituent.operation === "OR")
|
|
886
|
-
string +=
|
|
917
|
+
string += DEFAULT_PAREN[0];
|
|
887
918
|
string += stringify(constituent, opts);
|
|
888
919
|
if (alwaysParenthesize && constituent.type === "group" || expression.operation === "AND" && constituent.operation === "OR")
|
|
889
|
-
string +=
|
|
920
|
+
string += DEFAULT_PAREN[1];
|
|
890
921
|
}
|
|
891
922
|
break;
|
|
892
923
|
case "condition":
|
|
893
924
|
if (condenseBooleans && ["EQUAL", "NOTEQUAL"].includes(expression.operation) && typeof expression.value === "boolean") {
|
|
894
925
|
const negative = expression.operation === "EQUAL" && !expression.value || expression.operation === "NOTEQUAL" && expression.value;
|
|
895
|
-
string += `${negative ?
|
|
926
|
+
string += `${negative ? DEFAULT_NEGATOR : ""}${expression.field}`;
|
|
896
927
|
} else {
|
|
897
928
|
string += expression.field;
|
|
898
929
|
if (!compact || comparisonNotation === "linguistic")
|
|
@@ -901,8 +932,8 @@ function stringify(expression, opts = {}) {
|
|
|
901
932
|
if (!compact || comparisonNotation === "linguistic")
|
|
902
933
|
string += " ";
|
|
903
934
|
if (Array.isArray(expression.value)) {
|
|
904
|
-
const join = expression.value.map(addQuotesIfNecessary).join(compact ?
|
|
905
|
-
string +=
|
|
935
|
+
const join = expression.value.map(addQuotesIfNecessary).join(compact ? DEFAULT_DELIMITER : DEFAULT_DELIMITER + " ");
|
|
936
|
+
string += `${DEFAULT_BRACKET[0]}${join}${DEFAULT_BRACKET[1]}`;
|
|
906
937
|
} else
|
|
907
938
|
string += addQuotesIfNecessary(expression.value);
|
|
908
939
|
}
|
|
@@ -982,17 +1013,17 @@ function createDOMInput({ input, constraints, onUpdate, parseOnInitialize }) {
|
|
|
982
1013
|
element.toggleAttribute("data-quoted", true);
|
|
983
1014
|
if (!isNaN(Number(token.content)))
|
|
984
1015
|
element.toggleAttribute("data-number", true);
|
|
985
|
-
if (
|
|
986
|
-
if (inArray
|
|
1016
|
+
if (PARENS.concat(BRACKETS).some((entry) => entry.includes(token.content))) {
|
|
1017
|
+
if (inArray && BRACKETS.some(([o, c]) => inArray === o && token.content === c))
|
|
987
1018
|
inArray = undefined;
|
|
988
1019
|
if (!inArray)
|
|
989
1020
|
element.setAttribute("data-bracket", token.content);
|
|
990
|
-
if (!inArray && [
|
|
1021
|
+
if (!inArray && BRACKETS.some(([o]) => o === token.content))
|
|
991
1022
|
inArray = token.content;
|
|
992
1023
|
}
|
|
993
|
-
if (inArray && token.content
|
|
1024
|
+
if (inArray && ARRAY_DELIMITERS.includes(token.content))
|
|
994
1025
|
element.toggleAttribute("data-delimiter", true);
|
|
995
|
-
if (!inArray && token.content
|
|
1026
|
+
if (!inArray && NEGATORS.includes(token.content))
|
|
996
1027
|
element.toggleAttribute("data-negation", true);
|
|
997
1028
|
if (!inArray && token.content in OPERATION_ALIAS_DICTIONARY) {
|
|
998
1029
|
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.
|
|
9
|
+
"version": "2.1.1",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "git+https://github.com/guidance-analytics/wizard-ql.git"
|