@xano/xanoscript-language-server 11.12.0 → 11.12.2

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.
Files changed (42) hide show
  1. package/.claude/memory/feedback_no_git.md +11 -0
  2. package/.claude/settings.local.json +13 -28
  3. package/lexer/cast.js +3 -3
  4. package/lexer/control.js +0 -2
  5. package/lexer/literal.js +2 -2
  6. package/lexer/regexp.spec.js +84 -0
  7. package/lexer/security.js +2 -2
  8. package/lexer/tokens.js +22 -3
  9. package/package.json +1 -1
  10. package/parser/attributes/columnDefaultValueAttribute.js +10 -3
  11. package/parser/attributes/inputFilterFn.js +11 -4
  12. package/parser/clauses/flexibleStringClause.js +2 -2
  13. package/parser/clauses/nakedStackFn.js +43 -36
  14. package/parser/clauses/nakedStackFn.spec.js +10 -0
  15. package/parser/clauses/responseClause.spec.js +5 -0
  16. package/parser/definitions/columnDefinition.js +7 -2
  17. package/parser/functions/array/arrayNoValueAs.js +1 -1
  18. package/parser/functions/array/arrayPopFn.js +1 -3
  19. package/parser/functions/controls/forEachFn.spec.js +8 -0
  20. package/parser/functions/controls/functionRunFn.spec.js +10 -0
  21. package/parser/functions/controls/preconditionFn.js +3 -0
  22. package/parser/functions/db/dbExternalDirectQueryFn.js +1 -0
  23. package/parser/functions/db/dbQueryFn.spec.js +31 -0
  24. package/parser/functions/schema/schemaParseConstantFn.js +11 -10
  25. package/parser/functions/varFn.js +14 -2
  26. package/parser/functions/varFn.spec.js +20 -12
  27. package/parser/generic/asVariable.spec.js +12 -8
  28. package/parser/generic/assignableVariableAs.js +50 -11
  29. package/parser/generic/assignableVariableAs.spec.js +29 -7
  30. package/parser/generic/assignableVariableProperty.js +22 -10
  31. package/parser/generic/assignableVariableProperty.spec.js +21 -12
  32. package/parser/generic/expressionFn.js +9 -2
  33. package/parser/generic/expressionFn.spec.js +5 -0
  34. package/parser/generic/filterNames.js +9 -0
  35. package/parser/multidoc.js +38 -17
  36. package/parser/multidoc.spec.js +7 -1
  37. package/parser/query_parser.js +11 -3
  38. package/parser/query_parser.spec.js +0 -3
  39. package/parser/tests/function/function_parser.spec.js +4 -4
  40. package/parser/tests/query/query_parser.spec.js +3 -3
  41. package/parser/tool_parser.js +12 -3
  42. 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(npm run lint)",
6
- "Bash(npm run test:*)",
7
- "Bash(npx eslint:*)",
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
- "Bash(git -C /Users/justinalbrecht/git/xs-language-server log -p --all -S \"webhook\\\\|Bearer\\\\|discord\\\\|slack\\\\|xano_insiders\" -- \"*.xs\" \"*.js\")",
16
- "Bash(git -C /Users/justinalbrecht/git/xs-language-server ls-files:*)",
17
- "Bash(git -C /Users/justinalbrecht/git/xs-language-server status)",
18
- "Bash(git -C /Users/justinalbrecht/git/xs-language-server diff --stat)",
19
- "Bash(git -C /Users/justinalbrecht/git/xs-language-server log --oneline -5)",
20
- "Bash(git -C /Users/justinalbrecht/git/xs-language-server add README.md LICENSE package.json lexer/tests/query/valid_sources/basic_query.xs parser/functions/stream/streamFromRequestFn.js parser/functions/stream/streamFromRequestFn.spec.js parser/tests/function/valid_sources/discord_poll_send_to_slack.xs parser/tests/query/valid_sources/all_basics.xs)",
21
- "Bash(git -C /Users/justinalbrecht/git/xs-language-server commit -m \"$\\(cat <<''EOF''\nPrepare package for public npm release as @xano/xanoscript-language-server\n\n- Rename package to @xano/xanoscript-language-server\n- Add MIT LICENSE file\n- Add npm metadata \\(description, repository, author, license, bugs, homepage\\)\n- Remove private flag to allow publishing\n- Sanitize test files: replace internal URLs, IDs, and env var names with placeholders\n- Simplify README maintainer section\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
22
- "Bash(git -C /Users/justinalbrecht/git/xs-language-server log -1 --oneline)",
23
- "Bash(git add:*)",
24
- "Bash(git commit:*)",
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: /-?\d+\.\d+([eE][+-]?\d+)?/,
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: /-?\d+/,
33
+ pattern: /\d+/,
34
34
  longer_alt: FloatLiteral,
