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.cjs CHANGED
@@ -81,7 +81,8 @@ function membersOf(type, aliases, seen = /* @__PURE__ */ new Set()) {
81
81
  }
82
82
  function takesSelf(type) {
83
83
  for (const signature of signaturesOf(type)) {
84
- if (signature.params[0]?.name === "self") return true;
84
+ const first = signature.params[0]?.name;
85
+ if (first === "self" || first === "this") return true;
85
86
  }
86
87
  return false;
87
88
  }
@@ -102,7 +103,7 @@ function signatureLabel(signature) {
102
103
  });
103
104
  const generics = signature.typeParams?.length ? `<${signature.typeParams.join(", ")}>` : "";
104
105
  const varargs = signature.varargs ? [`...: ${(0, import_luaut_parser.formatType)(signature.varargs)}`] : [];
105
- const label = `${generics}(${[...parameters, ...varargs].join(", ")}) -> ${(0, import_luaut_parser.formatType)(signature.returns)}`;
106
+ const label = `${generics}(${[...parameters, ...varargs].join(", ")}) => ${(0, import_luaut_parser.formatType)(signature.returns)}`;
106
107
  return { label, parameters };
107
108
  }
108
109
 
@@ -682,7 +683,7 @@ function exportDeclaration(analyzer, module2, name, seen = /* @__PURE__ */ new S
682
683
  break;
683
684
  case "ExportStatement": {
684
685
  const declaration = statement.declaration;
685
- if (declaration.type === "FunctionDeclaration") {
686
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
686
687
  if (declaration.name.name === name) return here(declaration.name);
687
688
  } else {
688
689
  for (const target of declaration.names) {
@@ -794,16 +795,100 @@ function diagnostics(analysis) {
794
795
  }
795
796
 
796
797
  // src/features/hover.ts
798
+ var import_luaut_parser6 = require("luaut-parser");
799
+
800
+ // src/features/expand.ts
797
801
  var import_luaut_parser5 = require("luaut-parser");
798
- function hover(analysis, position) {
802
+ function expandAliases(type, aliases, depth, seen = []) {
803
+ if (depth <= 0) return type;
804
+ const name = withheldName(type);
805
+ if (name !== void 0 && !seen.includes(name)) {
806
+ const opened = aliases.get(name) ?? type;
807
+ const inner = [...seen, name];
808
+ return children2(unnamed(opened), (t) => expandAliases(t, aliases, depth - 1, inner));
809
+ }
810
+ return children2(type, (t) => expandAliases(t, aliases, depth, seen));
811
+ }
812
+ function withheldName(type) {
813
+ if ((0, import_luaut_parser5.isClassType)(type)) return void 0;
814
+ if (type.kind === "object" || type.kind === "intersection") return type.name;
815
+ if (type.kind === "genericRef") return type.typeArguments.length ? void 0 : type.name;
816
+ return void 0;
817
+ }
818
+ function unnamed(type) {
819
+ if (type.kind === "object") {
820
+ const out = (0, import_luaut_parser5.objectType)(type.properties, type.indexer, type.frozen);
821
+ return type.class ? Object.assign(out, { class: type.class, name: type.name }) : out;
822
+ }
823
+ if (type.kind === "intersection" && type.name) return { ...type, name: void 0 };
824
+ return type;
825
+ }
826
+ function children2(type, f) {
827
+ switch (type.kind) {
828
+ case "array":
829
+ return (0, import_luaut_parser5.arrayOf)(f(type.element));
830
+ case "tuple":
831
+ return (0, import_luaut_parser5.tuple)(type.elements.map(f), type.isPack);
832
+ case "union":
833
+ return (0, import_luaut_parser5.union)(type.types.map(f));
834
+ case "intersection": {
835
+ const out = (0, import_luaut_parser5.intersection)(type.types.map(f));
836
+ return type.name && out.kind === "intersection" ? { ...out, name: type.name } : out;
837
+ }
838
+ case "object": {
839
+ if ((0, import_luaut_parser5.isClassType)(type)) return type;
840
+ const out = (0, import_luaut_parser5.objectType)(
841
+ [...type.properties].map(([name, property]) => [name, { ...property, type: f(property.type) }]),
842
+ type.indexer && { key: type.indexer.key, value: f(type.indexer.value) },
843
+ type.frozen
844
+ );
845
+ if (type.name) out.name = type.name;
846
+ return out;
847
+ }
848
+ case "function":
849
+ return (0, import_luaut_parser5.fn)(
850
+ type.params.map((p) => ({ ...p, type: f(p.type) })),
851
+ f(type.returns),
852
+ type.varargs && f(type.varargs),
853
+ type.typeParams,
854
+ type.predicate
855
+ );
856
+ default:
857
+ return type;
858
+ }
859
+ }
860
+
861
+ // src/features/hover.ts
862
+ function hover(analysis, position, depth = 0) {
799
863
  const path = pathAt(analysis.program, position, true);
800
864
  for (let i = path.length - 1; i >= 0; i--) {
801
865
  if (UNNAMED.has(path[i].type)) return null;
802
- const text = describe(analysis, path, i);
803
- if (text) return { contents: { kind: "markdown", value: code(text) }, range: toRange(path[i]) };
866
+ const text = at(analysis, path, i, depth);
867
+ if (!text) continue;
868
+ const canExpand = at(analysis, path, i, depth + 1) !== text;
869
+ return {
870
+ contents: { kind: "markdown", value: code(text) },
871
+ range: toRange(path[i]),
872
+ depth,
873
+ canExpand
874
+ };
804
875
  }
805
876
  return null;
806
877
  }
878
+ function at(analysis, path, index, depth) {
879
+ const previous = expansion;
880
+ expansion = { depth, aliases: analysis.types.aliases, source: analysis.source };
881
+ try {
882
+ return describe(analysis, path, index);
883
+ } finally {
884
+ expansion = previous;
885
+ }
886
+ }
887
+ var expansion = {
888
+ depth: 0,
889
+ aliases: /* @__PURE__ */ new Map(),
890
+ source: ""
891
+ };
807
892
  var UNNAMED = /* @__PURE__ */ new Set([
808
893
  "BinaryExpression",
809
894
  "UnaryExpression",
@@ -836,8 +921,8 @@ function describe(analysis, path, index) {
836
921
  case "TableExpression": {
837
922
  const field = fieldWithKey(parent, node);
838
923
  if (!field) break;
839
- const objectType = types.typeOf.get(parent);
840
- const property = objectType?.kind === "object" ? objectType.properties.get(name) : void 0;
924
+ const objectType2 = types.typeOf.get(parent);
925
+ const property = objectType2?.kind === "object" ? objectType2.properties.get(name) : void 0;
841
926
  const type2 = property?.type ?? types.typeOf.get(field.value);
842
927
  return type2 && `(property) ${name}: ${pretty(type2)}`;
843
928
  }
@@ -885,6 +970,19 @@ function describe(analysis, path, index) {
885
970
  case "DeclareClassStatement":
886
971
  if (parent.name === node) return classText(analysis, name);
887
972
  break;
973
+ case "ClassDeclaration":
974
+ if (parent.name === node) return classText(analysis, name);
975
+ break;
976
+ case "ClassField":
977
+ if (parent.name === node) {
978
+ const type2 = typeOfNode(parent.typeAnnotation) ?? types.typeOf.get(parent.init);
979
+ const prefix = parent.isStatic ? "(static) " : "(field) ";
980
+ return type2 ? `${prefix}${name}: ${pretty(type2)}` : `${prefix}${name}`;
981
+ }
982
+ break;
983
+ case "ClassAccessor":
984
+ if (parent.name === node) return `(${parent.kind === "get" ? "getter" : "setter"}) ${name}`;
985
+ break;
888
986
  case "TableTypeProperty":
889
987
  if (parent.key === node) {
890
988
  const type2 = typeOfNode(parent.valueType);
@@ -907,7 +1005,7 @@ function describe(analysis, path, index) {
907
1005
  case "MappedTypeNode":
908
1006
  if (parent.parameterId === node) {
909
1007
  const keys = typeOfNode(parent.constraint);
910
- return `(type parameter) ${name}${keys ? ` in ${(0, import_luaut_parser5.formatType)(keys)}` : ""}`;
1008
+ return `(type parameter) ${name}${keys ? ` in ${(0, import_luaut_parser6.formatType)(keys)}` : ""}`;
911
1009
  }
912
1010
  break;
913
1011
  }
@@ -935,7 +1033,7 @@ function describe(analysis, path, index) {
935
1033
  case "TypedIdentifier": {
936
1034
  const binding = bindingOfNode(analysis, node);
937
1035
  const type2 = binding && types.bindingType.get(binding.id);
938
- return type2 ? bindingText(binding, type2) : void 0;
1036
+ return type2 ? bindingText(binding, type2, asWritten(node.typeAnnotation)) : void 0;
939
1037
  }
940
1038
  // A type written by name: `number`, `Shape`, `Partial<User>`, or a type
941
1039
  // parameter in scope.
@@ -949,7 +1047,7 @@ function describe(analysis, path, index) {
949
1047
  if (!node.typeArguments.length) {
950
1048
  const qualified = node.namespace ? `${node.namespace}.${base}` : base;
951
1049
  const alias = types.aliases.get(qualified);
952
- if (alias && (0, import_luaut_parser5.isClassType)(alias)) return classText(analysis, qualified);
1050
+ if (alias && (0, import_luaut_parser6.isClassType)(alias)) return classText(analysis, qualified);
953
1051
  if (alias) return `type ${qualified} = ${pretty(alias)}`;
954
1052
  }
955
1053
  const type2 = typeOfNode(node);
@@ -977,20 +1075,29 @@ function declareText(analysis, statement) {
977
1075
  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);
978
1076
  const others = total - 1;
979
1077
  const overloads = others > 0 ? ` (+${others} overload${others > 1 ? "s" : ""})` : "";
980
- return `declare function ${name}${(0, import_luaut_parser5.formatType)(own)}${overloads}`;
1078
+ return `declare function ${name}${(0, import_luaut_parser6.formatType)(own)}${overloads}`;
981
1079
  }
982
1080
  function classText(analysis, name) {
983
1081
  const type = analysis.types.aliases.get(name);
984
- if (!type || !(0, import_luaut_parser5.isClassType)(type)) return void 0;
1082
+ if (!type || !(0, import_luaut_parser6.isClassType)(type)) return void 0;
985
1083
  const superclass = type.class.superclass;
986
1084
  const inherited = superclass ? analysis.types.aliases.get(superclass) : void 0;
987
- const own = [...type.properties].filter(([key, property]) => inherited?.kind !== "object" || inherited.properties.get(key) !== property);
988
- const head = `declare class ${name}${superclass ? ` extends ${superclass}` : ""}`;
989
- if (!own.length) return `${head} {}`;
990
- const lines = own.map(([key, property]) => ` ${property.readonly ? "readonly " : ""}${key}${property.optional ? "?" : ""}: ${(0, import_luaut_parser5.formatType)(property.type)},`);
991
- return `${head} {
1085
+ const own = [...type.properties].filter(([key, property]) => (
1086
+ // `ClassObject` is on every instance and says nothing about this one.
1087
+ key !== "ClassObject" && (inherited?.kind !== "object" || inherited.properties.get(key) !== property)
1088
+ ));
1089
+ const written = isRuntimeClass(analysis, name);
1090
+ const head = `${written ? "" : "declare "}class ${name}${superclass ? ` extends ${superclass}` : ""}`;
1091
+ const lines = own.map(([key, property]) => ` ${property.readonly ? "readonly " : ""}${key}${property.optional ? "?" : ""}: ${(0, import_luaut_parser6.formatType)(property.type)}${written ? "" : ","}`);
1092
+ return own.length ? `${head} {
992
1093
  ${lines.join("\n")}
993
- }`;
1094
+ }` : `${head} {}`;
1095
+ }
1096
+ function isRuntimeClass(analysis, name) {
1097
+ return analysis.program.body.statements.some((statement) => {
1098
+ const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
1099
+ return declaration.type === "ClassDeclaration" && declaration.name.name === name;
1100
+ });
994
1101
  }
995
1102
  function typeParameterText(analysis, parameter) {
996
1103
  return `(type parameter) ${typeParameterSignature(analysis, parameter)}`;
@@ -999,7 +1106,7 @@ function typeParameterSignature(analysis, parameter) {
999
1106
  const p = parameter;
1000
1107
  if (p.infer) return `infer ${p.name}`;
1001
1108
  const constraint = p.constraint ? analysis.types.typeOfTypeNode.get(p.constraint) : void 0;
1002
- return `${p.isConst ? "const " : ""}${p.name}${constraint ? ` extends ${(0, import_luaut_parser5.formatType)(constraint)}` : ""}`;
1109
+ return `${p.isConst ? "const " : ""}${p.name}${constraint ? ` extends ${(0, import_luaut_parser6.formatType)(constraint)}` : ""}`;
1003
1110
  }
1004
1111
  function typeParameterInScope(path, index, name) {
1005
1112
  for (let i = index - 1; i >= 0; i--) {
@@ -1024,7 +1131,7 @@ function referenceText(analysis, reference) {
1024
1131
  if (!args.length) return name;
1025
1132
  const resolved = args.map((a) => {
1026
1133
  const t = analysis.types.typeOfTypeNode.get(a);
1027
- return t ? (0, import_luaut_parser5.formatType)(t) : "?";
1134
+ return t ? (0, import_luaut_parser6.formatType)(t) : "?";
1028
1135
  });
1029
1136
  return `${name}<${resolved.join(", ")}>`;
1030
1137
  }
@@ -1032,30 +1139,50 @@ function fieldWithKey(table, key) {
1032
1139
  const fields = table.fields;
1033
1140
  return fields.find((f) => f.type === "TableFieldNamed" && f.key === key);
1034
1141
  }
1035
- function pretty(type) {
1036
- const flat = (0, import_luaut_parser5.formatType)(type);
1142
+ function pretty(type, written) {
1143
+ const named = render(expandAliases(type, expansion.aliases, 0));
1144
+ const shorthand = written !== void 0 && written !== named ? written : void 0;
1145
+ const depth = shorthand === void 0 ? expansion.depth : expansion.depth - 1;
1146
+ if (depth < 0) return shorthand;
1147
+ return depth === 0 ? named : render(expandAliases(type, expansion.aliases, depth));
1148
+ }
1149
+ function asWritten(node) {
1150
+ if (!isSpanned(node) || !expansion.source) return void 0;
1151
+ const lines = expansion.source.split(/\r?\n/);
1152
+ const { line, column } = node;
1153
+ if (line.start < 1 || line.end > lines.length) return void 0;
1154
+ const text = line.start === line.end ? lines[line.start - 1].slice(column.start - 1, column.end - 1) : [
1155
+ lines[line.start - 1].slice(column.start - 1),
1156
+ ...lines.slice(line.start, line.end - 1),
1157
+ lines[line.end - 1].slice(0, column.end - 1)
1158
+ ].join(" ");
1159
+ const folded = text.trim().replace(/\s+/g, " ");
1160
+ return folded.length ? folded : void 0;
1161
+ }
1162
+ function render(type) {
1163
+ const flat = (0, import_luaut_parser6.formatType)(type);
1037
1164
  if (flat.length <= 80) return flat;
1038
1165
  if (type.kind === "object") {
1039
1166
  const lines = [];
1040
- if (type.indexer) lines.push(` [${(0, import_luaut_parser5.formatType)(type.indexer.key)}]: ${(0, import_luaut_parser5.formatType)(type.indexer.value)},`);
1167
+ if (type.indexer) lines.push(` [${(0, import_luaut_parser6.formatType)(type.indexer.key)}]: ${(0, import_luaut_parser6.formatType)(type.indexer.value)},`);
1041
1168
  for (const [name, property] of type.properties) {
1042
1169
  const readonly = property.readonly ? "readonly " : "";
1043
- lines.push(` ${readonly}${name}${property.optional ? "?" : ""}: ${(0, import_luaut_parser5.formatType)(property.type)},`);
1170
+ lines.push(` ${readonly}${name}${property.optional ? "?" : ""}: ${(0, import_luaut_parser6.formatType)(property.type)},`);
1044
1171
  }
1045
1172
  return `{
1046
1173
  ${lines.join("\n")}
1047
1174
  }`;
1048
1175
  }
1049
1176
  if (type.kind === "intersection" && type.types.every((t) => t.kind === "function")) {
1050
- return type.types.map(import_luaut_parser5.formatType).join("\n& ");
1177
+ return type.types.map(import_luaut_parser6.formatType).join("\n& ");
1051
1178
  }
1052
1179
  return flat;
1053
1180
  }
1054
- function bindingText(binding, type) {
1181
+ function bindingText(binding, type, written) {
1055
1182
  if (binding.declaredBy === "function" && type.kind === "function") {
1056
- return `function ${binding.name}${(0, import_luaut_parser5.formatType)(type)}`;
1183
+ return `function ${binding.name}${pretty(type)}`;
1057
1184
  }
1058
- return `${keyword(binding)} ${binding.name}: ${pretty(type)}`;
1185
+ return `${keyword(binding)} ${binding.name}: ${pretty(type, written)}`;
1059
1186
  }
1060
1187
  function keyword(binding) {
1061
1188
  if (binding.kind === "param" || binding.kind === "self") return "(parameter)";
@@ -1131,7 +1258,7 @@ function isIdentifier(name) {
1131
1258
 
1132
1259
  // src/features/completion.ts
1133
1260
  var import_vscode_languageserver5 = require("vscode-languageserver");
1134
- var import_luaut_parser6 = require("luaut-parser");
1261
+ var import_luaut_parser7 = require("luaut-parser");
1135
1262
 
1136
1263
  // src/features/autoImport.ts
1137
1264
  var import_node_fs3 = require("fs");
@@ -1168,7 +1295,7 @@ function importItems(analyzer, analysis, typePosition, taken) {
1168
1295
  function serviceItems(analysis, taken) {
1169
1296
  const services = analysis.types.aliases.get("Services");
1170
1297
  if (!services || services.kind !== "object") return [];
1171
- const at = serviceInsertion(analysis.program.body.statements);
1298
+ const at2 = serviceInsertion(analysis.program.body.statements);
1172
1299
  const items = [];
1173
1300
  for (const name of services.properties.keys()) {
1174
1301
  if (taken.has(name)) continue;
@@ -1179,8 +1306,8 @@ function serviceItems(analysis, taken) {
1179
1306
  labelDetails: { description: "service" },
1180
1307
  detail: line,
1181
1308
  sortText: `5${name}`,
1182
- additionalTextEdits: [{ range: { start: at.position, end: at.position }, newText: `${line}
1183
- ${at.gap}` }]
1309
+ additionalTextEdits: [{ range: { start: at2.position, end: at2.position }, newText: `${line}
1310
+ ${at2.gap}` }]
1184
1311
  });
1185
1312
  }
1186
1313
  return items;
@@ -1192,24 +1319,24 @@ function importEdit(analyzer, analysis, file, name, specifier, typePosition) {
1192
1319
  if (existing) {
1193
1320
  const last = existing.specifiers[existing.specifiers.length - 1];
1194
1321
  if (last) {
1195
- const at2 = endOf(last);
1196
- return { range: { start: at2, end: at2 }, newText: `, ${name}` };
1322
+ const at3 = endOf(last);
1323
+ return { range: { start: at3, end: at3 }, newText: `, ${name}` };
1197
1324
  }
1198
1325
  if (existing.defaultImport) {
1199
- const at2 = endOf(existing.defaultImport);
1200
- return { range: { start: at2, end: at2 }, newText: `, { ${name} }` };
1326
+ const at3 = endOf(existing.defaultImport);
1327
+ return { range: { start: at3, end: at3 }, newText: `, { ${name} }` };
1201
1328
  }
1202
1329
  }
1203
1330
  const line = `import { ${name} } from "${specifier}"`;
1204
1331
  const lastImport = imports[imports.length - 1];
1205
1332
  if (lastImport) {
1206
- const at2 = { line: lastImport.line.end, character: 0 };
1207
- return { range: { start: at2, end: at2 }, newText: `${line}
1333
+ const at3 = { line: lastImport.line.end, character: 0 };
1334
+ return { range: { start: at3, end: at3 }, newText: `${line}
1208
1335
  ` };
1209
1336
  }
1210
1337
  const first = statements[0];
1211
- const at = { line: first ? first.line.start - 1 : 0, character: 0 };
1212
- return { range: { start: at, end: at }, newText: first ? `${line}
1338
+ const at2 = { line: first ? first.line.start - 1 : 0, character: 0 };
1339
+ return { range: { start: at2, end: at2 }, newText: first ? `${line}
1213
1340
 
1214
1341
  ` : `${line}
1215
1342
  ` };
@@ -1302,12 +1429,12 @@ function completion(analyzer, document, position) {
1302
1429
  const operator = memberOperator(source, start);
1303
1430
  const alreadyCalled = /^\s*\(/.test(source.slice(end));
1304
1431
  const standIns = operator === ":" ? [alreadyCalled ? PLACEHOLDER : `${PLACEHOLDER}()`] : operator === "." && !alreadyCalled ? [PLACEHOLDER, `${PLACEHOLDER}()`] : [PLACEHOLDER];
1305
- const at = { line: position.line, character: position.character - (offset - start) };
1432
+ const at2 = { line: position.line, character: position.character - (offset - start) };
1306
1433
  let first;
1307
1434
  for (const standIn of standIns) {
1308
1435
  const patched = source.slice(0, start) + standIn + source.slice(end);
1309
1436
  const analysis = analyzer.analyze(document.uri, -1, patched);
1310
- const path = pathAt(analysis.program, at, true);
1437
+ const path = pathAt(analysis.program, at2, true);
1311
1438
  const index = path.findLastIndex(
1312
1439
  (n) => n.type === "Identifier" && n.name === PLACEHOLDER
1313
1440
  );
@@ -1323,8 +1450,8 @@ function completion(analyzer, document, position) {
1323
1450
  if (inTypePosition(first.path)) {
1324
1451
  const named = [...first.analysis.types.aliases].map(([name, type]) => ({
1325
1452
  label: name,
1326
- kind: (0, import_luaut_parser6.isClassType)(type) ? import_vscode_languageserver5.CompletionItemKind.Class : import_vscode_languageserver5.CompletionItemKind.Interface,
1327
- detail: (0, import_luaut_parser6.isClassType)(type) ? "class" : "type"
1453
+ kind: (0, import_luaut_parser7.isClassType)(type) ? import_vscode_languageserver5.CompletionItemKind.Class : import_vscode_languageserver5.CompletionItemKind.Interface,
1454
+ detail: (0, import_luaut_parser7.isClassType)(type) ? "class" : "type"
1328
1455
  }));
1329
1456
  const primitives = PRIMITIVES2.map((name) => ({
1330
1457
  label: name,
@@ -1344,7 +1471,7 @@ function completion(analyzer, document, position) {
1344
1471
  for (const binding of first.analysis.scopes.bindings.values()) taken.add(binding.name);
1345
1472
  const current = analyzer.get(document);
1346
1473
  return [
1347
- ...valueItems(first.analysis, at),
1474
+ ...valueItems(first.analysis, at2, insideFunction(first.path)),
1348
1475
  ...contextKeywords(source.slice(0, start)),
1349
1476
  ...importItems(analyzer, current, false, taken),
1350
1477
  ...serviceItems(current, taken)
@@ -1412,14 +1539,14 @@ function objectKeyItems(analysis, path, after) {
1412
1539
  return members.filter((member) => !written.has(member.name)).map((member) => ({
1413
1540
  label: member.name,
1414
1541
  kind: import_vscode_languageserver5.CompletionItemKind.Field,
1415
- detail: `${member.property.optional ? "?" : ""}: ${(0, import_luaut_parser6.formatType)(member.property.type)}`,
1542
+ detail: `${member.property.optional ? "?" : ""}: ${(0, import_luaut_parser7.formatType)(member.property.type)}`,
1416
1543
  insertText: colon ? member.name : `${member.name}: `
1417
1544
  }));
1418
1545
  }
1419
1546
  function indexKeys(analysis, position, literal) {
1420
1547
  const path = pathAt(analysis.program, position, false);
1421
- const at = path.indexOf(literal);
1422
- const parent = at > 0 ? path[at - 1] : void 0;
1548
+ const at2 = path.indexOf(literal);
1549
+ const parent = at2 > 0 ? path[at2 - 1] : void 0;
1423
1550
  if (!parent) return [];
1424
1551
  let indexed;
1425
1552
  if (parent.type === "IndexedAccessTypeNode" && parent.indexType === literal) {
@@ -1511,20 +1638,24 @@ function isMethodOnly(type) {
1511
1638
  return false;
1512
1639
  }
1513
1640
  }
1514
- function valueItems(analysis, at) {
1641
+ function insideFunction(path) {
1642
+ return path.some((n) => n.type === "FunctionExpression" || n.type === "FunctionDeclaration" || n.type === "FunctionDeclarationStatement" || n.type === "FunctionBody");
1643
+ }
1644
+ function valueItems(analysis, at2, inFunction) {
1515
1645
  const items = [];
1516
1646
  const seen = /* @__PURE__ */ new Set();
1517
1647
  for (const binding of analysis.scopes.bindings.values()) {
1518
1648
  if (binding.name === PLACEHOLDER || seen.has(binding.name)) continue;
1519
1649
  if (binding.declaredBy === "type") continue;
1520
1650
  const declaration = binding.declarationNode;
1521
- if (declaration && declaration.line.start - 1 > at.line) continue;
1651
+ const later = declaration !== void 0 && declaration.line.start - 1 > at2.line;
1652
+ if (later && !inFunction && binding.declaredBy !== "function") continue;
1522
1653
  seen.add(binding.name);
1523
1654
  const type = analysis.types.bindingType.get(binding.id);
1524
1655
  items.push({
1525
1656
  label: binding.name,
1526
1657
  kind: kindOf(type, binding.kind),
1527
- detail: type ? (0, import_luaut_parser6.formatType)(type) : void 0,
1658
+ detail: type ? (0, import_luaut_parser7.formatType)(type) : void 0,
1528
1659
  // Locals before globals, and globals before library names.
1529
1660
  sortText: `${binding.isBuiltin ? 2 : binding.kind === "global" ? 1 : 0}${binding.name}`
1530
1661
  });
@@ -1548,7 +1679,7 @@ function memberItem(name, type, readonly) {
1548
1679
  return {
1549
1680
  label: name,
1550
1681
  kind: import_vscode_languageserver5.CompletionItemKind.Field,
1551
- detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser6.formatType)(type)}`
1682
+ detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser7.formatType)(type)}`
1552
1683
  };
1553
1684
  }
1554
1685
  function kindOf(type, bindingKind) {
@@ -1656,9 +1787,9 @@ function helpAt(analysis, position) {
1656
1787
  function methodType(analysis, call) {
1657
1788
  const object = call.object;
1658
1789
  const method = call.method;
1659
- const objectType = analysis.types.typeOf.get(object);
1660
- if (!objectType) return void 0;
1661
- return memberType(objectType, method.name, analysis);
1790
+ const objectType2 = analysis.types.typeOf.get(object);
1791
+ if (!objectType2) return void 0;
1792
+ return memberType(objectType2, method.name, analysis);
1662
1793
  }
1663
1794
  function memberType(type, name, analysis) {
1664
1795
  if (type.kind === "object") return type.properties.get(name)?.type;
@@ -1689,7 +1820,7 @@ function activeArgument(call, position) {
1689
1820
 
1690
1821
  // src/features/symbols.ts
1691
1822
  var import_vscode_languageserver6 = require("vscode-languageserver");
1692
- var import_luaut_parser7 = require("luaut-parser");
1823
+ var import_luaut_parser8 = require("luaut-parser");
1693
1824
  function documentSymbols(analysis) {
1694
1825
  const out = [];
1695
1826
  walk(analysis.program, (node) => {
@@ -1706,10 +1837,19 @@ function documentSymbols(analysis) {
1706
1837
  const name = typeof named === "string" ? named : named?.name;
1707
1838
  if (name) {
1708
1839
  const alias = analysis.types.aliases.get(name);
1709
- out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Interface, node, alias ? (0, import_luaut_parser7.formatType)(alias) : void 0));
1840
+ out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Interface, node, alias ? (0, import_luaut_parser8.formatType)(alias) : void 0));
1710
1841
  }
1711
1842
  break;
1712
1843
  }
1844
+ case "ClassDeclaration": {
1845
+ const declaration = node;
1846
+ const superclass = declaration.superclass?.name;
1847
+ out.push({
1848
+ ...symbol(declaration.name.name, import_vscode_languageserver6.SymbolKind.Class, node, superclass && `extends ${superclass}`),
1849
+ children: declaration.members.map((member) => classMember(analysis, member))
1850
+ });
1851
+ break;
1852
+ }
1713
1853
  case "DeclareClassStatement": {
1714
1854
  const name = node.name.name;
1715
1855
  const superclass = node.superclass?.base;
@@ -1727,6 +1867,23 @@ function documentSymbols(analysis) {
1727
1867
  });
1728
1868
  return out;
1729
1869
  }
1870
+ function classMember(analysis, member) {
1871
+ switch (member.type) {
1872
+ case "ClassConstructor":
1873
+ return symbol("constructor", import_vscode_languageserver6.SymbolKind.Constructor, member);
1874
+ case "ClassField":
1875
+ return symbol(
1876
+ member.name.name,
1877
+ member.isStatic ? import_vscode_languageserver6.SymbolKind.Constant : import_vscode_languageserver6.SymbolKind.Field,
1878
+ member,
1879
+ member.typeAnnotation ? void 0 : detailOf(analysis, member)
1880
+ );
1881
+ case "ClassAccessor":
1882
+ return symbol(member.name.name, import_vscode_languageserver6.SymbolKind.Property, member, member.kind);
1883
+ case "ClassMethod":
1884
+ return symbol(member.name.name, import_vscode_languageserver6.SymbolKind.Method, member, member.isStatic ? "static" : void 0);
1885
+ }
1886
+ }
1730
1887
  function functionName(node) {
1731
1888
  const named = node;
1732
1889
  if (typeof named.name === "string") return named.name;
@@ -1743,7 +1900,7 @@ function detailOf(analysis, node) {
1743
1900
  if (name && typeof name === "object") {
1744
1901
  const binding = bindingOfNode(analysis, name);
1745
1902
  const type = binding && analysis.types.bindingType.get(binding.id);
1746
- if (type) return (0, import_luaut_parser7.formatType)(type);
1903
+ if (type) return (0, import_luaut_parser8.formatType)(type);
1747
1904
  }
1748
1905
  return void 0;
1749
1906
  }
@@ -1753,7 +1910,7 @@ function symbol(name, kind, node, detail) {
1753
1910
  }
1754
1911
 
1755
1912
  // src/features/semanticTokens.ts
1756
- var import_luaut_parser8 = require("luaut-parser");
1913
+ var import_luaut_parser9 = require("luaut-parser");
1757
1914
  var TOKEN_TYPES = [
1758
1915
  "namespace",
1759
1916
  "type",
@@ -1783,21 +1940,23 @@ var SOFT_KEYWORDS = /* @__PURE__ */ new Set([
1783
1940
  "asserts",
1784
1941
  "satisfies",
1785
1942
  "typeof",
1786
- "default"
1943
+ "default",
1944
+ "new",
1945
+ "super"
1787
1946
  ]);
1788
1947
  var CONTROL_KEYWORDS = /* @__PURE__ */ new Set(["default"]);
1789
1948
  var PRIMITIVES3 = /* @__PURE__ */ new Set(["any", "unknown", "never", "nil", "boolean", "number", "string", "thread", "buffer"]);
1790
1949
  function semanticTokens(analysis) {
1791
1950
  const entries = /* @__PURE__ */ new Map();
1792
- const add = (at, length, type, modifiers = []) => {
1793
- const line = at.line.start - 1;
1794
- const character = at.column.start - 1;
1951
+ const add = (at2, length, type, modifiers = []) => {
1952
+ const line = at2.line.start - 1;
1953
+ const character = at2.column.start - 1;
1795
1954
  const key = `${line}:${character}`;
1796
1955
  if (!entries.has(key)) entries.set(key, { line, character, length, type, modifiers });
1797
1956
  };
1798
1957
  let tokens = [];
1799
1958
  try {
1800
- tokens = (0, import_luaut_parser8.tokenize)(analysis.source);
1959
+ tokens = (0, import_luaut_parser9.tokenize)(analysis.source);
1801
1960
  } catch {
1802
1961
  }
1803
1962
  const identifiers = tokens.filter((t) => t.type === "Identifier");
@@ -1832,6 +1991,25 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1832
1991
  add(node, name.length, valueKind(analysis, binding), modifiersOf(binding, true));
1833
1992
  return;
1834
1993
  }
1994
+ // A class body's own words. `get`, `set`, `static` and `constructor`
1995
+ // are ordinary names anywhere else, so they are coloured from the
1996
+ // member they open rather than wherever they are written.
1997
+ case "ClassMethod":
1998
+ case "ClassField":
1999
+ case "ClassAccessor":
2000
+ case "ClassConstructor": {
2001
+ const opener = node.type === "ClassConstructor" ? "constructor" : node.type === "ClassAccessor" ? node.kind : void 0;
2002
+ const words = firstTokensWithin(identifiers, node, 2);
2003
+ let index = 0;
2004
+ if (node.isStatic === true && words[index] && wordOf(words[index]) === "static") {
2005
+ add(words[index], "static".length, "keyword");
2006
+ index++;
2007
+ }
2008
+ if (opener && words[index] && wordOf(words[index]) === opener) {
2009
+ add(words[index], opener.length, "keyword");
2010
+ }
2011
+ return;
2012
+ }
1835
2013
  case "TypeReference": {
1836
2014
  const base = node.base;
1837
2015
  const namespace = node.namespace;
@@ -1841,7 +2019,7 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1841
2019
  if (!baseToken) return;
1842
2020
  if (!namespace && typeParameterInScope2(ancestors, base)) {
1843
2021
  add(baseToken, base.length, "typeParameter");
1844
- } else if ((0, import_luaut_parser8.isClassType)(analysis.types.aliases.get(namespace ? `${namespace}.${base}` : base) ?? import_luaut_parser8.unknownType)) {
2022
+ } else if ((0, import_luaut_parser9.isClassType)(analysis.types.aliases.get(namespace ? `${namespace}.${base}` : base) ?? import_luaut_parser9.unknownType)) {
1845
2023
  add(baseToken, base.length, "class");
1846
2024
  } else {
1847
2025
  add(baseToken, base.length, "type", PRIMITIVES3.has(base) ? ["defaultLibrary"] : []);
@@ -1850,6 +2028,10 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1850
2028
  }
1851
2029
  }
1852
2030
  }
2031
+ function wordOf(token) {
2032
+ const value = token.value;
2033
+ return typeof value === "string" ? value : void 0;
2034
+ }
1853
2035
  function identifier(analysis, node, parent, add) {
1854
2036
  const name = node.name;
1855
2037
  const as = (type, modifiers = []) => add(node, name.length, type, modifiers);
@@ -1873,6 +2055,19 @@ function identifier(analysis, node, parent, add) {
1873
2055
  case "DeclareClassStatement":
1874
2056
  if (parent.name === node) return as("class", ["declaration"]);
1875
2057
  break;
2058
+ case "ClassDeclaration":
2059
+ if (parent.name === node) return as("class", ["declaration"]);
2060
+ if (parent.superclass === node) return as("class");
2061
+ break;
2062
+ case "ClassMethod":
2063
+ if (parent.name === node) return as("method", ["declaration"]);
2064
+ break;
2065
+ case "ClassField":
2066
+ if (parent.name === node) return as("property", ["declaration"]);
2067
+ break;
2068
+ case "ClassAccessor":
2069
+ if (parent.name === node) return as("property", ["declaration"]);
2070
+ break;
1876
2071
  case "DeclareStatement":
1877
2072
  if (parent.id === node) return as(isFunction(typeOfNode(parent.valueType)) ? "function" : "variable", ["declaration"]);
1878
2073
  break;
@@ -2093,6 +2288,11 @@ function createServer(connection, options = {}) {
2093
2288
  (d) => hover(analyzer.get(d), p.position),
2094
2289
  null
2095
2290
  ));
2291
+ connection.onRequest("luaut/hover", (p) => withDocument(
2292
+ p.textDocument.uri,
2293
+ (d) => hover(analyzer.get(d), p.position, Math.max(0, p.depth ?? 0)),
2294
+ null
2295
+ ));
2096
2296
  connection.onDefinition((p) => withDocument(
2097
2297
  p.textDocument.uri,
2098
2298
  (d) => {