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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startServer
4
- } from "./chunk-XK3KS3T4.js";
4
+ } from "./chunk-7N7PVZTQ.js";
5
5
 
6
6
  // src/cli.ts
7
7
  startServer();
package/dist/index.cjs CHANGED
@@ -136,7 +136,8 @@ function membersOf(type, aliases, seen = /* @__PURE__ */ new Set()) {
136
136
  }
137
137
  function takesSelf(type) {
138
138
  for (const signature of signaturesOf(type)) {
139
- if (signature.params[0]?.name === "self") return true;
139
+ const first = signature.params[0]?.name;
140
+ if (first === "self" || first === "this") return true;
140
141
  }
141
142
  return false;
142
143
  }
@@ -157,7 +158,7 @@ function signatureLabel(signature) {
157
158
  });
158
159
  const generics = signature.typeParams?.length ? `<${signature.typeParams.join(", ")}>` : "";
159
160
  const varargs = signature.varargs ? [`...: ${(0, import_luaut_parser.formatType)(signature.varargs)}`] : [];
160
- const label = `${generics}(${[...parameters, ...varargs].join(", ")}) -> ${(0, import_luaut_parser.formatType)(signature.returns)}`;
161
+ const label = `${generics}(${[...parameters, ...varargs].join(", ")}) => ${(0, import_luaut_parser.formatType)(signature.returns)}`;
161
162
  return { label, parameters };
162
163
  }
163
164
 
