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