35
35
  });
36
36
 
@@ -0,0 +1,84 @@
1
+ import { expect } from "chai";
2
+ import { describe, it } from "mocha";
3
+ import { lexDocument } from "./lexer.js";
4
+
5
+ function getTokenTypes(text) {
6
+ const result = lexDocument(text);
7
+ return {
8
+ tokens: result.tokens.map((t) => ({
9
+ type: t.tokenType.name,
10
+ image: t.image,
11
+ })),
12
+ errors: result.errors,
13
+ };
14
+ }
15
+
16
+ describe("RegExpToken lexer", () => {
17
+ it("should not lex a standalone regex without preceding colon", () => {
18
+ const { tokens } = getTokenTypes("/[^213]*/");
19
+ const regexpTokens = tokens.filter((t) => t.type === "RegExpToken");
20
+ expect(regexpTokens).to.have.length(0);
21
+ });
22
+
23
+ it("should lex a regex after a colon", () => {
24
+ const { tokens, errors } = getTokenTypes("pattern:/[^213]*/");
25
+ expect(errors).to.be.empty;
26
+ const regexpTokens = tokens.filter((t) => t.type === "RegExpToken");
27
+ expect(regexpTokens).to.have.length(1);
28
+ expect(regexpTokens[0].image).to.equal("/[^213]*/");
29
+ });
30
+
31
+ it("should lex / as divide after an identifier", () => {
32
+ const { tokens } = getTokenTypes("var_rel/55");
33
+ const divideTokens = tokens.filter((t) => t.type === "Divide");
34
+ expect(divideTokens).to.have.length(1);
35
+ const regexpTokens = tokens.filter((t) => t.type === "RegExpToken");
36
+ expect(regexpTokens).to.have.length(0);
37
+ });
38
+
39
+ it("should lex / as divide after a number", () => {
40
+ const { tokens } = getTokenTypes("55/2");
41
+ const divideTokens = tokens.filter((t) => t.type === "Divide");
42
+ expect(divideTokens).to.have.length(1);
43
+ const regexpTokens = tokens.filter((t) => t.type === "RegExpToken");
44
+ expect(regexpTokens).to.have.length(0);
45
+ });
46
+
47
+ it("should lex / as divide after closing paren", () => {
48
+ const { tokens } = getTokenTypes("(1+2)/3");
49
+ const divideTokens = tokens.filter((t) => t.type === "Divide");
50
+ expect(divideTokens).to.have.length(1);
51
+ });
52
+
53
+ it("should not capture division as regex in complex expressions", () => {
54
+ const { tokens, errors } = getTokenTypes("$SomeVar.0.var_rel/55");
55
+ expect(errors).to.be.empty;
56
+ const regexpTokens = tokens.filter((t) => t.type === "RegExpToken");
57
+ expect(regexpTokens).to.have.length(0);
58
+ const divideTokens = tokens.filter((t) => t.type === "Divide");
59
+ expect(divideTokens).to.have.length(1);
60
+ });
61
+
62
+ it("should lex / as divide with spaces around it", () => {
63
+ const { tokens, errors } = getTokenTypes("$var /64");
64
+ expect(errors).to.be.empty;
65
+ const regexpTokens = tokens.filter((t) => t.type === "RegExpToken");
66
+ expect(regexpTokens).to.have.length(0);
67
+ const divideTokens = tokens.filter((t) => t.type === "Divide");
68
+ expect(divideTokens).to.have.length(1);
69
+ });
70
+
71
+ it("should lex / as divide with space only before it", () => {
72
+ const { tokens, errors } = getTokenTypes("$var/ 64");
73
+ expect(errors).to.be.empty;
74
+ const divideTokens = tokens.filter((t) => t.type === "Divide");
75
+ expect(divideTokens).to.have.length(1);
76
+ });
77
+
78
+ it("should lex / as divide when no spaces around a value", () => {
79
+ const { tokens, errors } = getTokenTypes("$var /64/ 34");
80
+ expect(errors).to.be.empty;
81
+ const divideTokens = tokens.filter((t) => t.type === "Divide");
82
+ expect(divideTokens).to.have.length(2);
83
+ });
84
+ });
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
- llt: Identifier,
54
+ longer_alt: Identifier,
55
55
  categories: [Identifier],
56
56
  });
57
57
 
58
58
  // encrypt
59
59
  export const EncryptToken = createTokenByName("encrypt", {
60
- llt: Identifier,
60
+ longer_alt: Identifier,
61
61
  categories: [Identifier],
62
62
  });
63
63
 
