luaut-language-server 3.0.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-X2GHHRWT.js → chunk-SCZTSX3Y.js} +358 -30
- package/dist/chunk-SCZTSX3Y.js.map +1 -0
- package/dist/cli.cjs +384 -56
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +384 -56
- 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-X2GHHRWT.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,29 +814,39 @@ 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--) {
|
|
@@ -862,6 +893,27 @@ function describe(analysis, path, index) {
|
|
|
862
893
|
const type2 = property?.type ?? types.typeOf.get(field.value);
|
|
863
894
|
return type2 && `(property) ${name}: ${pretty(type2)}`;
|
|
864
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
|
+
}
|
|
865
917
|
case "ImportSpecifier": {
|
|
866
918
|
const alias = types.aliases.get(name);
|
|
867
919
|
const binding2 = bindingOfNode(analysis, identifier2);
|
|
@@ -907,7 +959,7 @@ function describe(analysis, path, index) {
|
|
|
907
959
|
case "MappedTypeNode":
|
|
908
960
|
if (parent.parameterId === node) {
|
|
909
961
|
const keys = typeOfNode(parent.constraint);
|
|
910
|
-
return `(type parameter) ${name}${keys ? ` in ${(0,
|
|
962
|
+
return `(type parameter) ${name}${keys ? ` in ${(0, import_luaut_parser5.formatType)(keys)}` : ""}`;
|
|
911
963
|
}
|
|
912
964
|
break;
|
|
913
965
|
}
|
|
@@ -924,6 +976,11 @@ function describe(analysis, path, index) {
|
|
|
924
976
|
}
|
|
925
977
|
return void 0;
|
|
926
978
|
}
|
|
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
|
+
}
|
|
927
984
|
// Declarations: `const x`, a parameter.
|
|
928
985
|
case "IdentifierPattern":
|
|
929
986
|
case "FunctionParameter":
|
|
@@ -944,7 +1001,7 @@ function describe(analysis, path, index) {
|
|
|
944
1001
|
if (!node.typeArguments.length) {
|
|
945
1002
|
const qualified = node.namespace ? `${node.namespace}.${base}` : base;
|
|
946
1003
|
const alias = types.aliases.get(qualified);
|
|
947
|
-
if (alias && (0,
|
|
1004
|
+
if (alias && (0, import_luaut_parser5.isClassType)(alias)) return classText(analysis, qualified);
|
|
948
1005
|
if (alias) return `type ${qualified} = ${pretty(alias)}`;
|
|
949
1006
|
}
|
|
950
1007
|
const type2 = typeOfNode(node);
|
|
@@ -972,17 +1029,17 @@ function declareText(analysis, statement) {
|
|
|
972
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);
|
|
973
1030
|
const others = total - 1;
|
|
974
1031
|
const overloads = others > 0 ? ` (+${others} overload${others > 1 ? "s" : ""})` : "";
|
|
975
|
-
return `declare function ${name}${(0,
|
|
1032
|
+
return `declare function ${name}${(0, import_luaut_parser5.formatType)(own)}${overloads}`;
|
|
976
1033
|
}
|
|
977
1034
|
function classText(analysis, name) {
|
|
978
1035
|
const type = analysis.types.aliases.get(name);
|
|
979
|
-
if (!type || !(0,
|
|
1036
|
+
if (!type || !(0, import_luaut_parser5.isClassType)(type)) return void 0;
|
|
980
1037
|
const superclass = type.class.superclass;
|
|
981
1038
|
const inherited = superclass ? analysis.types.aliases.get(superclass) : void 0;
|
|
982
1039
|
const own = [...type.properties].filter(([key, property]) => inherited?.kind !== "object" || inherited.properties.get(key) !== property);
|
|
983
1040
|
const head = `declare class ${name}${superclass ? ` extends ${superclass}` : ""}`;
|
|
984
1041
|
if (!own.length) return `${head} {}`;
|
|
985
|
-
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)},`);
|
|
986
1043
|
return `${head} {
|
|
987
1044
|
${lines.join("\n")}
|
|
988
1045
|
}`;
|
|
@@ -994,7 +1051,7 @@ function typeParameterSignature(analysis, parameter) {
|
|
|
994
1051
|
const p = parameter;
|
|
995
1052
|
if (p.infer) return `infer ${p.name}`;
|
|
996
1053
|
const constraint = p.constraint ? analysis.types.typeOfTypeNode.get(p.constraint) : void 0;
|
|
997
|
-
return `${p.isConst ? "const " : ""}${p.name}${constraint ? ` extends ${(0,
|
|
1054
|
+
return `${p.isConst ? "const " : ""}${p.name}${constraint ? ` extends ${(0, import_luaut_parser5.formatType)(constraint)}` : ""}`;
|
|
998
1055
|
}
|
|
999
1056
|
function typeParameterInScope(path, index, name) {
|
|
1000
1057
|
for (let i = index - 1; i >= 0; i--) {
|
|
@@ -1019,7 +1076,7 @@ function referenceText(analysis, reference) {
|
|
|
1019
1076
|
if (!args.length) return name;
|
|
1020
1077
|
const resolved = args.map((a) => {
|
|
1021
1078
|
const t = analysis.types.typeOfTypeNode.get(a);
|
|
1022
|
-
return t ? (0,
|
|
1079
|
+
return t ? (0, import_luaut_parser5.formatType)(t) : "?";
|
|
1023
1080
|
});
|
|
1024
1081
|
return `${name}<${resolved.join(", ")}>`;
|
|
1025
1082
|
}
|
|
@@ -1028,27 +1085,27 @@ function fieldWithKey(table, key) {
|
|
|
1028
1085
|
return fields.find((f) => f.type === "TableFieldNamed" && f.key === key);
|
|
1029
1086
|
}
|
|
1030
1087
|
function pretty(type) {
|
|
1031
|
-
const flat = (0,
|
|
1088
|
+
const flat = (0, import_luaut_parser5.formatType)(type);
|
|
1032
1089
|
if (flat.length <= 80) return flat;
|
|
1033
1090
|
if (type.kind === "object") {
|
|
1034
1091
|
const lines = [];
|
|
1035
|
-
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)},`);
|
|
1036
1093
|
for (const [name, property] of type.properties) {
|
|
1037
1094
|
const readonly = property.readonly ? "readonly " : "";
|
|
1038
|
-
lines.push(` ${readonly}${name}${property.optional ? "?" : ""}: ${(0,
|
|
1095
|
+
lines.push(` ${readonly}${name}${property.optional ? "?" : ""}: ${(0, import_luaut_parser5.formatType)(property.type)},`);
|
|
1039
1096
|
}
|
|
1040
1097
|
return `{
|
|
1041
1098
|
${lines.join("\n")}
|
|
1042
1099
|
}`;
|
|
1043
1100
|
}
|
|
1044
1101
|
if (type.kind === "intersection" && type.types.every((t) => t.kind === "function")) {
|
|
1045
|
-
return type.types.map(
|
|
1102
|
+
return type.types.map(import_luaut_parser5.formatType).join("\n& ");
|
|
1046
1103
|
}
|
|
1047
1104
|
return flat;
|
|
1048
1105
|
}
|
|
1049
1106
|
function bindingText(binding, type) {
|
|
1050
1107
|
if (binding.declaredBy === "function" && type.kind === "function") {
|
|
1051
|
-
return `function ${binding.name}${(0,
|
|
1108
|
+
return `function ${binding.name}${(0, import_luaut_parser5.formatType)(type)}`;
|
|
1052
1109
|
}
|
|
1053
1110
|
return `${keyword(binding)} ${binding.name}: ${pretty(type)}`;
|
|
1054
1111
|
}
|
|
@@ -1125,8 +1182,162 @@ function isIdentifier(name) {
|
|
|
1125
1182
|
}
|
|
1126
1183
|
|
|
1127
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");
|
|
1128
1191
|
var import_vscode_languageserver4 = require("vscode-languageserver");
|
|
1129
|
-
|
|
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
|
|
1130
1341
|
var PLACEHOLDER = "__luautCompletion__";
|
|
1131
1342
|
var IDENTIFIER_CHAR = /[A-Za-z0-9_]/;
|
|
1132
1343
|
function completion(analyzer, document, position) {
|
|
@@ -1159,28 +1370,58 @@ function completion(analyzer, document, position) {
|
|
|
1159
1370
|
first ??= { analysis, path };
|
|
1160
1371
|
}
|
|
1161
1372
|
if (operator || !first) return [];
|
|
1373
|
+
const keys = objectKeyItems(first.analysis, first.path, source.slice(end));
|
|
1374
|
+
if (keys) return keys;
|
|
1162
1375
|
if (inTypePosition(first.path)) {
|
|
1163
1376
|
const named = [...first.analysis.types.aliases].map(([name, type]) => ({
|
|
1164
1377
|
label: name,
|
|
1165
|
-
kind: (0,
|
|
1166
|
-
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"
|
|
1167
1380
|
}));
|
|
1168
1381
|
const primitives = PRIMITIVES2.map((name) => ({
|
|
1169
1382
|
label: name,
|
|
1170
|
-
kind:
|
|
1383
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Keyword,
|
|
1171
1384
|
detail: "type"
|
|
1172
1385
|
}));
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
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
|
+
];
|
|
1176
1404
|
}
|
|
1177
1405
|
function stringCompletion(analyzer, document, position) {
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
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
|
+
}
|
|
1182
1420
|
const expected = analysis.types.expectedTypeOf.get(literal);
|
|
1183
|
-
const values =
|
|
1421
|
+
const values = [.../* @__PURE__ */ new Set([
|
|
1422
|
+
...stringLiterals(expected, analysis.types.aliases),
|
|
1423
|
+
...indexKeys(analysis, position, literal)
|
|
1424
|
+
])];
|
|
1184
1425
|
if (!values.length) return [];
|
|
1185
1426
|
const line = literal.line.start - 1;
|
|
1186
1427
|
const range = literal.line.start === literal.line.end ? {
|
|
@@ -1189,10 +1430,81 @@ function stringCompletion(analyzer, document, position) {
|
|
|
1189
1430
|
} : void 0;
|
|
1190
1431
|
return values.map((value) => ({
|
|
1191
1432
|
label: value,
|
|
1192
|
-
kind:
|
|
1433
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Constant,
|
|
1193
1434
|
...range ? { textEdit: { range, newText: value } } : {}
|
|
1194
1435
|
}));
|
|
1195
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
|
+
}
|
|
1196
1508
|
function stringLiterals(type, aliases, seen = /* @__PURE__ */ new Set()) {
|
|
1197
1509
|
if (!type || seen.has(type)) return [];
|
|
1198
1510
|
seen.add(type);
|
|
@@ -1224,7 +1536,7 @@ function memberOperator(source, wordStart) {
|
|
|
1224
1536
|
}
|
|
1225
1537
|
function memberItems(analysis, access) {
|
|
1226
1538
|
const object = access.object;
|
|
1227
|
-
const type = analysis.types.typeOf.get(object);
|
|
1539
|
+
const type = withoutNil(analysis.types.typeOf.get(object));
|
|
1228
1540
|
const colon = access.type === "MethodCallExpression";
|
|
1229
1541
|
if (isStringLike(type)) {
|
|
1230
1542
|
if (!colon) return [];
|
|
@@ -1234,6 +1546,11 @@ function memberItems(analysis, access) {
|
|
|
1234
1546
|
}
|
|
1235
1547
|
return membersOf(type, analysis.types.aliases).filter((member) => colon ? member.isMethod : true).map((member) => memberItem(member.name, member.property.type, member.property.readonly));
|
|
1236
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
|
+
}
|
|
1237
1554
|
function isStringLike(type) {
|
|
1238
1555
|
if (!type) return false;
|
|
1239
1556
|
switch (type.kind) {
|
|
@@ -1262,13 +1579,13 @@ function valueItems(analysis, at) {
|
|
|
1262
1579
|
items.push({
|
|
1263
1580
|
label: binding.name,
|
|
1264
1581
|
kind: kindOf(type, binding.kind),
|
|
1265
|
-
detail: type ? (0,
|
|
1582
|
+
detail: type ? (0, import_luaut_parser6.formatType)(type) : void 0,
|
|
1266
1583
|
// Locals before globals, and globals before library names.
|
|
1267
1584
|
sortText: `${binding.isBuiltin ? 2 : binding.kind === "global" ? 1 : 0}${binding.name}`
|
|
1268
1585
|
});
|
|
1269
1586
|
}
|
|
1270
1587
|
for (const keyword2 of KEYWORDS) {
|
|
1271
|
-
items.push({ label: keyword2, kind:
|
|
1588
|
+
items.push({ label: keyword2, kind: import_vscode_languageserver5.CompletionItemKind.Keyword, sortText: `3${keyword2}` });
|
|
1272
1589
|
}
|
|
1273
1590
|
return items;
|
|
1274
1591
|
}
|
|
@@ -1277,22 +1594,22 @@ function memberItem(name, type, readonly) {
|
|
|
1277
1594
|
if (signatures.length) {
|
|
1278
1595
|
return {
|
|
1279
1596
|
label: name,
|
|
1280
|
-
kind:
|
|
1597
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Method,
|
|
1281
1598
|
detail: signatureLabel(signatures[0]).label,
|
|
1282
1599
|
insertText: `${name}($0)`,
|
|
1283
|
-
insertTextFormat:
|
|
1600
|
+
insertTextFormat: import_vscode_languageserver5.InsertTextFormat.Snippet
|
|
1284
1601
|
};
|
|
1285
1602
|
}
|
|
1286
1603
|
return {
|
|
1287
1604
|
label: name,
|
|
1288
|
-
kind:
|
|
1289
|
-
detail: `${readonly ? "readonly " : ""}${(0,
|
|
1605
|
+
kind: import_vscode_languageserver5.CompletionItemKind.Field,
|
|
1606
|
+
detail: `${readonly ? "readonly " : ""}${(0, import_luaut_parser6.formatType)(type)}`
|
|
1290
1607
|
};
|
|
1291
1608
|
}
|
|
1292
1609
|
function kindOf(type, bindingKind) {
|
|
1293
|
-
if (type && signaturesOf(type).length) return
|
|
1294
|
-
if (bindingKind === "param" || bindingKind === "self") return
|
|
1295
|
-
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;
|
|
1296
1613
|
}
|
|
1297
1614
|
function inTypePosition(path) {
|
|
1298
1615
|
return path.some(
|
|
@@ -1310,6 +1627,17 @@ var PRIMITIVES2 = [
|
|
|
1310
1627
|
"thread",
|
|
1311
1628
|
"buffer"
|
|
1312
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
|
+
}
|
|
1313
1641
|
var KEYWORDS = [
|
|
1314
1642
|
"const",
|
|
1315
1643
|
"let",
|
|
@@ -1415,8 +1743,8 @@ function activeArgument(call, position) {
|
|
|
1415
1743
|
}
|
|
1416
1744
|
|
|
1417
1745
|
// src/features/symbols.ts
|
|
1418
|
-
var
|
|
1419
|
-
var
|
|
1746
|
+
var import_vscode_languageserver6 = require("vscode-languageserver");
|
|
1747
|
+
var import_luaut_parser7 = require("luaut-parser");
|
|
1420
1748
|
function documentSymbols(analysis) {
|
|
1421
1749
|
const out = [];
|
|
1422
1750
|
walk(analysis.program, (node) => {
|
|
@@ -1424,7 +1752,7 @@ function documentSymbols(analysis) {
|
|
|
1424
1752
|
case "FunctionDeclaration":
|
|
1425
1753
|
case "FunctionDeclarationStatement": {
|
|
1426
1754
|
const name = functionName(node);
|
|
1427
|
-
if (name) out.push(symbol(name,
|
|
1755
|
+
if (name) out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Function, node, detailOf(analysis, node)));
|
|
1428
1756
|
break;
|
|
1429
1757
|
}
|
|
1430
1758
|
case "TypeAliasStatement":
|
|
@@ -1433,20 +1761,20 @@ function documentSymbols(analysis) {
|
|
|
1433
1761
|
const name = typeof named === "string" ? named : named?.name;
|
|
1434
1762
|
if (name) {
|
|
1435
1763
|
const alias = analysis.types.aliases.get(name);
|
|
1436
|
-
out.push(symbol(name,
|
|
1764
|
+
out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Interface, node, alias ? (0, import_luaut_parser7.formatType)(alias) : void 0));
|
|
1437
1765
|
}
|
|
1438
1766
|
break;
|
|
1439
1767
|
}
|
|
1440
1768
|
case "DeclareClassStatement": {
|
|
1441
1769
|
const name = node.name.name;
|
|
1442
1770
|
const superclass = node.superclass?.base;
|
|
1443
|
-
out.push(symbol(name,
|
|
1771
|
+
out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Class, node, superclass && `extends ${superclass}`));
|
|
1444
1772
|
break;
|
|
1445
1773
|
}
|
|
1446
1774
|
case "VariableDeclaration": {
|
|
1447
1775
|
for (const target of node.names ?? []) {
|
|
1448
1776
|
const name = target.name;
|
|
1449
|
-
if (name) out.push(symbol(name,
|
|
1777
|
+
if (name) out.push(symbol(name, import_vscode_languageserver6.SymbolKind.Variable, target));
|
|
1450
1778
|
}
|
|
1451
1779
|
break;
|
|
1452
1780
|
}
|
|
@@ -1470,7 +1798,7 @@ function detailOf(analysis, node) {
|
|
|
1470
1798
|
if (name && typeof name === "object") {
|
|
1471
1799
|
const binding = bindingOfNode(analysis, name);
|
|
1472
1800
|
const type = binding && analysis.types.bindingType.get(binding.id);
|
|
1473
|
-
if (type) return (0,
|
|
1801
|
+
if (type) return (0, import_luaut_parser7.formatType)(type);
|
|
1474
1802
|
}
|
|
1475
1803
|
return void 0;
|
|
1476
1804
|
}
|
|
@@ -1480,7 +1808,7 @@ function symbol(name, kind, node, detail) {
|
|
|
1480
1808
|
}
|
|
1481
1809
|
|
|
1482
1810
|
// src/features/semanticTokens.ts
|
|
1483
|
-
var
|
|
1811
|
+
var import_luaut_parser8 = require("luaut-parser");
|
|
1484
1812
|
var TOKEN_TYPES = [
|
|
1485
1813
|
"namespace",
|
|
1486
1814
|
"type",
|
|
@@ -1524,7 +1852,7 @@ function semanticTokens(analysis) {
|
|
|
1524
1852
|
};
|
|
1525
1853
|
let tokens = [];
|
|
1526
1854
|
try {
|
|
1527
|
-
tokens = (0,
|
|
1855
|
+
tokens = (0, import_luaut_parser8.tokenize)(analysis.source);
|
|
1528
1856
|
} catch {
|
|
1529
1857
|
}
|
|
1530
1858
|
const identifiers = tokens.filter((t) => t.type === "Identifier");
|
|
@@ -1568,7 +1896,7 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
|
|
|
1568
1896
|
if (!baseToken) return;
|
|
1569
1897
|
if (!namespace && typeParameterInScope2(ancestors, base)) {
|
|
1570
1898
|
add(baseToken, base.length, "typeParameter");
|
|
1571
|
-
} else if ((0,
|
|
1899
|
+
} else if ((0, import_luaut_parser8.isClassType)(analysis.types.aliases.get(namespace ? `${namespace}.${base}` : base) ?? import_luaut_parser8.unknownType)) {
|
|
1572
1900
|
add(baseToken, base.length, "class");
|
|
1573
1901
|
} else {
|
|
1574
1902
|
add(baseToken, base.length, "type", PRIMITIVES3.has(base) ? ["defaultLibrary"] : []);
|