luaut-language-server 3.1.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
@@ -61,13 +61,28 @@ function membersOf(type, aliases, seen = /* @__PURE__ */ new Set()) {
61
61
  }
62
62
  case "typeParam":
63
63
  return membersOf(type.constraint, aliases, seen);
64
+ // An array and a string answer to the methods the language gives them
65
+ // — `names:filter(f)`, `text:trim()`. They are written in the parser's
66
+ // prelude as `ArrayMethods<T>` and `StringMethods`, so the element
67
+ // type goes in where `T` stands.
68
+ case "array":
69
+ case "tuple": {
70
+ const element = type.kind === "array" ? type.element : (0, import_luaut_parser.union)(type.elements);
71
+ const methods = aliases.get("ArrayMethods");
72
+ return methods ? membersOf((0, import_luaut_parser.substitute)(methods, /* @__PURE__ */ new Map([["T", element]])), aliases, seen) : [];
73
+ }
74
+ case "primitive":
75
+ return type.name === "string" ? membersOf(aliases.get("StringMethods"), aliases, seen) : [];
76
+ case "literal":
77
+ return type.base === "string" ? membersOf(aliases.get("StringMethods"), aliases, seen) : [];
64
78
  default:
65
79
  return [];
66
80
  }
67
81
  }
68
82
  function takesSelf(type) {
69
83
  for (const signature of signaturesOf(type)) {
70
- if (signature.params[0]?.name === "self") return true;
84
+ const first = signature.params[0]?.name;
85
+ if (first === "self" || first === "this") return true;
71
86
  }
72
87
  return false;
73
88
  }
@@ -88,7 +103,7 @@ function signatureLabel(signature) {
88
103
  });
89
104
  const generics = signature.typeParams?.length ? `<${signature.typeParams.join(", ")}>` : "";
90
105
  const varargs = signature.varargs ? [`...: ${(0, import_luaut_parser.formatType)(signature.varargs)}`] : [];
