luaut-parser 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -30
- package/dist/index.cjs +501 -41
- package/dist/index.d.cts +98 -23
- package/dist/index.d.ts +98 -23
- package/dist/index.js +493 -31
- package/package.json +4 -5
- 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,10 +36,10 @@ __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,
|
|
@@ -49,11 +50,10 @@ __export(index_exports, {
|
|
|
49
50
|
isPossiblyTruthy: () => isPossiblyTruthy,
|
|
50
51
|
isUnassignedGlobal: () => isUnassignedGlobal,
|
|
51
52
|
literal: () => literal,
|
|
52
|
-
|
|
53
|
-
luauDefsPath: () => luauDefsPath,
|
|
54
|
-
luauLib: () => luauLib,
|
|
53
|
+
loadConfig: () => loadConfig,
|
|
55
54
|
luautparser: () => luautparser,
|
|
56
55
|
matchInfer: () => matchInfer,
|
|
56
|
+
moduleCandidates: () => moduleCandidates,
|
|
57
57
|
moduleExports: () => moduleExports,
|
|
58
58
|
narrowExclude: () => narrowExclude,
|
|
59
59
|
narrowFalsy: () => narrowFalsy,
|
|
@@ -61,6 +61,7 @@ __export(index_exports, {
|
|
|
61
61
|
narrowTruthy: () => narrowTruthy,
|
|
62
62
|
neverType: () => neverType,
|
|
63
63
|
nilType: () => nilType,
|
|
64
|
+
nodeHost: () => nodeHost,
|
|
64
65
|
numberType: () => numberType,
|
|
65
66
|
objectType: () => objectType,
|
|
66
67
|
optional: () => optional,
|
|
@@ -70,11 +71,12 @@ __export(index_exports, {
|
|
|
70
71
|
parseTokens: () => parseTokens,
|
|
71
72
|
parseWithRecovery: () => parseWithRecovery,
|
|
72
73
|
primitive: () => primitive,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
robloxLib: () => robloxLib,
|
|
74
|
+
resolveModulePath: () => resolveModulePath,
|
|
75
|
+
resolveTypeLibraries: () => resolveTypeLibraries,
|
|
76
76
|
setAliasExpander: () => setAliasExpander,
|
|
77
|
+
sourceMapTypes: () => sourceMapTypes,
|
|
77
78
|
stringType: () => stringType,
|
|
79
|
+
stripJsonComments: () => stripJsonComments,
|
|
78
80
|
substitute: () => substitute,
|
|
79
81
|
templateMatches: () => templateMatches,
|
|
80
82
|
threadType: () => threadType,
|
|
@@ -88,10 +90,6 @@ __export(index_exports, {
|
|
|
88
90
|
});
|
|
89
91
|
module.exports = __toCommonJS(index_exports);
|
|
90
92
|
|
|
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
93
|
// src/lexer/lexer.ts
|
|
96
94
|
var Keywords = [
|
|
97
95
|
"and",
|
|
@@ -3270,7 +3268,11 @@ function isAssignableInner(a, b) {
|
|
|
3270
3268
|
if (a.kind === "union") return a.types.every((t) => isAssignable(t, b));
|
|
3271
3269
|
if (b.kind === "union") return b.types.some((t) => isAssignable(a, t));
|
|
3272
3270
|
if (b.kind === "intersection") return b.types.every((t) => isAssignable(a, t));
|
|
3273
|
-
if (a.kind === "intersection")
|
|
3271
|
+
if (a.kind === "intersection") {
|
|
3272
|
+
if (a.types.some((t) => isAssignable(t, b))) return true;
|
|
3273
|
+
const merged = mergeObjectMembers(a.types);
|
|
3274
|
+
return merged !== void 0 && isAssignable(merged, b);
|
|
3275
|
+
}
|
|
3274
3276
|
if (a.kind === "literal") {
|
|
3275
3277
|
if (b.kind === "literal") return a.value === b.value;
|
|
3276
3278
|
if (b.kind === "primitive") return b.name === a.base;
|
|
@@ -3625,6 +3627,39 @@ var IDENT_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
3625
3627
|
function formatKey(k) {
|
|
3626
3628
|
return IDENT_KEY.test(k) ? k : JSON.stringify(k);
|
|
3627
3629
|
}
|
|
3630
|
+
function mergeObjectMembers(types) {
|
|
3631
|
+
const objects = [];
|
|
3632
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3633
|
+
const collect = (t) => {
|
|
3634
|
+
if (seen.has(t)) return true;
|
|
3635
|
+
seen.add(t);
|
|
3636
|
+
if (t.kind === "genericRef") {
|
|
3637
|
+
const expanded = expandAlias?.(t);
|
|
3638
|
+
return expanded !== void 0 && expanded !== t && collect(expanded);
|
|
3639
|
+
}
|
|
3640
|
+
if (t.kind === "intersection") return t.types.every(collect);
|
|
3641
|
+
if (t.kind === "object") {
|
|
3642
|
+
objects.push(t);
|
|
3643
|
+
return true;
|
|
3644
|
+
}
|
|
3645
|
+
return false;
|
|
3646
|
+
};
|
|
3647
|
+
if (!types.every(collect) || objects.length < 2) return void 0;
|
|
3648
|
+
const properties = /* @__PURE__ */ new Map();
|
|
3649
|
+
let indexer;
|
|
3650
|
+
for (const object of objects) {
|
|
3651
|
+
indexer ??= object.indexer;
|
|
3652
|
+
for (const [name, property] of object.properties) {
|
|
3653
|
+
const existing = properties.get(name);
|
|
3654
|
+
properties.set(name, existing ? {
|
|
3655
|
+
type: intersection([existing.type, property.type]),
|
|
3656
|
+
optional: existing.optional && property.optional,
|
|
3657
|
+
readonly: existing.readonly || property.readonly
|
|
3658
|
+
} : property);
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
return objectType([...properties], indexer);
|
|
3662
|
+
}
|
|
3628
3663
|
|
|
3629
3664
|
// src/ast/analyzeTypes.ts
|
|
3630
3665
|
function analyzeTypes(program, scopes, options = {}) {
|
|
@@ -3832,6 +3867,7 @@ var TypeAnalyzer = class {
|
|
|
3832
3867
|
bindingType = /* @__PURE__ */ new Map();
|
|
3833
3868
|
narrowedTypeOf = /* @__PURE__ */ new Map();
|
|
3834
3869
|
typeOfTypeNode = /* @__PURE__ */ new Map();
|
|
3870
|
+
expectedTypeOf = /* @__PURE__ */ new Map();
|
|
3835
3871
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
3836
3872
|
* `typeParam` nodes in the body). */
|
|
3837
3873
|
aliases = /* @__PURE__ */ new Map();
|
|
@@ -3897,6 +3933,7 @@ var TypeAnalyzer = class {
|
|
|
3897
3933
|
bindingType: this.bindingType,
|
|
3898
3934
|
narrowedTypeOf: this.narrowedTypeOf,
|
|
3899
3935
|
typeOfTypeNode: this.typeOfTypeNode,
|
|
3936
|
+
expectedTypeOf: this.expectedTypeOf,
|
|
3900
3937
|
aliases: this.resolveDeferredAliases(),
|
|
3901
3938
|
diagnostics: this.diagnostics
|
|
3902
3939
|
};
|
|
@@ -4878,16 +4915,76 @@ var TypeAnalyzer = class {
|
|
|
4878
4915
|
return void 0;
|
|
4879
4916
|
}
|
|
4880
4917
|
/** Can this signature be called with these argument types? The signature's
|
|
4881
|
-
* own
|
|
4882
|
-
*
|
|
4918
|
+
* own type parameters stand for what the call would infer, so each is
|
|
4919
|
+
* checked only against its constraint — `<K extends keyof Services>`
|
|
4920
|
+
* accepts `"Players"` but not `""`. */
|
|
4883
4921
|
overloadAccepts(f, argTypes) {
|
|
4884
4922
|
if (!f.varargs && argTypes.length > f.params.length) return false;
|
|
4885
|
-
const
|
|
4923
|
+
const params = this.boundParams(f);
|
|
4886
4924
|
return f.params.every((p, i) => {
|
|
4887
4925
|
if (argTypes[i] === void 0) return p.optional === true;
|
|
4888
|
-
return isAssignable(argTypes[i],
|
|
4926
|
+
return isAssignable(argTypes[i], params[i]);
|
|
4889
4927
|
});
|
|
4890
4928
|
}
|
|
4929
|
+
/** A signature's parameter types as a call site sees them before inference:
|
|
4930
|
+
* each type parameter replaced by its constraint, or by `any` when it has
|
|
4931
|
+
* none — or when the constraint mentions another type parameter, which a
|
|
4932
|
+
* lone argument cannot be checked against without false errors. */
|
|
4933
|
+
boundParams(f) {
|
|
4934
|
+
if (!f.typeParams?.length) return f.params.map((p) => p.type);
|
|
4935
|
+
const bounds = new Map(f.typeParams.map((name) => [name, anyType]));
|
|
4936
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
4937
|
+
const walk = (value) => {
|
|
4938
|
+
if (!value || typeof value !== "object" || seen.has(value)) return;
|
|
4939
|
+
seen.add(value);
|
|
4940
|
+
if (value instanceof Map) {
|
|
4941
|
+
value.forEach(walk);
|
|
4942
|
+
return;
|
|
4943
|
+
}
|
|
4944
|
+
const t = value;
|
|
4945
|
+
if (t.kind === "typeParam" && typeof t.name === "string" && bounds.has(t.name) && t.constraint && !containsTypeParam(t.constraint)) {
|
|
4946
|
+
bounds.set(t.name, this.reduceType(t.constraint));
|
|
4947
|
+
}
|
|
4948
|
+
for (const child of Object.values(value)) walk(child);
|
|
4949
|
+
};
|
|
4950
|
+
for (const p of f.params) walk(p.type);
|
|
4951
|
+
return f.params.map((p) => substitute(p.type, bounds));
|
|
4952
|
+
}
|
|
4953
|
+
/** Record what each written argument is expected to be — see
|
|
4954
|
+
* `TypeAnalysis.expectedTypeOf`. */
|
|
4955
|
+
recordExpected(written, fns, selfOf) {
|
|
4956
|
+
written.forEach((arg, j) => {
|
|
4957
|
+
const candidates = [];
|
|
4958
|
+
for (const f of fns) {
|
|
4959
|
+
const i = j + selfOf(f);
|
|
4960
|
+
const param = i < f.params.length ? this.boundParams(f)[i] : f.varargs;
|
|
4961
|
+
if (param) candidates.push(param);
|
|
4962
|
+
}
|
|
4963
|
+
if (candidates.length) this.expectedTypeOf.set(arg, union(candidates));
|
|
4964
|
+
});
|
|
4965
|
+
}
|
|
4966
|
+
/** No signature accepts the call, and the argument count is not the
|
|
4967
|
+
* problem: say which argument is wrong, the way TypeScript does. */
|
|
4968
|
+
reportArguments(call, written, fns, argsFor, selfOf) {
|
|
4969
|
+
if (!this.emitDiagnostics) return;
|
|
4970
|
+
if (fns.length > 1) {
|
|
4971
|
+
this.diagnostics.push({ node: call, message: "No overload matches this call" });
|
|
4972
|
+
return;
|
|
4973
|
+
}
|
|
4974
|
+
const f = fns[0];
|
|
4975
|
+
const args = argsFor(f);
|
|
4976
|
+
const params = this.boundParams(f);
|
|
4977
|
+
const self = selfOf(f);
|
|
4978
|
+
for (let i = 0; i < f.params.length; i++) {
|
|
4979
|
+
const arg = args[i];
|
|
4980
|
+
if (arg === void 0 || isAssignable(arg, params[i])) continue;
|
|
4981
|
+
this.diagnostics.push({
|
|
4982
|
+
node: written[i - self] ?? call,
|
|
4983
|
+
message: `Argument of type '${formatType(arg)}' is not assignable to parameter of type '${briefType(params[i])}'`
|
|
4984
|
+
});
|
|
4985
|
+
return;
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4891
4988
|
/** A required parameter may not follow an optional one — otherwise the
|
|
4892
4989
|
* optional one could never actually be omitted. Same rule as TypeScript,
|
|
4893
4990
|
* and it applies to a default (`a = 1`) as much as to a `?`. */
|
|
@@ -4917,22 +5014,25 @@ var TypeAnalyzer = class {
|
|
|
4917
5014
|
return { min, max: f.varargs ? void 0 : f.params.length };
|
|
4918
5015
|
}
|
|
4919
5016
|
/** Report a call that passes too few or too many arguments. Only fires
|
|
4920
|
-
* when *no* overload accepts the
|
|
4921
|
-
* once, against its first signature.
|
|
5017
|
+
* when *no* overload accepts the count, so an overload set still reports
|
|
5018
|
+
* once, against its first signature. Returns whether the count fits, so
|
|
5019
|
+
* an argument's type is only complained about when its count is right. */
|
|
4922
5020
|
checkArity(node, fns, argCount, selfArgs) {
|
|
4923
|
-
if (!
|
|
5021
|
+
if (!fns.length) return true;
|
|
4924
5022
|
const fits = fns.some((f) => {
|
|
4925
5023
|
const { min: min2, max: max2 } = this.arityOf(f);
|
|
4926
5024
|
const n = argCount + selfArgs;
|
|
4927
5025
|
return n >= min2 && (max2 === void 0 || n <= max2);
|
|
4928
5026
|
});
|
|
4929
|
-
if (fits) return;
|
|
5027
|
+
if (fits) return true;
|
|
5028
|
+
if (!this.emitDiagnostics) return false;
|
|
4930
5029
|
const { min, max } = this.arityOf(fns[0]);
|
|
4931
5030
|
const need = max === void 0 ? `at least ${min - selfArgs}` : min === max ? `${min - selfArgs}` : `${min - selfArgs}-${max - selfArgs}`;
|
|
4932
5031
|
this.diagnostics.push({
|
|
4933
5032
|
node,
|
|
4934
5033
|
message: `Expected ${need} argument${need === "1" ? "" : "s"}, got ${argCount}`
|
|
4935
5034
|
});
|
|
5035
|
+
return false;
|
|
4936
5036
|
}
|
|
4937
5037
|
signatureToFnType(sig) {
|
|
4938
5038
|
const names = sig.generics.map((g) => g.name);
|
|
@@ -5370,11 +5470,13 @@ var TypeAnalyzer = class {
|
|
|
5370
5470
|
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5371
5471
|
const fns = this.overloadsOf(callee);
|
|
5372
5472
|
if (fns.length) {
|
|
5373
|
-
this.
|
|
5473
|
+
this.recordExpected(expr.arguments, fns, () => 0);
|
|
5474
|
+
const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
|
|
5374
5475
|
const picked = this.pickOverload(fns, argTypes);
|
|
5375
5476
|
if (picked) {
|
|
5376
5477
|
return this.callReturn(picked, this.constArgs(picked, expr.arguments, argTypes, env));
|
|
5377
5478
|
}
|
|
5479
|
+
if (arityFits) this.reportArguments(expr, expr.arguments, fns, () => argTypes, () => 0);
|
|
5378
5480
|
return union(fns.map((f) => this.callReturn(f, argTypes)));
|
|
5379
5481
|
}
|
|
5380
5482
|
return callee.kind === "any" ? anyType : unknownType;
|
|
@@ -5385,13 +5487,16 @@ var TypeAnalyzer = class {
|
|
|
5385
5487
|
const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
|
|
5386
5488
|
if (fns.length) {
|
|
5387
5489
|
const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
|
|
5388
|
-
|
|
5490
|
+
const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
|
|
5491
|
+
this.recordExpected(expr.arguments, fns, selfOf);
|
|
5492
|
+
const arityFits = this.checkArity(expr, fns, argTypes.length, this.takesSelf(fns[0]) ? 1 : 0);
|
|
5389
5493
|
const picked = this.pickOverload(fns, argTypes, withSelf);
|
|
5390
5494
|
if (picked) {
|
|
5391
5495
|
const self = this.takesSelf(picked) ? 1 : 0;
|
|
5392
5496
|
const written = this.constArgs(picked, expr.arguments, argTypes, env, self);
|
|
5393
5497
|
return this.callReturn(picked, this.takesSelf(picked) ? [objType, ...written] : written);
|
|
5394
5498
|
}
|
|
5499
|
+
if (arityFits) this.reportArguments(expr, expr.arguments, fns, withSelf, selfOf);
|
|
5395
5500
|
return union(fns.map((f) => this.callReturn(f, withSelf(f))));
|
|
5396
5501
|
}
|
|
5397
5502
|
return objType.kind === "any" ? anyType : unknownType;
|
|
@@ -5839,23 +5944,376 @@ function containsTypeQuery(node) {
|
|
|
5839
5944
|
if (node.type === "TypeofTypeNode") return true;
|
|
5840
5945
|
return Object.values(node).some(containsTypeQuery);
|
|
5841
5946
|
}
|
|
5947
|
+
function briefType(t) {
|
|
5948
|
+
if (t.kind === "union" && t.types.length > 8) {
|
|
5949
|
+
const shown = t.types.slice(0, 6).map(formatType).join(" | ");
|
|
5950
|
+
return `${shown} | ... ${t.types.length - 6} more`;
|
|
5951
|
+
}
|
|
5952
|
+
return formatType(t);
|
|
5953
|
+
}
|
|
5842
5954
|
|
|
5843
|
-
// src/
|
|
5955
|
+
// src/project/host.ts
|
|
5844
5956
|
var import_node_fs = require("fs");
|
|
5845
|
-
var
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5957
|
+
var nodeHost = {
|
|
5958
|
+
readFile(path) {
|
|
5959
|
+
try {
|
|
5960
|
+
return (0, import_node_fs.statSync)(path).isFile() ? (0, import_node_fs.readFileSync)(path, "utf8") : void 0;
|
|
5961
|
+
} catch {
|
|
5962
|
+
return void 0;
|
|
5963
|
+
}
|
|
5964
|
+
}
|
|
5965
|
+
};
|
|
5849
5966
|
|
|
5850
|
-
// src/
|
|
5851
|
-
var
|
|
5852
|
-
var
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5967
|
+
// src/project/config.ts
|
|
5968
|
+
var import_node_path = require("path");
|
|
5969
|
+
var CONFIG_FILE_NAMES = ["luaut.config.json", "luaut.config.jsonc"];
|
|
5970
|
+
function findConfig(file, host = nodeHost) {
|
|
5971
|
+
const searched = [];
|
|
5972
|
+
let directory = (0, import_node_path.dirname)((0, import_node_path.resolve)(file));
|
|
5973
|
+
for (; ; ) {
|
|
5974
|
+
const found = [];
|
|
5975
|
+
for (const name of CONFIG_FILE_NAMES) {
|
|
5976
|
+
const path = (0, import_node_path.join)(directory, name);
|
|
5977
|
+
searched.push(path);
|
|
5978
|
+
if (host.readFile(path) !== void 0) found.push(path);
|
|
5979
|
+
}
|
|
5980
|
+
if (found.length > 1) {
|
|
5981
|
+
const message = `Only one luaut config may be in a folder, but both ${CONFIG_FILE_NAMES.join(" and ")} are in ${directory}`;
|
|
5982
|
+
return { searched, problems: found.map((path) => ({ file: path, message, line: 1, column: 1 })) };
|
|
5983
|
+
}
|
|
5984
|
+
if (found.length === 1) {
|
|
5985
|
+
const { config, problems } = loadConfig(found[0], host);
|
|
5986
|
+
return { config, problems, searched };
|
|
5987
|
+
}
|
|
5988
|
+
const parent = (0, import_node_path.dirname)(directory);
|
|
5989
|
+
if (parent === directory) return { searched, problems: [] };
|
|
5990
|
+
directory = parent;
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5993
|
+
var OPTIONS = ["types", "paths", "baseUrl", "sourceMap"];
|
|
5994
|
+
function loadConfig(path, host = nodeHost) {
|
|
5995
|
+
const file = (0, import_node_path.resolve)(path);
|
|
5996
|
+
const source = host.readFile(file);
|
|
5997
|
+
if (source === void 0) return { problems: [{ file, message: "Cannot read the config file" }] };
|
|
5998
|
+
let raw;
|
|
5999
|
+
try {
|
|
6000
|
+
raw = JSON.parse(stripJsonComments(source));
|
|
6001
|
+
} catch (error) {
|
|
6002
|
+
const message = error.message;
|
|
6003
|
+
return { problems: [{ file, message: `Invalid JSON: ${message}`, ...jsonErrorPosition(source, message) }] };
|
|
6004
|
+
}
|
|
6005
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
6006
|
+
return { problems: [{ file, message: "The config must be a JSON object", line: 1, column: 1 }] };
|
|
6007
|
+
}
|
|
6008
|
+
const directory = (0, import_node_path.dirname)(file);
|
|
6009
|
+
const options = raw;
|
|
6010
|
+
const problems = [];
|
|
6011
|
+
const at = (key) => keyPosition(source, key);
|
|
6012
|
+
const problem = (key, message) => {
|
|
6013
|
+
problems.push({ file, message, ...at(key) });
|
|
6014
|
+
};
|
|
6015
|
+
for (const key of Object.keys(options)) {
|
|
6016
|
+
if (!OPTIONS.includes(key)) {
|
|
6017
|
+
problem(key, `Unknown option '${key}'. Options are: ${OPTIONS.join(", ")}`);
|
|
6018
|
+
}
|
|
6019
|
+
}
|
|
6020
|
+
let types = [];
|
|
6021
|
+
if (options.types !== void 0) {
|
|
6022
|
+
if (Array.isArray(options.types) && options.types.every((t) => typeof t === "string")) types = options.types;
|
|
6023
|
+
else problem("types", `'types' must be an array of strings, such as ["luau"]`);
|
|
6024
|
+
}
|
|
6025
|
+
const paths = {};
|
|
6026
|
+
if (options.paths !== void 0) {
|
|
6027
|
+
const value = options.paths;
|
|
6028
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
6029
|
+
for (const [pattern, targets] of Object.entries(value)) {
|
|
6030
|
+
if (Array.isArray(targets) && targets.every((t) => typeof t === "string")) paths[pattern] = targets;
|
|
6031
|
+
else problem(pattern, `'paths' entry '${pattern}' must be an array of strings`);
|
|
6032
|
+
if (pattern.split("*").length > 2) problem(pattern, `'paths' pattern '${pattern}' may contain at most one '*'`);
|
|
6033
|
+
}
|
|
6034
|
+
} else {
|
|
6035
|
+
problem("paths", `'paths' must be an object, such as { "@shared/*": ["src/shared/*"] }`);
|
|
6036
|
+
}
|
|
6037
|
+
}
|
|
6038
|
+
let baseUrl = directory;
|
|
6039
|
+
if (options.baseUrl !== void 0) {
|
|
6040
|
+
if (typeof options.baseUrl === "string") baseUrl = (0, import_node_path.resolve)(directory, options.baseUrl);
|
|
6041
|
+
else problem("baseUrl", "'baseUrl' must be a string");
|
|
6042
|
+
}
|
|
6043
|
+
let sourceMap = null;
|
|
6044
|
+
if (options.sourceMap !== void 0 && options.sourceMap !== null) {
|
|
6045
|
+
if (typeof options.sourceMap === "string") sourceMap = (0, import_node_path.resolve)(directory, options.sourceMap);
|
|
6046
|
+
else problem("sourceMap", "'sourceMap' must be a path string, or null for none");
|
|
6047
|
+
}
|
|
6048
|
+
return { config: { path: file, directory, source, types, paths, baseUrl, sourceMap }, problems };
|
|
6049
|
+
}
|
|
6050
|
+
function stripJsonComments(text) {
|
|
6051
|
+
const out = text.split("");
|
|
6052
|
+
let i = 0;
|
|
6053
|
+
let inString = false;
|
|
6054
|
+
while (i < text.length) {
|
|
6055
|
+
const ch = text[i];
|
|
6056
|
+
if (inString) {
|
|
6057
|
+
if (ch === "\\") i += 2;
|
|
6058
|
+
else {
|
|
6059
|
+
if (ch === '"') inString = false;
|
|
6060
|
+
i++;
|
|
6061
|
+
}
|
|
6062
|
+
continue;
|
|
6063
|
+
}
|
|
6064
|
+
if (ch === '"') {
|
|
6065
|
+
inString = true;
|
|
6066
|
+
i++;
|
|
6067
|
+
} else if (ch === "/" && text[i + 1] === "/") {
|
|
6068
|
+
while (i < text.length && text[i] !== "\n") out[i++] = " ";
|
|
6069
|
+
} else if (ch === "/" && text[i + 1] === "*") {
|
|
6070
|
+
out[i++] = " ";
|
|
6071
|
+
out[i++] = " ";
|
|
6072
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) {
|
|
6073
|
+
if (text[i] !== "\n") out[i] = " ";
|
|
6074
|
+
i++;
|
|
6075
|
+
}
|
|
6076
|
+
if (i < text.length) {
|
|
6077
|
+
out[i++] = " ";
|
|
6078
|
+
out[i++] = " ";
|
|
6079
|
+
}
|
|
6080
|
+
} else if (ch === ",") {
|
|
6081
|
+
let j = i + 1;
|
|
6082
|
+
while (j < text.length && /\s/.test(text[j])) j++;
|
|
6083
|
+
if (text[j] === "}" || text[j] === "]") out[i] = " ";
|
|
6084
|
+
i++;
|
|
6085
|
+
} else {
|
|
6086
|
+
i++;
|
|
6087
|
+
}
|
|
6088
|
+
}
|
|
6089
|
+
return out.join("");
|
|
6090
|
+
}
|
|
6091
|
+
function jsonErrorPosition(source, message) {
|
|
6092
|
+
const lineColumn = /line (\d+) column (\d+)/.exec(message);
|
|
6093
|
+
if (lineColumn) return { line: Number(lineColumn[1]), column: Number(lineColumn[2]) };
|
|
6094
|
+
const position = /position (\d+)/.exec(message);
|
|
6095
|
+
return position ? offsetPosition(source, Number(position[1])) : { line: 1, column: 1 };
|
|
6096
|
+
}
|
|
6097
|
+
function keyPosition(source, key) {
|
|
6098
|
+
const offset = source.indexOf(JSON.stringify(key));
|
|
6099
|
+
return offset < 0 ? { line: 1, column: 1 } : offsetPosition(source, offset);
|
|
6100
|
+
}
|
|
6101
|
+
function offsetPosition(source, offset) {
|
|
6102
|
+
const before = source.slice(0, offset);
|
|
6103
|
+
const line = before.split("\n").length;
|
|
6104
|
+
return { line, column: offset - before.lastIndexOf("\n") };
|
|
6105
|
+
}
|
|
6106
|
+
|
|
6107
|
+
// src/project/libraries.ts
|
|
6108
|
+
var import_node_path2 = require("path");
|
|
6109
|
+
function resolveTypeLibraries(config, host = nodeHost) {
|
|
6110
|
+
const files = [];
|
|
6111
|
+
const problems = [];
|
|
6112
|
+
const loaded = /* @__PURE__ */ new Set();
|
|
6113
|
+
const addFile = (file) => {
|
|
6114
|
+
const key = pathKey(file);
|
|
6115
|
+
if (loaded.has(key)) return;
|
|
6116
|
+
loaded.add(key);
|
|
6117
|
+
files.push(file);
|
|
6118
|
+
};
|
|
6119
|
+
const addPackage = (directory, entryFile, visiting) => {
|
|
6120
|
+
const key = pathKey(directory);
|
|
6121
|
+
if (visiting.has(key)) return;
|
|
6122
|
+
visiting.add(key);
|
|
6123
|
+
for (const dependency of dependencyNames(directory, host)) {
|
|
6124
|
+
const found = findPackage(dependency, directory, host);
|
|
6125
|
+
if (found) addPackage(found.directory, found.file, visiting);
|
|
6126
|
+
}
|
|
6127
|
+
addFile(entryFile);
|
|
6128
|
+
};
|
|
6129
|
+
for (const entry of config.types) {
|
|
6130
|
+
const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);
|
|
6131
|
+
if (relative) {
|
|
6132
|
+
const target = (0, import_node_path2.resolve)(config.directory, entry);
|
|
6133
|
+
if (entry.endsWith(".luaut")) {
|
|
6134
|
+
if (host.readFile(target) !== void 0) addFile(target);
|
|
6135
|
+
else problems.push({ file: config.path, message: `Cannot find type library file '${entry}'`, ...entryPosition(config, entry) });
|
|
6136
|
+
continue;
|
|
6137
|
+
}
|
|
6138
|
+
const file = packageEntry(target, host);
|
|
6139
|
+
if (file) addPackage(target, file, /* @__PURE__ */ new Set());
|
|
6140
|
+
else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'luaut.types' in its package.json)`, ...entryPosition(config, entry) });
|
|
6141
|
+
continue;
|
|
6142
|
+
}
|
|
6143
|
+
const names = entry.startsWith("@") || entry.includes("/") ? [entry] : [`@luaut/${entry}`, entry];
|
|
6144
|
+
const found = names.map((name) => findPackage(name, config.directory, host)).find(Boolean);
|
|
6145
|
+
if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
|
|
6146
|
+
else {
|
|
6147
|
+
problems.push({
|
|
6148
|
+
file: config.path,
|
|
6149
|
+
message: `Cannot find type library '${entry}'. Install it with: npm i -D ${names[0]}`,
|
|
6150
|
+
...entryPosition(config, entry)
|
|
6151
|
+
});
|
|
6152
|
+
}
|
|
6153
|
+
}
|
|
6154
|
+
return { files, problems };
|
|
6155
|
+
}
|
|
6156
|
+
var ENTRY_FILE = "index.d.luaut";
|
|
6157
|
+
function packageEntry(directory, host) {
|
|
6158
|
+
const manifest = readJson((0, import_node_path2.join)(directory, "package.json"), host);
|
|
6159
|
+
const declared = manifest?.luaut?.types;
|
|
6160
|
+
const file = (0, import_node_path2.resolve)(directory, typeof declared === "string" ? declared : ENTRY_FILE);
|
|
6161
|
+
return host.readFile(file) !== void 0 ? file : void 0;
|
|
6162
|
+
}
|
|
6163
|
+
function findPackage(name, from, host) {
|
|
6164
|
+
let directory = (0, import_node_path2.resolve)(from);
|
|
6165
|
+
for (; ; ) {
|
|
6166
|
+
const candidate = (0, import_node_path2.join)(directory, "node_modules", ...name.split("/"));
|
|
6167
|
+
const file = packageEntry(candidate, host);
|
|
6168
|
+
if (file) return { directory: candidate, file };
|
|
6169
|
+
const parent = (0, import_node_path2.dirname)(directory);
|
|
6170
|
+
if (parent === directory) return void 0;
|
|
6171
|
+
directory = parent;
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
function dependencyNames(directory, host) {
|
|
6175
|
+
const manifest = readJson((0, import_node_path2.join)(directory, "package.json"), host);
|
|
6176
|
+
const names = /* @__PURE__ */ new Set();
|
|
6177
|
+
for (const field of ["dependencies", "peerDependencies"]) {
|
|
6178
|
+
const deps = manifest?.[field];
|
|
6179
|
+
if (deps && typeof deps === "object") for (const name of Object.keys(deps)) names.add(name);
|
|
6180
|
+
}
|
|
6181
|
+
return [...names];
|
|
6182
|
+
}
|
|
6183
|
+
function readJson(path, host) {
|
|
6184
|
+
const text = host.readFile(path);
|
|
6185
|
+
if (text === void 0) return void 0;
|
|
6186
|
+
try {
|
|
6187
|
+
const value = JSON.parse(text);
|
|
6188
|
+
return value && typeof value === "object" ? value : void 0;
|
|
6189
|
+
} catch {
|
|
6190
|
+
return void 0;
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
function entryPosition(config, entry) {
|
|
6194
|
+
return keyPosition(config.source, entry);
|
|
6195
|
+
}
|
|
6196
|
+
function pathKey(path) {
|
|
6197
|
+
const normalized = (0, import_node_path2.resolve)(path);
|
|
6198
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
6199
|
+
}
|
|
6200
|
+
|
|
6201
|
+
// src/project/modules.ts
|
|
6202
|
+
var import_node_path3 = require("path");
|
|
6203
|
+
function moduleCandidates(fromFile, specifier, config) {
|
|
6204
|
+
const bases = specifier.startsWith("./") || specifier.startsWith("../") ? [(0, import_node_path3.resolve)((0, import_node_path3.dirname)(fromFile), specifier)] : config ? aliasTargets(config, specifier) : [];
|
|
6205
|
+
return bases.flatMap((base) => base.endsWith(".luaut") ? [base] : [`${base}.luaut`, `${base}.d.luaut`, (0, import_node_path3.join)(base, "index.luaut")]);
|
|
6206
|
+
}
|
|
6207
|
+
function resolveModulePath(fromFile, specifier, config, host = nodeHost) {
|
|
6208
|
+
return moduleCandidates(fromFile, specifier, config).find((path) => host.readFile(path) !== void 0);
|
|
6209
|
+
}
|
|
6210
|
+
function aliasTargets(config, specifier) {
|
|
6211
|
+
let match;
|
|
6212
|
+
let prefixLength = -1;
|
|
6213
|
+
for (const pattern2 of Object.keys(config.paths)) {
|
|
6214
|
+
const star = pattern2.indexOf("*");
|
|
6215
|
+
if (star < 0) {
|
|
6216
|
+
if (pattern2 === specifier) {
|
|
6217
|
+
match = { pattern: pattern2, wildcard: "" };
|
|
6218
|
+
break;
|
|
6219
|
+
}
|
|
6220
|
+
continue;
|
|
6221
|
+
}
|
|
6222
|
+
const prefix = pattern2.slice(0, star);
|
|
6223
|
+
const suffix = pattern2.slice(star + 1);
|
|
6224
|
+
const fits = specifier.length >= prefix.length + suffix.length && specifier.startsWith(prefix) && specifier.endsWith(suffix);
|
|
6225
|
+
if (fits && prefix.length > prefixLength) {
|
|
6226
|
+
prefixLength = prefix.length;
|
|
6227
|
+
match = { pattern: pattern2, wildcard: specifier.slice(prefix.length, specifier.length - suffix.length) };
|
|
6228
|
+
}
|
|
6229
|
+
}
|
|
6230
|
+
if (!match) return [];
|
|
6231
|
+
const { pattern, wildcard } = match;
|
|
6232
|
+
return config.paths[pattern].map((target) => (0, import_node_path3.resolve)(config.baseUrl, target.replace("*", wildcard)));
|
|
6233
|
+
}
|
|
6234
|
+
|
|
6235
|
+
// src/project/sourcemap.ts
|
|
6236
|
+
var import_node_path4 = require("path");
|
|
6237
|
+
var IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
6238
|
+
var INSTANCE_MEMBERS = /* @__PURE__ */ new Set(["Name", "ClassName", "Parent", "Archivable"]);
|
|
6239
|
+
var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".luaut", ".luau", ".lua"]);
|
|
6240
|
+
function sourceMapTypes(text, path, options) {
|
|
6241
|
+
let root;
|
|
6242
|
+
try {
|
|
6243
|
+
root = JSON.parse(text);
|
|
6244
|
+
} catch (error) {
|
|
6245
|
+
return { problem: `Invalid sourcemap: ${error.message}` };
|
|
6246
|
+
}
|
|
6247
|
+
if (!isNode(root)) return { problem: "Invalid sourcemap: the root must be an object with 'name' and 'className'" };
|
|
6248
|
+
const directory = (0, import_node_path4.dirname)((0, import_node_path4.resolve)(path));
|
|
6249
|
+
const lines = [];
|
|
6250
|
+
const aliasOfFile = /* @__PURE__ */ new Map();
|
|
6251
|
+
const used = /* @__PURE__ */ new Set();
|
|
6252
|
+
const canOmit = options.classes.has("Omit");
|
|
6253
|
+
const aliasOfNode = /* @__PURE__ */ new Map();
|
|
6254
|
+
const aliasFor = (segments) => {
|
|
6255
|
+
const base = `SourceMap_${segments.map((s) => s.replace(/[^A-Za-z0-9_]/g, "_")).join("_")}`;
|
|
6256
|
+
let alias = base;
|
|
6257
|
+
for (let n = 2; used.has(alias); n++) alias = `${base}_${n}`;
|
|
6258
|
+
used.add(alias);
|
|
6259
|
+
return alias;
|
|
6260
|
+
};
|
|
6261
|
+
const visit = (node, segments, parent) => {
|
|
6262
|
+
const alias = aliasFor(segments);
|
|
6263
|
+
aliasOfNode.set(node, alias);
|
|
6264
|
+
for (const filePath of node.filePaths ?? []) aliasOfFile.set(fileKey((0, import_node_path4.resolve)(directory, filePath)), alias);
|
|
6265
|
+
const className = IDENTIFIER.test(node.className) && options.classes.has(node.className) ? node.className : "Instance";
|
|
6266
|
+
const taken = options.membersOf?.(className) ?? INSTANCE_MEMBERS;
|
|
6267
|
+
const members = [];
|
|
6268
|
+
if (parent && canOmit) members.push(`Parent: ${parent}`);
|
|
6269
|
+
const named = /* @__PURE__ */ new Set();
|
|
6270
|
+
for (const child of node.children ?? []) {
|
|
6271
|
+
if (!isNode(child)) continue;
|
|
6272
|
+
const childAlias = visit(child, [...segments, child.name], alias);
|
|
6273
|
+
if (!IDENTIFIER.test(child.name) || taken.has(child.name) || named.has(child.name)) continue;
|
|
6274
|
+
named.add(child.name);
|
|
6275
|
+
members.push(`${child.name}: ${childAlias}`);
|
|
6276
|
+
}
|
|
6277
|
+
const base = parent && canOmit ? `Omit<${className}, "Parent">` : className;
|
|
6278
|
+
lines.push(`type ${alias} = ${members.length ? `${base} & { ${members.join(", ")} }` : base}`);
|
|
6279
|
+
return alias;
|
|
6280
|
+
};
|
|
6281
|
+
const rootAlias = visit(root, [root.name], void 0);
|
|
6282
|
+
if (root.className === "DataModel") {
|
|
6283
|
+
lines.push(`declare game: ${rootAlias}`);
|
|
6284
|
+
const workspace = (root.children ?? []).find((child) => isNode(child) && child.className === "Workspace");
|
|
6285
|
+
const workspaceAlias = workspace && aliasOfNode.get(workspace);
|
|
6286
|
+
if (workspaceAlias) lines.push(`declare workspace: ${workspaceAlias}`);
|
|
6287
|
+
}
|
|
6288
|
+
let program;
|
|
6289
|
+
try {
|
|
6290
|
+
program = parse(lines.join("\n"));
|
|
6291
|
+
} catch (error) {
|
|
6292
|
+
return { problem: `Could not turn the sourcemap into types: ${error.message}` };
|
|
6293
|
+
}
|
|
6294
|
+
return {
|
|
6295
|
+
types: {
|
|
6296
|
+
program,
|
|
6297
|
+
scriptFor(file) {
|
|
6298
|
+
const alias = aliasOfFile.get(fileKey(file));
|
|
6299
|
+
return alias ? parse(`declare script: ${alias}`) : void 0;
|
|
6300
|
+
}
|
|
6301
|
+
}
|
|
6302
|
+
};
|
|
6303
|
+
}
|
|
6304
|
+
function isNode(value) {
|
|
6305
|
+
if (!value || typeof value !== "object") return false;
|
|
6306
|
+
const node = value;
|
|
6307
|
+
return typeof node.name === "string" && typeof node.className === "string";
|
|
6308
|
+
}
|
|
6309
|
+
function fileKey(path) {
|
|
6310
|
+
const extension = (0, import_node_path4.extname)(path);
|
|
6311
|
+
const bare = SCRIPT_EXTENSIONS.has(extension) ? path.slice(0, -extension.length) : path;
|
|
6312
|
+
const normalized = (0, import_node_path4.resolve)(bare);
|
|
6313
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
6314
|
+
}
|
|
5856
6315
|
|
|
5857
6316
|
// src/index.ts
|
|
5858
|
-
var defaultLibs = [luauLib, robloxLib];
|
|
5859
6317
|
var luautparser = {
|
|
5860
6318
|
tokenize,
|
|
5861
6319
|
parseTokens,
|
|
@@ -5872,6 +6330,7 @@ var index_default = luautparser;
|
|
|
5872
6330
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5873
6331
|
0 && (module.exports = {
|
|
5874
6332
|
BinaryOperators,
|
|
6333
|
+
CONFIG_FILE_NAMES,
|
|
5875
6334
|
Keywords,
|
|
5876
6335
|
LexError,
|
|
5877
6336
|
Operators,
|
|
@@ -5885,10 +6344,10 @@ var index_default = luautparser;
|
|
|
5885
6344
|
booleanType,
|
|
5886
6345
|
bufferType,
|
|
5887
6346
|
containsTypeParam,
|
|
5888
|
-
defaultLibs,
|
|
5889
6347
|
difference,
|
|
5890
6348
|
equalTypes,
|
|
5891
6349
|
falsyType,
|
|
6350
|
+
findConfig,
|
|
5892
6351
|
fn,
|
|
5893
6352
|
formatType,
|
|
5894
6353
|
getBinding,
|
|
@@ -5899,11 +6358,10 @@ var index_default = luautparser;
|
|
|
5899
6358
|
isPossiblyTruthy,
|
|
5900
6359
|
isUnassignedGlobal,
|
|
5901
6360
|
literal,
|
|
5902
|
-
|
|
5903
|
-
luauDefsPath,
|
|
5904
|
-
luauLib,
|
|
6361
|
+
loadConfig,
|
|
5905
6362
|
luautparser,
|
|
5906
6363
|
matchInfer,
|
|
6364
|
+
moduleCandidates,
|
|
5907
6365
|
moduleExports,
|
|
5908
6366
|
narrowExclude,
|
|
5909
6367
|
narrowFalsy,
|
|
@@ -5911,6 +6369,7 @@ var index_default = luautparser;
|
|
|
5911
6369
|
narrowTruthy,
|
|
5912
6370
|
neverType,
|
|
5913
6371
|
nilType,
|
|
6372
|
+
nodeHost,
|
|
5914
6373
|
numberType,
|
|
5915
6374
|
objectType,
|
|
5916
6375
|
optional,
|
|
@@ -5920,11 +6379,12 @@ var index_default = luautparser;
|
|
|
5920
6379
|
parseTokens,
|
|
5921
6380
|
parseWithRecovery,
|
|
5922
6381
|
primitive,
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
robloxLib,
|
|
6382
|
+
resolveModulePath,
|
|
6383
|
+
resolveTypeLibraries,
|
|
5926
6384
|
setAliasExpander,
|
|
6385
|
+
sourceMapTypes,
|
|
5927
6386
|
stringType,
|
|
6387
|
+
stripJsonComments,
|
|
5928
6388
|
substitute,
|
|
5929
6389
|
templateMatches,
|
|
5930
6390
|
threadType,
|