@xano/xanoscript-language-server 11.12.0 → 11.12.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/.claude/memory/feedback_no_git.md +11 -0
- package/.claude/settings.local.json +13 -28
- package/lexer/cast.js +3 -3
- package/lexer/control.js +0 -2
- package/lexer/literal.js +2 -2
- package/lexer/security.js +2 -2
- package/package.json +1 -1
- package/parser/attributes/columnDefaultValueAttribute.js +10 -3
- package/parser/attributes/inputFilterFn.js +11 -4
- package/parser/clauses/flexibleStringClause.js +2 -2
- package/parser/clauses/nakedStackFn.js +43 -36
- package/parser/clauses/nakedStackFn.spec.js +10 -0
- package/parser/clauses/responseClause.spec.js +5 -0
- package/parser/definitions/columnDefinition.js +7 -2
- package/parser/functions/array/arrayNoValueAs.js +1 -1
- package/parser/functions/array/arrayPopFn.js +1 -3
- package/parser/functions/controls/forEachFn.spec.js +8 -0
- package/parser/functions/controls/preconditionFn.js +3 -0
- package/parser/functions/db/dbExternalDirectQueryFn.js +1 -0
- package/parser/functions/db/dbQueryFn.spec.js +31 -0
- package/parser/functions/schema/schemaParseConstantFn.js +11 -10
- package/parser/functions/varFn.js +14 -2
- package/parser/functions/varFn.spec.js +20 -12
- package/parser/generic/asVariable.spec.js +12 -8
- package/parser/generic/assignableVariableAs.js +50 -11
- package/parser/generic/assignableVariableAs.spec.js +29 -7
- package/parser/generic/assignableVariableProperty.js +22 -10
- package/parser/generic/assignableVariableProperty.spec.js +21 -12
- package/parser/generic/expressionFn.js +9 -2
- package/parser/generic/expressionFn.spec.js +5 -0
- package/parser/generic/filterNames.js +9 -0
- package/parser/multidoc.js +38 -17
- package/parser/multidoc.spec.js +7 -1
- package/parser/query_parser.js +11 -3
- package/parser/query_parser.spec.js +0 -3
- package/parser/tests/function/function_parser.spec.js +4 -4
- package/parser/tests/multidoc/multidoc_parser.spec.js +55 -0
- package/parser/tests/multidoc/valid_sources/function_stack.xs +130603 -0
- package/parser/tests/query/query_parser.spec.js +3 -3
- package/parser/tool_parser.js +12 -3
- package/parser/tool_parser.spec.js +19 -6
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: no-git-interactions
|
|
3
|
+
description: User does not want Claude to interact with git (no commits, no staging, no git commands)
|
|
4
|
+
type: feedback
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Do not interact with git — no commits, no staging, no git commands. Focus purely on implementation and testing.
|
|
8
|
+
|
|
9
|
+
**Why:** User manages their own git workflow.
|
|
10
|
+
|
|
11
|
+
**How to apply:** Skip all git-related steps in plans. When dispatching subagents, explicitly tell them not to run any git commands. Remove commit steps from task instructions.
|
|
@@ -2,35 +2,20 @@
|
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
4
|
"Bash(npm test:*)",
|
|
5
|
-
"Bash(
|
|
6
|
-
"Bash(
|
|
7
|
-
"Bash(
|
|
8
|
-
"Bash(node:*)",
|
|
9
|
-
"Bash(npm install)",
|
|
5
|
+
"Bash(git checkout:*)",
|
|
6
|
+
"Bash(ls:*)",
|
|
7
|
+
"Bash(npm run lint:*)",
|
|
10
8
|
"Bash(grep:*)",
|
|
11
|
-
"Bash(git -C /Users/justinalbrecht/git/xs-language-server log --all --pretty=format:\"%h %s\")",
|
|
12
|
-
"Bash(git -C /Users/justinalbrecht/git/xs-language-server log --all -S \"password\\\\|secret\\\\|api_key\\\\|token\" --oneline)",
|
|
13
|
-
"Bash(xargs file:*)",
|
|
14
9
|
"Bash(find:*)",
|
|
15
|
-
"
|
|
16
|
-
"Bash(git
|
|
17
|
-
"Bash(
|
|
18
|
-
"Bash(
|
|
19
|
-
"Bash(
|
|
20
|
-
"Bash(
|
|
21
|
-
"Bash(
|
|
22
|
-
"Bash(
|
|
23
|
-
"Bash(
|
|
24
|
-
|
|
25
|
-
"Bash(git push)",
|
|
26
|
-
"Bash(npm whoami:*)",
|
|
27
|
-
"Bash(npm view:*)",
|
|
28
|
-
"Bash(npm version:*)",
|
|
29
|
-
"Bash(npm publish:*)",
|
|
30
|
-
"Bash(wc:*)",
|
|
31
|
-
"Bash(git push:*)"
|
|
32
|
-
],
|
|
33
|
-
"deny": [],
|
|
34
|
-
"ask": []
|
|
10
|
+
"Read(//tmp/**)",
|
|
11
|
+
"Bash(git stash:*)",
|
|
12
|
+
"Bash(do)",
|
|
13
|
+
"Bash(if ! grep -q \"guidFieldAttribute\" \"$f\")",
|
|
14
|
+
"Bash(then)",
|
|
15
|
+
"Bash(echo:*)",
|
|
16
|
+
"Bash(fi)",
|
|
17
|
+
"Bash(done)",
|
|
18
|
+
"Bash(npx eslint:*)"
|
|
19
|
+
]
|
|
35
20
|
}
|
|
36
21
|
}
|
package/lexer/cast.js
CHANGED
|
@@ -23,14 +23,14 @@ export const BoolCastToken = createUniqToken({
|
|
|
23
23
|
// !array
|
|
24
24
|
export const ArrayCastToken = createUniqToken({
|
|
25
25
|
name: "ArrayCast",
|
|
26
|
-
pattern: /!array/,
|
|
26
|
+
pattern: /!array(?!\.)/,
|
|
27
27
|
label: "!array",
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
// !text
|
|
31
31
|
export const TextCastToken = createUniqToken({
|
|
32
32
|
name: "TextCast",
|
|
33
|
-
pattern: /!text/,
|
|
33
|
+
pattern: /!text(?!\.)/,
|
|
34
34
|
label: "!text",
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -51,7 +51,7 @@ export const DecimalCastToken = createUniqToken({
|
|
|
51
51
|
// !object
|
|
52
52
|
export const ObjectCastToken = createUniqToken({
|
|
53
53
|
name: "ObjectCast",
|
|
54
|
-
pattern: /!object/,
|
|
54
|
+
pattern: /!object(?!\.)/,
|
|
55
55
|
label: "!object",
|
|
56
56
|
});
|
|
57
57
|
|
package/lexer/control.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Identifier } from "./identifier.js";
|
|
2
|
-
import { IntegerLiteral } from "./literal.js";
|
|
3
2
|
import { createTokenByName, createUniqToken } from "./utils.js";
|
|
4
3
|
|
|
5
4
|
// conditional
|
|
@@ -173,7 +172,6 @@ export const Minus = createUniqToken({
|
|
|
173
172
|
name: "Minus",
|
|
174
173
|
pattern: /-/,
|
|
175
174
|
label: "-",
|
|
176
|
-
longer_alt: IntegerLiteral,
|
|
177
175
|
});
|
|
178
176
|
|
|
179
177
|
// +
|
package/lexer/literal.js
CHANGED
|
@@ -24,13 +24,13 @@ export const SingleQuotedStringLiteral = createUniqToken({
|
|
|
24
24
|
export const FloatLiteral = createUniqToken({
|
|
25
25
|
name: "FloatLiteral",
|
|
26
26
|
label: "floating point number",
|
|
27
|
-
pattern:
|
|
27
|
+
pattern: /\d+\.\d+([eE][+-]?\d+)?/,
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
export const IntegerLiteral = createUniqToken({
|
|
31
31
|
name: "IntegerLiteral",
|
|
32
32
|
label: "integer",
|
|
33
|
-
pattern:
|
|
33
|
+
pattern: /\d+/,
|
|
34
34
|
longer_alt: FloatLiteral,
|
|
35
35
|
});
|
|
36
36
|
|
package/lexer/security.js
CHANGED
|
@@ -51,13 +51,13 @@ export const CreateSecretKeyToken = createTokenByName("create_secret_key", {
|
|
|
51
51
|
|
|
52
52
|
// decrypt
|
|
53
53
|
export const DecryptToken = createTokenByName("decrypt", {
|
|
54
|
-
|
|
54
|
+
longer_alt: Identifier,
|
|
55
55
|
categories: [Identifier],
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
// encrypt
|
|
59
59
|
export const EncryptToken = createTokenByName("encrypt", {
|
|
60
|
-
|
|
60
|
+
longer_alt: Identifier,
|
|
61
61
|
categories: [Identifier],
|
|
62
62
|
});
|
|
63
63
|
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EqualToken } from "../../lexer/control.js";
|
|
1
|
+
import { EqualToken, Minus } from "../../lexer/control.js";
|
|
2
2
|
import {
|
|
3
3
|
FloatLiteral,
|
|
4
4
|
IntegerLiteral,
|
|
@@ -21,8 +21,15 @@ export function columnDefaultValueAttribute($) {
|
|
|
21
21
|
$.CONSUME(EqualToken); // "="
|
|
22
22
|
$.OR([
|
|
23
23
|
{ ALT: () => $.CONSUME(StringLiteral) }, // e.g., "default value"
|
|
24
|
-
{
|
|
25
|
-
|
|
24
|
+
{
|
|
25
|
+
ALT: () => {
|
|
26
|
+
$.OPTION(() => $.CONSUME(Minus));
|
|
27
|
+
return $.OR1([
|
|
28
|
+
{ ALT: () => $.CONSUME(IntegerLiteral) },
|
|
29
|
+
{ ALT: () => $.CONSUME(FloatLiteral) },
|
|
30
|
+
]);
|
|
31
|
+
},
|
|
32
|
+
},
|
|
26
33
|
{ ALT: () => $.CONSUME(TrueToken) }, // true
|
|
27
34
|
{ ALT: () => $.CONSUME(FalseToken) }, // false
|
|
28
35
|
{ ALT: () => $.CONSUME(NowToken) }, // now
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ExclamationToken } from "../../lexer/cast.js";
|
|
2
|
-
import { ColonToken } from "../../lexer/control.js";
|
|
2
|
+
import { ColonToken, Minus } from "../../lexer/control.js";
|
|
3
3
|
import {
|
|
4
4
|
FloatLiteral,
|
|
5
5
|
IntegerLiteral,
|
|
@@ -36,8 +36,15 @@ export function inputFilterFn($) {
|
|
|
36
36
|
{ ALT: () => $.CONSUME2(Identifier) },
|
|
37
37
|
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
38
38
|
{ ALT: () => $.CONSUME1(RegExpToken) },
|
|
39
|
-
{
|
|
40
|
-
|
|
39
|
+
{
|
|
40
|
+
ALT: () => {
|
|
41
|
+
$.OPTION1(() => $.CONSUME(Minus));
|
|
42
|
+
$.OR1([
|
|
43
|
+
{ ALT: () => $.CONSUME(IntegerLiteral) },
|
|
44
|
+
{ ALT: () => $.CONSUME(FloatLiteral) },
|
|
45
|
+
]);
|
|
46
|
+
},
|
|
47
|
+
},
|
|
41
48
|
{ ALT: () => $.CONSUME1(FalseToken) },
|
|
42
49
|
{ ALT: () => $.CONSUME1(TrueToken) },
|
|
43
50
|
]);
|
|
@@ -52,7 +59,7 @@ export function inputFilterFn($) {
|
|
|
52
59
|
) {
|
|
53
60
|
$.addInvalidValueError(
|
|
54
61
|
filter,
|
|
55
|
-
`Filter '${filter.image}' cannot be applied to input of type '${inputTypeToken.image}'
|
|
62
|
+
`Filter '${filter.image}' cannot be applied to input of type '${inputTypeToken.image}'`,
|
|
56
63
|
);
|
|
57
64
|
}
|
|
58
65
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EqualToken } from "../../lexer/control.js";
|
|
2
|
-
import { Identifier } from "../../lexer/identifier.js";
|
|
3
2
|
import { StringLiteral } from "../../lexer/literal.js";
|
|
4
3
|
import { MultiLineStringToken } from "../../lexer/multiline.js";
|
|
4
|
+
import { InstructionsToken } from "../../lexer/tool.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @param {import('../base_parser.js').XanoBaseParser} $
|
|
@@ -14,7 +14,7 @@ export function flexibleStringClause($) {
|
|
|
14
14
|
return (args) => {
|
|
15
15
|
// During parser initialization, args is undefined
|
|
16
16
|
// Use a placeholder token for grammar recording
|
|
17
|
-
const token = args?.[0] ||
|
|
17
|
+
const token = args?.[0] || InstructionsToken;
|
|
18
18
|
const allowEmpty = args?.[1] ?? true;
|
|
19
19
|
|
|
20
20
|
$.sectionStack.push(token.name + "Clause");
|
|
@@ -20,46 +20,53 @@ export function nakedStackFn($) {
|
|
|
20
20
|
$.CONSUME(LCurly); // "{"
|
|
21
21
|
$.MANY(() => {
|
|
22
22
|
$.AT_LEAST_ONE1(() => $.CONSUME1(NewlineToken)); // required new line before each clause
|
|
23
|
-
$.OPTION(() => $.CONSUME(ExclamationToken)); // optional "!" to continue on error
|
|
23
|
+
// $.OPTION(() => $.CONSUME(ExclamationToken)); // optional "!" to continue on error
|
|
24
24
|
$.OR([
|
|
25
25
|
{ ALT: () => $.CONSUME(CommentToken) },
|
|
26
|
-
{ ALT: () => $.SUBRULE($.aiFn) },
|
|
27
|
-
{ ALT: () => $.SUBRULE($.dbFn) },
|
|
28
|
-
{ ALT: () => $.SUBRULE($.varFn) },
|
|
29
|
-
{ ALT: () => $.SUBRULE($.arrayFn) },
|
|
30
|
-
{ ALT: () => $.SUBRULE($.cloudFn) },
|
|
31
|
-
{ ALT: () => $.SUBRULE($.securityFn) },
|
|
32
|
-
{ ALT: () => $.SUBRULE($.mathFn) },
|
|
33
|
-
{ ALT: () => $.SUBRULE($.textFn) },
|
|
34
|
-
{ ALT: () => $.SUBRULE($.utilFn) },
|
|
35
|
-
{ ALT: () => $.SUBRULE($.preconditionFn) },
|
|
36
|
-
{ ALT: () => $.SUBRULE($.zipFn) },
|
|
37
|
-
{ ALT: () => $.SUBRULE($.objectFn) },
|
|
38
|
-
{ ALT: () => $.SUBRULE($.apiFn, { ARGS: [options] }) },
|
|
39
|
-
{ ALT: () => $.SUBRULE($.webflowFn) },
|
|
40
|
-
{ ALT: () => $.SUBRULE($.redisFn) },
|
|
41
|
-
{ ALT: () => $.SUBRULE($.controlFn, { ARGS: [options] }) },
|
|
42
|
-
{ ALT: () => $.SUBRULE($.debugFn) },
|
|
43
|
-
{ ALT: () => $.SUBRULE($.storageFn) },
|
|
44
|
-
{ ALT: () => $.SUBRULE($.streamFn) },
|
|
45
|
-
{ ALT: () => $.SUBRULE($.actionCallFn) },
|
|
46
26
|
{
|
|
47
|
-
|
|
48
|
-
|
|
27
|
+
ALT: () => {
|
|
28
|
+
$.OPTION(() => $.CONSUME(ExclamationToken)); // optional "!" to continue on error
|
|
29
|
+
$.OR1([
|
|
30
|
+
{ ALT: () => $.SUBRULE($.aiFn) },
|
|
31
|
+
{ ALT: () => $.SUBRULE($.dbFn) },
|
|
32
|
+
{ ALT: () => $.SUBRULE($.varFn) },
|
|
33
|
+
{ ALT: () => $.SUBRULE($.arrayFn) },
|
|
34
|
+
{ ALT: () => $.SUBRULE($.cloudFn) },
|
|
35
|
+
{ ALT: () => $.SUBRULE($.securityFn) },
|
|
36
|
+
{ ALT: () => $.SUBRULE($.mathFn) },
|
|
37
|
+
{ ALT: () => $.SUBRULE($.textFn) },
|
|
38
|
+
{ ALT: () => $.SUBRULE($.utilFn) },
|
|
39
|
+
{ ALT: () => $.SUBRULE($.preconditionFn) },
|
|
40
|
+
{ ALT: () => $.SUBRULE($.zipFn) },
|
|
41
|
+
{ ALT: () => $.SUBRULE($.objectFn) },
|
|
42
|
+
{ ALT: () => $.SUBRULE($.apiFn, { ARGS: [options] }) },
|
|
43
|
+
{ ALT: () => $.SUBRULE($.webflowFn) },
|
|
44
|
+
{ ALT: () => $.SUBRULE($.redisFn) },
|
|
45
|
+
{ ALT: () => $.SUBRULE($.controlFn, { ARGS: [options] }) },
|
|
46
|
+
{ ALT: () => $.SUBRULE($.debugFn) },
|
|
47
|
+
{ ALT: () => $.SUBRULE($.storageFn) },
|
|
48
|
+
{ ALT: () => $.SUBRULE($.streamFn) },
|
|
49
|
+
{ ALT: () => $.SUBRULE($.actionCallFn) },
|
|
50
|
+
{
|
|
51
|
+
GATE: () => options.allowCallStatements,
|
|
52
|
+
ALT: () => $.SUBRULE($.toolCallFn),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
GATE: () => options.allowCallStatements,
|
|
56
|
+
ALT: () => $.SUBRULE($.addonCallFn),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
GATE: () => options.allowCallStatements,
|
|
60
|
+
ALT: () => $.SUBRULE($.middlewareCallFn),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
GATE: () => options.allowCallStatements,
|
|
64
|
+
ALT: () => $.SUBRULE($.triggerCallFn),
|
|
65
|
+
},
|
|
66
|
+
{ ALT: () => $.SUBRULE($.workflowExpectFn) },
|
|
67
|
+
]);
|
|
68
|
+
},
|
|
49
69
|
},
|
|
50
|
-
{
|
|
51
|
-
GATE: () => options.allowCallStatements,
|
|
52
|
-
ALT: () => $.SUBRULE($.addonCallFn),
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
GATE: () => options.allowCallStatements,
|
|
56
|
-
ALT: () => $.SUBRULE($.middlewareCallFn),
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
GATE: () => options.allowCallStatements,
|
|
60
|
-
ALT: () => $.SUBRULE($.triggerCallFn),
|
|
61
|
-
},
|
|
62
|
-
{ ALT: () => $.SUBRULE($.workflowExpectFn) },
|
|
63
70
|
]);
|
|
64
71
|
});
|
|
65
72
|
$.AT_LEAST_ONE2(() => $.CONSUME2(NewlineToken));
|
|
@@ -31,6 +31,16 @@ describe("nakedStack", () => {
|
|
|
31
31
|
expect(parser.errors).to.be.empty;
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
+
it("nakedStack accepts a disabled array statement", () => {
|
|
35
|
+
const parser = parse(`{
|
|
36
|
+
!array.push $my_array {
|
|
37
|
+
value = 1
|
|
38
|
+
}
|
|
39
|
+
}`);
|
|
40
|
+
console.log(parser.errors);
|
|
41
|
+
expect(parser.errors).to.be.empty;
|
|
42
|
+
});
|
|
43
|
+
|
|
34
44
|
it("nakedStack accepts a multiline statement", () => {
|
|
35
45
|
const parser = parse(`{
|
|
36
46
|
var $some_var {
|
|
@@ -27,6 +27,11 @@ describe("responseClause", () => {
|
|
|
27
27
|
expect(parser.errors).to.be.empty;
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
it("responseClause accepts a string", () => {
|
|
31
|
+
const parser = parse(`response = "no exception"`);
|
|
32
|
+
expect(parser.errors).to.be.empty;
|
|
33
|
+
});
|
|
34
|
+
|
|
30
35
|
it("responseClause accepts an object", () => {
|
|
31
36
|
const parser = parse(`response = {result1: $x1, x2: $x2}`);
|
|
32
37
|
expect(parser.errors).to.be.empty;
|
|
@@ -115,7 +115,11 @@ export function columnDefinition($) {
|
|
|
115
115
|
|
|
116
116
|
// Track table reference from FK column definition
|
|
117
117
|
if (captured.table?.value) {
|
|
118
|
-
$.addReference(
|
|
118
|
+
$.addReference(
|
|
119
|
+
"table",
|
|
120
|
+
getVarName(captured.table.value),
|
|
121
|
+
captured.table.value,
|
|
122
|
+
);
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
if (inputTypeToken.image == "vector" && !captured.size) {
|
|
@@ -126,7 +130,8 @@ export function columnDefinition($) {
|
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
const inTable = $.sectionStack.includes("tableDeclaration");
|
|
129
|
-
|
|
133
|
+
const inIntput = $.sectionStack.includes("inputClause");
|
|
134
|
+
if (captured.visibility && !inTable && !inIntput) {
|
|
130
135
|
$.addInvalidValueError(
|
|
131
136
|
nameToken,
|
|
132
137
|
'The "visibility" attribute is only valid within a table schema definition.',
|
|
@@ -19,6 +19,14 @@ describe("forEachFn", () => {
|
|
|
19
19
|
expect(parser.errors).to.be.empty;
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
+
it("forEachFn should warn when using a reserved variable name", () => {
|
|
23
|
+
const parser = parse(`foreach ([]|push:1|push:2|push:3|push:4) {
|
|
24
|
+
each as $env
|
|
25
|
+
}`);
|
|
26
|
+
expect(parser.errors).to.be.empty;
|
|
27
|
+
expect(parser.warnings).to.not.be.empty;
|
|
28
|
+
});
|
|
29
|
+
|
|
22
30
|
it("forEachFn can also be foreach.remove", () => {
|
|
23
31
|
const parser = parse(`foreach.remove`);
|
|
24
32
|
expect(parser.errors).to.be.empty;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LParent, RParent } from "../../../lexer/control.js";
|
|
2
|
+
import { NewlineToken } from "../../../lexer/tokens.js";
|
|
2
3
|
import { PreconditionToken } from "../../../lexer/util.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -13,7 +14,9 @@ export function preconditionFn($) {
|
|
|
13
14
|
$.sectionStack.push("preconditionFn");
|
|
14
15
|
const tokenFn = $.CONSUME(PreconditionToken); // "precondition"
|
|
15
16
|
$.CONSUME(LParent);
|
|
17
|
+
$.MANY(() => $.CONSUME(NewlineToken));
|
|
16
18
|
$.SUBRULE($.expressionFn);
|
|
19
|
+
$.MANY1(() => $.CONSUME1(NewlineToken));
|
|
17
20
|
$.CONSUME(RParent);
|
|
18
21
|
$.SUBRULE($.schemaParseAttributeFn, {
|
|
19
22
|
ARGS: [
|
|
@@ -346,4 +346,35 @@ describe("dbQueryFn", () => {
|
|
|
346
346
|
const parser = parse(`query user as $user`);
|
|
347
347
|
expect(parser.errors).to.be.empty;
|
|
348
348
|
});
|
|
349
|
+
|
|
350
|
+
describe("aggregate eval with aggregator pipes", () => {
|
|
351
|
+
const aggregators = [
|
|
352
|
+
"avg",
|
|
353
|
+
"count",
|
|
354
|
+
"count_distinct",
|
|
355
|
+
"max",
|
|
356
|
+
"median",
|
|
357
|
+
"min",
|
|
358
|
+
"sum",
|
|
359
|
+
"to_distinct_list",
|
|
360
|
+
"to_distinct_list_asc",
|
|
361
|
+
"to_distinct_list_desc",
|
|
362
|
+
"to_list",
|
|
363
|
+
"to_list_asc",
|
|
364
|
+
"to_list_desc",
|
|
365
|
+
];
|
|
366
|
+
|
|
367
|
+
for (const agg of aggregators) {
|
|
368
|
+
it(`dbQueryFn return eval accepts |${agg} aggregator`, () => {
|
|
369
|
+
const parser = parse(`query user {
|
|
370
|
+
return = {
|
|
371
|
+
type: "aggregate"
|
|
372
|
+
group: {user_name: $db.user.name}
|
|
373
|
+
eval: {result: $db.user.name|${agg}}
|
|
374
|
+
}
|
|
375
|
+
} as $user`);
|
|
376
|
+
expect(parser.errors).to.be.empty;
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
});
|
|
349
380
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isArray, some } from "lodash-es";
|
|
2
|
+
import { Minus } from "../../../lexer/control.js";
|
|
2
3
|
import {
|
|
3
4
|
FloatLiteral,
|
|
4
5
|
IntegerLiteral,
|
|
@@ -37,18 +38,18 @@ export function schemaParseConstantFn($) {
|
|
|
37
38
|
return $.CONSUME(SingleQuotedStringLiteral); // e.g., 'field instructions'
|
|
38
39
|
},
|
|
39
40
|
},
|
|
41
|
+
|
|
40
42
|
{
|
|
41
43
|
ALT: () => {
|
|
44
|
+
$.OPTION(() => $.CONSUME(Minus));
|
|
42
45
|
attributeCapturedType = "number";
|
|
43
|
-
return $.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
ALT: () => {
|
|
48
|
-
attributeCapturedType = "number";
|
|
49
|
-
return $.CONSUME(FloatLiteral); // e.g., 42.54
|
|
46
|
+
return $.OR1([
|
|
47
|
+
{ ALT: () => $.CONSUME(IntegerLiteral) },
|
|
48
|
+
{ ALT: () => $.CONSUME(FloatLiteral) },
|
|
49
|
+
]);
|
|
50
50
|
},
|
|
51
51
|
},
|
|
52
|
+
|
|
52
53
|
{
|
|
53
54
|
ALT: () => {
|
|
54
55
|
attributeCapturedType = "boolean";
|
|
@@ -76,7 +77,7 @@ export function schemaParseConstantFn($) {
|
|
|
76
77
|
const isMatch = matchValueToSchema(
|
|
77
78
|
attributeCapturedType,
|
|
78
79
|
capturedValue.image,
|
|
79
|
-
schema
|
|
80
|
+
schema,
|
|
80
81
|
);
|
|
81
82
|
|
|
82
83
|
if (!isMatch) {
|
|
@@ -92,7 +93,7 @@ function matchValueToSchema(attributeCapturedType, value, schema) {
|
|
|
92
93
|
// schema can be an array of type/values required
|
|
93
94
|
if (isArray(schema)) {
|
|
94
95
|
return some(schema, (s) =>
|
|
95
|
-
matchValueToSchema(attributeCapturedType, value, s)
|
|
96
|
+
matchValueToSchema(attributeCapturedType, value, s),
|
|
96
97
|
);
|
|
97
98
|
}
|
|
98
99
|
|
|
@@ -115,7 +116,7 @@ function matchValueToSchema(attributeCapturedType, value, schema) {
|
|
|
115
116
|
return schemaValue === null;
|
|
116
117
|
default:
|
|
117
118
|
throw new Error(
|
|
118
|
-
`schemaParseConstantFn called with non-constant attribute: ${schema}
|
|
119
|
+
`schemaParseConstantFn called with non-constant attribute: ${schema}`,
|
|
119
120
|
);
|
|
120
121
|
}
|
|
121
122
|
}
|
|
@@ -2,9 +2,14 @@ import { DotToken } from "../../lexer/tokens.js";
|
|
|
2
2
|
import { UpdateToken, VarToken } from "../../lexer/var.js";
|
|
3
3
|
import {
|
|
4
4
|
ApiBaseUrlVariable,
|
|
5
|
+
AuthVariable,
|
|
5
6
|
BranchVariable,
|
|
6
7
|
DatasourceVariable,
|
|
8
|
+
EnvVariable,
|
|
9
|
+
ErrorVariable,
|
|
7
10
|
HttpHeadersVariable,
|
|
11
|
+
InputVariable,
|
|
12
|
+
LongFormVariable,
|
|
8
13
|
RemoteHostVariable,
|
|
9
14
|
RemoteIpVariable,
|
|
10
15
|
RemotePasswordVariable,
|
|
@@ -53,16 +58,23 @@ export function varFn($) {
|
|
|
53
58
|
{ ALT: () => $.CONSUME(RequestUriVariable) },
|
|
54
59
|
{ ALT: () => $.CONSUME(TenantVariable) },
|
|
55
60
|
{ ALT: () => $.CONSUME(WebflowVariable) },
|
|
61
|
+
// Not recommended but still valid
|
|
62
|
+
{ ALT: () => $.CONSUME(LongFormVariable) },
|
|
63
|
+
{ ALT: () => $.CONSUME(EnvVariable) },
|
|
64
|
+
{ ALT: () => $.CONSUME(AuthVariable) },
|
|
65
|
+
{ ALT: () => $.CONSUME(InputVariable) },
|
|
66
|
+
{ ALT: () => $.CONSUME(ErrorVariable) },
|
|
56
67
|
]);
|
|
68
|
+
|
|
57
69
|
if (variable.image) {
|
|
58
70
|
// Check for blacklisted variable names
|
|
59
71
|
const nameWithoutPrefix = variable.image.startsWith("$")
|
|
60
72
|
? variable.image.slice(1)
|
|
61
73
|
: variable.image;
|
|
62
74
|
if (isBlacklistedVariableName(nameWithoutPrefix)) {
|
|
63
|
-
$.
|
|
64
|
-
variable,
|
|
75
|
+
$.addWarning(
|
|
65
76
|
`'${variable.image}' is a reserved variable name and should not be used as a variable.`,
|
|
77
|
+
variable,
|
|
66
78
|
);
|
|
67
79
|
}
|
|
68
80
|
// we can directly add the variable here but
|
|
@@ -225,16 +225,18 @@ describe("varFn", () => {
|
|
|
225
225
|
const parser = parse(`var $${name} {
|
|
226
226
|
value = "test"
|
|
227
227
|
}`);
|
|
228
|
-
expect(parser.errors).to.
|
|
229
|
-
expect(parser.
|
|
228
|
+
expect(parser.errors).to.be.empty;
|
|
229
|
+
expect(parser.warnings).to.not.be.empty;
|
|
230
|
+
expect(parser.warnings[0].message).to.include("reserved");
|
|
230
231
|
});
|
|
231
232
|
|
|
232
233
|
it(`rejects var.update $${name} as variable name (short form)`, () => {
|
|
233
234
|
const parser = parse(`var.update $${name} {
|
|
234
235
|
value = "test"
|
|
235
236
|
}`);
|
|
236
|
-
expect(parser.errors).to.
|
|
237
|
-
expect(parser.
|
|
237
|
+
expect(parser.errors).to.be.empty;
|
|
238
|
+
expect(parser.warnings).to.not.be.empty;
|
|
239
|
+
expect(parser.warnings[0].message).to.include("reserved");
|
|
238
240
|
});
|
|
239
241
|
}
|
|
240
242
|
|
|
@@ -249,30 +251,35 @@ describe("varFn", () => {
|
|
|
249
251
|
];
|
|
250
252
|
|
|
251
253
|
for (const name of separateTokenBlacklisted) {
|
|
252
|
-
it(`
|
|
254
|
+
it(`warns var $${name} as variable name (separate token)`, () => {
|
|
253
255
|
const parser = parse(`var $${name} {
|
|
254
256
|
value = "test"
|
|
255
257
|
}`);
|
|
256
258
|
if (parser.errors.length === 0) {
|
|
257
259
|
console.log(parser.errors, name);
|
|
258
260
|
}
|
|
259
|
-
expect(parser.errors).to.
|
|
261
|
+
expect(parser.errors).to.be.empty;
|
|
262
|
+
expect(parser.warnings).to.not.be.empty;
|
|
263
|
+
expect(parser.warnings[0].message).to.include("reserved");
|
|
260
264
|
});
|
|
261
265
|
|
|
262
|
-
it(`
|
|
266
|
+
it(`warns var.update $${name} as variable name (separate token)`, () => {
|
|
263
267
|
const parser = parse(`var.update $${name} {
|
|
264
268
|
value = "test"
|
|
265
269
|
}`);
|
|
266
|
-
expect(parser.errors).to.
|
|
270
|
+
expect(parser.errors).to.be.empty;
|
|
271
|
+
expect(parser.warnings).to.not.be.empty;
|
|
272
|
+
expect(parser.warnings[0].message).to.include("reserved");
|
|
267
273
|
});
|
|
268
274
|
}
|
|
269
275
|
|
|
270
276
|
// $var is the LongFormVariable prefix, parser expects a dot after it
|
|
271
|
-
it("
|
|
277
|
+
it("warns var $var as variable name (long form prefix)", () => {
|
|
272
278
|
const parser = parse(`var $var {
|
|
273
279
|
value = "test"
|
|
274
280
|
}`);
|
|
275
|
-
expect(parser.
|
|
281
|
+
expect(parser.warnings).to.not.be.empty;
|
|
282
|
+
expect(parser.errors).to.be.empty;
|
|
276
283
|
});
|
|
277
284
|
|
|
278
285
|
it("accepts a backtick expression as value", () => {
|
|
@@ -301,8 +308,9 @@ describe("varFn", () => {
|
|
|
301
308
|
const parser = parse(`var.update $var.${name} {
|
|
302
309
|
value = "test"
|
|
303
310
|
}`);
|
|
304
|
-
expect(parser.errors).to.
|
|
305
|
-
expect(parser.
|
|
311
|
+
expect(parser.errors).to.be.empty;
|
|
312
|
+
expect(parser.warnings).to.not.be.empty;
|
|
313
|
+
expect(parser.warnings[0].message).to.include("reserved");
|
|
306
314
|
});
|
|
307
315
|
}
|
|
308
316
|
|