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