@xano/xanoscript-language-server 11.8.4 → 11.9.0

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 (59) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/cache/documentCache.js +58 -10
  3. package/lexer/comment.js +14 -24
  4. package/lexer/db.js +1 -2
  5. package/lexer/security.js +16 -0
  6. package/onCompletion/onCompletion.js +61 -1
  7. package/onDefinition/onDefinition.js +150 -0
  8. package/onDefinition/onDefinition.spec.js +313 -0
  9. package/onDidChangeContent/onDidChangeContent.js +53 -6
  10. package/onHover/functions.md +28 -0
  11. package/package.json +1 -1
  12. package/parser/base_parser.js +61 -3
  13. package/parser/clauses/middlewareClause.js +16 -0
  14. package/parser/definitions/columnDefinition.js +5 -0
  15. package/parser/functions/api/apiCallFn.js +5 -3
  16. package/parser/functions/controls/functionCallFn.js +5 -3
  17. package/parser/functions/controls/functionRunFn.js +61 -5
  18. package/parser/functions/controls/taskCallFn.js +5 -3
  19. package/parser/functions/db/captureFieldName.js +63 -0
  20. package/parser/functions/db/dbAddFn.js +5 -3
  21. package/parser/functions/db/dbAddOrEditFn.js +13 -3
  22. package/parser/functions/db/dbBulkAddFn.js +5 -3
  23. package/parser/functions/db/dbBulkDeleteFn.js +5 -3
  24. package/parser/functions/db/dbBulkPatchFn.js +5 -3
  25. package/parser/functions/db/dbBulkUpdateFn.js +5 -3
  26. package/parser/functions/db/dbDelFn.js +10 -3
  27. package/parser/functions/db/dbEditFn.js +13 -3
  28. package/parser/functions/db/dbGetFn.js +10 -3
  29. package/parser/functions/db/dbHasFn.js +9 -3
  30. package/parser/functions/db/dbPatchFn.js +10 -3
  31. package/parser/functions/db/dbQueryFn.js +29 -3
  32. package/parser/functions/db/dbSchemaFn.js +5 -3
  33. package/parser/functions/db/dbTruncateFn.js +5 -3
  34. package/parser/functions/middlewareCallFn.js +3 -1
  35. package/parser/functions/security/register.js +19 -9
  36. package/parser/functions/security/securityCreateAuthTokenFn.js +22 -0
  37. package/parser/functions/security/securityJweDecodeLegacyFn.js +24 -0
  38. package/parser/functions/security/securityJweDecodeLegacyFn.spec.js +26 -0
  39. package/parser/functions/security/securityJweEncodeLegacyFn.js +24 -0
  40. package/parser/functions/security/securityJweEncodeLegacyFn.spec.js +25 -0
  41. package/parser/functions/securityFn.js +2 -0
  42. package/parser/functions/varFn.js +1 -1
  43. package/parser/generic/asVariable.js +2 -0
  44. package/parser/generic/assignableVariableAs.js +1 -0
  45. package/parser/generic/assignableVariableProperty.js +5 -2
  46. package/parser/task_parser.js +2 -1
  47. package/parser/tests/task/valid_sources/create_leak.xs +165 -0
  48. package/parser/tests/variable_test/coverage_check.xs +293 -0
  49. package/parser/variableScanner.js +64 -0
  50. package/parser/variableValidator.js +44 -0
  51. package/parser/variableValidator.spec.js +179 -0
  52. package/server.js +206 -18
  53. package/utils.js +32 -0
  54. package/utils.spec.js +93 -1
  55. package/workspace/crossFileValidator.js +166 -0
  56. package/workspace/crossFileValidator.spec.js +654 -0
  57. package/workspace/referenceTracking.spec.js +420 -0
  58. package/workspace/workspaceIndex.js +149 -0
  59. package/workspace/workspaceIndex.spec.js +189 -0
@@ -2,6 +2,7 @@ import { CallToken } from "../../lexer/action.js";
2
2
  import { StringLiteral } from "../../lexer/literal.js";
3
3
  import { MiddlewareToken } from "../../lexer/middleware.js";
4
4
  import { DotToken, Identifier } from "../../lexer/tokens.js";
