@xano/xanoscript-language-server 11.11.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.
Files changed (62) 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/security.js +2 -2
  7. package/package.json +1 -1
  8. package/parser/addon_parser.js +2 -1
  9. package/parser/agent_parser.js +2 -1
  10. package/parser/agent_trigger_parser.js +2 -1
  11. package/parser/api_group_parser.js +2 -1
  12. package/parser/attributes/columnDefaultValueAttribute.js +10 -3
  13. package/parser/attributes/inputFilterFn.js +11 -4
  14. package/parser/base_parser.js +11 -0
  15. package/parser/branch_parser.js +2 -1
  16. package/parser/clauses/flexibleStringClause.js +2 -2
  17. package/parser/clauses/nakedStackFn.js +43 -36
  18. package/parser/clauses/nakedStackFn.spec.js +10 -0
  19. package/parser/clauses/responseClause.spec.js +5 -0
  20. package/parser/definitions/columnDefinition.js +7 -2
  21. package/parser/function_parser.js +1 -0
  22. package/parser/function_parser.spec.js +14 -0
  23. package/parser/functions/array/arrayNoValueAs.js +1 -1
  24. package/parser/functions/array/arrayPopFn.js +1 -3
  25. package/parser/functions/controls/forEachFn.spec.js +8 -0
  26. package/parser/functions/controls/preconditionFn.js +3 -0
  27. package/parser/functions/db/dbExternalDirectQueryFn.js +1 -0
  28. package/parser/functions/db/dbQueryFn.spec.js +31 -0
  29. package/parser/functions/schema/schemaParseConstantFn.js +11 -10
  30. package/parser/functions/varFn.js +14 -2
  31. package/parser/functions/varFn.spec.js +20 -12
  32. package/parser/generic/asVariable.spec.js +12 -8
  33. package/parser/generic/assignableVariableAs.js +50 -11
  34. package/parser/generic/assignableVariableAs.spec.js +29 -7
  35. package/parser/generic/assignableVariableProperty.js +22 -10
  36. package/parser/generic/assignableVariableProperty.spec.js +21 -12
  37. package/parser/generic/expressionFn.js +9 -2
  38. package/parser/generic/expressionFn.spec.js +5 -0
  39. package/parser/generic/filterNames.js +9 -0
  40. package/parser/mcp_server_parser.js +2 -1
  41. package/parser/mcp_server_trigger_parser.js +2 -1
  42. package/parser/middleware_parser.js +2 -1
  43. package/parser/multidoc.js +38 -17
  44. package/parser/multidoc.spec.js +7 -1
  45. package/parser/query_parser.js +12 -3
  46. package/parser/query_parser.spec.js +14 -3
  47. package/parser/realtime_channel_parser.js +2 -1
  48. package/parser/realtime_trigger_parser.js +2 -1
  49. package/parser/run_parser.js +4 -2
  50. package/parser/table_parser.js +2 -1
  51. package/parser/table_trigger_parser.js +2 -1
  52. package/parser/task_parser.js +2 -1
  53. package/parser/task_parser.spec.js +11 -0
  54. package/parser/tests/function/function_parser.spec.js +4 -4
  55. package/parser/tests/multidoc/multidoc_parser.spec.js +55 -0
  56. package/parser/tests/multidoc/valid_sources/function_stack.xs +130603 -0
  57. package/parser/tests/query/query_parser.spec.js +3 -3
  58. package/parser/tool_parser.js +14 -4
  59. package/parser/tool_parser.spec.js +19 -6
  60. package/parser/workflow_test_parser.js +2 -1
  61. package/parser/workspace_parser.js +2 -1
  62. package/parser/workspace_trigger_parser.js +2 -1
