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