luaut-language-server 2.1.0 → 3.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/README.md +2 -2
- package/dist/{chunk-HJ2C3OFZ.js → chunk-SCZTSX3Y.js} +393 -33
- package/dist/chunk-SCZTSX3Y.js.map +1 -0
- package/dist/cli.cjs +418 -58
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +418 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -1
- package/package.json +4 -4
- package/dist/chunk-HJ2C3OFZ.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -68,6 +68,19 @@ var import_luaut_parser2 = require("luaut-parser");
|
|
|
68
68
|
|
|
69
69
|
// src/features/members.ts
|
|
70
70
|
var import_luaut_parser = require("luaut-parser");
|
|
71
|
+
function literalKeys(key, aliases) {
|
|
72
|
+
if (!key) return [];
|
|
73
|
+
const resolved = key.kind === "genericRef" ? aliases.get(key.name) : key;
|
|
74
|
+
if (!resolved) return [];
|
|
75
|
+
const parts = resolved.kind === "union" ? resolved.types : [resolved];
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const part of parts) {
|
|
78
|
+
const member = part.kind === "genericRef" ? aliases.get(part.name) ?? part : part;
|
|
79
|
+
if (member.kind !== "literal" || typeof member.value !== "string") return [];
|
|
80
|
+
out.push(member.value);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
71
84
|
function membersOf(type, aliases, seen = /* @__PURE__ */ new Set()) {
|
|
72
85
|
if (!type || seen.has(type)) return [];
|
|
73
86
|
seen.add(type);
|
|
@@ -77,6 +90,11 @@ function membersOf(type, aliases, seen = /* @__PURE__ */ new Set()) {
|
|
|
77
90
|
for (const [name, property] of type.properties) {
|
|
78
91
|
out.push({ name, property, isMethod: takesSelf(property.type) });
|
|
79
92
|
}
|
|
93
|
+
for (const name of literalKeys(type.indexer?.key, aliases)) {
|
|
94
|
+
if (type.properties.has(name)) continue;
|
|
95
|
+
const property = { type: type.indexer.value, optional: true };
|
|
96
|
+
out.push({ name, property, isMethod: takesSelf(property.type) });
|
|
97
|
+
}
|
|
80
98
|
return out;
|
|
81
99
|
}
|
|
82
100
|
case "intersection": {
|
|
@@ -396,11 +414,13 @@ var Analyzer = class {
|
|
|
396
414
|
const script = path ? context.sourceMap?.scriptFor(path) : void 0;
|
|
397
415
|
const libs = script ? [...context.libs, script] : context.libs;
|
|
398
416
|
const globals = script ? [...context.globals, "script"] : context.globals;
|
|
399
|
-
const { program, errors } = (0, import_luaut_parser2.parseWithRecovery)(source);
|
|
400
|
-
const
|
|
417
|
+
const { program, errors, directives } = (0, import_luaut_parser2.parseWithRecovery)(source);
|
|
418
|
+
const reportUndeclared = context.libs.length > 0;
|
|
419
|
+
const scopes = (0, import_luaut_parser2.analyzeScopes)(program, { builtinGlobals: [...globals], reportUndeclared });
|
|
401
420
|
const dependencies = new Map(context.reads);
|
|
402
421
|
const types = (0, import_luaut_parser2.analyzeTypes)(program, scopes, {
|
|
403
422
|
libs,
|
|
423
|
+
reportUnknownTypes: reportUndeclared,
|
|
404
424
|
resolveModule: (specifier) => {
|
|
405
425
|
if (!path) return void 0;
|
|
406
426
|
const candidates = this.candidatesFor(path, specifier);
|
|
@@ -414,7 +434,7 @@ var Analyzer = class {
|
|
|
414
434
|
return exports2;
|
|
415
435
|
}
|
|
416
436
|
});
|
|
417
|
-
return { uri, version, source, program, parseErrors: errors, scopes, types, dependencies, project: context.project };
|
|
437
|
+
return { uri, version, source, program, parseErrors: errors, directives, scopes, types, dependencies, project: context.project };
|
|
418
438
|
}
|
|
419
439
|
exportsOf(path, importing) {
|
|
420
440
|
const key = pathKey(path);
|
|
@@ -509,7 +529,7 @@ function collect(container, out) {
|
|
|
509
529
|
}
|
|
510
530
|
}
|
|
511
531
|
function isSpanlessNode(v) {
|
|
512
|
-
return !!v && typeof v === "object" &&
|
|
532
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
513
533
|
}
|
|
514
534
|
function pathAt(root, pos, inclusive = false) {
|
|
515
535
|
let best;
|
|
@@ -780,6 +800,7 @@ function patternNamed(target, name) {
|
|
|
780
800
|
|
|
781
801
|
// src/features/diagnostics.ts
|
|
782
802
|
var import_vscode_languageserver2 = require("vscode-languageserver");
|
|
803
|
+
var import_luaut_parser4 = require("luaut-parser");
|
|
783
804
|
function diagnostics(analysis) {
|
|
784
805
|
const out = [];
|
|
785
806
|
for (const error of analysis.parseErrors) {
|
|
@@ -793,37 +814,64 @@ function diagnostics(analysis) {
|
|
|
793
814
|
message: error.message.replace(/\s*\(\d+:\d+\)$/, "")
|
|
794
815
|
});
|
|
795
816
|
}
|
|
796
|
-
|
|
797
|
-
|
|
817
|
+
const semantic = [
|
|
818
|
+
...analysis.scopes.diagnostics.map((d) => ({
|
|
798
819
|
range: toRange(d.node),
|
|
799
820
|
severity: import_vscode_languageserver2.DiagnosticSeverity.Error,
|
|
800
821
|
source: "luaut",
|
|
801
822
|
code: d.kind,
|
|
802
823
|
message: d.message
|
|
803
|
-
})
|
|
804
|
-
|
|
805
|
-
for (const d of analysis.types.diagnostics) {
|
|
806
|
-
out.push({
|
|
824
|
+
})),
|
|
825
|
+
...analysis.types.diagnostics.map((d) => ({
|
|
807
826
|
range: toRange(d.node),
|
|
808
827
|
severity: import_vscode_languageserver2.DiagnosticSeverity.Error,
|
|
809
828
|
source: "luaut",
|
|
810
829
|
code: "type",
|
|
811
830
|
message: d.message
|
|
831
|
+
}))
|
|
832
|
+
];
|
|
833
|
+
const { kept, unusedExpectErrors } = (0, import_luaut_parser4.applyDirectives)(analysis.directives, semantic, (d) => d.range.start.line + 1);
|
|
834
|
+
out.push(...kept);
|
|
835
|
+
for (const directive of unusedExpectErrors) {
|
|
836
|
+
const start = toPosition(directive.line, directive.column);
|
|
837
|
+
out.push({
|
|
838
|
+
range: { start, end: { line: start.line, character: start.character + "--@luaut-expect-error".length } },
|
|
839
|
+
severity: import_vscode_languageserver2.DiagnosticSeverity.Error,
|
|
840
|
+
source: "luaut",
|
|
841
|
+
code: "directive",
|
|
842
|
+
message: import_luaut_parser4.UNUSED_EXPECT_ERROR
|
|
812
843
|
});
|
|
813
844
|
}
|
|
814
845
|
return out;
|
|
815
846
|
}
|
|
816
847
|
|
|
817
848
|
// src/features/hover.ts
|
|
818
|
-
var
|
|
849
|
+
var import_luaut_parser5 = require("luaut-parser");
|
|
819
850
|
function hover(analysis, position) {
|
|
820
851
|
const path = pathAt(analysis.program, position, true);
|
|
821
852
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
853
|
+
if (UNNAMED.has(path[i].type)) return null;
|
|
822
854
|
const text = describe(analysis, path, i);
|
|
823
855
|
if (text) return { contents: { kind: "markdown", value: code(text) }, range: toRange(path[i]) };
|
|
824
856
|
}
|
|
825
857
|
return null;
|
|
826
858
|
}
|
|
859
|
+
var UNNAMED = /* @__PURE__ */ new Set([
|
|
860
|
+
"BinaryExpression",
|
|
861
|
+
"UnaryExpression",
|
|
862
|
+
"CallExpression",
|
|
863
|
+
"MethodCallExpression",
|
|
864
|
+
"MemberExpression",
|
|
865
|
+
"IndexExpression",
|
|
866
|
+
"ParenthesizedExpression",
|
|
867
|
+
"IfElseExpression",
|
|
868
|
+
"TableExpression",
|
|
869
|
+
"ArrayExpression",
|
|
870
|
+
"TypeAssertionExpression",
|
|
871
|
+
"SatisfiesExpression",
|
|
872
|
+
"AsConstExpression",
|
|
873
|
+
"InterpolatedStringExpression"
|
|
874
|
+
]);
|
|
827
875
|
var PRIMITIVES = /* @__PURE__ */ new Set(["any", "unknown", "never", "nil", "boolean", "number", "string", "thread", "buffer"]);
|
|
828
876
|
function describe(analysis, path, index) {
|
|
829
877
|
const { types } = analysis;
|
|
@@ -845,6 +893,27 @@ function describe(analysis, path, index) {
|
|
|
845
893
|
const type2 = property?.type ?? types.typeOf.get(field.value);
|
|
846
894
|
return type2 && `(property) ${name}: ${pretty(type2)}`;
|
|
847
895
|
}
|
|
896
|
+
// `const { name } = t`: a shorthand key *is* the binding it
|
|
897
|
+
// declares, and has the same span, so the cursor can land on
|
|
898
|
+
// either. A renamed key (`{ name: other }`) names the property
|
|
899
|
+
// the value is read from.
|
|
900
|
+
case "ObjectPatternProperty": {
|
|
901
|
+
if (parent.key !== node || parent.computed) break;
|
|
902
|
+
const value = parent.value;
|
|
903
|
+
if (parent.shorthand) return describe(analysis, [...path.slice(0, index), value], index);
|
|
904
|
+
const binding2 = value.type === "IdentifierPattern" ? bindingOfNode(analysis, value) : void 0;
|
|
905
|
+
const type2 = binding2 && types.bindingType.get(binding2.id);
|
|
906
|
+
return type2 && `(property) ${name}: ${pretty(type2)}`;
|
|
907
|
+
}
|
|
908
|
+
// One line of an overload set reads as its own signature.
|
|
909
|
+
// The line the body is on reads as the whole set, which is
|
|
910
|
+
// what the binding says and what the default path gives.
|
|
911
|
+
case "FunctionSignature": {
|
|
912
|
+
if (parent.name !== node) break;
|
|
913
|
+
const own = types.typeOfTypeNode.get(parent);
|
|
914
|
+
if (own) return `function ${name}${pretty(own)}`;
|
|
915
|
+
break;
|
|
916
|
+
}
|
|
848
917
|
case "ImportSpecifier": {
|
|
849
918
|
const alias = types.aliases.get(name);
|
|
850
919
|
const binding2 = bindingOfNode(analysis, identifier2);
|
|
@@ -890,7 +959,7 @@ function describe(analysis, path, index) {
|
|
|
890
959
|
case "MappedTypeNode":
|
|
891
960
|
if (parent.parameterId === node) {
|
|
892
961
|
const keys = typeOfNode(parent.constraint);
|
|
893
|
-
return `(type parameter) ${name}${keys ? ` in ${(0,
|
|
962
|
+
return `(type parameter) ${name}${keys ? ` in ${(0, import_luaut_parser5.formatType)(keys)}` : ""}`;
|
|
894
963
|
}
|
|
895
964
|
break;
|
|
896
965
|
}
|
|
@@ -899,7 +968,7 @@ function describe(analysis, path, index) {
|
|
|
899
968
|
const binding = bindingOfNode(analysis, identifier2);
|
|
900
969
|
if (binding) {
|
|
901
970
|
const type2 = types.bindingType.get(binding.id);
|
|
902
|
-
if (type2) return
|
|
971
|
+
if (type2) return bindingText(binding, type2);
|
|
903
972
|
}
|
|
904
973
|
if (parent?.type === "MemberExpression" || parent?.type === "MethodCallExpression") {
|
|
905
974
|
const type2 = types.typeOf.get(parent);
|
|
@@ -907,13 +976,18 @@ function describe(analysis, path, index) {
|
|
|
907
976
|
}
|
|
908
977
|
return void 0;
|
|
909
978
|
}
|
|
910
|
-
//
|
|
979
|
+
// `...` — what this function's extra arguments are.
|
|
980
|
+
case "VarargExpression": {
|
|
981
|
+
const type2 = types.typeOf.get(node);
|
|
982
|
+
return type2 ? `(vararg) ...: ${pretty(type2)}` : void 0;
|
|
983
|
+
}
|
|
984
|
+
// Declarations: `const x`, a parameter.
|
|
911
985
|
case "IdentifierPattern":
|
|
912
986
|
case "FunctionParameter":
|
|
913
987
|
case "TypedIdentifier": {
|
|
914
988
|
const binding = bindingOfNode(analysis, node);
|
|
915
989
|
const type2 = binding && types.bindingType.get(binding.id);
|
|
916
|
-
return type2 ?
|
|
990
|
+
return type2 ? bindingText(binding, type2) : void 0;
|
|
917
991
|
}
|
|
918
992
|
// A type written by name: `number`, `Shape`, `Partial<User>`, or a type
|
|
919
993
|
// parameter in scope.
|
|
@@ -927,7 +1001,7 @@ function describe(analysis, path, index) {
|
|
|
927
1001
|
if (!node.typeArguments.length) {
|
|
928
1002
|
const qualified = node.namespace ? `${node.namespace}.${base}` : base;
|
|
929
1003
|
const alias = types.aliases.get(qualified);
|
|
930
|
-
if (alias && (0,
|
|
1004
|
+
if (alias && (0, import_luaut_parser5.isClassType)(alias)) return classText(analysis, qualified);
|
|
931
1005
|
if (alias) return `type ${qualified} = ${pretty(alias)}`;
|
|
932
1006
|
}
|
|
933
1007
|
const type2 = typeOfNode(node);
|
|
@@ -955,17 +1029,17 @@ function declareText(analysis, statement) {
|
|
|
955
1029
|
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);
|
|
956
1030
|
const others = total - 1;
|
|
957
1031
|
const overloads = others > 0 ? ` (+${others} overload${others > 1 ? "s" : ""})` : "";
|
|
958
|
-
return `declare function ${name}${(0,
|
|
1032
|
+
return `declare function ${name}${(0, import_luaut_parser5.formatType)(own)}${overloads}`;
|
|
959
1033
|
}
|
|
960
1034
|
function classText(analysis, name) {
|
|
961
1035
|
const type = analysis.types.aliases.get(name);
|
|
962
|
-
if (!type || !(0,
|
|
1036
|
+
if (!type || !(0, import_luaut_parser5.isClassType)(type)) return void 0;
|
|
963
1037
|
const superclass = type.class.superclass;
|
|
964
1038
|
const inherited = superclass ? analysis.types.aliases.get(superclass) : void 0;
|
|
965
1039
|
const own = [...type.properties].filter(([key, property]) => inherited?.kind !== "object" || inherited.properties.get(key) !== property);
|
|
966
1040
|
const head = `declare class ${name}${superclass ? ` extends ${superclass}` : ""}`;
|
|
967
1041
|
if (!own.length) return `${head} {}`;
|
|
968
|
-
const lines = own.map(([key, property]) => ` ${property.readonly ? "readonly " : ""}${key}${property.optional ? "?" : ""}: ${(0,
|
|
1042
|
+
const lines = own.map(([key, property]) => ` ${property.readonly ? "readonly " : ""}${key}${property.optional ? "?" : ""}: ${(0, import_luaut_parser5.formatType)(property.type)},`);
|
|
969
1043
|
return `${head} {
|
|
970
1044
|
${lines.join("\n")}
|
|
971
1045
|
}`;
|
|
@@ -977,7 +1051,7 @@ function typeParameterSignature(analysis, parameter) {
|
|
|
977
1051
|
const p = parameter;
|
|
978
1052
|
if (p.infer) return `infer ${p.name}`;
|
|
979
1053
|
const constraint = p.constraint ? analysis.types.typeOfTypeNode.get(p.constraint) : void 0;
|
|
980
|
-
return `${p.isConst ? "const " : ""}${p.name}${constraint ? ` extends ${(0,
|
|
1054
|
+
return `${p.isConst ? "const " : ""}${p.name}${constraint ? ` extends ${(0, import_luaut_parser5.formatType)(constraint)}` : ""}`;
|
|
981
1055
|
}
|
|
982
1056
|
function typeParameterInScope(path, index, name) {
|
|
983
1057
|
for (let i = index - 1; i >= 0; i--) {
|
|
@@ -1002,7 +1076,7 @@ function referenceText(analysis, reference) {
|
|
|
1002
1076
|
if (!args.length) return name;
|
|
1003
1077
|
const resolved = args.map((a) => {
|
|
1004
1078
|
const t = analysis.types.typeOfTypeNode.get(a);
|
|
1005
|
-
return t ? (0,
|
|
1079
|
+
return t ? (0, import_luaut_parser5.formatType)(t) : "?";
|
|
1006
1080
|
});
|
|
1007
1081
|
return `${name}<${resolved.join(", ")}>`;
|
|
1008
1082
|
}
|
|
@@ -1011,28 +1085,37 @@ function fieldWithKey(table, key) {
|
|
|
1011
1085
|
return fields.find((f) => f.type === "TableFieldNamed" && f.key === key);
|
|
1012
1086
|
}
|
|
1013
1087
|
function pretty(type) {
|
|
1014
|
-
const flat = (0,
|
|
1088
|
+
const flat = (0, import_luaut_parser5.formatType)(type);
|
|
1015
1089
|
if (flat.length <= 80) return flat;
|
|
1016
1090
|
if (type.kind === "object") {
|
|
1017
1091
|
const lines = [];
|
|
1018
|
-
if (type.indexer) lines.push(` [${(0,
|
|
1092
|
+
if (type.indexer) lines.push(` [${(0, import_luaut_parser5.formatType)(type.indexer.key)}]: ${(0, import_luaut_parser5.formatType)(type.indexer.value)},`);
|
|
1019
1093
|
for (const [name, property] of type.properties) {
|
|
1020
1094
|
const readonly = property.readonly ? "readonly " : "";
|
|
1021
|
-
lines.push(` ${readonly}${name}${property.optional ? "?" : ""}: ${(0,
|
|
1095
|
+
lines.push(` ${readonly}${name}${property.optional ? "?" : ""}: ${(0, import_luaut_parser5.formatType)(property.type)},`);
|
|
1022
1096
|
}
|
|
1023
1097
|
return `{
|
|
1024
1098
|
${lines.join("\n")}
|
|
1025
1099
|
}`;
|
|
1026
1100
|
}
|
|
1027
1101
|
if (type.kind === "intersection" && type.types.every((t) => t.kind === "function")) {
|
|
1028
|
-
return type.types.map(
|
|
1102
|
+
return type.types.map(import_luaut_parser5.formatType).join("\n& ");
|
|
1029
1103
|
}
|
|
1030
1104
|
return flat;
|
|
1031
1105
|
}
|
|
1106
|
+
function bindingText(binding, type) {
|
|
1107
|
+
if (binding.declaredBy === "function" && type.kind === "function") {
|
|
1108
|
+
return `function ${binding.name}${(0, import_luaut_parser5.formatType)(type)}`;
|
|
1109
|
+
}
|
|
1110
|
+
return `${keyword(binding)} ${binding.name}: ${pretty(type)}`;
|
|
1111
|
+
}
|
|
1032
1112
|
function keyword(binding) {
|
|
1033
1113
|
if (binding.kind === "param" || binding.kind === "self") return "(parameter)";
|
|
1034
1114
|
if (binding.kind === "global") return "(global)";
|
|
1035
1115
|
if (binding.kind.startsWith("for-")) return "(loop variable)";
|
|
1116
|
+
if (binding.declaredBy === "import" || binding.declaredBy === "namespace") return "(import)";
|
|
1117
|
+
if (binding.declaredBy === "type") return "(type import)";
|
|
1118
|
+
if (binding.declaredBy === "function") return "function";
|
|
1036
1119
|
return binding.isConst ? "const" : "let";
|
|
1037
1120
|
}
|
|
1038
1121
|
function code(text) {
|
|
@@ -1099,8 +1182,162 @@ function isIdentifier(name) {
|
|
|
1099
1182
|
}
|
|
1100
1183
|
|
|
1101
1184
|
// src/features/completion.ts
|
|
1185
|
+
var import_vscode_languageserver5 = require("vscode-languageserver");
|
|
1186
|
+
var import_luaut_parser6 = require("luaut-parser");
|
|
1187
|
+
|
|
1188
|
+
// src/features/autoImport.ts
|
|
1189
|
+
var import_node_fs3 = require("fs");
|
|
1190
|
+
var import_node_path3 = require("path");
|
|
1102
1191
|
var import_vscode_languageserver4 = require("vscode-languageserver");
|
|
1103
|
-
|
|
1192
|
+
function importItems(analyzer, analysis, typePosition, taken) {
|
|
1193
|
+
const from = pathOfUri(analysis.uri);
|
|
1194
|
+
if (!from) return [];
|
|
1195
|
+
const config = analysis.project.config;
|
|
1196
|
+
const items = [];
|
|
1197
|
+
const offered = /* @__PURE__ */ new Set();
|
|
1198
|
+
for (const file of projectFiles(config?.directory ?? (0, import_node_path3.dirname)(from))) {
|
|
1199
|
+
if (samePath(file, from)) continue;
|
|
1200
|
+
const exports2 = analyzer.exportsAt(file);
|
|
1201
|
+
if (!exports2 || exports2.partial) continue;
|
|
1202
|
+
const names = typePosition ? [...exports2.types.keys()] : [...exports2.values.keys()];
|
|
1203
|
+
const specifier = specifierFor(from, file, config);
|
|
1204
|
+
for (const name of names) {
|
|
1205
|
+
if (taken.has(name) || offered.has(name)) continue;
|
|
1206
|
+
offered.add(name);
|
|
1207
|
+
const type = typePosition ? exports2.types.get(name)?.type : exports2.values.get(name);
|
|
1208
|
+
items.push({
|
|
1209
|
+
label: name,
|
|
1210
|
+
kind: typePosition ? import_vscode_languageserver4.CompletionItemKind.Interface : type && signaturesOf(type).length ? import_vscode_languageserver4.CompletionItemKind.Function : import_vscode_languageserver4.CompletionItemKind.Variable,
|
|
1211
|
+
labelDetails: { description: specifier },
|
|
1212
|
+
detail: `import { ${name} } from "${specifier}"`,
|
|
1213
|
+
sortText: `4${name}`,
|
|
1214
|
+
additionalTextEdits: [importEdit(analyzer, analysis, file, name, specifier, typePosition)]
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return items;
|
|
1219
|
+
}
|
|
1220
|
+
function serviceItems(analysis, taken) {
|
|
1221
|
+
const services = analysis.types.aliases.get("Services");
|
|
1222
|
+
if (!services || services.kind !== "object") return [];
|
|
1223
|
+
const at = serviceInsertion(analysis.program.body.statements);
|
|
1224
|
+
const items = [];
|
|
1225
|
+
for (const name of services.properties.keys()) {
|
|
1226
|
+
if (taken.has(name)) continue;
|
|
1227
|
+
const line = `const ${name} = game:GetService("${name}")`;
|
|
1228
|
+
items.push({
|
|
1229
|
+
label: name,
|
|
1230
|
+
kind: import_vscode_languageserver4.CompletionItemKind.Module,
|
|
1231
|
+
labelDetails: { description: "service" },
|
|
1232
|
+
detail: line,
|
|
1233
|
+
sortText: `5${name}`,
|
|
1234
|
+
additionalTextEdits: [{ range: { start: at.position, end: at.position }, newText: `${line}
|
|
1235
|
+
${at.gap}` }]
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
return items;
|
|
1239
|
+
}
|
|
1240
|
+
function importEdit(analyzer, analysis, file, name, specifier, typePosition) {
|
|
1241
|
+
const statements = analysis.program.body.statements;
|
|
1242
|
+
const imports = statements.filter((s) => s.type === "ImportStatement");
|
|
1243
|
+
const existing = imports.find((s) => !s.namespaceImport && (typePosition || !s.isTypeOnly) && samePathOrUndefined(analyzer.resolveModulePath(analysis.uri, s.source.value), file));
|
|
1244
|
+
if (existing) {
|
|
1245
|
+
const last = existing.specifiers[existing.specifiers.length - 1];
|
|
1246
|
+
if (last) {
|
|
1247
|
+
const at2 = endOf(last);
|
|
1248
|
+
return { range: { start: at2, end: at2 }, newText: `, ${name}` };
|
|
1249
|
+
}
|
|
1250
|
+
if (existing.defaultImport) {
|
|
1251
|
+
const at2 = endOf(existing.defaultImport);
|
|
1252
|
+
return { range: { start: at2, end: at2 }, newText: `, { ${name} }` };
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
const line = `import { ${name} } from "${specifier}"`;
|
|
1256
|
+
const lastImport = imports[imports.length - 1];
|
|
1257
|
+
if (lastImport) {
|
|
1258
|
+
const at2 = { line: lastImport.line.end, character: 0 };
|
|
1259
|
+
return { range: { start: at2, end: at2 }, newText: `${line}
|
|
1260
|
+
` };
|
|
1261
|
+
}
|
|
1262
|
+
const first = statements[0];
|
|
1263
|
+
const at = { line: first ? first.line.start - 1 : 0, character: 0 };
|
|
1264
|
+
return { range: { start: at, end: at }, newText: first ? `${line}
|
|
1265
|
+
|
|
1266
|
+
` : `${line}
|
|
1267
|
+
` };
|
|
1268
|
+
}
|
|
1269
|
+
function serviceInsertion(statements) {
|
|
1270
|
+
let last;
|
|
1271
|
+
for (const statement of statements) {
|
|
1272
|
+
if (statement.type !== "ImportStatement" && !isServiceDeclaration(statement)) break;
|
|
1273
|
+
last = statement;
|
|
1274
|
+
}
|
|
1275
|
+
if (last) return { position: { line: last.line.end, character: 0 }, gap: "" };
|
|
1276
|
+
const first = statements[0];
|
|
1277
|
+
return first ? { position: { line: first.line.start - 1, character: 0 }, gap: "\n" } : { position: { line: 0, character: 0 }, gap: "" };
|
|
1278
|
+
}
|
|
1279
|
+
function isServiceDeclaration(statement) {
|
|
1280
|
+
if (statement.type !== "VariableDeclaration") return false;
|
|
1281
|
+
const init = statement.init[0];
|
|
1282
|
+
return init?.type === "MethodCallExpression" && init.method.name === "GetService" && init.object.type === "Identifier" && init.object.name === "game";
|
|
1283
|
+
}
|
|
1284
|
+
function endOf(node) {
|
|
1285
|
+
return { line: node.line.end - 1, character: node.column.end - 1 };
|
|
1286
|
+
}
|
|
1287
|
+
function samePathOrUndefined(a, b) {
|
|
1288
|
+
return a !== void 0 && samePath(a, b);
|
|
1289
|
+
}
|
|
1290
|
+
function specifierFor(from, target, config) {
|
|
1291
|
+
const withoutExtension = (path) => {
|
|
1292
|
+
const bare = path.replace(/\\/g, "/").replace(/\.luaut$/, "");
|
|
1293
|
+
return bare.endsWith("/index") ? bare.slice(0, -"/index".length) : bare;
|
|
1294
|
+
};
|
|
1295
|
+
let relativePath = withoutExtension((0, import_node_path3.relative)((0, import_node_path3.dirname)(from), target));
|
|
1296
|
+
if (!relativePath.startsWith(".")) relativePath = `./${relativePath}`;
|
|
1297
|
+
if (!relativePath.startsWith("../") || !config) return relativePath;
|
|
1298
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
1299
|
+
const star = pattern.indexOf("*");
|
|
1300
|
+
if (star < 0) continue;
|
|
1301
|
+
for (const targetPattern of targets) {
|
|
1302
|
+
const cut = targetPattern.indexOf("*");
|
|
1303
|
+
if (cut < 0) continue;
|
|
1304
|
+
const head = (0, import_node_path3.resolve)(config.baseUrl, targetPattern.slice(0, cut));
|
|
1305
|
+
const rest = (0, import_node_path3.relative)(head, target);
|
|
1306
|
+
if (rest.startsWith("..") || (0, import_node_path3.resolve)(head, rest) !== (0, import_node_path3.resolve)(target)) continue;
|
|
1307
|
+
return `${pattern.slice(0, star)}${withoutExtension(rest)}${pattern.slice(star + 1)}`;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
return relativePath;
|
|
1311
|
+
}
|
|
1312
|
+
var FILE_LIMIT = 2e3;
|
|
1313
|
+
var LISTING_TTL = 3e3;
|
|
1314
|
+
var listings = /* @__PURE__ */ new Map();
|
|
1315
|
+
function projectFiles(root) {
|
|
1316
|
+
const cached = listings.get(root);
|
|
1317
|
+
if (cached && Date.now() - cached.at < LISTING_TTL) return cached.files;
|
|
1318
|
+
const files = [];
|
|
1319
|
+
const walk2 = (directory, depth) => {
|
|
1320
|
+
if (files.length >= FILE_LIMIT || depth > 12) return;
|
|
1321
|
+
let entries;
|
|
1322
|
+
try {
|
|
1323
|
+
entries = (0, import_node_fs3.readdirSync)(directory, { withFileTypes: true });
|
|
1324
|
+
} catch {
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
for (const entry of entries) {
|
|
1328
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
1329
|
+
const path = (0, import_node_path3.join)(directory, entry.name);
|
|
1330
|
+
if (entry.isDirectory()) walk2(path, depth + 1);
|
|
1331
|
+
else if (entry.name.endsWith(".luaut") && !entry.name.endsWith(".d.luaut")) files.push(path);
|
|
1332
|
+
if (files.length >= FILE_LIMIT) return;
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
walk2(root, 0);
|
|
1336
|
+
listings.set(root, { at: Date.now(), files });
|
|
1337
|
+
return files;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// src/features/completion.ts
|
|
1104
1341
|
var PLACEHOLDER = "__luautCompletion__";
|
|
1105
1342
|
var IDENTIFIER_CHAR = /[A-Za-z0-9_]/;
|
|
1106
1343
|
function completion(analyzer, document, position) {
|
|
@@ -1133,28 +1370,58 @@ function completion(analyzer, document, position) {
|
|
|
1133
1370
|
first ??= { analysis, path };
|
|
1134
1371
|
}
|
|
1135
1372
|
if (operator || !first) return [];
|
|
1373
|
+
const keys = objectKeyItems(first.analysis, first.path, source.slice(end));
|
|
1374
|
+
if (keys) return keys;
|
|
1136
1375
|
if (inTypePosition(first.path)) {
|
|
1137
1376
|
const named = [...first.analysis.types.aliases].map(([name, type]) => ({
|
|
1138
1377
|
label: name,
|
|
1139
|
-
kind: (0,
|
|
1140
|
-
detail: (0,
|
|
1378
|
+
kind: (0, import_luaut_parser6.isClassType)(type) ? import_vscode_languageserver5.CompletionItemKind.Class : import_vscode_languageserver5.CompletionItemKind.Interface,
|
|
1379
|
+
detail: (0, import_luaut_parser6.isClassType)(type) ? "class" : "type"
|
|
1141
1380
|
}));
|
|
1142
1381
|
const primitives = PRIMITIVES2.map((name) => ({
|
|
1143
1382
|
label: name,
|
|
1144
|
-
kind:
|
|
1383
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Keyword,
|
|
1145
1384
|
detail: "type"
|
|
1146
1385
|
}));
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1386
|
+
const keywords = TYPE_KEYWORDS.map((name) => ({
|
|
1387
|
+
label: name,
|
|
1388
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Keyword,
|
|
1389
|
+
sortText: `3${name}`
|
|
1390
|
+
}));
|
|
1391
|
+
const typeNames = new Set(first.analysis.types.aliases.keys());
|
|
1392
|
+
const imported = importItems(analyzer, analyzer.get(document), true, typeNames);
|
|
1393
|
+
return [...named, ...primitives, ...keywords, ...imported];
|
|
1394
|
+
}
|
|
1395
|
+
const taken = /* @__PURE__ */ new Set();
|
|
1396
|
+
for (const binding of first.analysis.scopes.bindings.values()) taken.add(binding.name);
|
|
1397
|
+
const current = analyzer.get(document);
|
|
1398
|
+
return [
|
|
1399
|
+
...valueItems(first.analysis, at),
|
|
1400
|
+
...contextKeywords(source.slice(0, start)),
|
|
1401
|
+
...importItems(analyzer, current, false, taken),
|
|
1402
|
+
...serviceItems(current, taken)
|
|
1403
|
+
];
|
|
1150
1404
|
}
|
|
1151
1405
|
function stringCompletion(analyzer, document, position) {
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1406
|
+
let analysis = analyzer.get(document);
|
|
1407
|
+
let literal = stringAt(analysis, position);
|
|
1408
|
+
if (!literal) {
|
|
1409
|
+
const repaired = repairedStrings(document, position);
|
|
1410
|
+
for (const text of repaired) {
|
|
1411
|
+
const candidate = analyzer.analyze(document.uri, -1, text);
|
|
1412
|
+
literal = stringAt(candidate, position);
|
|
1413
|
+
if (literal) {
|
|
1414
|
+
analysis = candidate;
|
|
1415
|
+
break;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
if (!literal) return repaired.length ? [] : void 0;
|
|
1419
|
+
}
|
|
1156
1420
|
const expected = analysis.types.expectedTypeOf.get(literal);
|
|
1157
|
-
const values =
|
|
1421
|
+
const values = [.../* @__PURE__ */ new Set([
|
|
1422
|
+
...stringLiterals(expected, analysis.types.aliases),
|
|
1423
|
+
...indexKeys(analysis, position, literal)
|
|
1424
|
+
])];
|
|
1158
1425
|
if (!values.length) return [];
|
|
1159
1426
|
const line = literal.line.start - 1;
|
|
1160
1427
|
const range = literal.line.start === literal.line.end ? {
|
|
@@ -1163,10 +1430,81 @@ function stringCompletion(analyzer, document, position) {
|
|
|
1163
1430
|
} : void 0;
|
|
1164
1431
|
return values.map((value) => ({
|
|
1165
1432
|
label: value,
|
|
1166
|
-
kind:
|
|
1433
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Constant,
|
|
1167
1434
|
...range ? { textEdit: { range, newText: value } } : {}
|
|
1168
1435
|
}));
|
|
1169
1436
|
}
|
|
1437
|
+
function stringAt(analysis, position) {
|
|
1438
|
+
const path = pathAt(analysis.program, position, false);
|
|
1439
|
+
return [...path].reverse().find((n) => n.type === "StringLiteral" || n.type === "TypeLiteralString");
|
|
1440
|
+
}
|
|
1441
|
+
function objectKeyItems(analysis, path, after) {
|
|
1442
|
+
const index = path.findLastIndex(
|
|
1443
|
+
(n) => n.type === "Identifier" && n.name === PLACEHOLDER
|
|
1444
|
+
);
|
|
1445
|
+
const literal = index > 0 ? path[index - 1] : void 0;
|
|
1446
|
+
if (literal?.type !== "TableExpression") return void 0;
|
|
1447
|
+
const fields = literal.fields;
|
|
1448
|
+
const atKey = fields.some((f) => f.type === "TableFieldShorthand" && f.name?.name === PLACEHOLDER);
|
|
1449
|
+
if (!atKey) return void 0;
|
|
1450
|
+
let expected = analysis.types.expectedTypeOf.get(literal);
|
|
1451
|
+
for (let up = index - 2; expected === void 0 && up >= 0; up--) {
|
|
1452
|
+
const outer = path[up];
|
|
1453
|
+
if (outer.type !== "AsConstExpression" && outer.type !== "ParenthesizedExpression") break;
|
|
1454
|
+
expected = analysis.types.expectedTypeOf.get(outer);
|
|
1455
|
+
}
|
|
1456
|
+
const members = membersOf(expected, analysis.types.aliases);
|
|
1457
|
+
if (!members.length) return void 0;
|
|
1458
|
+
const written = /* @__PURE__ */ new Set();
|
|
1459
|
+
for (const field of fields) {
|
|
1460
|
+
if (field.type === "TableFieldNamed") written.add(field.key?.name ?? field.key?.value ?? "");
|
|
1461
|
+
else if (field.type === "TableFieldShorthand" && field.name?.name !== PLACEHOLDER) written.add(field.name.name);
|
|
1462
|
+
}
|
|
1463
|
+
const colon = /^\s*:/.test(after);
|
|
1464
|
+
return members.filter((member) => !written.has(member.name)).map((member) => ({
|
|
1465
|
+
label: member.name,
|
|
1466
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Field,
|
|
1467
|
+
detail: `${member.property.optional ? "?" : ""}: ${(0, import_luaut_parser6.formatType)(member.property.type)}`,
|
|
1468
|
+
insertText: colon ? member.name : `${member.name}: `
|
|
1469
|
+
}));
|
|
1470
|
+
}
|
|
1471
|
+
function indexKeys(analysis, position, literal) {
|
|
1472
|
+
const path = pathAt(analysis.program, position, false);
|
|
1473
|
+
const at = path.indexOf(literal);
|
|
1474
|
+
const parent = at > 0 ? path[at - 1] : void 0;
|
|
1475
|
+
if (!parent) return [];
|
|
1476
|
+
let indexed;
|
|
1477
|
+
if (parent.type === "IndexedAccessTypeNode" && parent.indexType === literal) {
|
|
1478
|
+
indexed = analysis.types.typeOfTypeNode.get(parent.objectType);
|
|
1479
|
+
} else if (parent.type === "IndexExpression" && parent.index === literal) {
|
|
1480
|
+
indexed = withoutNil(analysis.types.typeOf.get(parent.object));
|
|
1481
|
+
}
|
|
1482
|
+
return membersOf(indexed, analysis.types.aliases).map((member) => member.name);
|
|
1483
|
+
}
|
|
1484
|
+
function repairedStrings(document, position) {
|
|
1485
|
+
const source = document.getText();
|
|
1486
|
+
const offset = document.offsetAt(position);
|
|
1487
|
+
const lineStart = offset - position.character;
|
|
1488
|
+
const lineEndIndex = source.indexOf("\n", offset);
|
|
1489
|
+
const lineEnd = lineEndIndex < 0 ? source.length : lineEndIndex;
|
|
1490
|
+
const before = source.slice(lineStart, offset);
|
|
1491
|
+
let quote;
|
|
1492
|
+
for (let i = 0; i < before.length; i++) {
|
|
1493
|
+
const ch = before[i];
|
|
1494
|
+
if (quote) {
|
|
1495
|
+
if (ch === "\\") i++;
|
|
1496
|
+
else if (ch === quote) quote = void 0;
|
|
1497
|
+
} else if (ch === '"' || ch === "'") {
|
|
1498
|
+
quote = ch;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
if (!quote) return [];
|
|
1502
|
+
let rest = source.slice(offset, lineEnd).replace(/\r$/, "");
|
|
1503
|
+
if (!rest.includes(quote)) rest += quote;
|
|
1504
|
+
const line = before + rest;
|
|
1505
|
+
const endings = ["", " then end", " do end", ")", ") then end", "]"];
|
|
1506
|
+
return endings.map((ending) => source.slice(0, lineStart) + line + ending + source.slice(lineEnd));
|
|
1507
|
+
}
|
|
1170
1508
|
function stringLiterals(type, aliases, seen = /* @__PURE__ */ new Set()) {
|
|
1171
1509
|
if (!type || seen.has(type)) return [];
|
|
1172
1510
|
seen.add(type);
|
|
@@ -1198,7 +1536,7 @@ function memberOperator(source, wordStart) {
|
|
|
1198
1536
|
}
|
|
1199
1537
|
function memberItems(analysis, access) {
|
|
1200
1538
|
const object = access.object;
|
|
1201
|
-
const type = analysis.types.typeOf.get(object);
|
|
1539
|
+
const type = withoutNil(analysis.types.typeOf.get(object));
|
|
1202
1540
|
const colon = access.type === "MethodCallExpression";
|
|
1203
1541
|
if (isStringLike(type)) {
|
|
1204
1542
|
if (!colon) return [];
|
|
@@ -1208,6 +1546,11 @@ function memberItems(analysis, access) {
|
|
|
1208
1546
|
}
|
|
1209
1547
|
return membersOf(type, analysis.types.aliases).filter((member) => colon ? member.isMethod : true).map((member) => memberItem(member.name, member.property.type, member.property.readonly));
|
|
1210
1548
|
}
|
|
1549
|
+
function withoutNil(type) {
|
|
1550
|
+
if (type?.kind !== "union") return type;
|
|
1551
|
+
const kept = type.types.filter((t) => !(t.kind === "primitive" && t.name === "nil"));
|
|
1552
|
+
return kept.length === 1 ? kept[0] : { ...type, types: kept };
|
|
1553
|
+
}
|
|
1211
1554
|
function isStringLike(type) {
|
|
1212
1555
|
if (!type) return false;
|
|
1213
1556
|
switch (type.kind) {
|
|
@@ -1228,6 +1571,7 @@ function valueItems(analysis, at) {
|
|
|
1228
1571
|
const seen = /* @__PURE__ */ new Set();
|
|
1229
1572
|
for (const binding of analysis.scopes.bindings.values()) {
|
|
1230
1573
|
if (binding.name === PLACEHOLDER || seen.has(binding.name)) continue;
|
|
1574
|
+
if (binding.declaredBy === "type") continue;
|
|
1231
1575
|
const declaration = binding.declarationNode;
|
|
1232
1576
|
if (declaration && declaration.line.start - 1 > at.line) continue;
|
|
1233
1577
|
seen.add(binding.name);
|
|
@@ -1235,13 +1579,13 @@ function valueItems(analysis, at) {
|
|
|
1235
1579
|
items.push({
|
|
1236
1580
|
label: binding.name,
|
|
1237
1581
|
kind: kindOf(type, binding.kind),
|
|
1238
|
-
detail: type ? (0,
|
|
1582
|
+
detail: type ? (0, import_luaut_parser6.formatType)(type) : void 0,
|
|
1239
1583
|
// Locals before globals, and globals before library names.
|
|
1240
1584
|
sortText: `${binding.isBuiltin ? 2 : binding.kind === "global" ? 1 : 0}${binding.name}`
|
|
1241
1585
|
});
|
|
1242
1586
|
}
|
|
1243
1587
|
for (const keyword2 of KEYWORDS) {
|
|
1244
|
-
items.push({ label: keyword2, kind:
|
|
1588
|
+
items.push({ label: keyword2, kind: import_vscode_languageserver5.CompletionItemKind.Keyword, sortText: `3${keyword2}` });
|
|
1245
1589
|
}
|
|
1246
1590
|
return items;
|
|
1247
1591
|
}
|
|
@@ -1250,22 +1594,22 @@ function memberItem(name, type, readonly) {
|
|
|
1250
1594
|
if (signatures.length) {
|
|
1251
1595
|
return {
|
|
1252
1596
|
label: name,
|
|
1253
|
-
kind:
|
|
1597
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Method,
|
|
1254
1598
|
detail: signatureLabel(signatures[0]).label,
|
|
1255
1599
|
insertText: `${name}($0)`,
|
|
1256
|
-
insertTextFormat:
|
|
1600
|
+
insertTextFormat: import_vscode_languageserver5.InsertTextFormat.Snippet
|
|
1257
1601
|
};
|
|
1258
1602
|
}
|
|
1259
1603
|
return {
|
|
1260
1604
|
label: name,
|
|
1261
|
-
kind:
|
|
1262
|
-
detail: `${readonly ? "readonly " : ""}${(0,
|
|
1605
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Field,
|
|
1606
|
+
detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser6.formatType)(type)}`
|
|
1263
1607
|
};
|
|
1264
1608
|
}
|
|
1265
1609
|
function kindOf(type, bindingKind) {
|
|
1266
|
-
if (type && signaturesOf(type).length) return
|
|
1267
|
-
if (bindingKind === "param" || bindingKind === "self") return
|
|
1268
|
-
return
|
|
1610
|
+
if (type && signaturesOf(type).length) return import_vscode_languageserver5.CompletionItemKind.Function;
|
|
1611
|
+
if (bindingKind === "param" || bindingKind === "self") return import_vscode_languageserver5.CompletionItemKind.Variable;
|
|
1612
|
+
return import_vscode_languageserver5.CompletionItemKind.Variable;
|
|
1269
1613
|
}
|
|
1270
1614
|
function inTypePosition(path) {
|
|
1271
1615
|
return path.some(
|
|
@@ -1283,6 +1627,17 @@ var PRIMITIVES2 = [
|
|
|
1283
1627
|
"thread",
|
|
1284
1628
|
"buffer"
|
|
1285
1629
|
];
|
|
1630
|
+
var TYPE_KEYWORDS = ["keyof", "typeof", "infer", "extends"];
|
|
1631
|
+
function contextKeywords(before) {
|
|
1632
|
+
const keyword2 = (name) => ({
|
|
1633
|
+
label: name,
|
|
1634
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Keyword,
|
|
1635
|
+
sortText: `3${name}`
|
|
1636
|
+
});
|
|
1637
|
+
if (/<\s*[A-Za-z_]\w*(?:\s*,\s*[A-Za-z_]\w*)*\s+$/.test(before)) return [keyword2("extends")];
|
|
1638
|
+
if (/[)\]}"'`\w][^\S\n]+$/.test(before)) return [keyword2("as"), keyword2("satisfies")];
|
|
1639
|
+
return [];
|
|
1640
|
+
}
|
|
1286
1641
|
var KEYWORDS = [
|
|
1287
1642
|
"const",
|
|
1288
1643
|
"let",
|
|
@@ -1388,8 +1743,8 @@ function activeArgument(call, position) {
|
|
|
1388
1743
|
}
|
|
1389
1744
|
|
|
1390
1745
|
// src/features/symbols.ts
|
|
1391
|
-
var
|
|
1392
|
-
var
|
|
1746
|
+
var import_vscode_languageserver6 = require("vscode-languageserver");
|
|
1747
|
+
var import_luaut_parser7 = require("luaut-parser");
|
|
1393
1748
|
function documentSymbols(analysis) {
|
|
1394
1749
|
const out = [];
|
|
1395
1750
|
walk(analysis.program, (node) => {
|
|
@@ -1397,7 +1752,7 @@ function documentSymbols(analysis) {
|
|
|
1397
1752
|
case "FunctionDeclaration":
|
|
1398
1753
|
case "FunctionDeclarationStatement": {
|
|
1399
1754
|
const name = functionName(node);
|
|
1400
|
-
if (name) out.push(symbol(name,
|
|
1755
|
+
if (name) out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Function, node, detailOf(analysis, node)));
|
|
1401
1756
|
break;
|
|
1402
1757
|
}
|
|
1403
1758
|
case "TypeAliasStatement":
|
|
@@ -1406,20 +1761,20 @@ function documentSymbols(analysis) {
|
|
|
1406
1761
|
const name = typeof named === "string" ? named : named?.name;
|
|
1407
1762
|
if (name) {
|
|
1408
1763
|
const alias = analysis.types.aliases.get(name);
|
|
1409
|
-
out.push(symbol(name,
|
|
1764
|
+
out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Interface, node, alias ? (0, import_luaut_parser7.formatType)(alias) : void 0));
|
|
1410
1765
|
}
|
|
1411
1766
|
break;
|
|
1412
1767
|
}
|
|
1413
1768
|
case "DeclareClassStatement": {
|
|
1414
1769
|
const name = node.name.name;
|
|
1415
1770
|
const superclass = node.superclass?.base;
|
|
1416
|
-
out.push(symbol(name,
|
|
1771
|
+
out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Class, node, superclass && `extends ${superclass}`));
|
|
1417
1772
|
break;
|
|
1418
1773
|
}
|
|
1419
1774
|
case "VariableDeclaration": {
|
|
1420
1775
|
for (const target of node.names ?? []) {
|
|
1421
1776
|
const name = target.name;
|
|
1422
|
-
if (name) out.push(symbol(name,
|
|
1777
|
+
if (name) out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Variable, target));
|
|
1423
1778
|
}
|
|
1424
1779
|
break;
|
|
1425
1780
|
}
|
|
@@ -1443,7 +1798,7 @@ function detailOf(analysis, node) {
|
|
|
1443
1798
|
if (name && typeof name === "object") {
|
|
1444
1799
|
const binding = bindingOfNode(analysis, name);
|
|
1445
1800
|
const type = binding && analysis.types.bindingType.get(binding.id);
|
|
1446
|
-
if (type) return (0,
|
|
1801
|
+
if (type) return (0, import_luaut_parser7.formatType)(type);
|
|
1447
1802
|
}
|
|
1448
1803
|
return void 0;
|
|
1449
1804
|
}
|
|
@@ -1453,7 +1808,7 @@ function symbol(name, kind, node, detail) {
|
|
|
1453
1808
|
}
|
|
1454
1809
|
|
|
1455
1810
|
// src/features/semanticTokens.ts
|
|
1456
|
-
var
|
|
1811
|
+
var import_luaut_parser8 = require("luaut-parser");
|
|
1457
1812
|
var TOKEN_TYPES = [
|
|
1458
1813
|
"namespace",
|
|
1459
1814
|
"type",
|
|
@@ -1497,7 +1852,7 @@ function semanticTokens(analysis) {
|
|
|
1497
1852
|
};
|
|
1498
1853
|
let tokens = [];
|
|
1499
1854
|
try {
|
|
1500
|
-
tokens = (0,
|
|
1855
|
+
tokens = (0, import_luaut_parser8.tokenize)(analysis.source);
|
|
1501
1856
|
} catch {
|
|
1502
1857
|
}
|
|
1503
1858
|
const identifiers = tokens.filter((t) => t.type === "Identifier");
|
|
@@ -1541,7 +1896,7 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
|
|
|
1541
1896
|
if (!baseToken) return;
|
|
1542
1897
|
if (!namespace && typeParameterInScope2(ancestors, base)) {
|
|
1543
1898
|
add(baseToken, base.length, "typeParameter");
|
|
1544
|
-
} else if ((0,
|
|
1899
|
+
} else if ((0, import_luaut_parser8.isClassType)(analysis.types.aliases.get(namespace ? `${namespace}.${base}` : base) ?? import_luaut_parser8.unknownType)) {
|
|
1545
1900
|
add(baseToken, base.length, "class");
|
|
1546
1901
|
} else {
|
|
1547
1902
|
add(baseToken, base.length, "type", PRIMITIVES3.has(base) ? ["defaultLibrary"] : []);
|
|
@@ -1596,10 +1951,14 @@ function identifier(analysis, node, parent, add) {
|
|
|
1596
1951
|
break;
|
|
1597
1952
|
case "ImportSpecifier": {
|
|
1598
1953
|
const binding2 = bindingOfNode(analysis, node);
|
|
1954
|
+
if (binding2?.declaredBy === "type") return as("type", ["declaration"]);
|
|
1599
1955
|
const value = binding2 && analysis.types.bindingType.get(binding2.id);
|
|
1600
1956
|
if (analysis.types.aliases.has(name) && (!value || value.kind === "any")) return as("type", ["declaration"]);
|
|
1601
1957
|
break;
|
|
1602
1958
|
}
|
|
1959
|
+
case "ImportStatement":
|
|
1960
|
+
if (parent.namespaceImport === node) return as("namespace", ["declaration"]);
|
|
1961
|
+
break;
|
|
1603
1962
|
case "ExportSpecifier":
|
|
1604
1963
|
if (!bindingOfNode(analysis, node) && analysis.types.aliases.has(name)) return as("type");
|
|
1605
1964
|
break;
|
|
@@ -1615,6 +1974,7 @@ function identifier(analysis, node, parent, add) {
|
|
|
1615
1974
|
function valueKind(analysis, binding) {
|
|
1616
1975
|
if (!binding) return "variable";
|
|
1617
1976
|
if (binding.kind === "param" || binding.kind === "self") return "parameter";
|
|
1977
|
+
if (binding.declaredBy === "namespace") return "namespace";
|
|
1618
1978
|
return isFunction(analysis.types.bindingType.get(binding.id)) ? "function" : "variable";
|
|
1619
1979
|
}
|
|
1620
1980
|
function modifiersOf(binding, isDeclaration) {
|