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/index.cjs CHANGED
@@ -116,13 +116,28 @@ function membersOf(type, aliases, seen = /* @__PURE__ */ new Set()) {
116
116
  }
117
117
  case "typeParam":
118
118
  return membersOf(type.constraint, aliases, seen);
119
+ // An array and a string answer to the methods the language gives them
120
+ // — `names:filter(f)`, `text:trim()`. They are written in the parser's
121
+ // prelude as `ArrayMethods<T>` and `StringMethods`, so the element
122
+ // type goes in where `T` stands.
123
+ case "array":
124
+ case "tuple": {
125
+ const element = type.kind === "array" ? type.element : (0, import_luaut_parser.union)(type.elements);
126
+ const methods = aliases.get("ArrayMethods");
127
+ return methods ? membersOf((0, import_luaut_parser.substitute)(methods, /* @__PURE__ */ new Map([["T", element]])), aliases, seen) : [];
128
+ }
129
+ case "primitive":
130
+ return type.name === "string" ? membersOf(aliases.get("StringMethods"), aliases, seen) : [];
131
+ case "literal":
132
+ return type.base === "string" ? membersOf(aliases.get("StringMethods"), aliases, seen) : [];
119
133
  default:
120
134
  return [];
121
135
  }
122
136
  }
123
137
  function takesSelf(type) {
124
138
  for (const signature of signaturesOf(type)) {
125
- if (signature.params[0]?.name === "self") return true;
139
+ const first = signature.params[0]?.name;
140
+ if (first === "self" || first === "this") return true;
126
141
  }
127
142
  return false;
128
143
  }
@@ -143,7 +158,7 @@ function signatureLabel(signature) {
143
158
  });
144
159
  const generics = signature.typeParams?.length ? `<${signature.typeParams.join(", ")}>` : "";
145
160
  const varargs = signature.varargs ? [`...: ${(0, import_luaut_parser.formatType)(signature.varargs)}`] : [];
