luaut-language-server 4.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,93 @@ 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 (type.kind === "genericRef") return type.typeArguments.length ? void 0 : type.name;
814
+ return (0, import_luaut_parser5.aliasNameOf)(type);
815
+ }
816
+ function unnamed(type) {
817
+ return (0, import_luaut_parser5.withoutAliasName)(type);
818
+ }
819
+ function children2(type, f) {
820
+ switch (type.kind) {
821
+ case "array":
822
+ return (0, import_luaut_parser5.arrayOf)(f(type.element));
823
+ case "tuple":
824
+ return (0, import_luaut_parser5.tuple)(type.elements.map(f), type.isPack);
825
+ case "union":
826
+ return (0, import_luaut_parser5.union)(type.types.map(f));
827
+ case "intersection": {
828
+ const out = (0, import_luaut_parser5.intersection)(type.types.map(f));
829
+ return type.name && out.kind === "intersection" ? { ...out, name: type.name } : out;
830
+ }
831
+ case "object": {
832
+ if ((0, import_luaut_parser5.isClassType)(type)) return type;
833
+ const out = (0, import_luaut_parser5.objectType)(
834
+ [...type.properties].map(([name, property]) => [name, { ...property, type: f(property.type) }]),
835
+ type.indexer && { key: type.indexer.key, value: f(type.indexer.value) },
836
+ type.frozen
837
+ );
838
+ if (type.name) out.name = type.name;
839
+ return out;
840
+ }
841
+ case "function":
842
+ return (0, import_luaut_parser5.fn)(
843
+ type.params.map((p) => ({ ...p, type: f(p.type) })),
844
+ f(type.returns),
845
+ type.varargs && f(type.varargs),
846
+ type.typeParams,
847
+ type.predicate
848
+ );
849
+ default:
850
+ return type;
851
+ }
852
+ }
853
+
854
+ // src/features/hover.ts
855
+ function hover(analysis, position, depth = 0) {
799
856
  const path = pathAt(analysis.program, position, true);
800
857
  for (let i = path.length - 1; i >= 0; i--) {
801
858
  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]) };
859
+ const text = at(analysis, path, i, depth);
860
+ if (!text) continue;
861
+ const canExpand = at(analysis, path, i, depth + 1) !== text;
862
+ return {
863
+ contents: { kind: "markdown", value: code(text) },
864
+ range: toRange(path[i]),
865
+ depth,
866
+ canExpand
867
+ };
804
868
  }
805
869
  return null;
806
870
  }
