luaut-language-server 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startServer
4
- } from "./chunk-XK3KS3T4.js";
4
+ } from "./chunk-GHAR7NOH.js";
5
5
 
6
6
  // src/cli.ts
7
7
  startServer();
package/dist/index.cjs CHANGED
@@ -136,7 +136,8 @@ function membersOf(type, aliases, seen = /* @__PURE__ */ new Set()) {
136
136
  }
137
137
  function takesSelf(type) {
138
138
  for (const signature of signaturesOf(type)) {
139
- if (signature.params[0]?.name === "self") return true;
139
+ const first = signature.params[0]?.name;
140
+ if (first === "self" || first === "this") return true;
140
141
  }
141
142
  return false;
142
143
  }
@@ -157,7 +158,7 @@ function signatureLabel(signature) {
157
158
  });
158
159
  const generics = signature.typeParams?.length ? `<${signature.typeParams.join(", ")}>` : "";
159
160
  const varargs = signature.varargs ? [`...: ${(0, import_luaut_parser.formatType)(signature.varargs)}`] : [];
160
- const label = `${generics}(${[...parameters, ...varargs].join(", ")}) -> ${(0, import_luaut_parser.formatType)(signature.returns)}`;
161
+ const label = `${generics}(${[...parameters, ...varargs].join(", ")}) => ${(0, import_luaut_parser.formatType)(signature.returns)}`;
161
162
  return { label, parameters };
162
163
  }
163
164
 