@@ -748,7 +749,7 @@ function exportDeclaration(analyzer, module2, name, seen = /* @__PURE__ */ new S
748
749
  break;
749
750
  case "ExportStatement": {
750
751
  const declaration = statement.declaration;
751
- if (declaration.type === "FunctionDeclaration") {
752
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
752
753
  if (declaration.name.name === name) return here(declaration.name);
753
754
  } else {
754
755
  for (const target of declaration.names) {
@@ -860,16 +861,93 @@ function diagnostics(analysis) {
860
861
  }
861
862
 
862
863
  // src/features/hover.ts
864
+ var import_luaut_parser6 = require("luaut-parser");
865
+
866
+ // src/features/expand.ts
863
867
  var import_luaut_parser5 = require("luaut-parser");
864
- function hover(analysis, position) {
868
+ function expandAliases(type, aliases, depth, seen = []) {
869
+ if (depth <= 0) return type;
870
+ const name = withheldName(type);
871
+ if (name !== void 0 && !seen.includes(name)) {
872
+ const opened = aliases.get(name) ?? type;
873
+ const inner = [...seen, name];
874
+ return children2(unnamed(opened), (t) => expandAliases(t, aliases, depth - 1, inner));
875
+ }
876
+ return children2(type, (t) => expandAliases(t, aliases, depth, seen));
877
+ }
878
+ function withheldName(type) {
879
+ if (type.kind === "genericRef") return type.typeArguments.length ? void 0 : type.name;
880
+ return (0, import_luaut_parser5.aliasNameOf)(type);
881
+ }
882
+ function unnamed(type) {
883
+ return (0, import_luaut_parser5.withoutAliasName)(type);
884
+ }
885
+ function children2(type, f) {
886
+ switch (type.kind) {
887
+ case "array":
888
+ return (0, import_luaut_parser5.arrayOf)(f(type.element));
889
+ case "tuple":
890
+ return (0, import_luaut_parser5.tuple)(type.elements.map(f), type.isPack);
891
+ case "union":
892
+ return (0, import_luaut_parser5.union)(type.types.map(f));
893
+ case "intersection": {
894
+ const out = (0, import_luaut_parser5.intersection)(type.types.map(f));
895
+ return type.name && out.kind === "intersection" ? { ...out, name: type.name } : out;
896
+ }
897
+ case "object": {
898
+ if ((0, import_luaut_parser5.isClassType)(type)) return type;
899
+ const out = (0, import_luaut_parser5.objectType)(
900
+ [...type.properties].map(([name, property]) => [name, { ...property, type: f(property.type) }]),
901
+ type.indexer && { key: type.indexer.key, value: f(type.indexer.value) },
902
+ type.frozen
903
+ );
904
+ if (type.name) out.name = type.name;
905
+ return out;
906
+ }
907
+ case "function":
908
+ return (0, import_luaut_parser5.fn)(
909
+ type.params.map((p) => ({ ...p, type: f(p.type) })),
910
+ f(type.returns),
911
+ type.varargs && f(type.varargs),
912
+ type.typeParams,
913
+ type.predicate
914
+ );
915
+ default:
916
+ return type;
917
+ }
918
+ }
919
+
920
+ // src/features/hover.ts
921
+ function hover(analysis, position, depth = 0) {
865
922
  const path = pathAt(analysis.program, position, true);
866
923
  for (let i = path.length - 1; i >= 0; i--) {
867
924
  if (UNNAMED.has(path[i].type)) return null;
868
- const text = describe(analysis, path, i);
869
- if (text) return { contents: { kind: "markdown", value: code(text) }, range: toRange(path[i]) };
925
+ const text = at(analysis, path, i, depth);
926
+ if (!text) continue;
927
+ const canExpand = at(analysis, path, i, depth + 1) !== text;
928
+ return {
929
+ contents: { kind: "markdown", value: code(text) },
930
+ range: toRange(path[i]),
931
+ depth,
932
+ canExpand
933
+ };
870
934
  }
871
935
  return null;
872
936
  }
937
+ function at(analysis, path, index, depth) {
938
+ const previous = expansion;
939
+ expansion = { depth, aliases: analysis.types.aliases, source: analysis.source };
940
+ try {
941
+ return describe(analysis, path, index);
942
+ } finally {
943
+ expansion = previous;
944
+ }
945
+ }
946
+ var expansion = {
947
+ depth: 0,
948
+ aliases: /* @__PURE__ */ new Map(),
949
+ source: ""
950
+ };
873
951
  var UNNAMED = /* @__PURE__ */ new Set([
874
952
  "BinaryExpression",
875
953
  "UnaryExpression",
@@ -902,8 +980,8 @@ function describe(analysis, path, index) {
902
980
  case "TableExpression": {
903
981
  const field = fieldWithKey(parent, node);
904
982
  if (!field) break;
905
- const objectType = types.typeOf.get(parent);
906
- const property = objectType?.kind === "object" ? objectType.properties.get(name) : void 0;
983
+ const objectType2 = types.typeOf.get(parent);
984
+ const property = objectType2?.kind === "object" ? objectType2.properties.get(name) : void 0;
907
985
  const type2 = property?.type ?? types.typeOf.get(field.value);
908
986
  return type2 && `(property) ${name}: ${pretty(type2)}`;
909
987
  }
@@ -951,6 +1029,19 @@ function describe(analysis, path, index) {
951
1029
  case "DeclareClassStatement":
952
1030
  if (parent.name === node) return classText(analysis, name);
953
1031
  break;
1032
+ case "ClassDeclaration":
1033
+ if (parent.name === node) return classText(analysis, name);
1034
+ break;
1035
+ case "ClassField":
1036
+ if (parent.name === node) {
1037
+ const type2 = typeOfNode(parent.typeAnnotation) ?? types.typeOf.get(parent.init);
1038
+ const prefix = parent.isStatic ? "(static) " : "(field) ";
1039
+ return type2 ? `${prefix}${name}: ${pretty(type2)}` : `${prefix}${name}`;
1040
+ }
1041
+ break;
1042
+ case "ClassAccessor":
1043
+ if (parent.name === node) return `(${parent.kind === "get" ? "getter" : "setter"}) ${name}`;
1044
+ break;
954
1045
  case "TableTypeProperty":
955
1046
  if (parent.key === node) {
956
1047
  const type2 = typeOfNode(parent.valueType);
@@ -973,7 +1064,7 @@ function describe(analysis, path, index) {
973
1064
  case "MappedTypeNode":
974
1065
  if (parent.parameterId === node) {
975
1066
  const keys = typeOfNode(parent.constraint);
976
- return `(type parameter) ${name}${keys ? ` in ${(0, import_luaut_parser5.formatType)(keys)}` : ""}`;
1067
+ return `(type parameter) ${name}${keys ? ` in ${(0, import_luaut_parser6.formatType)(keys)}` : ""}`;
977
1068
  }
978
1069
  break;
979
1070
  }
@@ -1001,7 +1092,7 @@ function describe(analysis, path, index) {
1001
1092
  case "TypedIdentifier": {
1002
1093
  const binding = bindingOfNode(analysis, node);
1003
1094
  const type2 = binding && types.bindingType.get(binding.id);
1004
- return type2 ? bindingText(binding, type2) : void 0;
1095
+ return type2 ? bindingText(binding, type2, asWritten(node.typeAnnotation)) : void 0;
1005
1096
  }
1006
1097
  // A type written by name: `number`, `Shape`, `Partial<User>`, or a type
1007
1098
  // parameter in scope.
@@ -1015,7 +1106,7 @@ function describe(analysis, path, index) {
1015
1106
  if (!node.typeArguments.length) {
1016
1107
  const qualified = node.namespace ? `${node.namespace}.${base}` : base;
1017
1108
  const alias = types.aliases.get(qualified);
1018
- if (alias && (0, import_luaut_parser5.isClassType)(alias)) return classText(analysis, qualified);
1109
+ if (alias && (0, import_luaut_parser6.isClassType)(alias)) return classText(analysis, qualified);
1019
1110
  if (alias) return `type ${qualified} = ${pretty(alias)}`;
1020
1111
  }
1021
1112
  const type2 = typeOfNode(node);
@@ -1043,20 +1134,29 @@ function declareText(analysis, statement) {
1043
1134
  const total = analysis.program.body.statements.filter((s) => s.type === "DeclareStatement" && s.name === name).reduce((n, s) => n + signaturesOf(analysis.types.typeOfTypeNode.get(s.valueType)).length, 0);
1044
1135
  const others = total - 1;
1045
1136
  const overloads = others > 0 ? ` (+${others} overload${others > 1 ? "s" : ""})` : "";
1046
- return `declare function ${name}${(0, import_luaut_parser5.formatType)(own)}${overloads}`;
1137
+ return `declare function ${name}${(0, import_luaut_parser6.formatType)(own)}${overloads}`;
1047
1138
  }
1048
1139
  function classText(analysis, name) {
1049
1140
  const type = analysis.types.aliases.get(name);
1050
- if (!type || !(0, import_luaut_parser5.isClassType)(type)) return void 0;
1141
+ if (!type || !(0, import_luaut_parser6.isClassType)(type)) return void 0;
1051
1142
  const superclass = type.class.superclass;
1052
1143
  const inherited = superclass ? analysis.types.aliases.get(superclass) : void 0;
1053
- const own = [...type.properties].filter(([key, property]) => inherited?.kind !== "object" || inherited.properties.get(key) !== property);
1054
- const head = `declare class ${name}${superclass ? ` extends ${superclass}` : ""}`;
1055
- if (!own.length) return `${head} {}`;
1056
- const lines = own.map(([key, property]) => ` ${property.readonly ? "readonly " : ""}${key}${property.optional ? "?" : ""}: ${(0, import_luaut_parser5.formatType)(property.type)},`);
1057
- return `${head} {
1144
+ const own = [...type.properties].filter(([key, property]) => (
1145
+ // `ClassObject` is on every instance and says nothing about this one.
1146
+ key !== "ClassObject" && (inherited?.kind !== "object" || inherited.properties.get(key) !== property)
1147
+ ));
1148
+ const written = isRuntimeClass(analysis, name);
1149
+ const head = `${written ? "" : "declare "}class ${name}${superclass ? ` extends ${superclass}` : ""}`;
1150
+ const lines = own.map(([key, property]) => ` ${property.readonly ? "readonly " : ""}${key}${property.optional ? "?" : ""}: ${(0, import_luaut_parser6.formatType)(property.type)}${written ? "" : ","}`);
1151
+ return own.length ? `${head} {
1058
1152
  ${lines.join("\n")}
1059
- }`;
1153
+ }` : `${head} {}`;
1154
+ }
1155
+ function isRuntimeClass(analysis, name) {
1156
+ return analysis.program.body.statements.some((statement) => {
1157
+ const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
1158
+ return declaration.type === "ClassDeclaration" && declaration.name.name === name;
1159
+ });
1060
1160
  }
1061
1161
  function typeParameterText(analysis, parameter) {
1062
1162
  return `(type parameter) ${typeParameterSignature(analysis, parameter)}`;
@@ -1065,7 +1165,7 @@ function typeParameterSignature(analysis, parameter) {
1065
1165
  const p = parameter;
1066
1166
  if (p.infer) return `infer ${p.name}`;
1067
1167
  const constraint = p.constraint ? analysis.types.typeOfTypeNode.get(p.constraint) : void 0;
1068
- return `${p.isConst ? "const " : ""}${p.name}${constraint ? ` extends ${(0, import_luaut_parser5.formatType)(constraint)}` : ""}`;
1168
+ return `${p.isConst ? "const " : ""}${p.name}${constraint ? ` extends ${(0, import_luaut_parser6.formatType)(constraint)}` : ""}`;
1069
1169
  }
1070
1170
  function typeParameterInScope(path, index, name) {
1071
1171
  for (let i = index - 1; i >= 0; i--) {
@@ -1090,7 +1190,7 @@ function referenceText(analysis, reference) {
1090
1190
  if (!args.length) return name;
1091
1191
  const resolved = args.map((a) => {
1092
1192
  const t = analysis.types.typeOfTypeNode.get(a);
1093
- return t ? (0, import_luaut_parser5.formatType)(t) : "?";
1193
+ return t ? (0, import_luaut_parser6.formatType)(t) : "?";
1094
1194
  });
1095
1195
  return `${name}<${resolved.join(", ")}>`;
1096
1196
  }
@@ -1098,30 +1198,50 @@ function fieldWithKey(table, key) {
1098
1198
  const fields = table.fields;
1099
1199
  return fields.find((f) => f.type === "TableFieldNamed" && f.key === key);
1100
1200
  }
1101
- function pretty(type) {
1102
- const flat = (0, import_luaut_parser5.formatType)(type);
1201
+ function pretty(type, written) {
1202
+ const named = render(expandAliases(type, expansion.aliases, 0));
1203
+ const shorthand = written !== void 0 && written !== named ? written : void 0;
1204
+ const depth = shorthand === void 0 ? expansion.depth : expansion.depth - 1;
1205
+ if (depth < 0) return shorthand;
1206
+ return depth === 0 ? named : render(expandAliases(type, expansion.aliases, depth));
1207
+ }
1208
+ function asWritten(node) {
1209
+ if (!isSpanned(node) || !expansion.source) return void 0;
1210
+ const lines = expansion.source.split(/\r?\n/);
1211
+ const { line, column } = node;
1212
+ if (line.start < 1 || line.end > lines.length) return void 0;
1213
+ const text = line.start === line.end ? lines[line.start - 1].slice(column.start - 1, column.end - 1) : [
1214
+ lines[line.start - 1].slice(column.start - 1),
1215
+ ...lines.slice(line.start, line.end - 1),
1216
+ lines[line.end - 1].slice(0, column.end - 1)
1217
+ ].join(" ");
1218
+ const folded = text.trim().replace(/\s+/g, " ");
1219
+ return folded.length ? folded : void 0;
1220
+ }
1221
+ function render(type) {
1222
+ const flat = (0, import_luaut_parser6.formatType)(type);
1103
1223
  if (flat.length <= 80) return flat;
1104
1224
  if (type.kind === "object") {
1105
1225
  const lines = [];
1106
- if (type.indexer) lines.push(` [${(0, import_luaut_parser5.formatType)(type.indexer.key)}]: ${(0, import_luaut_parser5.formatType)(type.indexer.value)},`);
1226
+ if (type.indexer) lines.push(` [${(0, import_luaut_parser6.formatType)(type.indexer.key)}]: ${(0, import_luaut_parser6.formatType)(type.indexer.value)},`);
1107
1227
  for (const [name, property] of type.properties) {
1108
1228
  const readonly = property.readonly ? "readonly " : "";
1109
- lines.push(` ${readonly}${name}${property.optional ? "?" : ""}: ${(0, import_luaut_parser5.formatType)(property.type)},`);
1229
+ lines.push(` ${readonly}${name}${property.optional ? "?" : ""}: ${(0, import_luaut_parser6.formatType)(property.type)},`);
1110
1230
  }
1111
1231
  return `{
1112
1232
  ${lines.join("\n")}
1113
1233
  }`;
1114
1234
  }
1115
1235
  if (type.kind === "intersection" && type.types.every((t) => t.kind === "function")) {
1116
- return type.types.map(import_luaut_parser5.formatType).join("\n& ");
1236
+ return type.types.map(import_luaut_parser6.formatType).join("\n& ");
1117
1237
  }
1118
1238
  return flat;
1119
1239
  }
1120
- function bindingText(binding, type) {
1240
+ function bindingText(binding, type, written) {
1121
1241
  if (binding.declaredBy === "function" && type.kind === "function") {
1122
- return `function ${binding.name}${(0, import_luaut_parser5.formatType)(type)}`;
1242
+ return `function ${binding.name}${pretty(type)}`;
1123
1243
  }
1124
- return `${keyword(binding)} ${binding.name}: ${pretty(type)}`;
1244
+ return `${keyword(binding)} ${binding.name}: ${pretty(type, written)}`;
1125
1245
  }
1126
1246
  function keyword(binding) {
1127
1247
  if (binding.kind === "param" || binding.kind === "self") return "(parameter)";
@@ -1197,7 +1317,7 @@ function isIdentifier(name) {
1197
1317
 
1198
1318
  // src/features/completion.ts
1199
1319
  var import_vscode_languageserver5 = require("vscode-languageserver");
1200
- var import_luaut_parser6 = require("luaut-parser");
1320
+ var import_luaut_parser7 = require("luaut-parser");
1201
1321
 
1202
1322
  // src/features/autoImport.ts
1203
1323
  var import_node_fs3 = require("fs");
@@ -1234,7 +1354,7 @@ function importItems(analyzer, analysis, typePosition, taken) {
1234
1354
  function serviceItems(analysis, taken) {
1235
1355
  const services = analysis.types.aliases.get("Services");
1236
1356
  if (!services || services.kind !== "object") return [];
1237
- const at = serviceInsertion(analysis.program.body.statements);
1357
+ const at2 = serviceInsertion(analysis.program.body.statements);
1238
1358
  const items = [];
1239
1359
  for (const name of services.properties.keys()) {
1240
1360
  if (taken.has(name)) continue;
@@ -1245,8 +1365,8 @@ function serviceItems(analysis, taken) {
1245
1365
  labelDetails: { description: "service" },
1246
1366
  detail: line,
1247
1367
  sortText: `5${name}`,
1248
- additionalTextEdits: [{ range: { start: at.position, end: at.position }, newText: `${line}
1249
- ${at.gap}` }]
1368
+ additionalTextEdits: [{ range: { start: at2.position, end: at2.position }, newText: `${line}
1369
+ ${at2.gap}` }]
1250
1370
  });
1251
1371
  }
1252
1372
  return items;
@@ -1258,24 +1378,24 @@ function importEdit(analyzer, analysis, file, name, specifier, typePosition) {
1258
1378
  if (existing) {
1259
1379
  const last = existing.specifiers[existing.specifiers.length - 1];
1260
1380
  if (last) {
1261
- const at2 = endOf(last);
1262
- return { range: { start: at2, end: at2 }, newText: `, ${name}` };
1381
+ const at3 = endOf(last);
1382
+ return { range: { start: at3, end: at3 }, newText: `, ${name}` };
1263
1383
  }
1264
1384
  if (existing.defaultImport) {
1265
- const at2 = endOf(existing.defaultImport);
1266
- return { range: { start: at2, end: at2 }, newText: `, { ${name} }` };
1385
+ const at3 = endOf(existing.defaultImport);
1386
+ return { range: { start: at3, end: at3 }, newText: `, { ${name} }` };
1267
1387
  }
1268
1388
  }
1269
1389
  const line = `import { ${name} } from "${specifier}"`;
1270
1390
  const lastImport = imports[imports.length - 1];
1271
1391
  if (lastImport) {
1272
- const at2 = { line: lastImport.line.end, character: 0 };
1273
- return { range: { start: at2, end: at2 }, newText: `${line}
1392
+ const at3 = { line: lastImport.line.end, character: 0 };
1393
+ return { range: { start: at3, end: at3 }, newText: `${line}
1274
1394
  ` };
1275
1395
  }
1276
1396
  const first = statements[0];
1277
- const at = { line: first ? first.line.start - 1 : 0, character: 0 };
1278
- return { range: { start: at, end: at }, newText: first ? `${line}
1397
+ const at2 = { line: first ? first.line.start - 1 : 0, character: 0 };
1398
+ return { range: { start: at2, end: at2 }, newText: first ? `${line}
1279
1399
 
1280
1400
  ` : `${line}
1281
1401
  ` };
@@ -1368,12 +1488,12 @@ function completion(analyzer, document, position) {
1368
1488
  const operator = memberOperator(source, start);
1369
1489
  const alreadyCalled = /^\s*\(/.test(source.slice(end));
1370
1490
  const standIns = operator === ":" ? [alreadyCalled ? PLACEHOLDER : `${PLACEHOLDER}()`] : operator === "." && !alreadyCalled ? [PLACEHOLDER, `${PLACEHOLDER}()`] : [PLACEHOLDER];
1371
- const at = { line: position.line, character: position.character - (offset - start) };
1491
+ const at2 = { line: position.line, character: position.character - (offset - start) };
1372
1492
  let first;
1373
1493
  for (const standIn of standIns) {
1374
1494
  const patched = source.slice(0, start) + standIn + source.slice(end);
1375
1495
  const analysis = analyzer.analyze(document.uri, -1, patched);
1376
- const path = pathAt(analysis.program, at, true);
1496
+ const path = pathAt(analysis.program, at2, true);
1377
1497
  const index = path.findLastIndex(
1378
1498
  (n) => n.type === "Identifier" && n.name === PLACEHOLDER
1379
1499
  );
@@ -1389,8 +1509,8 @@ function completion(analyzer, document, position) {
1389
1509
  if (inTypePosition(first.path)) {
1390
1510
  const named = [...first.analysis.types.aliases].map(([name, type]) => ({
1391
1511
  label: name,
1392
- kind: (0, import_luaut_parser6.isClassType)(type) ? import_vscode_languageserver5.CompletionItemKind.Class : import_vscode_languageserver5.CompletionItemKind.Interface,
1393
- detail: (0, import_luaut_parser6.isClassType)(type) ? "class" : "type"
1512
+ kind: (0, import_luaut_parser7.isClassType)(type) ? import_vscode_languageserver5.CompletionItemKind.Class : import_vscode_languageserver5.CompletionItemKind.Interface,
1513
+ detail: (0, import_luaut_parser7.isClassType)(type) ? "class" : "type"
1394
1514
  }));
1395
1515
  const primitives = PRIMITIVES2.map((name) => ({
1396
1516
  label: name,
@@ -1410,7 +1530,7 @@ function completion(analyzer, document, position) {
1410
1530
  for (const binding of first.analysis.scopes.bindings.values()) taken.add(binding.name);
1411
1531
  const current = analyzer.get(document);
1412
1532
  return [
1413
- ...valueItems(first.analysis, at),
1533
+ ...valueItems(first.analysis, at2, insideFunction(first.path)),
1414
1534
  ...contextKeywords(source.slice(0, start)),
1415
1535
  ...importItems(analyzer, current, false, taken),
1416
1536
  ...serviceItems(current, taken)
@@ -1478,14 +1598,14 @@ function objectKeyItems(analysis, path, after) {
1478
1598
  return members.filter((member) => !written.has(member.name)).map((member) => ({
1479
1599
  label: member.name,
1480
1600
  kind: import_vscode_languageserver5.CompletionItemKind.Field,
1481
- detail: `${member.property.optional ? "?" : ""}: ${(0, import_luaut_parser6.formatType)(member.property.type)}`,
1601
+ detail: `${member.property.optional ? "?" : ""}: ${(0, import_luaut_parser7.formatType)(member.property.type)}`,
1482
1602
  insertText: colon ? member.name : `${member.name}: `
1483
1603
  }));
1484
1604
  }
1485
1605
  function indexKeys(analysis, position, literal) {
1486
1606
  const path = pathAt(analysis.program, position, false);
1487
- const at = path.indexOf(literal);
1488
- const parent = at > 0 ? path[at - 1] : void 0;
1607
+ const at2 = path.indexOf(literal);
1608
+ const parent = at2 > 0 ? path[at2 - 1] : void 0;
1489
1609
  if (!parent) return [];
1490
1610
  let indexed;
1491
1611
  if (parent.type === "IndexedAccessTypeNode" && parent.indexType === literal) {
@@ -1577,20 +1697,24 @@ function isMethodOnly(type) {
1577
1697
  return false;
1578
1698
  }
1579
1699
  }
1580
- function valueItems(analysis, at) {
1700
+ function insideFunction(path) {
1701
+ return path.some((n) => n.type === "FunctionExpression" || n.type === "FunctionDeclaration" || n.type === "FunctionDeclarationStatement" || n.type === "FunctionBody");
1702
+ }
1703
+ function valueItems(analysis, at2, inFunction) {
1581
1704
  const items = [];
1582
1705
  const seen = /* @__PURE__ */ new Set();
1583
1706
  for (const binding of analysis.scopes.bindings.values()) {
1584
1707
  if (binding.name === PLACEHOLDER || seen.has(binding.name)) continue;
1585
1708
  if (binding.declaredBy === "type") continue;
1586
1709
  const declaration = binding.declarationNode;
1587
- if (declaration && declaration.line.start - 1 > at.line) continue;
1710
+ const later = declaration !== void 0 && declaration.line.start - 1 > at2.line;
1711
+ if (later && !inFunction && binding.declaredBy !== "function") continue;
1588
1712
  seen.add(binding.name);
1589
1713
  const type = analysis.types.bindingType.get(binding.id);
1590
1714
  items.push({
1591
1715
  label: binding.name,
1592
1716
  kind: kindOf(type, binding.kind),
1593
- detail: type ? (0, import_luaut_parser6.formatType)(type) : void 0,
1717
+ detail: type ? (0, import_luaut_parser7.formatType)(type) : void 0,
1594
1718
  // Locals before globals, and globals before library names.
1595
1719
  sortText: `${binding.isBuiltin ? 2 : binding.kind === "global" ? 1 : 0}${binding.name}`
1596
1720
  });
@@ -1614,7 +1738,7 @@ function memberItem(name, type, readonly) {
1614
1738
  return {
1615
1739
  label: name,
1616
1740
  kind: import_vscode_languageserver5.CompletionItemKind.Field,
1617
- detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser6.formatType)(type)}`
1741
+ detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser7.formatType)(type)}`
1618
1742
  };
1619
1743
  }
1620
1744
  function kindOf(type, bindingKind) {
@@ -1722,9 +1846,9 @@ function helpAt(analysis, position) {
1722
1846
  function methodType(analysis, call) {
1723
1847
  const object = call.object;
1724
1848
  const method = call.method;
1725
- const objectType = analysis.types.typeOf.get(object);
1726
- if (!objectType) return void 0;
1727
- return memberType(objectType, method.name, analysis);
1849
+ const objectType2 = analysis.types.typeOf.get(object);
1850
+ if (!objectType2) return void 0;
1851
+ return memberType(objectType2, method.name, analysis);
1728
1852
  }
1729
1853
  function memberType(type, name, analysis) {
1730
1854
  if (type.kind === "object") return type.properties.get(name)?.type;
@@ -1755,7 +1879,7 @@ function activeArgument(call, position) {
1755
1879
 
1756
1880
  // src/features/symbols.ts
1757
1881
  var import_vscode_languageserver6 = require("vscode-languageserver");
1758
- var import_luaut_parser7 = require("luaut-parser");
1882
+ var import_luaut_parser8 = require("luaut-parser");
1759
1883
  function documentSymbols(analysis) {
1760
1884
  const out = [];
1761
1885
  walk(analysis.program, (node) => {
@@ -1772,10 +1896,19 @@ function documentSymbols(analysis) {
1772
1896
  const name = typeof named === "string" ? named : named?.name;
1773
1897
  if (name) {
1774
1898
  const alias = analysis.types.aliases.get(name);
1775
- out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Interface, node, alias ? (0, import_luaut_parser7.formatType)(alias) : void 0));
1899
+ out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Interface, node, alias ? (0, import_luaut_parser8.formatType)(alias) : void 0));
1776
1900
  }
1777
1901
  break;
1778
1902
  }
1903
+ case "ClassDeclaration": {
1904
+ const declaration = node;
1905
+ const superclass = declaration.superclass?.name;
1906
+ out.push({
1907
+ ...symbol(declaration.name.name, import_vscode_languageserver6.SymbolKind.Class, node, superclass && `extends ${superclass}`),
1908
+ children: declaration.members.map((member) => classMember(analysis, member))
1909
+ });
1910
+ break;
1911
+ }
1779
1912
  case "DeclareClassStatement": {
1780
1913
  const name = node.name.name;
1781
1914
  const superclass = node.superclass?.base;
@@ -1793,6 +1926,23 @@ function documentSymbols(analysis) {
1793
1926
  });
1794
1927
  return out;
1795
1928
  }
1929
+ function classMember(analysis, member) {
1930
+ switch (member.type) {
1931
+ case "ClassConstructor":
1932
+ return symbol("constructor", import_vscode_languageserver6.SymbolKind.Constructor, member);
1933
+ case "ClassField":
1934
+ return symbol(
1935
+ member.name.name,
1936
+ member.isStatic ? import_vscode_languageserver6.SymbolKind.Constant : import_vscode_languageserver6.SymbolKind.Field,
1937
+ member,
1938
+ member.typeAnnotation ? void 0 : detailOf(analysis, member)
1939
+ );
1940
+ case "ClassAccessor":
1941
+ return symbol(member.name.name, import_vscode_languageserver6.SymbolKind.Property, member, member.kind);
1942
+ case "ClassMethod":
1943
+ return symbol(member.name.name, import_vscode_languageserver6.SymbolKind.Method, member, member.isStatic ? "static" : void 0);
1944
+ }
1945
+ }
1796
1946
  function functionName(node) {
1797
1947
  const named = node;
1798
1948
  if (typeof named.name === "string") return named.name;
@@ -1809,7 +1959,7 @@ function detailOf(analysis, node) {
1809
1959
  if (name && typeof name === "object") {
1810
1960
  const binding = bindingOfNode(analysis, name);
1811
1961
  const type = binding && analysis.types.bindingType.get(binding.id);
1812
- if (type) return (0, import_luaut_parser7.formatType)(type);
1962
+ if (type) return (0, import_luaut_parser8.formatType)(type);
1813
1963
  }
1814
1964
  return void 0;
1815
1965
  }
@@ -1819,7 +1969,7 @@ function symbol(name, kind, node, detail) {
1819
1969
  }
1820
1970
 
1821
1971
  // src/features/semanticTokens.ts
1822
- var import_luaut_parser8 = require("luaut-parser");
1972
+ var import_luaut_parser9 = require("luaut-parser");
1823
1973
  var TOKEN_TYPES = [
1824
1974
  "namespace",
1825
1975
  "type",
@@ -1849,21 +1999,23 @@ var SOFT_KEYWORDS = /* @__PURE__ */ new Set([
1849
1999
  "asserts",
1850
2000
  "satisfies",
1851
2001
  "typeof",
1852
- "default"
2002
+ "default",
2003
+ "new",
2004
+ "super"
1853
2005
  ]);
1854
2006
  var CONTROL_KEYWORDS = /* @__PURE__ */ new Set(["default"]);
1855
2007
  var PRIMITIVES3 = /* @__PURE__ */ new Set(["any", "unknown", "never", "nil", "boolean", "number", "string", "thread", "buffer"]);
1856
2008
  function semanticTokens(analysis) {
1857
2009
  const entries = /* @__PURE__ */ new Map();
1858
- const add = (at, length, type, modifiers = []) => {
1859
- const line = at.line.start - 1;
1860
- const character = at.column.start - 1;
2010
+ const add = (at2, length, type, modifiers = []) => {
2011
+ const line = at2.line.start - 1;
2012
+ const character = at2.column.start - 1;
1861
2013
  const key = `${line}:${character}`;
1862
2014
  if (!entries.has(key)) entries.set(key, { line, character, length, type, modifiers });
1863
2015
  };
1864
2016
  let tokens = [];
1865
2017
  try {
1866
- tokens = (0, import_luaut_parser8.tokenize)(analysis.source);
2018
+ tokens = (0, import_luaut_parser9.tokenize)(analysis.source);
1867
2019
  } catch {
1868
2020
  }
1869
2021
  const identifiers = tokens.filter((t) => t.type === "Identifier");
@@ -1898,6 +2050,25 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1898
2050
  add(node, name.length, valueKind(analysis, binding), modifiersOf(binding, true));
1899
2051
  return;
1900
2052
  }
2053
+ // A class body's own words. `get`, `set`, `static` and `constructor`
2054
+ // are ordinary names anywhere else, so they are coloured from the
2055
+ // member they open rather than wherever they are written.
2056
+ case "ClassMethod":
2057
+ case "ClassField":
2058
+ case "ClassAccessor":
2059
+ case "ClassConstructor": {
2060
+ const opener = node.type === "ClassConstructor" ? "constructor" : node.type === "ClassAccessor" ? node.kind : void 0;
2061
+ const words = firstTokensWithin(identifiers, node, 2);
2062
+ let index = 0;
2063
+ if (node.isStatic === true && words[index] && wordOf(words[index]) === "static") {
2064
+ add(words[index], "static".length, "keyword");
2065
+ index++;
2066
+ }
2067
+ if (opener && words[index] && wordOf(words[index]) === opener) {
2068
+ add(words[index], opener.length, "keyword");
2069
+ }
2070
+ return;
2071
+ }
1901
2072
  case "TypeReference": {
1902
2073
  const base = node.base;
1903
2074
  const namespace = node.namespace;
@@ -1907,7 +2078,7 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1907
2078
  if (!baseToken) return;
1908
2079
  if (!namespace && typeParameterInScope2(ancestors, base)) {
1909
2080
  add(baseToken, base.length, "typeParameter");
1910
- } else if ((0, import_luaut_parser8.isClassType)(analysis.types.aliases.get(namespace ? `${namespace}.${base}` : base) ?? import_luaut_parser8.unknownType)) {
2081
+ } else if ((0, import_luaut_parser9.isClassType)(analysis.types.aliases.get(namespace ? `${namespace}.${base}` : base) ?? import_luaut_parser9.unknownType)) {
1911
2082
  add(baseToken, base.length, "class");
1912
2083
  } else {
1913
2084
  add(baseToken, base.length, "type", PRIMITIVES3.has(base) ? ["defaultLibrary"] : []);
@@ -1916,6 +2087,10 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
1916
2087
  }
1917
2088
  }
1918
2089
  }
2090
+ function wordOf(token) {
2091
+ const value = token.value;
2092
+ return typeof value === "string" ? value : void 0;
2093
+ }
1919
2094
  function identifier(analysis, node, parent, add) {
1920
2095
  const name = node.name;
1921
2096
  const as = (type, modifiers = []) => add(node, name.length, type, modifiers);
@@ -1939,6 +2114,19 @@ function identifier(analysis, node, parent, add) {
1939
2114
  case "DeclareClassStatement":
1940
2115
  if (parent.name === node) return as("class", ["declaration"]);
1941
2116
  break;
2117
+ case "ClassDeclaration":
2118
+ if (parent.name === node) return as("class", ["declaration"]);
2119
+ if (parent.superclass === node) return as("class");
2120
+ break;
2121
+ case "ClassMethod":
2122
+ if (parent.name === node) return as("method", ["declaration"]);
2123
+ break;
2124
+ case "ClassField":
2125
+ if (parent.name === node) return as("property", ["declaration"]);
2126
+ break;
2127
+ case "ClassAccessor":
2128
+ if (parent.name === node) return as("property", ["declaration"]);
2129
+ break;
1942
2130
  case "DeclareStatement":
1943
2131
  if (parent.id === node) return as(isFunction(typeOfNode(parent.valueType)) ? "function" : "variable", ["declaration"]);
1944
2132
  break;
@@ -2159,6 +2347,11 @@ function createServer(connection, options = {}) {
2159
2347
  (d) => hover(analyzer.get(d), p.position),
2160
2348
  null
2161
2349
  ));
2350
+ connection.onRequest("luaut/hover", (p) => withDocument(
2351
+ p.textDocument.uri,
2352
+ (d) => hover(analyzer.get(d), p.position, Math.max(0, p.depth ?? 0)),
2353
+ null
2354
+ ));
2162
2355
  connection.onDefinition((p) => withDocument(
2163
2356
  p.textDocument.uri,
2164
2357
  (d) => {