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/cli.cjs
CHANGED
|
@@ -13,6 +13,19 @@ var import_luaut_parser2 = require("luaut-parser");
|
|
|
13
13
|
|
|
14
14
|
// src/features/members.ts
|
|
15
15
|
var import_luaut_parser = require("luaut-parser");
|
|
16
|
+
function literalKeys(key, aliases) {
|
|
17
|
+
if (!key) return [];
|
|
18
|
+
const resolved = key.kind === "genericRef" ? aliases.get(key.name) : key;
|
|
19
|
+
if (!resolved) return [];
|
|
20
|
+
const parts = resolved.kind === "union" ? resolved.types : [resolved];
|
|
21
|
+
const out = [];
|
|
22
|
+
for (const part of parts) {
|
|
23
|
+
const member = part.kind === "genericRef" ? aliases.get(part.name) ?? part : part;
|
|
24
|
+
if (member.kind !== "literal" || typeof member.value !== "string") return [];
|
|
25
|
+
out.push(member.value);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
16
29
|
function membersOf(type, aliases, seen = /* @__PURE__ */ new Set()) {
|
|
17
30
|
if (!type || seen.has(type)) return [];
|
|
18
31
|
seen.add(type);
|
|
@@ -22,6 +35,11 @@ function membersOf(type, aliases, seen = /* @__PURE__ */ new Set()) {
|
|
|
22
35
|
for (const [name, property] of type.properties) {
|
|
23
36
|
out.push({ name, property, isMethod: takesSelf(property.type) });
|
|
24
37
|
}
|
|
38
|
+
for (const name of literalKeys(type.indexer?.key, aliases)) {
|
|
39
|
+
if (type.properties.has(name)) continue;
|
|
40
|
+
const property = { type: type.indexer.value, optional: true };
|
|
41
|
+
out.push({ name, property, isMethod: takesSelf(property.type) });
|
|
42
|
+
}
|
|
25
43
|
return out;
|
|
26
44
|
}
|
|
27
45
|
case "intersection": {
|
|
@@ -341,11 +359,13 @@ var Analyzer = class {
|
|
|
341
359
|
const script = path ? context.sourceMap?.scriptFor(path) : void 0;
|
|
342
360
|
const libs = script ? [...context.libs, script] : context.libs;
|
|
343
361
|
const globals = script ? [...context.globals, "script"] : context.globals;
|
|
344
|
-
const { program, errors } = (0, import_luaut_parser2.parseWithRecovery)(source);
|
|
345
|
-
const
|
|
362
|
+
const { program, errors, directives } = (0, import_luaut_parser2.parseWithRecovery)(source);
|
|
363
|
+
const reportUndeclared = context.libs.length > 0;
|
|
364
|
+
const scopes = (0, import_luaut_parser2.analyzeScopes)(program, { builtinGlobals: [...globals], reportUndeclared });
|
|
346
365
|
const dependencies = new Map(context.reads);
|
|
347
366
|
const types = (0, import_luaut_parser2.analyzeTypes)(program, scopes, {
|
|
348
367
|
libs,
|
|
368
|
+
reportUnknownTypes: reportUndeclared,
|
|
349
369
|
resolveModule: (specifier) => {
|
|
350
370
|
if (!path) return void 0;
|
|
351
371
|
const candidates = this.candidatesFor(path, specifier);
|
|
@@ -359,7 +379,7 @@ var Analyzer = class {
|
|
|
359
379
|
return exports2;
|
|
360
380
|
}
|
|
361
381
|
});
|
|
362
|
-
return { uri, version, source, program, parseErrors: errors, scopes, types, dependencies, project: context.project };
|
|
382
|
+
return { uri, version, source, program, parseErrors: errors, directives, scopes, types, dependencies, project: context.project };
|
|
363
383
|
}
|
|
364
384
|
exportsOf(path, importing) {
|
|
365
385
|
const key = pathKey(path);
|
|
@@ -454,7 +474,7 @@ function collect(container, out) {
|
|
|
454
474
|
}
|
|
455
475
|
}
|
|
456
476
|
function isSpanlessNode(v) {
|
|
457
|
-
return !!v && typeof v === "object" &&
|
|
477
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
458
478
|
}
|
|
459
479
|
function pathAt(root, pos, inclusive = false) {
|
|
460
480
|
let best;
|
|
@@ -714,6 +734,7 @@ function patternNamed(target, name) {
|
|
|
714
734
|
|
|
715
735
|
// src/features/diagnostics.ts
|
|
716
736
|
var import_vscode_languageserver2 = require("vscode-languageserver");
|
|
737
|
+
var import_luaut_parser4 = require("luaut-parser");
|
|
717
738
|
function diagnostics(analysis) {
|
|
718
739
|
const out = [];
|
|
719
740
|
for (const error of analysis.parseErrors) {
|
|
@@ -727,37 +748,64 @@ function diagnostics(analysis) {
|
|
|
727
748
|
message: error.message.replace(/\s*\(\d+:\d+\)$/, "")
|
|
728
749
|
});
|
|
729
750
|
}
|
|
730
|
-
|
|
731
|
-
|
|
751
|
+
const semantic = [
|
|
752
|
+
...analysis.scopes.diagnostics.map((d) => ({
|
|
732
753
|
range: toRange(d.node),
|
|
733
754
|
severity: import_vscode_languageserver2.DiagnosticSeverity.Error,
|
|
734
755
|
source: "luaut",
|
|
735
756
|
code: d.kind,
|
|
736
757
|
message: d.message
|
|
737
|
-
})
|
|
738
|
-
|
|
739
|
-
for (const d of analysis.types.diagnostics) {
|
|
740
|
-
out.push({
|
|
758
|
+
})),
|
|
759
|
+
...analysis.types.diagnostics.map((d) => ({
|
|
741
760
|
range: toRange(d.node),
|
|
742
761
|
severity: import_vscode_languageserver2.DiagnosticSeverity.Error,
|
|
743
762
|
source: "luaut",
|
|
744
763
|
code: "type",
|
|
745
764
|
message: d.message
|
|
765
|
+
}))
|
|
766
|
+
];
|
|
767
|
+
const { kept, unusedExpectErrors } = (0, import_luaut_parser4.applyDirectives)(analysis.directives, semantic, (d) => d.range.start.line + 1);
|
|
768
|
+
out.push(...kept);
|
|
769
|
+
for (const directive of unusedExpectErrors) {
|
|
770
|
+
const start = toPosition(directive.line, directive.column);
|
|
771
|
+
out.push({
|
|
772
|
+
range: { start, end: { line: start.line, character: start.character + "--@luaut-expect-error".length } },
|
|
773
|
+
severity: import_vscode_languageserver2.DiagnosticSeverity.Error,
|
|
774
|
+
source: "luaut",
|
|
775
|
+
code: "directive",
|
|
776
|
+
message: import_luaut_parser4.UNUSED_EXPECT_ERROR
|
|
746
777
|
});
|
|
747
778
|
}
|
|
748
779
|
return out;
|
|
749
780
|
}
|
|
750
781
|
|
|
751
782
|
// src/features/hover.ts
|
|
752
|
-
var
|
|
783
|
+
var import_luaut_parser5 = require("luaut-parser");
|
|
753
784
|
function hover(analysis, position) {
|
|
754
785
|
const path = pathAt(analysis.program, position, true);
|
|
755
786
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
787
|
+
if (UNNAMED.has(path[i].type)) return null;
|
|
756
788
|
const text = describe(analysis, path, i);
|
|
757
789
|
if (text) return { contents: { kind: "markdown", value: code(text) }, range: toRange(path[i]) };
|
|
758
790
|
}
|
|
759
791
|
return null;
|
|
760
792
|
}
|
|
793
|
+
var UNNAMED = /* @__PURE__ */ new Set([
|
|
794
|
+
"BinaryExpression",
|
|
795
|
+
"UnaryExpression",
|
|
796
|
+
"CallExpression",
|
|
797
|
+
"MethodCallExpression",
|
|
798
|
+
"MemberExpression",
|
|
799
|
+
"IndexExpression",
|
|
800
|
+
"ParenthesizedExpression",
|
|
801
|
+
"IfElseExpression",
|
|
802
|
+
"TableExpression",
|
|
803
|
+
"ArrayExpression",
|
|
804
|
+
"TypeAssertionExpression",
|
|
805
|
+
"SatisfiesExpression",
|
|
806
|
+
"AsConstExpression",
|
|
807
|
+
"InterpolatedStringExpression"
|
|
808
|
+
]);
|
|
761
809
|
var PRIMITIVES = /* @__PURE__ */ new Set(["any", "unknown", "never", "nil", "boolean", "number", "string", "thread", "buffer"]);
|
|
762
810
|
function describe(analysis, path, index) {
|
|
763
811
|
const { types } = analysis;
|
|
@@ -779,6 +827,27 @@ function describe(analysis, path, index) {
|
|
|
779
827
|
const type2 = property?.type ?? types.typeOf.get(field.value);
|
|
780
828
|
return type2 && `(property) ${name}: ${pretty(type2)}`;
|
|
781
829
|
}
|
|
830
|
+
// `const { name } = t`: a shorthand key *is* the binding it
|
|
831
|
+
// declares, and has the same span, so the cursor can land on
|
|
832
|
+
// either. A renamed key (`{ name: other }`) names the property
|
|
833
|
+
// the value is read from.
|
|
834
|
+
case "ObjectPatternProperty": {
|
|
835
|
+
if (parent.key !== node || parent.computed) break;
|
|
836
|
+
const value = parent.value;
|
|
837
|
+
if (parent.shorthand) return describe(analysis, [...path.slice(0, index), value], index);
|
|
838
|
+
const binding2 = value.type === "IdentifierPattern" ? bindingOfNode(analysis, value) : void 0;
|
|
839
|
+
const type2 = binding2 && types.bindingType.get(binding2.id);
|
|
840
|
+
return type2 && `(property) ${name}: ${pretty(type2)}`;
|
|
841
|
+
}
|
|
842
|
+
// One line of an overload set reads as its own signature.
|
|
843
|
+
// The line the body is on reads as the whole set, which is
|
|
844
|
+
// what the binding says and what the default path gives.
|
|
845
|
+
case "FunctionSignature": {
|
|
846
|
+
if (parent.name !== node) break;
|
|
847
|
+
const own = types.typeOfTypeNode.get(parent);
|
|
848
|
+
if (own) return `function ${name}${pretty(own)}`;
|
|
849
|
+
break;
|
|
850
|
+
}
|
|
782
851
|
case "ImportSpecifier": {
|
|
783
852
|
const alias = types.aliases.get(name);
|
|
784
853
|
const binding2 = bindingOfNode(analysis, identifier2);
|
|
@@ -824,7 +893,7 @@ function describe(analysis, path, index) {
|
|
|
824
893
|
case "MappedTypeNode":
|
|
825
894
|
if (parent.parameterId === node) {
|
|
826
895
|
const keys = typeOfNode(parent.constraint);
|
|
827
|
-
return `(type parameter) ${name}${keys ? ` in ${(0,
|
|
896
|
+
return `(type parameter) ${name}${keys ? ` in ${(0, import_luaut_parser5.formatType)(keys)}` : ""}`;
|
|
828
897
|
}
|
|
829
898
|
break;
|
|
830
899
|
}
|
|
@@ -833,7 +902,7 @@ function describe(analysis, path, index) {
|
|
|
833
902
|
const binding = bindingOfNode(analysis, identifier2);
|
|
834
903
|
if (binding) {
|
|
835
904
|
const type2 = types.bindingType.get(binding.id);
|
|
836
|
-
if (type2) return
|
|
905
|
+
if (type2) return bindingText(binding, type2);
|
|
837
906
|
}
|
|
838
907
|
if (parent?.type === "MemberExpression" || parent?.type === "MethodCallExpression") {
|
|
839
908
|
const type2 = types.typeOf.get(parent);
|
|
@@ -841,13 +910,18 @@ function describe(analysis, path, index) {
|
|
|
841
910
|
}
|
|
842
911
|
return void 0;
|
|
843
912
|
}
|
|
844
|
-
//
|
|
913
|
+
// `...` — what this function's extra arguments are.
|
|
914
|
+
case "VarargExpression": {
|
|
915
|
+
const type2 = types.typeOf.get(node);
|
|
916
|
+
return type2 ? `(vararg) ...: ${pretty(type2)}` : void 0;
|
|
917
|
+
}
|
|
918
|
+
// Declarations: `const x`, a parameter.
|
|
845
919
|
case "IdentifierPattern":
|
|
846
920
|
case "FunctionParameter":
|
|
847
921
|
case "TypedIdentifier": {
|
|
848
922
|
const binding = bindingOfNode(analysis, node);
|
|
849
923
|
const type2 = binding && types.bindingType.get(binding.id);
|
|
850
|
-
return type2 ?
|
|
924
|
+
return type2 ? bindingText(binding, type2) : void 0;
|
|
851
925
|
}
|
|
852
926
|
// A type written by name: `number`, `Shape`, `Partial<User>`, or a type
|
|
853
927
|
// parameter in scope.
|
|
@@ -861,7 +935,7 @@ function describe(analysis, path, index) {
|
|
|
861
935
|
if (!node.typeArguments.length) {
|
|
862
936
|
const qualified = node.namespace ? `${node.namespace}.${base}` : base;
|
|
863
937
|
const alias = types.aliases.get(qualified);
|
|
864
|
-
if (alias && (0,
|
|
938
|
+
if (alias && (0, import_luaut_parser5.isClassType)(alias)) return classText(analysis, qualified);
|
|
865
939
|
if (alias) return `type ${qualified} = ${pretty(alias)}`;
|
|
866
940
|
}
|
|
867
941
|
const type2 = typeOfNode(node);
|
|
@@ -889,17 +963,17 @@ function declareText(analysis, statement) {
|
|
|
889
963
|
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);
|
|
890
964
|
const others = total - 1;
|
|
891
965
|
const overloads = others > 0 ? ` (+${others} overload${others > 1 ? "s" : ""})` : "";
|
|
892
|
-
return `declare function ${name}${(0,
|
|
966
|
+
return `declare function ${name}${(0, import_luaut_parser5.formatType)(own)}${overloads}`;
|
|
893
967
|
}
|
|
894
968
|
function classText(analysis, name) {
|
|
895
969
|
const type = analysis.types.aliases.get(name);
|
|
896
|
-
if (!type || !(0,
|
|
970
|
+
if (!type || !(0, import_luaut_parser5.isClassType)(type)) return void 0;
|
|
897
971
|
const superclass = type.class.superclass;
|
|
898
972
|
const inherited = superclass ? analysis.types.aliases.get(superclass) : void 0;
|
|
899
973
|
const own = [...type.properties].filter(([key, property]) => inherited?.kind !== "object" || inherited.properties.get(key) !== property);
|
|
900
974
|
const head = `declare class ${name}${superclass ? ` extends ${superclass}` : ""}`;
|
|
901
975
|
if (!own.length) return `${head} {}`;
|
|
902
|
-
const lines = own.map(([key, property]) => ` ${property.readonly ? "readonly " : ""}${key}${property.optional ? "?" : ""}: ${(0,
|
|
976
|
+
const lines = own.map(([key, property]) => ` ${property.readonly ? "readonly " : ""}${key}${property.optional ? "?" : ""}: ${(0, import_luaut_parser5.formatType)(property.type)},`);
|
|
903
977
|
return `${head} {
|
|
904
978
|
${lines.join("\n")}
|
|
905
979
|
}`;
|
|
@@ -911,7 +985,7 @@ function typeParameterSignature(analysis, parameter) {
|
|
|
911
985
|
const p = parameter;
|
|
912
986
|
if (p.infer) return `infer ${p.name}`;
|
|
913
987
|
const constraint = p.constraint ? analysis.types.typeOfTypeNode.get(p.constraint) : void 0;
|
|
914
|
-
return `${p.isConst ? "const " : ""}${p.name}${constraint ? ` extends ${(0,
|
|
988
|
+
return `${p.isConst ? "const " : ""}${p.name}${constraint ? ` extends ${(0, import_luaut_parser5.formatType)(constraint)}` : ""}`;
|
|
915
989
|
}
|
|
916
990
|
function typeParameterInScope(path, index, name) {
|
|
917
991
|
for (let i = index - 1; i >= 0; i--) {
|
|
@@ -936,7 +1010,7 @@ function referenceText(analysis, reference) {
|
|
|
936
1010
|
if (!args.length) return name;
|
|
937
1011
|
const resolved = args.map((a) => {
|
|
938
1012
|
const t = analysis.types.typeOfTypeNode.get(a);
|
|
939
|
-
return t ? (0,
|
|
1013
|
+
return t ? (0, import_luaut_parser5.formatType)(t) : "?";
|
|
940
1014
|
});
|
|
941
1015
|
return `${name}<${resolved.join(", ")}>`;
|
|
942
1016
|
}
|
|
@@ -945,28 +1019,37 @@ function fieldWithKey(table, key) {
|
|
|
945
1019
|
return fields.find((f) => f.type === "TableFieldNamed" && f.key === key);
|
|
946
1020
|
}
|
|
947
1021
|
function pretty(type) {
|
|
948
|
-
const flat = (0,
|
|
1022
|
+
const flat = (0, import_luaut_parser5.formatType)(type);
|
|
949
1023
|
if (flat.length <= 80) return flat;
|
|
950
1024
|
if (type.kind === "object") {
|
|
951
1025
|
const lines = [];
|
|
952
|
-
if (type.indexer) lines.push(` [${(0,
|
|
1026
|
+
if (type.indexer) lines.push(` [${(0, import_luaut_parser5.formatType)(type.indexer.key)}]: ${(0, import_luaut_parser5.formatType)(type.indexer.value)},`);
|
|
953
1027
|
for (const [name, property] of type.properties) {
|
|
954
1028
|
const readonly = property.readonly ? "readonly " : "";
|
|
955
|
-
lines.push(` ${readonly}${name}${property.optional ? "?" : ""}: ${(0,
|
|
1029
|
+
lines.push(` ${readonly}${name}${property.optional ? "?" : ""}: ${(0, import_luaut_parser5.formatType)(property.type)},`);
|
|
956
1030
|
}
|
|
957
1031
|
return `{
|
|
958
1032
|
${lines.join("\n")}
|
|
959
1033
|
}`;
|
|
960
1034
|
}
|
|
961
1035
|
if (type.kind === "intersection" && type.types.every((t) => t.kind === "function")) {
|
|
962
|
-
return type.types.map(
|
|
1036
|
+
return type.types.map(import_luaut_parser5.formatType).join("\n& ");
|
|
963
1037
|
}
|
|
964
1038
|
return flat;
|
|
965
1039
|
}
|
|
1040
|
+
function bindingText(binding, type) {
|
|
1041
|
+
if (binding.declaredBy === "function" && type.kind === "function") {
|
|
1042
|
+
return `function ${binding.name}${(0, import_luaut_parser5.formatType)(type)}`;
|
|
1043
|
+
}
|
|
1044
|
+
return `${keyword(binding)} ${binding.name}: ${pretty(type)}`;
|
|
1045
|
+
}
|
|
966
1046
|
function keyword(binding) {
|
|
967
1047
|
if (binding.kind === "param" || binding.kind === "self") return "(parameter)";
|
|
968
1048
|
if (binding.kind === "global") return "(global)";
|
|
969
1049
|
if (binding.kind.startsWith("for-")) return "(loop variable)";
|
|
1050
|
+
if (binding.declaredBy === "import" || binding.declaredBy === "namespace") return "(import)";
|
|
1051
|
+
if (binding.declaredBy === "type") return "(type import)";
|
|
1052
|
+
if (binding.declaredBy === "function") return "function";
|
|
970
1053
|
return binding.isConst ? "const" : "let";
|
|
971
1054
|
}
|
|
972
1055
|
function code(text) {
|
|
@@ -1033,8 +1116,162 @@ function isIdentifier(name) {
|
|
|
1033
1116
|
}
|
|
1034
1117
|
|
|
1035
1118
|
// src/features/completion.ts
|
|
1119
|
+
var import_vscode_languageserver5 = require("vscode-languageserver");
|
|
1120
|
+
var import_luaut_parser6 = require("luaut-parser");
|
|
1121
|
+
|
|
1122
|
+
// src/features/autoImport.ts
|
|
1123
|
+
var import_node_fs3 = require("fs");
|
|
1124
|
+
var import_node_path3 = require("path");
|
|
1036
1125
|
var import_vscode_languageserver4 = require("vscode-languageserver");
|
|
1037
|
-
|
|
1126
|
+
function importItems(analyzer, analysis, typePosition, taken) {
|
|
1127
|
+
const from = pathOfUri(analysis.uri);
|
|
1128
|
+
if (!from) return [];
|
|
1129
|
+
const config = analysis.project.config;
|
|
1130
|
+
const items = [];
|
|
1131
|
+
const offered = /* @__PURE__ */ new Set();
|
|
1132
|
+
for (const file of projectFiles(config?.directory ?? (0, import_node_path3.dirname)(from))) {
|
|
1133
|
+
if (samePath(file, from)) continue;
|
|
1134
|
+
const exports2 = analyzer.exportsAt(file);
|
|
1135
|
+
if (!exports2 || exports2.partial) continue;
|
|
1136
|
+
const names = typePosition ? [...exports2.types.keys()] : [...exports2.values.keys()];
|
|
1137
|
+
const specifier = specifierFor(from, file, config);
|
|
1138
|
+
for (const name of names) {
|
|
1139
|
+
if (taken.has(name) || offered.has(name)) continue;
|
|
1140
|
+
offered.add(name);
|
|
1141
|
+
const type = typePosition ? exports2.types.get(name)?.type : exports2.values.get(name);
|
|
1142
|
+
items.push({
|
|
1143
|
+
label: name,
|
|
1144
|
+
kind: typePosition ? import_vscode_languageserver4.CompletionItemKind.Interface : type && signaturesOf(type).length ? import_vscode_languageserver4.CompletionItemKind.Function : import_vscode_languageserver4.CompletionItemKind.Variable,
|
|
1145
|
+
labelDetails: { description: specifier },
|
|
1146
|
+
detail: `import { ${name} } from "${specifier}"`,
|
|
1147
|
+
sortText: `4${name}`,
|
|
1148
|
+
additionalTextEdits: [importEdit(analyzer, analysis, file, name, specifier, typePosition)]
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
return items;
|
|
1153
|
+
}
|
|
1154
|
+
function serviceItems(analysis, taken) {
|
|
1155
|
+
const services = analysis.types.aliases.get("Services");
|
|
1156
|
+
if (!services || services.kind !== "object") return [];
|
|
1157
|
+
const at = serviceInsertion(analysis.program.body.statements);
|
|
1158
|
+
const items = [];
|
|
1159
|
+
for (const name of services.properties.keys()) {
|
|
1160
|
+
if (taken.has(name)) continue;
|
|
1161
|
+
const line = `const ${name} = game:GetService("${name}")`;
|
|
1162
|
+
items.push({
|
|
1163
|
+
label: name,
|
|
1164
|
+
kind: import_vscode_languageserver4.CompletionItemKind.Module,
|
|
1165
|
+
labelDetails: { description: "service" },
|
|
1166
|
+
detail: line,
|
|
1167
|
+
sortText: `5${name}`,
|
|
1168
|
+
additionalTextEdits: [{ range: { start: at.position, end: at.position }, newText: `${line}
|
|
1169
|
+
${at.gap}` }]
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
return items;
|
|
1173
|
+
}
|
|
1174
|
+
function importEdit(analyzer, analysis, file, name, specifier, typePosition) {
|
|
1175
|
+
const statements = analysis.program.body.statements;
|
|
1176
|
+
const imports = statements.filter((s) => s.type === "ImportStatement");
|
|
1177
|
+
const existing = imports.find((s) => !s.namespaceImport && (typePosition || !s.isTypeOnly) && samePathOrUndefined(analyzer.resolveModulePath(analysis.uri, s.source.value), file));
|
|
1178
|
+
if (existing) {
|
|
1179
|
+
const last = existing.specifiers[existing.specifiers.length - 1];
|
|
1180
|
+
if (last) {
|
|
1181
|
+
const at2 = endOf(last);
|
|
1182
|
+
return { range: { start: at2, end: at2 }, newText: `, ${name}` };
|
|
1183
|
+
}
|
|
1184
|
+
if (existing.defaultImport) {
|
|
1185
|
+
const at2 = endOf(existing.defaultImport);
|
|
1186
|
+
return { range: { start: at2, end: at2 }, newText: `, { ${name} }` };
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
const line = `import { ${name} } from "${specifier}"`;
|
|
1190
|
+
const lastImport = imports[imports.length - 1];
|
|
1191
|
+
if (lastImport) {
|
|
1192
|
+
const at2 = { line: lastImport.line.end, character: 0 };
|
|
1193
|
+
return { range: { start: at2, end: at2 }, newText: `${line}
|
|
1194
|
+
` };
|
|
1195
|
+
}
|
|
1196
|
+
const first = statements[0];
|
|
1197
|
+
const at = { line: first ? first.line.start - 1 : 0, character: 0 };
|
|
1198
|
+
return { range: { start: at, end: at }, newText: first ? `${line}
|
|
1199
|
+
|
|
1200
|
+
` : `${line}
|
|
1201
|
+
` };
|
|
1202
|
+
}
|
|
1203
|
+
function serviceInsertion(statements) {
|
|
1204
|
+
let last;
|
|
1205
|
+
for (const statement of statements) {
|
|
1206
|
+
if (statement.type !== "ImportStatement" && !isServiceDeclaration(statement)) break;
|
|
1207
|
+
last = statement;
|
|
1208
|
+
}
|
|
1209
|
+
if (last) return { position: { line: last.line.end, character: 0 }, gap: "" };
|
|
1210
|
+
const first = statements[0];
|
|
1211
|
+
return first ? { position: { line: first.line.start - 1, character: 0 }, gap: "\n" } : { position: { line: 0, character: 0 }, gap: "" };
|
|
1212
|
+
}
|
|
1213
|
+
function isServiceDeclaration(statement) {
|
|
1214
|
+
if (statement.type !== "VariableDeclaration") return false;
|
|
1215
|
+
const init = statement.init[0];
|
|
1216
|
+
return init?.type === "MethodCallExpression" && init.method.name === "GetService" && init.object.type === "Identifier" && init.object.name === "game";
|
|
1217
|
+
}
|
|
1218
|
+
function endOf(node) {
|
|
1219
|
+
return { line: node.line.end - 1, character: node.column.end - 1 };
|
|
1220
|
+
}
|
|
1221
|
+
function samePathOrUndefined(a, b) {
|
|
1222
|
+
return a !== void 0 && samePath(a, b);
|
|
1223
|
+
}
|
|
1224
|
+
function specifierFor(from, target, config) {
|
|
1225
|
+
const withoutExtension = (path) => {
|
|
1226
|
+
const bare = path.replace(/\\/g, "/").replace(/\.luaut$/, "");
|
|
1227
|
+
return bare.endsWith("/index") ? bare.slice(0, -"/index".length) : bare;
|
|
1228
|
+
};
|
|
1229
|
+
let relativePath = withoutExtension((0, import_node_path3.relative)((0, import_node_path3.dirname)(from), target));
|
|
1230
|
+
if (!relativePath.startsWith(".")) relativePath = `./${relativePath}`;
|
|
1231
|
+
if (!relativePath.startsWith("../") || !config) return relativePath;
|
|
1232
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
1233
|
+
const star = pattern.indexOf("*");
|
|
1234
|
+
if (star < 0) continue;
|
|
1235
|
+
for (const targetPattern of targets) {
|
|
1236
|
+
const cut = targetPattern.indexOf("*");
|
|
1237
|
+
if (cut < 0) continue;
|
|
1238
|
+
const head = (0, import_node_path3.resolve)(config.baseUrl, targetPattern.slice(0, cut));
|
|
1239
|
+
const rest = (0, import_node_path3.relative)(head, target);
|
|
1240
|
+
if (rest.startsWith("..") || (0, import_node_path3.resolve)(head, rest) !== (0, import_node_path3.resolve)(target)) continue;
|
|
1241
|
+
return `${pattern.slice(0, star)}${withoutExtension(rest)}${pattern.slice(star + 1)}`;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
return relativePath;
|
|
1245
|
+
}
|
|
1246
|
+
var FILE_LIMIT = 2e3;
|
|
1247
|
+
var LISTING_TTL = 3e3;
|
|
1248
|
+
var listings = /* @__PURE__ */ new Map();
|
|
1249
|
+
function projectFiles(root) {
|
|
1250
|
+
const cached = listings.get(root);
|
|
1251
|
+
if (cached && Date.now() - cached.at < LISTING_TTL) return cached.files;
|
|
1252
|
+
const files = [];
|
|
1253
|
+
const walk2 = (directory, depth) => {
|
|
1254
|
+
if (files.length >= FILE_LIMIT || depth > 12) return;
|
|
1255
|
+
let entries;
|
|
1256
|
+
try {
|
|
1257
|
+
entries = (0, import_node_fs3.readdirSync)(directory, { withFileTypes: true });
|
|
1258
|
+
} catch {
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
for (const entry of entries) {
|
|
1262
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
1263
|
+
const path = (0, import_node_path3.join)(directory, entry.name);
|
|
1264
|
+
if (entry.isDirectory()) walk2(path, depth + 1);
|
|
1265
|
+
else if (entry.name.endsWith(".luaut") && !entry.name.endsWith(".d.luaut")) files.push(path);
|
|
1266
|
+
if (files.length >= FILE_LIMIT) return;
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
walk2(root, 0);
|
|
1270
|
+
listings.set(root, { at: Date.now(), files });
|
|
1271
|
+
return files;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// src/features/completion.ts
|
|
1038
1275
|
var PLACEHOLDER = "__luautCompletion__";
|
|
1039
1276
|
var IDENTIFIER_CHAR = /[A-Za-z0-9_]/;
|
|
1040
1277
|
function completion(analyzer, document, position) {
|
|
@@ -1067,28 +1304,58 @@ function completion(analyzer, document, position) {
|
|
|
1067
1304
|
first ??= { analysis, path };
|
|
1068
1305
|
}
|
|
1069
1306
|
if (operator || !first) return [];
|
|
1307
|
+
const keys = objectKeyItems(first.analysis, first.path, source.slice(end));
|
|
1308
|
+
if (keys) return keys;
|
|
1070
1309
|
if (inTypePosition(first.path)) {
|
|
1071
1310
|
const named = [...first.analysis.types.aliases].map(([name, type]) => ({
|
|
1072
1311
|
label: name,
|
|
1073
|
-
kind: (0,
|
|
1074
|
-
detail: (0,
|
|
1312
|
+
kind: (0, import_luaut_parser6.isClassType)(type) ? import_vscode_languageserver5.CompletionItemKind.Class : import_vscode_languageserver5.CompletionItemKind.Interface,
|
|
1313
|
+
detail: (0, import_luaut_parser6.isClassType)(type) ? "class" : "type"
|
|
1075
1314
|
}));
|
|
1076
1315
|
const primitives = PRIMITIVES2.map((name) => ({
|
|
1077
1316
|
label: name,
|
|
1078
|
-
kind:
|
|
1317
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Keyword,
|
|
1079
1318
|
detail: "type"
|
|
1080
1319
|
}));
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1320
|
+
const keywords = TYPE_KEYWORDS.map((name) => ({
|
|
1321
|
+
label: name,
|
|
1322
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Keyword,
|
|
1323
|
+
sortText: `3${name}`
|
|
1324
|
+
}));
|
|
1325
|
+
const typeNames = new Set(first.analysis.types.aliases.keys());
|
|
1326
|
+
const imported = importItems(analyzer, analyzer.get(document), true, typeNames);
|
|
1327
|
+
return [...named, ...primitives, ...keywords, ...imported];
|
|
1328
|
+
}
|
|
1329
|
+
const taken = /* @__PURE__ */ new Set();
|
|
1330
|
+
for (const binding of first.analysis.scopes.bindings.values()) taken.add(binding.name);
|
|
1331
|
+
const current = analyzer.get(document);
|
|
1332
|
+
return [
|
|
1333
|
+
...valueItems(first.analysis, at),
|
|
1334
|
+
...contextKeywords(source.slice(0, start)),
|
|
1335
|
+
...importItems(analyzer, current, false, taken),
|
|
1336
|
+
...serviceItems(current, taken)
|
|
1337
|
+
];
|
|
1084
1338
|
}
|
|
1085
1339
|
function stringCompletion(analyzer, document, position) {
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1340
|
+
let analysis = analyzer.get(document);
|
|
1341
|
+
let literal = stringAt(analysis, position);
|
|
1342
|
+
if (!literal) {
|
|
1343
|
+
const repaired = repairedStrings(document, position);
|
|
1344
|
+
for (const text of repaired) {
|
|
1345
|
+
const candidate = analyzer.analyze(document.uri, -1, text);
|
|
1346
|
+
literal = stringAt(candidate, position);
|
|
1347
|
+
if (literal) {
|
|
1348
|
+
analysis = candidate;
|
|
1349
|
+
break;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
if (!literal) return repaired.length ? [] : void 0;
|
|
1353
|
+
}
|
|
1090
1354
|
const expected = analysis.types.expectedTypeOf.get(literal);
|
|
1091
|
-
const values =
|
|
1355
|
+
const values = [.../* @__PURE__ */ new Set([
|
|
1356
|
+
...stringLiterals(expected, analysis.types.aliases),
|
|
1357
|
+
...indexKeys(analysis, position, literal)
|
|
1358
|
+
])];
|
|
1092
1359
|
if (!values.length) return [];
|
|
1093
1360
|
const line = literal.line.start - 1;
|
|
1094
1361
|
const range = literal.line.start === literal.line.end ? {
|
|
@@ -1097,10 +1364,81 @@ function stringCompletion(analyzer, document, position) {
|
|
|
1097
1364
|
} : void 0;
|
|
1098
1365
|
return values.map((value) => ({
|
|
1099
1366
|
label: value,
|
|
1100
|
-
kind:
|
|
1367
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Constant,
|
|
1101
1368
|
...range ? { textEdit: { range, newText: value } } : {}
|
|
1102
1369
|
}));
|
|
1103
1370
|
}
|
|
1371
|
+
function stringAt(analysis, position) {
|
|
1372
|
+
const path = pathAt(analysis.program, position, false);
|
|
1373
|
+
return [...path].reverse().find((n) => n.type === "StringLiteral" || n.type === "TypeLiteralString");
|
|
1374
|
+
}
|
|
1375
|
+
function objectKeyItems(analysis, path, after) {
|
|
1376
|
+
const index = path.findLastIndex(
|
|
1377
|
+
(n) => n.type === "Identifier" && n.name === PLACEHOLDER
|
|
1378
|
+
);
|
|
1379
|
+
const literal = index > 0 ? path[index - 1] : void 0;
|
|
1380
|
+
if (literal?.type !== "TableExpression") return void 0;
|
|
1381
|
+
const fields = literal.fields;
|
|
1382
|
+
const atKey = fields.some((f) => f.type === "TableFieldShorthand" && f.name?.name === PLACEHOLDER);
|
|
1383
|
+
if (!atKey) return void 0;
|
|
1384
|
+
let expected = analysis.types.expectedTypeOf.get(literal);
|
|
1385
|
+
for (let up = index - 2; expected === void 0 && up >= 0; up--) {
|
|
1386
|
+
const outer = path[up];
|
|
1387
|
+
if (outer.type !== "AsConstExpression" && outer.type !== "ParenthesizedExpression") break;
|
|
1388
|
+
expected = analysis.types.expectedTypeOf.get(outer);
|
|
1389
|
+
}
|
|
1390
|
+
const members = membersOf(expected, analysis.types.aliases);
|
|
1391
|
+
if (!members.length) return void 0;
|
|
1392
|
+
const written = /* @__PURE__ */ new Set();
|
|
1393
|
+
for (const field of fields) {
|
|
1394
|
+
if (field.type === "TableFieldNamed") written.add(field.key?.name ?? field.key?.value ?? "");
|
|
1395
|
+
else if (field.type === "TableFieldShorthand" && field.name?.name !== PLACEHOLDER) written.add(field.name.name);
|
|
1396
|
+
}
|
|
1397
|
+
const colon = /^\s*:/.test(after);
|
|
1398
|
+
return members.filter((member) => !written.has(member.name)).map((member) => ({
|
|
1399
|
+
label: member.name,
|
|
1400
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Field,
|
|
1401
|
+
detail: `${member.property.optional ? "?" : ""}: ${(0, import_luaut_parser6.formatType)(member.property.type)}`,
|
|
1402
|
+
insertText: colon ? member.name : `${member.name}: `
|
|
1403
|
+
}));
|
|
1404
|
+
}
|
|
1405
|
+
function indexKeys(analysis, position, literal) {
|
|
1406
|
+
const path = pathAt(analysis.program, position, false);
|
|
1407
|
+
const at = path.indexOf(literal);
|
|
1408
|
+
const parent = at > 0 ? path[at - 1] : void 0;
|
|
1409
|
+
if (!parent) return [];
|
|
1410
|
+
let indexed;
|
|
1411
|
+
if (parent.type === "IndexedAccessTypeNode" && parent.indexType === literal) {
|
|
1412
|
+
indexed = analysis.types.typeOfTypeNode.get(parent.objectType);
|
|
1413
|
+
} else if (parent.type === "IndexExpression" && parent.index === literal) {
|
|
1414
|
+
indexed = withoutNil(analysis.types.typeOf.get(parent.object));
|
|
1415
|
+
}
|
|
1416
|
+
return membersOf(indexed, analysis.types.aliases).map((member) => member.name);
|
|
1417
|
+
}
|
|
1418
|
+
function repairedStrings(document, position) {
|
|
1419
|
+
const source = document.getText();
|
|
1420
|
+
const offset = document.offsetAt(position);
|
|
1421
|
+
const lineStart = offset - position.character;
|
|
1422
|
+
const lineEndIndex = source.indexOf("\n", offset);
|
|
1423
|
+
const lineEnd = lineEndIndex < 0 ? source.length : lineEndIndex;
|
|
1424
|
+
const before = source.slice(lineStart, offset);
|
|
1425
|
+
let quote;
|
|
1426
|
+
for (let i = 0; i < before.length; i++) {
|
|
1427
|
+
const ch = before[i];
|
|
1428
|
+
if (quote) {
|
|
1429
|
+
if (ch === "\\") i++;
|
|
1430
|
+
else if (ch === quote) quote = void 0;
|
|
1431
|
+
} else if (ch === '"' || ch === "'") {
|
|
1432
|
+
quote = ch;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
if (!quote) return [];
|
|
1436
|
+
let rest = source.slice(offset, lineEnd).replace(/\r$/, "");
|
|
1437
|
+
if (!rest.includes(quote)) rest += quote;
|
|
1438
|
+
const line = before + rest;
|
|
1439
|
+
const endings = ["", " then end", " do end", ")", ") then end", "]"];
|
|
1440
|
+
return endings.map((ending) => source.slice(0, lineStart) + line + ending + source.slice(lineEnd));
|
|
1441
|
+
}
|
|
1104
1442
|
function stringLiterals(type, aliases, seen = /* @__PURE__ */ new Set()) {
|
|
1105
1443
|
if (!type || seen.has(type)) return [];
|
|
1106
1444
|
seen.add(type);
|
|
@@ -1132,7 +1470,7 @@ function memberOperator(source, wordStart) {
|
|
|
1132
1470
|
}
|
|
1133
1471
|
function memberItems(analysis, access) {
|
|
1134
1472
|
const object = access.object;
|
|
1135
|
-
const type = analysis.types.typeOf.get(object);
|
|
1473
|
+
const type = withoutNil(analysis.types.typeOf.get(object));
|
|
1136
1474
|
const colon = access.type === "MethodCallExpression";
|
|
1137
1475
|
if (isStringLike(type)) {
|
|
1138
1476
|
if (!colon) return [];
|
|
@@ -1142,6 +1480,11 @@ function memberItems(analysis, access) {
|
|
|
1142
1480
|
}
|
|
1143
1481
|
return membersOf(type, analysis.types.aliases).filter((member) => colon ? member.isMethod : true).map((member) => memberItem(member.name, member.property.type, member.property.readonly));
|
|
1144
1482
|
}
|
|
1483
|
+
function withoutNil(type) {
|
|
1484
|
+
if (type?.kind !== "union") return type;
|
|
1485
|
+
const kept = type.types.filter((t) => !(t.kind === "primitive" && t.name === "nil"));
|
|
1486
|
+
return kept.length === 1 ? kept[0] : { ...type, types: kept };
|
|
1487
|
+
}
|
|
1145
1488
|
function isStringLike(type) {
|
|
1146
1489
|
if (!type) return false;
|
|
1147
1490
|
switch (type.kind) {
|
|
@@ -1162,6 +1505,7 @@ function valueItems(analysis, at) {
|
|
|
1162
1505
|
const seen = /* @__PURE__ */ new Set();
|
|
1163
1506
|
for (const binding of analysis.scopes.bindings.values()) {
|
|
1164
1507
|
if (binding.name === PLACEHOLDER || seen.has(binding.name)) continue;
|
|
1508
|
+
if (binding.declaredBy === "type") continue;
|
|
1165
1509
|
const declaration = binding.declarationNode;
|
|
1166
1510
|
if (declaration && declaration.line.start - 1 > at.line) continue;
|
|
1167
1511
|
seen.add(binding.name);
|
|
@@ -1169,13 +1513,13 @@ function valueItems(analysis, at) {
|
|
|
1169
1513
|
items.push({
|
|
1170
1514
|
label: binding.name,
|
|
1171
1515
|
kind: kindOf(type, binding.kind),
|
|
1172
|
-
detail: type ? (0,
|
|
1516
|
+
detail: type ? (0, import_luaut_parser6.formatType)(type) : void 0,
|
|
1173
1517
|
// Locals before globals, and globals before library names.
|
|
1174
1518
|
sortText: `${binding.isBuiltin ? 2 : binding.kind === "global" ? 1 : 0}${binding.name}`
|
|
1175
1519
|
});
|
|
1176
1520
|
}
|
|
1177
1521
|
for (const keyword2 of KEYWORDS) {
|
|
1178
|
-
items.push({ label: keyword2, kind:
|
|
1522
|
+
items.push({ label: keyword2, kind: import_vscode_languageserver5.CompletionItemKind.Keyword, sortText: `3${keyword2}` });
|
|
1179
1523
|
}
|
|
1180
1524
|
return items;
|
|
1181
1525
|
}
|
|
@@ -1184,22 +1528,22 @@ function memberItem(name, type, readonly) {
|
|
|
1184
1528
|
if (signatures.length) {
|
|
1185
1529
|
return {
|
|
1186
1530
|
label: name,
|
|
1187
|
-
kind:
|
|
1531
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Method,
|
|
1188
1532
|
detail: signatureLabel(signatures[0]).label,
|
|
1189
1533
|
insertText: `${name}($0)`,
|
|
1190
|
-
insertTextFormat:
|
|
1534
|
+
insertTextFormat: import_vscode_languageserver5.InsertTextFormat.Snippet
|
|
1191
1535
|
};
|
|
1192
1536
|
}
|
|
1193
1537
|
return {
|
|
1194
1538
|
label: name,
|
|
1195
|
-
kind:
|
|
1196
|
-
detail: `${readonly ? "readonly " : ""}${(0,
|
|
1539
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Field,
|
|
1540
|
+
detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser6.formatType)(type)}`
|
|
1197
1541
|
};
|
|
1198
1542
|
}
|
|
1199
1543
|
function kindOf(type, bindingKind) {
|
|
1200
|
-
if (type && signaturesOf(type).length) return
|
|
1201
|
-
if (bindingKind === "param" || bindingKind === "self") return
|
|
1202
|
-
return
|
|
1544
|
+
if (type && signaturesOf(type).length) return import_vscode_languageserver5.CompletionItemKind.Function;
|
|
1545
|
+
if (bindingKind === "param" || bindingKind === "self") return import_vscode_languageserver5.CompletionItemKind.Variable;
|
|
1546
|
+
return import_vscode_languageserver5.CompletionItemKind.Variable;
|
|
1203
1547
|
}
|
|
1204
1548
|
function inTypePosition(path) {
|
|
1205
1549
|
return path.some(
|
|
@@ -1217,6 +1561,17 @@ var PRIMITIVES2 = [
|
|
|
1217
1561
|
"thread",
|
|
1218
1562
|
"buffer"
|
|
1219
1563
|
];
|
|
1564
|
+
var TYPE_KEYWORDS = ["keyof", "typeof", "infer", "extends"];
|
|
1565
|
+
function contextKeywords(before) {
|
|
1566
|
+
const keyword2 = (name) => ({
|
|
1567
|
+
label: name,
|
|
1568
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Keyword,
|
|
1569
|
+
sortText: `3${name}`
|
|
1570
|
+
});
|
|
1571
|
+
if (/<\s*[A-Za-z_]\w*(?:\s*,\s*[A-Za-z_]\w*)*\s+$/.test(before)) return [keyword2("extends")];
|
|
1572
|
+
if (/[)\]}"'`\w][^\S\n]+$/.test(before)) return [keyword2("as"), keyword2("satisfies")];
|
|
1573
|
+
return [];
|
|
1574
|
+
}
|
|
1220
1575
|
var KEYWORDS = [
|
|
1221
1576
|
"const",
|
|
1222
1577
|
"let",
|
|
@@ -1322,8 +1677,8 @@ function activeArgument(call, position) {
|
|
|
1322
1677
|
}
|
|
1323
1678
|
|
|
1324
1679
|
// src/features/symbols.ts
|
|
1325
|
-
var
|
|
1326
|
-
var
|
|
1680
|
+
var import_vscode_languageserver6 = require("vscode-languageserver");
|
|
1681
|
+
var import_luaut_parser7 = require("luaut-parser");
|
|
1327
1682
|
function documentSymbols(analysis) {
|
|
1328
1683
|
const out = [];
|
|
1329
1684
|
walk(analysis.program, (node) => {
|
|
@@ -1331,7 +1686,7 @@ function documentSymbols(analysis) {
|
|
|
1331
1686
|
case "FunctionDeclaration":
|
|
1332
1687
|
case "FunctionDeclarationStatement": {
|
|
1333
1688
|
const name = functionName(node);
|
|
1334
|
-
if (name) out.push(symbol(name,
|
|
1689
|
+
if (name) out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Function, node, detailOf(analysis, node)));
|
|
1335
1690
|
break;
|
|
1336
1691
|
}
|
|
1337
1692
|
case "TypeAliasStatement":
|
|
@@ -1340,20 +1695,20 @@ function documentSymbols(analysis) {
|
|
|
1340
1695
|
const name = typeof named === "string" ? named : named?.name;
|
|
1341
1696
|
if (name) {
|
|
1342
1697
|
const alias = analysis.types.aliases.get(name);
|
|
1343
|
-
out.push(symbol(name,
|
|
1698
|
+
out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Interface, node, alias ? (0, import_luaut_parser7.formatType)(alias) : void 0));
|
|
1344
1699
|
}
|
|
1345
1700
|
break;
|
|
1346
1701
|
}
|
|
1347
1702
|
case "DeclareClassStatement": {
|
|
1348
1703
|
const name = node.name.name;
|
|
1349
1704
|
const superclass = node.superclass?.base;
|
|
1350
|
-
out.push(symbol(name,
|
|
1705
|
+
out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Class, node, superclass && `extends ${superclass}`));
|
|
1351
1706
|
break;
|
|
1352
1707
|
}
|
|
1353
1708
|
case "VariableDeclaration": {
|
|
1354
1709
|
for (const target of node.names ?? []) {
|
|
1355
1710
|
const name = target.name;
|
|
1356
|
-
if (name) out.push(symbol(name,
|
|
1711
|
+
if (name) out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Variable, target));
|
|
1357
1712
|
}
|
|
1358
1713
|
break;
|
|
1359
1714
|
}
|
|
@@ -1377,7 +1732,7 @@ function detailOf(analysis, node) {
|
|
|
1377
1732
|
if (name && typeof name === "object") {
|
|
1378
1733
|
const binding = bindingOfNode(analysis, name);
|
|
1379
1734
|
const type = binding && analysis.types.bindingType.get(binding.id);
|
|
1380
|
-
if (type) return (0,
|
|
1735
|
+
if (type) return (0, import_luaut_parser7.formatType)(type);
|
|
1381
1736
|
}
|
|
1382
1737
|
return void 0;
|
|
1383
1738
|
}
|
|
@@ -1387,7 +1742,7 @@ function symbol(name, kind, node, detail) {
|
|
|
1387
1742
|
}
|
|
1388
1743
|
|
|
1389
1744
|
// src/features/semanticTokens.ts
|
|
1390
|
-
var
|
|
1745
|
+
var import_luaut_parser8 = require("luaut-parser");
|
|
1391
1746
|
var TOKEN_TYPES = [
|
|
1392
1747
|
"namespace",
|
|
1393
1748
|
"type",
|
|
@@ -1431,7 +1786,7 @@ function semanticTokens(analysis) {
|
|
|
1431
1786
|
};
|
|
1432
1787
|
let tokens = [];
|
|
1433
1788
|
try {
|
|
1434
|
-
tokens = (0,
|
|
1789
|
+
tokens = (0, import_luaut_parser8.tokenize)(analysis.source);
|
|
1435
1790
|
} catch {
|
|
1436
1791
|
}
|
|
1437
1792
|
const identifiers = tokens.filter((t) => t.type === "Identifier");
|
|
@@ -1475,7 +1830,7 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
|
|
|
1475
1830
|
if (!baseToken) return;
|
|
1476
1831
|
if (!namespace && typeParameterInScope2(ancestors, base)) {
|
|
1477
1832
|
add(baseToken, base.length, "typeParameter");
|
|
1478
|
-
} else if ((0,
|
|
1833
|
+
} else if ((0, import_luaut_parser8.isClassType)(analysis.types.aliases.get(namespace ? `${namespace}.${base}` : base) ?? import_luaut_parser8.unknownType)) {
|
|
1479
1834
|
add(baseToken, base.length, "class");
|
|
1480
1835
|
} else {
|
|
1481
1836
|
add(baseToken, base.length, "type", PRIMITIVES3.has(base) ? ["defaultLibrary"] : []);
|
|
@@ -1530,10 +1885,14 @@ function identifier(analysis, node, parent, add) {
|
|
|
1530
1885
|
break;
|
|
1531
1886
|
case "ImportSpecifier": {
|
|
1532
1887
|
const binding2 = bindingOfNode(analysis, node);
|
|
1888
|
+
if (binding2?.declaredBy === "type") return as("type", ["declaration"]);
|
|
1533
1889
|
const value = binding2 && analysis.types.bindingType.get(binding2.id);
|
|
1534
1890
|
if (analysis.types.aliases.has(name) && (!value || value.kind === "any")) return as("type", ["declaration"]);
|
|
1535
1891
|
break;
|
|
1536
1892
|
}
|
|
1893
|
+
case "ImportStatement":
|
|
1894
|
+
if (parent.namespaceImport === node) return as("namespace", ["declaration"]);
|
|
1895
|
+
break;
|
|
1537
1896
|
case "ExportSpecifier":
|
|
1538
1897
|
if (!bindingOfNode(analysis, node) && analysis.types.aliases.has(name)) return as("type");
|
|
1539
1898
|
break;
|
|
@@ -1549,6 +1908,7 @@ function identifier(analysis, node, parent, add) {
|
|
|
1549
1908
|
function valueKind(analysis, binding) {
|
|
1550
1909
|
if (!binding) return "variable";
|
|
1551
1910
|
if (binding.kind === "param" || binding.kind === "self") return "parameter";
|
|
1911
|
+
if (binding.declaredBy === "namespace") return "namespace";
|
|
1552
1912
|
return isFunction(analysis.types.bindingType.get(binding.id)) ? "function" : "variable";
|
|
1553
1913
|
}
|
|
1554
1914
|
function modifiersOf(binding, isDeclaration) {
|