@@ -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
 
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xano/xanoscript-language-server",
3
- "version": "11.11.0",
3
+ "version": "11.12.1",
4
4
  "description": "Language Server Protocol implementation for XanoScript",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -26,10 +26,11 @@ export function addonDeclaration($) {
26
26
  // Allow leading comments and newlines before the addon declaration
27
27
  $.SUBRULE($.optionalCommentBlockFn);
28
28
  const parent = $.CONSUME(AddonToken);
29
- $.OR([
29
+ const nameToken = $.OR([
30
30
  { ALT: () => $.CONSUME(StringLiteral) },
31
31
  { ALT: () => $.CONSUME(Identifier) },
32
32
  ]);
33
+ $.validateDeclarationName(nameToken, "addon");
33
34
  $.CONSUME(LCurly); // "{"
34
35
  $.MANY(() => {
35
36
  $.AT_LEAST_ONE(() => $.CONSUME(NewlineToken)); // at least one new line
@@ -21,10 +21,11 @@ export function agentDeclaration($) {
21
21
  // Allow leading comments and newlines before the agent declaration
22
22
  $.SUBRULE($.optionalCommentBlockFn);
23
23
  const parent = $.CONSUME(AgentToken); // agent
24
- $.OR([
24
+ const nameToken = $.OR([
25
25
  { ALT: () => $.CONSUME(StringLiteral) },
26
26
  { ALT: () => $.CONSUME(Identifier) },
27
27
  ]);
28
+ $.validateDeclarationName(nameToken, "agent");
28
29
  $.CONSUME(LCurly); // "{"
29
30
  $.MANY(() => {
30
31
  $.AT_LEAST_ONE(() => $.CONSUME(NewlineToken)); // at least one new line
@@ -26,10 +26,11 @@ export function agentTriggerDeclaration($) {
26
26
  // Allow leading comments and newlines before the agent_trigger declaration
27
27
  $.SUBRULE($.optionalCommentBlockFn);
28
28
  const parent = $.CONSUME(AgentTriggerToken); // agent_trigger
29
- $.OR([
29
+ const nameToken = $.OR([
30
30
  { ALT: () => $.CONSUME(StringLiteral) },
31
31
  { ALT: () => $.CONSUME(Identifier) },
32
32
  ]);
33
+ $.validateDeclarationName(nameToken, "agent_trigger");
33
34
  $.CONSUME(LCurly); // "{"
34
35
  $.MANY(() => {
35
36
  $.AT_LEAST_ONE(() => $.CONSUME(NewlineToken)); // at least one new line
@@ -27,10 +27,11 @@ export function apiGroupDeclaration($) {
27
27
  // Allow leading comments and newlines before the api_group declaration
28
28
  $.SUBRULE($.optionalCommentBlockFn);
29
29
  $.CONSUME(ApiGroupToken); // api_group
30
- $.OR([
30
+ const nameToken = $.OR([
31
31
  { ALT: () => $.CONSUME(StringLiteral) },
32
32
  { ALT: () => $.CONSUME(Identifier) },
33
33
  ]);
34
+ $.validateDeclarationName(nameToken, "api_group");
34
35
  $.CONSUME(LCurly); // "{"
35
36
  $.MANY(() => {
36
37
  $.AT_LEAST_ONE(() => $.CONSUME(NewlineToken));
@@ -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
  },
@@ -270,6 +270,17 @@ export class XanoBaseParser extends CstParser {
270
270
  this.SAVE_ERROR(new MismatchedTokenException(message, token));
271
271
  }
272
272
 
273
+ /**
274
+ * Validate that a declaration name token is not an empty string
275
+ * @param {import('chevrotain').IToken} nameToken
276
+ * @param {string} typeName the declaration type (e.g. "query", "function")
277
+ */
278
+ validateDeclarationName(nameToken, typeName) {
279
+ if (nameToken?.image === '""') {
280
+ this.addInvalidValueError(nameToken, `${typeName} name must not be empty`);
281
+ }
282
+ }
283
+
273
284
  /**
274
285
  * Setup all the required rules for the parser but without creating duplicates
275
286
  *
@@ -9,10 +9,11 @@ export function branchDeclaration($) {
9
9
  $.SUBRULE($.optionalCommentBlockFn);
10
10
 
11
11
  const parent = $.CONSUME(BranchToken); // branch
12
- $.OR([
12
+ const nameToken = $.OR([
13
13
  { ALT: () => $.CONSUME(StringLiteral) },
14
14
  { ALT: () => $.CONSUME(Identifier) },
15
15
  ]);
16
+ $.validateDeclarationName(nameToken, "branch");
16
17
 
17
18
  const middlewareSchema = {
18
19
  pre: [{ name: "[string]" }],
@@ -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.',
@@ -29,6 +29,7 @@ export function functionDeclaration($) {
29
29
  { ALT: () => $.CONSUME(StringLiteral) },
30
30
  ]);
31
31
 
32
+ $.validateDeclarationName(nameToken, "function");
32
33
  if (nameToken?.image && getVarName(nameToken).startsWith("/")) {
33
34
  $.addInvalidValueError(
34
35
  nameToken,
@@ -16,6 +16,20 @@ describe("function_parser", () => {
16
16
  expect(parser.errors).to.be.empty;
17
17
  });
18
18
 
19
+ it("should raise an error when a function name is empty", () => {
20
+ const parser = xanoscriptParser(`function "" {
21
+ input {
22
+ }
23
+
24
+ stack {
25
+ }
26
+
27
+ response = null
28
+ }`);
29
+ expect(parser.errors).to.not.be.empty;
30
+ expect(parser.errors[0].message).to.include("must not be empty");
31
+ });
32
+
19
33
  it("should raise an error when a function name starts with /", () => {
20
34
  const parser = xanoscriptParser(`function "/foo" {
21
35
  input {
@@ -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;
@@ -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
  });
@@ -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 $.CONSUME(IntegerLiteral); // e.g., 42
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
  }