@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,420 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { xanoscriptParser } from "../parser/parser.js";
|
|
4
|
+
|
|
5
|
+
describe("reference tracking in __symbolTable", () => {
|
|
6
|
+
it("should track function.run reference", () => {
|
|
7
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
8
|
+
stack {
|
|
9
|
+
function.run "my_target" as $result
|
|
10
|
+
}
|
|
11
|
+
}`);
|
|
12
|
+
const refs = parser.__symbolTable.references;
|
|
13
|
+
expect(refs).to.have.length(1);
|
|
14
|
+
expect(refs[0].refType).to.equal("function");
|
|
15
|
+
expect(refs[0].name).to.equal("my_target");
|
|
16
|
+
expect(refs[0].startOffset).to.be.a("number");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should track function.call reference", () => {
|
|
20
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
21
|
+
stack {
|
|
22
|
+
function.call "my_target" as $result
|
|
23
|
+
}
|
|
24
|
+
}`);
|
|
25
|
+
const refs = parser.__symbolTable.references;
|
|
26
|
+
expect(refs).to.have.length(1);
|
|
27
|
+
expect(refs[0].refType).to.equal("function");
|
|
28
|
+
expect(refs[0].name).to.equal("my_target");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should track db.get table reference", () => {
|
|
32
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
33
|
+
stack {
|
|
34
|
+
db.get users {
|
|
35
|
+
field_name = id
|
|
36
|
+
field_value = 1
|
|
37
|
+
} as $user
|
|
38
|
+
}
|
|
39
|
+
}`);
|
|
40
|
+
const refs = parser.__symbolTable.references;
|
|
41
|
+
expect(refs).to.have.length(1);
|
|
42
|
+
expect(refs[0].refType).to.equal("table");
|
|
43
|
+
expect(refs[0].name).to.equal("users");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should track db.query table reference", () => {
|
|
47
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
48
|
+
stack {
|
|
49
|
+
db.query users {
|
|
50
|
+
return {
|
|
51
|
+
type = list
|
|
52
|
+
}
|
|
53
|
+
} as $users
|
|
54
|
+
}
|
|
55
|
+
}`);
|
|
56
|
+
const refs = parser.__symbolTable.references;
|
|
57
|
+
expect(refs).to.have.length(1);
|
|
58
|
+
expect(refs[0].refType).to.equal("table");
|
|
59
|
+
expect(refs[0].name).to.equal("users");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should track multiple references in one file", () => {
|
|
63
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
64
|
+
stack {
|
|
65
|
+
db.get users {
|
|
66
|
+
field_name = "user_id"
|
|
67
|
+
field_value = 1
|
|
68
|
+
} as $user
|
|
69
|
+
function.run "helper" as $result
|
|
70
|
+
}
|
|
71
|
+
}`);
|
|
72
|
+
const refs = parser.__symbolTable.references;
|
|
73
|
+
expect(refs).to.have.length(2);
|
|
74
|
+
expect(refs[0].refType).to.equal("table");
|
|
75
|
+
expect(refs[0].name).to.equal("users");
|
|
76
|
+
expect(refs[1].refType).to.equal("function");
|
|
77
|
+
expect(refs[1].name).to.equal("helper");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should track task.call reference", () => {
|
|
81
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
82
|
+
stack {
|
|
83
|
+
task.call "my_task" as $result
|
|
84
|
+
}
|
|
85
|
+
}`);
|
|
86
|
+
const refs = parser.__symbolTable.references;
|
|
87
|
+
expect(refs).to.have.length(1);
|
|
88
|
+
expect(refs[0].refType).to.equal("task");
|
|
89
|
+
expect(refs[0].name).to.equal("my_task");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should track db.bulk.delete table reference", () => {
|
|
93
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
94
|
+
stack {
|
|
95
|
+
db.bulk.delete users {
|
|
96
|
+
field_name = id
|
|
97
|
+
field_value = [1, 2, 3]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}`);
|
|
101
|
+
const refs = parser.__symbolTable.references;
|
|
102
|
+
expect(refs).to.have.length(1);
|
|
103
|
+
expect(refs[0].refType).to.equal("table");
|
|
104
|
+
expect(refs[0].name).to.equal("users");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should track string literal table names", () => {
|
|
108
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
109
|
+
stack {
|
|
110
|
+
db.get "user table" {
|
|
111
|
+
field_name = id
|
|
112
|
+
field_value = 1
|
|
113
|
+
} as $user
|
|
114
|
+
}
|
|
115
|
+
}`);
|
|
116
|
+
const refs = parser.__symbolTable.references;
|
|
117
|
+
expect(refs).to.have.length(1);
|
|
118
|
+
expect(refs[0].name).to.equal("user table");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("should capture input args on function.run reference", () => {
|
|
122
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
123
|
+
stack {
|
|
124
|
+
function.run "my_target" {
|
|
125
|
+
input = { user_id: 1, name: "test" }
|
|
126
|
+
} as $result
|
|
127
|
+
}
|
|
128
|
+
}`);
|
|
129
|
+
const refs = parser.__symbolTable.references;
|
|
130
|
+
expect(refs).to.have.length(1);
|
|
131
|
+
expect(refs[0].refType).to.equal("function");
|
|
132
|
+
expect(refs[0].args).to.be.an("object");
|
|
133
|
+
expect(refs[0].args).to.have.property("user_id");
|
|
134
|
+
expect(refs[0].args).to.have.property("name");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("should capture literal types on input args", () => {
|
|
138
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
139
|
+
stack {
|
|
140
|
+
function.run "my_target" {
|
|
141
|
+
input = { count: 100, name: "test", rate: 3.14, active: true }
|
|
142
|
+
} as $result
|
|
143
|
+
}
|
|
144
|
+
}`);
|
|
145
|
+
const refs = parser.__symbolTable.references;
|
|
146
|
+
expect(refs).to.have.length(1);
|
|
147
|
+
const args = refs[0].args;
|
|
148
|
+
expect(args.count.type).to.equal("int");
|
|
149
|
+
expect(args.name.type).to.equal("text");
|
|
150
|
+
expect(args.rate.type).to.equal("decimal");
|
|
151
|
+
expect(args.active.type).to.equal("bool");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should capture input args with variable values as unknown type", () => {
|
|
155
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
156
|
+
stack {
|
|
157
|
+
function.run "my_target" {
|
|
158
|
+
input = { user_id: $var1 }
|
|
159
|
+
} as $result
|
|
160
|
+
}
|
|
161
|
+
}`);
|
|
162
|
+
const refs = parser.__symbolTable.references;
|
|
163
|
+
expect(refs).to.have.length(1);
|
|
164
|
+
expect(refs[0].args).to.have.property("user_id");
|
|
165
|
+
expect(refs[0].args.user_id.type).to.be.null;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should not have args when function.run has no input", () => {
|
|
169
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
170
|
+
stack {
|
|
171
|
+
function.run "my_target" as $result
|
|
172
|
+
}
|
|
173
|
+
}`);
|
|
174
|
+
const refs = parser.__symbolTable.references;
|
|
175
|
+
expect(refs).to.have.length(1);
|
|
176
|
+
expect(refs[0].args).to.be.undefined;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should not have args when function.run has non-input attributes only", () => {
|
|
180
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
181
|
+
stack {
|
|
182
|
+
function.run "my_target" {
|
|
183
|
+
description = "testing"
|
|
184
|
+
} as $result
|
|
185
|
+
}
|
|
186
|
+
}`);
|
|
187
|
+
const refs = parser.__symbolTable.references;
|
|
188
|
+
expect(refs).to.have.length(1);
|
|
189
|
+
expect(refs[0].args).to.be.undefined;
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should capture field_name on db.get reference with string literal", () => {
|
|
193
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
194
|
+
stack {
|
|
195
|
+
db.get users {
|
|
196
|
+
field_name = "id"
|
|
197
|
+
field_value = 1
|
|
198
|
+
} as $user
|
|
199
|
+
}
|
|
200
|
+
}`);
|
|
201
|
+
const refs = parser.__symbolTable.references;
|
|
202
|
+
expect(refs).to.have.length(1);
|
|
203
|
+
expect(refs[0].refType).to.equal("table");
|
|
204
|
+
expect(refs[0].fieldName).to.equal("id");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("should capture field_name on db.edit reference with string literal", () => {
|
|
208
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
209
|
+
stack {
|
|
210
|
+
db.edit users {
|
|
211
|
+
field_name = "id"
|
|
212
|
+
field_value = 1
|
|
213
|
+
data = { name: "test" }
|
|
214
|
+
} as $user
|
|
215
|
+
}
|
|
216
|
+
}`);
|
|
217
|
+
const refs = parser.__symbolTable.references;
|
|
218
|
+
expect(refs).to.have.length(1);
|
|
219
|
+
expect(refs[0].fieldName).to.equal("id");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("should not capture field_name when value is a variable expression", () => {
|
|
223
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
224
|
+
stack {
|
|
225
|
+
db.get users {
|
|
226
|
+
field_name = $input.field
|
|
227
|
+
field_value = 1
|
|
228
|
+
} as $user
|
|
229
|
+
}
|
|
230
|
+
}`);
|
|
231
|
+
const refs = parser.__symbolTable.references;
|
|
232
|
+
expect(refs).to.have.length(1);
|
|
233
|
+
expect(refs[0].fieldName).to.be.undefined;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("should not have field_name when db function has no field_name attribute", () => {
|
|
237
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
238
|
+
stack {
|
|
239
|
+
db.query users {
|
|
240
|
+
return {
|
|
241
|
+
type = list
|
|
242
|
+
}
|
|
243
|
+
} as $users
|
|
244
|
+
}
|
|
245
|
+
}`);
|
|
246
|
+
const refs = parser.__symbolTable.references;
|
|
247
|
+
expect(refs).to.have.length(1);
|
|
248
|
+
expect(refs[0].fieldName).to.be.undefined;
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("should track middleware references from query middleware clause", () => {
|
|
252
|
+
const parser = xanoscriptParser(`query foo verb=GET {
|
|
253
|
+
input {
|
|
254
|
+
text user_id filters=trim
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
stack {
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
response = null
|
|
261
|
+
|
|
262
|
+
middleware = {pre: [{name: "auth_middle"}], post: [{name: "log_middle"}]}
|
|
263
|
+
}`);
|
|
264
|
+
const refs = parser.__symbolTable.references;
|
|
265
|
+
const mwRefs = refs.filter((r) => r.refType === "middleware");
|
|
266
|
+
expect(mwRefs).to.have.length(2);
|
|
267
|
+
expect(mwRefs[0].name).to.equal("auth_middle");
|
|
268
|
+
expect(mwRefs[1].name).to.equal("log_middle");
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("should track middleware.call reference", () => {
|
|
272
|
+
const parser = xanoscriptParser(`workflow_test test_middleware {
|
|
273
|
+
stack {
|
|
274
|
+
middleware.call "auth_check" {
|
|
275
|
+
input = {
|
|
276
|
+
vars: $input,
|
|
277
|
+
type: "pre"
|
|
278
|
+
}
|
|
279
|
+
} as $result
|
|
280
|
+
}
|
|
281
|
+
}`);
|
|
282
|
+
const refs = parser.__symbolTable.references;
|
|
283
|
+
const mwRefs = refs.filter((r) => r.refType === "middleware");
|
|
284
|
+
expect(mwRefs).to.have.length(1);
|
|
285
|
+
expect(mwRefs[0].name).to.equal("auth_check");
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("should track data keys on db.edit reference", () => {
|
|
289
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
290
|
+
stack {
|
|
291
|
+
db.edit users {
|
|
292
|
+
field_name = "id"
|
|
293
|
+
field_value = 1
|
|
294
|
+
data = { name: "test", email: "foo@bar.com" }
|
|
295
|
+
} as $user
|
|
296
|
+
}
|
|
297
|
+
}`);
|
|
298
|
+
const refs = parser.__symbolTable.references;
|
|
299
|
+
expect(refs).to.have.length(1);
|
|
300
|
+
expect(refs[0].refType).to.equal("table");
|
|
301
|
+
expect(refs[0].dataKeys).to.be.an("array");
|
|
302
|
+
const keyNames = refs[0].dataKeys.map((k) => k.name);
|
|
303
|
+
expect(keyNames).to.include("name");
|
|
304
|
+
expect(keyNames).to.include("email");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("should not have dataKeys when db function has no data attribute", () => {
|
|
308
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
309
|
+
stack {
|
|
310
|
+
db.get users {
|
|
311
|
+
field_name = "id"
|
|
312
|
+
field_value = 1
|
|
313
|
+
} as $user
|
|
314
|
+
}
|
|
315
|
+
}`);
|
|
316
|
+
const refs = parser.__symbolTable.references;
|
|
317
|
+
expect(refs).to.have.length(1);
|
|
318
|
+
expect(refs[0].dataKeys).to.be.undefined;
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("should track join table references in db.query", () => {
|
|
322
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
323
|
+
stack {
|
|
324
|
+
db.query users {
|
|
325
|
+
join = {
|
|
326
|
+
profile: {
|
|
327
|
+
type: "left"
|
|
328
|
+
table: "user_profiles"
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return = {type: "list"}
|
|
332
|
+
} as $users
|
|
333
|
+
}
|
|
334
|
+
}`);
|
|
335
|
+
const refs = parser.__symbolTable.references;
|
|
336
|
+
const tableRefs = refs.filter((r) => r.refType === "table");
|
|
337
|
+
expect(tableRefs).to.have.length(2);
|
|
338
|
+
expect(tableRefs[0].name).to.equal("users");
|
|
339
|
+
expect(tableRefs[1].name).to.equal("user_profiles");
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("should track addon name references in db.query", () => {
|
|
343
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
344
|
+
stack {
|
|
345
|
+
db.query users {
|
|
346
|
+
addon = [{name: "get_posts", as: "_posts"}]
|
|
347
|
+
return = {type: "list"}
|
|
348
|
+
} as $users
|
|
349
|
+
}
|
|
350
|
+
}`);
|
|
351
|
+
const refs = parser.__symbolTable.references;
|
|
352
|
+
const fnRefs = refs.filter((r) => r.refType === "function");
|
|
353
|
+
expect(fnRefs).to.have.length(1);
|
|
354
|
+
expect(fnRefs[0].name).to.equal("get_posts");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("should track table relation references from column definitions", () => {
|
|
358
|
+
const parser = xanoscriptParser(`table beer {
|
|
359
|
+
schema {
|
|
360
|
+
int brewery_id {
|
|
361
|
+
table = "brewery"
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}`);
|
|
365
|
+
const refs = parser.__symbolTable.references;
|
|
366
|
+
const tableRefs = refs.filter((r) => r.refType === "table");
|
|
367
|
+
expect(tableRefs).to.have.length(1);
|
|
368
|
+
expect(tableRefs[0].name).to.equal("brewery");
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("should track function.run with empty name and not leak args to previous reference", () => {
|
|
372
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
373
|
+
stack {
|
|
374
|
+
db.edit claim {
|
|
375
|
+
field_name = "id"
|
|
376
|
+
field_value = 1
|
|
377
|
+
data = {status: "active"}
|
|
378
|
+
} as $updated_claim
|
|
379
|
+
|
|
380
|
+
function.run "" {
|
|
381
|
+
input = {
|
|
382
|
+
claim_id: 1
|
|
383
|
+
to_status: "assessment"
|
|
384
|
+
}
|
|
385
|
+
} as $transition_result
|
|
386
|
+
}
|
|
387
|
+
}`);
|
|
388
|
+
const refs = parser.__symbolTable.references;
|
|
389
|
+
const tableRefs = refs.filter((r) => r.refType === "table");
|
|
390
|
+
const fnRefs = refs.filter((r) => r.refType === "function");
|
|
391
|
+
|
|
392
|
+
// The table ref should NOT have args from the function.run
|
|
393
|
+
expect(tableRefs).to.have.length(1);
|
|
394
|
+
expect(tableRefs[0].name).to.equal("claim");
|
|
395
|
+
expect(tableRefs[0].args).to.be.undefined;
|
|
396
|
+
|
|
397
|
+
// The function ref should exist (even with empty name) and carry the args
|
|
398
|
+
expect(fnRefs).to.have.length(1);
|
|
399
|
+
expect(fnRefs[0].name).to.equal("");
|
|
400
|
+
expect(fnRefs[0].args).to.have.property("claim_id");
|
|
401
|
+
expect(fnRefs[0].args).to.have.property("to_status");
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it("should track security.create_auth_token table reference", () => {
|
|
405
|
+
const parser = xanoscriptParser(`function "caller" {
|
|
406
|
+
stack {
|
|
407
|
+
security.create_auth_token {
|
|
408
|
+
table = "users"
|
|
409
|
+
extras = {}
|
|
410
|
+
expiration = 86400
|
|
411
|
+
id = ""
|
|
412
|
+
} as $token
|
|
413
|
+
}
|
|
414
|
+
}`);
|
|
415
|
+
const refs = parser.__symbolTable.references;
|
|
416
|
+
const tableRefs = refs.filter((r) => r.refType === "table");
|
|
417
|
+
expect(tableRefs).to.have.length(1);
|
|
418
|
+
expect(tableRefs[0].name).to.equal("users");
|
|
419
|
+
});
|
|
420
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { getObjectInfoFromContent } from "../utils.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} IndexEntry
|
|
5
|
+
* @property {string} uri - Document URI
|
|
6
|
+
* @property {string} type - Object type (function, table, query, task, etc.)
|
|
7
|
+
* @property {string} name - Object name
|
|
8
|
+
* @property {Object} [inputs] - Input parameters/columns from __symbolTable.input
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* In-memory index of all XanoScript objects in the workspace.
|
|
13
|
+
* One entry per .xs file, keyed by (type, name).
|
|
14
|
+
*/
|
|
15
|
+
export class WorkspaceIndex {
|
|
16
|
+
constructor() {
|
|
17
|
+
/** @type {Map<string, Map<string, IndexEntry>>} type -> name -> entry */
|
|
18
|
+
this.index = new Map();
|
|
19
|
+
/** @type {Map<string, IndexEntry>} uri -> entry (reverse lookup) */
|
|
20
|
+
this.byUri = new Map();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @private
|
|
25
|
+
* Insert an entry into the index maps.
|
|
26
|
+
*/
|
|
27
|
+
_setEntry(entry) {
|
|
28
|
+
const { type, name, uri } = entry;
|
|
29
|
+
if (!this.index.has(type)) {
|
|
30
|
+
this.index.set(type, new Map());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const existing = this.index.get(type).get(name);
|
|
34
|
+
if (existing && existing.uri !== uri) {
|
|
35
|
+
this.byUri.delete(existing.uri);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.index.get(type).set(name, entry);
|
|
39
|
+
this.byUri.set(uri, entry);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Lightweight index: extract type+name via regex only (no parsing).
|
|
44
|
+
* Used for initial workspace scan to avoid heavy memory usage.
|
|
45
|
+
* Inputs will be populated later when the file is opened or parsed.
|
|
46
|
+
* @param {string} uri - Document URI
|
|
47
|
+
* @param {string} content - File content
|
|
48
|
+
* @returns {boolean} true if successfully indexed
|
|
49
|
+
*/
|
|
50
|
+
addFile(uri, content) {
|
|
51
|
+
this.removeFile(uri);
|
|
52
|
+
|
|
53
|
+
const info = getObjectInfoFromContent(content);
|
|
54
|
+
if (!info) return false;
|
|
55
|
+
|
|
56
|
+
const entry = { uri, type: info.type, name: info.name, inputs: {} };
|
|
57
|
+
this._setEntry(entry);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Add or update a file in the index using pre-parsed data.
|
|
63
|
+
* Avoids double-parsing when the caller already has the parse result.
|
|
64
|
+
* @param {string} uri - Document URI
|
|
65
|
+
* @param {string} content - File content (for name extraction)
|
|
66
|
+
* @param {Object} symbolTable - Parser's __symbolTable
|
|
67
|
+
* @returns {boolean} true if successfully indexed
|
|
68
|
+
*/
|
|
69
|
+
addParsed(uri, content, symbolTable) {
|
|
70
|
+
this.removeFile(uri);
|
|
71
|
+
|
|
72
|
+
const info = getObjectInfoFromContent(content);
|
|
73
|
+
if (!info) return false;
|
|
74
|
+
|
|
75
|
+
const inputs = symbolTable?.input ? { ...symbolTable.input } : {};
|
|
76
|
+
const entry = { uri, type: info.type, name: info.name, inputs };
|
|
77
|
+
this._setEntry(entry);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Remove a file from the index.
|
|
83
|
+
* @param {string} uri - Document URI
|
|
84
|
+
*/
|
|
85
|
+
removeFile(uri) {
|
|
86
|
+
const existing = this.byUri.get(uri);
|
|
87
|
+
if (existing) {
|
|
88
|
+
const typeMap = this.index.get(existing.type);
|
|
89
|
+
if (typeMap) {
|
|
90
|
+
typeMap.delete(existing.name);
|
|
91
|
+
if (typeMap.size === 0) {
|
|
92
|
+
this.index.delete(existing.type);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
this.byUri.delete(uri);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get an index entry by type and name.
|
|
101
|
+
* @param {string} type
|
|
102
|
+
* @param {string} name
|
|
103
|
+
* @returns {IndexEntry | undefined}
|
|
104
|
+
*/
|
|
105
|
+
get(type, name) {
|
|
106
|
+
return this.index.get(type)?.get(name);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check if an entry exists.
|
|
111
|
+
* @param {string} type
|
|
112
|
+
* @param {string} name
|
|
113
|
+
* @returns {boolean}
|
|
114
|
+
*/
|
|
115
|
+
has(type, name) {
|
|
116
|
+
return this.index.get(type)?.has(name) ?? false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get all entries for a given type.
|
|
121
|
+
* @param {string} type
|
|
122
|
+
* @returns {IndexEntry[]}
|
|
123
|
+
*/
|
|
124
|
+
getByType(type) {
|
|
125
|
+
const typeMap = this.index.get(type);
|
|
126
|
+
return typeMap ? [...typeMap.values()] : [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get all names for a given type.
|
|
131
|
+
* @param {string} type
|
|
132
|
+
* @returns {string[]}
|
|
133
|
+
*/
|
|
134
|
+
getAllNames(type) {
|
|
135
|
+
const typeMap = this.index.get(type);
|
|
136
|
+
return typeMap ? [...typeMap.keys()] : [];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get entry by URI (reverse lookup).
|
|
141
|
+
* @param {string} uri
|
|
142
|
+
* @returns {IndexEntry | undefined}
|
|
143
|
+
*/
|
|
144
|
+
getByUri(uri) {
|
|
145
|
+
return this.byUri.get(uri);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const workspaceIndex = new WorkspaceIndex();
|