xq-lsp 1.0.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.
@@ -0,0 +1,86 @@
1
+ module namespace map="http://www.w3.org/2005/xpath-functions/map";
2
+
3
+ (:~
4
+ : Returns a map that combines the entries of a sequence of maps.
5
+ : @param $maps The maps to merge
6
+ : @return A map containing all entries
7
+ :)
8
+ declare function map:merge($maps as map(*)*) as map(*) external;
9
+ (:~
10
+ : Merges maps with options controlling duplicate key handling.
11
+ : @param $maps The maps to merge
12
+ : @param $options Options map (e.g. map{"duplicates": "use-first"})
13
+ : @return A map containing all entries
14
+ :)
15
+ declare function map:merge($maps as map(*)*, $options as map(*)) as map(*) external;
16
+
17
+ (:~
18
+ : Returns the number of entries in a map.
19
+ : @param $map The map
20
+ : @return The number of key-value pairs
21
+ :)
22
+ declare function map:size($map as map(*)) as xs:integer external;
23
+
24
+ (:~
25
+ : Returns the keys of a map as a sequence of atomic values.
26
+ : @param $map The map
27
+ : @return Sequence of keys
28
+ :)
29
+ declare function map:keys($map as map(*)) as xs:anyAtomicType* external;
30
+
31
+ (:~
32
+ : Tests whether a map contains a given key.
33
+ : @param $map The map
34
+ : @param $key The key to look for
35
+ : @return true if the map contains the key
36
+ :)
37
+ declare function map:contains($map as map(*), $key as xs:anyAtomicType) as xs:boolean external;
38
+
39
+ (:~
40
+ : Returns the value associated with a key in a map.
41
+ : @param $map The map
42
+ : @param $key The key
43
+ : @return The associated value, or empty sequence if the key is absent
44
+ :)
45
+ declare function map:get($map as map(*), $key as xs:anyAtomicType) as item()* external;
46
+
47
+ (:~
48
+ : Searches a nested data structure for all values with a given key.
49
+ : @param $input The structure to search
50
+ : @param $key The key to find
51
+ : @return An array of all matching values
52
+ :)
53
+ declare function map:find($input as item()*, $key as xs:anyAtomicType) as array(*) external;
54
+
55
+ (:~
56
+ : Returns a new map with an added or replaced entry.
57
+ : @param $map The source map
58
+ : @param $key The key
59
+ : @param $value The value
60
+ : @return A new map with the entry added or replaced
61
+ :)
62
+ declare function map:put($map as map(*), $key as xs:anyAtomicType, $value as item()*) as map(*) external;
63
+
64
+ (:~
65
+ : Creates a single-entry map.
66
+ : @param $key The key
67
+ : @param $value The value
68
+ : @return A map with one entry
69
+ :)
70
+ declare function map:entry($key as xs:anyAtomicType, $value as item()*) as map(*) external;
71
+
72
+ (:~
73
+ : Returns a new map with one or more entries removed.
74
+ : @param $map The source map
75
+ : @param $keys Keys to remove
76
+ : @return A new map without the specified entries
77
+ :)
78
+ declare function map:remove($map as map(*), $keys as xs:anyAtomicType*) as map(*) external;
79
+
80
+ (:~
81
+ : Applies a function to each entry in a map, returning a sequence of results.
82
+ : @param $map The map
83
+ : @param $action Function taking (key, value) and returning items
84
+ : @return Concatenated results
85
+ :)
86
+ declare function map:for-each($map as map(*), $action as function(xs:anyAtomicType, item()*) as item()*) as item()* external;
@@ -0,0 +1,100 @@
1
+ module namespace math="http://www.w3.org/2005/xpath-functions/math";
2
+
3
+ (:~
4
+ : Returns the mathematical constant π (approximately 3.14159265358979).
5
+ : @return π as xs:double
6
+ :)
7
+ declare function math:pi() as xs:double external;
8
+
9
+ (:~
10
+ : Returns e raised to the power of a number (eˣ).
11
+ : @param $x The exponent
12
+ : @return e^x
13
+ :)
14
+ declare function math:exp($x as xs:double?) as xs:double? external;
15
+
16
+ (:~
17
+ : Returns 10 raised to the power of a number.
18
+ : @param $x The exponent
19
+ : @return 10^x
20
+ :)
21
+ declare function math:exp10($x as xs:double?) as xs:double? external;
22
+
23
+ (:~
24
+ : Returns the natural logarithm of a number.
25
+ : @param $x The value (must be positive)
26
+ : @return ln(x)
27
+ :)
28
+ declare function math:log($x as xs:double?) as xs:double? external;
29
+
30
+ (:~
31
+ : Returns the base-10 logarithm of a number.
32
+ : @param $x The value (must be positive)
33
+ : @return log₁₀(x)
34
+ :)
35
+ declare function math:log10($x as xs:double?) as xs:double? external;
36
+
37
+ (:~
38
+ : Returns a number raised to the power of another.
39
+ : @param $x The base
40
+ : @param $y The exponent
41
+ : @return x^y
42
+ :)
43
+ declare function math:pow($x as xs:double?, $y as xs:numeric) as xs:double? external;
44
+
45
+ (:~
46
+ : Returns the square root of a number.
47
+ : @param $x The value (must be non-negative)
48
+ : @return √x
49
+ :)
50
+ declare function math:sqrt($x as xs:double?) as xs:double? external;
51
+
52
+ (:~
53
+ : Returns the sine of an angle given in radians.
54
+ : @param $x The angle in radians
55
+ : @return sin(x)
56
+ :)
57
+ declare function math:sin($x as xs:double?) as xs:double? external;
58
+
59
+ (:~
60
+ : Returns the cosine of an angle given in radians.
61
+ : @param $x The angle in radians
62
+ : @return cos(x)
63
+ :)
64
+ declare function math:cos($x as xs:double?) as xs:double? external;
65
+
66
+ (:~
67
+ : Returns the tangent of an angle given in radians.
68
+ : @param $x The angle in radians
69
+ : @return tan(x)
70
+ :)
71
+ declare function math:tan($x as xs:double?) as xs:double? external;
72
+
73
+ (:~
74
+ : Returns the arc sine of a value, in radians.
75
+ : @param $x The value (between -1 and 1)
76
+ : @return asin(x) in radians
77
+ :)
78
+ declare function math:asin($x as xs:double?) as xs:double? external;
79
+
80
+ (:~
81
+ : Returns the arc cosine of a value, in radians.
82
+ : @param $x The value (between -1 and 1)
83
+ : @return acos(x) in radians
84
+ :)
85
+ declare function math:acos($x as xs:double?) as xs:double? external;
86
+
87
+ (:~
88
+ : Returns the arc tangent of a value, in radians.
89
+ : @param $x The value
90
+ : @return atan(x) in radians
91
+ :)
92
+ declare function math:atan($x as xs:double?) as xs:double? external;
93
+
94
+ (:~
95
+ : Returns the angle in radians from the positive x-axis to the point (x, y).
96
+ : @param $y The y coordinate
97
+ : @param $x The x coordinate
98
+ : @return The angle in radians (between -π and π)
99
+ :)
100
+ declare function math:atan2($y as xs:double, $x as xs:double) as xs:double external;
@@ -0,0 +1,14 @@
1
+ import type { FileAnalysis } from "./types.ts";
2
+ export declare const XMLNS_FN = "http://www.w3.org/2005/xpath-functions";
3
+ export declare const XMLNS_LOCAL = "http://www.w3.org/2005/xquery-local-functions";
4
+ export declare const XMLNS_XS = "http://www.w3.org/2001/XMLSchema";
5
+ export declare const XMLNS_MATH = "http://www.w3.org/2005/xpath-functions/math";
6
+ export declare const XMLNS_MAP = "http://www.w3.org/2005/xpath-functions/map";
7
+ export declare const XMLNS_ARRAY = "http://www.w3.org/2005/xpath-functions/array";
8
+ /**
9
+ * Resolve a namespace prefix to its URI given the namespace context of a file.
10
+ * Unprefixed calls (prefix="") resolve to the file's default function namespace.
11
+ * Unknown prefixes fall back to a stable synthetic URI so within-file matches still work.
12
+ */
13
+ export declare function resolvePrefix(prefix: string, analysis: FileAnalysis): string;
14
+ export declare function analyze(text: string, sourceUri: string): FileAnalysis;
@@ -0,0 +1,363 @@
1
+ import { XQuery31Full } from "xq-parser";
2
+ // ── Well-known namespace URIs ────────────────────────────────────────────────
3
+ export const XMLNS_FN = "http://www.w3.org/2005/xpath-functions";
4
+ export const XMLNS_LOCAL = "http://www.w3.org/2005/xquery-local-functions";
5
+ export const XMLNS_XS = "http://www.w3.org/2001/XMLSchema";
6
+ export const XMLNS_MATH = "http://www.w3.org/2005/xpath-functions/math";
7
+ export const XMLNS_MAP = "http://www.w3.org/2005/xpath-functions/map";
8
+ export const XMLNS_ARRAY = "http://www.w3.org/2005/xpath-functions/array";
9
+ const BUILTIN_PREFIXES = {
10
+ fn: XMLNS_FN,
11
+ local: XMLNS_LOCAL,
12
+ xs: XMLNS_XS,
13
+ math: XMLNS_MATH,
14
+ map: XMLNS_MAP,
15
+ array: XMLNS_ARRAY,
16
+ };
17
+ /**
18
+ * Resolve a namespace prefix to its URI given the namespace context of a file.
19
+ * Unprefixed calls (prefix="") resolve to the file's default function namespace.
20
+ * Unknown prefixes fall back to a stable synthetic URI so within-file matches still work.
21
+ */
22
+ export function resolvePrefix(prefix, analysis) {
23
+ if (!prefix)
24
+ return analysis.defaultFunctionNamespace;
25
+ const imp = analysis.imports.find((i) => i.prefix === prefix);
26
+ if (imp)
27
+ return imp.namespaceUri;
28
+ if (prefix === analysis.modulePrefix && analysis.moduleNamespaceUri)
29
+ return analysis.moduleNamespaceUri;
30
+ return BUILTIN_PREFIXES[prefix] ?? `urn:xq-lsp:undeclared:${prefix}`;
31
+ }
32
+ // ── AST helpers ─────────────────────────────────────────────────────────────
33
+ function isTerminal(node) {
34
+ return node.isTerminal;
35
+ }
36
+ function asNonTerminal(node) {
37
+ return node;
38
+ }
39
+ function firstTerminalValue(node) {
40
+ if (isTerminal(node))
41
+ return node.value;
42
+ for (const child of asNonTerminal(node).children) {
43
+ const v = firstTerminalValue(child);
44
+ if (v !== null)
45
+ return v;
46
+ }
47
+ return null;
48
+ }
49
+ function findAll(node, type, out = []) {
50
+ if (node.type === type)
51
+ out.push(node);
52
+ if (!isTerminal(node)) {
53
+ for (const child of asNonTerminal(node).children) {
54
+ findAll(child, type, out);
55
+ }
56
+ }
57
+ return out;
58
+ }
59
+ function directChildOf(node, type) {
60
+ if (isTerminal(node))
61
+ return undefined;
62
+ return asNonTerminal(node).children.find((c) => c.type === type);
63
+ }
64
+ function directChildrenOf(node, type) {
65
+ if (isTerminal(node))
66
+ return [];
67
+ return asNonTerminal(node).children.filter((c) => c.type === type);
68
+ }
69
+ // ── Doc comment parsing ──────────────────────────────────────────────────────
70
+ function parseDocComment(raw) {
71
+ // Strip (: / (:~ opening and :) closing, then strip leading " : " per line
72
+ const inner = raw
73
+ .replace(/^\(:~?/, "")
74
+ .replace(/:\)$/, "")
75
+ .split("\n")
76
+ .map((l) => l.replace(/^\s*:?\s?/, ""))
77
+ .join("\n")
78
+ .trim();
79
+ const params = {};
80
+ let returns;
81
+ const descLines = [];
82
+ let inDescription = true;
83
+ for (const line of inner.split("\n")) {
84
+ const paramMatch = line.match(/^@param\s+\$?([\w:\-]+)\s*(.*)/);
85
+ const returnMatch = line.match(/^@returns?\s+(.*)/);
86
+ if (paramMatch) {
87
+ inDescription = false;
88
+ params[paramMatch[1]] = paramMatch[2].trim();
89
+ }
90
+ else if (returnMatch) {
91
+ inDescription = false;
92
+ returns = returnMatch[1].trim();
93
+ }
94
+ else if (inDescription) {
95
+ descLines.push(line);
96
+ }
97
+ }
98
+ return { description: descLines.join("\n").trim(), params, returns };
99
+ }
100
+ /** Find a doc comment immediately preceding `offset` in raw text (regex fallback path). */
101
+ function findPrecedingDocInText(text, offset) {
102
+ const before = text.slice(0, offset);
103
+ const match = before.match(/\(:~?[\s\S]*?:\)\s*$/);
104
+ if (!match)
105
+ return undefined;
106
+ return parseDocComment(match[0]);
107
+ }
108
+ /** Find the xqDoc comment immediately preceding `offset` (only whitespace between). */
109
+ function findPrecedingDoc(comments, text, offset) {
110
+ // Walk backwards through comments sorted by end position
111
+ const before = comments.filter((c) => c.end <= offset).sort((a, b) => b.end - a.end);
112
+ for (const c of before) {
113
+ const between = text.slice(c.end, offset);
114
+ if (!/^\s*$/.test(between))
115
+ break; // something other than whitespace — stop
116
+ if (c.value.startsWith("(:~") || c.value.startsWith("(:")) {
117
+ return parseDocComment(c.value);
118
+ }
119
+ }
120
+ return undefined;
121
+ }
122
+ // ── Namespace helpers ────────────────────────────────────────────────────────
123
+ function extractModuleNamespace(text) {
124
+ const m = text.match(/module\s+namespace\s+([\w\-]+)\s*=\s*["']([^"']*)["']/);
125
+ return m ? { prefix: m[1], uri: m[2] } : undefined;
126
+ }
127
+ function extractDefaultFunctionNamespace(text) {
128
+ const m = text.match(/declare\s+default\s+function\s+namespace\s+["']([^"']*)["']/);
129
+ return m ? m[1] : undefined;
130
+ }
131
+ function buildPrefixMap(moduleNs, imports) {
132
+ const map = new Map(Object.entries(BUILTIN_PREFIXES));
133
+ if (moduleNs)
134
+ map.set(moduleNs.prefix, moduleNs.uri);
135
+ for (const imp of imports)
136
+ map.set(imp.prefix, imp.namespaceUri);
137
+ return map;
138
+ }
139
+ function resolveNamespaceUri(prefix, prefixMap) {
140
+ return prefixMap.get(prefix) ?? `urn:xq-lsp:undeclared:${prefix}`;
141
+ }
142
+ // ── Extract from valid AST ───────────────────────────────────────────────────
143
+ function sequenceTypeText(text, node) {
144
+ if (node.start === undefined || node.end === null)
145
+ return undefined;
146
+ return text.slice(node.start, node.end ?? undefined).trim() || undefined;
147
+ }
148
+ function extractFunctions(ast, sourceUri, comments, text, prefixMap) {
149
+ const results = [];
150
+ // Use AnnotatedDecl (starts at 'declare') so the comment lookup clears the 'declare' keyword
151
+ for (const annotated of findAll(ast, "AnnotatedDecl")) {
152
+ const decl = directChildOf(annotated, "FunctionDecl");
153
+ if (!decl)
154
+ continue;
155
+ const eqname = directChildOf(decl, "EQName");
156
+ const name = eqname ? firstTerminalValue(eqname) : null;
157
+ if (!name)
158
+ continue;
159
+ const colonIdx = name.indexOf(":");
160
+ const prefix = colonIdx >= 0 ? name.slice(0, colonIdx) : "";
161
+ const localName = colonIdx >= 0 ? name.slice(colonIdx + 1) : name;
162
+ const doc = findPrecedingDoc(comments, text, annotated.start);
163
+ const params = [];
164
+ const paramList = directChildOf(decl, "ParamList");
165
+ if (paramList) {
166
+ for (const param of findAll(paramList, "Param")) {
167
+ const paramEqname = directChildOf(param, "EQName");
168
+ const paramName = paramEqname ? firstTerminalValue(paramEqname) : null;
169
+ if (!paramName)
170
+ continue;
171
+ const typeDecl = directChildOf(param, "TypeDeclaration");
172
+ const seqTypeNode = typeDecl && directChildOf(typeDecl, "SequenceType");
173
+ const paramType = seqTypeNode ? sequenceTypeText(text, seqTypeNode) : undefined;
174
+ params.push({
175
+ name: paramName,
176
+ type: paramType,
177
+ description: doc?.params[paramName],
178
+ });
179
+ }
180
+ }
181
+ // Return type: 'as' followed by SequenceType (direct child of FunctionDecl)
182
+ const seqType = directChildOf(decl, "SequenceType");
183
+ const returnType = seqType ? sequenceTypeText(text, seqType) : undefined;
184
+ results.push({
185
+ name,
186
+ prefix,
187
+ localName,
188
+ namespaceUri: resolveNamespaceUri(prefix, prefixMap),
189
+ arity: params.length,
190
+ params,
191
+ returnType,
192
+ doc,
193
+ sourceUri,
194
+ sourceOffset: annotated.start,
195
+ });
196
+ }
197
+ return results;
198
+ }
199
+ function extractModuleVariables(ast, sourceUri) {
200
+ const results = [];
201
+ for (const varDecl of findAll(ast, "VarDecl")) {
202
+ const varName = directChildOf(varDecl, "VarName");
203
+ const name = varName ? firstTerminalValue(varName) : null;
204
+ if (!name)
205
+ continue;
206
+ results.push({
207
+ name,
208
+ offset: varDecl.start,
209
+ isModuleLevel: true,
210
+ sourceUri,
211
+ });
212
+ }
213
+ return results;
214
+ }
215
+ function extractLocalBindings(ast, sourceUri) {
216
+ const results = [];
217
+ for (const binding of [...findAll(ast, "LetBinding"), ...findAll(ast, "ForBinding")]) {
218
+ const varName = directChildOf(binding, "VarName");
219
+ const name = varName ? firstTerminalValue(varName) : null;
220
+ if (!name)
221
+ continue;
222
+ results.push({
223
+ name,
224
+ offset: binding.start,
225
+ isModuleLevel: false,
226
+ sourceUri,
227
+ });
228
+ }
229
+ return results;
230
+ }
231
+ function extractImports(ast) {
232
+ const results = [];
233
+ for (const mi of findAll(ast, "ModuleImport")) {
234
+ const ncname = directChildOf(mi, "NCName");
235
+ const prefix = ncname ? firstTerminalValue(ncname) : null;
236
+ if (!prefix)
237
+ continue;
238
+ const uris = directChildrenOf(mi, "URILiteral");
239
+ if (uris.length < 2)
240
+ continue;
241
+ const namespaceUri = stripQuotes(firstTerminalValue(uris[0]) ?? "");
242
+ const atPath = stripQuotes(firstTerminalValue(uris[1]) ?? "");
243
+ results.push({ prefix, namespaceUri, atPath });
244
+ }
245
+ return results;
246
+ }
247
+ function stripQuotes(s) {
248
+ return s.replace(/^["']|["']$/g, "");
249
+ }
250
+ function analyzeAst(ast, comments, text, sourceUri) {
251
+ const imports = extractImports(ast);
252
+ const moduleNs = extractModuleNamespace(text);
253
+ const defaultFunctionNamespace = extractDefaultFunctionNamespace(text) ?? XMLNS_FN;
254
+ const prefixMap = buildPrefixMap(moduleNs, imports);
255
+ return {
256
+ functions: extractFunctions(ast, sourceUri, comments, text, prefixMap),
257
+ moduleVariables: extractModuleVariables(ast, sourceUri),
258
+ localBindings: extractLocalBindings(ast, sourceUri),
259
+ imports,
260
+ defaultFunctionNamespace,
261
+ moduleNamespaceUri: moduleNs?.uri,
262
+ modulePrefix: moduleNs?.prefix,
263
+ };
264
+ }
265
+ // ── Regex fallback for syntactically invalid / partial XQuery ────────────────
266
+ const RE_FUNC = /declare\s+(?:%[\w:\-]+(?:\([^)]*\))?\s+)*function\s+([\w:\-]+)\s*\(([^)]*)\)/g;
267
+ const RE_VAR_DECL = /declare\s+(?:%[\w:\-]+(?:\([^)]*\))?\s+)*variable\s+\$([\w:\-]+)/g;
268
+ const RE_LET = /\blet\s+\$([\w:\-]+)\s*:=/g;
269
+ const RE_FOR = /\bfor\s+\$([\w:\-]+)\s+in\b/g;
270
+ const RE_IMPORT = /import\s+module\s+namespace\s+([\w\-]+)\s*=\s*["']([^"']*)["']\s+at\s+["']([^"']*)["']/g;
271
+ function analyzeRegex(text, sourceUri) {
272
+ const moduleVariables = [];
273
+ const localBindings = [];
274
+ const moduleNs = extractModuleNamespace(text);
275
+ const defaultFunctionNamespace = extractDefaultFunctionNamespace(text) ?? XMLNS_FN;
276
+ let m;
277
+ // Collect imports first so we can build the prefix map before resolving functions.
278
+ const imports = [];
279
+ RE_IMPORT.lastIndex = 0;
280
+ while ((m = RE_IMPORT.exec(text)) !== null) {
281
+ imports.push({ prefix: m[1], namespaceUri: m[2], atPath: m[3] });
282
+ }
283
+ const prefixMap = buildPrefixMap(moduleNs, imports);
284
+ const functions = [];
285
+ RE_FUNC.lastIndex = 0;
286
+ while ((m = RE_FUNC.exec(text)) !== null) {
287
+ const name = m[1];
288
+ const colonIdx = name.indexOf(":");
289
+ const prefix = colonIdx >= 0 ? name.slice(0, colonIdx) : "";
290
+ const localName = colonIdx >= 0 ? name.slice(colonIdx + 1) : name;
291
+ const doc = findPrecedingDocInText(text, m.index);
292
+ const rawParams = m[2].trim();
293
+ const params = rawParams
294
+ ? rawParams.split(",").map((p) => {
295
+ const varMatch = p.match(/\$([\w:\-]+)/);
296
+ const typeMatch = p.match(/as\s+([\w:\-]+(?:\(.*?\))?)/);
297
+ const paramName = varMatch ? varMatch[1] : p.trim();
298
+ return {
299
+ name: paramName,
300
+ type: typeMatch ? typeMatch[1] : undefined,
301
+ description: doc?.params[paramName],
302
+ };
303
+ })
304
+ : [];
305
+ functions.push({
306
+ name,
307
+ prefix,
308
+ localName,
309
+ namespaceUri: resolveNamespaceUri(prefix, prefixMap),
310
+ arity: params.length,
311
+ params,
312
+ doc,
313
+ sourceUri,
314
+ sourceOffset: m.index,
315
+ });
316
+ }
317
+ RE_VAR_DECL.lastIndex = 0;
318
+ while ((m = RE_VAR_DECL.exec(text)) !== null) {
319
+ moduleVariables.push({
320
+ name: m[1],
321
+ offset: m.index,
322
+ isModuleLevel: true,
323
+ sourceUri,
324
+ });
325
+ }
326
+ RE_LET.lastIndex = 0;
327
+ while ((m = RE_LET.exec(text)) !== null) {
328
+ localBindings.push({
329
+ name: m[1],
330
+ offset: m.index,
331
+ isModuleLevel: false,
332
+ sourceUri,
333
+ });
334
+ }
335
+ RE_FOR.lastIndex = 0;
336
+ while ((m = RE_FOR.exec(text)) !== null) {
337
+ localBindings.push({
338
+ name: m[1],
339
+ offset: m.index,
340
+ isModuleLevel: false,
341
+ sourceUri,
342
+ });
343
+ }
344
+ return {
345
+ functions,
346
+ moduleVariables,
347
+ localBindings,
348
+ imports,
349
+ defaultFunctionNamespace,
350
+ moduleNamespaceUri: moduleNs?.uri,
351
+ modulePrefix: moduleNs?.prefix,
352
+ };
353
+ }
354
+ // Testing
355
+ export function analyze(text, sourceUri) {
356
+ try {
357
+ const { ast, comments } = XQuery31Full(text);
358
+ return analyzeAst(ast, comments, text, sourceUri);
359
+ }
360
+ catch {
361
+ return analyzeRegex(text, sourceUri);
362
+ }
363
+ }
@@ -0,0 +1,2 @@
1
+ import type { FileAnalysis } from "./types.ts";
2
+ export declare function getBuiltins(): FileAnalysis;
@@ -0,0 +1,28 @@
1
+ import { readFileSync } from "fs";
2
+ import { fileURLToPath } from "url";
3
+ import { join, dirname } from "path";
4
+ import { analyze, XMLNS_FN } from "./analyzer.js";
5
+ const baseDir = dirname(fileURLToPath(import.meta.url));
6
+ const BUILTIN_FILES = [
7
+ "builtins/builtins-fn.xq",
8
+ "builtins/builtins-math.xq",
9
+ "builtins/builtins-map.xq",
10
+ "builtins/builtins-array.xq",
11
+ ];
12
+ let _builtins = null;
13
+ export function getBuiltins() {
14
+ if (!_builtins) {
15
+ const analyses = BUILTIN_FILES.map((f) => {
16
+ const text = readFileSync(join(baseDir, "..", f), "utf-8");
17
+ return analyze(text, `builtin:${f}`);
18
+ });
19
+ _builtins = {
20
+ functions: analyses.flatMap((a) => a.functions),
21
+ moduleVariables: [],
22
+ localBindings: [],
23
+ imports: [],
24
+ defaultFunctionNamespace: XMLNS_FN,
25
+ };
26
+ }
27
+ return _builtins;
28
+ }
@@ -0,0 +1,9 @@
1
+ import { CompletionItem } from "vscode-languageserver/node.js";
2
+ import type { FileAnalysis } from "./types.ts";
3
+ export interface CompletionContext {
4
+ /** Text from start of document up to (and including) the cursor position */
5
+ textBeforeCursor: string;
6
+ /** Character offset of the cursor in the document */
7
+ cursorOffset: number;
8
+ }
9
+ export declare function getCompletions(ctx: CompletionContext, currentAnalysis: FileAnalysis, importedAnalyses: Map<string, FileAnalysis>, snippets?: boolean): CompletionItem[];