luaut-parser 1.2.0 → 2.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 +103 -28
- package/dist/index.cjs +769 -59
- package/dist/index.d.cts +127 -25
- package/dist/index.d.ts +127 -25
- package/dist/index.js +760 -49
- package/package.json +2 -2
- package/dist/luau.d.luaut +0 -245
- package/dist/roblox.d.luaut +0 -503
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
BinaryOperators: () => BinaryOperators,
|
|
24
|
+
CONFIG_FILE_NAMES: () => CONFIG_FILE_NAMES,
|
|
24
25
|
Keywords: () => Keywords,
|
|
25
26
|
LexError: () => LexError,
|
|
26
27
|
Operators: () => Operators,
|
|
@@ -35,25 +36,25 @@ __export(index_exports, {
|
|
|
35
36
|
bufferType: () => bufferType,
|
|
36
37
|
containsTypeParam: () => containsTypeParam,
|
|
37
38
|
default: () => index_default,
|
|
38
|
-
defaultLibs: () => defaultLibs,
|
|
39
39
|
difference: () => difference,
|
|
40
40
|
equalTypes: () => equalTypes,
|
|
41
41
|
falsyType: () => falsyType,
|
|
42
|
+
findConfig: () => findConfig,
|
|
42
43
|
fn: () => fn,
|
|
43
44
|
formatType: () => formatType,
|
|
44
45
|
getBinding: () => getBinding,
|
|
45
46
|
intersection: () => intersection,
|
|
46
47
|
isAssignable: () => isAssignable,
|
|
48
|
+
isClassType: () => isClassType,
|
|
47
49
|
isGlobal: () => isGlobal,
|
|
48
50
|
isPossiblyFalsy: () => isPossiblyFalsy,
|
|
49
51
|
isPossiblyTruthy: () => isPossiblyTruthy,
|
|
50
52
|
isUnassignedGlobal: () => isUnassignedGlobal,
|
|
51
53
|
literal: () => literal,
|
|
52
|
-
|
|
53
|
-
luauDefsPath: () => luauDefsPath,
|
|
54
|
-
luauLib: () => luauLib,
|
|
54
|
+
loadConfig: () => loadConfig,
|
|
55
55
|
luautparser: () => luautparser,
|
|
56
56
|
matchInfer: () => matchInfer,
|
|
57
|
+
moduleCandidates: () => moduleCandidates,
|
|
57
58
|
moduleExports: () => moduleExports,
|
|
58
59
|
narrowExclude: () => narrowExclude,
|
|
59
60
|
narrowFalsy: () => narrowFalsy,
|
|
@@ -61,6 +62,7 @@ __export(index_exports, {
|
|
|
61
62
|
narrowTruthy: () => narrowTruthy,
|
|
62
63
|
neverType: () => neverType,
|
|
63
64
|
nilType: () => nilType,
|
|
65
|
+
nodeHost: () => nodeHost,
|
|
64
66
|
numberType: () => numberType,
|
|
65
67
|
objectType: () => objectType,
|
|
66
68
|
optional: () => optional,
|
|
@@ -70,11 +72,12 @@ __export(index_exports, {
|
|
|
70
72
|
parseTokens: () => parseTokens,
|
|
71
73
|
parseWithRecovery: () => parseWithRecovery,
|
|
72
74
|
primitive: () => primitive,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
robloxLib: () => robloxLib,
|
|
75
|
+
resolveModulePath: () => resolveModulePath,
|
|
76
|
+
resolveTypeLibraries: () => resolveTypeLibraries,
|
|
76
77
|
setAliasExpander: () => setAliasExpander,
|
|
78
|
+
sourceMapTypes: () => sourceMapTypes,
|
|
77
79
|
stringType: () => stringType,
|
|
80
|
+
stripJsonComments: () => stripJsonComments,
|
|
78
81
|
substitute: () => substitute,
|
|
79
82
|
templateMatches: () => templateMatches,
|
|
80
83
|
threadType: () => threadType,
|
|
@@ -88,10 +91,6 @@ __export(index_exports, {
|
|
|
88
91
|
});
|
|
89
92
|
module.exports = __toCommonJS(index_exports);
|
|
90
93
|
|
|
91
|
-
// node_modules/tsup/assets/cjs_shims.js
|
|
92
|
-
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
93
|
-
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
94
|
-
|
|
95
94
|
// src/lexer/lexer.ts
|
|
96
95
|
var Keywords = [
|
|
97
96
|
"and",
|
|
@@ -946,6 +945,9 @@ var Parser = class {
|
|
|
946
945
|
}
|
|
947
946
|
if (t.type === "Identifier" && t.value === "declare") {
|
|
948
947
|
const p1 = this.peek(1);
|
|
948
|
+
if (p1.type === "Identifier" && p1.value === "class" && this.peek(2).type === "Identifier") {
|
|
949
|
+
return this.parseDeclareClassStatement();
|
|
950
|
+
}
|
|
949
951
|
if (p1.type === "Identifier" || p1.type === "Keyword" && p1.value === "function") {
|
|
950
952
|
return this.parseDeclareStatement();
|
|
951
953
|
}
|
|
@@ -987,6 +989,36 @@ var Parser = class {
|
|
|
987
989
|
const valueType = this.parseType();
|
|
988
990
|
return { type: "DeclareStatement", name: nameTok.value, id: tokenIdentifier(nameTok), valueType, ...spanFrom(start, this.previous()) };
|
|
989
991
|
}
|
|
992
|
+
/** A declared type's name. It may be qualified once — `Enum.Material` —
|
|
993
|
+
* which is how a definitions file names types under a namespace, and how
|
|
994
|
+
* they are then written (`const m: Enum.Material`). */
|
|
995
|
+
parseTypeName() {
|
|
996
|
+
const first = this.expectIdentifier();
|
|
997
|
+
if (this.checkPunctuator(".") && this.peek(1).type === "Identifier") {
|
|
998
|
+
this.advance();
|
|
999
|
+
const second = this.expectIdentifier();
|
|
1000
|
+
return { type: "Identifier", name: `${first.value}.${second.value}`, ...spanFrom(first, second) };
|
|
1001
|
+
}
|
|
1002
|
+
return tokenIdentifier(first);
|
|
1003
|
+
}
|
|
1004
|
+
// `declare class Name extends Base { member: T, ... }`
|
|
1005
|
+
parseDeclareClassStatement() {
|
|
1006
|
+
const start = this.current();
|
|
1007
|
+
this.advance();
|
|
1008
|
+
this.advance();
|
|
1009
|
+
const name = this.parseTypeName();
|
|
1010
|
+
let superclass;
|
|
1011
|
+
if (this.checkIdentifierValue("extends")) {
|
|
1012
|
+
this.advance();
|
|
1013
|
+
const base = this.parseType();
|
|
1014
|
+
if (base.type !== "TypeReference") this.error("A class can only extend another class, written by name");
|
|
1015
|
+
superclass = base;
|
|
1016
|
+
}
|
|
1017
|
+
if (!this.checkPunctuator("{")) this.error("Expected '{' to start the class body");
|
|
1018
|
+
const body = this.parseTableType();
|
|
1019
|
+
if (body.type !== "TableTypeNode") this.error("A class body lists members ('name: T'), not a mapped type");
|
|
1020
|
+
return { type: "DeclareClassStatement", name, superclass, body, ...spanFrom(start, this.previous()) };
|
|
1021
|
+
}
|
|
990
1022
|
// `import { a, b as c } from '...'` / `import Default from '...'` /
|
|
991
1023
|
// `import Default, { a } from '...'`. Compiled away entirely by the
|
|
992
1024
|
// bundler — never survives into emitted Luau.
|
|
@@ -1317,8 +1349,7 @@ var Parser = class {
|
|
|
1317
1349
|
parseTypeAliasStatement() {
|
|
1318
1350
|
const start = this.current();
|
|
1319
1351
|
this.advance();
|
|
1320
|
-
const
|
|
1321
|
-
const name = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
|
|
1352
|
+
const name = this.parseTypeName();
|
|
1322
1353
|
let generics = [];
|
|
1323
1354
|
if (this.checkOperator("<")) {
|
|
1324
1355
|
generics = this.parseGenericTypeParameterList();
|
|
@@ -2820,6 +2851,9 @@ var Analyzer = class {
|
|
|
2820
2851
|
case "DeclareStatement":
|
|
2821
2852
|
this.visitType(stmt.valueType, scope);
|
|
2822
2853
|
return;
|
|
2854
|
+
case "DeclareClassStatement":
|
|
2855
|
+
this.visitType(stmt.body, scope);
|
|
2856
|
+
return;
|
|
2823
2857
|
case "TypeAliasStatement":
|
|
2824
2858
|
case "ExportTypeAliasStatement":
|
|
2825
2859
|
this.visitType(stmt.definition, scope);
|
|
@@ -2993,6 +3027,9 @@ function analyzeScopes(program, options = {}) {
|
|
|
2993
3027
|
}
|
|
2994
3028
|
|
|
2995
3029
|
// src/ast/typeModel.ts
|
|
3030
|
+
function isClassType(t) {
|
|
3031
|
+
return t.kind === "object" && t.class !== void 0;
|
|
3032
|
+
}
|
|
2996
3033
|
function typeParam(name, constraint, isConst) {
|
|
2997
3034
|
return { kind: "typeParam", name, constraint, isConst };
|
|
2998
3035
|
}
|
|
@@ -3052,10 +3089,16 @@ function substitute(t, subst) {
|
|
|
3052
3089
|
}
|
|
3053
3090
|
case "function": {
|
|
3054
3091
|
const inner = t.typeParams ? new Map([...subst].filter(([k]) => !t.typeParams.includes(k))) : subst;
|
|
3092
|
+
let params = t.params.map((p) => ({ ...p, type: substitute(p.type, inner) }));
|
|
3093
|
+
let varargs = t.varargs && substitute(t.varargs, inner);
|
|
3094
|
+
if (varargs?.kind === "tuple" && varargs.isPack) {
|
|
3095
|
+
params = [...params, ...varargs.elements.map((type) => ({ type }))];
|
|
3096
|
+
varargs = void 0;
|
|
3097
|
+
}
|
|
3055
3098
|
return {
|
|
3056
3099
|
kind: "function",
|
|
3057
|
-
params
|
|
3058
|
-
varargs
|
|
3100
|
+
params,
|
|
3101
|
+
varargs,
|
|
3059
3102
|
returns: substitute(t.returns, inner),
|
|
3060
3103
|
typeParams: t.typeParams,
|
|
3061
3104
|
predicate: t.predicate && {
|
|
@@ -3135,7 +3178,8 @@ function unify(param, arg, vars, out) {
|
|
|
3135
3178
|
}
|
|
3136
3179
|
return;
|
|
3137
3180
|
case "object":
|
|
3138
|
-
if (
|
|
3181
|
+
if (param.class) return;
|
|
3182
|
+
if (arg.kind === "object" && !arg.class) {
|
|
3139
3183
|
for (const [k, pv] of param.properties) {
|
|
3140
3184
|
const av = arg.properties.get(k);
|
|
3141
3185
|
if (av) unify(pv.type, av.type, vars, out);
|
|
@@ -3218,7 +3262,7 @@ function widen(t) {
|
|
|
3218
3262
|
case "tuple":
|
|
3219
3263
|
return tuple(t.elements.map(widen), t.isPack);
|
|
3220
3264
|
case "object": {
|
|
3221
|
-
if (t.frozen) return t;
|
|
3265
|
+
if (t.frozen || t.class) return t;
|
|
3222
3266
|
const entries = [];
|
|
3223
3267
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: widen(v.type) }]);
|
|
3224
3268
|
const w = objectType(entries, t.indexer && { key: t.indexer.key, value: widen(t.indexer.value) });
|
|
@@ -3245,6 +3289,10 @@ function isAssignable(rawA, rawB) {
|
|
|
3245
3289
|
if (expandAlias) {
|
|
3246
3290
|
if (a.kind === "genericRef" && b.kind !== "genericRef") a = expandAlias(a);
|
|
3247
3291
|
else if (b.kind === "genericRef" && a.kind !== "genericRef") b = expandAlias(b);
|
|
3292
|
+
else if (a.kind === "genericRef" && b.kind === "genericRef" && a.name !== b.name) {
|
|
3293
|
+
a = expandAlias(a);
|
|
3294
|
+
b = expandAlias(b);
|
|
3295
|
+
}
|
|
3248
3296
|
if (a === b) return true;
|
|
3249
3297
|
}
|
|
3250
3298
|
for (let i = 0; i < comparing.length; i += 2) {
|
|
@@ -3270,7 +3318,11 @@ function isAssignableInner(a, b) {
|
|
|
3270
3318
|
if (a.kind === "union") return a.types.every((t) => isAssignable(t, b));
|
|
3271
3319
|
if (b.kind === "union") return b.types.some((t) => isAssignable(a, t));
|
|
3272
3320
|
if (b.kind === "intersection") return b.types.every((t) => isAssignable(a, t));
|
|
3273
|
-
if (a.kind === "intersection")
|
|
3321
|
+
if (a.kind === "intersection") {
|
|
3322
|
+
if (a.types.some((t) => isAssignable(t, b))) return true;
|
|
3323
|
+
const merged = mergeObjectMembers(a.types);
|
|
3324
|
+
return merged !== void 0 && isAssignable(merged, b);
|
|
3325
|
+
}
|
|
3274
3326
|
if (a.kind === "literal") {
|
|
3275
3327
|
if (b.kind === "literal") return a.value === b.value;
|
|
3276
3328
|
if (b.kind === "primitive") return b.name === a.base;
|
|
@@ -3296,6 +3348,8 @@ function isAssignableInner(a, b) {
|
|
|
3296
3348
|
}
|
|
3297
3349
|
if (a.kind === "object") {
|
|
3298
3350
|
if (b.kind !== "object") return false;
|
|
3351
|
+
if (b.class) return a.class !== void 0 && a.class.ancestors.includes(b.class.name);
|
|
3352
|
+
if (a.class && (b.indexer || b.properties.size === 0)) return false;
|
|
3299
3353
|
for (const [name, bp] of b.properties) {
|
|
3300
3354
|
const ap = a.properties.get(name);
|
|
3301
3355
|
if (!ap) {
|
|
@@ -3438,6 +3492,7 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
3438
3492
|
case "intersection":
|
|
3439
3493
|
return t.types.some((m) => containsTypeParam(m, seen, bound));
|
|
3440
3494
|
case "object":
|
|
3495
|
+
if (t.class) return false;
|
|
3441
3496
|
return [...t.properties.values()].some((v) => containsTypeParam(v.type, seen, bound)) || !!t.indexer && (containsTypeParam(t.indexer.key, seen, bound) || containsTypeParam(t.indexer.value, seen, bound));
|
|
3442
3497
|
case "function": {
|
|
3443
3498
|
const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
|
|
@@ -3625,6 +3680,39 @@ var IDENT_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
3625
3680
|
function formatKey(k) {
|
|
3626
3681
|
return IDENT_KEY.test(k) ? k : JSON.stringify(k);
|
|
3627
3682
|
}
|
|
3683
|
+
function mergeObjectMembers(types) {
|
|
3684
|
+
const objects = [];
|
|
3685
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3686
|
+
const collect = (t) => {
|
|
3687
|
+
if (seen.has(t)) return true;
|
|
3688
|
+
seen.add(t);
|
|
3689
|
+
if (t.kind === "genericRef") {
|
|
3690
|
+
const expanded = expandAlias?.(t);
|
|
3691
|
+
return expanded !== void 0 && expanded !== t && collect(expanded);
|
|
3692
|
+
}
|
|
3693
|
+
if (t.kind === "intersection") return t.types.every(collect);
|
|
3694
|
+
if (t.kind === "object" && !t.class) {
|
|
3695
|
+
objects.push(t);
|
|
3696
|
+
return true;
|
|
3697
|
+
}
|
|
3698
|
+
return false;
|
|
3699
|
+
};
|
|
3700
|
+
if (!types.every(collect) || objects.length < 2) return void 0;
|
|
3701
|
+
const properties = /* @__PURE__ */ new Map();
|
|
3702
|
+
let indexer;
|
|
3703
|
+
for (const object of objects) {
|
|
3704
|
+
indexer ??= object.indexer;
|
|
3705
|
+
for (const [name, property] of object.properties) {
|
|
3706
|
+
const existing = properties.get(name);
|
|
3707
|
+
properties.set(name, existing ? {
|
|
3708
|
+
type: intersection([existing.type, property.type]),
|
|
3709
|
+
optional: existing.optional && property.optional,
|
|
3710
|
+
readonly: existing.readonly || property.readonly
|
|
3711
|
+
} : property);
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
return objectType([...properties], indexer);
|
|
3715
|
+
}
|
|
3628
3716
|
|
|
3629
3717
|
// src/ast/analyzeTypes.ts
|
|
3630
3718
|
function analyzeTypes(program, scopes, options = {}) {
|
|
@@ -3815,6 +3903,62 @@ function keepsLiterals(paramType) {
|
|
|
3815
3903
|
const members = paramType.constraint.kind === "union" ? paramType.constraint.types : [paramType.constraint];
|
|
3816
3904
|
return members.some((m) => m.kind === "literal");
|
|
3817
3905
|
}
|
|
3906
|
+
var AliasMap = class extends Map {
|
|
3907
|
+
pending = /* @__PURE__ */ new Map();
|
|
3908
|
+
defer(name, resolve5) {
|
|
3909
|
+
super.delete(name);
|
|
3910
|
+
this.pending.set(name, resolve5);
|
|
3911
|
+
}
|
|
3912
|
+
get(name) {
|
|
3913
|
+
const resolved = super.get(name);
|
|
3914
|
+
if (resolved !== void 0) return resolved;
|
|
3915
|
+
const resolve5 = this.pending.get(name);
|
|
3916
|
+
if (!resolve5) return void 0;
|
|
3917
|
+
this.pending.delete(name);
|
|
3918
|
+
const type = resolve5();
|
|
3919
|
+
super.set(name, type);
|
|
3920
|
+
return type;
|
|
3921
|
+
}
|
|
3922
|
+
has(name) {
|
|
3923
|
+
return super.has(name) || (this.pending?.has(name) ?? false);
|
|
3924
|
+
}
|
|
3925
|
+
set(name, type) {
|
|
3926
|
+
this.pending?.delete(name);
|
|
3927
|
+
return super.set(name, type);
|
|
3928
|
+
}
|
|
3929
|
+
delete(name) {
|
|
3930
|
+
const deferred = this.pending?.delete(name) ?? false;
|
|
3931
|
+
return super.delete(name) || deferred;
|
|
3932
|
+
}
|
|
3933
|
+
get size() {
|
|
3934
|
+
return super.size + (this.pending?.size ?? 0);
|
|
3935
|
+
}
|
|
3936
|
+
keys() {
|
|
3937
|
+
return [...super.keys(), ...this.pending?.keys() ?? []][Symbol.iterator]();
|
|
3938
|
+
}
|
|
3939
|
+
entries() {
|
|
3940
|
+
return [...this.keys()].map((name) => [name, this.get(name)])[Symbol.iterator]();
|
|
3941
|
+
}
|
|
3942
|
+
values() {
|
|
3943
|
+
return [...this.keys()].map((name) => this.get(name))[Symbol.iterator]();
|
|
3944
|
+
}
|
|
3945
|
+
forEach(callback, thisArg) {
|
|
3946
|
+
for (const [name, type] of this.entries()) callback.call(thisArg, type, name, this);
|
|
3947
|
+
}
|
|
3948
|
+
[Symbol.iterator]() {
|
|
3949
|
+
return this.entries();
|
|
3950
|
+
}
|
|
3951
|
+
};
|
|
3952
|
+
var METAMETHODS = {
|
|
3953
|
+
"+": "__add",
|
|
3954
|
+
"-": "__sub",
|
|
3955
|
+
"*": "__mul",
|
|
3956
|
+
"/": "__div",
|
|
3957
|
+
"//": "__idiv",
|
|
3958
|
+
"%": "__mod",
|
|
3959
|
+
"^": "__pow",
|
|
3960
|
+
"..": "__concat"
|
|
3961
|
+
};
|
|
3818
3962
|
function posKey(name, line, column) {
|
|
3819
3963
|
return `${name}@${line}:${column}`;
|
|
3820
3964
|
}
|
|
@@ -3835,9 +3979,12 @@ var TypeAnalyzer = class {
|
|
|
3835
3979
|
expectedTypeOf = /* @__PURE__ */ new Map();
|
|
3836
3980
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
3837
3981
|
* `typeParam` nodes in the body). */
|
|
3838
|
-
aliases =
|
|
3982
|
+
aliases = new AliasMap();
|
|
3839
3983
|
/** Uninstantiated alias definitions, for `Name<Args>` instantiation. */
|
|
3840
3984
|
aliasDefs = /* @__PURE__ */ new Map();
|
|
3985
|
+
/** See `resolveClass`. */
|
|
3986
|
+
classTypes = /* @__PURE__ */ new WeakMap();
|
|
3987
|
+
classMembers = /* @__PURE__ */ new WeakMap();
|
|
3841
3988
|
/** Generic parameters currently in lexical scope (alias body / generic fn),
|
|
3842
3989
|
* with their `extends` constraints resolved. */
|
|
3843
3990
|
typeParamScope = [];
|
|
@@ -3947,24 +4094,123 @@ var TypeAnalyzer = class {
|
|
|
3947
4094
|
for (const stmt of block.statements) {
|
|
3948
4095
|
const alias = stmt.type === "TypeAliasStatement" ? stmt : stmt.type === "ExportTypeAliasStatement" ? stmt.alias : void 0;
|
|
3949
4096
|
if (alias) this.aliasDefs.set(alias.name.name, { params: alias.generics, node: alias.definition });
|
|
4097
|
+
if (stmt.type === "DeclareClassStatement") {
|
|
4098
|
+
this.aliasDefs.set(stmt.name.name, { params: [], node: stmt.body, class: stmt });
|
|
4099
|
+
}
|
|
3950
4100
|
}
|
|
3951
4101
|
}
|
|
3952
|
-
/**
|
|
3953
|
-
|
|
3954
|
-
|
|
4102
|
+
/** A non-generic definition's type. */
|
|
4103
|
+
resolveDef(def) {
|
|
4104
|
+
return def.class ? this.classType(def.class) : this.resolveType(def.node);
|
|
4105
|
+
}
|
|
4106
|
+
/** One type per class declaration, so every mention of a class is the same
|
|
4107
|
+
* object — its own members included, which refer back to it. */
|
|
4108
|
+
classType(stmt) {
|
|
4109
|
+
return this.classTypes.get(stmt) ?? this.resolveClass(stmt);
|
|
4110
|
+
}
|
|
4111
|
+
/** A class's members are resolved the first time anyone asks for
|
|
4112
|
+
* `properties` — its own from its body, the inherited ones from its
|
|
4113
|
+
* superclass.
|
|
4114
|
+
*
|
|
4115
|
+
* Both have to wait. A definitions file for a whole engine declares
|
|
4116
|
+
* thousands of classes that all refer to one another; resolving each body
|
|
4117
|
+
* as soon as the class is named would resolve every class on every
|
|
4118
|
+
* analysis, when a script touches a handful. And classes refer to one
|
|
4119
|
+
* another constantly — `Object.IsA` mentions a map of every class, each
|
|
4120
|
+
* of which extends `Object` — so while one class resolves, one it extends
|
|
4121
|
+
* may itself be half-resolved; copying its members then would miss some
|
|
4122
|
+
* for good. */
|
|
4123
|
+
resolveClass(stmt) {
|
|
4124
|
+
const name = stmt.name.name;
|
|
4125
|
+
const { ancestors, cyclic } = this.classChain(stmt);
|
|
4126
|
+
const superclass = !cyclic && ancestors.length > 1 ? this.aliasDefs.get(ancestors[1])?.class : void 0;
|
|
4127
|
+
let own;
|
|
4128
|
+
let resolvingOwn = false;
|
|
4129
|
+
const ownMembers = () => {
|
|
4130
|
+
if (own || resolvingOwn) return own;
|
|
4131
|
+
resolvingOwn = true;
|
|
4132
|
+
try {
|
|
4133
|
+
own = this.resolveType(stmt.body);
|
|
4134
|
+
} finally {
|
|
4135
|
+
resolvingOwn = false;
|
|
4136
|
+
}
|
|
4137
|
+
return own;
|
|
4138
|
+
};
|
|
4139
|
+
let complete;
|
|
4140
|
+
const members = () => {
|
|
4141
|
+
if (complete) return complete;
|
|
4142
|
+
const mine = ownMembers();
|
|
4143
|
+
if (!mine) return void 0;
|
|
4144
|
+
const base = superclass ? this.classMembers.get(this.classType(superclass))?.() : void 0;
|
|
4145
|
+
if (superclass && !base) return void 0;
|
|
4146
|
+
return complete = {
|
|
4147
|
+
properties: new Map([...base?.properties ?? [], ...mine.properties]),
|
|
4148
|
+
indexer: mine.indexer ?? base?.indexer
|
|
4149
|
+
};
|
|
4150
|
+
};
|
|
4151
|
+
const type = { kind: "object", name, class: { name, superclass: superclass?.name.name, ancestors } };
|
|
4152
|
+
Object.defineProperties(type, {
|
|
4153
|
+
properties: { enumerable: true, get: () => members()?.properties ?? own?.properties ?? /* @__PURE__ */ new Map() },
|
|
4154
|
+
indexer: { enumerable: true, get: () => members()?.indexer ?? own?.indexer }
|
|
4155
|
+
});
|
|
4156
|
+
this.classTypes.set(stmt, type);
|
|
4157
|
+
this.classMembers.set(type, members);
|
|
4158
|
+
if (this.program.body.statements.includes(stmt)) ownMembers();
|
|
4159
|
+
return type;
|
|
4160
|
+
}
|
|
4161
|
+
/** `extends` must name a class, and the chain must end. */
|
|
4162
|
+
checkClass(stmt) {
|
|
4163
|
+
if (!stmt.superclass || !this.emitDiagnostics) return;
|
|
4164
|
+
const base = stmt.superclass.base;
|
|
4165
|
+
if (!this.aliasDefs.get(base)?.class) {
|
|
4166
|
+
const known = this.aliasDefs.has(base) || this.importedTypes.has(base);
|
|
4167
|
+
this.diagnostics.push({
|
|
4168
|
+
node: stmt.superclass,
|
|
4169
|
+
message: known ? `'${base}' is not a class; a class can only extend another class` : `Cannot find class '${base}'`
|
|
4170
|
+
});
|
|
4171
|
+
} else if (this.classChain(stmt).cyclic) {
|
|
4172
|
+
this.diagnostics.push({ node: stmt.superclass, message: `'${stmt.name.name}' cannot extend itself` });
|
|
4173
|
+
}
|
|
4174
|
+
}
|
|
4175
|
+
/** The class and the classes it extends, nearest first, read from the
|
|
4176
|
+
* declarations — no type has to be resolved to know them. The walk stops
|
|
4177
|
+
* at a superclass that is not a class. */
|
|
4178
|
+
classChain(stmt) {
|
|
4179
|
+
const ancestors = [stmt.name.name];
|
|
4180
|
+
for (let cls = stmt; cls?.superclass; ) {
|
|
4181
|
+
const base = cls.superclass.base;
|
|
4182
|
+
if (ancestors.includes(base)) return { ancestors, cyclic: true };
|
|
4183
|
+
cls = this.aliasDefs.get(base)?.class;
|
|
4184
|
+
if (!cls) break;
|
|
4185
|
+
ancestors.push(base);
|
|
4186
|
+
}
|
|
4187
|
+
return { ancestors, cyclic: false };
|
|
4188
|
+
}
|
|
4189
|
+
/** Seed global types from `declare` statements. Repeating a function name
|
|
4190
|
+
* builds an *overload set* (an intersection, in declaration order) rather
|
|
4191
|
+
* than replacing — which is how `typeof` gets one signature per result
|
|
4192
|
+
* string. Any other value is simply redeclared: a sourcemap's
|
|
4193
|
+
* `declare script: <this file's instance>` replaces the library's
|
|
4194
|
+
* `declare script: LuaSourceContainer`. */
|
|
3955
4195
|
harvestDeclares(block) {
|
|
3956
4196
|
for (const stmt of block.statements) {
|
|
3957
4197
|
if (stmt.type !== "DeclareStatement") continue;
|
|
3958
4198
|
const t = this.resolveType(stmt.valueType);
|
|
3959
4199
|
const prev = this.libGlobalTypes.get(stmt.name);
|
|
3960
|
-
|
|
4200
|
+
const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
|
|
4201
|
+
this.libGlobalTypes.set(stmt.name, overload ? intersection([prev, t]) : t);
|
|
3961
4202
|
}
|
|
3962
4203
|
}
|
|
3963
4204
|
resolveAllAliases() {
|
|
3964
4205
|
for (const [name, def] of this.aliasDefs) {
|
|
4206
|
+
if (def.class && !this.program.body.statements.includes(def.class)) {
|
|
4207
|
+
const cls = def.class;
|
|
4208
|
+
this.aliases.defer(name, () => this.classType(cls));
|
|
4209
|
+
continue;
|
|
4210
|
+
}
|
|
3965
4211
|
if (containsTypeQuery(def.node)) continue;
|
|
3966
4212
|
this.withTypeParams(def.params, () => {
|
|
3967
|
-
this.aliases.set(name, this.
|
|
4213
|
+
this.aliases.set(name, this.resolveDef(def));
|
|
3968
4214
|
});
|
|
3969
4215
|
}
|
|
3970
4216
|
}
|
|
@@ -3974,7 +4220,7 @@ var TypeAnalyzer = class {
|
|
|
3974
4220
|
for (const [name, def] of this.aliasDefs) {
|
|
3975
4221
|
if (this.aliases.has(name)) continue;
|
|
3976
4222
|
this.withTypeParams(def.params, () => {
|
|
3977
|
-
this.aliases.set(name, this.
|
|
4223
|
+
this.aliases.set(name, this.resolveDef(def));
|
|
3978
4224
|
});
|
|
3979
4225
|
}
|
|
3980
4226
|
return this.aliases;
|
|
@@ -4002,10 +4248,7 @@ var TypeAnalyzer = class {
|
|
|
4002
4248
|
/** Instantiate a generic alias: `Box<number>` -> `{ value: number }`. */
|
|
4003
4249
|
instantiateAlias(def, args) {
|
|
4004
4250
|
if (this.instantiationDepth > 20) return unknownType;
|
|
4005
|
-
const subst =
|
|
4006
|
-
def.params.forEach((p, i) => {
|
|
4007
|
-
subst.set(p.name, args[i] ?? (p.default ? this.resolveType(p.default) : unknownType));
|
|
4008
|
-
});
|
|
4251
|
+
const subst = this.bindTypeArguments(def.params, args);
|
|
4009
4252
|
this.instantiationDepth++;
|
|
4010
4253
|
try {
|
|
4011
4254
|
const body = this.withTypeParams(def.params, () => this.resolveType(def.node));
|
|
@@ -4014,6 +4257,24 @@ var TypeAnalyzer = class {
|
|
|
4014
4257
|
this.instantiationDepth--;
|
|
4015
4258
|
}
|
|
4016
4259
|
}
|
|
4260
|
+
/** Pair written type arguments with the parameters they instantiate. A
|
|
4261
|
+
* pack parameter (`T...`) takes every argument from its position on, as
|
|
4262
|
+
* one pack: `Signal<Instance, string>` binds `T` to `(Instance, string)`,
|
|
4263
|
+
* and `Signal<()>` to the empty pack. Left out, a parameter takes its
|
|
4264
|
+
* default (`T... = ...any` is `any`), or `unknown`. */
|
|
4265
|
+
bindTypeArguments(params, args) {
|
|
4266
|
+
const subst = /* @__PURE__ */ new Map();
|
|
4267
|
+
params.forEach((p, i) => {
|
|
4268
|
+
let arg = args[i];
|
|
4269
|
+
if (p.isPack && i < args.length) {
|
|
4270
|
+
const rest = args.slice(i);
|
|
4271
|
+
const single = rest.length === 1 ? rest[0] : void 0;
|
|
4272
|
+
arg = single && (single.kind === "tuple" && single.isPack || single.kind === "typeParam" || single.kind === "any") ? single : tuple([...rest], true);
|
|
4273
|
+
}
|
|
4274
|
+
subst.set(p.name, arg ?? (p.default ? this.resolveType(p.default) : unknownType));
|
|
4275
|
+
});
|
|
4276
|
+
return subst;
|
|
4277
|
+
}
|
|
4017
4278
|
// --------------------------------------------------------
|
|
4018
4279
|
// TypeNode -> Type
|
|
4019
4280
|
// --------------------------------------------------------
|
|
@@ -4075,6 +4336,12 @@ var TypeAnalyzer = class {
|
|
|
4075
4336
|
}
|
|
4076
4337
|
const lib = this.options.libTypes?.[node.base];
|
|
4077
4338
|
if (lib) return lib;
|
|
4339
|
+
} else if (this.aliasDefs.has(name)) {
|
|
4340
|
+
return this.expand({
|
|
4341
|
+
kind: "genericRef",
|
|
4342
|
+
name,
|
|
4343
|
+
typeArguments: node.typeArguments.map((a) => this.resolveType(a))
|
|
4344
|
+
});
|
|
4078
4345
|
}
|
|
4079
4346
|
return {
|
|
4080
4347
|
kind: "genericRef",
|
|
@@ -4201,6 +4468,7 @@ var TypeAnalyzer = class {
|
|
|
4201
4468
|
return this.resolveType(node.typeAnnotation);
|
|
4202
4469
|
case "TypePackNode": {
|
|
4203
4470
|
if (node.types.length === 1 && !node.hasVarargs) return this.resolveType(node.types[0]);
|
|
4471
|
+
if (!node.types.length && node.varargType) return this.resolveType(node.varargType);
|
|
4204
4472
|
return tuple(node.types.map((t) => this.resolveType(t)), true);
|
|
4205
4473
|
}
|
|
4206
4474
|
}
|
|
@@ -4224,7 +4492,7 @@ var TypeAnalyzer = class {
|
|
|
4224
4492
|
this.reduceDepth++;
|
|
4225
4493
|
try {
|
|
4226
4494
|
const result = this.reduceTypeInner(t);
|
|
4227
|
-
this.reduceCache.set(t, result);
|
|
4495
|
+
if (result.kind !== "keyof") this.reduceCache.set(t, result);
|
|
4228
4496
|
return result;
|
|
4229
4497
|
} finally {
|
|
4230
4498
|
this.reduceDepth--;
|
|
@@ -4236,6 +4504,7 @@ var TypeAnalyzer = class {
|
|
|
4236
4504
|
case "keyof": {
|
|
4237
4505
|
const target = this.reduceType(t.target);
|
|
4238
4506
|
if (containsTypeParam(target)) return { kind: "keyof", target };
|
|
4507
|
+
if (target.kind === "genericRef" && this.resolvingAliases.has(target.name)) return t;
|
|
4239
4508
|
return this.keysOf(target);
|
|
4240
4509
|
}
|
|
4241
4510
|
case "indexedAccess": {
|
|
@@ -4277,6 +4546,7 @@ var TypeAnalyzer = class {
|
|
|
4277
4546
|
t.predicate
|
|
4278
4547
|
);
|
|
4279
4548
|
case "object": {
|
|
4549
|
+
if (t.class) return t;
|
|
4280
4550
|
const entries = [];
|
|
4281
4551
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.reduceType(v.type) }]);
|
|
4282
4552
|
const reduced = objectType(entries, t.indexer && {
|
|
@@ -4416,6 +4686,7 @@ var TypeAnalyzer = class {
|
|
|
4416
4686
|
t.typeParams
|
|
4417
4687
|
);
|
|
4418
4688
|
case "object": {
|
|
4689
|
+
if (t.class) return t;
|
|
4419
4690
|
const entries = [];
|
|
4420
4691
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.stripInfer(v.type, bindings) }]);
|
|
4421
4692
|
return objectType(entries, t.indexer && {
|
|
@@ -4469,6 +4740,11 @@ var TypeAnalyzer = class {
|
|
|
4469
4740
|
visitStatement(stmt, env) {
|
|
4470
4741
|
switch (stmt.type) {
|
|
4471
4742
|
case "VariableDeclaration": {
|
|
4743
|
+
stmt.names.forEach((target, i) => {
|
|
4744
|
+
if (target.type === "IdentifierPattern" && target.typeAnnotation && stmt.init[i]) {
|
|
4745
|
+
this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
|
|
4746
|
+
}
|
|
4747
|
+
});
|
|
4472
4748
|
const { types: valueTypes, sources } = this.valueList(stmt.init, env);
|
|
4473
4749
|
stmt.names.forEach((target, i) => {
|
|
4474
4750
|
const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
|
|
@@ -4529,6 +4805,16 @@ var TypeAnalyzer = class {
|
|
|
4529
4805
|
return;
|
|
4530
4806
|
}
|
|
4531
4807
|
case "AssignmentStatement": {
|
|
4808
|
+
stmt.targets.forEach((target, i) => {
|
|
4809
|
+
const value = stmt.values[i];
|
|
4810
|
+
if (!value) return;
|
|
4811
|
+
if (target.type === "MemberExpression" || target.type === "IndexExpression") {
|
|
4812
|
+
this.applyContext(value, this.infer(target, env));
|
|
4813
|
+
} else if (target.type === "Identifier") {
|
|
4814
|
+
const id = this.bindingIdOf(target);
|
|
4815
|
+
if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
|
|
4816
|
+
}
|
|
4817
|
+
});
|
|
4532
4818
|
const { types: valueTypes, sources } = this.valueList(stmt.values, env);
|
|
4533
4819
|
stmt.targets.forEach((target, i) => {
|
|
4534
4820
|
const vt = valueTypes[i] ?? unknownType;
|
|
@@ -4673,6 +4959,9 @@ var TypeAnalyzer = class {
|
|
|
4673
4959
|
case "BreakStatement":
|
|
4674
4960
|
this.breakStates[this.breakStates.length - 1]?.push(forkEnv(env));
|
|
4675
4961
|
return;
|
|
4962
|
+
case "DeclareClassStatement":
|
|
4963
|
+
this.checkClass(stmt);
|
|
4964
|
+
return;
|
|
4676
4965
|
case "ContinueStatement":
|
|
4677
4966
|
case "TypeAliasStatement":
|
|
4678
4967
|
case "ExportTypeAliasStatement":
|
|
@@ -4794,7 +5083,46 @@ var TypeAnalyzer = class {
|
|
|
4794
5083
|
}
|
|
4795
5084
|
if (p.pattern) return this.patternToType(p.pattern, env);
|
|
4796
5085
|
if (p.default) return widen(this.infer(p.default, env));
|
|
4797
|
-
return anyType;
|
|
5086
|
+
return this.contextualParams.get(p) ?? anyType;
|
|
5087
|
+
}
|
|
5088
|
+
/** What a function expression's unannotated parameters are, from where
|
|
5089
|
+
* it is written — see `applyContext`. */
|
|
5090
|
+
contextualParams = /* @__PURE__ */ new WeakMap();
|
|
5091
|
+
/** `expected` is the type the surroundings want for `expr`. A function
|
|
5092
|
+
* expression written there takes its unannotated parameters' types from
|
|
5093
|
+
* it, as in TypeScript: `signal:Connect(function(player) ... end)` knows
|
|
5094
|
+
* `player` from `Connect`'s callback type. Anything else is inferred as
|
|
5095
|
+
* usual. */
|
|
5096
|
+
applyContext(expr, expected) {
|
|
5097
|
+
let e = expr;
|
|
5098
|
+
while (e.type === "ParenthesizedExpression") e = e.expression;
|
|
5099
|
+
if (e.type !== "FunctionExpression" || !expected) return;
|
|
5100
|
+
const members = expected.kind === "union" ? expected.types : [expected];
|
|
5101
|
+
const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
|
|
5102
|
+
if (!signatures.length) return;
|
|
5103
|
+
e.func.params.forEach((p, k) => {
|
|
5104
|
+
if (p.typeAnnotation || p.pattern || p.default) return;
|
|
5105
|
+
const candidates = [];
|
|
5106
|
+
for (const signature of signatures) {
|
|
5107
|
+
const t2 = signature.params[k]?.type ?? signature.varargs;
|
|
5108
|
+
if (t2) candidates.push(t2);
|
|
5109
|
+
}
|
|
5110
|
+
if (!candidates.length) return;
|
|
5111
|
+
const t = union(candidates);
|
|
5112
|
+
this.contextualParams.set(p, containsTypeParam(t) ? anyType : t);
|
|
5113
|
+
});
|
|
5114
|
+
}
|
|
5115
|
+
/** The parameter type each written argument lands on, across `fns`. */
|
|
5116
|
+
expectedArguments(written, fns, selfOf) {
|
|
5117
|
+
return written.map((_, j) => {
|
|
5118
|
+
const candidates = [];
|
|
5119
|
+
for (const f of fns) {
|
|
5120
|
+
const i = j + selfOf(f);
|
|
5121
|
+
const param = i < f.params.length ? this.boundParams(f)[i] : f.varargs;
|
|
5122
|
+
if (param) candidates.push(param);
|
|
5123
|
+
}
|
|
5124
|
+
return candidates.length ? union(candidates) : void 0;
|
|
5125
|
+
});
|
|
4798
5126
|
}
|
|
4799
5127
|
/** Synthesize a type from a destructuring pattern used without an
|
|
4800
5128
|
* annotation (`function f({ a, b = 1 })`). */
|
|
@@ -4846,7 +5174,8 @@ var TypeAnalyzer = class {
|
|
|
4846
5174
|
f.params.forEach((p, i) => {
|
|
4847
5175
|
const arg = argTypes[i];
|
|
4848
5176
|
if (arg === void 0) return;
|
|
4849
|
-
|
|
5177
|
+
const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
|
|
5178
|
+
unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
|
|
4850
5179
|
});
|
|
4851
5180
|
for (const name of f.typeParams ?? []) if (!subst.has(name)) subst.set(name, unknownType);
|
|
4852
5181
|
return subst;
|
|
@@ -4907,12 +5236,13 @@ var TypeAnalyzer = class {
|
|
|
4907
5236
|
return;
|
|
4908
5237
|
}
|
|
4909
5238
|
const t = value;
|
|
5239
|
+
if (t.kind === "object" && t.class) return;
|
|
4910
5240
|
if (t.kind === "typeParam" && typeof t.name === "string" && bounds.has(t.name) && t.constraint && !containsTypeParam(t.constraint)) {
|
|
4911
5241
|
bounds.set(t.name, this.reduceType(t.constraint));
|
|
4912
5242
|
}
|
|
4913
5243
|
for (const child of Object.values(value)) walk(child);
|
|
4914
5244
|
};
|
|
4915
|
-
for (const p of f.params) walk(p.type);
|
|
5245
|
+
for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
|
|
4916
5246
|
return f.params.map((p) => substitute(p.type, bounds));
|
|
4917
5247
|
}
|
|
4918
5248
|
/** Record what each written argument is expected to be — see
|
|
@@ -5251,7 +5581,7 @@ var TypeAnalyzer = class {
|
|
|
5251
5581
|
this.expandCache.set(key, t);
|
|
5252
5582
|
this.resolvingAliases.add(t.name);
|
|
5253
5583
|
try {
|
|
5254
|
-
const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.
|
|
5584
|
+
const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.resolveDef(def);
|
|
5255
5585
|
const named = def.params.length === 0 && (r.kind === "object" || r.kind === "intersection") && !r.name ? { ...r, name: t.name } : r;
|
|
5256
5586
|
this.expandCache.set(key, named);
|
|
5257
5587
|
return named;
|
|
@@ -5374,9 +5704,9 @@ var TypeAnalyzer = class {
|
|
|
5374
5704
|
case "not":
|
|
5375
5705
|
return booleanType;
|
|
5376
5706
|
case "-":
|
|
5377
|
-
return numberType;
|
|
5707
|
+
return this.operatorResult(expr, "-", arg, void 0) ?? numberType;
|
|
5378
5708
|
case "#":
|
|
5379
|
-
return numberType;
|
|
5709
|
+
return this.operatorResult(expr, "#", arg, void 0) ?? numberType;
|
|
5380
5710
|
}
|
|
5381
5711
|
return arg;
|
|
5382
5712
|
}
|
|
@@ -5398,7 +5728,7 @@ var TypeAnalyzer = class {
|
|
|
5398
5728
|
const r = this.infer(expr.right, env);
|
|
5399
5729
|
switch (op) {
|
|
5400
5730
|
case "..":
|
|
5401
|
-
return stringType;
|
|
5731
|
+
return this.operatorResult(expr, op, l, r) ?? stringType;
|
|
5402
5732
|
case "==":
|
|
5403
5733
|
case "~=":
|
|
5404
5734
|
case "<":
|
|
@@ -5413,7 +5743,7 @@ var TypeAnalyzer = class {
|
|
|
5413
5743
|
case "//":
|
|
5414
5744
|
case "%":
|
|
5415
5745
|
case "^":
|
|
5416
|
-
return numberType;
|
|
5746
|
+
return this.operatorResult(expr, op, l, r) ?? numberType;
|
|
5417
5747
|
}
|
|
5418
5748
|
return union([l, r]);
|
|
5419
5749
|
}
|
|
@@ -5432,8 +5762,10 @@ var TypeAnalyzer = class {
|
|
|
5432
5762
|
}
|
|
5433
5763
|
case "CallExpression": {
|
|
5434
5764
|
const callee = this.infer(expr.callee, env);
|
|
5435
|
-
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5436
5765
|
const fns = this.overloadsOf(callee);
|
|
5766
|
+
const expected = this.expectedArguments(expr.arguments, fns, () => 0);
|
|
5767
|
+
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
5768
|
+
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5437
5769
|
if (fns.length) {
|
|
5438
5770
|
this.recordExpected(expr.arguments, fns, () => 0);
|
|
5439
5771
|
const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
|
|
@@ -5448,8 +5780,10 @@ var TypeAnalyzer = class {
|
|
|
5448
5780
|
}
|
|
5449
5781
|
case "MethodCallExpression": {
|
|
5450
5782
|
const objType = this.infer(expr.object, env);
|
|
5451
|
-
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5452
5783
|
const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
|
|
5784
|
+
const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
|
|
5785
|
+
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
5786
|
+
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5453
5787
|
if (fns.length) {
|
|
5454
5788
|
const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
|
|
5455
5789
|
const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
|
|
@@ -5841,6 +6175,35 @@ var TypeAnalyzer = class {
|
|
|
5841
6175
|
this.selfType = saved;
|
|
5842
6176
|
}
|
|
5843
6177
|
}
|
|
6178
|
+
/** What an operator on a value with metamethods gives: `a + b` calls
|
|
6179
|
+
* `__add` on `a`, or failing that on `b` with the operands swapped — the
|
|
6180
|
+
* order Luau tries them in. That is how `Vector3 + Vector3`, `CFrame *
|
|
6181
|
+
* Vector3` and `2 * vector` get their types from the declarations.
|
|
6182
|
+
* `undefined` when neither operand declares the metamethod; an operand
|
|
6183
|
+
* that declares it but accepts neither argument is reported. */
|
|
6184
|
+
operatorResult(node, op, left, right) {
|
|
6185
|
+
const name = right === void 0 ? op === "-" ? "__unm" : "__len" : METAMETHODS[op];
|
|
6186
|
+
if (!name) return void 0;
|
|
6187
|
+
const candidates = right === void 0 ? [[left, void 0]] : [[left, right], [right, left]];
|
|
6188
|
+
let declared;
|
|
6189
|
+
for (const [receiver, other] of candidates) {
|
|
6190
|
+
const t = this.expand(receiver);
|
|
6191
|
+
const method = t.kind === "object" ? t.properties.get(name) : void 0;
|
|
6192
|
+
if (!method) continue;
|
|
6193
|
+
declared ??= receiver;
|
|
6194
|
+
const args = other === void 0 ? [receiver] : [receiver, other];
|
|
6195
|
+
const picked = this.pickOverload(this.overloadsOf(method.type), args);
|
|
6196
|
+
if (picked) return this.callReturn(picked, args);
|
|
6197
|
+
}
|
|
6198
|
+
if (declared && this.emitDiagnostics) {
|
|
6199
|
+
this.diagnostics.push({
|
|
6200
|
+
node,
|
|
6201
|
+
message: right === void 0 ? `Operator '${op}' cannot be applied to type '${formatType(left)}'` : `Operator '${op}' cannot be applied to types '${formatType(left)}' and '${formatType(right)}'`
|
|
6202
|
+
});
|
|
6203
|
+
return anyType;
|
|
6204
|
+
}
|
|
6205
|
+
return void 0;
|
|
6206
|
+
}
|
|
5844
6207
|
/** Does this signature take the receiver as its first parameter?
|
|
5845
6208
|
*
|
|
5846
6209
|
* Luau's `:` is sugar both ways: `function T:m(a)` declares
|
|
@@ -5917,22 +6280,366 @@ function briefType(t) {
|
|
|
5917
6280
|
return formatType(t);
|
|
5918
6281
|
}
|
|
5919
6282
|
|
|
5920
|
-
// src/
|
|
6283
|
+
// src/project/host.ts
|
|
5921
6284
|
var import_node_fs = require("fs");
|
|
5922
|
-
var
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
6285
|
+
var nodeHost = {
|
|
6286
|
+
readFile(path) {
|
|
6287
|
+
try {
|
|
6288
|
+
return (0, import_node_fs.statSync)(path).isFile() ? (0, import_node_fs.readFileSync)(path, "utf8") : void 0;
|
|
6289
|
+
} catch {
|
|
6290
|
+
return void 0;
|
|
6291
|
+
}
|
|
6292
|
+
}
|
|
6293
|
+
};
|
|
6294
|
+
|
|
6295
|
+
// src/project/config.ts
|
|
6296
|
+
var import_node_path = require("path");
|
|
6297
|
+
var CONFIG_FILE_NAMES = ["luaut.config.json", "luaut.config.jsonc"];
|
|
6298
|
+
function findConfig(file, host = nodeHost) {
|
|
6299
|
+
const searched = [];
|
|
6300
|
+
let directory = (0, import_node_path.dirname)((0, import_node_path.resolve)(file));
|
|
6301
|
+
for (; ; ) {
|
|
6302
|
+
const found = [];
|
|
6303
|
+
for (const name of CONFIG_FILE_NAMES) {
|
|
6304
|
+
const path = (0, import_node_path.join)(directory, name);
|
|
6305
|
+
searched.push(path);
|
|
6306
|
+
if (host.readFile(path) !== void 0) found.push(path);
|
|
6307
|
+
}
|
|
6308
|
+
if (found.length > 1) {
|
|
6309
|
+
const message = `Only one luaut config may be in a folder, but both ${CONFIG_FILE_NAMES.join(" and ")} are in ${directory}`;
|
|
6310
|
+
return { searched, problems: found.map((path) => ({ file: path, message, line: 1, column: 1 })) };
|
|
6311
|
+
}
|
|
6312
|
+
if (found.length === 1) {
|
|
6313
|
+
const { config, problems } = loadConfig(found[0], host);
|
|
6314
|
+
return { config, problems, searched };
|
|
6315
|
+
}
|
|
6316
|
+
const parent = (0, import_node_path.dirname)(directory);
|
|
6317
|
+
if (parent === directory) return { searched, problems: [] };
|
|
6318
|
+
directory = parent;
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6321
|
+
var OPTIONS = ["types", "paths", "baseUrl", "sourceMap"];
|
|
6322
|
+
function loadConfig(path, host = nodeHost) {
|
|
6323
|
+
const file = (0, import_node_path.resolve)(path);
|
|
6324
|
+
const source = host.readFile(file);
|
|
6325
|
+
if (source === void 0) return { problems: [{ file, message: "Cannot read the config file" }] };
|
|
6326
|
+
let raw;
|
|
6327
|
+
try {
|
|
6328
|
+
raw = JSON.parse(stripJsonComments(source));
|
|
6329
|
+
} catch (error) {
|
|
6330
|
+
const message = error.message;
|
|
6331
|
+
return { problems: [{ file, message: `Invalid JSON: ${message}`, ...jsonErrorPosition(source, message) }] };
|
|
6332
|
+
}
|
|
6333
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
6334
|
+
return { problems: [{ file, message: "The config must be a JSON object", line: 1, column: 1 }] };
|
|
6335
|
+
}
|
|
6336
|
+
const directory = (0, import_node_path.dirname)(file);
|
|
6337
|
+
const options = raw;
|
|
6338
|
+
const problems = [];
|
|
6339
|
+
const at = (key) => keyPosition(source, key);
|
|
6340
|
+
const problem = (key, message) => {
|
|
6341
|
+
problems.push({ file, message, ...at(key) });
|
|
6342
|
+
};
|
|
6343
|
+
for (const key of Object.keys(options)) {
|
|
6344
|
+
if (!OPTIONS.includes(key)) {
|
|
6345
|
+
problem(key, `Unknown option '${key}'. Options are: ${OPTIONS.join(", ")}`);
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
let types = [];
|
|
6349
|
+
if (options.types !== void 0) {
|
|
6350
|
+
if (Array.isArray(options.types) && options.types.every((t) => typeof t === "string")) types = options.types;
|
|
6351
|
+
else problem("types", `'types' must be an array of strings, such as ["luau"]`);
|
|
6352
|
+
}
|
|
6353
|
+
const paths = {};
|
|
6354
|
+
if (options.paths !== void 0) {
|
|
6355
|
+
const value = options.paths;
|
|
6356
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
6357
|
+
for (const [pattern, targets] of Object.entries(value)) {
|
|
6358
|
+
if (Array.isArray(targets) && targets.every((t) => typeof t === "string")) paths[pattern] = targets;
|
|
6359
|
+
else problem(pattern, `'paths' entry '${pattern}' must be an array of strings`);
|
|
6360
|
+
if (pattern.split("*").length > 2) problem(pattern, `'paths' pattern '${pattern}' may contain at most one '*'`);
|
|
6361
|
+
}
|
|
6362
|
+
} else {
|
|
6363
|
+
problem("paths", `'paths' must be an object, such as { "@shared/*": ["src/shared/*"] }`);
|
|
6364
|
+
}
|
|
6365
|
+
}
|
|
6366
|
+
let baseUrl = directory;
|
|
6367
|
+
if (options.baseUrl !== void 0) {
|
|
6368
|
+
if (typeof options.baseUrl === "string") baseUrl = (0, import_node_path.resolve)(directory, options.baseUrl);
|
|
6369
|
+
else problem("baseUrl", "'baseUrl' must be a string");
|
|
6370
|
+
}
|
|
6371
|
+
let sourceMap = null;
|
|
6372
|
+
if (options.sourceMap !== void 0 && options.sourceMap !== null) {
|
|
6373
|
+
if (typeof options.sourceMap === "string") sourceMap = (0, import_node_path.resolve)(directory, options.sourceMap);
|
|
6374
|
+
else problem("sourceMap", "'sourceMap' must be a path string, or null for none");
|
|
6375
|
+
}
|
|
6376
|
+
return { config: { path: file, directory, source, types, paths, baseUrl, sourceMap }, problems };
|
|
6377
|
+
}
|
|
6378
|
+
function stripJsonComments(text) {
|
|
6379
|
+
const out = text.split("");
|
|
6380
|
+
let i = 0;
|
|
6381
|
+
let inString = false;
|
|
6382
|
+
while (i < text.length) {
|
|
6383
|
+
const ch = text[i];
|
|
6384
|
+
if (inString) {
|
|
6385
|
+
if (ch === "\\") i += 2;
|
|
6386
|
+
else {
|
|
6387
|
+
if (ch === '"') inString = false;
|
|
6388
|
+
i++;
|
|
6389
|
+
}
|
|
6390
|
+
continue;
|
|
6391
|
+
}
|
|
6392
|
+
if (ch === '"') {
|
|
6393
|
+
inString = true;
|
|
6394
|
+
i++;
|
|
6395
|
+
} else if (ch === "/" && text[i + 1] === "/") {
|
|
6396
|
+
while (i < text.length && text[i] !== "\n") out[i++] = " ";
|
|
6397
|
+
} else if (ch === "/" && text[i + 1] === "*") {
|
|
6398
|
+
out[i++] = " ";
|
|
6399
|
+
out[i++] = " ";
|
|
6400
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) {
|
|
6401
|
+
if (text[i] !== "\n") out[i] = " ";
|
|
6402
|
+
i++;
|
|
6403
|
+
}
|
|
6404
|
+
if (i < text.length) {
|
|
6405
|
+
out[i++] = " ";
|
|
6406
|
+
out[i++] = " ";
|
|
6407
|
+
}
|
|
6408
|
+
} else if (ch === ",") {
|
|
6409
|
+
let j = i + 1;
|
|
6410
|
+
while (j < text.length && /\s/.test(text[j])) j++;
|
|
6411
|
+
if (text[j] === "}" || text[j] === "]") out[i] = " ";
|
|
6412
|
+
i++;
|
|
6413
|
+
} else {
|
|
6414
|
+
i++;
|
|
6415
|
+
}
|
|
6416
|
+
}
|
|
6417
|
+
return out.join("");
|
|
6418
|
+
}
|
|
6419
|
+
function jsonErrorPosition(source, message) {
|
|
6420
|
+
const lineColumn = /line (\d+) column (\d+)/.exec(message);
|
|
6421
|
+
if (lineColumn) return { line: Number(lineColumn[1]), column: Number(lineColumn[2]) };
|
|
6422
|
+
const position = /position (\d+)/.exec(message);
|
|
6423
|
+
return position ? offsetPosition(source, Number(position[1])) : { line: 1, column: 1 };
|
|
6424
|
+
}
|
|
6425
|
+
function keyPosition(source, key) {
|
|
6426
|
+
const offset = source.indexOf(JSON.stringify(key));
|
|
6427
|
+
return offset < 0 ? { line: 1, column: 1 } : offsetPosition(source, offset);
|
|
6428
|
+
}
|
|
6429
|
+
function offsetPosition(source, offset) {
|
|
6430
|
+
const before = source.slice(0, offset);
|
|
6431
|
+
const line = before.split("\n").length;
|
|
6432
|
+
return { line, column: offset - before.lastIndexOf("\n") };
|
|
6433
|
+
}
|
|
5926
6434
|
|
|
5927
|
-
// src/
|
|
5928
|
-
var
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
6435
|
+
// src/project/libraries.ts
|
|
6436
|
+
var import_node_path2 = require("path");
|
|
6437
|
+
function resolveTypeLibraries(config, host = nodeHost) {
|
|
6438
|
+
const files = [];
|
|
6439
|
+
const problems = [];
|
|
6440
|
+
const loaded = /* @__PURE__ */ new Set();
|
|
6441
|
+
const addFile = (file) => {
|
|
6442
|
+
const key = pathKey(file);
|
|
6443
|
+
if (loaded.has(key)) return;
|
|
6444
|
+
loaded.add(key);
|
|
6445
|
+
files.push(file);
|
|
6446
|
+
};
|
|
6447
|
+
const addPackage = (directory, entryFile, visiting) => {
|
|
6448
|
+
const key = pathKey(directory);
|
|
6449
|
+
if (visiting.has(key)) return;
|
|
6450
|
+
visiting.add(key);
|
|
6451
|
+
for (const dependency of dependencyNames(directory, host)) {
|
|
6452
|
+
const found = findPackage(dependency, directory, host);
|
|
6453
|
+
if (found) addPackage(found.directory, found.file, visiting);
|
|
6454
|
+
}
|
|
6455
|
+
addFile(entryFile);
|
|
6456
|
+
};
|
|
6457
|
+
for (const entry of config.types) {
|
|
6458
|
+
const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);
|
|
6459
|
+
if (relative) {
|
|
6460
|
+
const target = (0, import_node_path2.resolve)(config.directory, entry);
|
|
6461
|
+
if (entry.endsWith(".luaut")) {
|
|
6462
|
+
if (host.readFile(target) !== void 0) addFile(target);
|
|
6463
|
+
else problems.push({ file: config.path, message: `Cannot find type library file '${entry}'`, ...entryPosition(config, entry) });
|
|
6464
|
+
continue;
|
|
6465
|
+
}
|
|
6466
|
+
const file = packageEntry(target, host);
|
|
6467
|
+
if (file) addPackage(target, file, /* @__PURE__ */ new Set());
|
|
6468
|
+
else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'luaut.types' in its package.json)`, ...entryPosition(config, entry) });
|
|
6469
|
+
continue;
|
|
6470
|
+
}
|
|
6471
|
+
const names = entry.startsWith("@") || entry.includes("/") ? [entry] : [`@luaut/${entry}`, entry];
|
|
6472
|
+
const found = names.map((name) => findPackage(name, config.directory, host)).find(Boolean);
|
|
6473
|
+
if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
|
|
6474
|
+
else {
|
|
6475
|
+
problems.push({
|
|
6476
|
+
file: config.path,
|
|
6477
|
+
message: `Cannot find type library '${entry}'. Install it with: npm i -D ${names[0]}`,
|
|
6478
|
+
...entryPosition(config, entry)
|
|
6479
|
+
});
|
|
6480
|
+
}
|
|
6481
|
+
}
|
|
6482
|
+
return { files, problems };
|
|
6483
|
+
}
|
|
6484
|
+
var ENTRY_FILE = "index.d.luaut";
|
|
6485
|
+
function packageEntry(directory, host) {
|
|
6486
|
+
const manifest = readJson((0, import_node_path2.join)(directory, "package.json"), host);
|
|
6487
|
+
const declared = manifest?.luaut?.types;
|
|
6488
|
+
const file = (0, import_node_path2.resolve)(directory, typeof declared === "string" ? declared : ENTRY_FILE);
|
|
6489
|
+
return host.readFile(file) !== void 0 ? file : void 0;
|
|
6490
|
+
}
|
|
6491
|
+
function findPackage(name, from, host) {
|
|
6492
|
+
let directory = (0, import_node_path2.resolve)(from);
|
|
6493
|
+
for (; ; ) {
|
|
6494
|
+
const candidate = (0, import_node_path2.join)(directory, "node_modules", ...name.split("/"));
|
|
6495
|
+
const file = packageEntry(candidate, host);
|
|
6496
|
+
if (file) return { directory: candidate, file };
|
|
6497
|
+
const parent = (0, import_node_path2.dirname)(directory);
|
|
6498
|
+
if (parent === directory) return void 0;
|
|
6499
|
+
directory = parent;
|
|
6500
|
+
}
|
|
6501
|
+
}
|
|
6502
|
+
function dependencyNames(directory, host) {
|
|
6503
|
+
const manifest = readJson((0, import_node_path2.join)(directory, "package.json"), host);
|
|
6504
|
+
const names = /* @__PURE__ */ new Set();
|
|
6505
|
+
for (const field of ["dependencies", "peerDependencies"]) {
|
|
6506
|
+
const deps = manifest?.[field];
|
|
6507
|
+
if (deps && typeof deps === "object") for (const name of Object.keys(deps)) names.add(name);
|
|
6508
|
+
}
|
|
6509
|
+
return [...names];
|
|
6510
|
+
}
|
|
6511
|
+
function readJson(path, host) {
|
|
6512
|
+
const text = host.readFile(path);
|
|
6513
|
+
if (text === void 0) return void 0;
|
|
6514
|
+
try {
|
|
6515
|
+
const value = JSON.parse(text);
|
|
6516
|
+
return value && typeof value === "object" ? value : void 0;
|
|
6517
|
+
} catch {
|
|
6518
|
+
return void 0;
|
|
6519
|
+
}
|
|
6520
|
+
}
|
|
6521
|
+
function entryPosition(config, entry) {
|
|
6522
|
+
return keyPosition(config.source, entry);
|
|
6523
|
+
}
|
|
6524
|
+
function pathKey(path) {
|
|
6525
|
+
const normalized = (0, import_node_path2.resolve)(path);
|
|
6526
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
6527
|
+
}
|
|
6528
|
+
|
|
6529
|
+
// src/project/modules.ts
|
|
6530
|
+
var import_node_path3 = require("path");
|
|
6531
|
+
function moduleCandidates(fromFile, specifier, config) {
|
|
6532
|
+
const bases = specifier.startsWith("./") || specifier.startsWith("../") ? [(0, import_node_path3.resolve)((0, import_node_path3.dirname)(fromFile), specifier)] : config ? aliasTargets(config, specifier) : [];
|
|
6533
|
+
return bases.flatMap((base) => base.endsWith(".luaut") ? [base] : [`${base}.luaut`, `${base}.d.luaut`, (0, import_node_path3.join)(base, "index.luaut")]);
|
|
6534
|
+
}
|
|
6535
|
+
function resolveModulePath(fromFile, specifier, config, host = nodeHost) {
|
|
6536
|
+
return moduleCandidates(fromFile, specifier, config).find((path) => host.readFile(path) !== void 0);
|
|
6537
|
+
}
|
|
6538
|
+
function aliasTargets(config, specifier) {
|
|
6539
|
+
let match;
|
|
6540
|
+
let prefixLength = -1;
|
|
6541
|
+
for (const pattern2 of Object.keys(config.paths)) {
|
|
6542
|
+
const star = pattern2.indexOf("*");
|
|
6543
|
+
if (star < 0) {
|
|
6544
|
+
if (pattern2 === specifier) {
|
|
6545
|
+
match = { pattern: pattern2, wildcard: "" };
|
|
6546
|
+
break;
|
|
6547
|
+
}
|
|
6548
|
+
continue;
|
|
6549
|
+
}
|
|
6550
|
+
const prefix = pattern2.slice(0, star);
|
|
6551
|
+
const suffix = pattern2.slice(star + 1);
|
|
6552
|
+
const fits = specifier.length >= prefix.length + suffix.length && specifier.startsWith(prefix) && specifier.endsWith(suffix);
|
|
6553
|
+
if (fits && prefix.length > prefixLength) {
|
|
6554
|
+
prefixLength = prefix.length;
|
|
6555
|
+
match = { pattern: pattern2, wildcard: specifier.slice(prefix.length, specifier.length - suffix.length) };
|
|
6556
|
+
}
|
|
6557
|
+
}
|
|
6558
|
+
if (!match) return [];
|
|
6559
|
+
const { pattern, wildcard } = match;
|
|
6560
|
+
return config.paths[pattern].map((target) => (0, import_node_path3.resolve)(config.baseUrl, target.replace("*", wildcard)));
|
|
6561
|
+
}
|
|
6562
|
+
|
|
6563
|
+
// src/project/sourcemap.ts
|
|
6564
|
+
var import_node_path4 = require("path");
|
|
6565
|
+
var IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
6566
|
+
var INSTANCE_MEMBERS = /* @__PURE__ */ new Set(["Name", "ClassName", "Parent", "Archivable"]);
|
|
6567
|
+
var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".luaut", ".luau", ".lua"]);
|
|
6568
|
+
function sourceMapTypes(text, path, options) {
|
|
6569
|
+
let root;
|
|
6570
|
+
try {
|
|
6571
|
+
root = JSON.parse(text);
|
|
6572
|
+
} catch (error) {
|
|
6573
|
+
return { problem: `Invalid sourcemap: ${error.message}` };
|
|
6574
|
+
}
|
|
6575
|
+
if (!isNode(root)) return { problem: "Invalid sourcemap: the root must be an object with 'name' and 'className'" };
|
|
6576
|
+
const directory = (0, import_node_path4.dirname)((0, import_node_path4.resolve)(path));
|
|
6577
|
+
const lines = [];
|
|
6578
|
+
const aliasOfFile = /* @__PURE__ */ new Map();
|
|
6579
|
+
const used = /* @__PURE__ */ new Set();
|
|
6580
|
+
const aliasOfNode = /* @__PURE__ */ new Map();
|
|
6581
|
+
const aliasFor = (segments) => {
|
|
6582
|
+
const base = `SourceMap_${segments.map((s) => s.replace(/[^A-Za-z0-9_]/g, "_")).join("_")}`;
|
|
6583
|
+
let alias = base;
|
|
6584
|
+
for (let n = 2; used.has(alias); n++) alias = `${base}_${n}`;
|
|
6585
|
+
used.add(alias);
|
|
6586
|
+
return alias;
|
|
6587
|
+
};
|
|
6588
|
+
const visit = (node, segments, parent) => {
|
|
6589
|
+
const alias = aliasFor(segments);
|
|
6590
|
+
aliasOfNode.set(node, alias);
|
|
6591
|
+
for (const filePath of node.filePaths ?? []) aliasOfFile.set(fileKey((0, import_node_path4.resolve)(directory, filePath)), alias);
|
|
6592
|
+
const className = IDENTIFIER.test(node.className) && options.classes.has(node.className) ? node.className : "Instance";
|
|
6593
|
+
const taken = options.membersOf?.(className) ?? INSTANCE_MEMBERS;
|
|
6594
|
+
const members = [];
|
|
6595
|
+
if (parent) members.push(`Parent: ${parent}`);
|
|
6596
|
+
const named = /* @__PURE__ */ new Set();
|
|
6597
|
+
for (const child of node.children ?? []) {
|
|
6598
|
+
if (!isNode(child)) continue;
|
|
6599
|
+
const childAlias = visit(child, [...segments, child.name], alias);
|
|
6600
|
+
if (!IDENTIFIER.test(child.name) || taken.has(child.name) || named.has(child.name)) continue;
|
|
6601
|
+
named.add(child.name);
|
|
6602
|
+
members.push(`${child.name}: ${childAlias}`);
|
|
6603
|
+
}
|
|
6604
|
+
lines.push(`declare class ${alias} extends ${className} { ${members.join(", ")} }`);
|
|
6605
|
+
return alias;
|
|
6606
|
+
};
|
|
6607
|
+
const rootAlias = visit(root, [root.name], void 0);
|
|
6608
|
+
if (root.className === "DataModel") {
|
|
6609
|
+
lines.push(`declare game: ${rootAlias}`);
|
|
6610
|
+
const workspace = (root.children ?? []).find((child) => isNode(child) && child.className === "Workspace");
|
|
6611
|
+
const workspaceAlias = workspace && aliasOfNode.get(workspace);
|
|
6612
|
+
if (workspaceAlias) lines.push(`declare workspace: ${workspaceAlias}`);
|
|
6613
|
+
}
|
|
6614
|
+
let program;
|
|
6615
|
+
try {
|
|
6616
|
+
program = parse(lines.join("\n"));
|
|
6617
|
+
} catch (error) {
|
|
6618
|
+
return { problem: `Could not turn the sourcemap into types: ${error.message}` };
|
|
6619
|
+
}
|
|
6620
|
+
return {
|
|
6621
|
+
types: {
|
|
6622
|
+
program,
|
|
6623
|
+
scriptFor(file) {
|
|
6624
|
+
const alias = aliasOfFile.get(fileKey(file));
|
|
6625
|
+
return alias ? parse(`declare script: ${alias}`) : void 0;
|
|
6626
|
+
}
|
|
6627
|
+
}
|
|
6628
|
+
};
|
|
6629
|
+
}
|
|
6630
|
+
function isNode(value) {
|
|
6631
|
+
if (!value || typeof value !== "object") return false;
|
|
6632
|
+
const node = value;
|
|
6633
|
+
return typeof node.name === "string" && typeof node.className === "string";
|
|
6634
|
+
}
|
|
6635
|
+
function fileKey(path) {
|
|
6636
|
+
const extension = (0, import_node_path4.extname)(path);
|
|
6637
|
+
const bare = SCRIPT_EXTENSIONS.has(extension) ? path.slice(0, -extension.length) : path;
|
|
6638
|
+
const normalized = (0, import_node_path4.resolve)(bare);
|
|
6639
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
6640
|
+
}
|
|
5933
6641
|
|
|
5934
6642
|
// src/index.ts
|
|
5935
|
-
var defaultLibs = [luauLib, robloxLib];
|
|
5936
6643
|
var luautparser = {
|
|
5937
6644
|
tokenize,
|
|
5938
6645
|
parseTokens,
|
|
@@ -5949,6 +6656,7 @@ var index_default = luautparser;
|
|
|
5949
6656
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5950
6657
|
0 && (module.exports = {
|
|
5951
6658
|
BinaryOperators,
|
|
6659
|
+
CONFIG_FILE_NAMES,
|
|
5952
6660
|
Keywords,
|
|
5953
6661
|
LexError,
|
|
5954
6662
|
Operators,
|
|
@@ -5962,25 +6670,25 @@ var index_default = luautparser;
|
|
|
5962
6670
|
booleanType,
|
|
5963
6671
|
bufferType,
|
|
5964
6672
|
containsTypeParam,
|
|
5965
|
-
defaultLibs,
|
|
5966
6673
|
difference,
|
|
5967
6674
|
equalTypes,
|
|
5968
6675
|
falsyType,
|
|
6676
|
+
findConfig,
|
|
5969
6677
|
fn,
|
|
5970
6678
|
formatType,
|
|
5971
6679
|
getBinding,
|
|
5972
6680
|
intersection,
|
|
5973
6681
|
isAssignable,
|
|
6682
|
+
isClassType,
|
|
5974
6683
|
isGlobal,
|
|
5975
6684
|
isPossiblyFalsy,
|
|
5976
6685
|
isPossiblyTruthy,
|
|
5977
6686
|
isUnassignedGlobal,
|
|
5978
6687
|
literal,
|
|
5979
|
-
|
|
5980
|
-
luauDefsPath,
|
|
5981
|
-
luauLib,
|
|
6688
|
+
loadConfig,
|
|
5982
6689
|
luautparser,
|
|
5983
6690
|
matchInfer,
|
|
6691
|
+
moduleCandidates,
|
|
5984
6692
|
moduleExports,
|
|
5985
6693
|
narrowExclude,
|
|
5986
6694
|
narrowFalsy,
|
|
@@ -5988,6 +6696,7 @@ var index_default = luautparser;
|
|
|
5988
6696
|
narrowTruthy,
|
|
5989
6697
|
neverType,
|
|
5990
6698
|
nilType,
|
|
6699
|
+
nodeHost,
|
|
5991
6700
|
numberType,
|
|
5992
6701
|
objectType,
|
|
5993
6702
|
optional,
|
|
@@ -5997,11 +6706,12 @@ var index_default = luautparser;
|
|
|
5997
6706
|
parseTokens,
|
|
5998
6707
|
parseWithRecovery,
|
|
5999
6708
|
primitive,
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
robloxLib,
|
|
6709
|
+
resolveModulePath,
|
|
6710
|
+
resolveTypeLibraries,
|
|
6003
6711
|
setAliasExpander,
|
|
6712
|
+
sourceMapTypes,
|
|
6004
6713
|
stringType,
|
|
6714
|
+
stripJsonComments,
|
|
6005
6715
|
substitute,
|
|
6006
6716
|
templateMatches,
|
|
6007
6717
|
threadType,
|