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