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