5
+ import { getVarName } from "../generic/utils.js";
5
6
 
6
7
  /**
7
8
  *
@@ -15,10 +16,11 @@ export function middlewareCallFn($) {
15
16
  $.CONSUME(DotToken); // "."
16
17
  const fnToken = $.CONSUME(CallToken); // "call"
17
18
 
18
- $.OR([
19
+ const nameToken = $.OR([
19
20
  { ALT: () => $.CONSUME(Identifier) }, // user
20
21
  { ALT: () => $.CONSUME1(StringLiteral) }, // "user auth"
21
22
  ]);
23
+ $.addReference("middleware", getVarName(nameToken), nameToken);
22
24
 
23
25
  $.OPTION(() => {
24
26
  $.SUBRULE($.schemaParseAttributeFn, {
@@ -8,7 +8,9 @@ import { securityCreateUuidFn } from "./securityCreateUuidFn.js";
8
8
  import { securityDecryptFn } from "./securityDecryptFn.js";
9
9
  import { securityEncryptFn } from "./securityEncryptFn.js";
10
10
  import { securityJweDecodeFn } from "./securityJweDecodeFn.js";
11
+ import { securityJweDecodeLegacyFn } from "./securityJweDecodeLegacyFn.js";
11
12
  import { securityJweEncodeFn } from "./securityJweEncodeFn.js";
13
+ import { securityJweEncodeLegacyFn } from "./securityJweEncodeLegacyFn.js";
12
14
  import { securityJwsDecodeFn } from "./securityJwsDecodeFn.js";
13
15
  import { securityJwsEncodeFn } from "./securityJwsEncodeFn.js";
14
16
  import { securityRandomBytesFn } from "./securityRandomBytesFn.js";
@@ -21,44 +23,52 @@ import { securityRandomNumberFn } from "./securityRandomNumberFn.js";
21
23
  export const register = ($) => {
22
24
  $.securityCheckPasswordFn = $.RULE(
23
25
  "securityCheckPasswordFn",
24
- securityCheckPasswordFn($)
26
+ securityCheckPasswordFn($),
25
27
  );
26
28
  $.securityCreateAuthTokenFn = $.RULE(
27
29
  "securityCreateAuthTokenFn",
28
- securityCreateAuthTokenFn($)
30
+ securityCreateAuthTokenFn($),
29
31
  );
30
32
  $.securityCreateCurveKeyFn = $.RULE(
31
33
  "securityCreateCurveKeyFn",
32
- securityCreateCurveKeyFn($)
34
+ securityCreateCurveKeyFn($),
33
35
  );
34
36
  $.securityCreatePasswordFn = $.RULE(
35
37
  "securityCreatePasswordFn",
36
- securityCreatePasswordFn($)
38
+ securityCreatePasswordFn($),
37
39
  );
38
40
  $.securityCreateRsaKeyFn = $.RULE(
39
41
  "securityCreateRsaKeyFn",
40
- securityCreateRsaKeyFn($)
42
+ securityCreateRsaKeyFn($),
41
43
  );
42
44
  $.securityCreateSecretKeyFn = $.RULE(
43
45
  "securityCreateSecretKeyFn",
44
- securityCreateSecretKeyFn($)
46
+ securityCreateSecretKeyFn($),
45
47
  );
46
48
  $.securityCreateUuidFn = $.RULE(
47
49
  "securityCreateUuidFn",
48
- securityCreateUuidFn($)
50
+ securityCreateUuidFn($),
49
51
  );
50
52
  $.securityDecryptFn = $.RULE("securityDecryptFn", securityDecryptFn($));
51
53
  $.securityEncryptFn = $.RULE("securityEncryptFn", securityEncryptFn($));
52
54
  $.securityJweDecodeFn = $.RULE("securityJweDecodeFn", securityJweDecodeFn($));
53
55
  $.securityJweEncodeFn = $.RULE("securityJweEncodeFn", securityJweEncodeFn($));
56
+ $.securityJweDecodeLegacyFn = $.RULE(
57
+ "securityJweDecodeLegacyFn",
58
+ securityJweDecodeLegacyFn($),
59
+ );
60
+ $.securityJweEncodeLegacyFn = $.RULE(
61
+ "securityJweEncodeLegacyFn",
62
+ securityJweEncodeLegacyFn($),
63
+ );
54
64
  $.securityJwsDecodeFn = $.RULE("securityJwsDecodeFn", securityJwsDecodeFn($));
55
65
  $.securityJwsEncodeFn = $.RULE("securityJwsEncodeFn", securityJwsEncodeFn($));
56
66
  $.securityRandomBytesFn = $.RULE(
57
67
  "securityRandomBytesFn",
58
- securityRandomBytesFn($)
68
+ securityRandomBytesFn($),
59
69
  );
60
70
  $.securityRandomNumberFn = $.RULE(
61
71
  "securityRandomNumberFn",
62
- securityRandomNumberFn($)
72
+ securityRandomNumberFn($),
63
73
  );
64
74
  };
@@ -1,4 +1,8 @@
1
+ import { SingleQuotedStringLiteral,StringLiteral } from "../../../lexer/literal.js";
1
2
  import { CreateAuthTokenToken } from "../../../lexer/security.js";
3
+ import { getVarName } from "../../generic/utils.js";
4
+
5
+ const stringTokens = new Set([StringLiteral.name, SingleQuotedStringLiteral.name]);
2
6
 
3
7
  /**
4
8
  * @param {import('../../base_parser.js').XanoBaseParser} $
@@ -8,6 +12,7 @@ export function securityCreateAuthTokenFn($) {
8
12
  $.sectionStack.push("securityCreateAuthTokenFn");
9
13
  const fnToken = $.CONSUME(CreateAuthTokenToken); // "create_auth_token"
10
14
 
15
+ const captured = {};
11
16
  $.SUBRULE($.schemaParseAttributeFn, {
12
17
  ARGS: [
13
18
  fnToken,
@@ -19,9 +24,26 @@ export function securityCreateAuthTokenFn($) {
19
24
  "description?": "[string]",
20
25
  "disabled?": "[boolean]",
21
26
  },
27
+ captured,
22
28
  ],
23
29
  });
24
30
 
31
+ $.ACTION(() => {
32
+ if (!captured.table) return;
33
+ const keyOffset = captured.table.key?.startOffset;
34
+ if (keyOffset == null) return;
35
+ const tokens = $.input;
36
+ for (let i = 0; i < tokens.length - 2; i++) {
37
+ if (tokens[i].startOffset === keyOffset) {
38
+ const valueToken = tokens[i + 2];
39
+ if (valueToken && stringTokens.has(valueToken.tokenType.name)) {
40
+ $.addReference("table", getVarName(valueToken), valueToken);
41
+ }
42
+ break;
43
+ }
44
+ }
45
+ });
46
+
25
47
  $.SUBRULE($.asVariable, { ARGS: [fnToken] });
26
48
  $.sectionStack.pop();
27
49
  };
@@ -0,0 +1,24 @@
1
+ import { EqualToken, LCurly, RCurly } from "../../../lexer/control.js";
2
+ import { JweDecodeLegacyToken } from "../../../lexer/security.js";
3
+ import { Identifier, NewlineToken } from "../../../lexer/tokens.js";
4
+
5
+ /**
6
+ * @param {import('../../base_parser.js').XanoBaseParser} $
7
+ */
8
+ export function securityJweDecodeLegacyFn($) {
9
+ return () => {
10
+ $.sectionStack.push("securityJweDecodeLegacyFn");
11
+ const fnToken = $.CONSUME(JweDecodeLegacyToken); // "jwe_decode_legacy"
12
+ $.CONSUME(LCurly); // "{"
13
+ $.MANY(() => {
14
+ $.AT_LEAST_ONE(() => $.CONSUME(NewlineToken));
15
+ $.CONSUME1(Identifier); // "field_name"
16
+ $.CONSUME(EqualToken); // "="
17
+ $.SUBRULE($.expressionFn);
18
+ });
19
+ $.MANY1(() => $.CONSUME1(NewlineToken));
20
+ $.CONSUME(RCurly); // "}"
21
+ $.SUBRULE($.asVariable, { ARGS: [fnToken] });
22
+ $.sectionStack.pop();
23
+ };
24
+ }
@@ -0,0 +1,26 @@
1
+ import { expect } from "chai";
2
+ import { describe, it } from "mocha";
3
+ import { lexDocument } from "../../../lexer/lexer.js";
4
+ import { parser } from "../../test_parser.js";
5
+
6
+ function parse(inputText) {
7
+ parser.reset();
8
+ const lexResult = lexDocument(inputText);
9
+ parser.input = lexResult.tokens;
10
+ parser.securityJweDecodeLegacyFn();
11
+ return parser;
12
+ }
13
+
14
+ describe("securityJweDecodeLegacyFn", () => {
15
+ it("securityJweDecodeLegacyFn accepts attributes and store value in a variable", () => {
16
+ const parser = parse(`jwe_decode_legacy {
17
+ token = $input.magic_token
18
+ key = $env.magic_jwt_secret
19
+ audience = "Xano"
20
+ key_algorithm = "A256KW"
21
+ content_algorithm = "A256CBC-HS512"
22
+ } as $decoded_magic_token`);
23
+ console.log(parser.errors);
24
+ expect(parser.errors).to.be.empty;
25
+ });
26
+ });
@@ -0,0 +1,24 @@
1
+ import { EqualToken, LCurly, RCurly } from "../../../lexer/control.js";
2
+ import { JweEncodeLegacyToken } from "../../../lexer/security.js";
3
+ import { Identifier, NewlineToken } from "../../../lexer/tokens.js";
4
+
5
+ /**
6
+ * @param {import('../../base_parser.js').XanoBaseParser} $
7
+ */
8
+ export function securityJweEncodeLegacyFn($) {
9
+ return () => {
10
+ $.sectionStack.push("securityJweEncodeLegacyFn");
11
+ const fnToken = $.CONSUME(JweEncodeLegacyToken); // "jwe_encode_legacy"
12
+ $.CONSUME(LCurly); // "{"
13
+ $.MANY(() => {
14
+ $.AT_LEAST_ONE(() => $.CONSUME(NewlineToken));
15
+ $.CONSUME1(Identifier); // "field_name"
16
+ $.CONSUME(EqualToken); // "="
17
+ $.SUBRULE($.expressionFn);
18
+ });
19
+ $.MANY1(() => $.CONSUME1(NewlineToken));
20
+ $.CONSUME(RCurly); // "}"
21
+ $.SUBRULE($.asVariable, { ARGS: [fnToken] });
22
+ $.sectionStack.pop();
23
+ };
24
+ }
@@ -0,0 +1,25 @@
1
+ import { expect } from "chai";
2
+ import { describe, it } from "mocha";
3
+ import { lexDocument } from "../../../lexer/lexer.js";
4
+ import { parser } from "../../test_parser.js";
5
+
6
+ function parse(inputText) {
7
+ parser.reset();
8
+ const lexResult = lexDocument(inputText);
9
+ parser.input = lexResult.tokens;
10
+ parser.securityJweEncodeLegacyFn();
11
+ return parser;
12
+ }
13
+
14
+ describe("securityJweEncodeLegacyFn", () => {
15
+ it("securityJweEncodeLegacyFn accepts attributes and store value in a variable", () => {
16
+ const parser = parse(`jwe_encode_legacy {
17
+ payload = $jwt_payload
18
+ audience = "Xano"
19
+ key = $env.magic_jwt_secret
20
+ key_algorithm = "A256KW"
21
+ content_algorithm = "A256CBC-HS512"
22
+ } as $jwt`);
23
+ expect(parser.errors).to.be.empty;
24
+ });
25
+ });
@@ -20,6 +20,8 @@ export function securityFn($) {
20
20
  { ALT: () => $.SUBRULE($.securityEncryptFn) }, // security.encrypt
21
21
  { ALT: () => $.SUBRULE($.securityJweDecodeFn) }, // security.jwe_decode
22
22
  { ALT: () => $.SUBRULE($.securityJweEncodeFn) }, // security.jwe_encode
23
+ { ALT: () => $.SUBRULE($.securityJweDecodeLegacyFn) }, // security.jwe_decode_legacy
24
+ { ALT: () => $.SUBRULE($.securityJweEncodeLegacyFn) }, // security.jwe_encode_legacy
23
25
  { ALT: () => $.SUBRULE($.securityJwsDecodeFn) }, // security.jws_decode
24
26
  { ALT: () => $.SUBRULE($.securityJwsEncodeFn) }, // security.jws_encode
25
27
  { ALT: () => $.SUBRULE($.securityRandomBytesFn) }, // security.random_bytes
@@ -68,7 +68,7 @@ export function varFn($) {
68
68
  // we can directly add the variable here but
69
69
  // not necessary for the `assignableVariableProperty` below
70
70
  // which will add it on its own
71
- $.addVariable(variable.image, "unknown");
71
+ $.addVariable(variable.image, "unknown", variable);
72
72
  }
73
73
  },
