luaut-language-server 1.0.1 → 1.1.1
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 +29 -6
- package/dist/chunk-OQWE7ISD.js +1572 -0
- package/dist/chunk-OQWE7ISD.js.map +1 -0
- package/dist/cli.cjs +965 -161
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +981 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +134 -62
- package/dist/index.d.ts +134 -62
- package/dist/index.js +17 -1
- package/package.json +2 -2
- package/dist/chunk-FMWNSXUC.js +0 -751
- package/dist/chunk-FMWNSXUC.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -30,20 +30,28 @@ __export(index_exports, {
|
|
|
30
30
|
diagnostics: () => diagnostics,
|
|
31
31
|
documentSymbols: () => documentSymbols,
|
|
32
32
|
enclosing: () => enclosing,
|
|
33
|
+
exportDeclaration: () => exportDeclaration,
|
|
33
34
|
highlights: () => highlights,
|
|
34
35
|
hover: () => hover,
|
|
36
|
+
importCompletion: () => importCompletion,
|
|
37
|
+
importDefinition: () => importDefinition,
|
|
35
38
|
membersOf: () => membersOf,
|
|
36
39
|
nodeAt: () => nodeAt,
|
|
37
40
|
pathAt: () => pathAt,
|
|
41
|
+
pathOfUri: () => pathOfUri,
|
|
38
42
|
prepareRename: () => prepareRename,
|
|
39
43
|
references: () => references,
|
|
40
44
|
rename: () => rename,
|
|
45
|
+
samePath: () => samePath,
|
|
46
|
+
semanticTokens: () => semanticTokens,
|
|
47
|
+
semanticTokensLegend: () => semanticTokensLegend,
|
|
41
48
|
signatureHelp: () => signatureHelp,
|
|
42
49
|
signatureLabel: () => signatureLabel,
|
|
43
50
|
signaturesOf: () => signaturesOf,
|
|
44
51
|
startServer: () => startServer,
|
|
45
52
|
toPosition: () => toPosition,
|
|
46
53
|
toRange: () => toRange,
|
|
54
|
+
uriOfPath: () => uriOfPath,
|
|
47
55
|
walk: () => walk
|
|
48
56
|
});
|
|
49
57
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -53,6 +61,9 @@ var import_node = require("vscode-languageserver/node");
|
|
|
53
61
|
var import_vscode_languageserver_textdocument = require("vscode-languageserver-textdocument");
|
|
54
62
|
|
|
55
63
|
// src/analysis.ts
|
|
64
|
+
var import_node_fs = require("fs");
|
|
65
|
+
var import_node_path = require("path");
|
|
66
|
+
var import_node_url = require("url");
|
|
56
67
|
var import_luaut_parser = require("luaut-parser");
|
|
57
68
|
function globalsOf(libs) {
|
|
58
69
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -81,20 +92,45 @@ function declarationIndex(analysis) {
|
|
|
81
92
|
}
|
|
82
93
|
return index;
|
|
83
94
|
}
|
|
95
|
+
function pathOfUri(uri) {
|
|
96
|
+
if (!uri.startsWith("file:")) return void 0;
|
|
97
|
+
try {
|
|
98
|
+
return (0, import_node_url.fileURLToPath)(uri);
|
|
99
|
+
} catch {
|
|
100
|
+
return void 0;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function uriOfPath(path) {
|
|
104
|
+
return (0, import_node_url.pathToFileURL)(path).href;
|
|
105
|
+
}
|
|
106
|
+
function samePath(a, b) {
|
|
107
|
+
return pathKey(a) === pathKey(b);
|
|
108
|
+
}
|
|
109
|
+
function pathKey(path) {
|
|
110
|
+
const normalized = (0, import_node_path.resolve)(path);
|
|
111
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
112
|
+
}
|
|
113
|
+
var CYCLE = { values: /* @__PURE__ */ new Map(), types: /* @__PURE__ */ new Map(), partial: true };
|
|
84
114
|
var Analyzer = class {
|
|
85
115
|
libs;
|
|
86
116
|
builtinGlobals;
|
|
117
|
+
openDocument;
|
|
87
118
|
cache = /* @__PURE__ */ new Map();
|
|
119
|
+
/** Imported modules, by path key. */
|
|
120
|
+
modules = /* @__PURE__ */ new Map();
|
|
88
121
|
constructor(options = {}) {
|
|
89
122
|
this.libs = options.libs ?? import_luaut_parser.defaultLibs;
|
|
90
123
|
this.builtinGlobals = globalsOf(this.libs);
|
|
124
|
+
this.openDocument = options.openDocument;
|
|
91
125
|
}
|
|
92
|
-
/** Analyze `document`, reusing the previous result
|
|
93
|
-
*
|
|
126
|
+
/** Analyze `document`, reusing the previous result while neither it nor
|
|
127
|
+
* anything it imports has changed. */
|
|
94
128
|
get(document) {
|
|
95
129
|
const cached = this.cache.get(document.uri);
|
|
96
130
|
const source = document.getText();
|
|
97
|
-
if (cached && cached.version === document.version && cached.source === source
|
|
131
|
+
if (cached && cached.version === document.version && cached.source === source && this.isFresh(cached)) {
|
|
132
|
+
return cached;
|
|
133
|
+
}
|
|
98
134
|
const analysis = this.analyze(document.uri, document.version, source);
|
|
99
135
|
this.cache.set(document.uri, analysis);
|
|
100
136
|
return analysis;
|
|
@@ -102,18 +138,103 @@ var Analyzer = class {
|
|
|
102
138
|
/** Analyze source text that is not a tracked document — used by
|
|
103
139
|
* completion, which analyzes a speculatively edited copy of the file. */
|
|
104
140
|
analyze(uri, version, source) {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
const types = (0, import_luaut_parser.analyzeTypes)(program, scopes, { libs: this.libs });
|
|
108
|
-
return { uri, version, source, program, parseErrors: errors, scopes, types };
|
|
141
|
+
const path = pathOfUri(uri);
|
|
142
|
+
return this.analyzeModule(uri, version, source, new Set(path ? [pathKey(path)] : []));
|
|
109
143
|
}
|
|
110
144
|
forget(uri) {
|
|
111
145
|
this.cache.delete(uri);
|
|
112
146
|
}
|
|
147
|
+
/** The file an import in `fromUri` names. Relative paths only (`./x`,
|
|
148
|
+
* `../x`); the extension may be left off, and a folder means its
|
|
149
|
+
* `index.luaut`. */
|
|
150
|
+
resolveModulePath(fromUri, specifier) {
|
|
151
|
+
return this.moduleCandidates(fromUri, specifier).find((candidate) => this.sourceOf(candidate) !== void 0);
|
|
152
|
+
}
|
|
153
|
+
/** Every file an import could mean, in the order they are tried. */
|
|
154
|
+
moduleCandidates(fromUri, specifier) {
|
|
155
|
+
const from = pathOfUri(fromUri);
|
|
156
|
+
if (!from || !(specifier.startsWith("./") || specifier.startsWith("../"))) return [];
|
|
157
|
+
const base = (0, import_node_path.resolve)((0, import_node_path.dirname)(from), specifier);
|
|
158
|
+
return specifier.endsWith(".luaut") ? [base] : [`${base}.luaut`, `${base}.d.luaut`, (0, import_node_path.join)(base, "index.luaut")];
|
|
159
|
+
}
|
|
160
|
+
/** What the module at `path` exports, analyzing it if need be. */
|
|
161
|
+
exportsAt(path) {
|
|
162
|
+
return this.exportsOf(path, /* @__PURE__ */ new Set());
|
|
163
|
+
}
|
|
164
|
+
/** The analysis of the module at `path`, analyzing it if need be. */
|
|
165
|
+
moduleAt(path) {
|
|
166
|
+
this.exportsOf(path, /* @__PURE__ */ new Set());
|
|
167
|
+
return this.modules.get(pathKey(path))?.analysis;
|
|
168
|
+
}
|
|
169
|
+
sourceOf(path) {
|
|
170
|
+
const open = this.openDocument?.(path);
|
|
171
|
+
if (open) return open.getText();
|
|
172
|
+
try {
|
|
173
|
+
return (0, import_node_fs.statSync)(path).isFile() ? (0, import_node_fs.readFileSync)(path, "utf8") : void 0;
|
|
174
|
+
} catch {
|
|
175
|
+
return void 0;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** `importing` holds every module on the current import chain, so an
|
|
179
|
+
* import back into one of them is recognized as a cycle. */
|
|
180
|
+
analyzeModule(uri, version, source, importing) {
|
|
181
|
+
const { program, errors } = (0, import_luaut_parser.parseWithRecovery)(source);
|
|
182
|
+
const scopes = (0, import_luaut_parser.analyzeScopes)(program, { builtinGlobals: this.builtinGlobals });
|
|
183
|
+
const dependencies = /* @__PURE__ */ new Map();
|
|
184
|
+
const types = (0, import_luaut_parser.analyzeTypes)(program, scopes, {
|
|
185
|
+
libs: this.libs,
|
|
186
|
+
resolveModule: (specifier) => {
|
|
187
|
+
const target = this.resolveModulePath(uri, specifier);
|
|
188
|
+
if (!target) {
|
|
189
|
+
for (const candidate of this.moduleCandidates(uri, specifier)) dependencies.set(candidate, void 0);
|
|
190
|
+
return void 0;
|
|
191
|
+
}
|
|
192
|
+
const exports2 = this.exportsOf(target, importing);
|
|
193
|
+
dependencies.set(target, this.sourceOf(target));
|
|
194
|
+
return exports2;
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
return { uri, version, source, program, parseErrors: errors, scopes, types, dependencies };
|
|
198
|
+
}
|
|
199
|
+
exportsOf(path, importing) {
|
|
200
|
+
const key = pathKey(path);
|
|
201
|
+
if (importing.has(key)) return CYCLE;
|
|
202
|
+
const source = this.sourceOf(path);
|
|
203
|
+
if (source === void 0) return void 0;
|
|
204
|
+
const cached = this.modules.get(key);
|
|
205
|
+
if (cached && cached.analysis.source === source && this.isFresh(cached.analysis)) return cached.exports;
|
|
206
|
+
importing.add(key);
|
|
207
|
+
try {
|
|
208
|
+
const analysis = this.analyzeModule(uriOfPath(path), -1, source, importing);
|
|
209
|
+
const exports2 = (0, import_luaut_parser.moduleExports)(analysis.program, analysis.scopes, analysis.types, (specifier) => {
|
|
210
|
+
const next = this.resolveModulePath(analysis.uri, specifier);
|
|
211
|
+
return next ? this.exportsOf(next, importing) : void 0;
|
|
212
|
+
});
|
|
213
|
+
this.modules.set(key, { analysis, exports: exports2 });
|
|
214
|
+
return exports2;
|
|
215
|
+
} finally {
|
|
216
|
+
importing.delete(key);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/** Does every module `analysis` imported — and everything those import —
|
|
220
|
+
* still have the text it was analyzed against? */
|
|
221
|
+
isFresh(analysis, seen = /* @__PURE__ */ new Set()) {
|
|
222
|
+
if (seen.has(analysis)) return true;
|
|
223
|
+
seen.add(analysis);
|
|
224
|
+
for (const [path, source] of analysis.dependencies) {
|
|
225
|
+
if (this.sourceOf(path) !== source) return false;
|
|
226
|
+
const module2 = this.modules.get(pathKey(path));
|
|
227
|
+
if (module2 && !this.isFresh(module2.analysis, seen)) return false;
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
113
231
|
};
|
|
114
232
|
|
|
115
|
-
// src/features/
|
|
233
|
+
// src/features/imports.ts
|
|
234
|
+
var import_node_fs2 = require("fs");
|
|
235
|
+
var import_node_path2 = require("path");
|
|
116
236
|
var import_vscode_languageserver = require("vscode-languageserver");
|
|
237
|
+
var import_luaut_parser3 = require("luaut-parser");
|
|
117
238
|
|
|
118
239
|
// src/ast-utils.ts
|
|
119
240
|
function isSpanned(v) {
|
|
@@ -143,16 +264,21 @@ function containsPosition(node, pos, inclusive = false) {
|
|
|
143
264
|
}
|
|
144
265
|
function children(node) {
|
|
145
266
|
const out = [];
|
|
146
|
-
|
|
267
|
+
collect2(node, out);
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
function collect2(container, out) {
|
|
271
|
+
for (const key of Object.keys(container)) {
|
|
147
272
|
if (key === "line" || key === "column") continue;
|
|
148
|
-
const value =
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
out.push(value);
|
|
273
|
+
const value = container[key];
|
|
274
|
+
for (const item of Array.isArray(value) ? value : [value]) {
|
|
275
|
+
if (isSpanned(item)) out.push(item);
|
|
276
|
+
else if (isSpanlessNode(item)) collect2(item, out);
|
|
153
277
|
}
|
|
154
278
|
}
|
|
155
|
-
|
|
279
|
+
}
|
|
280
|
+
function isSpanlessNode(v) {
|
|
281
|
+
return !!v && typeof v === "object" && typeof v.type === "string";
|
|
156
282
|
}
|
|
157
283
|
function pathAt(root, pos, inclusive = false) {
|
|
158
284
|
let best;
|
|
@@ -188,14 +314,280 @@ function walk(root, visit, parent) {
|
|
|
188
314
|
for (const child of children(root)) walk(child, visit, root);
|
|
189
315
|
}
|
|
190
316
|
|
|
317
|
+
// src/features/members.ts
|
|
318
|
+
var import_luaut_parser2 = require("luaut-parser");
|
|
319
|
+
function membersOf(type, aliases, seen = /* @__PURE__ */ new Set()) {
|
|
320
|
+
if (!type || seen.has(type)) return [];
|
|
321
|
+
seen.add(type);
|
|
322
|
+
switch (type.kind) {
|
|
323
|
+
case "object": {
|
|
324
|
+
const out = [];
|
|
325
|
+
for (const [name, property] of type.properties) {
|
|
326
|
+
out.push({ name, property, isMethod: takesSelf(property.type) });
|
|
327
|
+
}
|
|
328
|
+
return out;
|
|
329
|
+
}
|
|
330
|
+
case "intersection": {
|
|
331
|
+
const merged = /* @__PURE__ */ new Map();
|
|
332
|
+
for (const part of type.types) {
|
|
333
|
+
for (const member of membersOf(part, aliases, seen)) merged.set(member.name, member);
|
|
334
|
+
}
|
|
335
|
+
return [...merged.values()];
|
|
336
|
+
}
|
|
337
|
+
case "union": {
|
|
338
|
+
const perBranch = type.types.map((part) => membersOf(part, aliases, seen));
|
|
339
|
+
if (!perBranch.length) return [];
|
|
340
|
+
const [first, ...rest] = perBranch;
|
|
341
|
+
return first.filter((member) => rest.every((other) => other.some((m) => m.name === member.name)));
|
|
342
|
+
}
|
|
343
|
+
case "genericRef": {
|
|
344
|
+
const alias = aliases.get(type.name);
|
|
345
|
+
return alias ? membersOf(alias, aliases, seen) : [];
|
|
346
|
+
}
|
|
347
|
+
case "typeParam":
|
|
348
|
+
return membersOf(type.constraint, aliases, seen);
|
|
349
|
+
default:
|
|
350
|
+
return [];
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function takesSelf(type) {
|
|
354
|
+
for (const signature of signaturesOf(type)) {
|
|
355
|
+
if (signature.params[0]?.name === "self") return true;
|
|
356
|
+
}
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
function signaturesOf(type, aliases) {
|
|
360
|
+
if (!type) return [];
|
|
361
|
+
if (type.kind === "function") return [type];
|
|
362
|
+
if (type.kind === "intersection") return type.types.flatMap((t) => signaturesOf(t, aliases));
|
|
363
|
+
if (type.kind === "genericRef" && aliases) {
|
|
364
|
+
const alias = aliases.get(type.name);
|
|
365
|
+
return alias ? signaturesOf(alias, aliases) : [];
|
|
366
|
+
}
|
|
367
|
+
return [];
|
|
368
|
+
}
|
|
369
|
+
function signatureLabel(signature) {
|
|
370
|
+
const parameters = signature.params.map((p, i) => {
|
|
371
|
+
const name = p.name ?? `arg${i + 1}`;
|
|
372
|
+
return `${name}${p.optional ? "?" : ""}: ${(0, import_luaut_parser2.formatType)(p.type)}`;
|
|
373
|
+
});
|
|
374
|
+
const generics = signature.typeParams?.length ? `<${signature.typeParams.join(", ")}>` : "";
|
|
375
|
+
const varargs = signature.varargs ? [`...: ${(0, import_luaut_parser2.formatType)(signature.varargs)}`] : [];
|
|
376
|
+
const label = `${generics}(${[...parameters, ...varargs].join(", ")}) -> ${(0, import_luaut_parser2.formatType)(signature.returns)}`;
|
|
377
|
+
return { label, parameters };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/features/imports.ts
|
|
381
|
+
var SUGGEST_AGAIN = { title: "Suggest", command: "editor.action.triggerSuggest" };
|
|
382
|
+
function importCompletion(analyzer, document, position) {
|
|
383
|
+
const text = document.getText();
|
|
384
|
+
const cursor = document.offsetAt(position);
|
|
385
|
+
const lineStart = document.offsetAt({ line: position.line, character: 0 });
|
|
386
|
+
const lineEnd = document.offsetAt({ line: position.line + 1, character: 0 });
|
|
387
|
+
const before = text.slice(lineStart, cursor);
|
|
388
|
+
const after = text.slice(cursor, lineEnd);
|
|
389
|
+
if (!/^\s*(?:import|export)\b/.test(before)) return void 0;
|
|
390
|
+
const path = /\bfrom\s*(["'])([^"']*)$/.exec(before);
|
|
391
|
+
if (path) return pathItems(document.uri, position, path[2]);
|
|
392
|
+
const braces = /^\s*(import|export)\s+(?:[A-Za-z_][A-Za-z0-9_]*\s*,\s*)?\{[^}]*$/.exec(before);
|
|
393
|
+
if (braces) {
|
|
394
|
+
const module2 = /\}\s*from\s*(["'])([^"']+)\1/.exec(after);
|
|
395
|
+
if (module2) return nameItems(analyzer, document.uri, module2[2], before);
|
|
396
|
+
return braces[1] === "import" ? [] : void 0;
|
|
397
|
+
}
|
|
398
|
+
return void 0;
|
|
399
|
+
}
|
|
400
|
+
function pathItems(fromUri, position, typed) {
|
|
401
|
+
const from = pathOfUri(fromUri);
|
|
402
|
+
if (!from) return [];
|
|
403
|
+
if (!typed.startsWith("./") && !typed.startsWith("../")) {
|
|
404
|
+
const range2 = rangeBack(position, typed.length);
|
|
405
|
+
return ["./", "../"].map((label) => ({
|
|
406
|
+
label,
|
|
407
|
+
kind: import_vscode_languageserver.CompletionItemKind.Folder,
|
|
408
|
+
textEdit: { range: range2, newText: label },
|
|
409
|
+
command: SUGGEST_AGAIN
|
|
410
|
+
}));
|
|
411
|
+
}
|
|
412
|
+
const slash = typed.lastIndexOf("/");
|
|
413
|
+
const directory = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(from), typed.slice(0, slash + 1));
|
|
414
|
+
const range = rangeBack(position, typed.length - slash - 1);
|
|
415
|
+
let entries;
|
|
416
|
+
try {
|
|
417
|
+
entries = (0, import_node_fs2.readdirSync)(directory, { withFileTypes: true });
|
|
418
|
+
} catch {
|
|
419
|
+
return [];
|
|
420
|
+
}
|
|
421
|
+
const items = [];
|
|
422
|
+
for (const entry of entries) {
|
|
423
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
424
|
+
if (entry.isDirectory()) {
|
|
425
|
+
items.push({
|
|
426
|
+
label: `${entry.name}/`,
|
|
427
|
+
kind: import_vscode_languageserver.CompletionItemKind.Folder,
|
|
428
|
+
textEdit: { range, newText: `${entry.name}/` },
|
|
429
|
+
command: SUGGEST_AGAIN
|
|
430
|
+
});
|
|
431
|
+
} else if (entry.name.endsWith(".luaut")) {
|
|
432
|
+
if (samePath((0, import_node_path2.resolve)(directory, entry.name), from)) continue;
|
|
433
|
+
const name = entry.name.replace(/(\.d)?\.luaut$/, "");
|
|
434
|
+
items.push({
|
|
435
|
+
label: name,
|
|
436
|
+
kind: import_vscode_languageserver.CompletionItemKind.File,
|
|
437
|
+
detail: entry.name,
|
|
438
|
+
textEdit: { range, newText: name }
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return items;
|
|
443
|
+
}
|
|
444
|
+
function nameItems(analyzer, fromUri, specifier, before) {
|
|
445
|
+
const target = analyzer.resolveModulePath(fromUri, specifier);
|
|
446
|
+
const exports2 = target ? analyzer.exportsAt(target) : void 0;
|
|
447
|
+
if (!exports2) return [];
|
|
448
|
+
const braces = before.slice(before.indexOf("{") + 1);
|
|
449
|
+
const listed = new Set(braces.split(",").map((part) => part.trim().split(/\s+/)[0]).filter(Boolean));
|
|
450
|
+
const items = [];
|
|
451
|
+
for (const [name, type] of exports2.values) {
|
|
452
|
+
if (listed.has(name)) continue;
|
|
453
|
+
items.push({
|
|
454
|
+
label: name,
|
|
455
|
+
kind: signaturesOf(type).length ? import_vscode_languageserver.CompletionItemKind.Function : import_vscode_languageserver.CompletionItemKind.Variable,
|
|
456
|
+
detail: (0, import_luaut_parser3.formatType)(type)
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
for (const [name, exported] of exports2.types) {
|
|
460
|
+
if (listed.has(name) || exports2.values.has(name)) continue;
|
|
461
|
+
items.push({
|
|
462
|
+
label: name,
|
|
463
|
+
kind: import_vscode_languageserver.CompletionItemKind.Interface,
|
|
464
|
+
detail: `type ${name} = ${(0, import_luaut_parser3.formatType)(exported.type)}`
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
return items;
|
|
468
|
+
}
|
|
469
|
+
function rangeBack(position, length) {
|
|
470
|
+
return { start: { line: position.line, character: position.character - length }, end: position };
|
|
471
|
+
}
|
|
472
|
+
function importDefinition(analyzer, analysis, position) {
|
|
473
|
+
const path = pathAt(analysis.program, position, true);
|
|
474
|
+
const statement = path.find(isModuleReference);
|
|
475
|
+
if (!statement?.source) return void 0;
|
|
476
|
+
const target = analyzer.resolveModulePath(analysis.uri, statement.source.value);
|
|
477
|
+
if (!target) return null;
|
|
478
|
+
const fileStart = {
|
|
479
|
+
uri: uriOfPath(target),
|
|
480
|
+
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }
|
|
481
|
+
};
|
|
482
|
+
const name = referencedName(statement, path[path.length - 1]);
|
|
483
|
+
if (!name) return fileStart;
|
|
484
|
+
const module2 = analyzer.moduleAt(target);
|
|
485
|
+
const found = module2 && exportDeclaration(analyzer, module2, name);
|
|
486
|
+
return found ? { uri: found.uri, range: toRange(found.node) } : fileStart;
|
|
487
|
+
}
|
|
488
|
+
function isModuleReference(node) {
|
|
489
|
+
return node.type === "ImportStatement" || node.type === "ExportAllStatement" || node.type === "ExportNamedStatement" && !!node.source;
|
|
490
|
+
}
|
|
491
|
+
function referencedName(statement, node) {
|
|
492
|
+
switch (statement.type) {
|
|
493
|
+
case "ImportStatement":
|
|
494
|
+
if (node === statement.defaultImport) return "default";
|
|
495
|
+
return statement.specifiers.find((s) => node === s.imported || node === s.local)?.imported.name;
|
|
496
|
+
case "ExportNamedStatement":
|
|
497
|
+
return statement.specifiers.find((s) => node === s.local || node === s.exported)?.local.name;
|
|
498
|
+
case "ExportAllStatement":
|
|
499
|
+
return void 0;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function exportDeclaration(analyzer, module2, name, seen = /* @__PURE__ */ new Set()) {
|
|
503
|
+
const key = `${module2.uri}#${name}`;
|
|
504
|
+
if (seen.has(key)) return void 0;
|
|
505
|
+
seen.add(key);
|
|
506
|
+
const here = (node) => ({ uri: module2.uri, node });
|
|
507
|
+
const stars = [];
|
|
508
|
+
for (const statement of module2.program.body.statements) {
|
|
509
|
+
switch (statement.type) {
|
|
510
|
+
case "ExportDefaultStatement":
|
|
511
|
+
if (name === "default") return here(statement);
|
|
512
|
+
break;
|
|
513
|
+
case "ExportTypeAliasStatement":
|
|
514
|
+
if (statement.alias.name.name === name) return here(statement.alias.name);
|
|
515
|
+
break;
|
|
516
|
+
case "ExportStatement": {
|
|
517
|
+
const declaration = statement.declaration;
|
|
518
|
+
if (declaration.type === "FunctionDeclaration") {
|
|
519
|
+
if (declaration.name.name === name) return here(declaration.name);
|
|
520
|
+
} else {
|
|
521
|
+
for (const target of declaration.names) {
|
|
522
|
+
const found = patternNamed(target, name);
|
|
523
|
+
if (found) return here(found);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
case "ExportNamedStatement": {
|
|
529
|
+
const specifier = statement.specifiers.find((s) => s.exported.name === name);
|
|
530
|
+
if (!specifier) break;
|
|
531
|
+
if (statement.source) {
|
|
532
|
+
const next = moduleFrom(analyzer, module2, statement.source.value);
|
|
533
|
+
return next && exportDeclaration(analyzer, next, specifier.local.name, seen);
|
|
534
|
+
}
|
|
535
|
+
return here(localDeclaration(module2, specifier.local) ?? specifier.local);
|
|
536
|
+
}
|
|
537
|
+
case "ExportAllStatement":
|
|
538
|
+
stars.push(statement.source.value);
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (name === "default") return void 0;
|
|
543
|
+
for (const specifier of stars) {
|
|
544
|
+
const next = moduleFrom(analyzer, module2, specifier);
|
|
545
|
+
const found = next && exportDeclaration(analyzer, next, name, seen);
|
|
546
|
+
if (found) return found;
|
|
547
|
+
}
|
|
548
|
+
return void 0;
|
|
549
|
+
}
|
|
550
|
+
function moduleFrom(analyzer, module2, specifier) {
|
|
551
|
+
const target = analyzer.resolveModulePath(module2.uri, specifier);
|
|
552
|
+
return target ? analyzer.moduleAt(target) : void 0;
|
|
553
|
+
}
|
|
554
|
+
function localDeclaration(module2, local) {
|
|
555
|
+
const binding = bindingOfNode(module2, local);
|
|
556
|
+
if (binding?.declarationNode) return binding.declarationNode;
|
|
557
|
+
for (const statement of module2.program.body.statements) {
|
|
558
|
+
const alias = statement.type === "TypeAliasStatement" ? statement : statement.type === "ExportTypeAliasStatement" ? statement.alias : void 0;
|
|
559
|
+
if (alias?.name.name === local.name) return alias.name;
|
|
560
|
+
}
|
|
561
|
+
return void 0;
|
|
562
|
+
}
|
|
563
|
+
function patternNamed(target, name) {
|
|
564
|
+
switch (target.type) {
|
|
565
|
+
case "IdentifierPattern":
|
|
566
|
+
return target.name === name ? target : void 0;
|
|
567
|
+
case "ObjectPattern":
|
|
568
|
+
for (const property of target.properties) {
|
|
569
|
+
const found = patternNamed(property.value, name);
|
|
570
|
+
if (found) return found;
|
|
571
|
+
}
|
|
572
|
+
return target.rest && patternNamed(target.rest, name);
|
|
573
|
+
case "ArrayPattern":
|
|
574
|
+
for (const element of target.elements) {
|
|
575
|
+
const found = element && patternNamed(element.value, name);
|
|
576
|
+
if (found) return found;
|
|
577
|
+
}
|
|
578
|
+
return target.rest && patternNamed(target.rest, name);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
191
582
|
// src/features/diagnostics.ts
|
|
583
|
+
var import_vscode_languageserver2 = require("vscode-languageserver");
|
|
192
584
|
function diagnostics(analysis) {
|
|
193
585
|
const out = [];
|
|
194
586
|
for (const error of analysis.parseErrors) {
|
|
195
587
|
const start = toPosition(error.line, error.column);
|
|
196
588
|
out.push({
|
|
197
589
|
range: { start, end: { line: start.line, character: start.character + 1 } },
|
|
198
|
-
severity:
|
|
590
|
+
severity: import_vscode_languageserver2.DiagnosticSeverity.Error,
|
|
199
591
|
source: "luaut",
|
|
200
592
|
code: "syntax",
|
|
201
593
|
// The parser appends `(line:column)`; the range already says that.
|
|
@@ -205,7 +597,7 @@ function diagnostics(analysis) {
|
|
|
205
597
|
for (const d of analysis.scopes.diagnostics) {
|
|
206
598
|
out.push({
|
|
207
599
|
range: toRange(d.node),
|
|
208
|
-
severity:
|
|
600
|
+
severity: import_vscode_languageserver2.DiagnosticSeverity.Error,
|
|
209
601
|
source: "luaut",
|
|
210
602
|
code: d.kind,
|
|
211
603
|
message: d.message
|
|
@@ -214,7 +606,7 @@ function diagnostics(analysis) {
|
|
|
214
606
|
for (const d of analysis.types.diagnostics) {
|
|
215
607
|
out.push({
|
|
216
608
|
range: toRange(d.node),
|
|
217
|
-
severity:
|
|
609
|
+
severity: import_vscode_languageserver2.DiagnosticSeverity.Error,
|
|
218
610
|
source: "luaut",
|
|
219
611
|
code: "type",
|
|
220
612
|
message: d.message
|
|
@@ -224,46 +616,201 @@ function diagnostics(analysis) {
|
|
|
224
616
|
}
|
|
225
617
|
|
|
226
618
|
// src/features/hover.ts
|
|
227
|
-
var
|
|
619
|
+
var import_luaut_parser4 = require("luaut-parser");
|
|
228
620
|
function hover(analysis, position) {
|
|
229
621
|
const path = pathAt(analysis.program, position, true);
|
|
230
622
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
if (found) return { contents: { kind: "markdown", value: code(found) }, range: toRange(node) };
|
|
623
|
+
const text = describe(analysis, path, i);
|
|
624
|
+
if (text) return { contents: { kind: "markdown", value: code(text) }, range: toRange(path[i]) };
|
|
234
625
|
}
|
|
235
626
|
return null;
|
|
236
627
|
}
|
|
237
|
-
|
|
628
|
+
var PRIMITIVES = /* @__PURE__ */ new Set(["any", "unknown", "never", "nil", "boolean", "number", "string", "thread", "buffer"]);
|
|
629
|
+
function describe(analysis, path, index) {
|
|
238
630
|
const { types } = analysis;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
631
|
+
const node = path[index];
|
|
632
|
+
const parent = path[index - 1];
|
|
633
|
+
const typeOfNode = (n) => types.typeOfTypeNode.get(n);
|
|
634
|
+
switch (node.type) {
|
|
635
|
+
case "Identifier": {
|
|
636
|
+
const identifier2 = node;
|
|
637
|
+
const name = identifier2.name;
|
|
638
|
+
switch (parent?.type) {
|
|
639
|
+
// `{ name: "n" }` — read the property off the object's type, so
|
|
640
|
+
// it widens the way the object did (`string`, not `"n"`).
|
|
641
|
+
case "TableExpression": {
|
|
642
|
+
const field = fieldWithKey(parent, node);
|
|
643
|
+
if (!field) break;
|
|
644
|
+
const objectType = types.typeOf.get(parent);
|
|
645
|
+
const property = objectType?.kind === "object" ? objectType.properties.get(name) : void 0;
|
|
646
|
+
const type2 = property?.type ?? types.typeOf.get(field.value);
|
|
647
|
+
return type2 && `(property) ${name}: ${pretty(type2)}`;
|
|
648
|
+
}
|
|
649
|
+
case "ImportSpecifier": {
|
|
650
|
+
const alias = types.aliases.get(name);
|
|
651
|
+
const binding2 = bindingOfNode(analysis, identifier2);
|
|
652
|
+
const value = binding2 && types.bindingType.get(binding2.id);
|
|
653
|
+
if (alias && (!value || value.kind === "any")) return `type ${name} = ${pretty(alias)}`;
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
case "ExportSpecifier": {
|
|
657
|
+
if (bindingOfNode(analysis, identifier2)) break;
|
|
658
|
+
const alias = types.aliases.get(name);
|
|
659
|
+
if (alias) return `type ${name} = ${pretty(alias)}`;
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
case "TypeAliasStatement":
|
|
663
|
+
case "ExportTypeAliasStatement":
|
|
664
|
+
if (parent.name === node) return aliasText(analysis, parent);
|
|
665
|
+
break;
|
|
666
|
+
case "DeclareStatement":
|
|
667
|
+
if (parent.id === node) return declareText(analysis, parent);
|
|
668
|
+
break;
|
|
669
|
+
case "TableTypeProperty":
|
|
670
|
+
if (parent.key === node) {
|
|
671
|
+
const type2 = typeOfNode(parent.valueType);
|
|
672
|
+
const readonly = parent.readonly ? "readonly " : "";
|
|
673
|
+
return type2 && `(property) ${readonly}${name}${parent.optional ? "?" : ""}: ${pretty(type2)}`;
|
|
674
|
+
}
|
|
675
|
+
break;
|
|
676
|
+
case "FunctionTypeParameter":
|
|
677
|
+
if (parent.id === node) {
|
|
678
|
+
const type2 = typeOfNode(parent.typeAnnotation);
|
|
679
|
+
return type2 && `(parameter) ${name}${parent.optional ? "?" : ""}: ${pretty(type2)}`;
|
|
680
|
+
}
|
|
681
|
+
break;
|
|
682
|
+
case "GenericTypeParameter":
|
|
683
|
+
if (parent.id === node) return typeParameterText(analysis, parent);
|
|
684
|
+
break;
|
|
685
|
+
case "InferTypeNode":
|
|
686
|
+
if (parent.id === node) return `(type parameter) infer ${name}`;
|
|
687
|
+
break;
|
|
688
|
+
case "MappedTypeNode":
|
|
689
|
+
if (parent.parameterId === node) {
|
|
690
|
+
const keys = typeOfNode(parent.constraint);
|
|
691
|
+
return `(type parameter) ${name}${keys ? ` in ${(0, import_luaut_parser4.formatType)(keys)}` : ""}`;
|
|
692
|
+
}
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
const narrowed = types.narrowedTypeOf.get(identifier2);
|
|
696
|
+
if (narrowed) return `${name}: ${pretty(narrowed)}`;
|
|
697
|
+
const binding = bindingOfNode(analysis, identifier2);
|
|
698
|
+
if (binding) {
|
|
699
|
+
const type2 = types.bindingType.get(binding.id);
|
|
700
|
+
if (type2) return `${keyword(binding)} ${binding.name}: ${pretty(type2)}`;
|
|
701
|
+
}
|
|
702
|
+
if (parent?.type === "MemberExpression" || parent?.type === "MethodCallExpression") {
|
|
703
|
+
const type2 = types.typeOf.get(parent);
|
|
704
|
+
if (type2) return `${name}: ${pretty(type2)}`;
|
|
705
|
+
}
|
|
706
|
+
return void 0;
|
|
252
707
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
708
|
+
// Declarations: `const x`, a parameter, `const function f`.
|
|
709
|
+
case "IdentifierPattern":
|
|
710
|
+
case "FunctionParameter":
|
|
711
|
+
case "TypedIdentifier": {
|
|
712
|
+
const binding = bindingOfNode(analysis, node);
|
|
713
|
+
const type2 = binding && types.bindingType.get(binding.id);
|
|
714
|
+
return type2 ? `${keyword(binding)} ${binding.name}: ${pretty(type2)}` : void 0;
|
|
256
715
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
716
|
+
// A type written by name: `number`, `Shape`, `Partial<User>`, or a type
|
|
717
|
+
// parameter in scope.
|
|
718
|
+
case "TypeReference": {
|
|
719
|
+
const base = node.base;
|
|
720
|
+
if (!node.namespace) {
|
|
721
|
+
const parameter = typeParameterInScope(path, index, base);
|
|
722
|
+
if (parameter) return typeParameterText(analysis, parameter);
|
|
723
|
+
if (PRIMITIVES.has(base)) return `type ${base}`;
|
|
724
|
+
}
|
|
725
|
+
if (!node.namespace && !node.typeArguments.length) {
|
|
726
|
+
const alias = types.aliases.get(base);
|
|
727
|
+
if (alias) return `type ${base} = ${pretty(alias)}`;
|
|
728
|
+
}
|
|
729
|
+
const type2 = typeOfNode(node);
|
|
730
|
+
return type2 && `type ${referenceText(analysis, node)} = ${pretty(type2)}`;
|
|
263
731
|
}
|
|
264
732
|
}
|
|
733
|
+
const annotated = typeOfNode(node);
|
|
734
|
+
if (annotated) return pretty(annotated);
|
|
265
735
|
const type = types.typeOf.get(node);
|
|
266
|
-
return type ? (
|
|
736
|
+
return type ? pretty(type) : void 0;
|
|
737
|
+
}
|
|
738
|
+
function aliasText(analysis, statement) {
|
|
739
|
+
const name = statement.name.name;
|
|
740
|
+
const alias = analysis.types.aliases.get(name);
|
|
741
|
+
if (!alias) return void 0;
|
|
742
|
+
const generics = statement.generics ?? [];
|
|
743
|
+
const parameters = generics.length ? `<${generics.map((g) => typeParameterSignature(analysis, g)).join(", ")}>` : "";
|
|
744
|
+
return `type ${name}${parameters} = ${pretty(alias)}`;
|
|
745
|
+
}
|
|
746
|
+
function declareText(analysis, statement) {
|
|
747
|
+
const name = statement.name;
|
|
748
|
+
const own = analysis.types.typeOfTypeNode.get(statement.valueType);
|
|
749
|
+
if (!own) return void 0;
|
|
750
|
+
if (own.kind !== "function") return `declare ${name}: ${pretty(own)}`;
|
|
751
|
+
const total = analysis.program.body.statements.filter((s) => s.type === "DeclareStatement" && s.name === name).reduce((n, s) => n + signaturesOf(analysis.types.typeOfTypeNode.get(s.valueType)).length, 0);
|
|
752
|
+
const others = total - 1;
|
|
753
|
+
const overloads = others > 0 ? ` (+${others} overload${others > 1 ? "s" : ""})` : "";
|
|
754
|
+
return `declare function ${name}${(0, import_luaut_parser4.formatType)(own)}${overloads}`;
|
|
755
|
+
}
|
|
756
|
+
function typeParameterText(analysis, parameter) {
|
|
757
|
+
return `(type parameter) ${typeParameterSignature(analysis, parameter)}`;
|
|
758
|
+
}
|
|
759
|
+
function typeParameterSignature(analysis, parameter) {
|
|
760
|
+
const p = parameter;
|
|
761
|
+
if (p.infer) return `infer ${p.name}`;
|
|
762
|
+
const constraint = p.constraint ? analysis.types.typeOfTypeNode.get(p.constraint) : void 0;
|
|
763
|
+
return `${p.isConst ? "const " : ""}${p.name}${constraint ? ` extends ${(0, import_luaut_parser4.formatType)(constraint)}` : ""}`;
|
|
764
|
+
}
|
|
765
|
+
function typeParameterInScope(path, index, name) {
|
|
766
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
767
|
+
const a = path[i];
|
|
768
|
+
const generic = a.generics?.find((g) => g.name === name);
|
|
769
|
+
if (generic) return generic;
|
|
770
|
+
if (a.type === "MappedTypeNode" && a.parameter === name) return { name };
|
|
771
|
+
if (a.type === "ConditionalTypeNode" && bindsInfer(a.extendsType, name)) return { name, infer: true };
|
|
772
|
+
}
|
|
773
|
+
return void 0;
|
|
774
|
+
}
|
|
775
|
+
function bindsInfer(node, name) {
|
|
776
|
+
if (!node || typeof node !== "object") return false;
|
|
777
|
+
if (Array.isArray(node)) return node.some((n2) => bindsInfer(n2, name));
|
|
778
|
+
const n = node;
|
|
779
|
+
if (n.type === "InferTypeNode" && n.name === name) return true;
|
|
780
|
+
return Object.values(n).some((v) => bindsInfer(v, name));
|
|
781
|
+
}
|
|
782
|
+
function referenceText(analysis, reference) {
|
|
783
|
+
const name = reference.namespace ? `${reference.namespace}.${reference.base}` : reference.base;
|
|
784
|
+
const args = reference.typeArguments ?? [];
|
|
785
|
+
if (!args.length) return name;
|
|
786
|
+
const resolved = args.map((a) => {
|
|
787
|
+
const t = analysis.types.typeOfTypeNode.get(a);
|
|
788
|
+
return t ? (0, import_luaut_parser4.formatType)(t) : "?";
|
|
789
|
+
});
|
|
790
|
+
return `${name}<${resolved.join(", ")}>`;
|
|
791
|
+
}
|
|
792
|
+
function fieldWithKey(table, key) {
|
|
793
|
+
const fields = table.fields;
|
|
794
|
+
return fields.find((f) => f.type === "TableFieldNamed" && f.key === key);
|
|
795
|
+
}
|
|
796
|
+
function pretty(type) {
|
|
797
|
+
const flat = (0, import_luaut_parser4.formatType)(type);
|
|
798
|
+
if (flat.length <= 80) return flat;
|
|
799
|
+
if (type.kind === "object") {
|
|
800
|
+
const lines = [];
|
|
801
|
+
if (type.indexer) lines.push(` [${(0, import_luaut_parser4.formatType)(type.indexer.key)}]: ${(0, import_luaut_parser4.formatType)(type.indexer.value)},`);
|
|
802
|
+
for (const [name, property] of type.properties) {
|
|
803
|
+
const readonly = property.readonly ? "readonly " : "";
|
|
804
|
+
lines.push(` ${readonly}${name}${property.optional ? "?" : ""}: ${(0, import_luaut_parser4.formatType)(property.type)},`);
|
|
805
|
+
}
|
|
806
|
+
return `{
|
|
807
|
+
${lines.join("\n")}
|
|
808
|
+
}`;
|
|
809
|
+
}
|
|
810
|
+
if (type.kind === "intersection" && type.types.every((t) => t.kind === "function")) {
|
|
811
|
+
return type.types.map(import_luaut_parser4.formatType).join("\n& ");
|
|
812
|
+
}
|
|
813
|
+
return flat;
|
|
267
814
|
}
|
|
268
815
|
function keyword(binding) {
|
|
269
816
|
if (binding.kind === "param" || binding.kind === "self") return "(parameter)";
|
|
@@ -272,11 +819,11 @@ function keyword(binding) {
|
|
|
272
819
|
return binding.isConst ? "const" : "let";
|
|
273
820
|
}
|
|
274
821
|
function code(text) {
|
|
275
|
-
return "```luaut\n" + text + "\n```";
|
|
822
|
+
return "```luaut-hover\n" + text + "\n```";
|
|
276
823
|
}
|
|
277
824
|
|
|
278
825
|
// src/features/navigation.ts
|
|
279
|
-
var
|
|
826
|
+
var import_vscode_languageserver3 = require("vscode-languageserver");
|
|
280
827
|
var NAMING = /* @__PURE__ */ new Set(["Identifier", "IdentifierPattern", "FunctionParameter", "TypedIdentifier"]);
|
|
281
828
|
function bindingAt(analysis, position) {
|
|
282
829
|
const path = pathAt(analysis.program, position, true);
|
|
@@ -310,7 +857,7 @@ function highlights(analysis, position) {
|
|
|
310
857
|
if (!binding) return [];
|
|
311
858
|
return sites(binding).map((node) => ({
|
|
312
859
|
range: toRange(node),
|
|
313
|
-
kind: node === binding.declarationNode ?
|
|
860
|
+
kind: node === binding.declarationNode ? import_vscode_languageserver3.DocumentHighlightKind.Write : import_vscode_languageserver3.DocumentHighlightKind.Read
|
|
314
861
|
}));
|
|
315
862
|
}
|
|
316
863
|
function prepareRename(analysis, position) {
|
|
@@ -318,9 +865,9 @@ function prepareRename(analysis, position) {
|
|
|
318
865
|
if (!binding) return null;
|
|
319
866
|
if (binding.isBuiltin || !binding.declarationNode) return null;
|
|
320
867
|
const path = pathAt(analysis.program, position, true);
|
|
321
|
-
const
|
|
322
|
-
if (!
|
|
323
|
-
return { range: toRange(
|
|
868
|
+
const identifier2 = [...path].reverse().find((n) => !!n.type && NAMING.has(n.type));
|
|
869
|
+
if (!identifier2) return null;
|
|
870
|
+
return { range: toRange(identifier2), placeholder: binding.name };
|
|
324
871
|
}
|
|
325
872
|
function rename(analysis, position, newName) {
|
|
326
873
|
if (!isIdentifier(newName)) return null;
|
|
@@ -335,113 +882,129 @@ function isIdentifier(name) {
|
|
|
335
882
|
}
|
|
336
883
|
|
|
337
884
|
// src/features/completion.ts
|
|
338
|
-
var
|
|
339
|
-
var
|
|
340
|
-
|
|
341
|
-
// src/features/members.ts
|
|
342
|
-
var import_luaut_parser3 = require("luaut-parser");
|
|
343
|
-
function membersOf(type, aliases, seen = /* @__PURE__ */ new Set()) {
|
|
344
|
-
if (!type || seen.has(type)) return [];
|
|
345
|
-
seen.add(type);
|
|
346
|
-
switch (type.kind) {
|
|
347
|
-
case "object": {
|
|
348
|
-
const out = [];
|
|
349
|
-
for (const [name, property] of type.properties) {
|
|
350
|
-
out.push({ name, property, isMethod: takesSelf(property.type) });
|
|
351
|
-
}
|
|
352
|
-
return out;
|
|
353
|
-
}
|
|
354
|
-
case "intersection": {
|
|
355
|
-
const merged = /* @__PURE__ */ new Map();
|
|
356
|
-
for (const part of type.types) {
|
|
357
|
-
for (const member of membersOf(part, aliases, seen)) merged.set(member.name, member);
|
|
358
|
-
}
|
|
359
|
-
return [...merged.values()];
|
|
360
|
-
}
|
|
361
|
-
case "union": {
|
|
362
|
-
const perBranch = type.types.map((part) => membersOf(part, aliases, seen));
|
|
363
|
-
if (!perBranch.length) return [];
|
|
364
|
-
const [first, ...rest] = perBranch;
|
|
365
|
-
return first.filter((member) => rest.every((other) => other.some((m) => m.name === member.name)));
|
|
366
|
-
}
|
|
367
|
-
case "genericRef": {
|
|
368
|
-
const alias = aliases.get(type.name);
|
|
369
|
-
return alias ? membersOf(alias, aliases, seen) : [];
|
|
370
|
-
}
|
|
371
|
-
case "typeParam":
|
|
372
|
-
return membersOf(type.constraint, aliases, seen);
|
|
373
|
-
default:
|
|
374
|
-
return [];
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
function takesSelf(type) {
|
|
378
|
-
for (const signature of signaturesOf(type)) {
|
|
379
|
-
if (signature.params[0]?.name === "self") return true;
|
|
380
|
-
}
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
|
-
function signaturesOf(type, aliases) {
|
|
384
|
-
if (!type) return [];
|
|
385
|
-
if (type.kind === "function") return [type];
|
|
386
|
-
if (type.kind === "intersection") return type.types.flatMap((t) => signaturesOf(t, aliases));
|
|
387
|
-
if (type.kind === "genericRef" && aliases) {
|
|
388
|
-
const alias = aliases.get(type.name);
|
|
389
|
-
return alias ? signaturesOf(alias, aliases) : [];
|
|
390
|
-
}
|
|
391
|
-
return [];
|
|
392
|
-
}
|
|
393
|
-
function signatureLabel(signature) {
|
|
394
|
-
const parameters = signature.params.map((p, i) => {
|
|
395
|
-
const name = p.name ?? `arg${i + 1}`;
|
|
396
|
-
return `${name}${p.optional ? "?" : ""}: ${(0, import_luaut_parser3.formatType)(p.type)}`;
|
|
397
|
-
});
|
|
398
|
-
const generics = signature.typeParams?.length ? `<${signature.typeParams.join(", ")}>` : "";
|
|
399
|
-
const varargs = signature.varargs ? [`...: ${(0, import_luaut_parser3.formatType)(signature.varargs)}`] : [];
|
|
400
|
-
const label = `${generics}(${[...parameters, ...varargs].join(", ")}) -> ${(0, import_luaut_parser3.formatType)(signature.returns)}`;
|
|
401
|
-
return { label, parameters };
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// src/features/completion.ts
|
|
885
|
+
var import_vscode_languageserver4 = require("vscode-languageserver");
|
|
886
|
+
var import_luaut_parser5 = require("luaut-parser");
|
|
405
887
|
var PLACEHOLDER = "__luautCompletion__";
|
|
406
888
|
var IDENTIFIER_CHAR = /[A-Za-z0-9_]/;
|
|
407
889
|
function completion(analyzer, document, position) {
|
|
890
|
+
const inImport = importCompletion(analyzer, document, position);
|
|
891
|
+
if (inImport) return inImport;
|
|
892
|
+
const inString = stringCompletion(analyzer, document, position);
|
|
893
|
+
if (inString) return inString;
|
|
408
894
|
const source = document.getText();
|
|
409
895
|
const offset = document.offsetAt(position);
|
|
410
896
|
let start = offset;
|
|
411
897
|
while (start > 0 && IDENTIFIER_CHAR.test(source[start - 1])) start--;
|
|
412
898
|
let end = offset;
|
|
413
899
|
while (end < source.length && IDENTIFIER_CHAR.test(source[end])) end++;
|
|
414
|
-
const
|
|
900
|
+
const operator = memberOperator(source, start);
|
|
415
901
|
const alreadyCalled = /^\s*\(/.test(source.slice(end));
|
|
416
|
-
const
|
|
417
|
-
const patched = source.slice(0, start) + stand_in + source.slice(end);
|
|
418
|
-
const analysis = analyzer.analyze(document.uri, -1, patched);
|
|
902
|
+
const standIns = operator === ":" ? [alreadyCalled ? PLACEHOLDER : `${PLACEHOLDER}()`] : operator === "." && !alreadyCalled ? [PLACEHOLDER, `${PLACEHOLDER}()`] : [PLACEHOLDER];
|
|
419
903
|
const at = { line: position.line, character: position.character - (offset - start) };
|
|
420
|
-
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
904
|
+
let first;
|
|
905
|
+
for (const standIn of standIns) {
|
|
906
|
+
const patched = source.slice(0, start) + standIn + source.slice(end);
|
|
907
|
+
const analysis = analyzer.analyze(document.uri, -1, patched);
|
|
908
|
+
const path = pathAt(analysis.program, at, true);
|
|
909
|
+
const index = path.findLastIndex(
|
|
910
|
+
(n) => n.type === "Identifier" && n.name === PLACEHOLDER
|
|
911
|
+
);
|
|
912
|
+
const parent = index > 0 ? path[index - 1] : void 0;
|
|
913
|
+
if (parent && (parent.type === "MemberExpression" || parent.type === "MethodCallExpression")) {
|
|
914
|
+
return memberItems(analysis, parent);
|
|
915
|
+
}
|
|
916
|
+
first ??= { analysis, path };
|
|
917
|
+
}
|
|
918
|
+
if (operator || !first) return [];
|
|
919
|
+
if (inTypePosition(first.path)) {
|
|
920
|
+
const named = [...first.analysis.types.aliases.keys()].map((name) => ({
|
|
433
921
|
label: name,
|
|
434
|
-
kind:
|
|
922
|
+
kind: import_vscode_languageserver4.CompletionItemKind.Interface,
|
|
435
923
|
detail: "type"
|
|
436
924
|
}));
|
|
437
|
-
const primitives =
|
|
925
|
+
const primitives = PRIMITIVES2.map((name) => ({
|
|
438
926
|
label: name,
|
|
439
|
-
kind:
|
|
927
|
+
kind: import_vscode_languageserver4.CompletionItemKind.Keyword,
|
|
440
928
|
detail: "type"
|
|
441
929
|
}));
|
|
442
930
|
return [...named, ...primitives];
|
|
443
931
|
}
|
|
444
|
-
return valueItems(analysis, at);
|
|
932
|
+
return valueItems(first.analysis, at);
|
|
933
|
+
}
|
|
934
|
+
function stringCompletion(analyzer, document, position) {
|
|
935
|
+
const analysis = analyzer.get(document);
|
|
936
|
+
const path = pathAt(analysis.program, position, false);
|
|
937
|
+
const literal = [...path].reverse().find((n) => n.type === "StringLiteral");
|
|
938
|
+
if (!literal) return void 0;
|
|
939
|
+
const expected = analysis.types.expectedTypeOf.get(literal);
|
|
940
|
+
const values = stringLiterals(expected, analysis.types.aliases);
|
|
941
|
+
if (!values.length) return [];
|
|
942
|
+
const line = literal.line.start - 1;
|
|
943
|
+
const range = literal.line.start === literal.line.end ? {
|
|
944
|
+
start: { line, character: literal.column.start },
|
|
945
|
+
end: { line, character: literal.column.end - 2 }
|
|
946
|
+
} : void 0;
|
|
947
|
+
return values.map((value) => ({
|
|
948
|
+
label: value,
|
|
949
|
+
kind: import_vscode_languageserver4.CompletionItemKind.Constant,
|
|
950
|
+
...range ? { textEdit: { range, newText: value } } : {}
|
|
951
|
+
}));
|
|
952
|
+
}
|
|
953
|
+
function stringLiterals(type, aliases, seen = /* @__PURE__ */ new Set()) {
|
|
954
|
+
if (!type || seen.has(type)) return [];
|
|
955
|
+
seen.add(type);
|
|
956
|
+
switch (type.kind) {
|
|
957
|
+
case "literal":
|
|
958
|
+
return typeof type.value === "string" ? [type.value] : [];
|
|
959
|
+
case "union":
|
|
960
|
+
return [...new Set(type.types.flatMap((t) => stringLiterals(t, aliases, seen)))];
|
|
961
|
+
case "genericRef": {
|
|
962
|
+
const alias = aliases.get(type.name);
|
|
963
|
+
return alias ? stringLiterals(alias, aliases, seen) : [];
|
|
964
|
+
}
|
|
965
|
+
case "typeParam":
|
|
966
|
+
return stringLiterals(type.constraint, aliases, seen);
|
|
967
|
+
default:
|
|
968
|
+
return [];
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
function memberOperator(source, wordStart) {
|
|
972
|
+
const ch = source[wordStart - 1];
|
|
973
|
+
if (ch === ":") return source[wordStart - 2] === ":" ? void 0 : ":";
|
|
974
|
+
if (ch !== ".") return void 0;
|
|
975
|
+
if (source[wordStart - 2] === ".") return void 0;
|
|
976
|
+
let i = wordStart - 2;
|
|
977
|
+
while (i >= 0 && /[0-9]/.test(source[i])) i--;
|
|
978
|
+
const digits = wordStart - 2 - i;
|
|
979
|
+
if (digits > 0 && (i < 0 || !/[A-Za-z_]/.test(source[i]))) return void 0;
|
|
980
|
+
return ".";
|
|
981
|
+
}
|
|
982
|
+
function memberItems(analysis, access) {
|
|
983
|
+
const object = access.object;
|
|
984
|
+
const type = analysis.types.typeOf.get(object);
|
|
985
|
+
const colon = access.type === "MethodCallExpression";
|
|
986
|
+
if (isStringLike(type)) {
|
|
987
|
+
if (!colon) return [];
|
|
988
|
+
const id = analysis.scopes.globalsByName.get("string");
|
|
989
|
+
const library = id === void 0 ? void 0 : analysis.types.bindingType.get(id);
|
|
990
|
+
return membersOf(library, analysis.types.aliases).filter((member) => signaturesOf(member.property.type).length > 0).map((member) => memberItem(member.name, member.property.type, member.property.readonly));
|
|
991
|
+
}
|
|
992
|
+
return membersOf(type, analysis.types.aliases).filter((member) => colon ? member.isMethod : true).map((member) => memberItem(member.name, member.property.type, member.property.readonly));
|
|
993
|
+
}
|
|
994
|
+
function isStringLike(type) {
|
|
995
|
+
if (!type) return false;
|
|
996
|
+
switch (type.kind) {
|
|
997
|
+
case "primitive":
|
|
998
|
+
return type.name === "string";
|
|
999
|
+
case "literal":
|
|
1000
|
+
return typeof type.value === "string";
|
|
1001
|
+
case "templateLiteral":
|
|
1002
|
+
return true;
|
|
1003
|
+
case "union":
|
|
1004
|
+
return type.types.length > 0 && type.types.every(isStringLike);
|
|
1005
|
+
default:
|
|
1006
|
+
return false;
|
|
1007
|
+
}
|
|
445
1008
|
}
|
|
446
1009
|
function valueItems(analysis, at) {
|
|
447
1010
|
const items = [];
|
|
@@ -455,13 +1018,13 @@ function valueItems(analysis, at) {
|
|
|
455
1018
|
items.push({
|
|
456
1019
|
label: binding.name,
|
|
457
1020
|
kind: kindOf(type, binding.kind),
|
|
458
|
-
detail: type ? (0,
|
|
1021
|
+
detail: type ? (0, import_luaut_parser5.formatType)(type) : void 0,
|
|
459
1022
|
// Locals before globals, and globals before library names.
|
|
460
1023
|
sortText: `${binding.isBuiltin ? 2 : binding.kind === "global" ? 1 : 0}${binding.name}`
|
|
461
1024
|
});
|
|
462
1025
|
}
|
|
463
1026
|
for (const keyword2 of KEYWORDS) {
|
|
464
|
-
items.push({ label: keyword2, kind:
|
|
1027
|
+
items.push({ label: keyword2, kind: import_vscode_languageserver4.CompletionItemKind.Keyword, sortText: `3${keyword2}` });
|
|
465
1028
|
}
|
|
466
1029
|
return items;
|
|
467
1030
|
}
|
|
@@ -470,29 +1033,29 @@ function memberItem(name, type, readonly) {
|
|
|
470
1033
|
if (signatures.length) {
|
|
471
1034
|
return {
|
|
472
1035
|
label: name,
|
|
473
|
-
kind:
|
|
1036
|
+
kind: import_vscode_languageserver4.CompletionItemKind.Method,
|
|
474
1037
|
detail: signatureLabel(signatures[0]).label,
|
|
475
1038
|
insertText: `${name}($0)`,
|
|
476
|
-
insertTextFormat:
|
|
1039
|
+
insertTextFormat: import_vscode_languageserver4.InsertTextFormat.Snippet
|
|
477
1040
|
};
|
|
478
1041
|
}
|
|
479
1042
|
return {
|
|
480
1043
|
label: name,
|
|
481
|
-
kind:
|
|
482
|
-
detail: `${readonly ? "readonly " : ""}${(0,
|
|
1044
|
+
kind: import_vscode_languageserver4.CompletionItemKind.Field,
|
|
1045
|
+
detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser5.formatType)(type)}`
|
|
483
1046
|
};
|
|
484
1047
|
}
|
|
485
1048
|
function kindOf(type, bindingKind) {
|
|
486
|
-
if (type && signaturesOf(type).length) return
|
|
487
|
-
if (bindingKind === "param" || bindingKind === "self") return
|
|
488
|
-
return
|
|
1049
|
+
if (type && signaturesOf(type).length) return import_vscode_languageserver4.CompletionItemKind.Function;
|
|
1050
|
+
if (bindingKind === "param" || bindingKind === "self") return import_vscode_languageserver4.CompletionItemKind.Variable;
|
|
1051
|
+
return import_vscode_languageserver4.CompletionItemKind.Variable;
|
|
489
1052
|
}
|
|
490
1053
|
function inTypePosition(path) {
|
|
491
1054
|
return path.some(
|
|
492
1055
|
(n) => !!n.type && (n.type.endsWith("TypeNode") || n.type === "TypeReference" || n.type === "TypeAliasStatement" || n.type === "ExportTypeAliasStatement")
|
|
493
1056
|
);
|
|
494
1057
|
}
|
|
495
|
-
var
|
|
1058
|
+
var PRIMITIVES2 = [
|
|
496
1059
|
"any",
|
|
497
1060
|
"unknown",
|
|
498
1061
|
"never",
|
|
@@ -608,8 +1171,8 @@ function activeArgument(call, position) {
|
|
|
608
1171
|
}
|
|
609
1172
|
|
|
610
1173
|
// src/features/symbols.ts
|
|
611
|
-
var
|
|
612
|
-
var
|
|
1174
|
+
var import_vscode_languageserver5 = require("vscode-languageserver");
|
|
1175
|
+
var import_luaut_parser6 = require("luaut-parser");
|
|
613
1176
|
function documentSymbols(analysis) {
|
|
614
1177
|
const out = [];
|
|
615
1178
|
walk(analysis.program, (node) => {
|
|
@@ -617,7 +1180,7 @@ function documentSymbols(analysis) {
|
|
|
617
1180
|
case "FunctionDeclaration":
|
|
618
1181
|
case "FunctionDeclarationStatement": {
|
|
619
1182
|
const name = functionName(node);
|
|
620
|
-
if (name) out.push(symbol(name,
|
|
1183
|
+
if (name) out.push(symbol(name, import_vscode_languageserver5.SymbolKind.Function, node, detailOf(analysis, node)));
|
|
621
1184
|
break;
|
|
622
1185
|
}
|
|
623
1186
|
case "TypeAliasStatement":
|
|
@@ -626,14 +1189,14 @@ function documentSymbols(analysis) {
|
|
|
626
1189
|
const name = typeof named === "string" ? named : named?.name;
|
|
627
1190
|
if (name) {
|
|
628
1191
|
const alias = analysis.types.aliases.get(name);
|
|
629
|
-
out.push(symbol(name,
|
|
1192
|
+
out.push(symbol(name, import_vscode_languageserver5.SymbolKind.Interface, node, alias ? (0, import_luaut_parser6.formatType)(alias) : void 0));
|
|
630
1193
|
}
|
|
631
1194
|
break;
|
|
632
1195
|
}
|
|
633
1196
|
case "VariableDeclaration": {
|
|
634
1197
|
for (const target of node.names ?? []) {
|
|
635
1198
|
const name = target.name;
|
|
636
|
-
if (name) out.push(symbol(name,
|
|
1199
|
+
if (name) out.push(symbol(name, import_vscode_languageserver5.SymbolKind.Variable, target));
|
|
637
1200
|
}
|
|
638
1201
|
break;
|
|
639
1202
|
}
|
|
@@ -657,7 +1220,7 @@ function detailOf(analysis, node) {
|
|
|
657
1220
|
if (name && typeof name === "object") {
|
|
658
1221
|
const binding = bindingOfNode(analysis, name);
|
|
659
1222
|
const type = binding && analysis.types.bindingType.get(binding.id);
|
|
660
|
-
if (type) return (0,
|
|
1223
|
+
if (type) return (0, import_luaut_parser6.formatType)(type);
|
|
661
1224
|
}
|
|
662
1225
|
return void 0;
|
|
663
1226
|
}
|
|
@@ -666,10 +1229,243 @@ function symbol(name, kind, node, detail) {
|
|
|
666
1229
|
return { name, kind, detail, range, selectionRange: range };
|
|
667
1230
|
}
|
|
668
1231
|
|
|
1232
|
+
// src/features/semanticTokens.ts
|
|
1233
|
+
var import_luaut_parser7 = require("luaut-parser");
|
|
1234
|
+
var TOKEN_TYPES = [
|
|
1235
|
+
"namespace",
|
|
1236
|
+
"type",
|
|
1237
|
+
"typeParameter",
|
|
1238
|
+
"parameter",
|
|
1239
|
+
"variable",
|
|
1240
|
+
"property",
|
|
1241
|
+
"function",
|
|
1242
|
+
"method",
|
|
1243
|
+
"keyword"
|
|
1244
|
+
];
|
|
1245
|
+
var TOKEN_MODIFIERS = ["declaration", "readonly", "defaultLibrary", "control"];
|
|
1246
|
+
var semanticTokensLegend = {
|
|
1247
|
+
tokenTypes: [...TOKEN_TYPES],
|
|
1248
|
+
tokenModifiers: [...TOKEN_MODIFIERS]
|
|
1249
|
+
};
|
|
1250
|
+
var SOFT_KEYWORDS = /* @__PURE__ */ new Set([
|
|
1251
|
+
"type",
|
|
1252
|
+
"declare",
|
|
1253
|
+
"extends",
|
|
1254
|
+
"keyof",
|
|
1255
|
+
"infer",
|
|
1256
|
+
"readonly",
|
|
1257
|
+
"is",
|
|
1258
|
+
"asserts",
|
|
1259
|
+
"satisfies",
|
|
1260
|
+
"typeof",
|
|
1261
|
+
"default"
|
|
1262
|
+
]);
|
|
1263
|
+
var CONTROL_KEYWORDS = /* @__PURE__ */ new Set(["default"]);
|
|
1264
|
+
var PRIMITIVES3 = /* @__PURE__ */ new Set(["any", "unknown", "never", "nil", "boolean", "number", "string", "thread", "buffer"]);
|
|
1265
|
+
function semanticTokens(analysis) {
|
|
1266
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1267
|
+
const add = (at, length, type, modifiers = []) => {
|
|
1268
|
+
const line = at.line.start - 1;
|
|
1269
|
+
const character = at.column.start - 1;
|
|
1270
|
+
const key = `${line}:${character}`;
|
|
1271
|
+
if (!entries.has(key)) entries.set(key, { line, character, length, type, modifiers });
|
|
1272
|
+
};
|
|
1273
|
+
let tokens = [];
|
|
1274
|
+
try {
|
|
1275
|
+
tokens = (0, import_luaut_parser7.tokenize)(analysis.source);
|
|
1276
|
+
} catch {
|
|
1277
|
+
}
|
|
1278
|
+
const identifiers = tokens.filter((t) => t.type === "Identifier");
|
|
1279
|
+
const ancestors = [];
|
|
1280
|
+
const walk2 = (node) => {
|
|
1281
|
+
classify(analysis, node, ancestors, identifiers, add);
|
|
1282
|
+
ancestors.push(node);
|
|
1283
|
+
for (const child of children(node)) walk2(child);
|
|
1284
|
+
ancestors.pop();
|
|
1285
|
+
};
|
|
1286
|
+
walk2(analysis.program);
|
|
1287
|
+
for (const token of tokens) {
|
|
1288
|
+
const value = token.value;
|
|
1289
|
+
if (token.type !== "Identifier" || typeof value !== "string" || !SOFT_KEYWORDS.has(value)) continue;
|
|
1290
|
+
add(token, value.length, "keyword", CONTROL_KEYWORDS.has(value) ? ["control"] : []);
|
|
1291
|
+
}
|
|
1292
|
+
return { data: encode([...entries.values()]) };
|
|
1293
|
+
}
|
|
1294
|
+
function classify(analysis, spanned, ancestors, identifiers, add) {
|
|
1295
|
+
const node = spanned;
|
|
1296
|
+
switch (node.type) {
|
|
1297
|
+
case "Identifier":
|
|
1298
|
+
identifier(analysis, node, ancestors[ancestors.length - 1], add);
|
|
1299
|
+
return;
|
|
1300
|
+
// Declarations whose node starts at the name.
|
|
1301
|
+
case "IdentifierPattern":
|
|
1302
|
+
case "TypedIdentifier":
|
|
1303
|
+
case "FunctionParameter": {
|
|
1304
|
+
const name = node.name;
|
|
1305
|
+
if (typeof name !== "string" || !name) return;
|
|
1306
|
+
const binding = bindingOfNode(analysis, node);
|
|
1307
|
+
add(node, name.length, valueKind(analysis, binding), modifiersOf(binding, true));
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
case "TypeReference": {
|
|
1311
|
+
const base = node.base;
|
|
1312
|
+
const namespace = node.namespace;
|
|
1313
|
+
const names = firstTokensWithin(identifiers, node, namespace ? 2 : 1);
|
|
1314
|
+
if (namespace && names[0]) add(names[0], namespace.length, "namespace");
|
|
1315
|
+
const baseToken = names[namespace ? 1 : 0];
|
|
1316
|
+
if (!baseToken) return;
|
|
1317
|
+
if (!namespace && typeParameterInScope2(ancestors, base)) {
|
|
1318
|
+
add(baseToken, base.length, "typeParameter");
|
|
1319
|
+
} else {
|
|
1320
|
+
add(baseToken, base.length, "type", PRIMITIVES3.has(base) ? ["defaultLibrary"] : []);
|
|
1321
|
+
}
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
function identifier(analysis, node, parent, add) {
|
|
1327
|
+
const name = node.name;
|
|
1328
|
+
const as = (type, modifiers = []) => add(node, name.length, type, modifiers);
|
|
1329
|
+
const typeOfNode = (n) => analysis.types.typeOfTypeNode.get(n);
|
|
1330
|
+
switch (parent?.type) {
|
|
1331
|
+
case "MemberExpression":
|
|
1332
|
+
if (parent.property === node) {
|
|
1333
|
+
return as(isFunction(analysis.types.typeOf.get(parent)) ? "method" : "property");
|
|
1334
|
+
}
|
|
1335
|
+
break;
|
|
1336
|
+
case "MethodCallExpression":
|
|
1337
|
+
if (parent.method === node) return as("method");
|
|
1338
|
+
break;
|
|
1339
|
+
case "TableExpression":
|
|
1340
|
+
if (isFieldKey(parent, node)) return as("property", ["declaration"]);
|
|
1341
|
+
break;
|
|
1342
|
+
case "TypeAliasStatement":
|
|
1343
|
+
case "ExportTypeAliasStatement":
|
|
1344
|
+
if (parent.name === node) return as("type", ["declaration"]);
|
|
1345
|
+
break;
|
|
1346
|
+
case "DeclareStatement":
|
|
1347
|
+
if (parent.id === node) return as(isFunction(typeOfNode(parent.valueType)) ? "function" : "variable", ["declaration"]);
|
|
1348
|
+
break;
|
|
1349
|
+
case "TableTypeProperty":
|
|
1350
|
+
if (parent.key === node) {
|
|
1351
|
+
return as(
|
|
1352
|
+
isFunction(typeOfNode(parent.valueType)) ? "method" : "property",
|
|
1353
|
+
parent.readonly ? ["declaration", "readonly"] : ["declaration"]
|
|
1354
|
+
);
|
|
1355
|
+
}
|
|
1356
|
+
break;
|
|
1357
|
+
case "FunctionTypeParameter":
|
|
1358
|
+
if (parent.id === node) return as("parameter", ["declaration"]);
|
|
1359
|
+
break;
|
|
1360
|
+
case "GenericTypeParameter":
|
|
1361
|
+
case "InferTypeNode":
|
|
1362
|
+
if (parent.id === node) return as("typeParameter", ["declaration"]);
|
|
1363
|
+
break;
|
|
1364
|
+
case "MappedTypeNode":
|
|
1365
|
+
if (parent.parameterId === node) return as("typeParameter", ["declaration"]);
|
|
1366
|
+
break;
|
|
1367
|
+
case "ImportSpecifier": {
|
|
1368
|
+
const binding2 = bindingOfNode(analysis, node);
|
|
1369
|
+
const value = binding2 && analysis.types.bindingType.get(binding2.id);
|
|
1370
|
+
if (analysis.types.aliases.has(name) && (!value || value.kind === "any")) return as("type", ["declaration"]);
|
|
1371
|
+
break;
|
|
1372
|
+
}
|
|
1373
|
+
case "ExportSpecifier":
|
|
1374
|
+
if (!bindingOfNode(analysis, node) && analysis.types.aliases.has(name)) return as("type");
|
|
1375
|
+
break;
|
|
1376
|
+
case "FunctionName":
|
|
1377
|
+
if (parent.path.includes(node)) return as("property");
|
|
1378
|
+
if (parent.method === node) return as("method", ["declaration"]);
|
|
1379
|
+
break;
|
|
1380
|
+
}
|
|
1381
|
+
const binding = bindingOfNode(analysis, node);
|
|
1382
|
+
if (!binding) return;
|
|
1383
|
+
as(valueKind(analysis, binding), modifiersOf(binding, binding.declarationNode === node));
|
|
1384
|
+
}
|
|
1385
|
+
function valueKind(analysis, binding) {
|
|
1386
|
+
if (!binding) return "variable";
|
|
1387
|
+
if (binding.kind === "param" || binding.kind === "self") return "parameter";
|
|
1388
|
+
return isFunction(analysis.types.bindingType.get(binding.id)) ? "function" : "variable";
|
|
1389
|
+
}
|
|
1390
|
+
function modifiersOf(binding, isDeclaration) {
|
|
1391
|
+
const modifiers = [];
|
|
1392
|
+
if (isDeclaration) modifiers.push("declaration");
|
|
1393
|
+
if (binding?.isConst) modifiers.push("readonly");
|
|
1394
|
+
if (binding?.isBuiltin) modifiers.push("defaultLibrary");
|
|
1395
|
+
return modifiers;
|
|
1396
|
+
}
|
|
1397
|
+
function isFunction(type) {
|
|
1398
|
+
return signaturesOf(type).length > 0;
|
|
1399
|
+
}
|
|
1400
|
+
function isFieldKey(table, key) {
|
|
1401
|
+
const fields = table.fields;
|
|
1402
|
+
return fields.some((f) => f.type === "TableFieldNamed" && f.key === key);
|
|
1403
|
+
}
|
|
1404
|
+
function typeParameterInScope2(ancestors, name) {
|
|
1405
|
+
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
1406
|
+
const a = ancestors[i];
|
|
1407
|
+
if (a.generics?.some((g) => g.name === name)) return true;
|
|
1408
|
+
if (a.type === "MappedTypeNode" && a.parameter === name) return true;
|
|
1409
|
+
if (a.type === "ConditionalTypeNode" && bindsInfer2(a.extendsType, name)) return true;
|
|
1410
|
+
}
|
|
1411
|
+
return false;
|
|
1412
|
+
}
|
|
1413
|
+
function bindsInfer2(node, name) {
|
|
1414
|
+
if (!node || typeof node !== "object") return false;
|
|
1415
|
+
if (Array.isArray(node)) return node.some((n2) => bindsInfer2(n2, name));
|
|
1416
|
+
const n = node;
|
|
1417
|
+
if (n.type === "InferTypeNode" && n.name === name) return true;
|
|
1418
|
+
return Object.values(n).some((v) => bindsInfer2(v, name));
|
|
1419
|
+
}
|
|
1420
|
+
function firstTokensWithin(tokens, node, count) {
|
|
1421
|
+
let lo = 0;
|
|
1422
|
+
let hi = tokens.length;
|
|
1423
|
+
while (lo < hi) {
|
|
1424
|
+
const mid = lo + hi >> 1;
|
|
1425
|
+
const t = tokens[mid];
|
|
1426
|
+
const before = t.line.start < node.line.start || t.line.start === node.line.start && t.column.start < node.column.start;
|
|
1427
|
+
if (before) lo = mid + 1;
|
|
1428
|
+
else hi = mid;
|
|
1429
|
+
}
|
|
1430
|
+
const out = [];
|
|
1431
|
+
for (let i = lo; i < tokens.length && out.length < count; i++) {
|
|
1432
|
+
const t = tokens[i];
|
|
1433
|
+
const after = t.line.start > node.line.end || t.line.start === node.line.end && t.column.start >= node.column.end;
|
|
1434
|
+
if (after) break;
|
|
1435
|
+
out.push(t);
|
|
1436
|
+
}
|
|
1437
|
+
return out;
|
|
1438
|
+
}
|
|
1439
|
+
function encode(entries) {
|
|
1440
|
+
entries.sort((a, b) => a.line - b.line || a.character - b.character);
|
|
1441
|
+
const data = [];
|
|
1442
|
+
let line = 0;
|
|
1443
|
+
let character = 0;
|
|
1444
|
+
for (const e of entries) {
|
|
1445
|
+
const deltaLine = e.line - line;
|
|
1446
|
+
data.push(
|
|
1447
|
+
deltaLine,
|
|
1448
|
+
deltaLine === 0 ? e.character - character : e.character,
|
|
1449
|
+
e.length,
|
|
1450
|
+
TOKEN_TYPES.indexOf(e.type),
|
|
1451
|
+
e.modifiers.reduce((bits, m) => bits | 1 << TOKEN_MODIFIERS.indexOf(m), 0)
|
|
1452
|
+
);
|
|
1453
|
+
line = e.line;
|
|
1454
|
+
character = e.character;
|
|
1455
|
+
}
|
|
1456
|
+
return data;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
669
1459
|
// src/server.ts
|
|
670
1460
|
function createServer(connection, options = {}) {
|
|
671
|
-
const analyzer = new Analyzer(options);
|
|
672
1461
|
const documents = new import_node.TextDocuments(import_vscode_languageserver_textdocument.TextDocument);
|
|
1462
|
+
const analyzer = new Analyzer({
|
|
1463
|
+
...options,
|
|
1464
|
+
openDocument: (path) => documents.all().find((document) => {
|
|
1465
|
+
const documentPath = pathOfUri(document.uri);
|
|
1466
|
+
return documentPath !== void 0 && samePath(documentPath, path);
|
|
1467
|
+
})
|
|
1468
|
+
});
|
|
673
1469
|
connection.onInitialize((_params) => ({
|
|
674
1470
|
capabilities: {
|
|
675
1471
|
textDocumentSync: import_node.TextDocumentSyncKind.Incremental,
|
|
@@ -682,13 +1478,21 @@ function createServer(connection, options = {}) {
|
|
|
682
1478
|
completionProvider: {
|
|
683
1479
|
// `.` and `:` open a member list; the rest of the time
|
|
684
1480
|
// completion is asked for as you type a word.
|
|
685
|
-
|
|
1481
|
+
// plus the characters that start or extend an import path.
|
|
1482
|
+
triggerCharacters: [".", ":", '"', "'", "/"],
|
|
686
1483
|
resolveProvider: false
|
|
687
1484
|
},
|
|
688
|
-
signatureHelpProvider: { triggerCharacters: ["(", ","], retriggerCharacters: [","] }
|
|
1485
|
+
signatureHelpProvider: { triggerCharacters: ["(", ","], retriggerCharacters: [","] },
|
|
1486
|
+
// Colours from the parser, not from patterns: whether a word is a
|
|
1487
|
+
// keyword, a type or a name depends on where it stands.
|
|
1488
|
+
semanticTokensProvider: { legend: semanticTokensLegend, full: true }
|
|
689
1489
|
},
|
|
690
1490
|
serverInfo: { name: "luaut-language-server" }
|
|
691
1491
|
}));
|
|
1492
|
+
connection.languages.semanticTokens.on((p) => {
|
|
1493
|
+
const document = documents.get(p.textDocument.uri);
|
|
1494
|
+
return document ? semanticTokens(analyzer.get(document)) : { data: [] };
|
|
1495
|
+
});
|
|
692
1496
|
const publish = (document) => {
|
|
693
1497
|
void connection.sendDiagnostics({
|
|
694
1498
|
uri: document.uri,
|
|
@@ -697,7 +1501,11 @@ function createServer(connection, options = {}) {
|
|
|
697
1501
|
});
|
|
698
1502
|
};
|
|
699
1503
|
documents.onDidOpen((e) => publish(e.document));
|
|
700
|
-
|
|
1504
|
+
const publishAll = () => {
|
|
1505
|
+
for (const document of documents.all()) publish(document);
|
|
1506
|
+
};
|
|
1507
|
+
documents.onDidChangeContent(publishAll);
|
|
1508
|
+
connection.onDidChangeWatchedFiles(publishAll);
|
|
701
1509
|
documents.onDidClose((e) => {
|
|
702
1510
|
analyzer.forget(e.document.uri);
|
|
703
1511
|
void connection.sendDiagnostics({ uri: e.document.uri, diagnostics: [] });
|
|
@@ -713,7 +1521,11 @@ function createServer(connection, options = {}) {
|
|
|
713
1521
|
));
|
|
714
1522
|
connection.onDefinition((p) => withDocument(
|
|
715
1523
|
p.textDocument.uri,
|
|
716
|
-
(d) =>
|
|
1524
|
+
(d) => {
|
|
1525
|
+
const analysis = analyzer.get(d);
|
|
1526
|
+
const across = importDefinition(analyzer, analysis, p.position);
|
|
1527
|
+
return across !== void 0 ? across : definition(analysis, p.position);
|
|
1528
|
+
},
|
|
717
1529
|
null
|
|
718
1530
|
));
|
|
719
1531
|
connection.onReferences((p) => withDocument(
|
|
@@ -772,20 +1584,28 @@ function startServer(options = {}) {
|
|
|
772
1584
|
diagnostics,
|
|
773
1585
|
documentSymbols,
|
|
774
1586
|
enclosing,
|
|
1587
|
+
exportDeclaration,
|
|
775
1588
|
highlights,
|
|
776
1589
|
hover,
|
|
1590
|
+
importCompletion,
|
|
1591
|
+
importDefinition,
|
|
777
1592
|
membersOf,
|
|
778
1593
|
nodeAt,
|
|
779
1594
|
pathAt,
|
|
1595
|
+
pathOfUri,
|
|
780
1596
|
prepareRename,
|
|
781
1597
|
references,
|
|
782
1598
|
rename,
|
|
1599
|
+
samePath,
|
|
1600
|
+
semanticTokens,
|
|
1601
|
+
semanticTokensLegend,
|
|
783
1602
|
signatureHelp,
|
|
784
1603
|
signatureLabel,
|
|
785
1604
|
signaturesOf,
|
|
786
1605
|
startServer,
|
|
787
1606
|
toPosition,
|
|
788
1607
|
toRange,
|
|
1608
|
+
uriOfPath,
|
|
789
1609
|
walk
|
|
790
1610
|
});
|
|
791
1611
|
//# sourceMappingURL=index.cjs.map
|