871
+ function at(analysis, path, index, depth) {
872
+ const previous = expansion;
873
+ expansion = { depth, aliases: analysis.types.aliases, source: analysis.source };
874
+ try {
875
+ return describe(analysis, path, index);
876
+ } finally {
877
+ expansion = previous;
878
+ }
879
+ }
880
+ var expansion = {
881
+ depth: 0,
882
+ aliases: /* @__PURE__ */ new Map(),
883
+ source: ""
884
+ };
807
885
  var UNNAMED = /* @__PURE__ */ new Set([
808
886
  "BinaryExpression",
809
887
  "UnaryExpression",
@@ -836,8 +914,8 @@ function describe(analysis, path, index) {
836
914
  case "TableExpression": {
837
915
  const field = fieldWithKey(parent, node);
838
916
  if (!field) break;
839
- const objectType = types.typeOf.get(parent);
840
- const property = objectType?.kind === "object" ? objectType.properties.get(name) : void 0;
917
+ const objectType2 = types.typeOf.get(parent);
918
+ const property = objectType2?.kind === "object" ? objectType2.properties.get(name) : void 0;
841
919
  const type2 = property?.type ?? types.typeOf.get(field.value);
842
920
  return type2 && `(property) ${name}: ${pretty(type2)}`;
843
921
  }
@@ -885,6 +963,19 @@ function describe(analysis, path, index) {
885
963
  case "DeclareClassStatement":
886
964
  if (parent.name === node) return classText(analysis, name);
887
965
  break;
966
+ case "ClassDeclaration":
967
+ if (parent.name === node) return classText(analysis, name);
968
+ break;
969
+ case "ClassField":
970
+ if (parent.name === node) {
971
+ const type2 = typeOfNode(parent.typeAnnotation) ?? types.typeOf.get(parent.init);
972
+ const prefix = parent.isStatic ? "(static) " : "(field) ";
973
+ return type2 ? `${prefix}${name}: ${pretty(type2)}` : `${prefix}${name}`;
974
+ }
975
+ break;
976
+ case "ClassAccessor":
977
+ if (parent.name === node) return `(${parent.kind === "get" ? "getter" : "setter"}) ${name}`;
978
+ break;
888
979
  case "TableTypeProperty":
889
980
  if (parent.key === node) {
890
981
  const type2 = typeOfNode(parent.valueType);
@@ -907,7 +998,7 @@ function describe(analysis, path, index) {
907
998
  case "MappedTypeNode":
908
999
  if (parent.parameterId === node) {
909
1000
  const keys = typeOfNode(parent.constraint);
910
- return `(type parameter) ${name}${keys ? ` in ${(0, import_luaut_parser5.formatType)(keys)}` : ""}`;
1001
+ return `(type parameter) ${name}${keys ? ` in ${(0, import_luaut_parser6.formatType)(keys)}` : ""}`;
911
1002
  }
912
1003
  break;
913
1004
  }
@@ -935,7 +1026,7 @@ function describe(analysis, path, index) {
935
1026
  case "TypedIdentifier": {
936
1027
  const binding = bindingOfNode(analysis, node);
937
1028
  const type2 = binding && types.bindingType.get(binding.id);
938
- return type2 ? bindingText(binding, type2) : void 0;
1029
+ return type2 ? bindingText(binding, type2, asWritten(node.typeAnnotation)) : void 0;
939
1030
  }
940
1031
  // A type written by name: `number`, `Shape`, `Partial<User>`, or a type
941
1032
  // parameter in scope.
@@ -949,7 +1040,7 @@ function describe(analysis, path, index) {
949
1040
  if (!node.typeArguments.length) {
950
1041
  const qualified = node.namespace ? `${node.namespace}.${base}` : base;
951
1042
  const alias = types.aliases.get(qualified);
952
- if (alias && (0, import_luaut_parser5.isClassType)(alias)) return classText(analysis, qualified);
1043
+ if (alias && (0, import_luaut_parser6.isClassType)(alias)) return classText(analysis, qualified);
953
1044
  if (alias) return `type ${qualified} = ${pretty(alias)}`;
954
1045
  }
955
1046
  const type2 = typeOfNode(node);
@@ -977,20 +1068,29 @@ function declareText(analysis, statement) {
977
1068
  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
1069
  const others = total - 1;
979
1070
  const overloads = others > 0 ? ` (+${others} overload${others > 1 ? "s" : ""})` : "";
980
- return `declare function ${name}${(0, import_luaut_parser5.formatType)(own)}${overloads}`;
1071
+ return `declare function ${name}${(0, import_luaut_parser6.formatType)(own)}${overloads}`;
981
1072
  }
982
1073
  function classText(analysis, name) {
983
1074
  const type = analysis.types.aliases.get(name);
984
- if (!type || !(0, import_luaut_parser5.isClassType)(type)) return void 0;
1075
+ if (!type || !(0, import_luaut_parser6.isClassType)(type)) return void 0;
985
1076
  const superclass = type.class.superclass;
986
1077
  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} {
1078
+ const own = [...type.properties].filter(([key, property]) => (
1079
+ // `ClassObject` is on every instance and says nothing about this one.
1080
+ key !== "ClassObject" && (inherited?.kind !== "object" || inherited.properties.get(key) !== property)
1081
+ ));
1082
+ const written = isRuntimeClass(analysis, name);
1083
+ const head = `${written ? "" : "declare "}class ${name}${superclass ? ` extends ${superclass}` : ""}`;
1084
+ const lines = own.map(([key, property]) => ` ${property.readonly ? "readonly " : ""}${key}${property.optional ? "?" : ""}: ${(0, import_luaut_parser6.formatType)(property.type)}${written ? "" : ","}`);
1085
+ return own.length ? `${head} {
992
1086
  ${lines.join("\n")}
993
- }`;
1087
+ }` : `${head} {}`;
1088
+ }
1089
+ function isRuntimeClass(analysis, name) {
1090
+ return analysis.program.body.statements.some((statement) => {
1091
+ const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
1092
+ return declaration.type === "ClassDeclaration" && declaration.name.name === name;
1093
+ });
994
1094
  }
995
1095
  function typeParameterText(analysis, parameter) {
996
1096
  return `(type parameter) ${typeParameterSignature(analysis, parameter)}`;
@@ -999,7 +1099,7 @@ function typeParameterSignature(analysis, parameter) {
999
1099
  const p = parameter;
1000
1100
  if (p.infer) return `infer ${p.name}`;
1001
1101
  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)}` : ""}`;
1102
+ return `${p.isConst ? "const " : ""}${p.name}${constraint ? ` extends ${(0, import_luaut_parser6.formatType)(constraint)}` : ""}`;
1003
1103
  }
1004
1104
  function typeParameterInScope(path, index, name) {
1005
1105
  for (let i = index - 1; i >= 0; i--) {
@@ -1024,7 +1124,7 @@ function referenceText(analysis, reference) {
1024
1124
  if (!args.length) return name;
1025
1125
  const resolved = args.map((a) => {
1026
1126
  const t = analysis.types.typeOfTypeNode.get(a);
1027
- return t ? (0, import_luaut_parser5.formatType)(t) : "?";
1127
+ return t ? (0, import_luaut_parser6.formatType)(t) : "?";
1028
1128
  });
1029
1129
  return `${name}<${resolved.join(", ")}>`;
1030
1130
  }
@@ -1032,30 +1132,50 @@ function fieldWithKey(table, key) {
1032
1132
  const fields = table.fields;
1033
1133
  return fields.find((f) => f.type === "TableFieldNamed" && f.key === key);
1034
1134
  }
1035
- function pretty(type) {
1036
- const flat = (0, import_luaut_parser5.formatType)(type);
1135
+ function pretty(type, written) {
1136
+ const named = render(expandAliases(type, expansion.aliases, 0));
1137
+ const shorthand = written !== void 0 && written !== named ? written : void 0;
1138
+ const depth = shorthand === void 0 ? expansion.depth : expansion.depth - 1;
1139
+ if (depth < 0) return shorthand;
1140
+ return depth === 0 ? named : render(expandAliases(type, expansion.aliases, depth));
1141
+ }
1142
+ function asWritten(node) {
1143
+ if (!isSpanned(node) || !expansion.source) return void 0;
1144
+ const lines = expansion.source.split(/\r?\n/);
1145
+ const { line, column } = node;
1146
+ if (line.start < 1 || line.end > lines.length) return void 0;
1147
+ const text = line.start === line.end ? lines[line.start - 1].slice(column.start - 1, column.end - 1) : [
1148
+ lines[line.start - 1].slice(column.start - 1),
1149
+ ...lines.slice(line.start, line.end - 1),
1150
+ lines[line.end - 1].slice(0, column.end - 1)
1151
+ ].join(" ");
1152
+ const folded = text.trim().replace(/\s+/g, " ");
1153
+ return folded.length ? folded : void 0;
1154
+ }
1155
+ function render(type) {
1156
+ const flat = (0, import_luaut_parser6.formatType)(type);
1037
1157
  if (flat.length <= 80) return flat;
1038
1158
  if (type.kind === "object") {
1039
1159
  const lines = [];
1040
- if (type.indexer) lines.push(` [${(0, import_luaut_parser5.formatType)(type.indexer.key)}]: ${(0, import_luaut_parser5.formatType)(type.indexer.value)},`);
1160
+ if (type.indexer) lines.push(` [${(0, import_luaut_parser6.formatType)(type.indexer.key)}]: ${(0, import_luaut_parser6.formatType)(type.indexer.value)},`);
1041
1161
  for (const [name, property] of type.properties) {
1042
1162
  const readonly = property.readonly ? "readonly " : "";
1043
- lines.push(` ${readonly}${name}${property.optional ? "?" : ""}: ${(0, import_luaut_parser5.formatType)(property.type)},`);
1163
+ lines.push(` ${readonly}${name}${property.optional ? "?" : ""}: ${(0, import_luaut_parser6.formatType)(property.type)},`);
1044
1164
  }
1045
1165
  return `{
1046
1166
  ${lines.join("\n")}
1047
1167
  }`;
1048
1168
  }
1049
1169
  if (type.kind === "intersection" && type.types.every((t) => t.kind === "function")) {
1050
- return type.types.map(import_luaut_parser5.formatType).join("\n& ");
1170
+ return type.types.map(import_luaut_parser6.formatType).join("\n& ");
1051
1171
  }
1052
1172
  return flat;
1053
1173
  }
1054
- function bindingText(binding, type) {
1174
+ function bindingText(binding, type, written) {
1055
1175
  if (binding.declaredBy === "function" && type.kind === "function") {
1056
- return `function ${binding.name}${(0, import_luaut_parser5.formatType)(type)}`;
1176
+ return `function ${binding.name}${pretty(type)}`;
1057
1177
  }
1058
- return `${keyword(binding)} ${binding.name}: ${pretty(type)}`;
1178
+ return `${keyword(binding)} ${binding.name}: ${pretty(type, written)}`;
1059
1179
  }
1060
1180
  function keyword(binding) {
1061
1181
  if (binding.kind === "param" || binding.kind === "self") return "(parameter)";
@@ -1131,7 +1251,7 @@ function isIdentifier(name) {
1131
1251
 
1132
1252
  // src/features/completion.ts
1133
1253
  var import_vscode_languageserver5 = require("vscode-languageserver");
1134
- var import_luaut_parser6 = require("luaut-parser");
1254
+ var import_luaut_parser7 = require("luaut-parser");
1135
1255
 
1136
1256
  // src/features/autoImport.ts
1137
1257
  var import_node_fs3 = require("fs");
@@ -1168,7 +1288,7 @@ function importItems(analyzer, analysis, typePosition, taken) {
1168
1288
  function serviceItems(analysis, taken) {
1169
1289
  const services = analysis.types.aliases.get("Services");
1170
1290
  if (!services || services.kind !== "object") return [];
1171
- const at = serviceInsertion(analysis.program.body.statements);
1291
+ const at2 = serviceInsertion(analysis.program.body.statements);
1172
1292
  const items = [];
1173
1293
  for (const name of services.properties.keys()) {
1174
1294
  if (taken.has(name)) continue;
@@ -1179,8 +1299,8 @@ function serviceItems(analysis, taken) {
1179
1299
  labelDetails: { description: "service" },
1180
1300
  detail: line,
1181
1301
  sortText: `5${name}`,
1182
- additionalTextEdits: [{ range: { start: at.position, end: at.position }, newText: `${line}
1183
- ${at.gap}` }]
1302
+ additionalTextEdits: [{ range: { start: at2.position, end: at2.position }, newText: `${line}
1303
+ ${at2.gap}` }]
1184
1304
  });
1185
1305
  }
1186
1306
  return items;
@@ -1192,24 +1312,24 @@ function importEdit(analyzer, analysis, file, name, specifier, typePosition) {
1192
1312
  if (existing) {
1193
1313
  const last = existing.specifiers[existing.specifiers.length - 1];
1194
1314
  if (last) {
1195
- const at2 = endOf(last);
1196
- return { range: { start: at2, end: at2 }, newText: `, ${name}` };
1315
+ const at3 = endOf(last);
1316
+ return { range: { start: at3, end: at3 }, newText: `, ${name}` };
1197
1317
  }
1198
1318
  if (existing.defaultImport) {
1199
- const at2 = endOf(existing.defaultImport);
1200
- return { range: { start: at2, end: at2 }, newText: `, { ${name} }` };
1319
+ const at3 = endOf(existing.defaultImport);
1320
+ return { range: { start: at3, end: at3 }, newText: `, { ${name} }` };
1201
1321
  }
1202
1322
  }
1203
1323
  const line = `import { ${name} } from "${specifier}"`;
1204
1324
  const lastImport = imports[imports.length - 1];
1205
1325
  if (lastImport) {
1206
- const at2 = { line: lastImport.line.end, character: 0 };
1207
- return { range: { start: at2, end: at2 }, newText: `${line}
1326
+ const at3 = { line: lastImport.line.end, character: 0 };
1327
+ return { range: { start: at3, end: at3 }, newText: `${line}
1208
1328
  ` };
1209
1329
  }
1210
1330
  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}
1331
+ const at2 = { line: first ? first.line.start - 1 : 0, character: 0 };
1332
+ return { range: { start: at2, end: at2 }, newText: first ? `${line}
1213
1333
 
1214
1334
  ` : `${line}
1215
1335
  ` };
@@ -1302,12 +1422,12 @@ function completion(analyzer, document, position) {
1302
1422
  const operator = memberOperator(source, start);
1303
1423
  const alreadyCalled = /^\s*\(/.test(source.slice(end));
1304
1424
  const standIns = operator === ":" ? [alreadyCalled ? PLACEHOLDER : `${PLACEHOLDER}()`] : operator === "." && !alreadyCalled ? [PLACEHOLDER, `${PLACEHOLDER}()`] : [PLACEHOLDER];
1305
- const at = { line: position.line, character: position.character - (offset - start) };
1425
+ const at2 = { line: position.line, character: position.character - (offset - start) };
1306
1426
  let first;
1307
1427
  for (const standIn of standIns) {
1308
1428
  const patched = source.slice(0, start) + standIn + source.slice(end);
1309
1429
  const analysis = analyzer.analyze(document.uri, -1, patched);
1310
- const path = pathAt(analysis.program, at, true);
1430
+ const path = pathAt(analysis.program, at2, true);
1311
1431
  const index = path.findLastIndex(
1312
1432
  (n) => n.type === "Identifier" && n.name === PLACEHOLDER
1313
1433
  );
@@ -1323,8 +1443,8 @@ function completion(analyzer, document, position) {
1323
1443
  if (inTypePosition(first.path)) {
1324
1444
  const named = [...first.analysis.types.aliases].map(([name, type]) => ({
1325
1445
  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"
1446
+ kind: (0, import_luaut_parser7.isClassType)(type) ? import_vscode_languageserver5.CompletionItemKind.Class : import_vscode_languageserver5.CompletionItemKind.Interface,
1447
+ detail: (0, import_luaut_parser7.isClassType)(type) ? "class" : "type"
1328
1448
  }));
1329
1449
  const primitives = PRIMITIVES2.map((name) => ({
1330
1450
  label: name,
@@ -1344,7 +1464,7 @@ function completion(analyzer, document, position) {
1344
1464
  for (const binding of first.analysis.scopes.bindings.values()) taken.add(binding.name);
1345
1465
  const current = analyzer.get(document);
1346
1466
  return [
1347
- ...valueItems(first.analysis, at),
1467
+ ...valueItems(first.analysis, at2, insideFunction(first.path)),
1348
1468
  ...contextKeywords(source.slice(0, start)),
1349
1469
  ...importItems(analyzer, current, false, taken),
1350
1470
  ...serviceItems(current, taken)
@@ -1412,14 +1532,14 @@ function objectKeyItems(analysis, path, after) {
1412
1532
  return members.filter((member) => !written.has(member.name)).map((member) => ({
1413
1533
  label: member.name,
1414
1534
  kind: import_vscode_languageserver5.CompletionItemKind.Field,
1415
- detail: `${member.property.optional ? "?" : ""}: ${(0, import_luaut_parser6.formatType)(member.property.type)}`,
1535
+ detail: `${member.property.optional ? "?" : ""}: ${(0, import_luaut_parser7.formatType)(member.property.type)}`,
1416
1536
  insertText: colon ? member.name : `${member.name}: `
1417
1537
  }));
1418
1538
  }
1419
1539
  function indexKeys(analysis, position, literal) {
1420
1540
  const path = pathAt(analysis.program, position, false);
1421
- const at = path.indexOf(literal);
1422
- const parent = at > 0 ? path[at - 1] : void 0;
1541
+ const at2 = path.indexOf(literal);
1542
+ const parent = at2 > 0 ? path[at2 - 1] : void 0;
1423
1543
  if (!parent) return [];
1424
1544
  let indexed;
1425
1545
  if (parent.type === "IndexedAccessTypeNode" && parent.indexType === literal) {
@@ -1511,20 +1631,24 @@ function isMethodOnly(type) {
1511
1631
  return false;
1512
1632
  }
1513
1633
  }
1514
- function valueItems(analysis, at) {
1634
+ function insideFunction(path) {
1635
+ return path.some((n) => n.type === "FunctionExpression" || n.type === "FunctionDeclaration" || n.type === "FunctionDeclarationStatement" || n.type === "FunctionBody");
1636
+ }
1637
+ function valueItems(analysis, at2, inFunction) {
1515
1638
  const items = [];
1516
1639
  const seen = /* @__PURE__ */ new Set();
1517
1640
  for (const binding of analysis.scopes.bindings.values()) {
1518
1641
  if (binding.name === PLACEHOLDER || seen.has(binding.name)) continue;
1519
1642
  if (binding.declaredBy === "type") continue;
1520
1643
  const declaration = binding.declarationNode;
1521
- if (declaration && declaration.line.start - 1 > at.line) continue;
1644
+ const later = declaration !== void 0 && declaration.line.start - 1 > at2.line;
1645
+ if (later && !inFunction && binding.declaredBy !== "function") continue;
1522
1646
  seen.add(binding.name);
1523
1647
  const type = analysis.types.bindingType.get(binding.id);
1524
1648
  items.push({
1525
1649
  label: binding.name,
1526
1650
  kind: kindOf(type, binding.kind),
1527
- detail: type ? (0, import_luaut_parser6.formatType)(type) : void 0,
1651
+ detail: type ? (0, import_luaut_parser7.formatType)(type) : void 0,
1528
1652
  // Locals before globals, and globals before library names.
1529
1653
  sortText: `${binding.isBuiltin ? 2 : binding.kind === "global" ? 1 : 0}${binding.name}`
1530
1654
  });
@@ -1548,7 +1672,7 @@ function memberItem(name, type, readonly) {
1548
1672
  return {
1549
1673
  label: name,
1550
1674
  kind: import_vscode_languageserver5.CompletionItemKind.Field,
1551
- detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser6.formatType)(type)}`
1675
+ detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser7.formatType)(type)}`
1552
1676
  };
1553
1677
  }
1554
1678
  function kindOf(type, bindingKind) {
@@ -1656,9 +1780,9 @@ function helpAt(analysis, position) {
1656
1780
  function methodType(analysis, call) {
1657
1781
  const object = call.object;
1658
1782
  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);
1783
+ const objectType2 = analysis.types.typeOf.get(object);
1784
+ if (!objectType2) return void 0;
1785
+ return memberType(objectType2, method.name, analysis);
1662
1786
  }
1663
1787
  function memberType(type, name, analysis) {
1664
1788
  if (type.kind === "object") return type.properties.get(name)?.type;
@@ -1689,7 +1813,7 @@ function activeArgument(call, position) {
1689
1813
 
1690
1814
  // src/features/symbols.ts
1691
1815
  var import_vscode_languageserver6 = require("vscode-languageserver");
1692
- var import_luaut_parser7 = require("luaut-parser");
1816
+ var import_luaut_parser8 = require("luaut-parser");
1693
1817
  function documentSymbols(analysis) {
1694
1818
  const out = [];
1695
1819
  walk(analysis.program, (node) => {
@@ -1706,10 +1830,19 @@ function documentSymbols(analysis) {
1706
1830
  const name = typeof named === "string" ? named : named?.name;
1707
1831
  if (name) {
1708
1832
  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));
1833
+ out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Interface, node, alias ? (0, import_luaut_parser8.formatType)(alias) : void 0));
1710
1834
  }
1711
1835
  break;
1712
1836
  }
1837
+ case "ClassDeclaration": {
1838
+ const declaration = node;
1839
+ const superclass = declaration.superclass?.name;
1840
+ out.push({
1841
+ ...symbol(declaration.name.name, import_vscode_languageserver6.SymbolKind.Class, node, superclass && `extends ${superclass}`),
1842
+ children: declaration.members.map((member) => classMember(analysis, member))
1843
+ });
1844
+ break;
1845
+ }
1713
1846
  case "DeclareClassStatement": {
1714
1847
  const name = node.name.name;
1715
1848
  const superclass = node.superclass?.base;
@@ -1727,6 +1860,23 @@ function documentSymbols(analysis) {
1727
1860
  });
1728
1861
  return out;
1729
1862
  }
1863
+ function classMember(analysis, member) {
1864
+ switch (member.type) {
1865
+ case "ClassConstructor":
1866
+ return symbol("constructor", import_vscode_languageserver6.SymbolKind.Constructor, member);
1867
+ case "ClassField":
1868
+ return symbol(
1869
+ member.name.name,
1870
+ member.isStatic ? import_vscode_languageserver6.SymbolKind.Constant : import_vscode_languageserver6.SymbolKind.Field,
1871
+ member,
1872
+ member.typeAnnotation ? void 0 : detailOf(analysis, member)
1873
+ );
1874
+ case "ClassAccessor":
1875
+ return symbol(member.name.name, import_vscode_languageserver6.SymbolKind.Property, member, member.kind);
1876
+ case "ClassMethod":
1877
+ return symbol(member.name.name, import_vscode_languageserver6.SymbolKind.Method, member, member.isStatic ? "static" : void 0);
1878
+ }
1879
+ }
1730
1880
  function functionName(node) {
1731
1881
  const named = node;
1732
1882
  if (typeof named.name === "string") return named.name;
@@ -1743,7 +1893,7 @@ function detailOf(analysis, node) {
1743
1893
  if (name && typeof name === "object") {
1744
1894
  const binding = bindingOfNode(analysis, name);
1745
1895
  const type = binding && analysis.types.bindingType.get(binding.id);
1746
- if (type) return (0, import_luaut_parser7.formatType)(type);
1896
+ if (type) return (0, import_luaut_parser8.formatType)(type);
1747
1897
  }
1748
1898
  return void 0;
1749
1899
  }
@@ -1753,7 +1903,7 @@ function symbol(name, kind, node, detail) {
1753
1903
  }
1754
1904
 
1755
1905
  // src/features/semanticTokens.ts
1756
- var import_luaut_parser8 = require("luaut-parser");
1906
+ var import_luaut_parser9 = require("luaut-parser");
1757
1907
  var TOKEN_TYPES = [
1758
1908
  "namespace",
1759
1909
  "type",
@@ -1783,21 +1933,23 @@ var SOFT_KEYWORDS = /* @__PURE__ */ new Set([
1783
1933
  "asserts",
1784
1934
  "satisfies",
1785
1935
  "typeof",
1786
- "default"
1936
+ "default",
1937
+ "new",
1938
+ "super"
1787
1939
  ]);
1788
1940
  var CONTROL_KEYWORDS = /* @__PURE__ */ new Set(["default"]);
1789
1941
  var PRIMITIVES3 = /* @__PURE__ */ new Set(["any", "unknown", "never", "nil", "boolean", "number", "string", "thread", "buffer"]);
1790
1942
  function semanticTokens(analysis) {
1791
1943
  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;
1944
+ const add = (at2, length, type, modifiers = []) => {
1945
+ const line = at2.line.start - 1;
1946
+ const character = at2.column.start - 1;
1795
1947
  const key = `${line}:${character}`;
1796
1948
  if (!entries.has(key)) entries.set(key, { line, character, length, type, modifiers });
1797
1949
  };
1798
1950
  let tokens = [];
1799
1951
  try {
1800
- tokens = (0, import_luaut_parser8.tokenize)(analysis.source);
1952
+ tokens = (0, import_luaut_parser9.tokenize)(analysis.source);
1801
1953
  } catch {
1802
1954
  }
1803
1955
  const identifiers = tokens.filter((t) => t.type === "Identifier");
@@ -1832,6 +1984,25 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1832
1984
  add(node, name.length, valueKind(analysis, binding), modifiersOf(binding, true));
1833
1985
  return;
1834
1986
  }
1987
+ // A class body's own words. `get`, `set`, `static` and `constructor`
1988
+ // are ordinary names anywhere else, so they are coloured from the
1989
+ // member they open rather than wherever they are written.
1990
+ case "ClassMethod":
1991
+ case "ClassField":
1992
+ case "ClassAccessor":
1993
+ case "ClassConstructor": {
1994
+ const opener = node.type === "ClassConstructor" ? "constructor" : node.type === "ClassAccessor" ? node.kind : void 0;
1995
+ const words = firstTokensWithin(identifiers, node, 2);
1996
+ let index = 0;
1997
+ if (node.isStatic === true && words[index] && wordOf(words[index]) === "static") {
1998
+ add(words[index], "static".length, "keyword");
1999
+ index++;
2000
+ }
2001
+ if (opener && words[index] && wordOf(words[index]) === opener) {
2002
+ add(words[index], opener.length, "keyword");
2003
+ }
2004
+ return;
2005
+ }
1835
2006
  case "TypeReference": {
1836
2007
  const base = node.base;
1837
2008
  const namespace = node.namespace;
@@ -1841,7 +2012,7 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1841
2012
  if (!baseToken) return;
1842
2013
  if (!namespace && typeParameterInScope2(ancestors, base)) {
1843
2014
  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)) {
2015
+ } else if ((0, import_luaut_parser9.isClassType)(analysis.types.aliases.get(namespace ? `${namespace}.${base}` : base) ?? import_luaut_parser9.unknownType)) {
1845
2016
  add(baseToken, base.length, "class");
1846
2017
  } else {
1847
2018
  add(baseToken, base.length, "type", PRIMITIVES3.has(base) ? ["defaultLibrary"] : []);
@@ -1850,6 +2021,10 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1850
2021
  }
1851
2022
  }
1852
2023
  }
2024
+ function wordOf(token) {
2025
+ const value = token.value;
2026
+ return typeof value === "string" ? value : void 0;
2027
+ }
1853
2028
  function identifier(analysis, node, parent, add) {
1854
2029
  const name = node.name;
1855
2030
  const as = (type, modifiers = []) => add(node, name.length, type, modifiers);
@@ -1873,6 +2048,19 @@ function identifier(analysis, node, parent, add) {
1873
2048
  case "DeclareClassStatement":
1874
2049
  if (parent.name === node) return as("class", ["declaration"]);
1875
2050
  break;
2051
+ case "ClassDeclaration":
2052
+ if (parent.name === node) return as("class", ["declaration"]);
2053
+ if (parent.superclass === node) return as("class");
2054
+ break;
2055
+ case "ClassMethod":
2056
+ if (parent.name === node) return as("method", ["declaration"]);
2057
+ break;
2058
+ case "ClassField":
2059
+ if (parent.name === node) return as("property", ["declaration"]);
2060
+ break;
2061
+ case "ClassAccessor":
2062
+ if (parent.name === node) return as("property", ["declaration"]);
2063
+ break;
1876
2064
  case "DeclareStatement":
1877
2065
  if (parent.id === node) return as(isFunction(typeOfNode(parent.valueType)) ? "function" : "variable", ["declaration"]);
1878
2066
  break;
@@ -2093,6 +2281,11 @@ function createServer(connection, options = {}) {
2093
2281
  (d) => hover(analyzer.get(d), p.position),
2094
2282
  null
2095
2283
  ));
2284
+ connection.onRequest("luaut/hover", (p) => withDocument(
2285
+ p.textDocument.uri,
2286
+ (d) => hover(analyzer.get(d), p.position, Math.max(0, p.depth ?? 0)),
2287
+ null
2288
+ ));
2096
2289
  connection.onDefinition((p) => withDocument(
2097
2290
  p.textDocument.uri,
2098
2291
  (d) => {