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/README.md +10 -1
- package/dist/{chunk-SCZTSX3Y.js → chunk-GHAR7NOH.js} +276 -65
- package/dist/chunk-GHAR7NOH.js.map +1 -0
- package/dist/cli.cjs +288 -77
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +288 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +1 -1
- package/package.json +4 -4
- package/dist/chunk-SCZTSX3Y.js.map +0 -1
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
|
-
|
|
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(", ")})
|
|
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
|
|
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 =
|
|
855
|
-
if (text)
|
|
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
|
|
892
|
-
const property =
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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]) =>
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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(
|
|
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}${(
|
|
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
|
|
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
|
|
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:
|
|
1235
|
-
${
|
|
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
|
|
1248
|
-
return { range: { start:
|
|
1388
|
+
const at3 = endOf(last);
|
|
1389
|
+
return { range: { start: at3, end: at3 }, newText: `, ${name}` };
|
|
1249
1390
|
}
|
|
1250
1391
|
if (existing.defaultImport) {
|
|
1251
|
-
const
|
|
1252
|
-
return { range: { start:
|
|
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
|
|
1259
|
-
return { range: { start:
|
|
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
|
|
1264
|
-
return { range: { start:
|
|
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
|
|
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,
|
|
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,
|
|
1379
|
-
detail: (0,
|
|
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,
|
|
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,
|
|
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
|
|
1474
|
-
const parent =
|
|
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 (
|
|
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
|
|
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(
|
|
1702
|
+
return type.types.length > 0 && type.types.every(isMethodOnly);
|
|
1565
1703
|
default:
|
|
1566
1704
|
return false;
|
|
1567
1705
|
}
|
|
1568
1706
|
}
|
|
1569
|
-
function
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
1715
|
-
if (!
|
|
1716
|
-
return memberType(
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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 = (
|
|
1848
|
-
const line =
|
|
1849
|
-
const character =
|
|
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,
|
|
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,
|
|
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
|
|
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) => {
|