luaut-language-server 1.0.1 → 1.1.0

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