luaut-language-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -0
- package/dist/chunk-RHII344O.js +733 -0
- package/dist/chunk-RHII344O.js.map +1 -0
- package/dist/cli.cjs +691 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +774 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +183 -0
- package/dist/index.d.ts +183 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Analyzer: () => Analyzer,
|
|
24
|
+
bindingAt: () => bindingAt,
|
|
25
|
+
children: () => children,
|
|
26
|
+
completion: () => completion,
|
|
27
|
+
containsPosition: () => containsPosition,
|
|
28
|
+
createServer: () => createServer,
|
|
29
|
+
definition: () => definition,
|
|
30
|
+
diagnostics: () => diagnostics,
|
|
31
|
+
documentSymbols: () => documentSymbols,
|
|
32
|
+
enclosing: () => enclosing,
|
|
33
|
+
highlights: () => highlights,
|
|
34
|
+
hover: () => hover,
|
|
35
|
+
membersOf: () => membersOf,
|
|
36
|
+
nodeAt: () => nodeAt,
|
|
37
|
+
pathAt: () => pathAt,
|
|
38
|
+
prepareRename: () => prepareRename,
|
|
39
|
+
references: () => references,
|
|
40
|
+
rename: () => rename,
|
|
41
|
+
signatureHelp: () => signatureHelp,
|
|
42
|
+
signatureLabel: () => signatureLabel,
|
|
43
|
+
signaturesOf: () => signaturesOf,
|
|
44
|
+
startServer: () => startServer,
|
|
45
|
+
toPosition: () => toPosition,
|
|
46
|
+
toRange: () => toRange,
|
|
47
|
+
walk: () => walk
|
|
48
|
+
});
|
|
49
|
+
module.exports = __toCommonJS(index_exports);
|
|
50
|
+
|
|
51
|
+
// src/server.ts
|
|
52
|
+
var import_node = require("vscode-languageserver/node");
|
|
53
|
+
var import_vscode_languageserver_textdocument = require("vscode-languageserver-textdocument");
|
|
54
|
+
|
|
55
|
+
// src/analysis.ts
|
|
56
|
+
var import_luaut_parser = require("luaut-parser");
|
|
57
|
+
function globalsOf(libs) {
|
|
58
|
+
const names = /* @__PURE__ */ new Set();
|
|
59
|
+
for (const lib of libs) collect(lib.body.statements, names);
|
|
60
|
+
return [...names];
|
|
61
|
+
}
|
|
62
|
+
function collect(statements, into) {
|
|
63
|
+
for (const statement of statements) {
|
|
64
|
+
if (statement.type === "DeclareStatement") into.add(statement.name);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
var Analyzer = class {
|
|
68
|
+
libs;
|
|
69
|
+
builtinGlobals;
|
|
70
|
+
cache = /* @__PURE__ */ new Map();
|
|
71
|
+
constructor(options = {}) {
|
|
72
|
+
this.libs = options.libs ?? import_luaut_parser.defaultLibs;
|
|
73
|
+
this.builtinGlobals = globalsOf(this.libs);
|
|
74
|
+
}
|
|
75
|
+
/** Analyze `document`, reusing the previous result if its version is
|
|
76
|
+
* unchanged. */
|
|
77
|
+
get(document) {
|
|
78
|
+
const cached = this.cache.get(document.uri);
|
|
79
|
+
const source = document.getText();
|
|
80
|
+
if (cached && cached.version === document.version && cached.source === source) return cached;
|
|
81
|
+
const analysis = this.analyze(document.uri, document.version, source);
|
|
82
|
+
this.cache.set(document.uri, analysis);
|
|
83
|
+
return analysis;
|
|
84
|
+
}
|
|
85
|
+
/** Analyze source text that is not a tracked document — used by
|
|
86
|
+
* completion, which analyzes a speculatively edited copy of the file. */
|
|
87
|
+
analyze(uri, version, source) {
|
|
88
|
+
const { program, errors } = (0, import_luaut_parser.parseWithRecovery)(source);
|
|
89
|
+
const scopes = (0, import_luaut_parser.analyzeScopes)(program, { builtinGlobals: this.builtinGlobals });
|
|
90
|
+
const types = (0, import_luaut_parser.analyzeTypes)(program, scopes, { libs: this.libs });
|
|
91
|
+
return { uri, version, source, program, parseErrors: errors, scopes, types };
|
|
92
|
+
}
|
|
93
|
+
forget(uri) {
|
|
94
|
+
this.cache.delete(uri);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/features/diagnostics.ts
|
|
99
|
+
var import_vscode_languageserver = require("vscode-languageserver");
|
|
100
|
+
|
|
101
|
+
// src/ast-utils.ts
|
|
102
|
+
function isSpanned(v) {
|
|
103
|
+
if (!v || typeof v !== "object") return false;
|
|
104
|
+
const n = v;
|
|
105
|
+
return typeof n.line === "object" && n.line !== null && typeof n.column === "object" && n.column !== null;
|
|
106
|
+
}
|
|
107
|
+
function toRange(node) {
|
|
108
|
+
return {
|
|
109
|
+
start: { line: node.line.start - 1, character: node.column.start - 1 },
|
|
110
|
+
end: { line: node.line.end - 1, character: node.column.end - 1 }
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function toPosition(line, column) {
|
|
114
|
+
return { line: line - 1, character: column - 1 };
|
|
115
|
+
}
|
|
116
|
+
function containsPosition(node, pos, inclusive = false) {
|
|
117
|
+
const startLine = node.line.start - 1;
|
|
118
|
+
const endLine = node.line.end - 1;
|
|
119
|
+
if (pos.line < startLine || pos.line > endLine) return false;
|
|
120
|
+
if (pos.line === startLine && pos.character < node.column.start - 1) return false;
|
|
121
|
+
if (pos.line === endLine) {
|
|
122
|
+
const end = node.column.end - 1;
|
|
123
|
+
if (inclusive ? pos.character > end : pos.character >= end) return false;
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
function children(node) {
|
|
128
|
+
const out = [];
|
|
129
|
+
for (const key of Object.keys(node)) {
|
|
130
|
+
if (key === "line" || key === "column") continue;
|
|
131
|
+
const value = node[key];
|
|
132
|
+
if (Array.isArray(value)) {
|
|
133
|
+
for (const item of value) if (isSpanned(item)) out.push(item);
|
|
134
|
+
} else if (isSpanned(value)) {
|
|
135
|
+
out.push(value);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
function pathAt(root, pos, inclusive = false) {
|
|
141
|
+
let best;
|
|
142
|
+
const descend = (node, ancestors) => {
|
|
143
|
+
const here = [...ancestors, node];
|
|
144
|
+
if (containsPosition(node, pos, inclusive)) {
|
|
145
|
+
const incumbent = best?.[best.length - 1];
|
|
146
|
+
if (!incumbent || spanLength(node) < spanLength(incumbent) || spanLength(node) === spanLength(incumbent) && here.length > best.length) {
|
|
147
|
+
best = here;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
for (const child of children(node)) descend(child, here);
|
|
151
|
+
};
|
|
152
|
+
descend(root, []);
|
|
153
|
+
return best ?? [];
|
|
154
|
+
}
|
|
155
|
+
function nodeAt(root, pos, inclusive = false) {
|
|
156
|
+
const path = pathAt(root, pos, inclusive);
|
|
157
|
+
return path[path.length - 1];
|
|
158
|
+
}
|
|
159
|
+
function enclosing(root, pos, types, inclusive = false) {
|
|
160
|
+
const path = pathAt(root, pos, inclusive);
|
|
161
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
162
|
+
if (path[i].type && types.includes(path[i].type)) return path[i];
|
|
163
|
+
}
|
|
164
|
+
return void 0;
|
|
165
|
+
}
|
|
166
|
+
function spanLength(node) {
|
|
167
|
+
return (node.line.end - node.line.start) * 1e4 + (node.column.end - node.column.start);
|
|
168
|
+
}
|
|
169
|
+
function walk(root, visit, parent) {
|
|
170
|
+
visit(root, parent);
|
|
171
|
+
for (const child of children(root)) walk(child, visit, root);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/features/diagnostics.ts
|
|
175
|
+
function diagnostics(analysis) {
|
|
176
|
+
const out = [];
|
|
177
|
+
for (const error of analysis.parseErrors) {
|
|
178
|
+
const start = toPosition(error.line, error.column);
|
|
179
|
+
out.push({
|
|
180
|
+
range: { start, end: { line: start.line, character: start.character + 1 } },
|
|
181
|
+
severity: import_vscode_languageserver.DiagnosticSeverity.Error,
|
|
182
|
+
source: "luaut",
|
|
183
|
+
code: "syntax",
|
|
184
|
+
// The parser appends `(line:column)`; the range already says that.
|
|
185
|
+
message: error.message.replace(/\s*\(\d+:\d+\)$/, "")
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
for (const d of analysis.scopes.diagnostics) {
|
|
189
|
+
out.push({
|
|
190
|
+
range: toRange(d.node),
|
|
191
|
+
severity: import_vscode_languageserver.DiagnosticSeverity.Error,
|
|
192
|
+
source: "luaut",
|
|
193
|
+
code: d.kind,
|
|
194
|
+
message: d.message
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
for (const d of analysis.types.diagnostics) {
|
|
198
|
+
out.push({
|
|
199
|
+
range: toRange(d.node),
|
|
200
|
+
severity: import_vscode_languageserver.DiagnosticSeverity.Error,
|
|
201
|
+
source: "luaut",
|
|
202
|
+
code: "type",
|
|
203
|
+
message: d.message
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/features/hover.ts
|
|
210
|
+
var import_luaut_parser2 = require("luaut-parser");
|
|
211
|
+
function hover(analysis, position) {
|
|
212
|
+
const path = pathAt(analysis.program, position, true);
|
|
213
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
214
|
+
const node = path[i];
|
|
215
|
+
const found = describe(analysis, node, path[i - 1]);
|
|
216
|
+
if (found) return { contents: { kind: "markdown", value: code(found) }, range: toRange(node) };
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
function describe(analysis, node, parent) {
|
|
221
|
+
const { types, scopes } = analysis;
|
|
222
|
+
if (node.type === "TypeAliasStatement" || node.type === "ExportTypeAliasStatement") {
|
|
223
|
+
const name = node.name;
|
|
224
|
+
const alias = types.aliases.get(name);
|
|
225
|
+
if (alias) return `type ${name} = ${(0, import_luaut_parser2.formatType)(alias)}`;
|
|
226
|
+
}
|
|
227
|
+
if (node.type === "Identifier") {
|
|
228
|
+
const identifier = node;
|
|
229
|
+
const narrowed = types.narrowedTypeOf.get(identifier);
|
|
230
|
+
if (narrowed) return `${identifier.name}: ${(0, import_luaut_parser2.formatType)(narrowed)}`;
|
|
231
|
+
const binding = (0, import_luaut_parser2.getBinding)(scopes, identifier);
|
|
232
|
+
if (binding) {
|
|
233
|
+
const type2 = types.bindingType.get(binding.id);
|
|
234
|
+
if (type2) return `${keyword(binding)} ${binding.name}: ${(0, import_luaut_parser2.formatType)(type2)}`;
|
|
235
|
+
}
|
|
236
|
+
if (parent && (parent.type === "MemberExpression" || parent.type === "MethodCallExpression")) {
|
|
237
|
+
const type2 = types.typeOf.get(parent);
|
|
238
|
+
if (type2) return `${identifier.name}: ${(0, import_luaut_parser2.formatType)(type2)}`;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (node.type === "IdentifierPattern") {
|
|
242
|
+
const binding = (0, import_luaut_parser2.getBinding)(scopes, node);
|
|
243
|
+
if (binding) {
|
|
244
|
+
const type2 = types.bindingType.get(binding.id);
|
|
245
|
+
if (type2) return `${keyword(binding)} ${binding.name}: ${(0, import_luaut_parser2.formatType)(type2)}`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const type = types.typeOf.get(node);
|
|
249
|
+
return type ? (0, import_luaut_parser2.formatType)(type) : void 0;
|
|
250
|
+
}
|
|
251
|
+
function keyword(binding) {
|
|
252
|
+
if (binding.kind === "param" || binding.kind === "self") return "(parameter)";
|
|
253
|
+
if (binding.kind === "global") return "(global)";
|
|
254
|
+
if (binding.kind.startsWith("for-")) return "(loop variable)";
|
|
255
|
+
return binding.isConst ? "const" : "let";
|
|
256
|
+
}
|
|
257
|
+
function code(text) {
|
|
258
|
+
return "```luaut\n" + text + "\n```";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/features/navigation.ts
|
|
262
|
+
var import_vscode_languageserver2 = require("vscode-languageserver");
|
|
263
|
+
var import_luaut_parser3 = require("luaut-parser");
|
|
264
|
+
function bindingAt(analysis, position) {
|
|
265
|
+
const path = pathAt(analysis.program, position, true);
|
|
266
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
267
|
+
const node = path[i];
|
|
268
|
+
if (node.type !== "Identifier" && node.type !== "IdentifierPattern") continue;
|
|
269
|
+
const binding = (0, import_luaut_parser3.getBinding)(analysis.scopes, node);
|
|
270
|
+
if (binding) return binding;
|
|
271
|
+
}
|
|
272
|
+
return void 0;
|
|
273
|
+
}
|
|
274
|
+
function sites(binding) {
|
|
275
|
+
const out = [];
|
|
276
|
+
if (binding.declarationNode) out.push(binding.declarationNode);
|
|
277
|
+
out.push(...binding.references);
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
function definition(analysis, position) {
|
|
281
|
+
const binding = bindingAt(analysis, position);
|
|
282
|
+
if (!binding?.declarationNode) return null;
|
|
283
|
+
return { uri: analysis.uri, range: toRange(binding.declarationNode) };
|
|
284
|
+
}
|
|
285
|
+
function references(analysis, position, includeDeclaration) {
|
|
286
|
+
const binding = bindingAt(analysis, position);
|
|
287
|
+
if (!binding) return [];
|
|
288
|
+
const nodes = includeDeclaration ? sites(binding) : binding.references;
|
|
289
|
+
return nodes.map((node) => ({ uri: analysis.uri, range: toRange(node) }));
|
|
290
|
+
}
|
|
291
|
+
function highlights(analysis, position) {
|
|
292
|
+
const binding = bindingAt(analysis, position);
|
|
293
|
+
if (!binding) return [];
|
|
294
|
+
return sites(binding).map((node) => ({
|
|
295
|
+
range: toRange(node),
|
|
296
|
+
kind: node === binding.declarationNode ? import_vscode_languageserver2.DocumentHighlightKind.Write : import_vscode_languageserver2.DocumentHighlightKind.Read
|
|
297
|
+
}));
|
|
298
|
+
}
|
|
299
|
+
function prepareRename(analysis, position) {
|
|
300
|
+
const binding = bindingAt(analysis, position);
|
|
301
|
+
if (!binding) return null;
|
|
302
|
+
if (binding.isBuiltin || !binding.declarationNode) return null;
|
|
303
|
+
const path = pathAt(analysis.program, position, true);
|
|
304
|
+
const identifier = [...path].reverse().find((n) => n.type === "Identifier" || n.type === "IdentifierPattern");
|
|
305
|
+
if (!identifier) return null;
|
|
306
|
+
return { range: toRange(identifier), placeholder: binding.name };
|
|
307
|
+
}
|
|
308
|
+
function rename(analysis, position, newName) {
|
|
309
|
+
if (!isIdentifier(newName)) return null;
|
|
310
|
+
const binding = bindingAt(analysis, position);
|
|
311
|
+
if (!binding || binding.isBuiltin || !binding.declarationNode) return null;
|
|
312
|
+
const edits = sites(binding).map((node) => ({ range: toRange(node), newText: newName }));
|
|
313
|
+
return { changes: { [analysis.uri]: edits } };
|
|
314
|
+
}
|
|
315
|
+
var IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
316
|
+
function isIdentifier(name) {
|
|
317
|
+
return IDENTIFIER.test(name);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/features/completion.ts
|
|
321
|
+
var import_vscode_languageserver3 = require("vscode-languageserver");
|
|
322
|
+
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
|
+
var PLACEHOLDER = "__luautCompletion__";
|
|
389
|
+
var IDENTIFIER_CHAR = /[A-Za-z0-9_]/;
|
|
390
|
+
function completion(analyzer, document, position) {
|
|
391
|
+
const source = document.getText();
|
|
392
|
+
const offset = document.offsetAt(position);
|
|
393
|
+
let start = offset;
|
|
394
|
+
while (start > 0 && IDENTIFIER_CHAR.test(source[start - 1])) start--;
|
|
395
|
+
let end = offset;
|
|
396
|
+
while (end < source.length && IDENTIFIER_CHAR.test(source[end])) end++;
|
|
397
|
+
const afterColon = source[start - 1] === ":";
|
|
398
|
+
const alreadyCalled = /^\s*\(/.test(source.slice(end));
|
|
399
|
+
const stand_in = afterColon && !alreadyCalled ? `${PLACEHOLDER}()` : PLACEHOLDER;
|
|
400
|
+
const patched = source.slice(0, start) + stand_in + source.slice(end);
|
|
401
|
+
const analysis = analyzer.analyze(document.uri, -1, patched);
|
|
402
|
+
const at = { line: position.line, character: position.character - (offset - start) };
|
|
403
|
+
const path = pathAt(analysis.program, at, true);
|
|
404
|
+
const placeholder = [...path].reverse().find(
|
|
405
|
+
(n) => n.type === "Identifier" && n.name === PLACEHOLDER
|
|
406
|
+
);
|
|
407
|
+
const parent = placeholder ? path[path.indexOf(placeholder) - 1] : path[path.length - 1];
|
|
408
|
+
if (parent && (parent.type === "MemberExpression" || parent.type === "MethodCallExpression")) {
|
|
409
|
+
const object = parent.object;
|
|
410
|
+
const type = analysis.types.typeOf.get(object);
|
|
411
|
+
const wantMethods = parent.type === "MethodCallExpression";
|
|
412
|
+
return membersOf(type, analysis.types.aliases).filter((member) => wantMethods ? member.isMethod : true).map((member) => memberItem(member.name, member.property.type, member.property.readonly));
|
|
413
|
+
}
|
|
414
|
+
if (inTypePosition(path)) {
|
|
415
|
+
const named = [...analysis.types.aliases.keys()].map((name) => ({
|
|
416
|
+
label: name,
|
|
417
|
+
kind: import_vscode_languageserver3.CompletionItemKind.Interface,
|
|
418
|
+
detail: "type"
|
|
419
|
+
}));
|
|
420
|
+
const primitives = PRIMITIVES.map((name) => ({
|
|
421
|
+
label: name,
|
|
422
|
+
kind: import_vscode_languageserver3.CompletionItemKind.Keyword,
|
|
423
|
+
detail: "type"
|
|
424
|
+
}));
|
|
425
|
+
return [...named, ...primitives];
|
|
426
|
+
}
|
|
427
|
+
return valueItems(analysis, at);
|
|
428
|
+
}
|
|
429
|
+
function valueItems(analysis, at) {
|
|
430
|
+
const items = [];
|
|
431
|
+
const seen = /* @__PURE__ */ new Set();
|
|
432
|
+
for (const binding of analysis.scopes.bindings.values()) {
|
|
433
|
+
if (binding.name === PLACEHOLDER || seen.has(binding.name)) continue;
|
|
434
|
+
const declaration = binding.declarationNode;
|
|
435
|
+
if (declaration && declaration.line.start - 1 > at.line) continue;
|
|
436
|
+
seen.add(binding.name);
|
|
437
|
+
const type = analysis.types.bindingType.get(binding.id);
|
|
438
|
+
items.push({
|
|
439
|
+
label: binding.name,
|
|
440
|
+
kind: kindOf(type, binding.kind),
|
|
441
|
+
detail: type ? (0, import_luaut_parser5.formatType)(type) : void 0,
|
|
442
|
+
// Locals before globals, and globals before library names.
|
|
443
|
+
sortText: `${binding.isBuiltin ? 2 : binding.kind === "global" ? 1 : 0}${binding.name}`
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
for (const keyword2 of KEYWORDS) {
|
|
447
|
+
items.push({ label: keyword2, kind: import_vscode_languageserver3.CompletionItemKind.Keyword, sortText: `3${keyword2}` });
|
|
448
|
+
}
|
|
449
|
+
return items;
|
|
450
|
+
}
|
|
451
|
+
function memberItem(name, type, readonly) {
|
|
452
|
+
const signatures = signaturesOf(type);
|
|
453
|
+
if (signatures.length) {
|
|
454
|
+
return {
|
|
455
|
+
label: name,
|
|
456
|
+
kind: import_vscode_languageserver3.CompletionItemKind.Method,
|
|
457
|
+
detail: signatureLabel(signatures[0]).label,
|
|
458
|
+
insertText: `${name}($0)`,
|
|
459
|
+
insertTextFormat: import_vscode_languageserver3.InsertTextFormat.Snippet
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
return {
|
|
463
|
+
label: name,
|
|
464
|
+
kind: import_vscode_languageserver3.CompletionItemKind.Field,
|
|
465
|
+
detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser5.formatType)(type)}`
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function kindOf(type, bindingKind) {
|
|
469
|
+
if (type && signaturesOf(type).length) return import_vscode_languageserver3.CompletionItemKind.Function;
|
|
470
|
+
if (bindingKind === "param" || bindingKind === "self") return import_vscode_languageserver3.CompletionItemKind.Variable;
|
|
471
|
+
return import_vscode_languageserver3.CompletionItemKind.Variable;
|
|
472
|
+
}
|
|
473
|
+
function inTypePosition(path) {
|
|
474
|
+
return path.some(
|
|
475
|
+
(n) => !!n.type && (n.type.endsWith("TypeNode") || n.type === "TypeReference" || n.type === "TypeAliasStatement" || n.type === "ExportTypeAliasStatement")
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
var PRIMITIVES = [
|
|
479
|
+
"any",
|
|
480
|
+
"unknown",
|
|
481
|
+
"never",
|
|
482
|
+
"nil",
|
|
483
|
+
"boolean",
|
|
484
|
+
"number",
|
|
485
|
+
"string",
|
|
486
|
+
"thread",
|
|
487
|
+
"buffer"
|
|
488
|
+
];
|
|
489
|
+
var KEYWORDS = [
|
|
490
|
+
"const",
|
|
491
|
+
"let",
|
|
492
|
+
"function",
|
|
493
|
+
"return",
|
|
494
|
+
"if",
|
|
495
|
+
"then",
|
|
496
|
+
"elseif",
|
|
497
|
+
"else",
|
|
498
|
+
"end",
|
|
499
|
+
"for",
|
|
500
|
+
"in",
|
|
501
|
+
"while",
|
|
502
|
+
"do",
|
|
503
|
+
"repeat",
|
|
504
|
+
"until",
|
|
505
|
+
"break",
|
|
506
|
+
"continue",
|
|
507
|
+
"type",
|
|
508
|
+
"declare",
|
|
509
|
+
"export",
|
|
510
|
+
"import",
|
|
511
|
+
"and",
|
|
512
|
+
"or",
|
|
513
|
+
"not",
|
|
514
|
+
"true",
|
|
515
|
+
"false",
|
|
516
|
+
"nil"
|
|
517
|
+
];
|
|
518
|
+
|
|
519
|
+
// src/features/signatureHelp.ts
|
|
520
|
+
function signatureHelp(analyzer, document, position) {
|
|
521
|
+
const source = document.getText();
|
|
522
|
+
const offset = document.offsetAt(position);
|
|
523
|
+
for (const repair of ["", "nil", "nil)", ")"]) {
|
|
524
|
+
const text = source.slice(0, offset) + repair + source.slice(offset);
|
|
525
|
+
const analysis = analyzer.analyze(document.uri, -1, text);
|
|
526
|
+
const found = helpAt(analysis, position);
|
|
527
|
+
if (found) return found;
|
|
528
|
+
}
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
function helpAt(analysis, position) {
|
|
532
|
+
const path = pathAt(analysis.program, position, true);
|
|
533
|
+
const call = [...path].reverse().find(
|
|
534
|
+
(n) => n.type === "CallExpression" || n.type === "MethodCallExpression"
|
|
535
|
+
);
|
|
536
|
+
if (!call) return null;
|
|
537
|
+
const callee = call.type === "CallExpression" ? call.callee : call;
|
|
538
|
+
const calleeType = call.type === "CallExpression" ? analysis.types.typeOf.get(callee) : methodType(analysis, call);
|
|
539
|
+
const signatures = signaturesOf(calleeType, analysis.types.aliases);
|
|
540
|
+
if (!signatures.length) return null;
|
|
541
|
+
const selfOffset = call.type === "MethodCallExpression" ? 1 : 0;
|
|
542
|
+
const written = activeArgument(call, position);
|
|
543
|
+
const infos = signatures.map((signature) => {
|
|
544
|
+
const { label, parameters } = signatureLabel(signature);
|
|
545
|
+
return { label, parameters: parameters.map((p) => ({ label: p })) };
|
|
546
|
+
});
|
|
547
|
+
const wanted = written + selfOffset + 1;
|
|
548
|
+
let active = signatures.findIndex((s) => s.params.length >= wanted || s.varargs);
|
|
549
|
+
if (active < 0) active = 0;
|
|
550
|
+
return {
|
|
551
|
+
signatures: infos,
|
|
552
|
+
activeSignature: active,
|
|
553
|
+
activeParameter: Math.min(
|
|
554
|
+
written + selfOffset,
|
|
555
|
+
Math.max(0, signatures[active].params.length - 1)
|
|
556
|
+
)
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
function methodType(analysis, call) {
|
|
560
|
+
const object = call.object;
|
|
561
|
+
const method = call.method;
|
|
562
|
+
const objectType = analysis.types.typeOf.get(object);
|
|
563
|
+
if (!objectType) return void 0;
|
|
564
|
+
return memberType(objectType, method.name, analysis);
|
|
565
|
+
}
|
|
566
|
+
function memberType(type, name, analysis) {
|
|
567
|
+
if (type.kind === "object") return type.properties.get(name)?.type;
|
|
568
|
+
if (type.kind === "intersection") {
|
|
569
|
+
for (const part of type.types) {
|
|
570
|
+
const found = memberType(part, name, analysis);
|
|
571
|
+
if (found) return found;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (type.kind === "genericRef") {
|
|
575
|
+
const alias = analysis.types.aliases.get(type.name);
|
|
576
|
+
if (alias) return memberType(alias, name, analysis);
|
|
577
|
+
}
|
|
578
|
+
return void 0;
|
|
579
|
+
}
|
|
580
|
+
function activeArgument(call, position) {
|
|
581
|
+
const args = call.arguments;
|
|
582
|
+
for (let i = 0; i < args.length; i++) {
|
|
583
|
+
if (containsPosition(args[i], position, true)) return i;
|
|
584
|
+
}
|
|
585
|
+
let count = 0;
|
|
586
|
+
for (const arg of args) {
|
|
587
|
+
const before = arg.line.end - 1 < position.line || arg.line.end - 1 === position.line && arg.column.end - 1 <= position.character;
|
|
588
|
+
if (before) count++;
|
|
589
|
+
}
|
|
590
|
+
return count;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/features/symbols.ts
|
|
594
|
+
var import_vscode_languageserver4 = require("vscode-languageserver");
|
|
595
|
+
var import_luaut_parser6 = require("luaut-parser");
|
|
596
|
+
function documentSymbols(analysis) {
|
|
597
|
+
const out = [];
|
|
598
|
+
walk(analysis.program, (node) => {
|
|
599
|
+
switch (node.type) {
|
|
600
|
+
case "FunctionDeclaration":
|
|
601
|
+
case "FunctionDeclarationStatement": {
|
|
602
|
+
const name = functionName(node);
|
|
603
|
+
if (name) out.push(symbol(name, import_vscode_languageserver4.SymbolKind.Function, node, detailOf(analysis, node)));
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
case "TypeAliasStatement":
|
|
607
|
+
case "ExportTypeAliasStatement": {
|
|
608
|
+
const named = node.name;
|
|
609
|
+
const name = typeof named === "string" ? named : named?.name;
|
|
610
|
+
if (name) {
|
|
611
|
+
const alias = analysis.types.aliases.get(name);
|
|
612
|
+
out.push(symbol(name, import_vscode_languageserver4.SymbolKind.Interface, node, alias ? (0, import_luaut_parser6.formatType)(alias) : void 0));
|
|
613
|
+
}
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
case "VariableDeclaration": {
|
|
617
|
+
for (const target of node.names ?? []) {
|
|
618
|
+
const name = target.name;
|
|
619
|
+
if (name) out.push(symbol(name, import_vscode_languageserver4.SymbolKind.Variable, target));
|
|
620
|
+
}
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
return out;
|
|
626
|
+
}
|
|
627
|
+
function functionName(node) {
|
|
628
|
+
const named = node;
|
|
629
|
+
if (typeof named.name === "string") return named.name;
|
|
630
|
+
if (named.name && typeof named.name === "object") return named.name.name;
|
|
631
|
+
if (named.target?.base?.name) {
|
|
632
|
+
const path = (named.target.path ?? []).map((p) => p.name).filter(Boolean);
|
|
633
|
+
const dotted = [named.target.base.name, ...path].join(".");
|
|
634
|
+
return named.target.method ? `${dotted}:${named.target.method.name}` : dotted;
|
|
635
|
+
}
|
|
636
|
+
return void 0;
|
|
637
|
+
}
|
|
638
|
+
function detailOf(analysis, node) {
|
|
639
|
+
const name = node.name;
|
|
640
|
+
if (name && typeof name === "object") {
|
|
641
|
+
const binding = (0, import_luaut_parser6.getBinding)(analysis.scopes, name);
|
|
642
|
+
const type = binding && analysis.types.bindingType.get(binding.id);
|
|
643
|
+
if (type) return (0, import_luaut_parser6.formatType)(type);
|
|
644
|
+
}
|
|
645
|
+
return void 0;
|
|
646
|
+
}
|
|
647
|
+
function symbol(name, kind, node, detail) {
|
|
648
|
+
const range = toRange(node);
|
|
649
|
+
return { name, kind, detail, range, selectionRange: range };
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/server.ts
|
|
653
|
+
function createServer(connection, options = {}) {
|
|
654
|
+
const analyzer = new Analyzer(options);
|
|
655
|
+
const documents = new import_node.TextDocuments(import_vscode_languageserver_textdocument.TextDocument);
|
|
656
|
+
connection.onInitialize((_params) => ({
|
|
657
|
+
capabilities: {
|
|
658
|
+
textDocumentSync: import_node.TextDocumentSyncKind.Incremental,
|
|
659
|
+
hoverProvider: true,
|
|
660
|
+
definitionProvider: true,
|
|
661
|
+
referencesProvider: true,
|
|
662
|
+
documentHighlightProvider: true,
|
|
663
|
+
documentSymbolProvider: true,
|
|
664
|
+
renameProvider: { prepareProvider: true },
|
|
665
|
+
completionProvider: {
|
|
666
|
+
// `.` and `:` open a member list; the rest of the time
|
|
667
|
+
// completion is asked for as you type a word.
|
|
668
|
+
triggerCharacters: [".", ":"],
|
|
669
|
+
resolveProvider: false
|
|
670
|
+
},
|
|
671
|
+
signatureHelpProvider: { triggerCharacters: ["(", ","], retriggerCharacters: [","] }
|
|
672
|
+
},
|
|
673
|
+
serverInfo: { name: "luaut-language-server" }
|
|
674
|
+
}));
|
|
675
|
+
const publish = (document) => {
|
|
676
|
+
void connection.sendDiagnostics({
|
|
677
|
+
uri: document.uri,
|
|
678
|
+
version: document.version,
|
|
679
|
+
diagnostics: diagnostics(analyzer.get(document))
|
|
680
|
+
});
|
|
681
|
+
};
|
|
682
|
+
documents.onDidOpen((e) => publish(e.document));
|
|
683
|
+
documents.onDidChangeContent((e) => publish(e.document));
|
|
684
|
+
documents.onDidClose((e) => {
|
|
685
|
+
analyzer.forget(e.document.uri);
|
|
686
|
+
void connection.sendDiagnostics({ uri: e.document.uri, diagnostics: [] });
|
|
687
|
+
});
|
|
688
|
+
const withDocument = (uri, f, fallback) => {
|
|
689
|
+
const document = documents.get(uri);
|
|
690
|
+
return document ? f(document) : fallback;
|
|
691
|
+
};
|
|
692
|
+
connection.onHover((p) => withDocument(
|
|
693
|
+
p.textDocument.uri,
|
|
694
|
+
(d) => hover(analyzer.get(d), p.position),
|
|
695
|
+
null
|
|
696
|
+
));
|
|
697
|
+
connection.onDefinition((p) => withDocument(
|
|
698
|
+
p.textDocument.uri,
|
|
699
|
+
(d) => definition(analyzer.get(d), p.position),
|
|
700
|
+
null
|
|
701
|
+
));
|
|
702
|
+
connection.onReferences((p) => withDocument(
|
|
703
|
+
p.textDocument.uri,
|
|
704
|
+
(d) => references(analyzer.get(d), p.position, p.context.includeDeclaration),
|
|
705
|
+
[]
|
|
706
|
+
));
|
|
707
|
+
connection.onDocumentHighlight((p) => withDocument(
|
|
708
|
+
p.textDocument.uri,
|
|
709
|
+
(d) => highlights(analyzer.get(d), p.position),
|
|
710
|
+
[]
|
|
711
|
+
));
|
|
712
|
+
connection.onDocumentSymbol((p) => withDocument(
|
|
713
|
+
p.textDocument.uri,
|
|
714
|
+
(d) => documentSymbols(analyzer.get(d)),
|
|
715
|
+
[]
|
|
716
|
+
));
|
|
717
|
+
connection.onPrepareRename((p) => withDocument(
|
|
718
|
+
p.textDocument.uri,
|
|
719
|
+
(d) => {
|
|
720
|
+
const prepared = prepareRename(analyzer.get(d), p.position);
|
|
721
|
+
return prepared ? { range: prepared.range, placeholder: prepared.placeholder } : null;
|
|
722
|
+
},
|
|
723
|
+
null
|
|
724
|
+
));
|
|
725
|
+
connection.onRenameRequest((p) => withDocument(
|
|
726
|
+
p.textDocument.uri,
|
|
727
|
+
(d) => rename(analyzer.get(d), p.position, p.newName),
|
|
728
|
+
null
|
|
729
|
+
));
|
|
730
|
+
connection.onCompletion((p) => withDocument(
|
|
731
|
+
p.textDocument.uri,
|
|
732
|
+
(d) => completion(analyzer, d, p.position),
|
|
733
|
+
[]
|
|
734
|
+
));
|
|
735
|
+
connection.onSignatureHelp((p) => withDocument(
|
|
736
|
+
p.textDocument.uri,
|
|
737
|
+
(d) => signatureHelp(analyzer, d, p.position),
|
|
738
|
+
null
|
|
739
|
+
));
|
|
740
|
+
documents.listen(connection);
|
|
741
|
+
connection.listen();
|
|
742
|
+
}
|
|
743
|
+
function startServer(options = {}) {
|
|
744
|
+
createServer((0, import_node.createConnection)(import_node.ProposedFeatures.all), options);
|
|
745
|
+
}
|
|
746
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
747
|
+
0 && (module.exports = {
|
|
748
|
+
Analyzer,
|
|
749
|
+
bindingAt,
|
|
750
|
+
children,
|
|
751
|
+
completion,
|
|
752
|
+
containsPosition,
|
|
753
|
+
createServer,
|
|
754
|
+
definition,
|
|
755
|
+
diagnostics,
|
|
756
|
+
documentSymbols,
|
|
757
|
+
enclosing,
|
|
758
|
+
highlights,
|
|
759
|
+
hover,
|
|
760
|
+
membersOf,
|
|
761
|
+
nodeAt,
|
|
762
|
+
pathAt,
|
|
763
|
+
prepareRename,
|
|
764
|
+
references,
|
|
765
|
+
rename,
|
|
766
|
+
signatureHelp,
|
|
767
|
+
signatureLabel,
|
|
768
|
+
signaturesOf,
|
|
769
|
+
startServer,
|
|
770
|
+
toPosition,
|
|
771
|
+
toRange,
|
|
772
|
+
walk
|
|
773
|
+
});
|
|
774
|
+
//# sourceMappingURL=index.cjs.map
|