package/lexer/tokens.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Lexer } from "chevrotain";
1
+ import { createToken as chevrotainCreateToken, Lexer } from "chevrotain";
2
2
  import { Identifier } from "./identifier.js";
3
3
  import {
4
4
  createTokenByName,
@@ -150,10 +150,29 @@ export const NullToken = createTokenByName("null", {
150
150
  });
151
151
 
152
152
  // regular expression (regexp) pattern
153
- export const RegExpToken = createToken({
153
+ // A regexp literal only appears after a colon (e.g., filter:pattern:/[^abc]*/)
154
+ const regexpPattern = /\/(?:[^/\n\\ ]|\\.)(?:[^/\n\\]|\\.)*\//y;
155
+ function matchRegExp(text, startOffset, tokens) {
156
+ if (
157
+ tokens.length === 0 ||
158
+ tokens[tokens.length - 1].tokenType.name !== "ColonToken"
159
+ ) {
160
+ return null;
161
+ }
162
+ regexpPattern.lastIndex = startOffset;
163
+ const result = regexpPattern.exec(text);
164
+ if (result) {
165
+ return [result[0]];
166
+ }
167
+ return null;
168
+ }
169
+
170
+ export const RegExpToken = chevrotainCreateToken({
154
171
  name: "RegExpToken",
155
172
  label: "regexp",
156
- pattern: /\/(?:[^/\n\\ ]|\\.)(?:[^/\n\\]|\\.)*\//,
173
+ pattern: { exec: matchRegExp },
174
+ line_breaks: false,
175
+ start_chars_hint: ["/"],
157
176
  });
158
177
 
159
178
  export const WhiteSpace = createToken({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xano/xanoscript-language-server",
3
- "version": "11.12.0",
3
+ "version": "11.12.2",
4
4
  "description": "Language Server Protocol implementation for XanoScript",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -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
- { ALT: () => $.CONSUME(FloatLiteral) }, // e.g., 42.54
25
- { ALT: () => $.CONSUME(IntegerLiteral) }, // e.g., 42
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
- { ALT: () => $.CONSUME1(IntegerLiteral) },
40
- { ALT: () => $.CONSUME1(FloatLiteral) },
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] || Identifier;
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
- GATE: () => options.allowCallStatements,
48
- ALT: () => $.SUBRULE($.toolCallFn),
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("table", getVarName(captured.table.value), captured.table.value);
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
- if (captured.visibility && !inTable) {
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.',
@@ -25,7 +25,7 @@ export function arrayNoValueAs($) {
25
25
  $.CONSUME(RCurly);
26
26
  });
27
27
  $.CONSUME(AsToken);
28
- $.SUBRULE($.variableOnly);
28
+ $.SUBRULE($.assignableVariableAs);
29
29
  $.sectionStack.pop();
30
30
  };
31
31
  }
@@ -1,8 +1,6 @@
1
1
  import { PopToken } from "../../../lexer/arrays.js";
2
2
 
3
- // array.pop {
4
- // value = "test"
5
- // } as test
3
+ // array.pop $var as $unit
6
4
  /**
7
5
  * @param {import('../../base_parser.js').XanoBaseParser} $
8
6
  */
@@ -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;
@@ -26,6 +26,16 @@ describe("functionRunFn", () => {
26
26
  expect(parser.errors).to.be.empty;
27
27
  });
28
28
 
29
+ it("functionRunFn accepts complex expression as input", () => {
30
+ const parser = parse(`function.run "my_complex_function" {
31
+ input = {
32
+ Snumber : (($SomeVar.0.var_rel/55) > 0 ? ($SomeVar.0.var_rel1/61) : 6)+(($My_Var_S2.0.elo/ 60) > 7 ? ($My_Var_S2.0.elo/62) : 9)+(($Optional_var2.0.det/63) > 4 ? ($Optional_var2.0.det /64) : 2)+(($PtSgeS_sge.0.elo/65) > 1 ? ($PtSgeS_sge.0.elo/66) : 3)
33
+ NumberObject: []
34
+ }
35
+ } as $Var_Round`);
36
+ expect(parser.errors).to.be.empty;
37
+ });
38
+
29
39
  it("functionRunFn accepts a string for function name", () => {
30
40
  const parser = parse(`function.run "my/function" {
31
41
  input = {a: $input.a, b: $b}
@@ -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: [
@@ -14,6 +14,7 @@ export function dbExternalDirectQueryFn($) {
14
14
  {
15
15
  sql: "[expression]",
16
16
  response_type: ["list", "single"],
17
+ "parser?": "template_engine",
17
18
  connection_string: "[expression]",
18
19
  "description?": "[string]",
19
20
  "disabled?": "[boolean]",
@@ -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
  });