luaut-language-server 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -100
- package/dist/chunk-HK7PKBDB.js +1535 -0
- package/dist/chunk-HK7PKBDB.js.map +1 -0
- package/dist/cli.cjs +945 -161
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +961 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +136 -63
- package/dist/index.d.ts +136 -63
- package/dist/index.js +17 -1
- package/package.json +53 -49
- package/dist/chunk-RHII344O.js +0 -733
- package/dist/chunk-RHII344O.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();
|
|
@@ -17,20 +20,62 @@ function collect(statements, into) {
|
|
|
17
20
|
if (statement.type === "DeclareStatement") into.add(statement.name);
|
|
18
21
|
}
|
|
19
22
|
}
|
|
23
|
+
function bindingOfNode(analysis, node) {
|
|
24
|
+
const used = (0, import_luaut_parser.getBinding)(analysis.scopes, node);
|
|
25
|
+
if (used) return used;
|
|
26
|
+
return declarationIndex(analysis).get(node);
|
|
27
|
+
}
|
|
28
|
+
var declarationIndexes = /* @__PURE__ */ new WeakMap();
|
|
29
|
+
function declarationIndex(analysis) {
|
|
30
|
+
let index = declarationIndexes.get(analysis);
|
|
31
|
+
if (!index) {
|
|
32
|
+
index = /* @__PURE__ */ new Map();
|
|
33
|
+
for (const binding of analysis.scopes.bindings.values()) {
|
|
34
|
+
if (binding.declarationNode) index.set(binding.declarationNode, binding);
|
|
35
|
+
}
|
|
36
|
+
declarationIndexes.set(analysis, index);
|
|
37
|
+
}
|
|
38
|
+
return index;
|
|
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 };
|
|
20
59
|
var Analyzer = class {
|
|
21
60
|
libs;
|
|
22
61
|
builtinGlobals;
|
|
62
|
+
openDocument;
|
|
23
63
|
cache = /* @__PURE__ */ new Map();
|
|
64
|
+
/** Imported modules, by path key. */
|
|
65
|
+
modules = /* @__PURE__ */ new Map();
|
|
24
66
|
constructor(options = {}) {
|
|
25
67
|
this.libs = options.libs ?? import_luaut_parser.defaultLibs;
|
|
26
68
|
this.builtinGlobals = globalsOf(this.libs);
|
|
69
|
+
this.openDocument = options.openDocument;
|
|
27
70
|
}
|
|
28
|
-
/** Analyze `document`, reusing the previous result
|
|
29
|
-
*
|
|
71
|
+
/** Analyze `document`, reusing the previous result while neither it nor
|
|
72
|
+
* anything it imports has changed. */
|
|
30
73
|
get(document) {
|
|
31
74
|
const cached = this.cache.get(document.uri);
|
|
32
75
|
const source = document.getText();
|
|
33
|
-
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
|
+
}
|
|
34
79
|
const analysis = this.analyze(document.uri, document.version, source);
|
|
35
80
|
this.cache.set(document.uri, analysis);
|
|
36
81
|
return analysis;
|
|
@@ -38,18 +83,103 @@ var Analyzer = class {
|
|
|
38
83
|
/** Analyze source text that is not a tracked document — used by
|
|
39
84
|
* completion, which analyzes a speculatively edited copy of the file. */
|
|
40
85
|
analyze(uri, version, source) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const types = (0, import_luaut_parser.analyzeTypes)(program, scopes, { libs: this.libs });
|
|
44
|
-
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)] : []));
|
|
45
88
|
}
|
|
46
89
|
forget(uri) {
|
|
47
90
|
this.cache.delete(uri);
|
|
48
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
|
+
}
|
|
49
176
|
};
|
|
50
177
|
|
|
51
|
-
// src/features/
|
|
178
|
+
// src/features/imports.ts
|
|
179
|
+
var import_node_fs2 = require("fs");
|
|
180
|
+
var import_node_path2 = require("path");
|
|
52
181
|
var import_vscode_languageserver = require("vscode-languageserver");
|
|
182
|
+
var import_luaut_parser3 = require("luaut-parser");
|
|
53
183
|
|
|
54
184
|
// src/ast-utils.ts
|
|
55
185
|
function isSpanned(v) {
|
|
@@ -79,16 +209,21 @@ function containsPosition(node, pos, inclusive = false) {
|
|
|
79
209
|
}
|
|
80
210
|
function children(node) {
|
|
81
211
|
const out = [];
|
|
82
|
-
|
|
212
|
+
collect2(node, out);
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
function collect2(container, out) {
|
|
216
|
+
for (const key of Object.keys(container)) {
|
|
83
217
|
if (key === "line" || key === "column") continue;
|
|
84
|
-
const value =
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
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);
|
|
89
222
|
}
|
|
90
223
|
}
|
|
91
|
-
|
|
224
|
+
}
|
|
225
|
+
function isSpanlessNode(v) {
|
|
226
|
+
return !!v && typeof v === "object" && typeof v.type === "string";
|
|
92
227
|
}
|
|
93
228
|
function pathAt(root, pos, inclusive = false) {
|
|
94
229
|
let best;
|
|
@@ -113,14 +248,280 @@ function walk(root, visit, parent) {
|
|
|
113
248
|
for (const child of children(root)) walk(child, visit, root);
|
|
114
249
|
}
|
|
115
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
|
+
|
|
116
516
|
// src/features/diagnostics.ts
|
|
517
|
+
var import_vscode_languageserver2 = require("vscode-languageserver");
|
|
117
518
|
function diagnostics(analysis) {
|
|
118
519
|
const out = [];
|
|
119
520
|
for (const error of analysis.parseErrors) {
|
|
120
521
|
const start = toPosition(error.line, error.column);
|
|
121
522
|
out.push({
|
|
122
523
|
range: { start, end: { line: start.line, character: start.character + 1 } },
|
|
123
|
-
severity:
|
|
524
|
+
severity: import_vscode_languageserver2.DiagnosticSeverity.Error,
|
|
124
525
|
source: "luaut",
|
|
125
526
|
code: "syntax",
|
|
126
527
|
// The parser appends `(line:column)`; the range already says that.
|
|
@@ -130,7 +531,7 @@ function diagnostics(analysis) {
|
|
|
130
531
|
for (const d of analysis.scopes.diagnostics) {
|
|
131
532
|
out.push({
|
|
132
533
|
range: toRange(d.node),
|
|
133
|
-
severity:
|
|
534
|
+
severity: import_vscode_languageserver2.DiagnosticSeverity.Error,
|
|
134
535
|
source: "luaut",
|
|
135
536
|
code: d.kind,
|
|
136
537
|
message: d.message
|
|
@@ -139,7 +540,7 @@ function diagnostics(analysis) {
|
|
|
139
540
|
for (const d of analysis.types.diagnostics) {
|
|
140
541
|
out.push({
|
|
141
542
|
range: toRange(d.node),
|
|
142
|
-
severity:
|
|
543
|
+
severity: import_vscode_languageserver2.DiagnosticSeverity.Error,
|
|
143
544
|
source: "luaut",
|
|
144
545
|
code: "type",
|
|
145
546
|
message: d.message
|
|
@@ -149,46 +550,201 @@ function diagnostics(analysis) {
|
|
|
149
550
|
}
|
|
150
551
|
|
|
151
552
|
// src/features/hover.ts
|
|
152
|
-
var
|
|
553
|
+
var import_luaut_parser4 = require("luaut-parser");
|
|
153
554
|
function hover(analysis, position) {
|
|
154
555
|
const path = pathAt(analysis.program, position, true);
|
|
155
556
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
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]) };
|
|
159
559
|
}
|
|
160
560
|
return null;
|
|
161
561
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
562
|
+
var PRIMITIVES = /* @__PURE__ */ new Set(["any", "unknown", "never", "nil", "boolean", "number", "string", "thread", "buffer"]);
|
|
563
|
+
function describe(analysis, path, index) {
|
|
564
|
+
const { types } = analysis;
|
|
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;
|
|
177
641
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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;
|
|
181
649
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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)}`;
|
|
188
665
|
}
|
|
189
666
|
}
|
|
667
|
+
const annotated = typeOfNode(node);
|
|
668
|
+
if (annotated) return pretty(annotated);
|
|
190
669
|
const type = types.typeOf.get(node);
|
|
191
|
-
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;
|
|
192
748
|
}
|
|
193
749
|
function keyword(binding) {
|
|
194
750
|
if (binding.kind === "param" || binding.kind === "self") return "(parameter)";
|
|
@@ -197,18 +753,18 @@ function keyword(binding) {
|
|
|
197
753
|
return binding.isConst ? "const" : "let";
|
|
198
754
|
}
|
|
199
755
|
function code(text) {
|
|
200
|
-
return "```luaut\n" + text + "\n```";
|
|
756
|
+
return "```luaut-hover\n" + text + "\n```";
|
|
201
757
|
}
|
|
202
758
|
|
|
203
759
|
// src/features/navigation.ts
|
|
204
|
-
var
|
|
205
|
-
var
|
|
760
|
+
var import_vscode_languageserver3 = require("vscode-languageserver");
|
|
761
|
+
var NAMING = /* @__PURE__ */ new Set(["Identifier", "IdentifierPattern", "FunctionParameter", "TypedIdentifier"]);
|
|
206
762
|
function bindingAt(analysis, position) {
|
|
207
763
|
const path = pathAt(analysis.program, position, true);
|
|
208
764
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
209
765
|
const node = path[i];
|
|
210
|
-
if (node.type
|
|
211
|
-
const binding = (
|
|
766
|
+
if (!node.type || !NAMING.has(node.type)) continue;
|
|
767
|
+
const binding = bindingOfNode(analysis, node);
|
|
212
768
|
if (binding) return binding;
|
|
213
769
|
}
|
|
214
770
|
return void 0;
|
|
@@ -235,7 +791,7 @@ function highlights(analysis, position) {
|
|
|
235
791
|
if (!binding) return [];
|
|
236
792
|
return sites(binding).map((node) => ({
|
|
237
793
|
range: toRange(node),
|
|
238
|
-
kind: node === binding.declarationNode ?
|
|
794
|
+
kind: node === binding.declarationNode ? import_vscode_languageserver3.DocumentHighlightKind.Write : import_vscode_languageserver3.DocumentHighlightKind.Read
|
|
239
795
|
}));
|
|
240
796
|
}
|
|
241
797
|
function prepareRename(analysis, position) {
|
|
@@ -243,9 +799,9 @@ function prepareRename(analysis, position) {
|
|
|
243
799
|
if (!binding) return null;
|
|
244
800
|
if (binding.isBuiltin || !binding.declarationNode) return null;
|
|
245
801
|
const path = pathAt(analysis.program, position, true);
|
|
246
|
-
const
|
|
247
|
-
if (!
|
|
248
|
-
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 };
|
|
249
805
|
}
|
|
250
806
|
function rename(analysis, position, newName) {
|
|
251
807
|
if (!isIdentifier(newName)) return null;
|
|
@@ -260,113 +816,90 @@ function isIdentifier(name) {
|
|
|
260
816
|
}
|
|
261
817
|
|
|
262
818
|
// src/features/completion.ts
|
|
263
|
-
var
|
|
819
|
+
var import_vscode_languageserver4 = require("vscode-languageserver");
|
|
264
820
|
var import_luaut_parser5 = require("luaut-parser");
|
|
265
|
-
|
|
266
|
-
// src/features/members.ts
|
|
267
|
-
var import_luaut_parser4 = require("luaut-parser");
|
|
268
|
-
function membersOf(type, aliases, seen = /* @__PURE__ */ new Set()) {
|
|
269
|
-
if (!type || seen.has(type)) return [];
|
|
270
|
-
seen.add(type);
|
|
271
|
-
switch (type.kind) {
|
|
272
|
-
case "object": {
|
|
273
|
-
const out = [];
|
|
274
|
-
for (const [name, property] of type.properties) {
|
|
275
|
-
out.push({ name, property, isMethod: takesSelf(property.type) });
|
|
276
|
-
}
|
|
277
|
-
return out;
|
|
278
|
-
}
|
|
279
|
-
case "intersection": {
|
|
280
|
-
const merged = /* @__PURE__ */ new Map();
|
|
281
|
-
for (const part of type.types) {
|
|
282
|
-
for (const member of membersOf(part, aliases, seen)) merged.set(member.name, member);
|
|
283
|
-
}
|
|
284
|
-
return [...merged.values()];
|
|
285
|
-
}
|
|
286
|
-
case "union": {
|
|
287
|
-
const perBranch = type.types.map((part) => membersOf(part, aliases, seen));
|
|
288
|
-
if (!perBranch.length) return [];
|
|
289
|
-
const [first, ...rest] = perBranch;
|
|
290
|
-
return first.filter((member) => rest.every((other) => other.some((m) => m.name === member.name)));
|
|
291
|
-
}
|
|
292
|
-
case "genericRef": {
|
|
293
|
-
const alias = aliases.get(type.name);
|
|
294
|
-
return alias ? membersOf(alias, aliases, seen) : [];
|
|
295
|
-
}
|
|
296
|
-
case "typeParam":
|
|
297
|
-
return membersOf(type.constraint, aliases, seen);
|
|
298
|
-
default:
|
|
299
|
-
return [];
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
function takesSelf(type) {
|
|
303
|
-
for (const signature of signaturesOf(type)) {
|
|
304
|
-
if (signature.params[0]?.name === "self") return true;
|
|
305
|
-
}
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
function signaturesOf(type, aliases) {
|
|
309
|
-
if (!type) return [];
|
|
310
|
-
if (type.kind === "function") return [type];
|
|
311
|
-
if (type.kind === "intersection") return type.types.flatMap((t) => signaturesOf(t, aliases));
|
|
312
|
-
if (type.kind === "genericRef" && aliases) {
|
|
313
|
-
const alias = aliases.get(type.name);
|
|
314
|
-
return alias ? signaturesOf(alias, aliases) : [];
|
|
315
|
-
}
|
|
316
|
-
return [];
|
|
317
|
-
}
|
|
318
|
-
function signatureLabel(signature) {
|
|
319
|
-
const parameters = signature.params.map((p, i) => {
|
|
320
|
-
const name = p.name ?? `arg${i + 1}`;
|
|
321
|
-
return `${name}${p.optional ? "?" : ""}: ${(0, import_luaut_parser4.formatType)(p.type)}`;
|
|
322
|
-
});
|
|
323
|
-
const generics = signature.typeParams?.length ? `<${signature.typeParams.join(", ")}>` : "";
|
|
324
|
-
const varargs = signature.varargs ? [`...: ${(0, import_luaut_parser4.formatType)(signature.varargs)}`] : [];
|
|
325
|
-
const label = `${generics}(${[...parameters, ...varargs].join(", ")}) -> ${(0, import_luaut_parser4.formatType)(signature.returns)}`;
|
|
326
|
-
return { label, parameters };
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// src/features/completion.ts
|
|
330
821
|
var PLACEHOLDER = "__luautCompletion__";
|
|
331
822
|
var IDENTIFIER_CHAR = /[A-Za-z0-9_]/;
|
|
332
823
|
function completion(analyzer, document, position) {
|
|
824
|
+
const inImport = importCompletion(analyzer, document, position);
|
|
825
|
+
if (inImport) return inImport;
|
|
333
826
|
const source = document.getText();
|
|
334
827
|
const offset = document.offsetAt(position);
|
|
335
828
|
let start = offset;
|
|
336
829
|
while (start > 0 && IDENTIFIER_CHAR.test(source[start - 1])) start--;
|
|
337
830
|
let end = offset;
|
|
338
831
|
while (end < source.length && IDENTIFIER_CHAR.test(source[end])) end++;
|
|
339
|
-
const
|
|
832
|
+
const operator = memberOperator(source, start);
|
|
340
833
|
const alreadyCalled = /^\s*\(/.test(source.slice(end));
|
|
341
|
-
const
|
|
342
|
-
const patched = source.slice(0, start) + stand_in + source.slice(end);
|
|
343
|
-
const analysis = analyzer.analyze(document.uri, -1, patched);
|
|
834
|
+
const standIns = operator === ":" ? [alreadyCalled ? PLACEHOLDER : `${PLACEHOLDER}()`] : operator === "." && !alreadyCalled ? [PLACEHOLDER, `${PLACEHOLDER}()`] : [PLACEHOLDER];
|
|
344
835
|
const at = { line: position.line, character: position.character - (offset - start) };
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
836
|
+
let first;
|
|
837
|
+
for (const standIn of standIns) {
|
|
838
|
+
const patched = source.slice(0, start) + standIn + source.slice(end);
|
|
839
|
+
const analysis = analyzer.analyze(document.uri, -1, patched);
|
|
840
|
+
const path = pathAt(analysis.program, at, true);
|
|
841
|
+
const index = path.findLastIndex(
|
|
842
|
+
(n) => n.type === "Identifier" && n.name === PLACEHOLDER
|
|
843
|
+
);
|
|
844
|
+
const parent = index > 0 ? path[index - 1] : void 0;
|
|
845
|
+
if (parent && (parent.type === "MemberExpression" || parent.type === "MethodCallExpression")) {
|
|
846
|
+
return memberItems(analysis, parent);
|
|
847
|
+
}
|
|
848
|
+
first ??= { analysis, path };
|
|
849
|
+
}
|
|
850
|
+
if (operator || !first) return [];
|
|
851
|
+
if (inTypePosition(first.path)) {
|
|
852
|
+
const named = [...first.analysis.types.aliases.keys()].map((name) => ({
|
|
358
853
|
label: name,
|
|
359
|
-
kind:
|
|
854
|
+
kind: import_vscode_languageserver4.CompletionItemKind.Interface,
|
|
360
855
|
detail: "type"
|
|
361
856
|
}));
|
|
362
|
-
const primitives =
|
|
857
|
+
const primitives = PRIMITIVES2.map((name) => ({
|
|
363
858
|
label: name,
|
|
364
|
-
kind:
|
|
859
|
+
kind: import_vscode_languageserver4.CompletionItemKind.Keyword,
|
|
365
860
|
detail: "type"
|
|
366
861
|
}));
|
|
367
862
|
return [...named, ...primitives];
|
|
368
863
|
}
|
|
369
|
-
return valueItems(analysis, at);
|
|
864
|
+
return valueItems(first.analysis, at);
|
|
865
|
+
}
|
|
866
|
+
function memberOperator(source, wordStart) {
|
|
867
|
+
const ch = source[wordStart - 1];
|
|
868
|
+
if (ch === ":") return source[wordStart - 2] === ":" ? void 0 : ":";
|
|
869
|
+
if (ch !== ".") return void 0;
|
|
870
|
+
if (source[wordStart - 2] === ".") return void 0;
|
|
871
|
+
let i = wordStart - 2;
|
|
872
|
+
while (i >= 0 && /[0-9]/.test(source[i])) i--;
|
|
873
|
+
const digits = wordStart - 2 - i;
|
|
874
|
+
if (digits > 0 && (i < 0 || !/[A-Za-z_]/.test(source[i]))) return void 0;
|
|
875
|
+
return ".";
|
|
876
|
+
}
|
|
877
|
+
function memberItems(analysis, access) {
|
|
878
|
+
const object = access.object;
|
|
879
|
+
const type = analysis.types.typeOf.get(object);
|
|
880
|
+
const colon = access.type === "MethodCallExpression";
|
|
881
|
+
if (isStringLike(type)) {
|
|
882
|
+
if (!colon) return [];
|
|
883
|
+
const id = analysis.scopes.globalsByName.get("string");
|
|
884
|
+
const library = id === void 0 ? void 0 : analysis.types.bindingType.get(id);
|
|
885
|
+
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));
|
|
886
|
+
}
|
|
887
|
+
return membersOf(type, analysis.types.aliases).filter((member) => colon ? member.isMethod : true).map((member) => memberItem(member.name, member.property.type, member.property.readonly));
|
|
888
|
+
}
|
|
889
|
+
function isStringLike(type) {
|
|
890
|
+
if (!type) return false;
|
|
891
|
+
switch (type.kind) {
|
|
892
|
+
case "primitive":
|
|
893
|
+
return type.name === "string";
|
|
894
|
+
case "literal":
|
|
895
|
+
return typeof type.value === "string";
|
|
896
|
+
case "templateLiteral":
|
|
897
|
+
return true;
|
|
898
|
+
case "union":
|
|
899
|
+
return type.types.length > 0 && type.types.every(isStringLike);
|
|
900
|
+
default:
|
|
901
|
+
return false;
|
|
902
|
+
}
|
|
370
903
|
}
|
|
371
904
|
function valueItems(analysis, at) {
|
|
372
905
|
const items = [];
|
|
@@ -386,7 +919,7 @@ function valueItems(analysis, at) {
|
|
|
386
919
|
});
|
|
387
920
|
}
|
|
388
921
|
for (const keyword2 of KEYWORDS) {
|
|
389
|
-
items.push({ label: keyword2, kind:
|
|
922
|
+
items.push({ label: keyword2, kind: import_vscode_languageserver4.CompletionItemKind.Keyword, sortText: `3${keyword2}` });
|
|
390
923
|
}
|
|
391
924
|
return items;
|
|
392
925
|
}
|
|
@@ -395,29 +928,29 @@ function memberItem(name, type, readonly) {
|
|
|
395
928
|
if (signatures.length) {
|
|
396
929
|
return {
|
|
397
930
|
label: name,
|
|
398
|
-
kind:
|
|
931
|
+
kind: import_vscode_languageserver4.CompletionItemKind.Method,
|
|
399
932
|
detail: signatureLabel(signatures[0]).label,
|
|
400
933
|
insertText: `${name}($0)`,
|
|
401
|
-
insertTextFormat:
|
|
934
|
+
insertTextFormat: import_vscode_languageserver4.InsertTextFormat.Snippet
|
|
402
935
|
};
|
|
403
936
|
}
|
|
404
937
|
return {
|
|
405
938
|
label: name,
|
|
406
|
-
kind:
|
|
939
|
+
kind: import_vscode_languageserver4.CompletionItemKind.Field,
|
|
407
940
|
detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser5.formatType)(type)}`
|
|
408
941
|
};
|
|
409
942
|
}
|
|
410
943
|
function kindOf(type, bindingKind) {
|
|
411
|
-
if (type && signaturesOf(type).length) return
|
|
412
|
-
if (bindingKind === "param" || bindingKind === "self") return
|
|
413
|
-
return
|
|
944
|
+
if (type && signaturesOf(type).length) return import_vscode_languageserver4.CompletionItemKind.Function;
|
|
945
|
+
if (bindingKind === "param" || bindingKind === "self") return import_vscode_languageserver4.CompletionItemKind.Variable;
|
|
946
|
+
return import_vscode_languageserver4.CompletionItemKind.Variable;
|
|
414
947
|
}
|
|
415
948
|
function inTypePosition(path) {
|
|
416
949
|
return path.some(
|
|
417
950
|
(n) => !!n.type && (n.type.endsWith("TypeNode") || n.type === "TypeReference" || n.type === "TypeAliasStatement" || n.type === "ExportTypeAliasStatement")
|
|
418
951
|
);
|
|
419
952
|
}
|
|
420
|
-
var
|
|
953
|
+
var PRIMITIVES2 = [
|
|
421
954
|
"any",
|
|
422
955
|
"unknown",
|
|
423
956
|
"never",
|
|
@@ -533,7 +1066,7 @@ function activeArgument(call, position) {
|
|
|
533
1066
|
}
|
|
534
1067
|
|
|
535
1068
|
// src/features/symbols.ts
|
|
536
|
-
var
|
|
1069
|
+
var import_vscode_languageserver5 = require("vscode-languageserver");
|
|
537
1070
|
var import_luaut_parser6 = require("luaut-parser");
|
|
538
1071
|
function documentSymbols(analysis) {
|
|
539
1072
|
const out = [];
|
|
@@ -542,7 +1075,7 @@ function documentSymbols(analysis) {
|
|
|
542
1075
|
case "FunctionDeclaration":
|
|
543
1076
|
case "FunctionDeclarationStatement": {
|
|
544
1077
|
const name = functionName(node);
|
|
545
|
-
if (name) out.push(symbol(name,
|
|
1078
|
+
if (name) out.push(symbol(name, import_vscode_languageserver5.SymbolKind.Function, node, detailOf(analysis, node)));
|
|
546
1079
|
break;
|
|
547
1080
|
}
|
|
548
1081
|
case "TypeAliasStatement":
|
|
@@ -551,14 +1084,14 @@ function documentSymbols(analysis) {
|
|
|
551
1084
|
const name = typeof named === "string" ? named : named?.name;
|
|
552
1085
|
if (name) {
|
|
553
1086
|
const alias = analysis.types.aliases.get(name);
|
|
554
|
-
out.push(symbol(name,
|
|
1087
|
+
out.push(symbol(name, import_vscode_languageserver5.SymbolKind.Interface, node, alias ? (0, import_luaut_parser6.formatType)(alias) : void 0));
|
|
555
1088
|
}
|
|
556
1089
|
break;
|
|
557
1090
|
}
|
|
558
1091
|
case "VariableDeclaration": {
|
|
559
1092
|
for (const target of node.names ?? []) {
|
|
560
1093
|
const name = target.name;
|
|
561
|
-
if (name) out.push(symbol(name,
|
|
1094
|
+
if (name) out.push(symbol(name, import_vscode_languageserver5.SymbolKind.Variable, target));
|
|
562
1095
|
}
|
|
563
1096
|
break;
|
|
564
1097
|
}
|
|
@@ -580,7 +1113,7 @@ function functionName(node) {
|
|
|
580
1113
|
function detailOf(analysis, node) {
|
|
581
1114
|
const name = node.name;
|
|
582
1115
|
if (name && typeof name === "object") {
|
|
583
|
-
const binding = (
|
|
1116
|
+
const binding = bindingOfNode(analysis, name);
|
|
584
1117
|
const type = binding && analysis.types.bindingType.get(binding.id);
|
|
585
1118
|
if (type) return (0, import_luaut_parser6.formatType)(type);
|
|
586
1119
|
}
|
|
@@ -591,10 +1124,245 @@ function symbol(name, kind, node, detail) {
|
|
|
591
1124
|
return { name, kind, detail, range, selectionRange: range };
|
|
592
1125
|
}
|
|
593
1126
|
|
|
1127
|
+
// src/features/semanticTokens.ts
|
|
1128
|
+
var import_luaut_parser7 = require("luaut-parser");
|
|
1129
|
+
var TOKEN_TYPES = [
|
|
1130
|
+
"namespace",
|
|
1131
|
+
"type",
|
|
1132
|
+
"typeParameter",
|
|
1133
|
+
"parameter",
|
|
1134
|
+
"variable",
|
|
1135
|
+
"property",
|
|
1136
|
+
"function",
|
|
1137
|
+
"method",
|
|
1138
|
+
"keyword"
|
|
1139
|
+
];
|
|
1140
|
+
var TOKEN_MODIFIERS = ["declaration", "readonly", "defaultLibrary"];
|
|
1141
|
+
var semanticTokensLegend = {
|
|
1142
|
+
tokenTypes: [...TOKEN_TYPES],
|
|
1143
|
+
tokenModifiers: [...TOKEN_MODIFIERS]
|
|
1144
|
+
};
|
|
1145
|
+
var SOFT_KEYWORDS = /* @__PURE__ */ new Set([
|
|
1146
|
+
"type",
|
|
1147
|
+
"declare",
|
|
1148
|
+
"extends",
|
|
1149
|
+
"keyof",
|
|
1150
|
+
"infer",
|
|
1151
|
+
"readonly",
|
|
1152
|
+
"is",
|
|
1153
|
+
"asserts",
|
|
1154
|
+
"satisfies",
|
|
1155
|
+
"typeof"
|
|
1156
|
+
]);
|
|
1157
|
+
var PRIMITIVES3 = /* @__PURE__ */ new Set(["any", "unknown", "never", "nil", "boolean", "number", "string", "thread", "buffer"]);
|
|
1158
|
+
function semanticTokens(analysis) {
|
|
1159
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1160
|
+
const add = (at, length, type, modifiers = []) => {
|
|
1161
|
+
const line = at.line.start - 1;
|
|
1162
|
+
const character = at.column.start - 1;
|
|
1163
|
+
const key = `${line}:${character}`;
|
|
1164
|
+
if (!entries.has(key)) entries.set(key, { line, character, length, type, modifiers });
|
|
1165
|
+
};
|
|
1166
|
+
let tokens = [];
|
|
1167
|
+
try {
|
|
1168
|
+
tokens = (0, import_luaut_parser7.tokenize)(analysis.source);
|
|
1169
|
+
} catch {
|
|
1170
|
+
}
|
|
1171
|
+
const identifiers = tokens.filter((t) => t.type === "Identifier");
|
|
1172
|
+
const ancestors = [];
|
|
1173
|
+
const walk2 = (node) => {
|
|
1174
|
+
classify(analysis, node, ancestors, identifiers, add);
|
|
1175
|
+
ancestors.push(node);
|
|
1176
|
+
for (const child of children(node)) walk2(child);
|
|
1177
|
+
ancestors.pop();
|
|
1178
|
+
};
|
|
1179
|
+
walk2(analysis.program);
|
|
1180
|
+
for (const token of tokens) {
|
|
1181
|
+
const value = token.value;
|
|
1182
|
+
if (typeof value !== "string") continue;
|
|
1183
|
+
if (token.type === "Keyword" && value !== "true" && value !== "false" && value !== "nil") {
|
|
1184
|
+
add(token, value.length, "keyword");
|
|
1185
|
+
} else if (token.type === "Identifier" && SOFT_KEYWORDS.has(value)) {
|
|
1186
|
+
add(token, value.length, "keyword");
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return { data: encode([...entries.values()]) };
|
|
1190
|
+
}
|
|
1191
|
+
function classify(analysis, spanned, ancestors, identifiers, add) {
|
|
1192
|
+
const node = spanned;
|
|
1193
|
+
switch (node.type) {
|
|
1194
|
+
case "Identifier":
|
|
1195
|
+
identifier(analysis, node, ancestors[ancestors.length - 1], add);
|
|
1196
|
+
return;
|
|
1197
|
+
// Declarations whose node starts at the name.
|
|
1198
|
+
case "IdentifierPattern":
|
|
1199
|
+
case "TypedIdentifier":
|
|
1200
|
+
case "FunctionParameter": {
|
|
1201
|
+
const name = node.name;
|
|
1202
|
+
if (typeof name !== "string" || !name) return;
|
|
1203
|
+
const binding = bindingOfNode(analysis, node);
|
|
1204
|
+
add(node, name.length, valueKind(analysis, binding), modifiersOf(binding, true));
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
case "TypeReference": {
|
|
1208
|
+
const base = node.base;
|
|
1209
|
+
const namespace = node.namespace;
|
|
1210
|
+
const names = firstTokensWithin(identifiers, node, namespace ? 2 : 1);
|
|
1211
|
+
if (namespace && names[0]) add(names[0], namespace.length, "namespace");
|
|
1212
|
+
const baseToken = names[namespace ? 1 : 0];
|
|
1213
|
+
if (!baseToken) return;
|
|
1214
|
+
if (!namespace && typeParameterInScope2(ancestors, base)) {
|
|
1215
|
+
add(baseToken, base.length, "typeParameter");
|
|
1216
|
+
} else {
|
|
1217
|
+
add(baseToken, base.length, "type", PRIMITIVES3.has(base) ? ["defaultLibrary"] : []);
|
|
1218
|
+
}
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
function identifier(analysis, node, parent, add) {
|
|
1224
|
+
const name = node.name;
|
|
1225
|
+
const as = (type, modifiers = []) => add(node, name.length, type, modifiers);
|
|
1226
|
+
const typeOfNode = (n) => analysis.types.typeOfTypeNode.get(n);
|
|
1227
|
+
switch (parent?.type) {
|
|
1228
|
+
case "MemberExpression":
|
|
1229
|
+
if (parent.property === node) {
|
|
1230
|
+
return as(isFunction(analysis.types.typeOf.get(parent)) ? "method" : "property");
|
|
1231
|
+
}
|
|
1232
|
+
break;
|
|
1233
|
+
case "MethodCallExpression":
|
|
1234
|
+
if (parent.method === node) return as("method");
|
|
1235
|
+
break;
|
|
1236
|
+
case "TableExpression":
|
|
1237
|
+
if (isFieldKey(parent, node)) return as("property", ["declaration"]);
|
|
1238
|
+
break;
|
|
1239
|
+
case "TypeAliasStatement":
|
|
1240
|
+
case "ExportTypeAliasStatement":
|
|
1241
|
+
if (parent.name === node) return as("type", ["declaration"]);
|
|
1242
|
+
break;
|
|
1243
|
+
case "DeclareStatement":
|
|
1244
|
+
if (parent.id === node) return as(isFunction(typeOfNode(parent.valueType)) ? "function" : "variable", ["declaration"]);
|
|
1245
|
+
break;
|
|
1246
|
+
case "TableTypeProperty":
|
|
1247
|
+
if (parent.key === node) {
|
|
1248
|
+
return as(
|
|
1249
|
+
isFunction(typeOfNode(parent.valueType)) ? "method" : "property",
|
|
1250
|
+
parent.readonly ? ["declaration", "readonly"] : ["declaration"]
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
break;
|
|
1254
|
+
case "FunctionTypeParameter":
|
|
1255
|
+
if (parent.id === node) return as("parameter", ["declaration"]);
|
|
1256
|
+
break;
|
|
1257
|
+
case "GenericTypeParameter":
|
|
1258
|
+
case "InferTypeNode":
|
|
1259
|
+
if (parent.id === node) return as("typeParameter", ["declaration"]);
|
|
1260
|
+
break;
|
|
1261
|
+
case "MappedTypeNode":
|
|
1262
|
+
if (parent.parameterId === node) return as("typeParameter", ["declaration"]);
|
|
1263
|
+
break;
|
|
1264
|
+
case "ImportSpecifier": {
|
|
1265
|
+
const binding2 = bindingOfNode(analysis, node);
|
|
1266
|
+
const value = binding2 && analysis.types.bindingType.get(binding2.id);
|
|
1267
|
+
if (analysis.types.aliases.has(name) && (!value || value.kind === "any")) return as("type", ["declaration"]);
|
|
1268
|
+
break;
|
|
1269
|
+
}
|
|
1270
|
+
case "ExportSpecifier":
|
|
1271
|
+
if (!bindingOfNode(analysis, node) && analysis.types.aliases.has(name)) return as("type");
|
|
1272
|
+
break;
|
|
1273
|
+
case "FunctionName":
|
|
1274
|
+
if (parent.path.includes(node)) return as("property");
|
|
1275
|
+
if (parent.method === node) return as("method", ["declaration"]);
|
|
1276
|
+
break;
|
|
1277
|
+
}
|
|
1278
|
+
const binding = bindingOfNode(analysis, node);
|
|
1279
|
+
if (!binding) return;
|
|
1280
|
+
as(valueKind(analysis, binding), modifiersOf(binding, binding.declarationNode === node));
|
|
1281
|
+
}
|
|
1282
|
+
function valueKind(analysis, binding) {
|
|
1283
|
+
if (!binding) return "variable";
|
|
1284
|
+
if (binding.kind === "param" || binding.kind === "self") return "parameter";
|
|
1285
|
+
return isFunction(analysis.types.bindingType.get(binding.id)) ? "function" : "variable";
|
|
1286
|
+
}
|
|
1287
|
+
function modifiersOf(binding, isDeclaration) {
|
|
1288
|
+
const modifiers = [];
|
|
1289
|
+
if (isDeclaration) modifiers.push("declaration");
|
|
1290
|
+
if (binding?.isConst) modifiers.push("readonly");
|
|
1291
|
+
if (binding?.isBuiltin) modifiers.push("defaultLibrary");
|
|
1292
|
+
return modifiers;
|
|
1293
|
+
}
|
|
1294
|
+
function isFunction(type) {
|
|
1295
|
+
return signaturesOf(type).length > 0;
|
|
1296
|
+
}
|
|
1297
|
+
function isFieldKey(table, key) {
|
|
1298
|
+
const fields = table.fields;
|
|
1299
|
+
return fields.some((f) => f.type === "TableFieldNamed" && f.key === key);
|
|
1300
|
+
}
|
|
1301
|
+
function typeParameterInScope2(ancestors, name) {
|
|
1302
|
+
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
1303
|
+
const a = ancestors[i];
|
|
1304
|
+
if (a.generics?.some((g) => g.name === name)) return true;
|
|
1305
|
+
if (a.type === "MappedTypeNode" && a.parameter === name) return true;
|
|
1306
|
+
if (a.type === "ConditionalTypeNode" && bindsInfer2(a.extendsType, name)) return true;
|
|
1307
|
+
}
|
|
1308
|
+
return false;
|
|
1309
|
+
}
|
|
1310
|
+
function bindsInfer2(node, name) {
|
|
1311
|
+
if (!node || typeof node !== "object") return false;
|
|
1312
|
+
if (Array.isArray(node)) return node.some((n2) => bindsInfer2(n2, name));
|
|
1313
|
+
const n = node;
|
|
1314
|
+
if (n.type === "InferTypeNode" && n.name === name) return true;
|
|
1315
|
+
return Object.values(n).some((v) => bindsInfer2(v, name));
|
|
1316
|
+
}
|
|
1317
|
+
function firstTokensWithin(tokens, node, count) {
|
|
1318
|
+
let lo = 0;
|
|
1319
|
+
let hi = tokens.length;
|
|
1320
|
+
while (lo < hi) {
|
|
1321
|
+
const mid = lo + hi >> 1;
|
|
1322
|
+
const t = tokens[mid];
|
|
1323
|
+
const before = t.line.start < node.line.start || t.line.start === node.line.start && t.column.start < node.column.start;
|
|
1324
|
+
if (before) lo = mid + 1;
|
|
1325
|
+
else hi = mid;
|
|
1326
|
+
}
|
|
1327
|
+
const out = [];
|
|
1328
|
+
for (let i = lo; i < tokens.length && out.length < count; i++) {
|
|
1329
|
+
const t = tokens[i];
|
|
1330
|
+
const after = t.line.start > node.line.end || t.line.start === node.line.end && t.column.start >= node.column.end;
|
|
1331
|
+
if (after) break;
|
|
1332
|
+
out.push(t);
|
|
1333
|
+
}
|
|
1334
|
+
return out;
|
|
1335
|
+
}
|
|
1336
|
+
function encode(entries) {
|
|
1337
|
+
entries.sort((a, b) => a.line - b.line || a.character - b.character);
|
|
1338
|
+
const data = [];
|
|
1339
|
+
let line = 0;
|
|
1340
|
+
let character = 0;
|
|
1341
|
+
for (const e of entries) {
|
|
1342
|
+
const deltaLine = e.line - line;
|
|
1343
|
+
data.push(
|
|
1344
|
+
deltaLine,
|
|
1345
|
+
deltaLine === 0 ? e.character - character : e.character,
|
|
1346
|
+
e.length,
|
|
1347
|
+
TOKEN_TYPES.indexOf(e.type),
|
|
1348
|
+
e.modifiers.reduce((bits, m) => bits | 1 << TOKEN_MODIFIERS.indexOf(m), 0)
|
|
1349
|
+
);
|
|
1350
|
+
line = e.line;
|
|
1351
|
+
character = e.character;
|
|
1352
|
+
}
|
|
1353
|
+
return data;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
594
1356
|
// src/server.ts
|
|
595
1357
|
function createServer(connection, options = {}) {
|
|
596
|
-
const analyzer = new Analyzer(options);
|
|
597
1358
|
const documents = new import_node.TextDocuments(import_vscode_languageserver_textdocument.TextDocument);
|
|
1359
|
+
const analyzer = new Analyzer({
|
|
1360
|
+
...options,
|
|
1361
|
+
openDocument: (path) => documents.all().find((document) => {
|
|
1362
|
+
const documentPath = pathOfUri(document.uri);
|
|
1363
|
+
return documentPath !== void 0 && samePath(documentPath, path);
|
|
1364
|
+
})
|
|
1365
|
+
});
|
|
598
1366
|
connection.onInitialize((_params) => ({
|
|
599
1367
|
capabilities: {
|
|
600
1368
|
textDocumentSync: import_node.TextDocumentSyncKind.Incremental,
|
|
@@ -607,13 +1375,21 @@ function createServer(connection, options = {}) {
|
|
|
607
1375
|
completionProvider: {
|
|
608
1376
|
// `.` and `:` open a member list; the rest of the time
|
|
609
1377
|
// completion is asked for as you type a word.
|
|
610
|
-
|
|
1378
|
+
// plus the characters that start or extend an import path.
|
|
1379
|
+
triggerCharacters: [".", ":", '"', "'", "/"],
|
|
611
1380
|
resolveProvider: false
|
|
612
1381
|
},
|
|
613
|
-
signatureHelpProvider: { triggerCharacters: ["(", ","], retriggerCharacters: [","] }
|
|
1382
|
+
signatureHelpProvider: { triggerCharacters: ["(", ","], retriggerCharacters: [","] },
|
|
1383
|
+
// Colours from the parser, not from patterns: whether a word is a
|
|
1384
|
+
// keyword, a type or a name depends on where it stands.
|
|
1385
|
+
semanticTokensProvider: { legend: semanticTokensLegend, full: true }
|
|
614
1386
|
},
|
|
615
1387
|
serverInfo: { name: "luaut-language-server" }
|
|
616
1388
|
}));
|
|
1389
|
+
connection.languages.semanticTokens.on((p) => {
|
|
1390
|
+
const document = documents.get(p.textDocument.uri);
|
|
1391
|
+
return document ? semanticTokens(analyzer.get(document)) : { data: [] };
|
|
1392
|
+
});
|
|
617
1393
|
const publish = (document) => {
|
|
618
1394
|
void connection.sendDiagnostics({
|
|
619
1395
|
uri: document.uri,
|
|
@@ -622,7 +1398,11 @@ function createServer(connection, options = {}) {
|
|
|
622
1398
|
});
|
|
623
1399
|
};
|
|
624
1400
|
documents.onDidOpen((e) => publish(e.document));
|
|
625
|
-
|
|
1401
|
+
const publishAll = () => {
|
|
1402
|
+
for (const document of documents.all()) publish(document);
|
|
1403
|
+
};
|
|
1404
|
+
documents.onDidChangeContent(publishAll);
|
|
1405
|
+
connection.onDidChangeWatchedFiles(publishAll);
|
|
626
1406
|
documents.onDidClose((e) => {
|
|
627
1407
|
analyzer.forget(e.document.uri);
|
|
628
1408
|
void connection.sendDiagnostics({ uri: e.document.uri, diagnostics: [] });
|
|
@@ -638,7 +1418,11 @@ function createServer(connection, options = {}) {
|
|
|
638
1418
|
));
|
|
639
1419
|
connection.onDefinition((p) => withDocument(
|
|
640
1420
|
p.textDocument.uri,
|
|
641
|
-
(d) =>
|
|
1421
|
+
(d) => {
|
|
1422
|
+
const analysis = analyzer.get(d);
|
|
1423
|
+
const across = importDefinition(analyzer, analysis, p.position);
|
|
1424
|
+
return across !== void 0 ? across : definition(analysis, p.position);
|
|
1425
|
+
},
|
|
642
1426
|
null
|
|
643
1427
|
));
|
|
644
1428
|
connection.onReferences((p) => withDocument(
|