@@ -748,7 +749,7 @@ function exportDeclaration(analyzer, module2, name, seen = /* @__PURE__ */ new S
748
749
  break;
749
750
  case "ExportStatement": {
750
751
  const declaration = statement.declaration;
751
- if (declaration.type === "FunctionDeclaration") {
752
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
752
753
  if (declaration.name.name === name) return here(declaration.name);
753
754
  } else {
754
755
  for (const target of declaration.names) {
@@ -860,16 +861,100 @@ function diagnostics(analysis) {
860
861
  }
861
862
 
862
863
  // src/features/hover.ts
864
+ var import_luaut_parser6 = require("luaut-parser");
865
+
866
+ // src/features/expand.ts
863
867
  var import_luaut_parser5 = require("luaut-parser");
864
- function hover(analysis, position) {
868
+ function expandAliases(type, aliases, depth, seen = []) {
869
+ if (depth <= 0) return type;
870
+ const name = withheldName(type);
871
+ if (name !== void 0 && !seen.includes(name)) {
872
+ const opened = aliases.get(name) ?? type;
873
+ const inner = [...seen, name];
874
+ return children2(unnamed(opened), (t) => expandAliases(t, aliases, depth - 1, inner));
875
+ }
876
+ return children2(type, (t) => expandAliases(t, aliases, depth, seen));
877
+ }
878
+ function withheldName(type) {
879
+ if ((0, import_luaut_parser5.isClassType)(type)) return void 0;
880
+ if (type.kind === "object" || type.kind === "intersection") return type.name;
881
+ if (type.kind === "genericRef") return type.typeArguments.length ? void 0 : type.name;
882
+ return void 0;
883
+ }
884
+ function unnamed(type) {
885
+ if (type.kind === "object") {
886
+ const out = (0, import_luaut_parser5.objectType)(type.properties, type.indexer, type.frozen);
887
+ return type.class ? Object.assign(out, { class: type.class, name: type.name }) : out;
888
+ }
889
+ if (type.kind === "intersection" && type.name) return { ...type, name: void 0 };
890
+ return type;
891
+ }
892
+ function children2(type, f) {
893
+ switch (type.kind) {
894
+ case "array":
895
+ return (0, import_luaut_parser5.arrayOf)(f(type.element));
896
+ case "tuple":
897
+ return (0, import_luaut_parser5.tuple)(type.elements.map(f), type.isPack);
898
+ case "union":
899
+ return (0, import_luaut_parser5.union)(type.types.map(f));
900
+ case "intersection": {
901
+ const out = (0, import_luaut_parser5.intersection)(type.types.map(f));
902
+ return type.name && out.kind === "intersection" ? { ...out, name: type.name } : out;
903
+ }
904
+ case "object": {
905
+ if ((0, import_luaut_parser5.isClassType)(type)) return type;
906
+ const out = (0, import_luaut_parser5.objectType)(
907
+ [...type.properties].map(([name, property]) => [name, { ...property, type: f(property.type) }]),
908
+ type.indexer && { key: type.indexer.key, value: f(type.indexer.value) },
909
+ type.frozen
910
+ );
911
+ if (type.name) out.name = type.name;
912
+ return out;
913
+ }
914
+ case "function":
915
+ return (0, import_luaut_parser5.fn)(
916
+ type.params.map((p) => ({ ...p, type: f(p.type) })),
917
+ f(type.returns),
918
+ type.varargs && f(type.varargs),
919
+ type.typeParams,
920
+ type.predicate
921
+ );
922
+ default:
923
+ return type;
924
+ }
925
+ }
926
+
927
+ // src/features/hover.ts
928
+ function hover(analysis, position, depth = 0) {
865
929
  const path = pathAt(analysis.program, position, true);
866
930
  for (let i = path.length - 1; i >= 0; i--) {
867
931
  if (UNNAMED.has(path[i].type)) return null;
868
- const text = describe(analysis, path, i);
869
- if (text) return { contents: { kind: "markdown", value: code(text) }, range: toRange(path[i]) };
932
+ const text = at(analysis, path, i, depth);
933
+ if (!text) continue;
934
+ const canExpand = at(analysis, path, i, depth + 1) !== text;
935
+ return {
936
+ contents: { kind: "markdown", value: code(text) },
937
+ range: toRange(path[i]),
938
+ depth,
939
+ canExpand
940
+ };
870
941
  }
871
942
  return null;
872
943
  }
944
+ function at(analysis, path, index, depth) {
945
+ const previous = expansion;
946
+ expansion = { depth, aliases: analysis.types.aliases, source: analysis.source };
947
+ try {
948
+ return describe(analysis, path, index);
949
+ } finally {
950
+ expansion = previous;
951
+ }
952
+ }
953
+ var expansion = {
954
+ depth: 0,
955
+ aliases: /* @__PURE__ */ new Map(),
956
+ source: ""
957
+ };
873
958
  var UNNAMED = /* @__PURE__ */ new Set([
874
959
  "BinaryExpression",
875
960
  "UnaryExpression",
@@ -902,8 +987,8 @@ function describe(analysis, path, index) {
902
987
  case "TableExpression": {
903
988
  const field = fieldWithKey(parent, node);
904
989
  if (!field) break;
905
- const objectType = types.typeOf.get(parent);
906
- const property = objectType?.kind === "object" ? objectType.properties.get(name) : void 0;
990
+ const objectType2 = types.typeOf.get(parent);
991
+ const property = objectType2?.kind === "object" ? objectType2.properties.get(name) : void 0;
907
992
  const type2 = property?.type ?? types.typeOf.get(field.value);
908
993
  return type2 && `(property) ${name}: ${pretty(type2)}`;
909
994
  }
@@ -951,6 +1036,19 @@ function describe(analysis, path, index) {
951
1036
  case "DeclareClassStatement":
952
1037
  if (parent.name === node) return classText(analysis, name);
953
1038
  break;
1039
+ case "ClassDeclaration":
1040
+ if (parent.name === node) return classText(analysis, name);
1041
+ break;
1042
+ case "ClassField":
1043
+ if (parent.name === node) {
1044
+ const type2 = typeOfNode(parent.typeAnnotation) ?? types.typeOf.get(parent.init);
1045
+ const prefix = parent.isStatic ? "(static) " : "(field) ";
1046
+ return type2 ? `${prefix}${name}: ${pretty(type2)}` : `${prefix}${name}`;
1047
+ }
1048
+ break;
1049
+ case "ClassAccessor":
1050
+ if (parent.name === node) return `(${parent.kind === "get" ? "getter" : "setter"}) ${name}`;
1051
+ break;
954
1052
  case "TableTypeProperty":
955
1053
  if (parent.key === node) {
956
1054
  const type2 = typeOfNode(parent.valueType);
@@ -973,7 +1071,7 @@ function describe(analysis, path, index) {
973
1071
  case "MappedTypeNode":
974
1072
  if (parent.parameterId === node) {
975
1073
  const keys = typeOfNode(parent.constraint);
976
- return `(type parameter) ${name}${keys ? ` in ${(0, import_luaut_parser5.formatType)(keys)}` : ""}`;
1074
+ return `(type parameter) ${name}${keys ? ` in ${(0, import_luaut_parser6.formatType)(keys)}` : ""}`;
977
1075
  }
978
1076
  break;
979
1077
  }
@@ -1001,7 +1099,7 @@ function describe(analysis, path, index) {
1001
1099
  case "TypedIdentifier": {
1002
1100
  const binding = bindingOfNode(analysis, node);
1003
1101
  const type2 = binding && types.bindingType.get(binding.id);
1004
- return type2 ? bindingText(binding, type2) : void 0;
1102
+ return type2 ? bindingText(binding, type2, asWritten(node.typeAnnotation)) : void 0;
1005
1103
  }
1006
1104
  // A type written by name: `number`, `Shape`, `Partial<User>`, or a type
1007
1105
  // parameter in scope.
@@ -1015,7 +1113,7 @@ function describe(analysis, path, index) {
1015
1113
  if (!node.typeArguments.length) {
1016
1114
  const qualified = node.namespace ? `${node.namespace}.${base}` : base;
1017
1115
  const alias = types.aliases.get(qualified);
1018
- if (alias && (0, import_luaut_parser5.isClassType)(alias)) return classText(analysis, qualified);
1116
+ if (alias && (0, import_luaut_parser6.isClassType)(alias)) return classText(analysis, qualified);
1019
1117
  if (alias) return `type ${qualified} = ${pretty(alias)}`;
1020
1118
  }
1021
1119
  const type2 = typeOfNode(node);
@@ -1043,20 +1141,29 @@ function declareText(analysis, statement) {
1043
1141
  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);
1044
1142
  const others = total - 1;
1045
1143
  const overloads = others > 0 ? ` (+${others} overload${others > 1 ? "s" : ""})` : "";
1046
- return `declare function ${name}${(0, import_luaut_parser5.formatType)(own)}${overloads}`;
1144
+ return `declare function ${name}${(0, import_luaut_parser6.formatType)(own)}${overloads}`;
1047
1145
  }
1048
1146
  function classText(analysis, name) {
1049
1147
  const type = analysis.types.aliases.get(name);
1050
- if (!type || !(0, import_luaut_parser5.isClassType)(type)) return void 0;
1148
+ if (!type || !(0, import_luaut_parser6.isClassType)(type)) return void 0;
1051
1149
  const superclass = type.class.superclass;
1052
1150
  const inherited = superclass ? analysis.types.aliases.get(superclass) : void 0;
1053
- const own = [...type.properties].filter(([key, property]) => inherited?.kind !== "object" || inherited.properties.get(key) !== property);
1054
- const head = `declare class ${name}${superclass ? ` extends ${superclass}` : ""}`;
1055
- if (!own.length) return `${head} {}`;
1056
- const lines = own.map(([key, property]) => ` ${property.readonly ? "readonly " : ""}${key}${property.optional ? "?" : ""}: ${(0, import_luaut_parser5.formatType)(property.type)},`);
1057
- return `${head} {
1151
+ const own = [...type.properties].filter(([key, property]) => (
1152
+ // `ClassObject` is on every instance and says nothing about this one.
1153
+ key !== "ClassObject" && (inherited?.kind !== "object" || inherited.properties.get(key) !== property)
1154
+ ));
1155
+ const written = isRuntimeClass(analysis, name);
1156
+ const head = `${written ? "" : "declare "}class ${name}${superclass ? ` extends ${superclass}` : ""}`;
1157
+ const lines = own.map(([key, property]) => ` ${property.readonly ? "readonly " : ""}${key}${property.optional ? "?" : ""}: ${(0, import_luaut_parser6.formatType)(property.type)}${written ? "" : ","}`);
1158
+ return own.length ? `${head} {
1058
1159
  ${lines.join("\n")}
1059
- }`;
1160
+ }` : `${head} {}`;
1161
+ }
1162
+ function isRuntimeClass(analysis, name) {
1163
+ return analysis.program.body.statements.some((statement) => {
1164
+ const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
1165
+ return declaration.type === "ClassDeclaration" && declaration.name.name === name;
1166
+ });
1060
1167
  }
1061
1168
  function typeParameterText(analysis, parameter) {
1062
1169
  return `(type parameter) ${typeParameterSignature(analysis, parameter)}`;
@@ -1065,7 +1172,7 @@ function typeParameterSignature(analysis, parameter) {
1065
1172
  const p = parameter;
1066
1173
  if (p.infer) return `infer ${p.name}`;
1067
1174
  const constraint = p.constraint ? analysis.types.typeOfTypeNode.get(p.constraint) : void 0;
1068
- return `${p.isConst ? "const " : ""}${p.name}${constraint ? ` extends ${(0, import_luaut_parser5.formatType)(constraint)}` : ""}`;
1175
+ return `${p.isConst ? "const " : ""}${p.name}${constraint ? ` extends ${(0, import_luaut_parser6.formatType)(constraint)}` : ""}`;
1069
1176
  }
1070
1177
  function typeParameterInScope(path, index, name) {
1071
1178
  for (let i = index - 1; i >= 0; i--) {
@@ -1090,7 +1197,7 @@ function referenceText(analysis, reference) {
1090
1197
  if (!args.length) return name;
1091
1198
  const resolved = args.map((a) => {
1092
1199
  const t = analysis.types.typeOfTypeNode.get(a);
1093
- return t ? (0, import_luaut_parser5.formatType)(t) : "?";
1200
+ return t ? (0, import_luaut_parser6.formatType)(t) : "?";
1094
1201
  });
1095
1202
  return `${name}<${resolved.join(", ")}>`;
1096
1203
  }
@@ -1098,30 +1205,50 @@ function fieldWithKey(table, key) {
1098
1205
  const fields = table.fields;
1099
1206
  return fields.find((f) => f.type === "TableFieldNamed" && f.key === key);
1100
1207
  }
1101
- function pretty(type) {
1102
- const flat = (0, import_luaut_parser5.formatType)(type);
1208
+ function pretty(type, written) {
1209
+ const named = render(expandAliases(type, expansion.aliases, 0));
1210
+ const shorthand = written !== void 0 && written !== named ? written : void 0;
1211
+ const depth = shorthand === void 0 ? expansion.depth : expansion.depth - 1;
1212
+ if (depth < 0) return shorthand;
1213
+ return depth === 0 ? named : render(expandAliases(type, expansion.aliases, depth));
1214
+ }
1215
+ function asWritten(node) {
1216
+ if (!isSpanned(node) || !expansion.source) return void 0;
1217
+ const lines = expansion.source.split(/\r?\n/);
1218
+ const { line, column } = node;
1219
+ if (line.start < 1 || line.end > lines.length) return void 0;
1220
+ const text = line.start === line.end ? lines[line.start - 1].slice(column.start - 1, column.end - 1) : [
1221
+ lines[line.start - 1].slice(column.start - 1),
1222
+ ...lines.slice(line.start, line.end - 1),
1223
+ lines[line.end - 1].slice(0, column.end - 1)
1224
+ ].join(" ");
1225
+ const folded = text.trim().replace(/\s+/g, " ");
1226
+ return folded.length ? folded : void 0;
1227
+ }
1228
+ function render(type) {
1229
+ const flat = (0, import_luaut_parser6.formatType)(type);
1103
1230
  if (flat.length <= 80) return flat;
1104
1231
  if (type.kind === "object") {
1105
1232
  const lines = [];
1106
- if (type.indexer) lines.push(` [${(0, import_luaut_parser5.formatType)(type.indexer.key)}]: ${(0, import_luaut_parser5.formatType)(type.indexer.value)},`);
1233
+ if (type.indexer) lines.push(` [${(0, import_luaut_parser6.formatType)(type.indexer.key)}]: ${(0, import_luaut_parser6.formatType)(type.indexer.value)},`);
1107
1234
  for (const [name, property] of type.properties) {
1108
1235
  const readonly = property.readonly ? "readonly " : "";
1109
- lines.push(` ${readonly}${name}${property.optional ? "?" : ""}: ${(0, import_luaut_parser5.formatType)(property.type)},`);
1236
+ lines.push(` ${readonly}${name}${property.optional ? "?" : ""}: ${(0, import_luaut_parser6.formatType)(property.type)},`);
1110
1237
  }
1111
1238
  return `{
1112
1239
  ${lines.join("\n")}
1113
1240
  }`;
1114
1241
  }
1115
1242
  if (type.kind === "intersection" && type.types.every((t) => t.kind === "function")) {
1116
- return type.types.map(import_luaut_parser5.formatType).join("\n& ");
1243
+ return type.types.map(import_luaut_parser6.formatType).join("\n& ");
1117
1244
  }
1118
1245
  return flat;
1119
1246
  }
1120
- function bindingText(binding, type) {
1247
+ function bindingText(binding, type, written) {
1121
1248
  if (binding.declaredBy === "function" && type.kind === "function") {
1122
- return `function ${binding.name}${(0, import_luaut_parser5.formatType)(type)}`;
1249
+ return `function ${binding.name}${pretty(type)}`;
1123
1250
  }
1124
- return `${keyword(binding)} ${binding.name}: ${pretty(type)}`;
1251
+ return `${keyword(binding)} ${binding.name}: ${pretty(type, written)}`;
1125
1252
  }
1126
1253
  function keyword(binding) {
1127
1254
  if (binding.kind === "param" || binding.kind === "self") return "(parameter)";
@@ -1197,7 +1324,7 @@ function isIdentifier(name) {
1197
1324
 
1198
1325
  // src/features/completion.ts
1199
1326
  var import_vscode_languageserver5 = require("vscode-languageserver");
1200
- var import_luaut_parser6 = require("luaut-parser");
1327
+ var import_luaut_parser7 = require("luaut-parser");
1201
1328
 
1202
1329
  // src/features/autoImport.ts
1203
1330
  var import_node_fs3 = require("fs");
@@ -1234,7 +1361,7 @@ function importItems(analyzer, analysis, typePosition, taken) {
1234
1361
  function serviceItems(analysis, taken) {
1235
1362
  const services = analysis.types.aliases.get("Services");
1236
1363
  if (!services || services.kind !== "object") return [];
1237
- const at = serviceInsertion(analysis.program.body.statements);
1364
+ const at2 = serviceInsertion(analysis.program.body.statements);
1238
1365
  const items = [];
1239
1366
  for (const name of services.properties.keys()) {
1240
1367
  if (taken.has(name)) continue;
@@ -1245,8 +1372,8 @@ function serviceItems(analysis, taken) {
1245
1372
  labelDetails: { description: "service" },
1246
1373
  detail: line,
1247
1374
  sortText: `5${name}`,
1248
- additionalTextEdits: [{ range: { start: at.position, end: at.position }, newText: `${line}
1249
- ${at.gap}` }]
1375
+ additionalTextEdits: [{ range: { start: at2.position, end: at2.position }, newText: `${line}
1376
+ ${at2.gap}` }]
1250
1377
  });
1251
1378
  }
1252
1379
  return items;
@@ -1258,24 +1385,24 @@ function importEdit(analyzer, analysis, file, name, specifier, typePosition) {
1258
1385
  if (existing) {
1259
1386
  const last = existing.specifiers[existing.specifiers.length - 1];
1260
1387
  if (last) {
1261
- const at2 = endOf(last);
1262
- return { range: { start: at2, end: at2 }, newText: `, ${name}` };
1388
+ const at3 = endOf(last);
1389
+ return { range: { start: at3, end: at3 }, newText: `, ${name}` };
1263
1390
  }
1264
1391
  if (existing.defaultImport) {
1265
- const at2 = endOf(existing.defaultImport);
1266
- return { range: { start: at2, end: at2 }, newText: `, { ${name} }` };
1392
+ const at3 = endOf(existing.defaultImport);
1393
+ return { range: { start: at3, end: at3 }, newText: `, { ${name} }` };
1267
1394
  }
1268
1395
  }
1269
1396
  const line = `import { ${name} } from "${specifier}"`;
1270
1397
  const lastImport = imports[imports.length - 1];
1271
1398
  if (lastImport) {
1272
- const at2 = { line: lastImport.line.end, character: 0 };
1273
- return { range: { start: at2, end: at2 }, newText: `${line}
1399
+ const at3 = { line: lastImport.line.end, character: 0 };
1400
+ return { range: { start: at3, end: at3 }, newText: `${line}
1274
1401
  ` };
1275
1402
  }
1276
1403
  const first = statements[0];
1277
- const at = { line: first ? first.line.start - 1 : 0, character: 0 };
1278
- return { range: { start: at, end: at }, newText: first ? `${line}
1404
+ const at2 = { line: first ? first.line.start - 1 : 0, character: 0 };
1405
+ return { range: { start: at2, end: at2 }, newText: first ? `${line}
1279
1406
 
1280
1407
  ` : `${line}
1281
1408
  ` };
@@ -1368,12 +1495,12 @@ function completion(analyzer, document, position) {
1368
1495
  const operator = memberOperator(source, start);
1369
1496
  const alreadyCalled = /^\s*\(/.test(source.slice(end));
1370
1497
  const standIns = operator === ":" ? [alreadyCalled ? PLACEHOLDER : `${PLACEHOLDER}()`] : operator === "." && !alreadyCalled ? [PLACEHOLDER, `${PLACEHOLDER}()`] : [PLACEHOLDER];
1371
- const at = { line: position.line, character: position.character - (offset - start) };
1498
+ const at2 = { line: position.line, character: position.character - (offset - start) };
1372
1499
  let first;
1373
1500
  for (const standIn of standIns) {
1374
1501
  const patched = source.slice(0, start) + standIn + source.slice(end);
1375
1502
  const analysis = analyzer.analyze(document.uri, -1, patched);
1376
- const path = pathAt(analysis.program, at, true);
1503
+ const path = pathAt(analysis.program, at2, true);
1377
1504
  const index = path.findLastIndex(
1378
1505
  (n) => n.type === "Identifier" && n.name === PLACEHOLDER
1379
1506
  );
@@ -1389,8 +1516,8 @@ function completion(analyzer, document, position) {
1389
1516
  if (inTypePosition(first.path)) {
1390
1517
  const named = [...first.analysis.types.aliases].map(([name, type]) => ({
1391
1518
  label: name,
1392
- kind: (0, import_luaut_parser6.isClassType)(type) ? import_vscode_languageserver5.CompletionItemKind.Class : import_vscode_languageserver5.CompletionItemKind.Interface,
1393
- detail: (0, import_luaut_parser6.isClassType)(type) ? "class" : "type"
1519
+ kind: (0, import_luaut_parser7.isClassType)(type) ? import_vscode_languageserver5.CompletionItemKind.Class : import_vscode_languageserver5.CompletionItemKind.Interface,
1520
+ detail: (0, import_luaut_parser7.isClassType)(type) ? "class" : "type"
1394
1521
  }));
1395
1522
  const primitives = PRIMITIVES2.map((name) => ({
1396
1523
  label: name,
@@ -1410,7 +1537,7 @@ function completion(analyzer, document, position) {
1410
1537
  for (const binding of first.analysis.scopes.bindings.values()) taken.add(binding.name);
1411
1538
  const current = analyzer.get(document);
1412
1539
  return [
1413
- ...valueItems(first.analysis, at),
1540
+ ...valueItems(first.analysis, at2, insideFunction(first.path)),
1414
1541
  ...contextKeywords(source.slice(0, start)),
1415
1542
  ...importItems(analyzer, current, false, taken),
1416
1543
  ...serviceItems(current, taken)
@@ -1478,14 +1605,14 @@ function objectKeyItems(analysis, path, after) {
1478
1605
  return members.filter((member) => !written.has(member.name)).map((member) => ({
1479
1606
  label: member.name,
1480
1607
  kind: import_vscode_languageserver5.CompletionItemKind.Field,
1481
- detail: `${member.property.optional ? "?" : ""}: ${(0, import_luaut_parser6.formatType)(member.property.type)}`,
1608
+ detail: `${member.property.optional ? "?" : ""}: ${(0, import_luaut_parser7.formatType)(member.property.type)}`,
1482
1609
  insertText: colon ? member.name : `${member.name}: `
1483
1610
  }));
1484
1611
  }
1485
1612
  function indexKeys(analysis, position, literal) {
1486
1613
  const path = pathAt(analysis.program, position, false);
1487
- const at = path.indexOf(literal);
1488
- const parent = at > 0 ? path[at - 1] : void 0;
1614
+ const at2 = path.indexOf(literal);
1615
+ const parent = at2 > 0 ? path[at2 - 1] : void 0;
1489
1616
  if (!parent) return [];
1490
1617
  let indexed;
1491
1618
  if (parent.type === "IndexedAccessTypeNode" && parent.indexType === literal) {
@@ -1577,20 +1704,24 @@ function isMethodOnly(type) {
1577
1704
  return false;
1578
1705
  }
1579
1706
  }
1580
- function valueItems(analysis, at) {
1707
+ function insideFunction(path) {
1708
+ return path.some((n) => n.type === "FunctionExpression" || n.type === "FunctionDeclaration" || n.type === "FunctionDeclarationStatement" || n.type === "FunctionBody");
1709
+ }
1710
+ function valueItems(analysis, at2, inFunction) {
1581
1711
  const items = [];
1582
1712
  const seen = /* @__PURE__ */ new Set();
1583
1713
  for (const binding of analysis.scopes.bindings.values()) {
1584
1714
  if (binding.name === PLACEHOLDER || seen.has(binding.name)) continue;
1585
1715
  if (binding.declaredBy === "type") continue;
1586
1716
  const declaration = binding.declarationNode;
1587
- if (declaration && declaration.line.start - 1 > at.line) continue;
1717
+ const later = declaration !== void 0 && declaration.line.start - 1 > at2.line;
1718
+ if (later && !inFunction && binding.declaredBy !== "function") continue;
1588
1719
  seen.add(binding.name);
1589
1720
  const type = analysis.types.bindingType.get(binding.id);
1590
1721
  items.push({
1591
1722
  label: binding.name,
1592
1723
  kind: kindOf(type, binding.kind),
1593
- detail: type ? (0, import_luaut_parser6.formatType)(type) : void 0,
1724
+ detail: type ? (0, import_luaut_parser7.formatType)(type) : void 0,
1594
1725
  // Locals before globals, and globals before library names.
1595
1726
  sortText: `${binding.isBuiltin ? 2 : binding.kind === "global" ? 1 : 0}${binding.name}`
1596
1727
  });
@@ -1614,7 +1745,7 @@ function memberItem(name, type, readonly) {
1614
1745
  return {
1615
1746
  label: name,
1616
1747
  kind: import_vscode_languageserver5.CompletionItemKind.Field,
1617
- detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser6.formatType)(type)}`
1748
+ detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser7.formatType)(type)}`
1618
1749
  };
1619
1750
  }
1620
1751
  function kindOf(type, bindingKind) {
@@ -1722,9 +1853,9 @@ function helpAt(analysis, position) {
1722
1853
  function methodType(analysis, call) {
1723
1854
  const object = call.object;
1724
1855
  const method = call.method;
1725
- const objectType = analysis.types.typeOf.get(object);
1726
- if (!objectType) return void 0;
1727
- return memberType(objectType, method.name, analysis);
1856
+ const objectType2 = analysis.types.typeOf.get(object);
1857
+ if (!objectType2) return void 0;
1858
+ return memberType(objectType2, method.name, analysis);
1728
1859
  }
1729
1860
  function memberType(type, name, analysis) {
1730
1861
  if (type.kind === "object") return type.properties.get(name)?.type;
@@ -1755,7 +1886,7 @@ function activeArgument(call, position) {
1755
1886
 
1756
1887
  // src/features/symbols.ts
1757
1888
  var import_vscode_languageserver6 = require("vscode-languageserver");
1758
- var import_luaut_parser7 = require("luaut-parser");
1889
+ var import_luaut_parser8 = require("luaut-parser");
1759
1890
  function documentSymbols(analysis) {
1760
1891
  const out = [];
1761
1892
  walk(analysis.program, (node) => {
@@ -1772,10 +1903,19 @@ function documentSymbols(analysis) {
1772
1903
  const name = typeof named === "string" ? named : named?.name;
1773
1904
  if (name) {
1774
1905
  const alias = analysis.types.aliases.get(name);
1775
- out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Interface, node, alias ? (0, import_luaut_parser7.formatType)(alias) : void 0));
1906
+ out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Interface, node, alias ? (0, import_luaut_parser8.formatType)(alias) : void 0));
1776
1907
  }
1777
1908
  break;
1778
1909
  }
1910
+ case "ClassDeclaration": {
1911
+ const declaration = node;
1912
+ const superclass = declaration.superclass?.name;
1913
+ out.push({
1914
+ ...symbol(declaration.name.name, import_vscode_languageserver6.SymbolKind.Class, node, superclass && `extends ${superclass}`),
1915
+ children: declaration.members.map((member) => classMember(analysis, member))
1916
+ });
1917
+ break;
1918
+ }
1779
1919
  case "DeclareClassStatement": {
1780
1920
  const name = node.name.name;
1781
1921
  const superclass = node.superclass?.base;
@@ -1793,6 +1933,23 @@ function documentSymbols(analysis) {
1793
1933
  });
1794
1934
  return out;
1795
1935
  }
1936
+ function classMember(analysis, member) {
1937
+ switch (member.type) {
1938
+ case "ClassConstructor":
1939
+ return symbol("constructor", import_vscode_languageserver6.SymbolKind.Constructor, member);
1940
+ case "ClassField":
1941
+ return symbol(
1942
+ member.name.name,
1943
+ member.isStatic ? import_vscode_languageserver6.SymbolKind.Constant : import_vscode_languageserver6.SymbolKind.Field,
1944
+ member,
1945
+ member.typeAnnotation ? void 0 : detailOf(analysis, member)
1946
+ );
1947
+ case "ClassAccessor":
1948
+ return symbol(member.name.name, import_vscode_languageserver6.SymbolKind.Property, member, member.kind);
1949
+ case "ClassMethod":
1950
+ return symbol(member.name.name, import_vscode_languageserver6.SymbolKind.Method, member, member.isStatic ? "static" : void 0);
1951
+ }
1952
+ }
1796
1953
  function functionName(node) {
1797
1954
  const named = node;
1798
1955
  if (typeof named.name === "string") return named.name;
@@ -1809,7 +1966,7 @@ function detailOf(analysis, node) {
1809
1966
  if (name && typeof name === "object") {
1810
1967
  const binding = bindingOfNode(analysis, name);
1811
1968
  const type = binding && analysis.types.bindingType.get(binding.id);
1812
- if (type) return (0, import_luaut_parser7.formatType)(type);
1969
+ if (type) return (0, import_luaut_parser8.formatType)(type);
1813
1970
  }
1814
1971
  return void 0;
1815
1972
  }
@@ -1819,7 +1976,7 @@ function symbol(name, kind, node, detail) {
1819
1976
  }
1820
1977
 
1821
1978
  // src/features/semanticTokens.ts
1822
- var import_luaut_parser8 = require("luaut-parser");
1979
+ var import_luaut_parser9 = require("luaut-parser");
1823
1980
  var TOKEN_TYPES = [
1824
1981
  "namespace",
1825
1982
  "type",
@@ -1849,21 +2006,23 @@ var SOFT_KEYWORDS = /* @__PURE__ */ new Set([
1849
2006
  "asserts",
1850
2007
  "satisfies",
1851
2008
  "typeof",
1852
- "default"
2009
+ "default",
2010
+ "new",
2011
+ "super"
1853
2012
  ]);
1854
2013
  var CONTROL_KEYWORDS = /* @__PURE__ */ new Set(["default"]);
1855
2014
  var PRIMITIVES3 = /* @__PURE__ */ new Set(["any", "unknown", "never", "nil", "boolean", "number", "string", "thread", "buffer"]);
1856
2015
  function semanticTokens(analysis) {
1857
2016
  const entries = /* @__PURE__ */ new Map();
1858
- const add = (at, length, type, modifiers = []) => {
1859
- const line = at.line.start - 1;
1860
- const character = at.column.start - 1;
2017
+ const add = (at2, length, type, modifiers = []) => {
2018
+ const line = at2.line.start - 1;
2019
+ const character = at2.column.start - 1;
1861
2020
  const key = `${line}:${character}`;
1862
2021
  if (!entries.has(key)) entries.set(key, { line, character, length, type, modifiers });
1863
2022
  };
1864
2023
  let tokens = [];
1865
2024
  try {
1866
- tokens = (0, import_luaut_parser8.tokenize)(analysis.source);
2025
+ tokens = (0, import_luaut_parser9.tokenize)(analysis.source);
1867
2026
  } catch {
1868
2027
  }
1869
2028
  const identifiers = tokens.filter((t) => t.type === "Identifier");
@@ -1898,6 +2057,25 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1898
2057
  add(node, name.length, valueKind(analysis, binding), modifiersOf(binding, true));
1899
2058
  return;
1900
2059
  }
2060
+ // A class body's own words. `get`, `set`, `static` and `constructor`
2061
+ // are ordinary names anywhere else, so they are coloured from the
2062
+ // member they open rather than wherever they are written.
2063
+ case "ClassMethod":
2064
+ case "ClassField":
2065
+ case "ClassAccessor":
2066
+ case "ClassConstructor": {
2067
+ const opener = node.type === "ClassConstructor" ? "constructor" : node.type === "ClassAccessor" ? node.kind : void 0;
2068
+ const words = firstTokensWithin(identifiers, node, 2);
2069
+ let index = 0;
2070
+ if (node.isStatic === true && words[index] && wordOf(words[index]) === "static") {
2071
+ add(words[index], "static".length, "keyword");
2072
+ index++;
2073
+ }
2074
+ if (opener && words[index] && wordOf(words[index]) === opener) {
2075
+ add(words[index], opener.length, "keyword");
2076
+ }
2077
+ return;
2078
+ }
1901
2079
  case "TypeReference": {
1902
2080
  const base = node.base;
1903
2081
  const namespace = node.namespace;
@@ -1907,7 +2085,7 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1907
2085
  if (!baseToken) return;
1908
2086
  if (!namespace && typeParameterInScope2(ancestors, base)) {
1909
2087
  add(baseToken, base.length, "typeParameter");
1910
- } else if ((0, import_luaut_parser8.isClassType)(analysis.types.aliases.get(namespace ? `${namespace}.${base}` : base) ?? import_luaut_parser8.unknownType)) {
2088
+ } else if ((0, import_luaut_parser9.isClassType)(analysis.types.aliases.get(namespace ? `${namespace}.${base}` : base) ?? import_luaut_parser9.unknownType)) {
1911
2089
  add(baseToken, base.length, "class");
1912
2090
  } else {
1913
2091
  add(baseToken, base.length, "type", PRIMITIVES3.has(base) ? ["defaultLibrary"] : []);
@@ -1916,6 +2094,10 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1916
2094
  }
1917
2095
  }
1918
2096
  }
2097
+ function wordOf(token) {
2098
+ const value = token.value;
2099
+ return typeof value === "string" ? value : void 0;
2100
+ }
1919
2101
  function identifier(analysis, node, parent, add) {
1920
2102
  const name = node.name;
1921
2103
  const as = (type, modifiers = []) => add(node, name.length, type, modifiers);
@@ -1939,6 +2121,19 @@ function identifier(analysis, node, parent, add) {
1939
2121
  case "DeclareClassStatement":
1940
2122
  if (parent.name === node) return as("class", ["declaration"]);
1941
2123
  break;
2124
+ case "ClassDeclaration":
2125
+ if (parent.name === node) return as("class", ["declaration"]);
2126
+ if (parent.superclass === node) return as("class");
2127
+ break;
2128
+ case "ClassMethod":
2129
+ if (parent.name === node) return as("method", ["declaration"]);
2130
+ break;
2131
+ case "ClassField":
2132
+ if (parent.name === node) return as("property", ["declaration"]);
2133
+ break;
2134
+ case "ClassAccessor":
2135
+ if (parent.name === node) return as("property", ["declaration"]);
2136
+ break;
1942
2137
  case "DeclareStatement":
1943
2138
  if (parent.id === node) return as(isFunction(typeOfNode(parent.valueType)) ? "function" : "variable", ["declaration"]);
1944
2139
  break;
@@ -2159,6 +2354,11 @@ function createServer(connection, options = {}) {
2159
2354
  (d) => hover(analyzer.get(d), p.position),
2160
2355
  null
2161
2356
  ));
2357
+ connection.onRequest("luaut/hover", (p) => withDocument(
2358
+ p.textDocument.uri,
2359
+ (d) => hover(analyzer.get(d), p.position, Math.max(0, p.depth ?? 0)),
2360
+ null
2361
+ ));
2162
2362
  connection.onDefinition((p) => withDocument(
2163
2363
  p.textDocument.uri,
2164
2364
  (d) => {