@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.
- package/.claude/settings.local.json +2 -1
- package/cache/documentCache.js +58 -10
- package/lexer/comment.js +14 -24
- package/lexer/db.js +1 -2
- package/lexer/security.js +16 -0
- package/onCompletion/onCompletion.js +61 -1
- package/onDefinition/onDefinition.js +150 -0
- package/onDefinition/onDefinition.spec.js +313 -0
- package/onDidChangeContent/onDidChangeContent.js +53 -6
- package/onHover/functions.md +28 -0
- package/package.json +1 -1
- package/parser/base_parser.js +61 -3
- package/parser/clauses/middlewareClause.js +16 -0
- package/parser/definitions/columnDefinition.js +5 -0
- package/parser/functions/api/apiCallFn.js +5 -3
- package/parser/functions/controls/functionCallFn.js +5 -3
- package/parser/functions/controls/functionRunFn.js +61 -5
- package/parser/functions/controls/taskCallFn.js +5 -3
- package/parser/functions/db/captureFieldName.js +63 -0
- package/parser/functions/db/dbAddFn.js +5 -3
- package/parser/functions/db/dbAddOrEditFn.js +13 -3
- package/parser/functions/db/dbBulkAddFn.js +5 -3
- package/parser/functions/db/dbBulkDeleteFn.js +5 -3
- package/parser/functions/db/dbBulkPatchFn.js +5 -3
- package/parser/functions/db/dbBulkUpdateFn.js +5 -3
- package/parser/functions/db/dbDelFn.js +10 -3
- package/parser/functions/db/dbEditFn.js +13 -3
- package/parser/functions/db/dbGetFn.js +10 -3
- package/parser/functions/db/dbHasFn.js +9 -3
- package/parser/functions/db/dbPatchFn.js +10 -3
- package/parser/functions/db/dbQueryFn.js +29 -3
- package/parser/functions/db/dbSchemaFn.js +5 -3
- package/parser/functions/db/dbTruncateFn.js +5 -3
- package/parser/functions/middlewareCallFn.js +3 -1
- package/parser/functions/security/register.js +19 -9
- package/parser/functions/security/securityCreateAuthTokenFn.js +22 -0
- package/parser/functions/security/securityJweDecodeLegacyFn.js +24 -0
- package/parser/functions/security/securityJweDecodeLegacyFn.spec.js +26 -0
- package/parser/functions/security/securityJweEncodeLegacyFn.js +24 -0
- package/parser/functions/security/securityJweEncodeLegacyFn.spec.js +25 -0
- package/parser/functions/securityFn.js +2 -0
- package/parser/functions/varFn.js +1 -1
- package/parser/generic/asVariable.js +2 -0
- package/parser/generic/assignableVariableAs.js +1 -0
- package/parser/generic/assignableVariableProperty.js +5 -2
- package/parser/task_parser.js +2 -1
- package/parser/tests/task/valid_sources/create_leak.xs +165 -0
- package/parser/tests/variable_test/coverage_check.xs +293 -0
- package/parser/variableScanner.js +64 -0
- package/parser/variableValidator.js +44 -0
- package/parser/variableValidator.spec.js +179 -0
- package/server.js +206 -18
- package/utils.js +32 -0
- package/utils.spec.js +93 -1
- package/workspace/crossFileValidator.js +166 -0
- package/workspace/crossFileValidator.spec.js +654 -0
- package/workspace/referenceTracking.spec.js +420 -0
- package/workspace/workspaceIndex.js +149 -0
- package/workspace/workspaceIndex.spec.js +189 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { before, beforeEach, describe, it } from "mocha";
|
|
4
|
+
import { lexDocument } from "../lexer/lexer.js";
|
|
5
|
+
import { xanoscriptParser } from "../parser/parser.js";
|
|
6
|
+
import { WorkspaceIndex } from "../workspace/workspaceIndex.js";
|
|
7
|
+
import { findDefinition } from "./onDefinition.js";
|
|
8
|
+
|
|
9
|
+
describe("findDefinition", () => {
|
|
10
|
+
let index;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
index = new WorkspaceIndex();
|
|
14
|
+
index.addFile("file:///ws/users.xs", "table users {\n schema {\n }\n}");
|
|
15
|
+
index.addFile("file:///ws/helper.xs", 'function "helper" {\n}');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should find definition for function.run reference", () => {
|
|
19
|
+
const text = `function "caller" {
|
|
20
|
+
stack {
|
|
21
|
+
function.run "helper" as $result
|
|
22
|
+
}
|
|
23
|
+
}`;
|
|
24
|
+
const offset = text.indexOf('"helper"') + 1;
|
|
25
|
+
const result = findDefinition(text, offset, index);
|
|
26
|
+
expect(result).to.exist;
|
|
27
|
+
expect(result.uri).to.equal("file:///ws/helper.xs");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should find definition for db.get reference", () => {
|
|
31
|
+
const text = `function "caller" {
|
|
32
|
+
stack {
|
|
33
|
+
db.get users {
|
|
34
|
+
field_name = "user_id"
|
|
35
|
+
field_value = 1
|
|
36
|
+
} as $user
|
|
37
|
+
}
|
|
38
|
+
}`;
|
|
39
|
+
const offset = text.indexOf("users") + 2;
|
|
40
|
+
const result = findDefinition(text, offset, index);
|
|
41
|
+
expect(result).to.exist;
|
|
42
|
+
expect(result.uri).to.equal("file:///ws/users.xs");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should return null when cursor is not on a reference", () => {
|
|
46
|
+
const text = `function "caller" {
|
|
47
|
+
stack {
|
|
48
|
+
}
|
|
49
|
+
}`;
|
|
50
|
+
const offset = text.indexOf("stack") + 2;
|
|
51
|
+
const result = findDefinition(text, offset, index);
|
|
52
|
+
expect(result).to.be.null;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should return null when reference target not in index", () => {
|
|
56
|
+
const text = `function "caller" {
|
|
57
|
+
stack {
|
|
58
|
+
function.run "unknown" as $result
|
|
59
|
+
}
|
|
60
|
+
}`;
|
|
61
|
+
const offset = text.indexOf('"unknown"') + 1;
|
|
62
|
+
const result = findDefinition(text, offset, index);
|
|
63
|
+
expect(result).to.be.null;
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("findDefinition - variable go-to-definition with coverage_check.xs", () => {
|
|
68
|
+
let text;
|
|
69
|
+
let lexResult;
|
|
70
|
+
let st;
|
|
71
|
+
let index;
|
|
72
|
+
let tokens;
|
|
73
|
+
|
|
74
|
+
before(() => {
|
|
75
|
+
text = readFileSync(
|
|
76
|
+
new URL(
|
|
77
|
+
"../parser/tests/variable_test/coverage_check.xs",
|
|
78
|
+
import.meta.url,
|
|
79
|
+
),
|
|
80
|
+
"utf8",
|
|
81
|
+
);
|
|
82
|
+
lexResult = lexDocument(text);
|
|
83
|
+
const parser = xanoscriptParser(text, undefined, lexResult);
|
|
84
|
+
st = { ...parser.__symbolTable };
|
|
85
|
+
st.varDeclarations = [...parser.__symbolTable.varDeclarations];
|
|
86
|
+
tokens = lexResult.tokens;
|
|
87
|
+
index = new WorkspaceIndex();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
function lineOf(offset) {
|
|
91
|
+
return text.substring(0, offset).split("\n").length;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function findAtLine(line, varImage) {
|
|
95
|
+
// Find token matching varImage on the given line
|
|
96
|
+
for (const t of tokens) {
|
|
97
|
+
if (t.image === varImage && lineOf(t.startOffset) === line) {
|
|
98
|
+
return findDefinition(text, t.startOffset, index, st);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`Token '${varImage}' not found on line ${line}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// --- Declarations should return NULL (already at definition) ---
|
|
105
|
+
|
|
106
|
+
it("L26: as $vehicle (declaration) -> null", () => {
|
|
107
|
+
expect(findAtLine(26, "$vehicle")).to.be.null;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("L35: var $unused_variable (declaration) -> null", () => {
|
|
111
|
+
expect(findAtLine(35, "$unused_variable")).to.be.null;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("L63: as $policy (declaration) -> null", () => {
|
|
115
|
+
expect(findAtLine(63, "$policy")).to.be.null;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("L109: each as $pc (declaration) -> null", () => {
|
|
119
|
+
expect(findAtLine(109, "$pc")).to.be.null;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("L160: each as $claim (declaration) -> null", () => {
|
|
123
|
+
expect(findAtLine(160, "$claim")).to.be.null;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// --- Built-in $db should return NULL ---
|
|
127
|
+
|
|
128
|
+
it("L19: $db (built-in) -> null", () => {
|
|
129
|
+
expect(findAtLine(19, "$db")).to.be.null;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// --- Usages should resolve to correct declaration line ---
|
|
133
|
+
|
|
134
|
+
it("L29: $vehicle in precondition -> decl at L26", () => {
|
|
135
|
+
const r = findAtLine(29, "$vehicle");
|
|
136
|
+
expect(r).to.exist;
|
|
137
|
+
expect(lineOf(r.offset)).to.equal(26);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("L42: $vehicle.vin in var value -> decl at L26", () => {
|
|
141
|
+
const r = findAtLine(42, "$vehicle");
|
|
142
|
+
expect(r).to.exist;
|
|
143
|
+
expect(lineOf(r.offset)).to.equal(26);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("L57: $vehicle.id in db.query where -> decl at L26", () => {
|
|
147
|
+
const r = findAtLine(57, "$vehicle");
|
|
148
|
+
expect(r).to.exist;
|
|
149
|
+
expect(lineOf(r.offset)).to.equal(26);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("L82: $policy in conditional if -> decl at L63", () => {
|
|
153
|
+
const r = findAtLine(82, "$policy");
|
|
154
|
+
expect(r).to.exist;
|
|
155
|
+
expect(lineOf(r.offset)).to.equal(63);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("L87: $policy_response in var.update -> decl at L66", () => {
|
|
159
|
+
const r = findAtLine(87, "$policy_response");
|
|
160
|
+
expect(r).to.exist;
|
|
161
|
+
expect(lineOf(r.offset)).to.equal(66);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("L89: $policy.policy_number in value -> decl at L63", () => {
|
|
165
|
+
const r = findAtLine(89, "$policy");
|
|
166
|
+
expect(r).to.exist;
|
|
167
|
+
expect(lineOf(r.offset)).to.equal(63);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("L108: $policy_coverages in foreach -> decl at L105", () => {
|
|
171
|
+
const r = findAtLine(108, "$policy_coverages");
|
|
172
|
+
expect(r).to.exist;
|
|
173
|
+
expect(lineOf(r.offset)).to.equal(105);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("L112: $pc.coverage_type_id in field_value -> decl at L109", () => {
|
|
177
|
+
const r = findAtLine(112, "$pc");
|
|
178
|
+
expect(r).to.exist;
|
|
179
|
+
expect(lineOf(r.offset)).to.equal(109);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("L118: $ctype.code in value -> decl at L113", () => {
|
|
183
|
+
const r = findAtLine(118, "$ctype");
|
|
184
|
+
expect(r).to.exist;
|
|
185
|
+
expect(lineOf(r.offset)).to.equal(113);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("L127: $coverages_response in array.push -> decl at L70", () => {
|
|
189
|
+
const r = findAtLine(127, "$coverages_response");
|
|
190
|
+
expect(r).to.exist;
|
|
191
|
+
expect(lineOf(r.offset)).to.equal(70);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("L128: $coverage_item in value -> decl at L116", () => {
|
|
195
|
+
const r = findAtLine(128, "$coverage_item");
|
|
196
|
+
expect(r).to.exist;
|
|
197
|
+
expect(lineOf(r.offset)).to.equal(116);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("L146: $all_claims in value -> decl at L142", () => {
|
|
201
|
+
const r = findAtLine(146, "$all_claims");
|
|
202
|
+
expect(r).to.exist;
|
|
203
|
+
expect(lineOf(r.offset)).to.equal(142);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("L163: $claim.incident_date in value -> decl at L160", () => {
|
|
207
|
+
const r = findAtLine(163, "$claim");
|
|
208
|
+
expect(r).to.exist;
|
|
209
|
+
expect(lineOf(r.offset)).to.equal(160);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("L168: $claim_date_ts in conditional if -> decl at L162", () => {
|
|
213
|
+
const r = findAtLine(168, "$claim_date_ts");
|
|
214
|
+
expect(r).to.exist;
|
|
215
|
+
expect(lineOf(r.offset)).to.equal(162);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("L168: $two_years_ago in conditional if -> decl at L150", () => {
|
|
219
|
+
const r = findAtLine(168, "$two_years_ago");
|
|
220
|
+
expect(r).to.exist;
|
|
221
|
+
expect(lineOf(r.offset)).to.equal(150);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("L179: $recent_claims_list in array.push -> decl at L155", () => {
|
|
225
|
+
const r = findAtLine(179, "$recent_claims_list");
|
|
226
|
+
expect(r).to.exist;
|
|
227
|
+
expect(lineOf(r.offset)).to.equal(155);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("L180: $recent_claim_item in value -> decl at L169", () => {
|
|
231
|
+
const r = findAtLine(180, "$recent_claim_item");
|
|
232
|
+
expect(r).to.exist;
|
|
233
|
+
expect(lineOf(r.offset)).to.equal(169);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("L188: $claims_summary in var.update -> decl at L74", () => {
|
|
237
|
+
const r = findAtLine(188, "$claims_summary");
|
|
238
|
+
expect(r).to.exist;
|
|
239
|
+
expect(lineOf(r.offset)).to.equal(74);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("L190: $total_claims_count in value -> decl at L145", () => {
|
|
243
|
+
const r = findAtLine(190, "$total_claims_count");
|
|
244
|
+
expect(r).to.exist;
|
|
245
|
+
expect(lineOf(r.offset)).to.equal(145);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("L216: $shop in conditional if -> decl at L212", () => {
|
|
249
|
+
const r = findAtLine(216, "$shop");
|
|
250
|
+
expect(r).to.exist;
|
|
251
|
+
expect(lineOf(r.offset)).to.equal(212);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("L217: $queried_by in var.update -> decl at L202", () => {
|
|
255
|
+
const r = findAtLine(217, "$queried_by");
|
|
256
|
+
expect(r).to.exist;
|
|
257
|
+
expect(lineOf(r.offset)).to.equal(202);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("L232: $vehicle_response in final result -> decl at L40", () => {
|
|
261
|
+
const r = findAtLine(232, "$vehicle_response");
|
|
262
|
+
expect(r).to.exist;
|
|
263
|
+
expect(lineOf(r.offset)).to.equal(40);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("L233: $policy_response in final result -> decl at L66", () => {
|
|
267
|
+
const r = findAtLine(233, "$policy_response");
|
|
268
|
+
expect(r).to.exist;
|
|
269
|
+
expect(lineOf(r.offset)).to.equal(66);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("L234: $coverages_response in final result -> decl at L70", () => {
|
|
273
|
+
const r = findAtLine(234, "$coverages_response");
|
|
274
|
+
expect(r).to.exist;
|
|
275
|
+
expect(lineOf(r.offset)).to.equal(70);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("L235: $claims_summary in final result -> decl at L74", () => {
|
|
279
|
+
const r = findAtLine(235, "$claims_summary");
|
|
280
|
+
expect(r).to.exist;
|
|
281
|
+
expect(lineOf(r.offset)).to.equal(74);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("L241: $queried_by in conditional if -> decl at L202", () => {
|
|
285
|
+
const r = findAtLine(241, "$queried_by");
|
|
286
|
+
expect(r).to.exist;
|
|
287
|
+
expect(lineOf(r.offset)).to.equal(202);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("L242: $result in var.update -> decl at L230", () => {
|
|
291
|
+
const r = findAtLine(242, "$result");
|
|
292
|
+
expect(r).to.exist;
|
|
293
|
+
expect(lineOf(r.offset)).to.equal(230);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("L243: $result in value expression -> decl at L230", () => {
|
|
297
|
+
const r = findAtLine(243, "$result");
|
|
298
|
+
expect(r).to.exist;
|
|
299
|
+
expect(lineOf(r.offset)).to.equal(230);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("L243: $queried_by in value expression -> decl at L202", () => {
|
|
303
|
+
const r = findAtLine(243, "$queried_by");
|
|
304
|
+
expect(r).to.exist;
|
|
305
|
+
expect(lineOf(r.offset)).to.equal(202);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("L251: $result in response = -> decl at L230", () => {
|
|
309
|
+
const r = findAtLine(251, "$result");
|
|
310
|
+
expect(r).to.exist;
|
|
311
|
+
expect(lineOf(r.offset)).to.equal(230);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { documentCache } from "../cache/documentCache.js";
|
|
2
|
+
import { validateVariables } from "../parser/variableValidator.js";
|
|
3
|
+
import { crossFileValidate } from "../workspace/crossFileValidator.js";
|
|
4
|
+
import { workspaceIndex } from "../workspace/workspaceIndex.js";
|
|
2
5
|
|
|
3
6
|
// Diagnostic severity constants
|
|
4
7
|
const SEVERITY = {
|
|
@@ -56,7 +59,7 @@ function createDiagnostics(parser, document) {
|
|
|
56
59
|
* @param {import('vscode-languageserver').Connection} connection
|
|
57
60
|
* @returns
|
|
58
61
|
*/
|
|
59
|
-
export function onDidChangeContent(params, connection) {
|
|
62
|
+
export function onDidChangeContent(params, connection, features = {}) {
|
|
60
63
|
const document = params.document;
|
|
61
64
|
|
|
62
65
|
if (!document) {
|
|
@@ -71,16 +74,14 @@ export function onDidChangeContent(params, connection) {
|
|
|
71
74
|
|
|
72
75
|
try {
|
|
73
76
|
// Parse the XanoScript file using cache
|
|
74
|
-
const { parser, scheme } = documentCache.getOrParse(
|
|
77
|
+
const { lexResult, parser, scheme } = documentCache.getOrParse(
|
|
75
78
|
document.uri,
|
|
76
79
|
document.version,
|
|
77
80
|
text,
|
|
78
81
|
);
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
connection.sendDiagnostics({ uri: document.uri, diagnostics: [] });
|
|
83
|
-
}
|
|
83
|
+
// Update workspace index with already-parsed data (no re-parse needed)
|
|
84
|
+
workspaceIndex.addParsed(document.uri, text, parser.__symbolTable);
|
|
84
85
|
|
|
85
86
|
for (const error of parser.errors) {
|
|
86
87
|
console.error(
|
|
@@ -91,6 +92,52 @@ export function onDidChangeContent(params, connection) {
|
|
|
91
92
|
// Create diagnostics in a single pass
|
|
92
93
|
const diagnostics = createDiagnostics(parser, document);
|
|
93
94
|
|
|
95
|
+
// Run cross-file validation and append as warnings
|
|
96
|
+
if (features.crossFileValidation !== false && parser.__symbolTable?.references) {
|
|
97
|
+
const crossFileWarnings = crossFileValidate(
|
|
98
|
+
parser.__symbolTable.references,
|
|
99
|
+
workspaceIndex,
|
|
100
|
+
);
|
|
101
|
+
for (const warning of crossFileWarnings) {
|
|
102
|
+
diagnostics.push({
|
|
103
|
+
severity: SEVERITY.WARNING,
|
|
104
|
+
range: {
|
|
105
|
+
start: document.positionAt(warning.startOffset),
|
|
106
|
+
end: document.positionAt(warning.endOffset + 1),
|
|
107
|
+
},
|
|
108
|
+
message: warning.message,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Run variable validation and append warnings/hints
|
|
114
|
+
if (features.variableValidation !== false && parser.__symbolTable?.varDeclarations) {
|
|
115
|
+
const varResult = validateVariables(
|
|
116
|
+
parser.__symbolTable,
|
|
117
|
+
lexResult.tokens,
|
|
118
|
+
);
|
|
119
|
+
for (const warning of varResult.warnings) {
|
|
120
|
+
diagnostics.push({
|
|
121
|
+
severity: SEVERITY.WARNING,
|
|
122
|
+
range: {
|
|
123
|
+
start: document.positionAt(warning.startOffset),
|
|
124
|
+
end: document.positionAt(warning.endOffset + 1),
|
|
125
|
+
},
|
|
126
|
+
message: warning.message,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
for (const hint of varResult.hints) {
|
|
130
|
+
diagnostics.push({
|
|
131
|
+
severity: SEVERITY.HINT,
|
|
132
|
+
range: {
|
|
133
|
+
start: document.positionAt(hint.startOffset),
|
|
134
|
+
end: document.positionAt(hint.endOffset + 1),
|
|
135
|
+
},
|
|
136
|
+
message: hint.message,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
94
141
|
console.log(
|
|
95
142
|
`onDidChangeContent(): sending diagnostic (${parser.errors.length} errors) for scheme:`,
|
|
96
143
|
scheme,
|
package/onHover/functions.md
CHANGED
|
@@ -1538,6 +1538,34 @@ security.jwe_encode {
|
|
|
1538
1538
|
|
|
1539
1539
|
Encodes a payload as a JSON Web Encryption (JWE) token with `headers`, `claims`, and a `key`. The `key_algorithm` (e.g., `A256KW`) and `content_algorithm` (e.g., `A256GCM`) secure the token, and `ttl` sets its validity (0 for no expiration). The token is stored in the variable defined by `as`, here `$encrypted_token`.
|
|
1540
1540
|
|
|
1541
|
+
# security.jwe_encode_legacy
|
|
1542
|
+
|
|
1543
|
+
```xs
|
|
1544
|
+
security.jwe_encode_legacy {
|
|
1545
|
+
payload = $jwt_payload
|
|
1546
|
+
audience = "LegacyApp"
|
|
1547
|
+
key = $env.magic_jwt_secret
|
|
1548
|
+
key_algorithm = "A256KW"
|
|
1549
|
+
content_algorithm = "A256CBC-HS512"
|
|
1550
|
+
} as $jwt
|
|
1551
|
+
```
|
|
1552
|
+
|
|
1553
|
+
Encodes a payload as a JSON Web Encryption (JWE) token using legacy algorithms. The `payload` is the data to encrypt, `audience` specifies the intended recipient, and `key` is used for encryption. The `key_algorithm` (e.g., `A256KW`) and `content_algorithm` (e.g., `A256CBC-HS512`) secure the token. The resulting JWE token is stored in the variable defined by `as`, here `$jwt`.
|
|
1554
|
+
|
|
1555
|
+
# security.jwe_decode_legacy
|
|
1556
|
+
|
|
1557
|
+
```xs
|
|
1558
|
+
security.jwe_decode_legacy {
|
|
1559
|
+
token = $input.magic_token
|
|
1560
|
+
key = $env.magic_jwt_secret
|
|
1561
|
+
audience = "LegacyApp"
|
|
1562
|
+
key_algorithm = "A256KW"
|
|
1563
|
+
content_algorithm = "A256CBC-HS512"
|
|
1564
|
+
} as $decoded_magic_token
|
|
1565
|
+
```
|
|
1566
|
+
|
|
1567
|
+
Decodes a JSON Web Encryption (JWE) token that was encoded using legacy algorithms. The `token` is the JWE to decode, `key` is used for decryption, and `audience` specifies the expected recipient. The `key_algorithm` (e.g., `A256KW`) and `content_algorithm` (e.g., `A256CBC-HS512`) are used to properly decode the token. The decoded payload is stored in the variable defined by `as`, here `$decoded_magic_token`.
|
|
1568
|
+
|
|
1541
1569
|
# security.create_secret_key
|
|
1542
1570
|
|
|
1543
1571
|
```xs
|
package/package.json
CHANGED
package/parser/base_parser.js
CHANGED
|
@@ -42,10 +42,16 @@ export class XanoBaseParser extends CstParser {
|
|
|
42
42
|
* Add a variable to the registry
|
|
43
43
|
* @param {string} name the name of the variable
|
|
44
44
|
* @param {string} type the type of the variable (int, decimal, text, bool, etc)
|
|
45
|
-
* @param {
|
|
45
|
+
* @param {import('chevrotain').IToken} [token] the token for position tracking
|
|
46
46
|
*/
|
|
47
|
-
addVariable(name, type,
|
|
48
|
-
this.__symbolTable.var[name] = { type, value };
|
|
47
|
+
addVariable(name, type, token = null) {
|
|
48
|
+
this.__symbolTable.var[name] = { type, value: null };
|
|
49
|
+
this.__symbolTable.varDeclarations.push({
|
|
50
|
+
name,
|
|
51
|
+
type,
|
|
52
|
+
startOffset: token?.startOffset ?? null,
|
|
53
|
+
endOffset: token?.endOffset ?? null,
|
|
54
|
+
});
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
/**
|
|
@@ -58,6 +64,56 @@ export class XanoBaseParser extends CstParser {
|
|
|
58
64
|
this.__symbolTable.input[name] = { type, iterable, nullable, optional };
|
|
59
65
|
}
|
|
60
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Record a cross-file reference for validation.
|
|
69
|
+
* @param {string} refType - The type of object referenced ("function", "table", "query", "task")
|
|
70
|
+
* @param {string} name - The referenced object name
|
|
71
|
+
* @param {import('chevrotain').IToken} token - The token containing the name (for diagnostics positioning)
|
|
72
|
+
*/
|
|
73
|
+
addReference(refType, name, token) {
|
|
74
|
+
if (name == null || !token) return;
|
|
75
|
+
this.__symbolTable.references.push({
|
|
76
|
+
refType,
|
|
77
|
+
name,
|
|
78
|
+
startOffset: token.startOffset,
|
|
79
|
+
endOffset: token.endOffset,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Attach argument keys to the most recent reference (for input validation).
|
|
85
|
+
* @param {Object} args - Map of argument names to { value, startOffset, endOffset }
|
|
86
|
+
*/
|
|
87
|
+
setReferenceArgs(args) {
|
|
88
|
+
const refs = this.__symbolTable.references;
|
|
89
|
+
if (refs.length === 0) return;
|
|
90
|
+
refs[refs.length - 1].args = args;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Attach field_name to the most recent reference (for column validation).
|
|
95
|
+
* @param {string} fieldName - The field name value
|
|
96
|
+
* @param {number} startOffset
|
|
97
|
+
* @param {number} endOffset
|
|
98
|
+
*/
|
|
99
|
+
setReferenceFieldName(fieldName, startOffset, endOffset) {
|
|
100
|
+
const refs = this.__symbolTable.references;
|
|
101
|
+
if (refs.length === 0) return;
|
|
102
|
+
refs[refs.length - 1].fieldName = fieldName;
|
|
103
|
+
refs[refs.length - 1].fieldNameStartOffset = startOffset;
|
|
104
|
+
refs[refs.length - 1].fieldNameEndOffset = endOffset;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Attach data keys to the most recent reference (for column validation).
|
|
109
|
+
* @param {Array<{name: string, startOffset: number, endOffset: number}>} dataKeys
|
|
110
|
+
*/
|
|
111
|
+
setReferenceDataKeys(dataKeys) {
|
|
112
|
+
const refs = this.__symbolTable.references;
|
|
113
|
+
if (refs.length === 0) return;
|
|
114
|
+
refs[refs.length - 1].dataKeys = dataKeys;
|
|
115
|
+
}
|
|
116
|
+
|
|
61
117
|
/**
|
|
62
118
|
* Add a warning message to the parser
|
|
63
119
|
* @param {*} message
|
|
@@ -244,6 +300,8 @@ export class XanoBaseParser extends CstParser {
|
|
|
244
300
|
var: {},
|
|
245
301
|
auth: {},
|
|
246
302
|
env: {},
|
|
303
|
+
references: [],
|
|
304
|
+
varDeclarations: [],
|
|
247
305
|
};
|
|
248
306
|
}
|
|
249
307
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EqualToken } from "../../lexer/control.js";
|
|
2
2
|
import { MiddlewareToken } from "../../lexer/tokens.js";
|
|
3
|
+
import { getVarName } from "../generic/utils.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* response = $entries
|
|
@@ -12,6 +13,7 @@ export function middlewareClause($) {
|
|
|
12
13
|
|
|
13
14
|
const parent = $.CONSUME(MiddlewareToken); // "middleware"
|
|
14
15
|
$.CONSUME(EqualToken); // "="
|
|
16
|
+
const captured = {};
|
|
15
17
|
$.SUBRULE($.schemaParseObjectFn, {
|
|
16
18
|
ARGS: [
|
|
17
19
|
parent,
|
|
@@ -31,9 +33,23 @@ export function middlewareClause($) {
|
|
|
31
33
|
},
|
|
32
34
|
],
|
|
33
35
|
},
|
|
36
|
+
captured,
|
|
34
37
|
],
|
|
35
38
|
});
|
|
36
39
|
|
|
40
|
+
$.ACTION(() => {
|
|
41
|
+
for (const phase of ["pre", "post"]) {
|
|
42
|
+
const items = captured[phase]?.value;
|
|
43
|
+
if (!Array.isArray(items)) continue;
|
|
44
|
+
for (const item of items) {
|
|
45
|
+
const nameEntry = item.name;
|
|
46
|
+
if (!nameEntry?.value) continue;
|
|
47
|
+
const token = nameEntry.value;
|
|
48
|
+
$.addReference("middleware", getVarName(token), token);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
37
53
|
$.sectionStack.pop();
|
|
38
54
|
};
|
|
39
55
|
}
|
|
@@ -113,6 +113,11 @@ export function columnDefinition($) {
|
|
|
113
113
|
],
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
+
// Track table reference from FK column definition
|
|
117
|
+
if (captured.table?.value) {
|
|
118
|
+
$.addReference("table", getVarName(captured.table.value), captured.table.value);
|
|
119
|
+
}
|
|
120
|
+
|
|
116
121
|
if (inputTypeToken.image == "vector" && !captured.size) {
|
|
117
122
|
$.addWarning(
|
|
118
123
|
'Column named "vector" should have a size attribute defining its length.',
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
VerbToken,
|
|
11
11
|
} from "../../../lexer/query.js";
|
|
12
12
|
import { Identifier } from "../../../lexer/tokens.js";
|
|
13
|
+
import { getVarName } from "../../generic/utils.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* @param {import('../../base_parser.js').XanoBaseParser} $
|
|
@@ -36,10 +37,11 @@ export function apiCallFn($) {
|
|
|
36
37
|
);
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
$.OR([
|
|
40
|
-
{ ALT: () => $.CONSUME(StringLiteral) },
|
|
41
|
-
{ ALT: () => $.CONSUME(Identifier) },
|
|
40
|
+
const nameToken = $.OR([
|
|
41
|
+
{ ALT: () => $.CONSUME(StringLiteral) },
|
|
42
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
42
43
|
]);
|
|
44
|
+
$.addReference("query", getVarName(nameToken), nameToken);
|
|
43
45
|
$.CONSUME(VerbToken);
|
|
44
46
|
$.CONSUME(EqualToken);
|
|
45
47
|
$.OR1([
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CallToken, FunctionToken } from "../../../lexer/function.js";
|
|
2
2
|
import { StringLiteral } from "../../../lexer/literal.js";
|
|
3
3
|
import { DotToken, Identifier } from "../../../lexer/tokens.js";
|
|
4
|
+
import { getVarName } from "../../generic/utils.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
*
|
|
@@ -17,10 +18,11 @@ export function functionCallFn($) {
|
|
|
17
18
|
$.CONSUME(DotToken); // "."
|
|
18
19
|
const fnToken = $.CONSUME(CallToken); // "call"
|
|
19
20
|
|
|
20
|
-
$.OR([
|
|
21
|
-
{ ALT: () => $.CONSUME(Identifier) },
|
|
22
|
-
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
21
|
+
const nameToken = $.OR([
|
|
22
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
23
|
+
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
23
24
|
]);
|
|
25
|
+
$.addReference("function", getVarName(nameToken), nameToken);
|
|
24
26
|
|
|
25
27
|
$.SUBRULE($.functionAttrReq, {
|
|
26
28
|
ARGS: [
|