@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
@@ -299,6 +299,10 @@ function bar {
299
299
  var.update $x {
300
300
  value = 2
301
301
  }
302
+
303
+ var $y {
304
+ value = 2
305
+ }
302
306
  }
303
307
 
304
308
  response = null
@@ -691,7 +695,9 @@ function bar {
691
695
  // The error must be within seg3's global range
692
696
  const error = result.errors[0];
693
697
  expect(error.token.startOffset).to.be.at.least(expectedSeg3Start);
694
- expect(error.token.startOffset).to.be.lessThan(expectedSeg3Start + seg3.length);
698
+ expect(error.token.startOffset).to.be.lessThan(
699
+ expectedSeg3Start + seg3.length,
700
+ );
695
701
  });
696
702
 
697
703
  it("should isolate errors — bad seg1 should not prevent seg2 from parsing", () => {
@@ -30,6 +30,7 @@ export function queryDeclaration($) {
30
30
  let hasTags = false;
31
31
  let hasApiGroup = false;
32
32
  let hasResponseType = false;
33
+ let responseType = "standard";
33
34
 
34
35
  const testNames = [];
35
36
 
@@ -43,6 +44,7 @@ export function queryDeclaration($) {
43
44
  { ALT: () => $.CONSUME(Identifier) }, // foo
44
45
  ]);
45
46
 
47
+ $.validateDeclarationName(nameToken, "query");
46
48
  if (nameToken?.image && getVarName(nameToken).startsWith("/")) {
47
49
  $.addInvalidValueError(nameToken, "query name must not start with '/'");
48
50
  }
@@ -79,8 +81,8 @@ export function queryDeclaration($) {
79
81
  $.CONSUME(ResponseTypeToken);
80
82
  $.CONSUME8(EqualToken);
81
83
  const valueToken = $.CONSUME2(StringLiteral);
82
- const value = getVarName(valueToken);
83
- if (!["stream", "standard"].includes(value)) {
84
+ responseType = getVarName(valueToken);
85
+ if (!["stream", "standard"].includes(responseType)) {
84
86
  $.addInvalidValueError(
85
87
  valueToken,
86
88
  "response_type must be either 'stream' or 'standard'",
@@ -192,10 +194,17 @@ export function queryDeclaration($) {
192
194
  $.addMissingError(parent, "{} is missing a stack clause");
193
195
  }
194
196
 
195
- if (!hasResponse) {
197
+ if (!hasResponse && responseType === "standard") {
196
198
  $.addMissingError(parent, "{} is missing a response clause");
197
199
  }
198
200
 
201
+ if (hasResponse && responseType === "stream") {
202
+ $.addInvalidValueError(
203
+ parent,
204
+ "response clause is not allowed when response_type is 'stream'",
205
+ );
206
+ }
207
+
199
208
  $.MANY1(() => $.CONSUME1(NewlineToken)); // optional new line
200
209
  $.CONSUME(RCurly); // "}"
201
210
  $.MANY2(() => $.CONSUME2(NewlineToken)); // optional new line
@@ -16,6 +16,20 @@ describe("query_parser", () => {
16
16
  expect(parser.errors).to.be.empty;
17
17
  });
18
18
 
19
+ it("should raise an error when a query name is empty", () => {
20
+ const parser = xanoscriptParser(`query "" verb=GET {
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 query starts with /", () => {
20
34
  const parser = xanoscriptParser(`query "/foo" verb=GET {
21
35
  input {
@@ -60,7 +74,6 @@ describe("query_parser", () => {
60
74
  expect(parser.errors).to.be.empty;
61
75
 
62
76
  parser = xanoscriptParser(`query foo verb=GET {
63
-
64
77
  response_type = "stream"
65
78
 
66
79
  input {
@@ -68,8 +81,6 @@ describe("query_parser", () => {
68
81
 
69
82
  stack {
70
83
  }
71
-
72
- response = null
73
84
  }`);
74
85
  expect(parser.errors).to.be.empty;
75
86
  });
@@ -9,10 +9,11 @@ export function realtimeChannelDeclaration($) {
9
9
  $.SUBRULE($.optionalCommentBlockFn);
10
10
 
11
11
  const parent = $.CONSUME(RealtimeChannelToken); // realtime_channel
12
- $.OR([
12
+ const nameToken = $.OR([
13
13
  { ALT: () => $.CONSUME(StringLiteral) },
14
14
  { ALT: () => $.CONSUME(Identifier) },
15
15
  ]);
16
+ $.validateDeclarationName(nameToken, "realtime_channel");
16
17
 
17
18
  $.SUBRULE($.schemaParseAttributeFn, {
18
19
  ARGS: [
@@ -25,10 +25,11 @@ export function realtimeTriggerDeclaration($) {
25
25
  $.SUBRULE($.optionalCommentBlockFn);
26
26
 
27
27
  const parent = $.CONSUME(RealtimeTriggerToken); // realtime_trigger
28
- $.OR([
28
+ const nameToken = $.OR([
29
29
  { ALT: () => $.CONSUME(StringLiteral) },
30
30
  { ALT: () => $.CONSUME(Identifier) },
31
31
  ]);
32
+ $.validateDeclarationName(nameToken, "realtime_trigger");
32
33
  $.CONSUME(LCurly); // "{"
33
34
  $.MANY(() => {
34
35
  $.AT_LEAST_ONE(() => $.CONSUME(NewlineToken)); // at least one new line
@@ -25,10 +25,11 @@ export function runJobClause($) {
25
25
  return () => {
26
26
  const parent = $.CONSUME(JobToken); // "job"
27
27
 
28
- $.OR([
28
+ const nameToken = $.OR([
29
29
  { ALT: () => $.CONSUME(StringLiteral) },
30
30
  { ALT: () => $.CONSUME(Identifier) },
31
31
  ]);
32
+ $.validateDeclarationName(nameToken, "job");
32
33
 
33
34
  $.SUBRULE($.schemaParseAttributeFn, {
34
35
  ARGS: [
@@ -49,10 +50,11 @@ export function runServiceClause($) {
49
50
  return () => {
50
51
  const parent = $.CONSUME(ServiceToken); // "service"
51
52
 
52
- $.OR([
53
+ const nameToken = $.OR([
53
54
  { ALT: () => $.CONSUME(StringLiteral) },
54
55
  { ALT: () => $.CONSUME(Identifier) },
55
56
  ]);
57
+ $.validateDeclarationName(nameToken, "service");
56
58
 
57
59
  $.SUBRULE($.schemaParseAttributeFn, {
58
60
  ARGS: [
@@ -25,7 +25,7 @@ export function tableDeclaration($) {
25
25
  $.SUBRULE($.optionalCommentBlockFn);
26
26
 
27
27
  $.CONSUME(TableToken); // "table"
28
- $.OR1([
28
+ const nameToken = $.OR1([
29
29
  {
30
30
  ALT: () =>
31
31
  $.CONSUME(StringLiteral, {
@@ -39,6 +39,7 @@ export function tableDeclaration($) {
39
39
  }),
40
40
  },
41
41
  ]);
42
+ $.validateDeclarationName(nameToken, "table");
42
43
 
43
44
  let hasSchema = false;
44
45
  let hasView = false;
@@ -30,10 +30,11 @@ export function tableTriggerDeclaration($) {
30
30
  $.SUBRULE($.optionalCommentBlockFn);
31
31
 
32
32
  const parent = $.CONSUME(TableTriggerToken); // table_trigger
33
- $.OR([
33
+ const nameToken = $.OR([
34
34
  { ALT: () => $.CONSUME(StringLiteral) },
35
35
  { ALT: () => $.CONSUME(Identifier) },
36
36
  ]);
37
+ $.validateDeclarationName(nameToken, "table_trigger");
37
38
  $.CONSUME(LCurly); // "{"
38
39
  $.MANY(() => {
39
40
  $.AT_LEAST_ONE(() => $.CONSUME(NewlineToken)); // at least one new line
@@ -25,10 +25,11 @@ export function taskDeclaration($) {
25
25
  $.SUBRULE($.optionalCommentBlockFn);
26
26
 
27
27
  $.CONSUME(TaskToken);
28
- $.OR([
28
+ const nameToken = $.OR([
29
29
  { ALT: () => $.CONSUME(StringLiteral) },
30
30
  { ALT: () => $.CONSUME(Identifier) },
31
31
  ]);
32
+ $.validateDeclarationName(nameToken, "task");
32
33
  $.CONSUME(LCurly); // "{"
33
34
  $.MANY(() => {
34
35
  $.AT_LEAST_ONE(() => $.CONSUME(NewlineToken)); // at least one new line
@@ -13,6 +13,17 @@ describe("task_parser", () => {
13
13
  expect(parser.errors).to.be.empty;
14
14
  });
15
15
 
16
+ it("should raise an error when a task name is empty", () => {
17
+ const parser = xanoscriptParser(`task "" {
18
+ stack {
19
+ }
20
+
21
+ schedule = []
22
+ }`);
23
+ expect(parser.errors).to.not.be.empty;
24
+ expect(parser.errors[0].message).to.include("must not be empty");
25
+ });
26
+
16
27
  it("should parse an active task", () => {
17
28
  const parser = xanoscriptParser(`task task_for_listing {
18
29
  active = true
@@ -9,12 +9,12 @@ describe("Xanoscript Function Parser", () => {
9
9
  it("parses all the valid sources", () => {
10
10
  const errors = [];
11
11
  const files = readdirSync(
12
- join(dirname("./"), "parser/tests/function/valid_sources")
12
+ join(dirname("./"), "parser/tests/function/valid_sources"),
13
13
  );
14
14
  files.forEach((file) => {
15
15
  const source = readFileSync(
16
16
  join(dirname("./"), "parser/tests/function/valid_sources", file),
17
- "utf8"
17
+ "utf8",
18
18
  );
19
19
  const parser = xanoscriptParser(source);
20
20
 
@@ -26,7 +26,7 @@ describe("Xanoscript Function Parser", () => {
26
26
  error.previousToken.image,
27
27
  {
28
28
  length: 20,
29
- }
29
+ },
30
30
  )})`
31
31
  : "N/A",
32
32
  file: `parser/tests/function/valid_sources/${file}:${error.token.startLine}:${error.token.startColumn}`,
@@ -36,7 +36,7 @@ describe("Xanoscript Function Parser", () => {
36
36
  error.token.image,
37
37
  {
38
38
  length: 20,
39
- }
39
+ },
40
40
  )})`,
41
41
  });
42
42
  });
@@ -0,0 +1,55 @@
1
+ import { expect } from "chai";
2
+ import { readdirSync, readFileSync } from "fs";
3
+ import { truncate } from "lodash-es";
4
+ import { describe, it } from "mocha";
5
+ import { dirname, join } from "path";
6
+ import { multidocParser } from "../../multidoc.js";
7
+
8
+ describe("Xanoscript multidoc Parser", () => {
9
+ it("parses all the valid sources", () => {
10
+ const errors = [];
11
+ const files = readdirSync(
12
+ join(dirname("./"), "parser/tests/multidoc/valid_sources"),
13
+ );
14
+
15
+ files.forEach((file) => {
16
+ const source = readFileSync(
17
+ join(dirname("./"), "parser/tests/multidoc/valid_sources", file),
18
+ "utf8",
19
+ );
20
+ const parser = multidocParser(source);
21
+
22
+ if (parser.errors.length > 0) {
23
+ parser.errors.forEach((error) => {
24
+ errors.push({
25
+ previous: error.previousToken
26
+ ? `${error.previousToken.tokenTypeName} (${truncate(
27
+ error.previousToken.image,
28
+ {
29
+ length: 20,
30
+ },
31
+ )})`
32
+ : "N/A",
33
+ file: error.token
34
+ ? `parser/tests/multidoc/valid_sources/${file}:${error.token.startLine}:${error.token.startColumn}`
35
+ : `parser/tests/multidoc/valid_sources/${file}`,
36
+ stack:
37
+ parser.sectionStack?.[parser.sectionStack.length - 1] ?? "N/A",
38
+ message: truncate(error.message, { length: 50 }),
39
+ token: error.token
40
+ ? `${error.token.tokenTypeName} (${truncate(error.token.image, {
41
+ length: 20,
42
+ })})`
43
+ : "N/A",
44
+ });
45
+ });
46
+ }
47
+ });
48
+
49
+ if (errors.length > 0) {
50
+ console.table(errors);
51
+ }
52
+
53
+ expect(errors).to.be.empty;
54
+ });
55
+ });