74
74
  },
@@ -13,7 +13,9 @@ export function asVariable($) {
13
13
  $.OPTION(() => {
14
14
  hasAs = true;
15
15
  $.CONSUME(AsToken); // "as"
16
+ $.__assignableIsDeclaration = true;
16
17
  $.SUBRULE($.assignableVariableProperty);
18
+ $.__assignableIsDeclaration = false;
17
19
  });
18
20
 
19
21
  if (!hasAs && parent) {
@@ -45,6 +45,7 @@ export function assignableVariableAs($) {
45
45
  )
46
46
  );
47
47
  }
48
+ $.addVariable(variableName, "unknown", variableToken);
48
49
  }
49
50
  };
50
51
  }
@@ -128,8 +128,11 @@ export function assignableVariableProperty($) {
128
128
  },
129
129
  });
130
130
 
131
- if (variableName) {
132
- $.addVariable(variableName, "unknown");
131
+ // Only register as a declaration when used in an "as $var" context.
132
+ // Other callers (var.update, math.*, text.*) are usages, not declarations,
133
+ // and the token scanner will pick them up automatically.
134
+ if (variableName && $.__assignableIsDeclaration) {
135
+ $.addVariable(variableName, "unknown", variableToken);
133
136
  }
134
137
  };
135
138
  }