91
- 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)}`;
92
107
  return { label, parameters };
93
108
  }
94
109
 
@@ -668,7 +683,7 @@ function exportDeclaration(analyzer, module2, name, seen = /* @__PURE__ */ new S
668
683
  break;
669
684
  case "ExportStatement": {
670
685
  const declaration = statement.declaration;
671
- if (declaration.type === "FunctionDeclaration") {
686
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
672
687
  if (declaration.name.name === name) return here(declaration.name);
673
688
  } else {
674
689
  for (const target of declaration.names) {
@@ -780,16 +795,100 @@ function diagnostics(analysis) {
780
795
  }
781
796
 
782
797
  // src/features/hover.ts
798
+ var import_luaut_parser6 = require("luaut-parser");
799
+
800
+ // src/features/expand.ts
783
801
  var import_luaut_parser5 = require("luaut-parser");
784
- 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) {
785
863
  const path = pathAt(analysis.program, position, true);
786
864
  for (let i = path.length - 1; i >= 0; i--) {
787
865
  if (UNNAMED.has(path[i].type)) return null;
788
- const text = describe(analysis, path, i);
789
- 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
+ };
790
875
  }
791
876
  return null;
792
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
+ };
793
892
  var UNNAMED = /* @__PURE__ */ new Set([
794
893
  "BinaryExpression",
795
894
  "UnaryExpression",
@@ -822,8 +921,8 @@ function describe(analysis, path, index) {
822
921
  case "TableExpression": {
823
922
  const field = fieldWithKey(parent, node);
824
923
  if (!field) break;
825
- const objectType = types.typeOf.get(parent);
826
- 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;
827
926
  const type2 = property?.type ?? types.typeOf.get(field.value);
828
927
  return type2 && `(property) ${name}: ${pretty(type2)}`;
829
928
  }
@@ -871,6 +970,19 @@ function describe(analysis, path, index) {
871
970
  case "DeclareClassStatement":
872
971
  if (parent.name === node) return classText(analysis, name);
873
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;
874
986
  case "TableTypeProperty":
875
987
  if (parent.key === node) {
876
988
  const type2 = typeOfNode(parent.valueType);
@@ -893,7 +1005,7 @@ function describe(analysis, path, index) {
893
1005
  case "MappedTypeNode":
894
1006
  if (parent.parameterId === node) {
895
1007
  const keys = typeOfNode(parent.constraint);
896
- 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)}` : ""}`;
897
1009
  }
898
1010
  break;
899
1011
  }
@@ -921,7 +1033,7 @@ function describe(analysis, path, index) {
921
1033
  case "TypedIdentifier": {
922
1034
  const binding = bindingOfNode(analysis, node);
923
1035
  const type2 = binding && types.bindingType.get(binding.id);
924
- return type2 ? bindingText(binding, type2) : void 0;
1036
+ return type2 ? bindingText(binding, type2, asWritten(node.typeAnnotation)) : void 0;
925
1037
  }
926
1038
  // A type written by name: `number`, `Shape`, `Partial<User>`, or a type
927
1039
  // parameter in scope.
@@ -935,7 +1047,7 @@ function describe(analysis, path, index) {
935
1047
  if (!node.typeArguments.length) {
936
1048
  const qualified = node.namespace ? `${node.namespace}.${base}` : base;
937
1049
  const alias = types.aliases.get(qualified);
938
- 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);
939
1051
  if (alias) return `type ${qualified} = ${pretty(alias)}`;
940
1052
  }
941
1053
  const type2 = typeOfNode(node);
@@ -963,20 +1075,29 @@ function declareText(analysis, statement) {
963
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);
964
1076
  const others = total - 1;
965
1077
  const overloads = others > 0 ? ` (+${others} overload${others > 1 ? "s" : ""})` : "";
966
- return `declare function ${name}${(0, import_luaut_parser5.formatType)(own)}${overloads}`;
1078
+ return `declare function ${name}${(0, import_luaut_parser6.formatType)(own)}${overloads}`;
967
1079
  }
968
1080
  function classText(analysis, name) {
969
1081
  const type = analysis.types.aliases.get(name);
970
- if (!type || !(0, import_luaut_parser5.isClassType)(type)) return void 0;
1082
+ if (!type || !(0, import_luaut_parser6.isClassType)(type)) return void 0;
971
1083
  const superclass = type.class.superclass;
972
1084
  const inherited = superclass ? analysis.types.aliases.get(superclass) : void 0;
973
- const own = [...type.properties].filter(([key, property]) => inherited?.kind !== "object" || inherited.properties.get(key) !== property);
974
- const head = `declare class ${name}${superclass ? ` extends ${superclass}` : ""}`;
975
- if (!own.length) return `${head} {}`;
976
- const lines = own.map(([key, property]) => ` ${property.readonly ? "readonly " : ""}${key}${property.optional ? "?" : ""}: ${(0, import_luaut_parser5.formatType)(property.type)},`);
977
- 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} {
978
1093
  ${lines.join("\n")}
979
- }`;
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
+ });
980
1101
  }
981
1102
  function typeParameterText(analysis, parameter) {
982
1103
  return `(type parameter) ${typeParameterSignature(analysis, parameter)}`;
@@ -985,7 +1106,7 @@ function typeParameterSignature(analysis, parameter) {
985
1106
  const p = parameter;
986
1107
  if (p.infer) return `infer ${p.name}`;
987
1108
  const constraint = p.constraint ? analysis.types.typeOfTypeNode.get(p.constraint) : void 0;
988
- 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)}` : ""}`;
989
1110
  }
990
1111
  function typeParameterInScope(path, index, name) {
991
1112
  for (let i = index - 1; i >= 0; i--) {
@@ -1010,7 +1131,7 @@ function referenceText(analysis, reference) {
1010
1131
  if (!args.length) return name;
1011
1132
  const resolved = args.map((a) => {
1012
1133
  const t = analysis.types.typeOfTypeNode.get(a);
1013
- return t ? (0, import_luaut_parser5.formatType)(t) : "?";
1134
+ return t ? (0, import_luaut_parser6.formatType)(t) : "?";
1014
1135
  });
1015
1136
  return `${name}<${resolved.join(", ")}>`;
1016
1137
  }
@@ -1018,30 +1139,50 @@ function fieldWithKey(table, key) {
1018
1139
  const fields = table.fields;
1019
1140
  return fields.find((f) => f.type === "TableFieldNamed" && f.key === key);
1020
1141
  }
1021
- function pretty(type) {
1022
- 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);
1023
1164
  if (flat.length <= 80) return flat;
1024
1165
  if (type.kind === "object") {
1025
1166
  const lines = [];
1026
- 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)},`);
1027
1168
  for (const [name, property] of type.properties) {
1028
1169
  const readonly = property.readonly ? "readonly " : "";
1029
- 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)},`);
1030
1171
  }
1031
1172
  return `{
1032
1173
  ${lines.join("\n")}
1033
1174
  }`;
