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.
- package/README.md +84 -0
- package/builtins/builtins-array.xq +160 -0
- package/builtins/builtins-fn.xq +1100 -0
- package/builtins/builtins-map.xq +86 -0
- package/builtins/builtins-math.xq +100 -0
- package/dist/analyzer.d.ts +14 -0
- package/dist/analyzer.js +363 -0
- package/dist/builtins.d.ts +2 -0
- package/dist/builtins.js +28 -0
- package/dist/completion.d.ts +9 -0
- package/dist/completion.js +152 -0
- package/dist/features.d.ts +8 -0
- package/dist/features.js +214 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +145 -0
- package/dist/types.d.ts +42 -0
- package/dist/types.js +0 -0
- package/package.json +29 -0
- package/src/analyzer.ts +402 -0
- package/src/builtins.ts +33 -0
- package/src/completion.ts +179 -0
- package/src/features.ts +260 -0
- package/src/server.ts +165 -0
- package/src/types.ts +47 -0
- package/tsconfig.json +22 -0
package/src/features.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SignatureInformation,
|
|
3
|
+
ParameterInformation,
|
|
4
|
+
SymbolKind,
|
|
5
|
+
Location,
|
|
6
|
+
MarkupKind,
|
|
7
|
+
} from "vscode-languageserver/node.js";
|
|
8
|
+
import type { Hover, SignatureHelp, DocumentSymbol, Range, Position } from "vscode-languageserver/node.js";
|
|
9
|
+
import type { TextDocument } from "vscode-languageserver-textdocument";
|
|
10
|
+
import * as fs from "fs";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
import type { FileAnalysis, FunctionSymbol } from "./types.ts";
|
|
13
|
+
import { resolvePrefix } from "./analyzer.ts";
|
|
14
|
+
|
|
15
|
+
// ── Shared helpers ───────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve a qualified-name word (e.g. "fn:true", "m:sin", "true") to a FunctionSymbol
|
|
19
|
+
* using proper namespace URI resolution against the current file's namespace context.
|
|
20
|
+
*/
|
|
21
|
+
function resolveFunction(
|
|
22
|
+
word: string,
|
|
23
|
+
currentAnalysis: FileAnalysis,
|
|
24
|
+
fns: FunctionSymbol[],
|
|
25
|
+
): FunctionSymbol | undefined {
|
|
26
|
+
const colonIdx = word.indexOf(":");
|
|
27
|
+
const prefix = colonIdx >= 0 ? word.slice(0, colonIdx) : "";
|
|
28
|
+
const localName = colonIdx >= 0 ? word.slice(colonIdx + 1) : word;
|
|
29
|
+
const uri = resolvePrefix(prefix, currentAnalysis);
|
|
30
|
+
return fns.find((f) => f.namespaceUri === uri && f.localName === localName);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function wordAt(text: string, offset: number): { word: string; start: number; end: number } {
|
|
34
|
+
let start = offset;
|
|
35
|
+
let end = offset;
|
|
36
|
+
while (start > 0 && /[\w:\-]/.test(text[start - 1])) start--;
|
|
37
|
+
while (end < text.length && /[\w:\-]/.test(text[end])) end++;
|
|
38
|
+
return { word: text.slice(start, end), start, end };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function functionSignature(fn: FunctionSymbol): string {
|
|
42
|
+
const params = fn.params.map((p) => `$${p.name}${p.type ? " as " + p.type : ""}`).join(", ");
|
|
43
|
+
const ret = fn.returnType ? ` as ${fn.returnType}` : "";
|
|
44
|
+
return `declare function ${fn.name}(${params})${ret}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function allFunctions(current: FileAnalysis, imported: Map<string, FileAnalysis>): FunctionSymbol[] {
|
|
48
|
+
const fns = [...current.functions];
|
|
49
|
+
for (const a of imported.values()) fns.push(...a.functions);
|
|
50
|
+
return fns;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function positionInText(text: string, offset: number): Position {
|
|
54
|
+
const before = text.slice(0, offset);
|
|
55
|
+
const lines = before.split("\n");
|
|
56
|
+
return { line: lines.length - 1, character: lines[lines.length - 1].length };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function rangeFromOffset(doc: TextDocument, start: number, end: number): Range {
|
|
60
|
+
return { start: doc.positionAt(start), end: doc.positionAt(end) };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Hover ────────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
export function getHover(
|
|
66
|
+
doc: TextDocument,
|
|
67
|
+
offset: number,
|
|
68
|
+
current: FileAnalysis,
|
|
69
|
+
imported: Map<string, FileAnalysis>,
|
|
70
|
+
): Hover | null {
|
|
71
|
+
const text = doc.getText();
|
|
72
|
+
const { word, start, end } = wordAt(text, offset);
|
|
73
|
+
if (!word) return null;
|
|
74
|
+
|
|
75
|
+
// Check if hovering over a $ variable
|
|
76
|
+
const hasDollar = start > 0 && text[start - 1] === "$";
|
|
77
|
+
if (hasDollar) {
|
|
78
|
+
const varName = word;
|
|
79
|
+
const allVars = [
|
|
80
|
+
...current.moduleVariables,
|
|
81
|
+
...current.localBindings,
|
|
82
|
+
...[...imported.values()].flatMap((a) => a.moduleVariables),
|
|
83
|
+
];
|
|
84
|
+
const v = allVars.find((v) => v.name === varName);
|
|
85
|
+
if (v) {
|
|
86
|
+
return {
|
|
87
|
+
contents: { kind: MarkupKind.Markdown, value: `\`$${v.name}\`` },
|
|
88
|
+
range: rangeFromOffset(doc, start - 1, end),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check if hovering over a function name
|
|
95
|
+
const fn = resolveFunction(word, current, allFunctions(current, imported));
|
|
96
|
+
if (fn) {
|
|
97
|
+
return {
|
|
98
|
+
contents: {
|
|
99
|
+
kind: MarkupKind.Markdown,
|
|
100
|
+
value: "```xquery\n" + functionSignature(fn) + "\n```",
|
|
101
|
+
},
|
|
102
|
+
range: rangeFromOffset(doc, start, end),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── Signature help ───────────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Scan left from cursor to find the function call we're inside.
|
|
113
|
+
* Returns { name, activeParam } or null.
|
|
114
|
+
*/
|
|
115
|
+
function findEnclosingCall(text: string, offset: number): { name: string; activeParam: number } | null {
|
|
116
|
+
let depth = 0;
|
|
117
|
+
let commas = 0;
|
|
118
|
+
|
|
119
|
+
for (let i = offset - 1; i >= 0; i--) {
|
|
120
|
+
const ch = text[i];
|
|
121
|
+
if (ch === ")") {
|
|
122
|
+
depth++;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (ch === "(") {
|
|
126
|
+
if (depth > 0) {
|
|
127
|
+
depth--;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
// Found the opening paren of our call — scan left for the function name
|
|
131
|
+
let nameEnd = i;
|
|
132
|
+
while (nameEnd > 0 && /\s/.test(text[nameEnd - 1])) nameEnd--;
|
|
133
|
+
const { word, start } = wordAt(text, nameEnd - 1);
|
|
134
|
+
if (!word) return null;
|
|
135
|
+
return { name: word, activeParam: commas };
|
|
136
|
+
}
|
|
137
|
+
if (ch === "," && depth === 0) commas++;
|
|
138
|
+
// Stop at statement boundaries
|
|
139
|
+
if ((ch === ";" || ch === "{") && depth === 0) return null;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function getSignatureHelp(
|
|
145
|
+
doc: TextDocument,
|
|
146
|
+
offset: number,
|
|
147
|
+
current: FileAnalysis,
|
|
148
|
+
imported: Map<string, FileAnalysis>,
|
|
149
|
+
): SignatureHelp | null {
|
|
150
|
+
const text = doc.getText();
|
|
151
|
+
const call = findEnclosingCall(text, offset);
|
|
152
|
+
if (!call) return null;
|
|
153
|
+
|
|
154
|
+
const fn = resolveFunction(call.name, current, allFunctions(current, imported));
|
|
155
|
+
if (!fn) return null;
|
|
156
|
+
|
|
157
|
+
const sig: SignatureInformation = {
|
|
158
|
+
label: functionSignature(fn),
|
|
159
|
+
parameters: fn.params.map((p) => {
|
|
160
|
+
const label = `$${p.name}${p.type ? " as " + p.type : ""}`;
|
|
161
|
+
return ParameterInformation.create(label);
|
|
162
|
+
}),
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
signatures: [sig],
|
|
167
|
+
activeSignature: 0,
|
|
168
|
+
activeParameter: Math.min(call.activeParam, fn.params.length - 1),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Document symbols ─────────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
export function getDocumentSymbols(doc: TextDocument, current: FileAnalysis): DocumentSymbol[] {
|
|
175
|
+
const symbols: DocumentSymbol[] = [];
|
|
176
|
+
|
|
177
|
+
for (const fn of current.functions) {
|
|
178
|
+
const pos = doc.positionAt(fn.sourceOffset ?? 0);
|
|
179
|
+
const range: Range = { start: pos, end: pos };
|
|
180
|
+
symbols.push({
|
|
181
|
+
name: fn.name,
|
|
182
|
+
kind: SymbolKind.Function,
|
|
183
|
+
range,
|
|
184
|
+
selectionRange: range,
|
|
185
|
+
detail: `#${fn.arity}`,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
for (const v of current.moduleVariables) {
|
|
190
|
+
const pos = doc.positionAt(v.offset);
|
|
191
|
+
const range: Range = { start: pos, end: pos };
|
|
192
|
+
symbols.push({
|
|
193
|
+
name: `$${v.name}`,
|
|
194
|
+
kind: SymbolKind.Variable,
|
|
195
|
+
range,
|
|
196
|
+
selectionRange: range,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return symbols;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ── Go to definition ─────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
export function getDefinition(
|
|
206
|
+
doc: TextDocument,
|
|
207
|
+
offset: number,
|
|
208
|
+
current: FileAnalysis,
|
|
209
|
+
imported: Map<string, FileAnalysis>,
|
|
210
|
+
resolveUri: (atPath: string) => string,
|
|
211
|
+
): Location | null {
|
|
212
|
+
const text = doc.getText();
|
|
213
|
+
const { word, start } = wordAt(text, offset);
|
|
214
|
+
if (!word) return null;
|
|
215
|
+
|
|
216
|
+
const hasDollar = start > 0 && text[start - 1] === "$";
|
|
217
|
+
|
|
218
|
+
if (hasDollar) {
|
|
219
|
+
// Variable definition — search current file only
|
|
220
|
+
const allVars = [...current.moduleVariables, ...current.localBindings];
|
|
221
|
+
const v = allVars.find((v) => v.name === word);
|
|
222
|
+
if (!v) return null;
|
|
223
|
+
const pos = doc.positionAt(v.offset);
|
|
224
|
+
return Location.create(doc.uri, { start: pos, end: pos });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Function definition — resolve by namespace URI, search current file then all imports
|
|
228
|
+
const colonIdx = word.indexOf(":");
|
|
229
|
+
const wordPrefix = colonIdx >= 0 ? word.slice(0, colonIdx) : "";
|
|
230
|
+
const wordLocalName = colonIdx >= 0 ? word.slice(colonIdx + 1) : word;
|
|
231
|
+
const targetUri = resolvePrefix(wordPrefix, current);
|
|
232
|
+
|
|
233
|
+
const localFn = current.functions.find((f) => f.namespaceUri === targetUri && f.localName === wordLocalName);
|
|
234
|
+
if (localFn) {
|
|
235
|
+
const pos = doc.positionAt(localFn.sourceOffset ?? 0);
|
|
236
|
+
return Location.create(doc.uri, { start: pos, end: pos });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Search imported files: find which atPath contains the function, then navigate there
|
|
240
|
+
for (const imp of current.imports) {
|
|
241
|
+
const analysis = imported.get(imp.atPath);
|
|
242
|
+
if (!analysis) continue;
|
|
243
|
+
const fn = analysis.functions.find((f) => f.namespaceUri === targetUri && f.localName === wordLocalName);
|
|
244
|
+
if (!fn) continue;
|
|
245
|
+
const uri = resolveUri(imp.atPath);
|
|
246
|
+
let pos: Position = { line: 0, character: 0 };
|
|
247
|
+
if (fn.sourceOffset !== undefined) {
|
|
248
|
+
try {
|
|
249
|
+
const filePath = fileURLToPath(uri);
|
|
250
|
+
const srcText = fs.readFileSync(filePath, "utf-8");
|
|
251
|
+
pos = positionInText(srcText, fn.sourceOffset);
|
|
252
|
+
} catch {
|
|
253
|
+
/* file not readable, use line 0 */
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return Location.create(uri, { start: pos, end: pos });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return null;
|
|
260
|
+
}
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createConnection,
|
|
4
|
+
TextDocuments,
|
|
5
|
+
ProposedFeatures,
|
|
6
|
+
TextDocumentSyncKind,
|
|
7
|
+
DiagnosticSeverity,
|
|
8
|
+
} from 'vscode-languageserver/node.js';
|
|
9
|
+
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
13
|
+
import { XQuery31Full } from 'xq-parser';
|
|
14
|
+
import { analyze } from './analyzer.ts';
|
|
15
|
+
import { getBuiltins } from './builtins.ts';
|
|
16
|
+
import { getCompletions } from './completion.ts';
|
|
17
|
+
import { getHover, getSignatureHelp, getDocumentSymbols, getDefinition } from './features.ts';
|
|
18
|
+
import type { FileAnalysis } from './types.ts';
|
|
19
|
+
|
|
20
|
+
const connection = createConnection(ProposedFeatures.all);
|
|
21
|
+
const documents = new TextDocuments(TextDocument);
|
|
22
|
+
|
|
23
|
+
const analysisCache = new Map<string, FileAnalysis>();
|
|
24
|
+
|
|
25
|
+
function analyzeDocument(doc: TextDocument): FileAnalysis {
|
|
26
|
+
const analysis = analyze(doc.getText(), doc.uri);
|
|
27
|
+
analysisCache.set(doc.uri, analysis);
|
|
28
|
+
return analysis;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function uriToPath(uri: string): string {
|
|
32
|
+
try {
|
|
33
|
+
return fileURLToPath(uri);
|
|
34
|
+
} catch {
|
|
35
|
+
return uri.replace(/^file:\/\//, '');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveImportUri(currentUri: string, atPath: string): string {
|
|
40
|
+
const currentPath = uriToPath(currentUri);
|
|
41
|
+
const dir = path.dirname(currentPath);
|
|
42
|
+
const resolved = path.resolve(dir, atPath);
|
|
43
|
+
return pathToFileURL(resolved).toString();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getImportedAnalysis(importUri: string): FileAnalysis | null {
|
|
47
|
+
if (analysisCache.has(importUri)) return analysisCache.get(importUri)!;
|
|
48
|
+
try {
|
|
49
|
+
const text = fs.readFileSync(uriToPath(importUri), 'utf-8');
|
|
50
|
+
const analysis = analyze(text, importUri);
|
|
51
|
+
analysisCache.set(importUri, analysis);
|
|
52
|
+
return analysis;
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveImports(currentUri: string, analysis: FileAnalysis): Map<string, FileAnalysis> {
|
|
59
|
+
const result = new Map<string, FileAnalysis>();
|
|
60
|
+
result.set('builtin:fn', getBuiltins());
|
|
61
|
+
for (const imp of analysis.imports) {
|
|
62
|
+
const uri = resolveImportUri(currentUri, imp.atPath);
|
|
63
|
+
const imported = getImportedAnalysis(uri);
|
|
64
|
+
if (imported) result.set(imp.atPath, imported);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function parseDiagnostics(text: string, uri: string) {
|
|
70
|
+
try {
|
|
71
|
+
XQuery31Full(text);
|
|
72
|
+
return [];
|
|
73
|
+
} catch (e: unknown) {
|
|
74
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
75
|
+
// "at line N, column M:" in the parser's error messages
|
|
76
|
+
const loc = msg.match(/at line (\d+), column (\d+)/);
|
|
77
|
+
const line = loc ? parseInt(loc[1]) - 1 : 0;
|
|
78
|
+
const col = loc ? parseInt(loc[2]) - 1 : 0;
|
|
79
|
+
return [{
|
|
80
|
+
severity: DiagnosticSeverity.Error,
|
|
81
|
+
range: { start: { line, character: col }, end: { line, character: col + 1 } },
|
|
82
|
+
message: msg.split('\n')[0],
|
|
83
|
+
source: 'xquery-lsp',
|
|
84
|
+
}];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── LSP lifecycle ────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
let snippetSupport = false;
|
|
91
|
+
|
|
92
|
+
connection.onInitialize((params) => {
|
|
93
|
+
snippetSupport =
|
|
94
|
+
params.capabilities.textDocument?.completion?.completionItem?.snippetSupport ?? false;
|
|
95
|
+
return {
|
|
96
|
+
capabilities: {
|
|
97
|
+
textDocumentSync: TextDocumentSyncKind.Incremental,
|
|
98
|
+
completionProvider: {
|
|
99
|
+
triggerCharacters: ['$', ':'],
|
|
100
|
+
resolveProvider: false,
|
|
101
|
+
},
|
|
102
|
+
hoverProvider: true,
|
|
103
|
+
signatureHelpProvider: {
|
|
104
|
+
triggerCharacters: ['(', ','],
|
|
105
|
+
},
|
|
106
|
+
documentSymbolProvider: true,
|
|
107
|
+
definitionProvider: true,
|
|
108
|
+
},
|
|
109
|
+
}});
|
|
110
|
+
|
|
111
|
+
documents.onDidChangeContent(change => {
|
|
112
|
+
const doc = change.document;
|
|
113
|
+
analyzeDocument(doc);
|
|
114
|
+
const diagnostics = parseDiagnostics(doc.getText(), doc.uri);
|
|
115
|
+
connection.sendDiagnostics({ uri: doc.uri, diagnostics });
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
connection.onCompletion(params => {
|
|
119
|
+
const doc = documents.get(params.textDocument.uri);
|
|
120
|
+
if (!doc) return [];
|
|
121
|
+
const analysis = analysisCache.get(doc.uri) ?? analyzeDocument(doc);
|
|
122
|
+
const imported = resolveImports(doc.uri, analysis);
|
|
123
|
+
const offset = doc.offsetAt(params.position);
|
|
124
|
+
return getCompletions({ textBeforeCursor: doc.getText().slice(0, offset), cursorOffset: offset }, analysis, imported, snippetSupport);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
connection.onHover(params => {
|
|
128
|
+
const doc = documents.get(params.textDocument.uri);
|
|
129
|
+
if (!doc) return null;
|
|
130
|
+
const analysis = analysisCache.get(doc.uri) ?? analyzeDocument(doc);
|
|
131
|
+
const imported = resolveImports(doc.uri, analysis);
|
|
132
|
+
return getHover(doc, doc.offsetAt(params.position), analysis, imported);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
connection.onSignatureHelp(params => {
|
|
136
|
+
const doc = documents.get(params.textDocument.uri);
|
|
137
|
+
if (!doc) return null;
|
|
138
|
+
const analysis = analysisCache.get(doc.uri) ?? analyzeDocument(doc);
|
|
139
|
+
const imported = resolveImports(doc.uri, analysis);
|
|
140
|
+
return getSignatureHelp(doc, doc.offsetAt(params.position), analysis, imported);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
connection.onDocumentSymbol(params => {
|
|
144
|
+
const doc = documents.get(params.textDocument.uri);
|
|
145
|
+
if (!doc) return [];
|
|
146
|
+
const analysis = analysisCache.get(doc.uri) ?? analyzeDocument(doc);
|
|
147
|
+
return getDocumentSymbols(doc, analysis);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
connection.onDefinition(params => {
|
|
151
|
+
const doc = documents.get(params.textDocument.uri);
|
|
152
|
+
if (!doc) return null;
|
|
153
|
+
const analysis = analysisCache.get(doc.uri) ?? analyzeDocument(doc);
|
|
154
|
+
const imported = resolveImports(doc.uri, analysis);
|
|
155
|
+
return getDefinition(
|
|
156
|
+
doc,
|
|
157
|
+
doc.offsetAt(params.position),
|
|
158
|
+
analysis,
|
|
159
|
+
imported,
|
|
160
|
+
atPath => resolveImportUri(doc.uri, atPath),
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
documents.listen(connection);
|
|
165
|
+
connection.listen();
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface ParamInfo {
|
|
2
|
+
name: string;
|
|
3
|
+
type?: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface DocComment {
|
|
8
|
+
description: string;
|
|
9
|
+
params: Record<string, string>; // param name (without $) → description
|
|
10
|
+
returns?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface FunctionSymbol {
|
|
14
|
+
name: string; // full qualified name as declared, e.g. "local:add"
|
|
15
|
+
prefix: string; // namespace prefix as declared, e.g. "local"
|
|
16
|
+
localName: string; // local part, e.g. "add"
|
|
17
|
+
namespaceUri: string; // resolved namespace URI, e.g. "http://www.w3.org/2005/xquery-local-functions"
|
|
18
|
+
arity: number;
|
|
19
|
+
params: ParamInfo[];
|
|
20
|
+
returnType?: string;
|
|
21
|
+
doc?: DocComment;
|
|
22
|
+
sourceUri: string;
|
|
23
|
+
sourceOffset?: number; // char offset of the declare keyword
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface VariableSymbol {
|
|
27
|
+
name: string; // without $, e.g. "sum" or "local:count"
|
|
28
|
+
offset: number; // char offset in source where it's defined
|
|
29
|
+
isModuleLevel: boolean;
|
|
30
|
+
sourceUri: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ImportInfo {
|
|
34
|
+
prefix: string;
|
|
35
|
+
namespaceUri: string;
|
|
36
|
+
atPath: string; // as written in source, e.g. "./other.xq"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface FileAnalysis {
|
|
40
|
+
functions: FunctionSymbol[];
|
|
41
|
+
moduleVariables: VariableSymbol[]; // declare variable
|
|
42
|
+
localBindings: VariableSymbol[]; // let/for bindings
|
|
43
|
+
imports: ImportInfo[];
|
|
44
|
+
defaultFunctionNamespace: string; // from 'declare default function namespace', else XMLNS_FN
|
|
45
|
+
moduleNamespaceUri?: string; // from 'module namespace prefix="uri"'
|
|
46
|
+
modulePrefix?: string; // from 'module namespace prefix="uri"'
|
|
47
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "esnext",
|
|
4
|
+
"module": "preserve",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"erasableSyntaxOnly": true,
|
|
8
|
+
"verbatimModuleSyntax": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"rewriteRelativeImportExtensions": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"types": ["node"]
|
|
15
|
+
},
|
|
16
|
+
"paths": {
|
|
17
|
+
"vscode-languageserver/node.js": ["./node_modules/vscode-languageserver/node.d.ts"]
|
|
18
|
+
},
|
|
19
|
+
"include": [
|
|
20
|
+
"./src/*"
|
|
21
|
+
]
|
|
22
|
+
}
|