@@ -1,4 +1,5 @@
1
1
  import { ActiveToken } from "../lexer/api_group.js";
2
+ import { CommentToken } from "../lexer/comment.js";
2
3
  import { EqualToken, LCurly, RCurly } from "../lexer/control.js";
3
4
  import { StringLiteral } from "../lexer/literal.js";
4
5
  import { DataSourceToken, TaskToken } from "../lexer/task.js";
@@ -32,7 +33,7 @@ export function taskDeclaration($) {
32
33
  $.MANY(() => {
33
34
  $.AT_LEAST_ONE(() => $.CONSUME(NewlineToken)); // at least one new line
34
35
  $.OR2([
35
- { ALT: () => $.SUBRULE($.commentBlockFn) },
36
+ { ALT: () => $.CONSUME(CommentToken) },
36
37
  {
37
38
  GATE: () => !hasActive,
38
39
  ALT: () => {
@@ -0,0 +1,165 @@
1
+ task claim_reminders {
2
+ description = "Runs every 6 hours to send reminders for claims stalled at the assessment, review, or approval workflow stages."
3
+
4
+ stack {
5
+ debug.log {
6
+ value = "Claim reminders task started"
7
+ }
8
+
9
+ // --- Cutoff timestamps ---
10
+
11
+ var $forty_eight_hours_ago {
12
+ value = now|transform_timestamp:"-48 hours"
13
+ }
14
+
15
+ var $twenty_four_hours_ago {
16
+ value = now|transform_timestamp:"-24 hours"
17
+ }
18
+
19
+ var $twelve_hours_ago {
20
+ value = now|transform_timestamp:"-12 hours"
21
+ }
22
+
23
+ var $assessment_reminder_count {
24
+ value = 0
25
+ }
26
+
27
+ var $review_reminder_count {
28
+ value = 0
29
+ }
30
+
31
+ var $approval_reminder_count {
32
+ value = 0
33
+ }
34
+
35
+ // === Step 1: Garage Assessment Reminders (stalled > 48h) ===
36
+
37
+ db.query claim {
38
+ where = ($db.claim.workflow_status == "assessment" && $db.claim.updated_at < $forty_eight_hours_ago)
39
+ } as $assessment_claims
40
+
41
+ foreach ($assessment_claims) {
42
+ each as $claim {
43
+ try_catch {
44
+ try {
45
+ function.run "communication/send_notification" {
46
+ input = {
47
+ user_id : $claim.garage_id
48
+ claim_id: $claim.id
49
+ message : "Reminder: Vehicle assessment for claim " ~ $claim.claim_number ~ " is awaiting your action. Please complete the assessment report at your earliest convenience."
50
+ subject : "Assessment Reminder - Claim " ~ $claim.claim_number
51
+ }
52
+ }
53
+
54
+ var.update $assessment_reminder_count {
55
+ value = $assessment_reminder_count + 1
56
+ }
57
+ }
58
+ catch {
59
+ debug.log {
60
+ value = {
61
+ error : "Failed to send assessment reminder"
62
+ claim_number: $claim.claim_number
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+
70
+ debug.log {
71
+ value = "Assessment reminders sent: " ~ $assessment_reminder_count
72
+ }
73
+
74
+ // === Step 2: Agent Review Reminders (stalled > 24h) ===
75
+
76
+ db.query claim {
77
+ where = ($db.claim.workflow_status == "review" && $db.claim.updated_at < $twenty_four_hours_ago)
78
+ } as $review_claims
79
+
80
+ foreach ($review_claims) {
81
+ each as $claim {
82
+ try_catch {
83
+ try {
84
+ function.run "communication/send_notification" {
85
+ input = {
86
+ user_id : $claim.assigned_agent_id
87
+ claim_id: $claim.id
88
+ message : "Reminder: Claim " ~ $claim.claim_number ~ " is awaiting your review. Please complete your review to keep the claim moving forward."
89
+ subject : "Review Reminder - Claim " ~ $claim.claim_number
90
+ }
91
+ }
92
+
93
+ var.update $review_reminder_count {
94
+ value = $review_reminder_count + 1
95
+ }
96
+ }
97
+ catch {
98
+ debug.log {
99
+ value = {
100
+ error : "Failed to send review reminder"
101
+ claim_number: $claim.claim_number
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ debug.log {
110
+ value = "Review reminders sent: " ~ $review_reminder_count
111
+ }
112
+
113
+ // === Step 3: Agent Approval Reminders - URGENT (stalled > 12h) ===
114
+
115
+ db.query claim {
116
+ where = ($db.claim.workflow_status == "approval" && $db.claim.updated_at < $twelve_hours_ago)
117
+ } as $approval_claims
118
+
119
+ foreach ($approval_claims) {
120
+ each as $claim {
121
+ try_catch {
122
+ try {
123
+ function.run "communication/send_notification" {
124
+ input = {
125
+ user_id : $claim.assigned_agent_id
126
+ claim_id: $claim.id
127
+ message : "URGENT: Claim " ~ $claim.claim_number ~ " requires your immediate approval. This claim has been pending approval for over 12 hours. Please review and take action as soon as possible."
128
+ subject : "URGENT: Approval Required - Claim " ~ $claim.claim_number
129
+ }
130
+ }
131
+
132
+ var.update $approval_reminder_count {
133
+ value = $approval_reminder_count + 1
134
+ }
135
+ }
136
+ catch {
137
+ debug.log {
138
+ value = {
139
+ error : "Failed to send approval reminder"
140
+ claim_number: $claim.claim_number
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ debug.log {
149
+ value = "Approval reminders sent: " ~ $approval_reminder_count
150
+ }
151
+
152
+ // === Step 4: Final summary ===
153
+
154
+ debug.log {
155
+ value = {
156
+ assessment_reminders_sent: $assessment_reminder_count
157
+ review_reminders_sent : $review_reminder_count
158
+ approval_reminders_sent : $approval_reminder_count
159
+ total_reminders_sent : ($assessment_reminder_count + $review_reminder_count + $approval_reminder_count)
160
+ }
161
+ }
162
+ }
163
+
164
+ schedule = [{starts_on: 2026-01-01 00:00:00+0000, freq: 21600}]
165
+ }