146
- 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)}`;
147
162
  return { label, parameters };
148
163
  }
149
164
 
@@ -734,7 +749,7 @@ function exportDeclaration(analyzer, module2, name, seen = /* @__PURE__ */ new S
734
749
  break;
735
750
  case "ExportStatement": {
736
751
  const declaration = statement.declaration;
737
- if (declaration.type === "FunctionDeclaration") {
752
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
738
753
  if (declaration.name.name === name) return here(declaration.name);
739
754
  } else {
740
755
  for (const target of declaration.names) {
@@ -846,16 +861,100 @@ function diagnostics(analysis) {
846
861
  }
847
862
 
848
863
  // src/features/hover.ts
864
+ var import_luaut_parser6 = require("luaut-parser");
865
+
866
+ // src/features/expand.ts
849
867
  var import_luaut_parser5 = require("luaut-parser");
850
- 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) {
851
929
  const path = pathAt(analysis.program, position, true);
852
930
  for (let i = path.length - 1; i >= 0; i--) {
853
931
  if (UNNAMED.has(path[i].type)) return null;
854
- const text = describe(analysis, path, i);
855
- 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
+ };
856
941
  }
857
942
  return null;
858
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
+ };
859
958
  var UNNAMED = /* @__PURE__ */ new Set([
860
959
  "BinaryExpression",
861
960
  "UnaryExpression",
@@ -888,8 +987,8 @@ function describe(analysis, path, index) {
888
987
  case "TableExpression": {
889
988
  const field = fieldWithKey(parent, node);
890
989
  if (!field) break;
891
- const objectType = types.typeOf.get(parent);
892
- 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;
893
992
  const type2 = property?.type ?? types.typeOf.get(field.value);
894
993
  return type2 && `(property) ${name}: ${pretty(type2)}`;
895
994
  }
@@ -937,6 +1036,19 @@ function describe(analysis, path, index) {
937
1036
  case "DeclareClassStatement":
938
1037
  if (parent.name === node) return classText(analysis, name);
939
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;
940
1052
  case "TableTypeProperty":
941
1053
  if (parent.key === node) {
942
1054
  const type2 = typeOfNode(parent.valueType);
@@ -959,7 +1071,7 @@ function describe(analysis, path, index) {
959
1071
  case "MappedTypeNode":
960
1072
  if (parent.parameterId === node) {
961
1073
  const keys = typeOfNode(parent.constraint);
962
- 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)}` : ""}`;
963
1075
  }
964
1076
  break;
965
1077
  }
@@ -987,7 +1099,7 @@ function describe(analysis, path, index) {
987
1099
  case "TypedIdentifier": {
988
1100
  const binding = bindingOfNode(analysis, node);
989
1101
  const type2 = binding && types.bindingType.get(binding.id);
990
- return type2 ? bindingText(binding, type2) : void 0;
1102
+ return type2 ? bindingText(binding, type2, asWritten(node.typeAnnotation)) : void 0;
991
1103
  }
992
1104
  // A type written by name: `number`, `Shape`, `Partial<User>`, or a type
993
1105
  // parameter in scope.
@@ -1001,7 +1113,7 @@ function describe(analysis, path, index) {
1001
1113
  if (!node.typeArguments.length) {
1002
1114
  const qualified = node.namespace ? `${node.namespace}.${base}` : base;
1003
1115
  const alias = types.aliases.get(qualified);
1004
- 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);
1005
1117
  if (alias) return `type ${qualified} = ${pretty(alias)}`;
1006
1118
  }
1007
1119
  const type2 = typeOfNode(node);
@@ -1029,20 +1141,29 @@ function declareText(analysis, statement) {
1029
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);
1030
1142
  const others = total - 1;
1031
1143
  const overloads = others > 0 ? ` (+${others} overload${others > 1 ? "s" : ""})` : "";
1032
- return `declare function ${name}${(0, import_luaut_parser5.formatType)(own)}${overloads}`;
1144
+ return `declare function ${name}${(0, import_luaut_parser6.formatType)(own)}${overloads}`;
1033
1145
  }
1034
1146
  function classText(analysis, name) {
1035
1147
  const type = analysis.types.aliases.get(name);
1036
- if (!type || !(0, import_luaut_parser5.isClassType)(type)) return void 0;
1148
+ if (!type || !(0, import_luaut_parser6.isClassType)(type)) return void 0;
1037
1149
  const superclass = type.class.superclass;
1038
1150
  const inherited = superclass ? analysis.types.aliases.get(superclass) : void 0;
1039
- const own = [...type.properties].filter(([key, property]) => inherited?.kind !== "object" || inherited.properties.get(key) !== property);
1040
- const head = `declare class ${name}${superclass ? ` extends ${superclass}` : ""}`;
1041
- if (!own.length) return `${head} {}`;
1042
- const lines = own.map(([key, property]) => ` ${property.readonly ? "readonly " : ""}${key}${property.optional ? "?" : ""}: ${(0, import_luaut_parser5.formatType)(property.type)},`);
1043
- 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} {
1044
1159
  ${lines.join("\n")}
1045
- }`;
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
+ });
1046
1167
  }
1047
1168
  function typeParameterText(analysis, parameter) {
1048
1169
  return `(type parameter) ${typeParameterSignature(analysis, parameter)}`;
@@ -1051,7 +1172,7 @@ function typeParameterSignature(analysis, parameter) {
1051
1172
  const p = parameter;
1052
1173
  if (p.infer) return `infer ${p.name}`;
1053
1174
  const constraint = p.constraint ? analysis.types.typeOfTypeNode.get(p.constraint) : void 0;
1054
- 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)}` : ""}`;
1055
1176
  }
1056
1177
  function typeParameterInScope(path, index, name) {
1057
1178
  for (let i = index - 1; i >= 0; i--) {
@@ -1076,7 +1197,7 @@ function referenceText(analysis, reference) {
1076
1197
  if (!args.length) return name;
1077
1198
  const resolved = args.map((a) => {
1078
1199
  const t = analysis.types.typeOfTypeNode.get(a);
1079
- return t ? (0, import_luaut_parser5.formatType)(t) : "?";
1200
+ return t ? (0, import_luaut_parser6.formatType)(t) : "?";
1080
1201
  });
1081
1202
  return `${name}<${resolved.join(", ")}>`;
1082
1203
  }
@@ -1084,30 +1205,50 @@ function fieldWithKey(table, key) {
1084
1205
  const fields = table.fields;
1085
1206
  return fields.find((f) => f.type === "TableFieldNamed" && f.key === key);
1086
1207
  }
1087
- function pretty(type) {
1088
- 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);
1089
1230
  if (flat.length <= 80) return flat;
1090
1231
  if (type.kind === "object") {
1091
1232
  const lines = [];
1092
- 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)},`);
1093
1234
  for (const [name, property] of type.properties) {
1094
1235
  const readonly = property.readonly ? "readonly " : "";
1095
- 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)},`);
1096
1237
  }