1034
1175
  }
1035
1176
  if (type.kind === "intersection" && type.types.every((t) => t.kind === "function")) {
1036
- return type.types.map(import_luaut_parser5.formatType).join("\n& ");
1177
+ return type.types.map(import_luaut_parser6.formatType).join("\n& ");
1037
1178
  }
1038
1179
  return flat;
1039
1180
  }
1040
- function bindingText(binding, type) {
1181
+ function bindingText(binding, type, written) {
1041
1182
  if (binding.declaredBy === "function" && type.kind === "function") {
1042
- return `function ${binding.name}${(0, import_luaut_parser5.formatType)(type)}`;
1183
+ return `function ${binding.name}${pretty(type)}`;
1043
1184
  }
1044
- return `${keyword(binding)} ${binding.name}: ${pretty(type)}`;
1185
+ return `${keyword(binding)} ${binding.name}: ${pretty(type, written)}`;
1045
1186
  }
1046
1187
  function keyword(binding) {
1047
1188
  if (binding.kind === "param" || binding.kind === "self") return "(parameter)";
@@ -1117,7 +1258,7 @@ function isIdentifier(name) {
1117
1258
 
1118
1259
  // src/features/completion.ts
1119
1260
  var import_vscode_languageserver5 = require("vscode-languageserver");
1120
- var import_luaut_parser6 = require("luaut-parser");
1261
+ var import_luaut_parser7 = require("luaut-parser");
1121
1262
 
1122
1263
  // src/features/autoImport.ts
1123
1264
  var import_node_fs3 = require("fs");
@@ -1154,7 +1295,7 @@ function importItems(analyzer, analysis, typePosition, taken) {
1154
1295
  function serviceItems(analysis, taken) {
1155
1296
  const services = analysis.types.aliases.get("Services");
1156
1297
  if (!services || services.kind !== "object") return [];
1157
- const at = serviceInsertion(analysis.program.body.statements);
1298
+ const at2 = serviceInsertion(analysis.program.body.statements);
1158
1299
  const items = [];
1159
1300
  for (const name of services.properties.keys()) {
1160
1301
  if (taken.has(name)) continue;
@@ -1165,8 +1306,8 @@ function serviceItems(analysis, taken) {
1165
1306
  labelDetails: { description: "service" },
1166
1307
  detail: line,
1167
1308
  sortText: `5${name}`,
1168
- additionalTextEdits: [{ range: { start: at.position, end: at.position }, newText: `${line}
1169
- ${at.gap}` }]
1309
+ additionalTextEdits: [{ range: { start: at2.position, end: at2.position }, newText: `${line}
1310
+ ${at2.gap}` }]
1170
1311
  });
1171
1312
  }
1172
1313
  return items;
@@ -1178,24 +1319,24 @@ function importEdit(analyzer, analysis, file, name, specifier, typePosition) {
1178
1319
  if (existing) {
1179
1320
  const last = existing.specifiers[existing.specifiers.length - 1];
1180
1321
  if (last) {
1181
- const at2 = endOf(last);
1182
- return { range: { start: at2, end: at2 }, newText: `, ${name}` };
1322
+ const at3 = endOf(last);
1323
+ return { range: { start: at3, end: at3 }, newText: `, ${name}` };
1183
1324
  }
1184
1325
  if (existing.defaultImport) {
1185
- const at2 = endOf(existing.defaultImport);
1186
- return { range: { start: at2, end: at2 }, newText: `, { ${name} }` };
1326
+ const at3 = endOf(existing.defaultImport);
1327
+ return { range: { start: at3, end: at3 }, newText: `, { ${name} }` };
1187
1328
  }
1188
1329
  }
1189
1330
  const line = `import { ${name} } from "${specifier}"`;
1190
1331
  const lastImport = imports[imports.length - 1];
1191
1332
  if (lastImport) {
1192
- const at2 = { line: lastImport.line.end, character: 0 };
1193
- 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}
1194
1335
  ` };
1195
1336
  }
1196
1337
  const first = statements[0];
1197
- const at = { line: first ? first.line.start - 1 : 0, character: 0 };
1198
- 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}
1199
1340
 
1200
1341
  ` : `${line}
1201
1342
  ` };
@@ -1288,12 +1429,12 @@ function completion(analyzer, document, position) {
1288
1429
  const operator = memberOperator(source, start);
1289
1430
  const alreadyCalled = /^\s*\(/.test(source.slice(end));
1290
1431
  const standIns = operator === ":" ? [alreadyCalled ? PLACEHOLDER : `${PLACEHOLDER}()`] : operator === "." && !alreadyCalled ? [PLACEHOLDER, `${PLACEHOLDER}()`] : [PLACEHOLDER];
1291
- const at = { line: position.line, character: position.character - (offset - start) };
1432
+ const at2 = { line: position.line, character: position.character - (offset - start) };
1292
1433
  let first;
1293
1434
  for (const standIn of standIns) {
1294
1435
  const patched = source.slice(0, start) + standIn + source.slice(end);
1295
1436
  const analysis = analyzer.analyze(document.uri, -1, patched);
1296
- const path = pathAt(analysis.program, at, true);
1437
+ const path = pathAt(analysis.program, at2, true);
1297
1438
  const index = path.findLastIndex(
1298
1439
  (n) => n.type === "Identifier" && n.name === PLACEHOLDER
1299
1440
  );
@@ -1309,8 +1450,8 @@ function completion(analyzer, document, position) {
1309
1450
  if (inTypePosition(first.path)) {
1310
1451
  const named = [...first.analysis.types.aliases].map(([name, type]) => ({
1311
1452
  label: name,
1312
- kind: (0, import_luaut_parser6.isClassType)(type) ? import_vscode_languageserver5.CompletionItemKind.Class : import_vscode_languageserver5.CompletionItemKind.Interface,
1313
- 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"
1314
1455
  }));
1315
1456
  const primitives = PRIMITIVES2.map((name) => ({
1316
1457
  label: name,
@@ -1330,7 +1471,7 @@ function completion(analyzer, document, position) {
1330
1471
  for (const binding of first.analysis.scopes.bindings.values()) taken.add(binding.name);
1331
1472
  const current = analyzer.get(document);
1332
1473
  return [
1333
- ...valueItems(first.analysis, at),
1474
+ ...valueItems(first.analysis, at2, insideFunction(first.path)),
1334
1475
  ...contextKeywords(source.slice(0, start)),
1335
1476
  ...importItems(analyzer, current, false, taken),
1336
1477
  ...serviceItems(current, taken)
@@ -1398,14 +1539,14 @@ function objectKeyItems(analysis, path, after) {
1398
1539
  return members.filter((member) => !written.has(member.name)).map((member) => ({
1399
1540
  label: member.name,
1400
1541
  kind: import_vscode_languageserver5.CompletionItemKind.Field,
1401
- 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)}`,
1402
1543
  insertText: colon ? member.name : `${member.name}: `
1403
1544
  }));
1404
1545
  }
1405
1546
  function indexKeys(analysis, position, literal) {
1406
1547
  const path = pathAt(analysis.program, position, false);
1407
- const at = path.indexOf(literal);
1408
- 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;
1409
1550
  if (!parent) return [];
1410
1551
  let indexed;
1411
1552
  if (parent.type === "IndexedAccessTypeNode" && parent.indexType === literal) {
@@ -1472,12 +1613,7 @@ function memberItems(analysis, access) {
1472
1613
  const object = access.object;
1473
1614
  const type = withoutNil(analysis.types.typeOf.get(object));
1474
1615
  const colon = access.type === "MethodCallExpression";
1475
- if (isStringLike(type)) {
1476
- if (!colon) return [];
1477
- const id = analysis.scopes.globalsByName.get("string");
1478
- const library = id === void 0 ? void 0 : analysis.types.bindingType.get(id);
1479
- 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));
1480
- }
1616
+ if (isMethodOnly(type) && !colon) return [];
1481
1617
  return membersOf(type, analysis.types.aliases).filter((member) => colon ? member.isMethod : true).map((member) => memberItem(member.name, member.property.type, member.property.readonly));
1482
1618
  }
1483
1619
  function withoutNil(type) {
@@ -1485,35 +1621,41 @@ function withoutNil(type) {
1485
1621
  const kept = type.types.filter((t) => !(t.kind === "primitive" && t.name === "nil"));
1486
1622
  return kept.length === 1 ? kept[0] : { ...type, types: kept };
1487
1623
  }
1488
- function isStringLike(type) {
1624
+ function isMethodOnly(type) {
1489
1625
  if (!type) return false;
1490
1626
  switch (type.kind) {
1627
+ case "array":
1628
+ case "tuple":
1629
+ case "templateLiteral":
1630
+ return true;
1491
1631
  case "primitive":
1492
1632
  return type.name === "string";
1493
1633
  case "literal":
1494
1634
  return typeof type.value === "string";
1495
- case "templateLiteral":
1496
- return true;
1497
1635
  case "union":
1498
- return type.types.length > 0 && type.types.every(isStringLike);
1636
+ return type.types.length > 0 && type.types.every(isMethodOnly);
1499
1637
  default:
1500
1638
  return false;
1501
1639
  }
1502
1640
  }
1503
- 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) {
1504
1645
  const items = [];
1505
1646
  const seen = /* @__PURE__ */ new Set();
1506
1647
  for (const binding of analysis.scopes.bindings.values()) {
1507
1648
  if (binding.name === PLACEHOLDER || seen.has(binding.name)) continue;
1508
1649
  if (binding.declaredBy === "type") continue;
1509
1650
  const declaration = binding.declarationNode;
1510
- 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;
1511
1653
  seen.add(binding.name);
1512
1654
  const type = analysis.types.bindingType.get(binding.id);
1513
1655
  items.push({
1514
1656
  label: binding.name,
1515
1657
  kind: kindOf(type, binding.kind),
1516
- detail: type ? (0, import_luaut_parser6.formatType)(type) : void 0,
1658
+ detail: type ? (0, import_luaut_parser7.formatType)(type) : void 0,
1517
1659
  // Locals before globals, and globals before library names.
1518
1660
  sortText: `${binding.isBuiltin ? 2 : binding.kind === "global" ? 1 : 0}${binding.name}`
1519
1661
  });
@@ -1537,7 +1679,7 @@ function memberItem(name, type, readonly) {
1537
1679
  return {
1538
1680
  label: name,
1539
1681
  kind: import_vscode_languageserver5.CompletionItemKind.Field,
1540
- detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser6.formatType)(type)}`
1682
+ detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser7.formatType)(type)}`
1541
1683
  };
1542
1684
  }
1543
1685
  function kindOf(type, bindingKind) {
@@ -1645,9 +1787,9 @@ function helpAt(analysis, position) {
1645
1787
  function methodType(analysis, call) {
1646
1788
  const object = call.object;
1647
1789
  const method = call.method;
1648
- const objectType = analysis.types.typeOf.get(object);
1649
- if (!objectType) return void 0;
1650
- 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);
1651
1793
  }
1652
1794
  function memberType(type, name, analysis) {
1653
1795
  if (type.kind === "object") return type.properties.get(name)?.type;
@@ -1678,7 +1820,7 @@ function activeArgument(call, position) {
1678
1820
 
1679
1821
  // src/features/symbols.ts
1680
1822
  var import_vscode_languageserver6 = require("vscode-languageserver");
1681
- var import_luaut_parser7 = require("luaut-parser");
1823
+ var import_luaut_parser8 = require("luaut-parser");
1682
1824
  function documentSymbols(analysis) {
1683
1825
  const out = [];
1684
1826
  walk(analysis.program, (node) => {
@@ -1695,10 +1837,19 @@ function documentSymbols(analysis) {
1695
1837
  const name = typeof named === "string" ? named : named?.name;
1696
1838
  if (name) {
1697
1839
  const alias = analysis.types.aliases.get(name);
1698
- 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));
1699
1841
  }
1700
1842
  break;
1701
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
+ }
1702
1853
  case "DeclareClassStatement": {
1703
1854
  const name = node.name.name;
1704
1855
  const superclass = node.superclass?.base;
@@ -1716,6 +1867,23 @@ function documentSymbols(analysis) {
1716
1867
  });
1717
1868
  return out;
1718
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
+ }
1719
1887
  function functionName(node) {
1720
1888
  const named = node;
1721
1889
  if (typeof named.name === "string") return named.name;
@@ -1732,7 +1900,7 @@ function detailOf(analysis, node) {
1732
1900
  if (name && typeof name === "object") {
1733
1901
  const binding = bindingOfNode(analysis, name);
1734
1902
  const type = binding && analysis.types.bindingType.get(binding.id);
1735
- if (type) return (0, import_luaut_parser7.formatType)(type);
1903
+ if (type) return (0, import_luaut_parser8.formatType)(type);
1736
1904
  }
1737
1905
  return void 0;
1738
1906
  }
@@ -1742,7 +1910,7 @@ function symbol(name, kind, node, detail) {
1742
1910
  }
1743
1911
 
1744
1912
  // src/features/semanticTokens.ts
1745
- var import_luaut_parser8 = require("luaut-parser");
1913
+ var import_luaut_parser9 = require("luaut-parser");
1746
1914
  var TOKEN_TYPES = [
1747
1915
  "namespace",
1748
1916
  "type",
@@ -1772,21 +1940,23 @@ var SOFT_KEYWORDS = /* @__PURE__ */ new Set([
1772
1940
  "asserts",
1773
1941
  "satisfies",
1774
1942
  "typeof",
1775
- "default"
1943
+ "default",
1944
+ "new",
1945
+ "super"
1776
1946
  ]);
1777
1947
  var CONTROL_KEYWORDS = /* @__PURE__ */ new Set(["default"]);
1778
1948
  var PRIMITIVES3 = /* @__PURE__ */ new Set(["any", "unknown", "never", "nil", "boolean", "number", "string", "thread", "buffer"]);
1779
1949
  function semanticTokens(analysis) {
1780
1950
  const entries = /* @__PURE__ */ new Map();
1781
- const add = (at, length, type, modifiers = []) => {
1782
- const line = at.line.start - 1;
1783
- 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;
1784
1954
  const key = `${line}:${character}`;
1785
1955
  if (!entries.has(key)) entries.set(key, { line, character, length, type, modifiers });
1786
1956
  };
1787
1957
  let tokens = [];
1788
1958
  try {
1789
- tokens = (0, import_luaut_parser8.tokenize)(analysis.source);
1959
+ tokens = (0, import_luaut_parser9.tokenize)(analysis.source);
1790
1960
  } catch {
1791
1961
  }
1792
1962
  const identifiers = tokens.filter((t) => t.type === "Identifier");
@@ -1821,6 +1991,25 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1821
1991
  add(node, name.length, valueKind(analysis, binding), modifiersOf(binding, true));
1822
1992
  return;
1823
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
+ }
1824
2013
  case "TypeReference": {
1825
2014
  const base = node.base;
1826
2015
  const namespace = node.namespace;
@@ -1830,7 +2019,7 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1830
2019
  if (!baseToken) return;
1831
2020
  if (!namespace && typeParameterInScope2(ancestors, base)) {
1832
2021
  add(baseToken, base.length, "typeParameter");
1833
- } 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)) {
1834
2023
  add(baseToken, base.length, "class");
1835
2024
  } else {
1836
2025
  add(baseToken, base.length, "type", PRIMITIVES3.has(base) ? ["defaultLibrary"] : []);
@@ -1839,6 +2028,10 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1839
2028
  }
1840
2029
  }
1841
2030
  }
2031
+ function wordOf(token) {
2032
+ const value = token.value;
2033
+ return typeof value === "string" ? value : void 0;
2034
+ }
1842
2035
  function identifier(analysis, node, parent, add) {
1843
2036
  const name = node.name;
1844
2037
  const as = (type, modifiers = []) => add(node, name.length, type, modifiers);
@@ -1862,6 +2055,19 @@ function identifier(analysis, node, parent, add) {
1862
2055
  case "DeclareClassStatement":
1863
2056
  if (parent.name === node) return as("class", ["declaration"]);
1864
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;
1865
2071
  case "DeclareStatement":
1866
2072
  if (parent.id === node) return as(isFunction(typeOfNode(parent.valueType)) ? "function" : "variable", ["declaration"]);
1867
2073
  break;
@@ -2063,7 +2269,7 @@ function createServer(connection, options = {}) {
2063
2269
  severity: import_node.DiagnosticSeverity.Information,
2064
2270
  source: "luaut",
2065
2271
  code: "no-config",
2066
- message: 'No luaut.config.json applies to this file, so no types are loaded \u2014 not even `print`. Add one to this folder or a folder above, such as { "types": ["luau"], "paths": {}, "sourceMap": null }'
2272
+ message: 'No luaut.config.json applies to this file, so no types are loaded \u2014 not even `print`. Add one to this folder or a folder above: { "types": [], "paths": {}, "sourceMap": null }, listing in `types` the type libraries the project has installed.'
2067
2273
  }];
2068
2274
  };
2069
2275
  documents.onDidOpen(publishAll);
@@ -2082,6 +2288,11 @@ function createServer(connection, options = {}) {
2082
2288
  (d) => hover(analyzer.get(d), p.position),
2083
2289
  null
2084
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
+ ));
2085
2296
  connection.onDefinition((p) => withDocument(
2086
2297
  p.textDocument.uri,
2087
2298
  (d) => {