1097
1238
  return `{
1098
1239
  ${lines.join("\n")}
1099
1240
  }`;
1100
1241
  }
1101
1242
  if (type.kind === "intersection" && type.types.every((t) => t.kind === "function")) {
1102
- return type.types.map(import_luaut_parser5.formatType).join("\n& ");
1243
+ return type.types.map(import_luaut_parser6.formatType).join("\n& ");
1103
1244
  }
1104
1245
  return flat;
1105
1246
  }
1106
- function bindingText(binding, type) {
1247
+ function bindingText(binding, type, written) {
1107
1248
  if (binding.declaredBy === "function" && type.kind === "function") {
1108
- return `function ${binding.name}${(0, import_luaut_parser5.formatType)(type)}`;
1249
+ return `function ${binding.name}${pretty(type)}`;
1109
1250
  }
1110
- return `${keyword(binding)} ${binding.name}: ${pretty(type)}`;
1251
+ return `${keyword(binding)} ${binding.name}: ${pretty(type, written)}`;
1111
1252
  }
1112
1253
  function keyword(binding) {
1113
1254
  if (binding.kind === "param" || binding.kind === "self") return "(parameter)";
@@ -1183,7 +1324,7 @@ function isIdentifier(name) {
1183
1324
 
1184
1325
  // src/features/completion.ts
1185
1326
  var import_vscode_languageserver5 = require("vscode-languageserver");
1186
- var import_luaut_parser6 = require("luaut-parser");
1327
+ var import_luaut_parser7 = require("luaut-parser");
1187
1328
 
1188
1329
  // src/features/autoImport.ts
1189
1330
  var import_node_fs3 = require("fs");
@@ -1220,7 +1361,7 @@ function importItems(analyzer, analysis, typePosition, taken) {
1220
1361
  function serviceItems(analysis, taken) {
1221
1362
  const services = analysis.types.aliases.get("Services");
1222
1363
  if (!services || services.kind !== "object") return [];
1223
- const at = serviceInsertion(analysis.program.body.statements);
1364
+ const at2 = serviceInsertion(analysis.program.body.statements);
1224
1365
  const items = [];
1225
1366
  for (const name of services.properties.keys()) {
1226
1367
  if (taken.has(name)) continue;
@@ -1231,8 +1372,8 @@ function serviceItems(analysis, taken) {
1231
1372
  labelDetails: { description: "service" },
1232
1373
  detail: line,
1233
1374
  sortText: `5${name}`,
1234
- additionalTextEdits: [{ range: { start: at.position, end: at.position }, newText: `${line}
1235
- ${at.gap}` }]
1375
+ additionalTextEdits: [{ range: { start: at2.position, end: at2.position }, newText: `${line}
1376
+ ${at2.gap}` }]
1236
1377
  });
1237
1378
  }
1238
1379
  return items;
@@ -1244,24 +1385,24 @@ function importEdit(analyzer, analysis, file, name, specifier, typePosition) {
1244
1385
  if (existing) {
1245
1386
  const last = existing.specifiers[existing.specifiers.length - 1];
1246
1387
  if (last) {
1247
- const at2 = endOf(last);
1248
- return { range: { start: at2, end: at2 }, newText: `, ${name}` };
1388
+ const at3 = endOf(last);
1389
+ return { range: { start: at3, end: at3 }, newText: `, ${name}` };
1249
1390
  }
1250
1391
  if (existing.defaultImport) {
1251
- const at2 = endOf(existing.defaultImport);
1252
- return { range: { start: at2, end: at2 }, newText: `, { ${name} }` };
1392
+ const at3 = endOf(existing.defaultImport);
1393
+ return { range: { start: at3, end: at3 }, newText: `, { ${name} }` };
1253
1394
  }
1254
1395
  }
1255
1396
  const line = `import { ${name} } from "${specifier}"`;
1256
1397
  const lastImport = imports[imports.length - 1];
1257
1398
  if (lastImport) {
1258
- const at2 = { line: lastImport.line.end, character: 0 };
1259
- 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}
1260
1401
  ` };
1261
1402
  }
1262
1403
  const first = statements[0];
1263
- const at = { line: first ? first.line.start - 1 : 0, character: 0 };
1264
- 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}
1265
1406
 
1266
1407
  ` : `${line}
1267
1408
  ` };
@@ -1354,12 +1495,12 @@ function completion(analyzer, document, position) {
1354
1495
  const operator = memberOperator(source, start);
1355
1496
  const alreadyCalled = /^\s*\(/.test(source.slice(end));
1356
1497
  const standIns = operator === ":" ? [alreadyCalled ? PLACEHOLDER : `${PLACEHOLDER}()`] : operator === "." && !alreadyCalled ? [PLACEHOLDER, `${PLACEHOLDER}()`] : [PLACEHOLDER];
1357
- const at = { line: position.line, character: position.character - (offset - start) };
1498
+ const at2 = { line: position.line, character: position.character - (offset - start) };
1358
1499
  let first;
1359
1500
  for (const standIn of standIns) {
1360
1501
  const patched = source.slice(0, start) + standIn + source.slice(end);
1361
1502
  const analysis = analyzer.analyze(document.uri, -1, patched);
1362
- const path = pathAt(analysis.program, at, true);
1503
+ const path = pathAt(analysis.program, at2, true);
1363
1504
  const index = path.findLastIndex(
1364
1505
  (n) => n.type === "Identifier" && n.name === PLACEHOLDER
1365
1506
  );
@@ -1375,8 +1516,8 @@ function completion(analyzer, document, position) {
1375
1516
  if (inTypePosition(first.path)) {
1376
1517
  const named = [...first.analysis.types.aliases].map(([name, type]) => ({
1377
1518
  label: name,
1378
- kind: (0, import_luaut_parser6.isClassType)(type) ? import_vscode_languageserver5.CompletionItemKind.Class : import_vscode_languageserver5.CompletionItemKind.Interface,
1379
- 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"
1380
1521
  }));
1381
1522
  const primitives = PRIMITIVES2.map((name) => ({
1382
1523
  label: name,
@@ -1396,7 +1537,7 @@ function completion(analyzer, document, position) {
1396
1537
  for (const binding of first.analysis.scopes.bindings.values()) taken.add(binding.name);
1397
1538
  const current = analyzer.get(document);
1398
1539
  return [
1399
- ...valueItems(first.analysis, at),
1540
+ ...valueItems(first.analysis, at2, insideFunction(first.path)),
1400
1541
  ...contextKeywords(source.slice(0, start)),
1401
1542
  ...importItems(analyzer, current, false, taken),
1402
1543
  ...serviceItems(current, taken)
@@ -1464,14 +1605,14 @@ function objectKeyItems(analysis, path, after) {
1464
1605
  return members.filter((member) => !written.has(member.name)).map((member) => ({
1465
1606
  label: member.name,
1466
1607
  kind: import_vscode_languageserver5.CompletionItemKind.Field,
1467
- 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)}`,
1468
1609
  insertText: colon ? member.name : `${member.name}: `
1469
1610
  }));
1470
1611
  }
1471
1612
  function indexKeys(analysis, position, literal) {
1472
1613
  const path = pathAt(analysis.program, position, false);
1473
- const at = path.indexOf(literal);
1474
- 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;
1475
1616
  if (!parent) return [];
1476
1617
  let indexed;
1477
1618
  if (parent.type === "IndexedAccessTypeNode" && parent.indexType === literal) {
@@ -1538,12 +1679,7 @@ function memberItems(analysis, access) {
1538
1679
  const object = access.object;
1539
1680
  const type = withoutNil(analysis.types.typeOf.get(object));
1540
1681
  const colon = access.type === "MethodCallExpression";
1541
- if (isStringLike(type)) {
1542
- if (!colon) return [];
1543
- const id = analysis.scopes.globalsByName.get("string");
1544
- const library = id === void 0 ? void 0 : analysis.types.bindingType.get(id);
1545
- 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));
1546
- }
1682
+ if (isMethodOnly(type) && !colon) return [];
1547
1683
  return membersOf(type, analysis.types.aliases).filter((member) => colon ? member.isMethod : true).map((member) => memberItem(member.name, member.property.type, member.property.readonly));
1548
1684
  }
1549
1685
  function withoutNil(type) {
@@ -1551,35 +1687,41 @@ function withoutNil(type) {
1551
1687
  const kept = type.types.filter((t) => !(t.kind === "primitive" && t.name === "nil"));
1552
1688
  return kept.length === 1 ? kept[0] : { ...type, types: kept };
1553
1689
  }
1554
- function isStringLike(type) {
1690
+ function isMethodOnly(type) {
1555
1691
  if (!type) return false;
1556
1692
  switch (type.kind) {
1693
+ case "array":
1694
+ case "tuple":
1695
+ case "templateLiteral":
1696
+ return true;
1557
1697
  case "primitive":
1558
1698
  return type.name === "string";
1559
1699
  case "literal":
1560
1700
  return typeof type.value === "string";
1561
- case "templateLiteral":
1562
- return true;
1563
1701
  case "union":
1564
- return type.types.length > 0 && type.types.every(isStringLike);
1702
+ return type.types.length > 0 && type.types.every(isMethodOnly);
1565
1703
  default:
1566
1704
  return false;
1567
1705
  }
1568
1706
  }
1569
- 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) {
1570
1711
  const items = [];
1571
1712
  const seen = /* @__PURE__ */ new Set();
1572
1713
  for (const binding of analysis.scopes.bindings.values()) {
1573
1714
  if (binding.name === PLACEHOLDER || seen.has(binding.name)) continue;
1574
1715
  if (binding.declaredBy === "type") continue;
1575
1716
  const declaration = binding.declarationNode;
1576
- 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;
1577
1719
  seen.add(binding.name);
1578
1720
  const type = analysis.types.bindingType.get(binding.id);
1579
1721
  items.push({
1580
1722
  label: binding.name,
1581
1723
  kind: kindOf(type, binding.kind),
1582
- detail: type ? (0, import_luaut_parser6.formatType)(type) : void 0,
1724
+ detail: type ? (0, import_luaut_parser7.formatType)(type) : void 0,
1583
1725
  // Locals before globals, and globals before library names.
1584
1726
  sortText: `${binding.isBuiltin ? 2 : binding.kind === "global" ? 1 : 0}${binding.name}`
1585
1727
  });
@@ -1603,7 +1745,7 @@ function memberItem(name, type, readonly) {
1603
1745
  return {
1604
1746
  label: name,
1605
1747
  kind: import_vscode_languageserver5.CompletionItemKind.Field,
1606
- detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser6.formatType)(type)}`
1748
+ detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser7.formatType)(type)}`
1607
1749
  };
1608
1750
  }
1609
1751
  function kindOf(type, bindingKind) {
@@ -1711,9 +1853,9 @@ function helpAt(analysis, position) {
1711
1853
  function methodType(analysis, call) {
1712
1854
  const object = call.object;
1713
1855
  const method = call.method;
1714
- const objectType = analysis.types.typeOf.get(object);
1715
- if (!objectType) return void 0;
1716
- 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);
1717
1859
  }
1718
1860
  function memberType(type, name, analysis) {
1719
1861
  if (type.kind === "object") return type.properties.get(name)?.type;
@@ -1744,7 +1886,7 @@ function activeArgument(call, position) {
1744
1886
 
1745
1887
  // src/features/symbols.ts
1746
1888
  var import_vscode_languageserver6 = require("vscode-languageserver");
1747
- var import_luaut_parser7 = require("luaut-parser");
1889
+ var import_luaut_parser8 = require("luaut-parser");
1748
1890
  function documentSymbols(analysis) {
1749
1891
  const out = [];
1750
1892
  walk(analysis.program, (node) => {
@@ -1761,10 +1903,19 @@ function documentSymbols(analysis) {
1761
1903
  const name = typeof named === "string" ? named : named?.name;
1762
1904
  if (name) {
1763
1905
  const alias = analysis.types.aliases.get(name);
1764
- 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));
1765
1907
  }
1766
1908
  break;
1767
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
+ }
1768
1919
  case "DeclareClassStatement": {
1769
1920
  const name = node.name.name;
1770
1921
  const superclass = node.superclass?.base;
@@ -1782,6 +1933,23 @@ function documentSymbols(analysis) {
1782
1933
  });
1783
1934
  return out;
1784
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
+ }
1785
1953
  function functionName(node) {
1786
1954
  const named = node;
1787
1955
  if (typeof named.name === "string") return named.name;
@@ -1798,7 +1966,7 @@ function detailOf(analysis, node) {
1798
1966
  if (name && typeof name === "object") {
1799
1967
  const binding = bindingOfNode(analysis, name);
1800
1968
  const type = binding && analysis.types.bindingType.get(binding.id);
1801
- if (type) return (0, import_luaut_parser7.formatType)(type);
1969
+ if (type) return (0, import_luaut_parser8.formatType)(type);
1802
1970
  }
1803
1971
  return void 0;
1804
1972
  }
@@ -1808,7 +1976,7 @@ function symbol(name, kind, node, detail) {
1808
1976
  }
1809
1977
 
1810
1978
  // src/features/semanticTokens.ts
1811
- var import_luaut_parser8 = require("luaut-parser");
1979
+ var import_luaut_parser9 = require("luaut-parser");
1812
1980
  var TOKEN_TYPES = [
1813
1981
  "namespace",
1814
1982
  "type",
@@ -1838,21 +2006,23 @@ var SOFT_KEYWORDS = /* @__PURE__ */ new Set([
1838
2006
  "asserts",
1839
2007
  "satisfies",
1840
2008
  "typeof",
1841
- "default"
2009
+ "default",
2010
+ "new",
2011
+ "super"
1842
2012
  ]);
1843
2013
  var CONTROL_KEYWORDS = /* @__PURE__ */ new Set(["default"]);
1844
2014
  var PRIMITIVES3 = /* @__PURE__ */ new Set(["any", "unknown", "never", "nil", "boolean", "number", "string", "thread", "buffer"]);
1845
2015
  function semanticTokens(analysis) {
1846
2016
  const entries = /* @__PURE__ */ new Map();
1847
- const add = (at, length, type, modifiers = []) => {
1848
- const line = at.line.start - 1;
1849
- 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;
1850
2020
  const key = `${line}:${character}`;
1851
2021
  if (!entries.has(key)) entries.set(key, { line, character, length, type, modifiers });
1852
2022
  };
1853
2023
  let tokens = [];
1854
2024
  try {
1855
- tokens = (0, import_luaut_parser8.tokenize)(analysis.source);
2025
+ tokens = (0, import_luaut_parser9.tokenize)(analysis.source);
1856
2026
  } catch {
1857
2027
  }
1858
2028
  const identifiers = tokens.filter((t) => t.type === "Identifier");
@@ -1887,6 +2057,25 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1887
2057
  add(node, name.length, valueKind(analysis, binding), modifiersOf(binding, true));
1888
2058
  return;
1889
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
+ }
1890
2079
  case "TypeReference": {
1891
2080
  const base = node.base;
1892
2081
  const namespace = node.namespace;
@@ -1896,7 +2085,7 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1896
2085
  if (!baseToken) return;
1897
2086
  if (!namespace && typeParameterInScope2(ancestors, base)) {
1898
2087
  add(baseToken, base.length, "typeParameter");
1899
- } 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)) {
1900
2089
  add(baseToken, base.length, "class");
1901
2090
  } else {
1902
2091
  add(baseToken, base.length, "type", PRIMITIVES3.has(base) ? ["defaultLibrary"] : []);
@@ -1905,6 +2094,10 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1905
2094
  }
1906
2095
  }
1907
2096
  }
2097
+ function wordOf(token) {
2098
+ const value = token.value;
2099
+ return typeof value === "string" ? value : void 0;
2100
+ }
1908
2101
  function identifier(analysis, node, parent, add) {
1909
2102
  const name = node.name;
1910
2103
  const as = (type, modifiers = []) => add(node, name.length, type, modifiers);
@@ -1928,6 +2121,19 @@ function identifier(analysis, node, parent, add) {
1928
2121
  case "DeclareClassStatement":
1929
2122
  if (parent.name === node) return as("class", ["declaration"]);
1930
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;
1931
2137
  case "DeclareStatement":
1932
2138
  if (parent.id === node) return as(isFunction(typeOfNode(parent.valueType)) ? "function" : "variable", ["declaration"]);
1933
2139
  break;
@@ -2129,7 +2335,7 @@ function createServer(connection, options = {}) {
2129
2335
  severity: import_node.DiagnosticSeverity.Information,
2130
2336
  source: "luaut",
2131
2337
  code: "no-config",
2132
- 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 }'
2338
+ 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.'
2133
2339
  }];
2134
2340
  };
2135
2341
  documents.onDidOpen(publishAll);
@@ -2148,6 +2354,11 @@ function createServer(connection, options = {}) {
2148
2354
  (d) => hover(analyzer.get(d), p.position),
2149
2355
  null
2150
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
+ ));
2151
2362
  connection.onDefinition((p) => withDocument(
2152
2363
  p.textDocument.uri,
2153
2364
  (d) => {