@thigasdevelopment/luam 0.19.1 → 0.19.3
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 +4 -1
- package/lua/class.lua +47 -9
- package/lua/validate.lua +239 -0
- package/luam.mjs +2574 -367
- package/package.json +1 -1
package/luam.mjs
CHANGED
|
@@ -1644,8 +1644,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1644
1644
|
* @param {boolean} [combine] - if `true` or omitted, an optional value can be specified directly after the flag.
|
|
1645
1645
|
* @return {Command} `this` command for chaining
|
|
1646
1646
|
*/
|
|
1647
|
-
combineFlagAndOptionalValue(
|
|
1648
|
-
this._combineFlagAndOptionalValue = !!
|
|
1647
|
+
combineFlagAndOptionalValue(combine2 = true) {
|
|
1648
|
+
this._combineFlagAndOptionalValue = !!combine2;
|
|
1649
1649
|
return this;
|
|
1650
1650
|
}
|
|
1651
1651
|
/**
|
|
@@ -3143,6 +3143,7 @@ var DEFAULT_ENVIRONMENT_FILE = ".env";
|
|
|
3143
3143
|
var DEFAULT_LOCAL_ENVIRONMENT_FILE = ".env.local";
|
|
3144
3144
|
var DEFAULT_ASSET_DESTINATION = ".";
|
|
3145
3145
|
var DEFAULT_OUT_DIR = "build";
|
|
3146
|
+
var DEFAULT_CONTRACTS_DIR = ".luam/contracts";
|
|
3146
3147
|
var DEFAULT_RESOURCES_DIR = "mods/deathmatch/resources";
|
|
3147
3148
|
var DEFAULT_COMPILER_OPTIONS = {
|
|
3148
3149
|
strict: true,
|
|
@@ -3252,7 +3253,7 @@ var EXIT_USAGE = 2;
|
|
|
3252
3253
|
// src/config/manifest-context.ts
|
|
3253
3254
|
var DEVELOPMENT_MODE = "development";
|
|
3254
3255
|
var PRODUCTION_MODE = "production";
|
|
3255
|
-
var DEVELOPMENT_COMMANDS = ["dev", "ensure", "server"];
|
|
3256
|
+
var DEVELOPMENT_COMMANDS = ["dev", "ensure", "server", "test"];
|
|
3256
3257
|
var PRODUCTION_COMMANDS = ["build"];
|
|
3257
3258
|
function manifestMode(command) {
|
|
3258
3259
|
if (DEVELOPMENT_COMMANDS.includes(command)) {
|
|
@@ -3492,14 +3493,14 @@ function readEscape(cursor) {
|
|
|
3492
3493
|
}
|
|
3493
3494
|
function scanQuotedString(cursor, diagnostics) {
|
|
3494
3495
|
const position2 = cursor.position();
|
|
3495
|
-
const
|
|
3496
|
+
const quote3 = cursor.peek();
|
|
3496
3497
|
cursor.advance();
|
|
3497
3498
|
let value = "";
|
|
3498
|
-
while (!cursor.isEof() && cursor.peek() !==
|
|
3499
|
+
while (!cursor.isEof() && cursor.peek() !== quote3 && !isLineBreak(cursor.peek())) {
|
|
3499
3500
|
value += cursor.peek() === "\\" ? readEscape(cursor) : cursor.advance();
|
|
3500
3501
|
}
|
|
3501
|
-
if (cursor.peek() !==
|
|
3502
|
-
const message = `Unterminated string. Close it with ${
|
|
3502
|
+
if (cursor.peek() !== quote3) {
|
|
3503
|
+
const message = `Unterminated string. Close it with ${quote3}.`;
|
|
3503
3504
|
diagnostics.push(createDiagnostic("lexer", "lex-unterminated-string", message, position2));
|
|
3504
3505
|
return value;
|
|
3505
3506
|
}
|
|
@@ -3827,19 +3828,21 @@ function parseTypeName(stream) {
|
|
|
3827
3828
|
return { kind: "type-name", name: token.value, typeArguments, position: token.position };
|
|
3828
3829
|
}
|
|
3829
3830
|
function parseParameterType(stream) {
|
|
3831
|
+
let name = null;
|
|
3830
3832
|
if (stream.check("identifier") && stream.checkAhead(1, "punctuation", ":")) {
|
|
3831
|
-
stream.next();
|
|
3833
|
+
name = stream.next().value;
|
|
3832
3834
|
stream.next();
|
|
3833
3835
|
}
|
|
3834
|
-
return parseTypeAnnotation(stream);
|
|
3836
|
+
return { name, annotation: parseTypeAnnotation(stream) };
|
|
3835
3837
|
}
|
|
3836
3838
|
function parseFunctionType(stream) {
|
|
3837
3839
|
const position2 = stream.expect("identifier", FUNCTION_TYPE).position;
|
|
3838
3840
|
if (!stream.check("punctuation", "(")) {
|
|
3839
|
-
return { kind: "type-function", parameters: [], isVariadic: true, returnType: null, position: position2 };
|
|
3841
|
+
return { kind: "type-function", parameters: [], parameterNames: [], isVariadic: true, returnType: null, position: position2 };
|
|
3840
3842
|
}
|
|
3841
3843
|
stream.next();
|
|
3842
3844
|
const parameters = [];
|
|
3845
|
+
const parameterNames = [];
|
|
3843
3846
|
let isVariadic = false;
|
|
3844
3847
|
while (!stream.check("punctuation", ")") && !stream.isEof()) {
|
|
3845
3848
|
if (stream.match("operator", "...")) {
|
|
@@ -3848,7 +3851,9 @@ function parseFunctionType(stream) {
|
|
|
3848
3851
|
parseTypeAnnotation(stream);
|
|
3849
3852
|
}
|
|
3850
3853
|
} else {
|
|
3851
|
-
|
|
3854
|
+
const parameter = parseParameterType(stream);
|
|
3855
|
+
parameters.push(parameter.annotation);
|
|
3856
|
+
parameterNames.push(parameter.name);
|
|
3852
3857
|
}
|
|
3853
3858
|
if (!stream.match("punctuation", ",")) {
|
|
3854
3859
|
break;
|
|
@@ -3856,7 +3861,7 @@ function parseFunctionType(stream) {
|
|
|
3856
3861
|
}
|
|
3857
3862
|
stream.expect("punctuation", ")");
|
|
3858
3863
|
const returnType = stream.match("punctuation", ":") ? parseTypeAnnotation(stream) : null;
|
|
3859
|
-
return { kind: "type-function", parameters, isVariadic, returnType, position: position2 };
|
|
3864
|
+
return { kind: "type-function", parameters, parameterNames, isVariadic, returnType, position: position2 };
|
|
3860
3865
|
}
|
|
3861
3866
|
function parseGroupedType(stream) {
|
|
3862
3867
|
const position2 = stream.expect("punctuation", "(").position;
|
|
@@ -3989,10 +3994,18 @@ function parseNamedAnnotation(stream, declaration) {
|
|
|
3989
3994
|
if (optional === null && annotation?.kind === "type-optional") {
|
|
3990
3995
|
stream.report("parse-optional-position", `Write optional ${label2} as "name?: Type", not "name: Type?".`, annotation.position);
|
|
3991
3996
|
}
|
|
3997
|
+
if (optional !== null && annotation?.kind === "type-optional") {
|
|
3998
|
+
const message = `The marker on the name already allows "nil". Remove the "?" after the type.`;
|
|
3999
|
+
stream.report("parse-redundant-optional", message, annotation.position);
|
|
4000
|
+
}
|
|
3992
4001
|
if (optional !== null && annotation !== null) {
|
|
3993
4002
|
stream.eraseFrom(checkpoint);
|
|
3994
4003
|
}
|
|
3995
|
-
|
|
4004
|
+
if (optional === null || annotation === null) {
|
|
4005
|
+
return annotation;
|
|
4006
|
+
}
|
|
4007
|
+
const element = annotation.kind === "type-optional" ? annotation.element : annotation;
|
|
4008
|
+
return { kind: "type-optional", element, position: optional.position };
|
|
3996
4009
|
}
|
|
3997
4010
|
|
|
3998
4011
|
// ../compiler/src/parser/function-expression.ts
|
|
@@ -4146,7 +4159,8 @@ function parsePrimary(stream) {
|
|
|
4146
4159
|
if (token.kind === "keyword" && token.value === "new" && stream.checkAhead(1, "identifier")) {
|
|
4147
4160
|
stream.next();
|
|
4148
4161
|
const className = stream.next().value;
|
|
4149
|
-
|
|
4162
|
+
const typeArguments = parseNewTypeArguments(stream);
|
|
4163
|
+
return { kind: "new-expression", className, typeArguments, args: parseArguments(stream), position: token.position };
|
|
4150
4164
|
}
|
|
4151
4165
|
if (token.kind === "keyword" && CALLABLE_KEYWORDS.has(token.value) && stream.checkAhead(1, "punctuation", "(")) {
|
|
4152
4166
|
stream.next();
|
|
@@ -4178,6 +4192,20 @@ function parsePrimary(stream) {
|
|
|
4178
4192
|
}
|
|
4179
4193
|
throw stream.error(`Unexpected "${stream.describeCurrent()}" in expression.`, "parse-unexpected-token");
|
|
4180
4194
|
}
|
|
4195
|
+
function parseNewTypeArguments(stream) {
|
|
4196
|
+
const args = [];
|
|
4197
|
+
if (!stream.check("operator", "<")) {
|
|
4198
|
+
return args;
|
|
4199
|
+
}
|
|
4200
|
+
const checkpoint = stream.checkpoint();
|
|
4201
|
+
stream.next();
|
|
4202
|
+
do {
|
|
4203
|
+
args.push(parseTypeAnnotation(stream));
|
|
4204
|
+
} while (stream.match("punctuation", ","));
|
|
4205
|
+
stream.expect("operator", ">");
|
|
4206
|
+
stream.eraseFrom(checkpoint);
|
|
4207
|
+
return args;
|
|
4208
|
+
}
|
|
4181
4209
|
function parseArguments(stream) {
|
|
4182
4210
|
if (stream.check("string") || stream.check("template") || stream.check("punctuation", "{")) {
|
|
4183
4211
|
return [parsePrimary(stream)];
|
|
@@ -4464,8 +4492,23 @@ var TokenStream = class {
|
|
|
4464
4492
|
// ../compiler/src/parser/declarations.ts
|
|
4465
4493
|
var DECLARATION_NAMES = /* @__PURE__ */ new Set(["class", "interface", "enum"]);
|
|
4466
4494
|
var CLASS_MODIFIERS = /* @__PURE__ */ new Set(["extends", "implements"]);
|
|
4495
|
+
function skipTypeParameters(stream, offset) {
|
|
4496
|
+
if (!stream.checkAhead(offset, "operator", "<")) {
|
|
4497
|
+
return offset;
|
|
4498
|
+
}
|
|
4499
|
+
let depth = 0;
|
|
4500
|
+
let index = offset;
|
|
4501
|
+
do {
|
|
4502
|
+
const token = stream.peek(index);
|
|
4503
|
+
depth += token.kind === "operator" && token.value === "<" ? 1 : 0;
|
|
4504
|
+
depth -= token.kind === "operator" && token.value === ">" ? 1 : 0;
|
|
4505
|
+
index += 1;
|
|
4506
|
+
} while (depth > 0 && stream.peek(index).kind !== "eof");
|
|
4507
|
+
return index;
|
|
4508
|
+
}
|
|
4467
4509
|
function isClassHeader(stream, offset = 0) {
|
|
4468
|
-
|
|
4510
|
+
const after = skipTypeParameters(stream, offset + 2);
|
|
4511
|
+
return stream.checkAhead(after, "punctuation", "{") || stream.checkAhead(after, "keyword", "extends") || stream.checkAhead(after, "keyword", "implements");
|
|
4469
4512
|
}
|
|
4470
4513
|
function isDeclarationStart(stream) {
|
|
4471
4514
|
let offset = 0;
|
|
@@ -4595,10 +4638,40 @@ function parseClassMember(stream) {
|
|
|
4595
4638
|
expectClassFieldBoundary(stream, token);
|
|
4596
4639
|
return { kind: "class-field", name: token.value, annotation, value, decorators, isStatic, position: token.position };
|
|
4597
4640
|
}
|
|
4641
|
+
function parseTypeParameters(stream) {
|
|
4642
|
+
const list = { names: [], constraints: [] };
|
|
4643
|
+
if (!stream.check("operator", "<")) {
|
|
4644
|
+
return list;
|
|
4645
|
+
}
|
|
4646
|
+
const checkpoint = stream.checkpoint();
|
|
4647
|
+
stream.next();
|
|
4648
|
+
do {
|
|
4649
|
+
list.names.push(stream.expect("identifier").value);
|
|
4650
|
+
list.constraints.push(stream.match("keyword", "extends") ? parseTypeAnnotation(stream) : null);
|
|
4651
|
+
} while (stream.match("punctuation", ","));
|
|
4652
|
+
stream.expect("operator", ">");
|
|
4653
|
+
stream.eraseFrom(checkpoint);
|
|
4654
|
+
return list;
|
|
4655
|
+
}
|
|
4656
|
+
function parseTypeArguments(stream) {
|
|
4657
|
+
const args = [];
|
|
4658
|
+
if (!stream.check("operator", "<")) {
|
|
4659
|
+
return args;
|
|
4660
|
+
}
|
|
4661
|
+
const checkpoint = stream.checkpoint();
|
|
4662
|
+
stream.next();
|
|
4663
|
+
do {
|
|
4664
|
+
args.push(parseTypeAnnotation(stream));
|
|
4665
|
+
} while (stream.match("punctuation", ","));
|
|
4666
|
+
stream.expect("operator", ">");
|
|
4667
|
+
stream.eraseFrom(checkpoint);
|
|
4668
|
+
return args;
|
|
4669
|
+
}
|
|
4598
4670
|
function parseClassModifiers(stream, declaration) {
|
|
4599
4671
|
while (stream.check("keyword") && CLASS_MODIFIERS.has(stream.current().value)) {
|
|
4600
4672
|
if (stream.next().value === "extends") {
|
|
4601
4673
|
declaration.superClass = stream.expect("identifier").value;
|
|
4674
|
+
declaration.superClassArguments = parseTypeArguments(stream);
|
|
4602
4675
|
continue;
|
|
4603
4676
|
}
|
|
4604
4677
|
do {
|
|
@@ -4609,7 +4682,19 @@ function parseClassModifiers(stream, declaration) {
|
|
|
4609
4682
|
function parseClassDeclaration(stream, decorators) {
|
|
4610
4683
|
const position2 = stream.next().position;
|
|
4611
4684
|
const name = stream.expect("identifier").value;
|
|
4612
|
-
const
|
|
4685
|
+
const typeParameters = parseTypeParameters(stream);
|
|
4686
|
+
const declaration = {
|
|
4687
|
+
kind: "class-declaration",
|
|
4688
|
+
name,
|
|
4689
|
+
typeParameters: typeParameters.names,
|
|
4690
|
+
typeConstraints: typeParameters.constraints,
|
|
4691
|
+
superClass: null,
|
|
4692
|
+
superClassArguments: [],
|
|
4693
|
+
interfaces: [],
|
|
4694
|
+
members: [],
|
|
4695
|
+
decorators,
|
|
4696
|
+
position: position2
|
|
4697
|
+
};
|
|
4613
4698
|
parseClassModifiers(stream, declaration);
|
|
4614
4699
|
stream.expect("punctuation", "{");
|
|
4615
4700
|
while (!stream.check("punctuation", "}") && !stream.isEof()) {
|
|
@@ -5069,8 +5154,8 @@ var TABLE_TYPE = { kind: "table" };
|
|
|
5069
5154
|
function createArray(element) {
|
|
5070
5155
|
return { kind: "array", element };
|
|
5071
5156
|
}
|
|
5072
|
-
function createNamed(name) {
|
|
5073
|
-
return { kind: "named", name };
|
|
5157
|
+
function createNamed(name, typeArguments = []) {
|
|
5158
|
+
return typeArguments.length === 0 ? { kind: "named", name } : { kind: "named", name, typeArguments };
|
|
5074
5159
|
}
|
|
5075
5160
|
function createMap(key, value) {
|
|
5076
5161
|
return { kind: "map", key, value };
|
|
@@ -5183,6 +5268,12 @@ function createUnion(options) {
|
|
|
5183
5268
|
}
|
|
5184
5269
|
return { kind: "union", options: flattened };
|
|
5185
5270
|
}
|
|
5271
|
+
function namedNesting(type) {
|
|
5272
|
+
if (type.kind !== "named") {
|
|
5273
|
+
return 0;
|
|
5274
|
+
}
|
|
5275
|
+
return 1 + Math.max(0, ...(type.typeArguments ?? []).map(namedNesting));
|
|
5276
|
+
}
|
|
5186
5277
|
function typeToString(type) {
|
|
5187
5278
|
if (type.kind === "array") {
|
|
5188
5279
|
return `${typeToString(type.element)}[]`;
|
|
@@ -5202,7 +5293,11 @@ function typeToString(type) {
|
|
|
5202
5293
|
if (type.kind === "boolean-literal" || type.kind === "number-literal") {
|
|
5203
5294
|
return String(type.value);
|
|
5204
5295
|
}
|
|
5205
|
-
if (type.kind === "named"
|
|
5296
|
+
if (type.kind === "named") {
|
|
5297
|
+
const args = type.typeArguments ?? [];
|
|
5298
|
+
return args.length === 0 ? type.name : `${type.name}<${args.map(typeToString).join(", ")}>`;
|
|
5299
|
+
}
|
|
5300
|
+
if (type.kind === "record") {
|
|
5206
5301
|
return type.name;
|
|
5207
5302
|
}
|
|
5208
5303
|
if (type.kind === "tuple") {
|
|
@@ -5292,6 +5387,59 @@ function isRecordAssignable(source, target, options) {
|
|
|
5292
5387
|
}
|
|
5293
5388
|
return true;
|
|
5294
5389
|
}
|
|
5390
|
+
function isNamedAssignable(source, target, options) {
|
|
5391
|
+
const sourceArguments = source.typeArguments ?? [];
|
|
5392
|
+
const targetArguments = target.typeArguments ?? [];
|
|
5393
|
+
if (source.name !== target.name || sourceArguments.length === 0 || targetArguments.length !== sourceArguments.length) {
|
|
5394
|
+
return true;
|
|
5395
|
+
}
|
|
5396
|
+
return sourceArguments.every((argument, index) => isAssignable(argument, targetArguments[index] ?? ANY_TYPE, options));
|
|
5397
|
+
}
|
|
5398
|
+
function asRecord(name, members) {
|
|
5399
|
+
return { kind: "record", name, origin: null, members };
|
|
5400
|
+
}
|
|
5401
|
+
function shapeOf(name, options) {
|
|
5402
|
+
return options.resolveNominal?.(name) ?? null;
|
|
5403
|
+
}
|
|
5404
|
+
function withVisit(options, pair) {
|
|
5405
|
+
return { ...options, visited: /* @__PURE__ */ new Set([...options.visited ?? [], pair]) };
|
|
5406
|
+
}
|
|
5407
|
+
function isNominalAssignable(source, target, options) {
|
|
5408
|
+
const pair = `${source.name}>${target.name}`;
|
|
5409
|
+
if (options.visited?.has(pair) === true) {
|
|
5410
|
+
return true;
|
|
5411
|
+
}
|
|
5412
|
+
const declared = shapeOf(target.name, options);
|
|
5413
|
+
const actual = shapeOf(source.name, options);
|
|
5414
|
+
if (declared === null || actual === null) {
|
|
5415
|
+
return true;
|
|
5416
|
+
}
|
|
5417
|
+
if (declared.kind === "class" || actual.kind === "class") {
|
|
5418
|
+
return actual.kind === "class" && actual.ancestors.has(target.name);
|
|
5419
|
+
}
|
|
5420
|
+
return isRecordAssignable(asRecord(source.name, actual.members), asRecord(target.name, declared.members), withVisit(options, pair));
|
|
5421
|
+
}
|
|
5422
|
+
var SCALAR_KINDS = /* @__PURE__ */ new Set(["string", "number", "boolean", "thread", "userdata", "void", "nil", "function"]);
|
|
5423
|
+
function isNamedSourceAssignable(source, target, options) {
|
|
5424
|
+
const actual = shapeOf(source.name, options);
|
|
5425
|
+
if (actual === null) {
|
|
5426
|
+
return true;
|
|
5427
|
+
}
|
|
5428
|
+
if (actual.kind === "class") {
|
|
5429
|
+
return !SCALAR_KINDS.has(target.kind) && !isLiteralType(target);
|
|
5430
|
+
}
|
|
5431
|
+
return isAssignable(asRecord(source.name, actual.members), target, options);
|
|
5432
|
+
}
|
|
5433
|
+
function isNamedTargetAssignable(source, target, options) {
|
|
5434
|
+
const declared = shapeOf(target.name, options);
|
|
5435
|
+
if (declared === null) {
|
|
5436
|
+
return true;
|
|
5437
|
+
}
|
|
5438
|
+
if (declared.kind === "class") {
|
|
5439
|
+
return false;
|
|
5440
|
+
}
|
|
5441
|
+
return isAssignable(source, asRecord(target.name, declared.members), options);
|
|
5442
|
+
}
|
|
5295
5443
|
function isAssignable(source, target, options = { allowNil: false }) {
|
|
5296
5444
|
if (source.kind === "any" || target.kind === "any" || target.kind === "unknown") {
|
|
5297
5445
|
return true;
|
|
@@ -5299,9 +5447,6 @@ function isAssignable(source, target, options = { allowNil: false }) {
|
|
|
5299
5447
|
if (source.kind === "tuple" || target.kind === "tuple") {
|
|
5300
5448
|
return isAssignable(firstValueOf(source), firstValueOf(target), options);
|
|
5301
5449
|
}
|
|
5302
|
-
if (source.kind === "named" || target.kind === "named") {
|
|
5303
|
-
return true;
|
|
5304
|
-
}
|
|
5305
5450
|
if (source.kind === "nil" && (options.allowNil || target.kind === "optional" || target.kind === "nil")) {
|
|
5306
5451
|
return true;
|
|
5307
5452
|
}
|
|
@@ -5320,6 +5465,15 @@ function isAssignable(source, target, options = { allowNil: false }) {
|
|
|
5320
5465
|
if (target.kind === "optional") {
|
|
5321
5466
|
return isAssignable(source, target.element, options);
|
|
5322
5467
|
}
|
|
5468
|
+
if (source.kind === "named" && target.kind === "named") {
|
|
5469
|
+
return source.name === target.name ? isNamedAssignable(source, target, options) : isNominalAssignable(source, target, options);
|
|
5470
|
+
}
|
|
5471
|
+
if (source.kind === "named") {
|
|
5472
|
+
return isNamedSourceAssignable(source, target, options);
|
|
5473
|
+
}
|
|
5474
|
+
if (target.kind === "named") {
|
|
5475
|
+
return isNamedTargetAssignable(source, target, options);
|
|
5476
|
+
}
|
|
5323
5477
|
if (target.kind === "table") {
|
|
5324
5478
|
return isTableLike(source) || source.kind === "record";
|
|
5325
5479
|
}
|
|
@@ -5458,7 +5612,8 @@ var RUNTIME_HELPERS = {
|
|
|
5458
5612
|
math: { name: "math", file: "math.lua", injection: "automatic", features: ["number-extension"] },
|
|
5459
5613
|
string: { name: "string", file: "string.lua", injection: "automatic", features: ["string-extension", "template-string"] },
|
|
5460
5614
|
table: { name: "table", file: "table.lua", injection: "automatic", features: ["table-extension"] },
|
|
5461
|
-
threads: { name: "threads", file: "threads.lua", injection: "reference", features: ["thread"], globals: ["sleep", "Threads"] }
|
|
5615
|
+
threads: { name: "threads", file: "threads.lua", injection: "reference", features: ["thread"], globals: ["sleep", "Threads"] },
|
|
5616
|
+
validate: { name: "validate", file: "validate.lua", injection: "automatic", features: ["boundary-validation"] }
|
|
5462
5617
|
};
|
|
5463
5618
|
var DEVELOPMENT_RUNTIME_HELPERS = {
|
|
5464
5619
|
"development-logs-client": {
|
|
@@ -5599,6 +5754,11 @@ var MANIFEST_FIELDS = [
|
|
|
5599
5754
|
owner: "dependencies",
|
|
5600
5755
|
allowEmpty: true
|
|
5601
5756
|
}),
|
|
5757
|
+
field("contracts", STRING_TYPE, "Directory the export contract is written to and dependency contracts are read from.", {
|
|
5758
|
+
defaultValue: DEFAULT_CONTRACTS_DIR,
|
|
5759
|
+
rule: "contained-path",
|
|
5760
|
+
owner: "dependencies"
|
|
5761
|
+
}),
|
|
5602
5762
|
table("engine", "Requirements the MTA server and client must meet.", ENGINE_FIELDS, { defaultValue: {}, owner: "engine" }),
|
|
5603
5763
|
table("environment", "Files that declare and override the project environment.", ENVIRONMENT_FIELDS, { defaultValue: {}, owner: "environment" }),
|
|
5604
5764
|
field("outDir", STRING_TYPE, "Directory the built resource is written to.", { defaultValue: DEFAULT_OUT_DIR, rule: "static-path", owner: "output" }),
|
|
@@ -5758,8 +5918,8 @@ function isNumber(type) {
|
|
|
5758
5918
|
function isText(type) {
|
|
5759
5919
|
return type.kind === "string" || type.kind === "string-literal" || type.kind === "any";
|
|
5760
5920
|
}
|
|
5761
|
-
function fail(operator, expected, left, right,
|
|
5762
|
-
const culprit =
|
|
5921
|
+
function fail(operator, expected, left, right, describe2) {
|
|
5922
|
+
const culprit = describe2(isNumber(left) || isText(left) ? right : left);
|
|
5763
5923
|
return { evaluated: null, error: `"${operator}" expects ${expected} on both sides but received ${culprit}.` };
|
|
5764
5924
|
}
|
|
5765
5925
|
function arithmetic(operator, left, right) {
|
|
@@ -5798,22 +5958,22 @@ function applyOr(left, right) {
|
|
|
5798
5958
|
const value = isTruthy(left.value) ? left.value : right.value;
|
|
5799
5959
|
return { value, type: unionOf([left.truthy, right.type]), truthy: unionOf([left.truthy, right.truthy]), falsy: right.falsy };
|
|
5800
5960
|
}
|
|
5801
|
-
function applyOrdering(operator, left, right,
|
|
5961
|
+
function applyOrdering(operator, left, right, describe2) {
|
|
5802
5962
|
const numeric = isNumber(left.type) && isNumber(right.type);
|
|
5803
5963
|
const textual = isText(left.type) && isText(right.type);
|
|
5804
5964
|
if (!numeric && !textual) {
|
|
5805
|
-
return fail(operator, "two numbers or two strings", left.type, right.type,
|
|
5965
|
+
return fail(operator, "two numbers or two strings", left.type, right.type, describe2);
|
|
5806
5966
|
}
|
|
5807
5967
|
return { evaluated: booleanValue(compare(operator, left.value, right.value)), error: null };
|
|
5808
5968
|
}
|
|
5809
|
-
function applyConcat(left, right,
|
|
5969
|
+
function applyConcat(left, right, describe2) {
|
|
5810
5970
|
const usable = (type) => isText(type) || isNumber(type);
|
|
5811
5971
|
if (!usable(left.type) || !usable(right.type)) {
|
|
5812
|
-
return fail("..", "a string or a number", left.type, right.type,
|
|
5972
|
+
return fail("..", "a string or a number", left.type, right.type, describe2);
|
|
5813
5973
|
}
|
|
5814
5974
|
return { evaluated: stringValue(`${left.value}${right.value}`), error: null };
|
|
5815
5975
|
}
|
|
5816
|
-
function applyBinary(operator, left, right,
|
|
5976
|
+
function applyBinary(operator, left, right, describe2) {
|
|
5817
5977
|
if (operator === "and") {
|
|
5818
5978
|
return { evaluated: applyAnd(left, right), error: null };
|
|
5819
5979
|
}
|
|
@@ -5825,23 +5985,23 @@ function applyBinary(operator, left, right, describe) {
|
|
|
5825
5985
|
return { evaluated: booleanValue(operator === "==" ? same : !same), error: null };
|
|
5826
5986
|
}
|
|
5827
5987
|
if (ORDERING.includes(operator)) {
|
|
5828
|
-
return applyOrdering(operator, left, right,
|
|
5988
|
+
return applyOrdering(operator, left, right, describe2);
|
|
5829
5989
|
}
|
|
5830
5990
|
if (operator === "..") {
|
|
5831
|
-
return applyConcat(left, right,
|
|
5991
|
+
return applyConcat(left, right, describe2);
|
|
5832
5992
|
}
|
|
5833
5993
|
if (!isNumber(left.type) || !isNumber(right.type)) {
|
|
5834
|
-
return fail(operator, "a number", left.type, right.type,
|
|
5994
|
+
return fail(operator, "a number", left.type, right.type, describe2);
|
|
5835
5995
|
}
|
|
5836
5996
|
return { evaluated: numberValue(arithmetic(operator, left.value, right.value)), error: null };
|
|
5837
5997
|
}
|
|
5838
|
-
function applyUnary(operator, operand,
|
|
5998
|
+
function applyUnary(operator, operand, describe2) {
|
|
5839
5999
|
if (operator === "not") {
|
|
5840
6000
|
return { evaluated: booleanValue(!isTruthy(operand.value)), error: null };
|
|
5841
6001
|
}
|
|
5842
6002
|
if (operator === "-") {
|
|
5843
6003
|
if (!isNumber(operand.type)) {
|
|
5844
|
-
return { evaluated: null, error: `"-" expects a number but received ${
|
|
6004
|
+
return { evaluated: null, error: `"-" expects a number but received ${describe2(operand.type)}.` };
|
|
5845
6005
|
}
|
|
5846
6006
|
return { evaluated: numberValue(-operand.value), error: null };
|
|
5847
6007
|
}
|
|
@@ -6300,8 +6460,8 @@ var RuleWalk = class {
|
|
|
6300
6460
|
}
|
|
6301
6461
|
};
|
|
6302
6462
|
function normalizeManifest(value, positions) {
|
|
6303
|
-
const
|
|
6304
|
-
return { value:
|
|
6463
|
+
const walk4 = new RuleWalk(positions);
|
|
6464
|
+
return { value: walk4.fields(MANIFEST_FIELDS, value, "", ""), diagnostics: walk4.diagnostics };
|
|
6305
6465
|
}
|
|
6306
6466
|
|
|
6307
6467
|
// ../compiler/src/manifest/manifest-analysis.ts
|
|
@@ -6537,6 +6697,7 @@ function validateConfig(value, positions) {
|
|
|
6537
6697
|
sources: readSourceMapping(value),
|
|
6538
6698
|
assets: readAssetMappings(value),
|
|
6539
6699
|
dependencies,
|
|
6700
|
+
contracts: readString(value, "contracts") ?? "",
|
|
6540
6701
|
engine: readEngine(value),
|
|
6541
6702
|
environment: readEnvironmentFiles(value),
|
|
6542
6703
|
outDir: readString(value, "outDir") ?? "",
|
|
@@ -6601,7 +6762,7 @@ import { tmpdir } from "node:os";
|
|
|
6601
6762
|
import { join as join2 } from "node:path";
|
|
6602
6763
|
|
|
6603
6764
|
// src/cli/version.ts
|
|
6604
|
-
var VERSION = true ? "0.19.
|
|
6765
|
+
var VERSION = true ? "0.19.3" : "0.0.0-dev";
|
|
6605
6766
|
var PROGRAM_NAME = "luam";
|
|
6606
6767
|
var PROGRAM_DESCRIPTION = "luam \u2014 the Luam compiler for Multi Theft Auto resources.";
|
|
6607
6768
|
|
|
@@ -6786,9 +6947,9 @@ function pluralize(count, singular, plural = `${singular}s`) {
|
|
|
6786
6947
|
var MAX_ENTRIES_PER_FILE = 10;
|
|
6787
6948
|
var MAX_FILES = 10;
|
|
6788
6949
|
var GUTTER_WIDTH = 6;
|
|
6789
|
-
function groupByFile(
|
|
6950
|
+
function groupByFile(entries3) {
|
|
6790
6951
|
const groups = /* @__PURE__ */ new Map();
|
|
6791
|
-
for (const entry of
|
|
6952
|
+
for (const entry of entries3) {
|
|
6792
6953
|
const group = groups.get(entry.path);
|
|
6793
6954
|
if (group === void 0) {
|
|
6794
6955
|
groups.set(entry.path, [entry]);
|
|
@@ -6835,19 +6996,19 @@ function emit(reporter, entry, block) {
|
|
|
6835
6996
|
}
|
|
6836
6997
|
reporter.rawWarn(block);
|
|
6837
6998
|
}
|
|
6838
|
-
function reportGroup(reporter, path,
|
|
6999
|
+
function reportGroup(reporter, path, entries3, options) {
|
|
6839
7000
|
const limit = options.maxPerFile ?? MAX_ENTRIES_PER_FILE;
|
|
6840
7001
|
const source = options.sources.get(path);
|
|
6841
7002
|
reporter.raw(reporter.style.paint("strong", path));
|
|
6842
|
-
for (const entry of
|
|
7003
|
+
for (const entry of entries3.slice(0, limit)) {
|
|
6843
7004
|
emit(reporter, entry, entryBlock(reporter, entry, source));
|
|
6844
7005
|
}
|
|
6845
|
-
if (
|
|
6846
|
-
reporter.detail(` ... and ${pluralize(
|
|
7006
|
+
if (entries3.length > limit) {
|
|
7007
|
+
reporter.detail(` ... and ${pluralize(entries3.length - limit, "more diagnostic")} in this file.`);
|
|
6847
7008
|
}
|
|
6848
7009
|
}
|
|
6849
|
-
function reportGroupedDiagnostics(reporter,
|
|
6850
|
-
const groups = [...groupByFile(
|
|
7010
|
+
function reportGroupedDiagnostics(reporter, entries3, options) {
|
|
7011
|
+
const groups = [...groupByFile(entries3)];
|
|
6851
7012
|
const limit = options.maxFiles ?? MAX_FILES;
|
|
6852
7013
|
for (const [path, group] of groups.slice(0, limit)) {
|
|
6853
7014
|
reportGroup(reporter, path, group, options);
|
|
@@ -6863,9 +7024,9 @@ function formatFileDiagnostic(entry) {
|
|
|
6863
7024
|
const location = `${path}:${diagnostic.position.line}:${diagnostic.position.column}`;
|
|
6864
7025
|
return `${location} ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}`;
|
|
6865
7026
|
}
|
|
6866
|
-
function countFileDiagnostics(
|
|
6867
|
-
const errors =
|
|
6868
|
-
return { errors, warnings:
|
|
7027
|
+
function countFileDiagnostics(entries3) {
|
|
7028
|
+
const errors = entries3.filter((entry) => entry.diagnostic.severity === "error").length;
|
|
7029
|
+
return { errors, warnings: entries3.length - errors };
|
|
6869
7030
|
}
|
|
6870
7031
|
function countCliDiagnostics(diagnostics) {
|
|
6871
7032
|
const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
|
|
@@ -6875,8 +7036,8 @@ function reportManifestDiagnostics(reporter, path, source, diagnostics) {
|
|
|
6875
7036
|
if (diagnostics.length === 0) {
|
|
6876
7037
|
return;
|
|
6877
7038
|
}
|
|
6878
|
-
const
|
|
6879
|
-
reportFileDiagnostics(reporter,
|
|
7039
|
+
const entries3 = diagnostics.map((diagnostic) => ({ path, diagnostic }));
|
|
7040
|
+
reportFileDiagnostics(reporter, entries3, /* @__PURE__ */ new Map([[path, source]]));
|
|
6880
7041
|
}
|
|
6881
7042
|
function reportCliDiagnostics(reporter, diagnostics) {
|
|
6882
7043
|
for (const diagnostic of diagnostics) {
|
|
@@ -6888,8 +7049,8 @@ function reportCliDiagnostics(reporter, diagnostics) {
|
|
|
6888
7049
|
reporter.warn(line2);
|
|
6889
7050
|
}
|
|
6890
7051
|
}
|
|
6891
|
-
function reportPlainFileDiagnostics(reporter,
|
|
6892
|
-
for (const entry of
|
|
7052
|
+
function reportPlainFileDiagnostics(reporter, entries3) {
|
|
7053
|
+
for (const entry of entries3) {
|
|
6893
7054
|
const line2 = formatFileDiagnostic(entry);
|
|
6894
7055
|
if (entry.diagnostic.severity === "error") {
|
|
6895
7056
|
reporter.rawError(line2);
|
|
@@ -6898,12 +7059,12 @@ function reportPlainFileDiagnostics(reporter, entries2) {
|
|
|
6898
7059
|
reporter.rawWarn(line2);
|
|
6899
7060
|
}
|
|
6900
7061
|
}
|
|
6901
|
-
function reportFileDiagnostics(reporter,
|
|
7062
|
+
function reportFileDiagnostics(reporter, entries3, sources) {
|
|
6902
7063
|
if (!reporter.capability.interactive) {
|
|
6903
|
-
reportPlainFileDiagnostics(reporter,
|
|
7064
|
+
reportPlainFileDiagnostics(reporter, entries3);
|
|
6904
7065
|
return;
|
|
6905
7066
|
}
|
|
6906
|
-
reportGroupedDiagnostics(reporter,
|
|
7067
|
+
reportGroupedDiagnostics(reporter, entries3, { sources });
|
|
6907
7068
|
}
|
|
6908
7069
|
function formatCounts(counts) {
|
|
6909
7070
|
return `${pluralize(counts.errors, "error")}, ${pluralize(counts.warnings, "warning")}`;
|
|
@@ -7154,8 +7315,153 @@ function addProjectOptions(command) {
|
|
|
7154
7315
|
return command.addOption(cwdOption()).addOption(manifestOption()).addOption(colorOption());
|
|
7155
7316
|
}
|
|
7156
7317
|
|
|
7318
|
+
// src/build/contract-files.ts
|
|
7319
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
7320
|
+
import { dirname as dirname2, resolve as resolve4 } from "node:path";
|
|
7321
|
+
|
|
7322
|
+
// ../compiler/src/project/export-abi.ts
|
|
7323
|
+
var ABI_VERSION = 1;
|
|
7324
|
+
var ABI_EXTENSION = ".abi.json";
|
|
7325
|
+
var MAX_ABI_BYTES = 262144;
|
|
7326
|
+
var MAX_EXPORTS = 512;
|
|
7327
|
+
var MAX_PARAMETERS = 32;
|
|
7328
|
+
var MAX_TYPE_LENGTH = 512;
|
|
7329
|
+
var NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
7330
|
+
var RESOURCE_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
7331
|
+
var SIDES = /* @__PURE__ */ new Set(["server", "client", "shared"]);
|
|
7332
|
+
function serializeAbi(abi) {
|
|
7333
|
+
const exports = [...abi.exports].sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
7334
|
+
return `${JSON.stringify({ abi: abi.abi, resource: abi.resource, exports }, null, 4)}
|
|
7335
|
+
`;
|
|
7336
|
+
}
|
|
7337
|
+
function readString2(value, limit) {
|
|
7338
|
+
return typeof value === "string" && value.length > 0 && value.length <= limit ? value : null;
|
|
7339
|
+
}
|
|
7340
|
+
function readParameter(value) {
|
|
7341
|
+
if (typeof value !== "object" || value === null) {
|
|
7342
|
+
return null;
|
|
7343
|
+
}
|
|
7344
|
+
const entry = value;
|
|
7345
|
+
const name = readString2(entry.name, 64);
|
|
7346
|
+
const type = readString2(entry.type, MAX_TYPE_LENGTH);
|
|
7347
|
+
return name === null || type === null || !NAME_PATTERN.test(name) ? null : { name, type };
|
|
7348
|
+
}
|
|
7349
|
+
function readExport(value) {
|
|
7350
|
+
if (typeof value !== "object" || value === null) {
|
|
7351
|
+
return null;
|
|
7352
|
+
}
|
|
7353
|
+
const entry = value;
|
|
7354
|
+
const name = readString2(entry.name, 128);
|
|
7355
|
+
const side2 = readString2(entry.side, 16);
|
|
7356
|
+
const returns = readString2(entry.returns, MAX_TYPE_LENGTH);
|
|
7357
|
+
const parameters = Array.isArray(entry.parameters) ? entry.parameters : null;
|
|
7358
|
+
if (name === null || !NAME_PATTERN.test(name) || side2 === null || !SIDES.has(side2) || returns === null || parameters === null) {
|
|
7359
|
+
return null;
|
|
7360
|
+
}
|
|
7361
|
+
if (parameters.length > MAX_PARAMETERS) {
|
|
7362
|
+
return null;
|
|
7363
|
+
}
|
|
7364
|
+
const read = parameters.map(readParameter);
|
|
7365
|
+
if (read.some((parameter) => parameter === null)) {
|
|
7366
|
+
return null;
|
|
7367
|
+
}
|
|
7368
|
+
return {
|
|
7369
|
+
name,
|
|
7370
|
+
side: side2,
|
|
7371
|
+
http: entry.http === true,
|
|
7372
|
+
parameters: read,
|
|
7373
|
+
minimumArguments: typeof entry.minimumArguments === "number" && Number.isInteger(entry.minimumArguments) ? entry.minimumArguments : 0,
|
|
7374
|
+
variadic: entry.variadic === true,
|
|
7375
|
+
returns
|
|
7376
|
+
};
|
|
7377
|
+
}
|
|
7378
|
+
function parseResourceAbi(text) {
|
|
7379
|
+
if (text.length > MAX_ABI_BYTES) {
|
|
7380
|
+
return null;
|
|
7381
|
+
}
|
|
7382
|
+
let parsed;
|
|
7383
|
+
try {
|
|
7384
|
+
parsed = JSON.parse(text);
|
|
7385
|
+
} catch {
|
|
7386
|
+
return null;
|
|
7387
|
+
}
|
|
7388
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
7389
|
+
return null;
|
|
7390
|
+
}
|
|
7391
|
+
const document = parsed;
|
|
7392
|
+
const resource = readString2(document.resource, 64);
|
|
7393
|
+
const entries3 = Array.isArray(document.exports) ? document.exports : null;
|
|
7394
|
+
if (document.abi !== ABI_VERSION || resource === null || !RESOURCE_PATTERN.test(resource) || entries3 === null || entries3.length > MAX_EXPORTS) {
|
|
7395
|
+
return null;
|
|
7396
|
+
}
|
|
7397
|
+
const read = entries3.map(readExport);
|
|
7398
|
+
if (read.some((entry) => entry === null)) {
|
|
7399
|
+
return null;
|
|
7400
|
+
}
|
|
7401
|
+
return { abi: ABI_VERSION, resource, exports: read };
|
|
7402
|
+
}
|
|
7403
|
+
function abiFileName(resource) {
|
|
7404
|
+
return `${resource}${ABI_EXTENSION}`;
|
|
7405
|
+
}
|
|
7406
|
+
function lookupAbiExport(contracts, resource, name) {
|
|
7407
|
+
return contracts.find((entry) => entry.resource === resource)?.exports.find((entry) => entry.name === name) ?? null;
|
|
7408
|
+
}
|
|
7409
|
+
function knowsResource(contracts, resource) {
|
|
7410
|
+
return contracts.some((entry) => entry.resource === resource);
|
|
7411
|
+
}
|
|
7412
|
+
function buildResourceAbi(resource, contributions) {
|
|
7413
|
+
const exports = contributions.map((contribution) => {
|
|
7414
|
+
const signature = contribution.signature;
|
|
7415
|
+
return {
|
|
7416
|
+
name: contribution.name,
|
|
7417
|
+
side: contribution.side,
|
|
7418
|
+
http: contribution.http,
|
|
7419
|
+
parameters: signature?.parameters ?? [],
|
|
7420
|
+
minimumArguments: signature?.minimumArguments ?? 0,
|
|
7421
|
+
variadic: signature?.variadic ?? true,
|
|
7422
|
+
returns: signature?.returns ?? "any"
|
|
7423
|
+
};
|
|
7424
|
+
});
|
|
7425
|
+
return { abi: ABI_VERSION, resource, exports };
|
|
7426
|
+
}
|
|
7427
|
+
|
|
7428
|
+
// src/build/contract-files.ts
|
|
7429
|
+
function contractPath(root, config, resource) {
|
|
7430
|
+
return resolve4(root, config.contracts, abiFileName(resource));
|
|
7431
|
+
}
|
|
7432
|
+
function readDependencyContracts(root, config) {
|
|
7433
|
+
const contracts = [];
|
|
7434
|
+
const diagnostics = [];
|
|
7435
|
+
for (const dependency of config.dependencies) {
|
|
7436
|
+
const path = contractPath(root, config, dependency);
|
|
7437
|
+
let text;
|
|
7438
|
+
try {
|
|
7439
|
+
text = readFileSync3(path, "utf8");
|
|
7440
|
+
} catch {
|
|
7441
|
+
continue;
|
|
7442
|
+
}
|
|
7443
|
+
const parsed = parseResourceAbi(text);
|
|
7444
|
+
if (parsed === null) {
|
|
7445
|
+
diagnostics.push(cliWarning("build-invalid-contract", `The export contract for "${dependency}" at "${path}" could not be read and was ignored.`));
|
|
7446
|
+
continue;
|
|
7447
|
+
}
|
|
7448
|
+
if (parsed.resource !== dependency) {
|
|
7449
|
+
diagnostics.push(cliWarning("build-invalid-contract", `The export contract at "${path}" names resource "${parsed.resource}" and was ignored.`));
|
|
7450
|
+
continue;
|
|
7451
|
+
}
|
|
7452
|
+
contracts.push(parsed);
|
|
7453
|
+
}
|
|
7454
|
+
return { contracts, diagnostics };
|
|
7455
|
+
}
|
|
7456
|
+
function writeResourceContract(root, config, contract) {
|
|
7457
|
+
const path = contractPath(root, config, config.name);
|
|
7458
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
7459
|
+
writeFileSync3(path, serializeAbi(contract), "utf8");
|
|
7460
|
+
return path;
|
|
7461
|
+
}
|
|
7462
|
+
|
|
7157
7463
|
// src/build/helper-files.ts
|
|
7158
|
-
import { existsSync as existsSync3, readFileSync as
|
|
7464
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
7159
7465
|
import { fileURLToPath } from "node:url";
|
|
7160
7466
|
function bundledHelperPath(file) {
|
|
7161
7467
|
return fileURLToPath(new URL(`./lua/${file}`, import.meta.url));
|
|
@@ -7167,7 +7473,7 @@ function resolveHelperPath(helper, file) {
|
|
|
7167
7473
|
return existsSync3(packaged) ? packaged : bundledHelperPath(file);
|
|
7168
7474
|
}
|
|
7169
7475
|
function readHelperSource(helper, file) {
|
|
7170
|
-
return
|
|
7476
|
+
return readFileSync4(resolveHelperPath(helper, file), "utf8");
|
|
7171
7477
|
}
|
|
7172
7478
|
|
|
7173
7479
|
// src/build/build-phase.ts
|
|
@@ -7236,16 +7542,16 @@ function createPhaseTracker(listener = null) {
|
|
|
7236
7542
|
}
|
|
7237
7543
|
|
|
7238
7544
|
// src/build/project-inputs.ts
|
|
7239
|
-
import { existsSync as existsSync4, readFileSync as
|
|
7240
|
-
import { resolve as
|
|
7545
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
7546
|
+
import { resolve as resolve7 } from "node:path";
|
|
7241
7547
|
|
|
7242
7548
|
// src/build/asset-resolution.ts
|
|
7243
7549
|
import { statSync as statSync3 } from "node:fs";
|
|
7244
|
-
import { resolve as
|
|
7550
|
+
import { resolve as resolve6 } from "node:path";
|
|
7245
7551
|
|
|
7246
7552
|
// src/build/project-files.ts
|
|
7247
7553
|
import { readdirSync, statSync as statSync2 } from "node:fs";
|
|
7248
|
-
import { join as join3, relative, resolve as
|
|
7554
|
+
import { join as join3, relative, resolve as resolve5 } from "node:path";
|
|
7249
7555
|
function isDirectory(path) {
|
|
7250
7556
|
try {
|
|
7251
7557
|
return statSync2(path).isDirectory();
|
|
@@ -7257,14 +7563,14 @@ function relativePath(root, absolute) {
|
|
|
7257
7563
|
return normalizePattern(relative(root, absolute));
|
|
7258
7564
|
}
|
|
7259
7565
|
function walk(root, directory, excluded, tree) {
|
|
7260
|
-
let
|
|
7566
|
+
let entries3;
|
|
7261
7567
|
try {
|
|
7262
|
-
|
|
7568
|
+
entries3 = readdirSync(directory, { withFileTypes: true });
|
|
7263
7569
|
} catch (error) {
|
|
7264
7570
|
tree.errors.push(`"${relativePath(root, directory)}" could not be read: ${error instanceof Error ? error.message : String(error)}`);
|
|
7265
7571
|
return;
|
|
7266
7572
|
}
|
|
7267
|
-
for (const entry of
|
|
7573
|
+
for (const entry of entries3) {
|
|
7268
7574
|
const absolute = join3(directory, entry.name);
|
|
7269
7575
|
const path = relativePath(root, absolute);
|
|
7270
7576
|
if (path.length === 0 || isExcludedPath(path, excluded)) {
|
|
@@ -7281,7 +7587,7 @@ function listProjectFiles(root, roots, excluded = []) {
|
|
|
7281
7587
|
const tree = { files: [], errors: [] };
|
|
7282
7588
|
const seen = /* @__PURE__ */ new Set();
|
|
7283
7589
|
for (const start of [...new Set(roots)].sort()) {
|
|
7284
|
-
const absolute =
|
|
7590
|
+
const absolute = resolve5(root, start);
|
|
7285
7591
|
if (!isDirectory(absolute) || seen.has(absolute)) {
|
|
7286
7592
|
continue;
|
|
7287
7593
|
}
|
|
@@ -7425,9 +7731,23 @@ var OCCUPIED_SIDES = {
|
|
|
7425
7731
|
function occupiedSides(contribution) {
|
|
7426
7732
|
return OCCUPIED_SIDES[contribution.side];
|
|
7427
7733
|
}
|
|
7428
|
-
function
|
|
7734
|
+
function exportSignature(type) {
|
|
7735
|
+
if (type === void 0 || type.kind !== "function") {
|
|
7736
|
+
return null;
|
|
7737
|
+
}
|
|
7738
|
+
const parameters = type.parameters.map((parameter, index) => ({ name: type.parameterNames?.[index] ?? `arg${index + 1}`, type: typeToString(parameter) }));
|
|
7739
|
+
return { parameters, minimumArguments: type.minimumArguments, variadic: type.isVariadic, returns: typeToString(type.returnType) };
|
|
7740
|
+
}
|
|
7741
|
+
function toContributions(directives, environment, globals) {
|
|
7429
7742
|
return directives.exports.map(
|
|
7430
|
-
(entry) => ({
|
|
7743
|
+
(entry) => ({
|
|
7744
|
+
kind: "export",
|
|
7745
|
+
name: entry.name,
|
|
7746
|
+
http: entry.http,
|
|
7747
|
+
side: environment,
|
|
7748
|
+
signature: exportSignature(globals.get(entry.name)),
|
|
7749
|
+
position: entry.position
|
|
7750
|
+
})
|
|
7431
7751
|
);
|
|
7432
7752
|
}
|
|
7433
7753
|
var INDENT = " ";
|
|
@@ -7461,8 +7781,8 @@ function attribute(name, value) {
|
|
|
7461
7781
|
function textElement(name, value) {
|
|
7462
7782
|
return `${INDENT}<${name}>${escapeXml(value)}</${name}>`;
|
|
7463
7783
|
}
|
|
7464
|
-
function section(text,
|
|
7465
|
-
return
|
|
7784
|
+
function section(text, entries3) {
|
|
7785
|
+
return entries3.length === 0 ? [] : [`${INDENT}<!-- ${text} -->`, ...entries3];
|
|
7466
7786
|
}
|
|
7467
7787
|
function infoElement(info) {
|
|
7468
7788
|
const attributes = info.author === void 0 ? [] : [attribute("author", info.author)];
|
|
@@ -7590,12 +7910,12 @@ var MISSING_LOAD_ORDER = 'is listed in "loadOrder" but no source file or asset m
|
|
|
7590
7910
|
function missingEntry(entry) {
|
|
7591
7911
|
return { path: entry, diagnostic: createDiagnostic("project", "project-load-order-missing", `"${entry}" ${MISSING_LOAD_ORDER}`, FILE_START) };
|
|
7592
7912
|
}
|
|
7593
|
-
function resolveLoadOrder(
|
|
7913
|
+
function resolveLoadOrder(entries3, scripts, assets) {
|
|
7594
7914
|
const byScript = new Map(scripts.map((script) => [normalizePath(script.source), script]));
|
|
7595
7915
|
const byAsset = new Map(assets.map((asset) => [normalizePath(asset.source), asset]));
|
|
7596
7916
|
const resolved2 = { scripts: [], assets: [], diagnostics: [] };
|
|
7597
7917
|
const seen = /* @__PURE__ */ new Set();
|
|
7598
|
-
for (const entry of
|
|
7918
|
+
for (const entry of entries3) {
|
|
7599
7919
|
const path = normalizePath(entry).replace(/^\.\//, "");
|
|
7600
7920
|
const script = byScript.get(path);
|
|
7601
7921
|
const asset = byAsset.get(path);
|
|
@@ -7671,11 +7991,11 @@ var ENTRY = /^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
|
|
|
7671
7991
|
var SENSITIVE = /(password|secret|token|key|credential|dsn|private)/i;
|
|
7672
7992
|
var EMPTY_ENV_FILE = { entries: [], errors: [] };
|
|
7673
7993
|
function unquote(raw) {
|
|
7674
|
-
const
|
|
7675
|
-
if (
|
|
7994
|
+
const quote3 = raw.slice(0, 1);
|
|
7995
|
+
if (quote3 !== '"' && quote3 !== "'") {
|
|
7676
7996
|
return null;
|
|
7677
7997
|
}
|
|
7678
|
-
const closing = raw.lastIndexOf(
|
|
7998
|
+
const closing = raw.lastIndexOf(quote3);
|
|
7679
7999
|
return closing > 0 ? raw.slice(1, closing) : null;
|
|
7680
8000
|
}
|
|
7681
8001
|
function stripComment(raw) {
|
|
@@ -7693,7 +8013,7 @@ function classify(raw) {
|
|
|
7693
8013
|
return { value, kind: value.length > 0 && Number.isFinite(Number(value)) ? "number" : "string" };
|
|
7694
8014
|
}
|
|
7695
8015
|
function parseEnvFile(source) {
|
|
7696
|
-
const
|
|
8016
|
+
const entries3 = [];
|
|
7697
8017
|
const errors = [];
|
|
7698
8018
|
source.split(/\r?\n/).forEach((raw, index) => {
|
|
7699
8019
|
const trimmed = raw.trim();
|
|
@@ -7707,9 +8027,9 @@ function parseEnvFile(source) {
|
|
|
7707
8027
|
return;
|
|
7708
8028
|
}
|
|
7709
8029
|
const { value, kind } = classify(match[2]);
|
|
7710
|
-
|
|
8030
|
+
entries3.push({ key: match[1], value, kind, line: line2 });
|
|
7711
8031
|
});
|
|
7712
|
-
return { entries:
|
|
8032
|
+
return { entries: entries3, errors };
|
|
7713
8033
|
}
|
|
7714
8034
|
function isSensitiveKey(key) {
|
|
7715
8035
|
return SENSITIVE.test(key);
|
|
@@ -7875,14 +8195,14 @@ var MANIFEST_OUTPUT = "meta.xml";
|
|
|
7875
8195
|
var HERE = ".";
|
|
7876
8196
|
function isDirectory2(root, path) {
|
|
7877
8197
|
try {
|
|
7878
|
-
return statSync3(
|
|
8198
|
+
return statSync3(resolve6(root, path)).isDirectory();
|
|
7879
8199
|
} catch {
|
|
7880
8200
|
return false;
|
|
7881
8201
|
}
|
|
7882
8202
|
}
|
|
7883
8203
|
function exists(root, path) {
|
|
7884
8204
|
try {
|
|
7885
|
-
statSync3(
|
|
8205
|
+
statSync3(resolve6(root, path));
|
|
7886
8206
|
return true;
|
|
7887
8207
|
} catch {
|
|
7888
8208
|
return false;
|
|
@@ -7950,10 +8270,10 @@ var CONFIGURATION_FILE = "config.lua";
|
|
|
7950
8270
|
var MALFORMED_ENV = "build-env-malformed";
|
|
7951
8271
|
var MISSING_ENV = "config-missing-env-file";
|
|
7952
8272
|
function readTextFile(path) {
|
|
7953
|
-
return existsSync4(path) ?
|
|
8273
|
+
return existsSync4(path) ? readFileSync5(path, "utf8") : null;
|
|
7954
8274
|
}
|
|
7955
8275
|
function readEnvironment(root, file, required, diagnostics) {
|
|
7956
|
-
const source = readTextFile(
|
|
8276
|
+
const source = readTextFile(resolve7(root, file));
|
|
7957
8277
|
if (source === null) {
|
|
7958
8278
|
if (required) {
|
|
7959
8279
|
diagnostics.push(cliError(MISSING_ENV, `"${file}" is configured under "environment" but does not exist. Create it or remove the setting.`));
|
|
@@ -7967,7 +8287,7 @@ function readEnvironment(root, file, required, diagnostics) {
|
|
|
7967
8287
|
return parsed;
|
|
7968
8288
|
}
|
|
7969
8289
|
function readConfiguration(root, diagnostics) {
|
|
7970
|
-
const content = readTextFile(
|
|
8290
|
+
const content = readTextFile(resolve7(root, CONFIGURATION_FILE));
|
|
7971
8291
|
if (content === null) {
|
|
7972
8292
|
return null;
|
|
7973
8293
|
}
|
|
@@ -7986,14 +8306,14 @@ function readProjectInputs(root, options) {
|
|
|
7986
8306
|
}
|
|
7987
8307
|
|
|
7988
8308
|
// src/build/source-discovery.ts
|
|
7989
|
-
import { readFileSync as
|
|
7990
|
-
import { resolve as
|
|
8309
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
8310
|
+
import { resolve as resolve8 } from "node:path";
|
|
7991
8311
|
|
|
7992
8312
|
// ../compiler/src/project/source-mapping.ts
|
|
7993
8313
|
function createSourceResolver(mapping) {
|
|
7994
8314
|
const matchers = new Map(SOURCE_SIDES.map((environment) => [environment, createPatternMatcher(mapping[environment])]));
|
|
7995
8315
|
const patterns = SOURCE_SIDES.flatMap((environment) => [...matchers.get(environment)?.patterns ?? []]);
|
|
7996
|
-
function
|
|
8316
|
+
function resolve21(path) {
|
|
7997
8317
|
const normalized = normalizePattern(path);
|
|
7998
8318
|
const matches = [];
|
|
7999
8319
|
for (const environment of SOURCE_SIDES) {
|
|
@@ -8007,8 +8327,8 @@ function createSourceResolver(mapping) {
|
|
|
8007
8327
|
return {
|
|
8008
8328
|
patterns,
|
|
8009
8329
|
roots: watchRoots(patterns),
|
|
8010
|
-
resolve:
|
|
8011
|
-
side: (path) =>
|
|
8330
|
+
resolve: resolve21,
|
|
8331
|
+
side: (path) => resolve21(path).environment
|
|
8012
8332
|
};
|
|
8013
8333
|
}
|
|
8014
8334
|
function describeMatches(matches) {
|
|
@@ -8018,17 +8338,28 @@ function describeMatches(matches) {
|
|
|
8018
8338
|
// ../compiler/src/project/source-kind.ts
|
|
8019
8339
|
var SOURCE_EXTENSION = ".luam";
|
|
8020
8340
|
var DECLARATION_EXTENSION = ".d.luam";
|
|
8341
|
+
var TEST_EXTENSION = ".test.luam";
|
|
8021
8342
|
function isDeclarationPath(path) {
|
|
8022
8343
|
return normalizePath(path).endsWith(DECLARATION_EXTENSION);
|
|
8023
8344
|
}
|
|
8345
|
+
function isTestPath(path) {
|
|
8346
|
+
return normalizePath(path).endsWith(TEST_EXTENSION);
|
|
8347
|
+
}
|
|
8024
8348
|
|
|
8025
8349
|
// src/build/source-discovery.ts
|
|
8026
8350
|
var MISSING_SOURCE = "config-missing-source";
|
|
8027
8351
|
var NO_SOURCES = "config-no-sources";
|
|
8028
8352
|
var SIDE_CONFLICT = "config-source-side-conflict";
|
|
8353
|
+
var TEST_SOURCE = "config-test-source";
|
|
8029
8354
|
var UNREADABLE_SOURCE = "build-source-unreadable";
|
|
8030
8355
|
function checkLiterals(root, resolver, found, diagnostics) {
|
|
8031
8356
|
for (const pattern of resolver.patterns.filter(isLiteralPattern)) {
|
|
8357
|
+
if (isTestPath(pattern)) {
|
|
8358
|
+
diagnostics.push(
|
|
8359
|
+
cliError(TEST_SOURCE, `"${pattern}" is listed in "sources" but "${TEST_EXTENSION}" files are run by "luam test" and never built into the resource.`)
|
|
8360
|
+
);
|
|
8361
|
+
continue;
|
|
8362
|
+
}
|
|
8032
8363
|
if (!pattern.endsWith(SOURCE_EXTENSION)) {
|
|
8033
8364
|
diagnostics.push(cliError(MISSING_SOURCE, `"${pattern}" is listed in "sources" but does not end in "${SOURCE_EXTENSION}".`));
|
|
8034
8365
|
continue;
|
|
@@ -8040,7 +8371,7 @@ function checkLiterals(root, resolver, found, diagnostics) {
|
|
|
8040
8371
|
}
|
|
8041
8372
|
function readSource(root, path, diagnostics) {
|
|
8042
8373
|
try {
|
|
8043
|
-
return
|
|
8374
|
+
return readFileSync6(resolve8(root, path), "utf8");
|
|
8044
8375
|
} catch (error) {
|
|
8045
8376
|
diagnostics.push(cliError(UNREADABLE_SOURCE, `The source file "${path}" could not be read: ${error instanceof Error ? error.message : String(error)}`));
|
|
8046
8377
|
return null;
|
|
@@ -8052,7 +8383,7 @@ function discoverSources(root, sources, excluded = []) {
|
|
|
8052
8383
|
const diagnostics = tree.errors.map((message) => cliError(UNREADABLE_SOURCE, message));
|
|
8053
8384
|
const files = [];
|
|
8054
8385
|
const matched = /* @__PURE__ */ new Set();
|
|
8055
|
-
for (const path of tree.files.filter((entry) => entry.endsWith(SOURCE_EXTENSION))) {
|
|
8386
|
+
for (const path of tree.files.filter((entry) => entry.endsWith(SOURCE_EXTENSION) && !isTestPath(entry))) {
|
|
8056
8387
|
const resolution = resolver.resolve(path);
|
|
8057
8388
|
if (resolution.matches.length === 0) {
|
|
8058
8389
|
continue;
|
|
@@ -8120,25 +8451,57 @@ function fn(parameters, returnType, minimumArguments2, isVariadic = false, param
|
|
|
8120
8451
|
var EMPTY_PROJECT_DECLARATIONS = { globals: [] };
|
|
8121
8452
|
var ENV_GLOBAL = "env";
|
|
8122
8453
|
var VALUE_TYPES = { boolean: BOOLEAN, number: NUMBER, string: STRING };
|
|
8123
|
-
function envMembers(
|
|
8454
|
+
function envMembers(entries3) {
|
|
8124
8455
|
const members = /* @__PURE__ */ new Map();
|
|
8125
|
-
for (const entry of
|
|
8456
|
+
for (const entry of entries3) {
|
|
8126
8457
|
members.set(entry.key, { name: entry.key, type: VALUE_TYPES[entry.kind] });
|
|
8127
8458
|
}
|
|
8128
8459
|
return [...members.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
8129
8460
|
}
|
|
8130
|
-
function envDeclaration(
|
|
8131
|
-
const env = record(ENV_GLOBAL, envMembers(
|
|
8461
|
+
function envDeclaration(entries3, origin) {
|
|
8462
|
+
const env = record(ENV_GLOBAL, envMembers(entries3), origin);
|
|
8132
8463
|
return { name: ENV_GLOBAL, environment: "server", source: "project", type: env };
|
|
8133
8464
|
}
|
|
8134
|
-
function projectDeclarations(
|
|
8135
|
-
if (
|
|
8465
|
+
function projectDeclarations(entries3, origin) {
|
|
8466
|
+
if (entries3 === null) {
|
|
8136
8467
|
return EMPTY_PROJECT_DECLARATIONS;
|
|
8137
8468
|
}
|
|
8138
|
-
return { globals: [envDeclaration(
|
|
8139
|
-
}
|
|
8469
|
+
return { globals: [envDeclaration(entries3, origin)] };
|
|
8470
|
+
}
|
|
8471
|
+
|
|
8472
|
+
// ../compiler/src/checker/test-declarations.ts
|
|
8473
|
+
var TEST_ORIGIN = "luam:test";
|
|
8474
|
+
var CALLBACK = fn([], VOID);
|
|
8475
|
+
var EXPECTATION_MEMBERS = [
|
|
8476
|
+
{ name: "toBe", type: fn([ANY], VOID, 1, false, ["expected"]) },
|
|
8477
|
+
{ name: "toBeFalsy", type: fn([], VOID) },
|
|
8478
|
+
{ name: "toBeNil", type: fn([], VOID) },
|
|
8479
|
+
{ name: "toBeTruthy", type: fn([], VOID) },
|
|
8480
|
+
{ name: "toContain", type: fn([ANY], VOID, 1, false, ["value"]) },
|
|
8481
|
+
{ name: "toEqual", type: fn([ANY], VOID, 1, false, ["expected"]) },
|
|
8482
|
+
{ name: "toNotBe", type: fn([ANY], VOID, 1, false, ["expected"]) },
|
|
8483
|
+
{ name: "toNotEqual", type: fn([ANY], VOID, 1, false, ["expected"]) },
|
|
8484
|
+
{ name: "toThrow", type: fn([optionalOf(STRING)], VOID, 0, false, ["message"]) }
|
|
8485
|
+
];
|
|
8486
|
+
var STUB_MEMBERS = [
|
|
8487
|
+
{ name: "calls", type: fn([STRING], arrayOf(TABLE), 1, false, ["name"]) },
|
|
8488
|
+
{ name: "reset", type: fn([], VOID) },
|
|
8489
|
+
{ name: "returns", type: fn([STRING, ANY], VOID, 2, false, ["name", "value"]) },
|
|
8490
|
+
{ name: "stub", type: fn([STRING, ANY], VOID, 2, false, ["name", "implementation"]) }
|
|
8491
|
+
];
|
|
8492
|
+
var EXPECTATION_TYPE = record("Expectation", EXPECTATION_MEMBERS, TEST_ORIGIN);
|
|
8493
|
+
var MTA_STUBS_TYPE = record("MtaStubs", STUB_MEMBERS, TEST_ORIGIN);
|
|
8494
|
+
var TEST_DECLARATIONS = [
|
|
8495
|
+
{ name: "afterEach", environment: "shared", source: "extension", type: fn([CALLBACK], VOID, 1, false, ["body"]) },
|
|
8496
|
+
{ name: "beforeEach", environment: "shared", source: "extension", type: fn([CALLBACK], VOID, 1, false, ["body"]) },
|
|
8497
|
+
{ name: "describe", environment: "shared", source: "extension", type: fn([STRING, CALLBACK], VOID, 2, false, ["name", "body"]) },
|
|
8498
|
+
{ name: "expect", environment: "shared", source: "extension", type: fn([ANY], EXPECTATION_TYPE, 1, false, ["value"]) },
|
|
8499
|
+
{ name: "mta", environment: "shared", source: "extension", type: MTA_STUBS_TYPE },
|
|
8500
|
+
{ name: "test", environment: "shared", source: "extension", type: fn([STRING, CALLBACK], VOID, 2, false, ["name", "body"]) }
|
|
8501
|
+
];
|
|
8140
8502
|
|
|
8141
8503
|
// ../compiler/src/checker/ambient.ts
|
|
8504
|
+
var EVENT_NAME_PREFIX = "event:";
|
|
8142
8505
|
var EMPTY_AMBIENT = { classes: [], interfaces: [], enums: [], globals: [], events: [] };
|
|
8143
8506
|
function ambientFromRegistry(registry) {
|
|
8144
8507
|
return {
|
|
@@ -8149,9 +8512,9 @@ function ambientFromRegistry(registry) {
|
|
|
8149
8512
|
events: registry.allEvents()
|
|
8150
8513
|
};
|
|
8151
8514
|
}
|
|
8152
|
-
function withoutNames(
|
|
8515
|
+
function withoutNames(entries3, excluded) {
|
|
8153
8516
|
const names = new Set(excluded.map((entry) => entry.name));
|
|
8154
|
-
return
|
|
8517
|
+
return entries3.filter((entry) => !names.has(entry.name));
|
|
8155
8518
|
}
|
|
8156
8519
|
function ownDeclarations(registry, ambient) {
|
|
8157
8520
|
const all = ambientFromRegistry(registry);
|
|
@@ -8328,6 +8691,9 @@ var Binder = class {
|
|
|
8328
8691
|
declaredSymbols() {
|
|
8329
8692
|
return [...this.discarded, ...this.scopes.flatMap((scope) => [...scope.values()])];
|
|
8330
8693
|
}
|
|
8694
|
+
currentScopeNames() {
|
|
8695
|
+
return [...this.scopes[this.scopes.length - 1]?.keys() ?? []];
|
|
8696
|
+
}
|
|
8331
8697
|
declare(symbol) {
|
|
8332
8698
|
const scope = this.scopes[this.scopes.length - 1];
|
|
8333
8699
|
scope?.set(symbol.name, symbol);
|
|
@@ -8366,6 +8732,211 @@ var Binder = class {
|
|
|
8366
8732
|
}
|
|
8367
8733
|
};
|
|
8368
8734
|
|
|
8735
|
+
// ../compiler/src/checker/flow.ts
|
|
8736
|
+
function createFlow() {
|
|
8737
|
+
return { facts: /* @__PURE__ */ new Map(), reachable: true };
|
|
8738
|
+
}
|
|
8739
|
+
function cloneFlow(state) {
|
|
8740
|
+
return { facts: new Map(state.facts), reachable: state.reachable };
|
|
8741
|
+
}
|
|
8742
|
+
function applyFacts(state, facts) {
|
|
8743
|
+
for (const [path, type] of facts) {
|
|
8744
|
+
state.facts.set(path, type);
|
|
8745
|
+
}
|
|
8746
|
+
}
|
|
8747
|
+
function forgetPath(state, path) {
|
|
8748
|
+
for (const key of [...state.facts.keys()]) {
|
|
8749
|
+
if (key === path || key.startsWith(`${path}.`) || path.startsWith(`${key}.`)) {
|
|
8750
|
+
state.facts.delete(key);
|
|
8751
|
+
}
|
|
8752
|
+
}
|
|
8753
|
+
}
|
|
8754
|
+
function joinFlows(states) {
|
|
8755
|
+
const live = states.filter((state) => state.reachable);
|
|
8756
|
+
const [first, ...rest] = live;
|
|
8757
|
+
if (first === void 0) {
|
|
8758
|
+
return { facts: /* @__PURE__ */ new Map(), reachable: false };
|
|
8759
|
+
}
|
|
8760
|
+
const facts = /* @__PURE__ */ new Map();
|
|
8761
|
+
for (const [path, type] of first.facts) {
|
|
8762
|
+
const others = rest.map((state) => state.facts.get(path));
|
|
8763
|
+
if (others.some((other) => other === void 0)) {
|
|
8764
|
+
continue;
|
|
8765
|
+
}
|
|
8766
|
+
facts.set(path, createUnion([type, ...others]));
|
|
8767
|
+
}
|
|
8768
|
+
return { facts, reachable: true };
|
|
8769
|
+
}
|
|
8770
|
+
function optionsOf(declared) {
|
|
8771
|
+
if (declared.kind === "union") {
|
|
8772
|
+
return declared.options;
|
|
8773
|
+
}
|
|
8774
|
+
return declared.kind === "optional" ? [declared.element, NIL_TYPE] : [];
|
|
8775
|
+
}
|
|
8776
|
+
function assignmentFact(narrowed, declared) {
|
|
8777
|
+
const matched = optionsOf(declared).filter((option) => isAssignable(narrowed, option));
|
|
8778
|
+
const [only] = matched;
|
|
8779
|
+
return matched.length === 1 && only !== void 0 ? only : null;
|
|
8780
|
+
}
|
|
8781
|
+
|
|
8782
|
+
// ../compiler/src/checker/type-substitution.ts
|
|
8783
|
+
function substituteType(type, substitutions) {
|
|
8784
|
+
if (type.kind === "named") {
|
|
8785
|
+
const args = type.typeArguments ?? [];
|
|
8786
|
+
if (args.length > 0) {
|
|
8787
|
+
return createNamed(type.name, args.map((argument) => substituteType(argument, substitutions)));
|
|
8788
|
+
}
|
|
8789
|
+
return substitutions.get(type.name) ?? type;
|
|
8790
|
+
}
|
|
8791
|
+
if (type.kind === "array") {
|
|
8792
|
+
return createArray(substituteType(type.element, substitutions));
|
|
8793
|
+
}
|
|
8794
|
+
if (type.kind === "map") {
|
|
8795
|
+
return createMap(substituteType(type.key, substitutions), substituteType(type.value, substitutions));
|
|
8796
|
+
}
|
|
8797
|
+
if (type.kind === "optional") {
|
|
8798
|
+
return createOptional(substituteType(type.element, substitutions));
|
|
8799
|
+
}
|
|
8800
|
+
if (type.kind === "union") {
|
|
8801
|
+
return createUnion(type.options.map((option) => substituteType(option, substitutions)));
|
|
8802
|
+
}
|
|
8803
|
+
if (type.kind === "function") {
|
|
8804
|
+
const parameters = type.parameters.map((parameter) => substituteType(parameter, substitutions));
|
|
8805
|
+
const returnType = substituteType(type.returnType, substitutions);
|
|
8806
|
+
const variadicType = type.variadicType === void 0 ? void 0 : substituteType(type.variadicType, substitutions);
|
|
8807
|
+
return createFunction(parameters, returnType, type.minimumArguments, type.isVariadic, type.parameterNames, variadicType);
|
|
8808
|
+
}
|
|
8809
|
+
if (type.kind === "tuple") {
|
|
8810
|
+
return createTuple(type.elements.map((element) => substituteType(element, substitutions)));
|
|
8811
|
+
}
|
|
8812
|
+
if (type.kind === "record") {
|
|
8813
|
+
const members = new Map([...type.members].map(([name, member]) => [name, substituteType(member, substitutions)]));
|
|
8814
|
+
return createRecord(type.name, members, type.origin);
|
|
8815
|
+
}
|
|
8816
|
+
return type;
|
|
8817
|
+
}
|
|
8818
|
+
|
|
8819
|
+
// ../compiler/src/checker/generic-class.ts
|
|
8820
|
+
var MAX_DEPTH = 32;
|
|
8821
|
+
function classSubstitutions(info, typeArguments) {
|
|
8822
|
+
return new Map(info.typeParameters.map((parameter, index) => [parameter, typeArguments[index] ?? ANY_TYPE]));
|
|
8823
|
+
}
|
|
8824
|
+
function parentSubstitutions(registry, info, current) {
|
|
8825
|
+
const parent = info.superClass === null ? null : registry.lookupClass(info.superClass);
|
|
8826
|
+
if (parent === null) {
|
|
8827
|
+
return null;
|
|
8828
|
+
}
|
|
8829
|
+
return classSubstitutions(parent, info.superArguments.map((argument) => substituteType(argument, current)));
|
|
8830
|
+
}
|
|
8831
|
+
function substitutionsFor(registry, receiver, owner) {
|
|
8832
|
+
const info = registry.lookupClass(receiver.name);
|
|
8833
|
+
if (info === null) {
|
|
8834
|
+
return /* @__PURE__ */ new Map();
|
|
8835
|
+
}
|
|
8836
|
+
let current = info;
|
|
8837
|
+
let substitutions = classSubstitutions(info, receiver.typeArguments ?? []);
|
|
8838
|
+
for (let depth = 0; current !== null && depth < MAX_DEPTH; depth += 1) {
|
|
8839
|
+
if (current.name === owner) {
|
|
8840
|
+
return substitutions;
|
|
8841
|
+
}
|
|
8842
|
+
const next = parentSubstitutions(registry, current, substitutions);
|
|
8843
|
+
if (next === null) {
|
|
8844
|
+
return /* @__PURE__ */ new Map();
|
|
8845
|
+
}
|
|
8846
|
+
substitutions = next;
|
|
8847
|
+
current = current.superClass === null ? null : registry.lookupClass(current.superClass);
|
|
8848
|
+
}
|
|
8849
|
+
return /* @__PURE__ */ new Map();
|
|
8850
|
+
}
|
|
8851
|
+
function specializeMember(context, receiver, member, property) {
|
|
8852
|
+
const owner = context.declarations.lookupMemberOwner(receiver.name, property);
|
|
8853
|
+
if (owner === null || owner === receiver.name) {
|
|
8854
|
+
const own = context.declarations.lookupClass(receiver.name);
|
|
8855
|
+
if (own === null || own.typeParameters.length === 0 || (receiver.typeArguments ?? []).length === 0) {
|
|
8856
|
+
return member;
|
|
8857
|
+
}
|
|
8858
|
+
return { ...member, type: substituteType(member.type, classSubstitutions(own, receiver.typeArguments ?? [])) };
|
|
8859
|
+
}
|
|
8860
|
+
return { ...member, type: substituteType(member.type, substitutionsFor(context.declarations, receiver, owner)) };
|
|
8861
|
+
}
|
|
8862
|
+
function memberOf(context, receiver, property) {
|
|
8863
|
+
const member = context.declarations.lookupMember(receiver.name, property);
|
|
8864
|
+
return member === null ? null : specializeMember(context, receiver, member, property);
|
|
8865
|
+
}
|
|
8866
|
+
function unify(parameter, argument, names, bindings) {
|
|
8867
|
+
if (parameter.kind === "named" && names.has(parameter.name)) {
|
|
8868
|
+
if (!bindings.has(parameter.name) && argument.kind !== "nil") {
|
|
8869
|
+
bindings.set(parameter.name, argument);
|
|
8870
|
+
}
|
|
8871
|
+
return;
|
|
8872
|
+
}
|
|
8873
|
+
if (parameter.kind === "array" && argument.kind === "array") {
|
|
8874
|
+
unify(parameter.element, argument.element, names, bindings);
|
|
8875
|
+
return;
|
|
8876
|
+
}
|
|
8877
|
+
if (parameter.kind === "optional") {
|
|
8878
|
+
unify(parameter.element, argument.kind === "optional" ? argument.element : argument, names, bindings);
|
|
8879
|
+
return;
|
|
8880
|
+
}
|
|
8881
|
+
if (parameter.kind === "map" && argument.kind === "map") {
|
|
8882
|
+
unify(parameter.key, argument.key, names, bindings);
|
|
8883
|
+
unify(parameter.value, argument.value, names, bindings);
|
|
8884
|
+
return;
|
|
8885
|
+
}
|
|
8886
|
+
if (parameter.kind === "named" && argument.kind === "named" && parameter.name === argument.name) {
|
|
8887
|
+
const parameterArguments = parameter.typeArguments ?? [];
|
|
8888
|
+
const argumentArguments = argument.typeArguments ?? [];
|
|
8889
|
+
parameterArguments.forEach((entry, index) => unify(entry, argumentArguments[index] ?? ANY_TYPE, names, bindings));
|
|
8890
|
+
return;
|
|
8891
|
+
}
|
|
8892
|
+
if (parameter.kind === "function" && argument.kind === "function") {
|
|
8893
|
+
parameter.parameters.forEach((entry, index) => unify(entry, argument.parameters[index] ?? ANY_TYPE, names, bindings));
|
|
8894
|
+
unify(parameter.returnType, argument.returnType, names, bindings);
|
|
8895
|
+
}
|
|
8896
|
+
}
|
|
8897
|
+
function inferTypeArguments(typeParameters, parameters, args) {
|
|
8898
|
+
const names = new Set(typeParameters);
|
|
8899
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
8900
|
+
parameters.forEach((parameter, index) => {
|
|
8901
|
+
const argument = args[index];
|
|
8902
|
+
if (argument !== void 0) {
|
|
8903
|
+
unify(parameter, widenInferred(argument), names, bindings);
|
|
8904
|
+
}
|
|
8905
|
+
});
|
|
8906
|
+
return typeParameters.map((parameter) => bindings.get(parameter) ?? ANY_TYPE);
|
|
8907
|
+
}
|
|
8908
|
+
function inheritsFrom(context, name, target) {
|
|
8909
|
+
let current = context.declarations.lookupClass(name);
|
|
8910
|
+
for (let depth = 0; current !== null && depth < MAX_DEPTH; depth += 1) {
|
|
8911
|
+
if (current.name === target || current.interfaces.includes(target)) {
|
|
8912
|
+
return true;
|
|
8913
|
+
}
|
|
8914
|
+
current = current.superClass === null ? null : context.declarations.lookupClass(current.superClass);
|
|
8915
|
+
}
|
|
8916
|
+
return context.declarations.interfaceExtends(name, target);
|
|
8917
|
+
}
|
|
8918
|
+
function satisfies(context, argument, constraint) {
|
|
8919
|
+
if (argument.kind === "named" && constraint.kind === "named") {
|
|
8920
|
+
return argument.name === constraint.name || inheritsFrom(context, argument.name, constraint.name);
|
|
8921
|
+
}
|
|
8922
|
+
return isAssignable(argument, constraint, { allowNil: false });
|
|
8923
|
+
}
|
|
8924
|
+
function checkTypeConstraints(context, name, typeArguments, position2) {
|
|
8925
|
+
const info = context.declarations.lookupClass(name);
|
|
8926
|
+
if (info === null) {
|
|
8927
|
+
return;
|
|
8928
|
+
}
|
|
8929
|
+
info.typeConstraints.forEach((constraint, index) => {
|
|
8930
|
+
const argument = typeArguments[index];
|
|
8931
|
+
if (constraint === null || argument === void 0 || argument.kind === "any" || satisfies(context, argument, constraint)) {
|
|
8932
|
+
return;
|
|
8933
|
+
}
|
|
8934
|
+
const parameter = info.typeParameters[index] ?? "T";
|
|
8935
|
+
const message = `Type argument "${typeToString(argument)}" does not satisfy "${parameter} extends ${typeToString(constraint)}" on class "${name}".`;
|
|
8936
|
+
context.report("check-generic-constraint", message, position2);
|
|
8937
|
+
});
|
|
8938
|
+
}
|
|
8939
|
+
|
|
8369
8940
|
// ../mta-types/src/lua-standard.ts
|
|
8370
8941
|
var LUA_GLOBALS = {
|
|
8371
8942
|
_G: TABLE,
|
|
@@ -13203,6 +13774,18 @@ var DeclarationRegistry = class {
|
|
|
13203
13774
|
}
|
|
13204
13775
|
return null;
|
|
13205
13776
|
}
|
|
13777
|
+
lookupMemberOwner(name, member) {
|
|
13778
|
+
const visited = /* @__PURE__ */ new Set();
|
|
13779
|
+
let current = this.lookupClass(name);
|
|
13780
|
+
while (current !== null && !visited.has(current.name)) {
|
|
13781
|
+
if (current.members.has(member)) {
|
|
13782
|
+
return current.name;
|
|
13783
|
+
}
|
|
13784
|
+
visited.add(current.name);
|
|
13785
|
+
current = current.superClass === null ? null : this.lookupClass(current.superClass);
|
|
13786
|
+
}
|
|
13787
|
+
return null;
|
|
13788
|
+
}
|
|
13206
13789
|
lookupStaticMember(name, member) {
|
|
13207
13790
|
const visited = /* @__PURE__ */ new Set();
|
|
13208
13791
|
let current = this.lookupClass(name);
|
|
@@ -13262,7 +13845,17 @@ function buildRegistry() {
|
|
|
13262
13845
|
procedural: member.procedural
|
|
13263
13846
|
});
|
|
13264
13847
|
}
|
|
13265
|
-
registry.declareClass({
|
|
13848
|
+
registry.declareClass({
|
|
13849
|
+
name: declaration.name,
|
|
13850
|
+
typeParameters: [],
|
|
13851
|
+
typeConstraints: [],
|
|
13852
|
+
superClass: declaration.parent,
|
|
13853
|
+
superArguments: [],
|
|
13854
|
+
interfaces: [],
|
|
13855
|
+
members,
|
|
13856
|
+
statics: /* @__PURE__ */ new Map(),
|
|
13857
|
+
position: MTA_POSITION
|
|
13858
|
+
});
|
|
13266
13859
|
}
|
|
13267
13860
|
return registry;
|
|
13268
13861
|
}
|
|
@@ -13349,12 +13942,22 @@ function missingKeys(source, target) {
|
|
|
13349
13942
|
}
|
|
13350
13943
|
return missing2;
|
|
13351
13944
|
}
|
|
13352
|
-
function
|
|
13945
|
+
function expandCandidate(option, resolveNominal) {
|
|
13946
|
+
if (option.kind === "record") {
|
|
13947
|
+
return option;
|
|
13948
|
+
}
|
|
13949
|
+
if (option.kind !== "named") {
|
|
13950
|
+
return null;
|
|
13951
|
+
}
|
|
13952
|
+
const shape = resolveNominal?.(option.name) ?? null;
|
|
13953
|
+
return shape === null || shape.kind !== "interface" ? null : { kind: "record", name: option.name, origin: null, members: shape.members };
|
|
13954
|
+
}
|
|
13955
|
+
function missingKeyHint(source, target, resolveNominal) {
|
|
13353
13956
|
if (source.kind !== "record" || source.isLiteral !== true) {
|
|
13354
13957
|
return "";
|
|
13355
13958
|
}
|
|
13356
13959
|
const options = target.kind === "union" ? target.options : [target];
|
|
13357
|
-
const candidates3 = options.
|
|
13960
|
+
const candidates3 = options.map((option) => expandCandidate(option, resolveNominal)).filter((option) => option !== null);
|
|
13358
13961
|
const candidate = chooseCandidate(source, candidates3);
|
|
13359
13962
|
if (candidate === null) {
|
|
13360
13963
|
return "";
|
|
@@ -13402,39 +14005,6 @@ function mergeIntersection(context, parts, position2) {
|
|
|
13402
14005
|
return createObjectType(members);
|
|
13403
14006
|
}
|
|
13404
14007
|
|
|
13405
|
-
// ../compiler/src/checker/type-substitution.ts
|
|
13406
|
-
function substituteType(type, substitutions) {
|
|
13407
|
-
if (type.kind === "named") {
|
|
13408
|
-
return substitutions.get(type.name) ?? type;
|
|
13409
|
-
}
|
|
13410
|
-
if (type.kind === "array") {
|
|
13411
|
-
return createArray(substituteType(type.element, substitutions));
|
|
13412
|
-
}
|
|
13413
|
-
if (type.kind === "map") {
|
|
13414
|
-
return createMap(substituteType(type.key, substitutions), substituteType(type.value, substitutions));
|
|
13415
|
-
}
|
|
13416
|
-
if (type.kind === "optional") {
|
|
13417
|
-
return createOptional(substituteType(type.element, substitutions));
|
|
13418
|
-
}
|
|
13419
|
-
if (type.kind === "union") {
|
|
13420
|
-
return createUnion(type.options.map((option) => substituteType(option, substitutions)));
|
|
13421
|
-
}
|
|
13422
|
-
if (type.kind === "function") {
|
|
13423
|
-
const parameters = type.parameters.map((parameter) => substituteType(parameter, substitutions));
|
|
13424
|
-
const returnType = substituteType(type.returnType, substitutions);
|
|
13425
|
-
const variadicType = type.variadicType === void 0 ? void 0 : substituteType(type.variadicType, substitutions);
|
|
13426
|
-
return createFunction(parameters, returnType, type.minimumArguments, type.isVariadic, type.parameterNames, variadicType);
|
|
13427
|
-
}
|
|
13428
|
-
if (type.kind === "tuple") {
|
|
13429
|
-
return createTuple(type.elements.map((element) => substituteType(element, substitutions)));
|
|
13430
|
-
}
|
|
13431
|
-
if (type.kind === "record") {
|
|
13432
|
-
const members = new Map([...type.members].map(([name, member]) => [name, substituteType(member, substitutions)]));
|
|
13433
|
-
return createRecord(type.name, members, type.origin);
|
|
13434
|
-
}
|
|
13435
|
-
return type;
|
|
13436
|
-
}
|
|
13437
|
-
|
|
13438
14008
|
// ../compiler/src/checker/context.ts
|
|
13439
14009
|
var BUILTIN_TYPES = {
|
|
13440
14010
|
any: ANY_TYPE,
|
|
@@ -13450,6 +14020,7 @@ var BUILTIN_TYPES = {
|
|
|
13450
14020
|
};
|
|
13451
14021
|
var TABLE_ANNOTATION = "table";
|
|
13452
14022
|
var MAP_ARGUMENTS = 2;
|
|
14023
|
+
var MAX_GENERIC_DEPTH = 8;
|
|
13453
14024
|
var PROJECT_POSITION = { line: 0, column: 0, offset: 0 };
|
|
13454
14025
|
function nilHint(source, target, mode) {
|
|
13455
14026
|
if (mode !== "strict" || source.kind !== "nil" || target.kind === "nil" || target.kind === "optional") {
|
|
@@ -13462,12 +14033,15 @@ var CheckContext = class {
|
|
|
13462
14033
|
declarations = new DeclarationRegistry();
|
|
13463
14034
|
diagnostics = [];
|
|
13464
14035
|
reported = /* @__PURE__ */ new Set();
|
|
13465
|
-
|
|
14036
|
+
flow = createFlow();
|
|
13466
14037
|
types = /* @__PURE__ */ new Map();
|
|
13467
14038
|
references = /* @__PURE__ */ new Set();
|
|
13468
14039
|
declaredGlobals = /* @__PURE__ */ new Map();
|
|
14040
|
+
moduleGlobals = /* @__PURE__ */ new Map();
|
|
13469
14041
|
externalReferences = /* @__PURE__ */ new Map();
|
|
13470
14042
|
unknownTypes = /* @__PURE__ */ new Map();
|
|
14043
|
+
eventReferences = /* @__PURE__ */ new Set();
|
|
14044
|
+
globalReferences = /* @__PURE__ */ new Set();
|
|
13471
14045
|
calledMembers = /* @__PURE__ */ new Set();
|
|
13472
14046
|
staticAccess = /* @__PURE__ */ new Set();
|
|
13473
14047
|
typeParameters = /* @__PURE__ */ new Set();
|
|
@@ -13475,6 +14049,7 @@ var CheckContext = class {
|
|
|
13475
14049
|
mode;
|
|
13476
14050
|
environment;
|
|
13477
14051
|
mtaClasses;
|
|
14052
|
+
contracts = [];
|
|
13478
14053
|
isDeclarationFile = false;
|
|
13479
14054
|
returnStack = [];
|
|
13480
14055
|
methodStack = [];
|
|
@@ -13500,33 +14075,28 @@ var CheckContext = class {
|
|
|
13500
14075
|
declareModuleGlobal(symbol) {
|
|
13501
14076
|
this.binder.declareGlobal(symbol);
|
|
13502
14077
|
this.declaredGlobals.set(symbol.name, symbol.position);
|
|
14078
|
+
this.moduleGlobals.set(symbol.name, symbol.type);
|
|
13503
14079
|
}
|
|
13504
|
-
|
|
13505
|
-
this.
|
|
14080
|
+
get flowState() {
|
|
14081
|
+
return this.flow;
|
|
13506
14082
|
}
|
|
13507
|
-
|
|
13508
|
-
|
|
14083
|
+
setFlow(state) {
|
|
14084
|
+
this.flow = state;
|
|
14085
|
+
}
|
|
14086
|
+
applyFlowFacts(facts) {
|
|
14087
|
+
applyFacts(this.flow, facts);
|
|
14088
|
+
}
|
|
14089
|
+
markUnreachable() {
|
|
14090
|
+
this.flow.reachable = false;
|
|
13509
14091
|
}
|
|
13510
|
-
|
|
13511
|
-
this.
|
|
14092
|
+
get isNarrowed() {
|
|
14093
|
+
return this.flow.facts.size > 0;
|
|
13512
14094
|
}
|
|
13513
14095
|
forgetNarrowing(path) {
|
|
13514
|
-
|
|
13515
|
-
for (const key of [...frame.keys()]) {
|
|
13516
|
-
if (key === path || key.startsWith(`${path}.`) || path.startsWith(`${key}.`)) {
|
|
13517
|
-
frame.delete(key);
|
|
13518
|
-
}
|
|
13519
|
-
}
|
|
13520
|
-
}
|
|
14096
|
+
forgetPath(this.flow, path);
|
|
13521
14097
|
}
|
|
13522
14098
|
narrowedType(path) {
|
|
13523
|
-
|
|
13524
|
-
const found = this.narrowings[index]?.get(path);
|
|
13525
|
-
if (found !== void 0) {
|
|
13526
|
-
return found;
|
|
13527
|
-
}
|
|
13528
|
-
}
|
|
13529
|
-
return null;
|
|
14099
|
+
return this.flow.facts.get(path) ?? null;
|
|
13530
14100
|
}
|
|
13531
14101
|
predeclareClass(info) {
|
|
13532
14102
|
this.predeclared.set(info.name, info);
|
|
@@ -13593,6 +14163,12 @@ var CheckContext = class {
|
|
|
13593
14163
|
isTypeParameter(name) {
|
|
13594
14164
|
return this.typeParameters.has(name);
|
|
13595
14165
|
}
|
|
14166
|
+
noteGlobalReference(name) {
|
|
14167
|
+
this.globalReferences.add(name);
|
|
14168
|
+
}
|
|
14169
|
+
noteEventReference(name) {
|
|
14170
|
+
this.eventReferences.add(name);
|
|
14171
|
+
}
|
|
13596
14172
|
noteUnknownType(name, position2) {
|
|
13597
14173
|
if (!this.unknownTypes.has(name)) {
|
|
13598
14174
|
this.unknownTypes.set(name, position2);
|
|
@@ -13682,17 +14258,57 @@ var CheckContext = class {
|
|
|
13682
14258
|
const parameters = annotation.parameters.map((parameter) => this.resolveAnnotation(parameter));
|
|
13683
14259
|
const optional = parameters.findIndex((parameter) => parameter.kind === "optional");
|
|
13684
14260
|
const minimum = optional === -1 ? parameters.length : optional;
|
|
13685
|
-
return createFunction(parameters, this.resolveAnnotation(annotation.returnType), minimum, annotation.isVariadic);
|
|
14261
|
+
return createFunction(parameters, this.resolveAnnotation(annotation.returnType), minimum, annotation.isVariadic, [...annotation.parameterNames]);
|
|
13686
14262
|
}
|
|
13687
14263
|
return this.resolveNamedAnnotation(annotation.name, annotation.typeArguments, annotation.position);
|
|
13688
14264
|
}
|
|
14265
|
+
nominalShape(name) {
|
|
14266
|
+
const declaredInterface = this.declarations.lookupInterface(name);
|
|
14267
|
+
if (declaredInterface !== null) {
|
|
14268
|
+
const ancestors = this.declarations.allInterfaces().filter((other) => this.declarations.interfaceExtends(name, other.name));
|
|
14269
|
+
return {
|
|
14270
|
+
kind: "interface",
|
|
14271
|
+
members: new Map(this.declarations.collectInterfaceContract(name).map((member) => [member.name, member.type])),
|
|
14272
|
+
ancestors: new Set(ancestors.map((other) => other.name))
|
|
14273
|
+
};
|
|
14274
|
+
}
|
|
14275
|
+
const declaredClass = this.declarations.lookupClass(name);
|
|
14276
|
+
if (declaredClass === null) {
|
|
14277
|
+
return null;
|
|
14278
|
+
}
|
|
14279
|
+
return {
|
|
14280
|
+
kind: "class",
|
|
14281
|
+
members: new Map(this.declarations.collectMembers(name).map((member) => [member.name, member.type])),
|
|
14282
|
+
ancestors: this.classAncestors(name)
|
|
14283
|
+
};
|
|
14284
|
+
}
|
|
14285
|
+
classAncestors(name) {
|
|
14286
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
14287
|
+
let current = this.declarations.lookupClass(name);
|
|
14288
|
+
while (current !== null && !ancestors.has(current.name)) {
|
|
14289
|
+
ancestors.add(current.name);
|
|
14290
|
+
for (const contract of current.interfaces) {
|
|
14291
|
+
ancestors.add(contract);
|
|
14292
|
+
for (const other of this.declarations.allInterfaces()) {
|
|
14293
|
+
if (this.declarations.interfaceExtends(contract, other.name)) {
|
|
14294
|
+
ancestors.add(other.name);
|
|
14295
|
+
}
|
|
14296
|
+
}
|
|
14297
|
+
}
|
|
14298
|
+
current = current.superClass === null ? null : this.declarations.lookupClass(current.superClass);
|
|
14299
|
+
}
|
|
14300
|
+
return ancestors;
|
|
14301
|
+
}
|
|
14302
|
+
assignability() {
|
|
14303
|
+
return { allowNil: this.allowNil, resolveNominal: (name) => this.nominalShape(name) };
|
|
14304
|
+
}
|
|
13689
14305
|
expectAssignable(source, target, position2, subject) {
|
|
13690
|
-
if (isAssignable(source, target,
|
|
14306
|
+
if (isAssignable(source, target, this.assignability())) {
|
|
13691
14307
|
return;
|
|
13692
14308
|
}
|
|
13693
14309
|
const received = isLiteralType(target) || target.kind === "union" ? source : widenLiteral(source);
|
|
13694
14310
|
const message = `${subject} expects "${typeToString(target)}" but received "${typeToString(received)}".`;
|
|
13695
|
-
const hint = `${nilHint(source, target, this.mode)}${missingKeyHint(source, target)}`;
|
|
14311
|
+
const hint = `${nilHint(source, target, this.mode)}${missingKeyHint(source, target, (name) => this.nominalShape(name))}`;
|
|
13696
14312
|
this.report("check-type-mismatch", `${message}${hint}`, position2);
|
|
13697
14313
|
}
|
|
13698
14314
|
declareProject(project) {
|
|
@@ -13738,6 +14354,25 @@ var CheckContext = class {
|
|
|
13738
14354
|
}
|
|
13739
14355
|
return createMap(this.resolveAnnotation(key), this.resolveAnnotation(value));
|
|
13740
14356
|
}
|
|
14357
|
+
resolveClassAnnotation(name, typeArguments, position2) {
|
|
14358
|
+
const declared = this.declarations.lookupClass(name)?.typeParameters ?? [];
|
|
14359
|
+
const resolved2 = typeArguments.map((argument) => this.resolveAnnotation(argument));
|
|
14360
|
+
if (declared.length === 0) {
|
|
14361
|
+
return createNamed(name);
|
|
14362
|
+
}
|
|
14363
|
+
if (resolved2.length !== declared.length) {
|
|
14364
|
+
const expected = declared.length === 1 ? "1 type argument" : `${declared.length} type arguments`;
|
|
14365
|
+
this.report("check-generic-arity", `Class "${name}" expects ${expected} but received ${resolved2.length}.`, position2);
|
|
14366
|
+
}
|
|
14367
|
+
const specialized = createNamed(name, declared.map((unused, index) => resolved2[index] ?? ANY_TYPE));
|
|
14368
|
+
if (namedNesting(specialized) > MAX_GENERIC_DEPTH) {
|
|
14369
|
+
const message = `Class "${name}" is nested more than ${MAX_GENERIC_DEPTH} levels deep here. Name the inner type with a "type" alias.`;
|
|
14370
|
+
this.report("check-generic-depth", message, position2);
|
|
14371
|
+
return createNamed(name);
|
|
14372
|
+
}
|
|
14373
|
+
checkTypeConstraints(this, name, specialized.typeArguments ?? [], position2);
|
|
14374
|
+
return specialized;
|
|
14375
|
+
}
|
|
13741
14376
|
resolveNamedAnnotation(name, typeArguments, position2) {
|
|
13742
14377
|
const builtin = BUILTIN_TYPES[name];
|
|
13743
14378
|
if (builtin !== void 0) {
|
|
@@ -13752,7 +14387,7 @@ var CheckContext = class {
|
|
|
13752
14387
|
const alias = this.binder.lookupAlias(name);
|
|
13753
14388
|
if (alias === null) {
|
|
13754
14389
|
this.noteUnknownType(name, position2);
|
|
13755
|
-
return
|
|
14390
|
+
return this.resolveClassAnnotation(name, typeArguments, position2);
|
|
13756
14391
|
}
|
|
13757
14392
|
if (typeArguments.length !== alias.parameters.length) {
|
|
13758
14393
|
const expected = alias.parameters.length === 1 ? "1 type argument" : `${alias.parameters.length} type arguments`;
|
|
@@ -14009,6 +14644,73 @@ function checkOverrides(context, info, statement) {
|
|
|
14009
14644
|
}
|
|
14010
14645
|
}
|
|
14011
14646
|
|
|
14647
|
+
// ../compiler/src/checker/metamethods.ts
|
|
14648
|
+
var CONTRACTS = [
|
|
14649
|
+
{ name: "__tostring", parameters: 0, returns: "string", documentation: 'Renders the instance for "tostring" and string coercion.' },
|
|
14650
|
+
{ name: "__eq", parameters: 1, returns: "boolean", documentation: 'Compares two instances of the same class with "==".' },
|
|
14651
|
+
{ name: "__lt", parameters: 1, returns: "boolean", documentation: 'Orders two instances with "<" and ">".' },
|
|
14652
|
+
{ name: "__le", parameters: 1, returns: "boolean", documentation: 'Orders two instances with "<=" and ">=".' },
|
|
14653
|
+
{ name: "__len", parameters: 0, returns: "number", documentation: 'Answers the length operator "#".' },
|
|
14654
|
+
{ name: "__concat", parameters: 1, returns: "any", documentation: 'Answers the concatenation operator "..".' },
|
|
14655
|
+
{ name: "__unm", parameters: 0, returns: "any", documentation: "Answers unary minus." },
|
|
14656
|
+
{ name: "__add", parameters: 1, returns: "any", documentation: 'Answers "+".' },
|
|
14657
|
+
{ name: "__sub", parameters: 1, returns: "any", documentation: 'Answers "-".' },
|
|
14658
|
+
{ name: "__mul", parameters: 1, returns: "any", documentation: 'Answers "*".' },
|
|
14659
|
+
{ name: "__div", parameters: 1, returns: "any", documentation: 'Answers "/".' },
|
|
14660
|
+
{ name: "__mod", parameters: 1, returns: "any", documentation: 'Answers "%".' },
|
|
14661
|
+
{ name: "__pow", parameters: 1, returns: "any", documentation: 'Answers "^".' }
|
|
14662
|
+
];
|
|
14663
|
+
var BLOCKED = {
|
|
14664
|
+
__index: "it replaces member lookup, which the class helper owns",
|
|
14665
|
+
__newindex: "it swallows a field write, which the class helper owns",
|
|
14666
|
+
__call: "it makes an instance callable, which hides construction",
|
|
14667
|
+
__gc: "Lua 5.1 does not run it for a table",
|
|
14668
|
+
__metatable: "it hides the metatable the class helper needs",
|
|
14669
|
+
__mode: "it turns instances into weak references the class helper cannot track"
|
|
14670
|
+
};
|
|
14671
|
+
var METAMETHOD_CONTRACTS = new Map(CONTRACTS.map((contract) => [contract.name, contract]));
|
|
14672
|
+
function isMetamethodName(name) {
|
|
14673
|
+
return name.startsWith("__");
|
|
14674
|
+
}
|
|
14675
|
+
function isAllowedMetamethod(name) {
|
|
14676
|
+
return METAMETHOD_CONTRACTS.has(name);
|
|
14677
|
+
}
|
|
14678
|
+
function isMetamethodMember(member) {
|
|
14679
|
+
return member.kind === "class-method" && !member.isStatic && isAllowedMetamethod(member.name);
|
|
14680
|
+
}
|
|
14681
|
+
function reportReturn(context, className, contract, type, member) {
|
|
14682
|
+
if (contract.returns === "any" || type.kind === "any" || type.kind === contract.returns) {
|
|
14683
|
+
return;
|
|
14684
|
+
}
|
|
14685
|
+
const message = `Metamethod "${contract.name}" of class "${className}" must return "${contract.returns}" but returns "${typeToString(type)}".`;
|
|
14686
|
+
context.report("check-invalid-metamethod", message, member.position);
|
|
14687
|
+
}
|
|
14688
|
+
function checkMetamethod(context, className, member, type) {
|
|
14689
|
+
const contract = METAMETHOD_CONTRACTS.get(member.name);
|
|
14690
|
+
if (contract === void 0 || type.kind !== "function") {
|
|
14691
|
+
return;
|
|
14692
|
+
}
|
|
14693
|
+
if (type.parameters.length !== contract.parameters) {
|
|
14694
|
+
const expected = contract.parameters === 1 ? "1 parameter" : `${contract.parameters} parameters`;
|
|
14695
|
+
const message = `Metamethod "${contract.name}" of class "${className}" takes ${expected} beside "self" but declares ${type.parameters.length}.`;
|
|
14696
|
+
context.report("check-invalid-metamethod", message, member.position);
|
|
14697
|
+
}
|
|
14698
|
+
reportReturn(context, className, contract, type.returnType, member);
|
|
14699
|
+
}
|
|
14700
|
+
function reportRejectedMetamethod(context, className, member) {
|
|
14701
|
+
if (member.kind !== "class-method" || !isMetamethodName(member.name) || isAllowedMetamethod(member.name)) {
|
|
14702
|
+
return;
|
|
14703
|
+
}
|
|
14704
|
+
const blocked = BLOCKED[member.name];
|
|
14705
|
+
if (blocked !== void 0) {
|
|
14706
|
+
context.report("check-blocked-metamethod", `Metamethod "${member.name}" cannot be declared on class "${className}" because ${blocked}.`, member.position);
|
|
14707
|
+
return;
|
|
14708
|
+
}
|
|
14709
|
+
const known = [...METAMETHOD_CONTRACTS.keys()].map((name) => `"${name}"`).join(", ");
|
|
14710
|
+
const message = `"${member.name}" is not a metamethod Luam exposes on class "${className}". Declared metamethods are ${known}.`;
|
|
14711
|
+
context.report("check-blocked-metamethod", message, member.position);
|
|
14712
|
+
}
|
|
14713
|
+
|
|
14012
14714
|
// ../compiler/src/checker/accessor-names.ts
|
|
14013
14715
|
function capitalize(name) {
|
|
14014
14716
|
return `${name.charAt(0).toUpperCase()}${name.slice(1)}`;
|
|
@@ -14093,6 +14795,25 @@ var KNOWN_DECORATORS = /* @__PURE__ */ new Map([
|
|
|
14093
14795
|
diagnostics: [CONFLICT]
|
|
14094
14796
|
}
|
|
14095
14797
|
],
|
|
14798
|
+
[
|
|
14799
|
+
"Validated",
|
|
14800
|
+
{
|
|
14801
|
+
name: "Validated",
|
|
14802
|
+
documentation: "Generates runtime checks for the declared field types.",
|
|
14803
|
+
targets: ["class"],
|
|
14804
|
+
generates: [
|
|
14805
|
+
"`ClassName.validate(value: table): ClassName`, returning the value or raising a Lua error naming the path and the expected type.",
|
|
14806
|
+
"`ClassName.matches(value: table): boolean`, the same check without raising."
|
|
14807
|
+
],
|
|
14808
|
+
rules: [
|
|
14809
|
+
"Both are static, so they are called on the class and never on an instance.",
|
|
14810
|
+
"The check walks the declared field types with a depth, entry-count and string-length limit, and a failure names the path and the expected type but never the value.",
|
|
14811
|
+
"A field whose type has no runtime shape is `check-unreifiable-type`, reported before anything is emitted.",
|
|
14812
|
+
"It is the only decorator that adds runtime code beyond the class helper; a class without it emits nothing extra."
|
|
14813
|
+
],
|
|
14814
|
+
diagnostics: [CONFLICT, "`check-unreifiable-type` when a field type cannot be checked at runtime."]
|
|
14815
|
+
}
|
|
14816
|
+
],
|
|
14096
14817
|
[
|
|
14097
14818
|
"Serializable",
|
|
14098
14819
|
{
|
|
@@ -14183,6 +14904,102 @@ var KNOWN_DECORATORS = /* @__PURE__ */ new Map([
|
|
|
14183
14904
|
]
|
|
14184
14905
|
]);
|
|
14185
14906
|
|
|
14907
|
+
// ../compiler/src/checker/validation-descriptor.ts
|
|
14908
|
+
var MAX_EXPANSION = 8;
|
|
14909
|
+
var PRIMITIVE_KINDS = {
|
|
14910
|
+
nil: "nil",
|
|
14911
|
+
boolean: "boolean",
|
|
14912
|
+
number: "number",
|
|
14913
|
+
string: "string",
|
|
14914
|
+
table: "table",
|
|
14915
|
+
thread: "thread",
|
|
14916
|
+
userdata: "userdata",
|
|
14917
|
+
function: "function"
|
|
14918
|
+
};
|
|
14919
|
+
function quote2(value) {
|
|
14920
|
+
return `'${value.replace(/([\\'])/g, "\\$1").replace(/\n/g, "\\n")}'`;
|
|
14921
|
+
}
|
|
14922
|
+
function failed(type) {
|
|
14923
|
+
return { lua: null, unreifiable: typeToString(type) };
|
|
14924
|
+
}
|
|
14925
|
+
function entries2(parts) {
|
|
14926
|
+
return `{ ${parts.join(", ")} }`;
|
|
14927
|
+
}
|
|
14928
|
+
function combine(parts, render2) {
|
|
14929
|
+
const broken = parts.find((part) => part.lua === null);
|
|
14930
|
+
if (broken !== void 0) {
|
|
14931
|
+
return broken;
|
|
14932
|
+
}
|
|
14933
|
+
return { lua: render2(parts.map((part) => part.lua)), unreifiable: null };
|
|
14934
|
+
}
|
|
14935
|
+
function namedDescriptor(context, type, name, seen) {
|
|
14936
|
+
if (context.declarations.lookupClass(name) !== null) {
|
|
14937
|
+
return { lua: entries2([`kind = 'instance'`, `name = ${quote2(name)}`]), unreifiable: null };
|
|
14938
|
+
}
|
|
14939
|
+
if (context.declarations.lookupEnum(name) !== null) {
|
|
14940
|
+
return { lua: entries2([`kind = 'number'`]), unreifiable: null };
|
|
14941
|
+
}
|
|
14942
|
+
if (context.declarations.lookupInterface(name) === null || seen.has(name) || seen.size >= MAX_EXPANSION) {
|
|
14943
|
+
return failed(type);
|
|
14944
|
+
}
|
|
14945
|
+
const members = context.declarations.collectMembers(name);
|
|
14946
|
+
const nested = /* @__PURE__ */ new Set([...seen, name]);
|
|
14947
|
+
const parts = members.map((member) => describe(context, member.type, nested));
|
|
14948
|
+
return combine(parts, (values) => {
|
|
14949
|
+
const rendered = members.map((member, index) => entries2([`key = ${quote2(member.name)}`, `value = ${values[index] ?? ""}`]));
|
|
14950
|
+
return entries2([`kind = 'record'`, `name = ${quote2(name)}`, `members = ${entries2(rendered)}`]);
|
|
14951
|
+
});
|
|
14952
|
+
}
|
|
14953
|
+
function describe(context, type, seen = /* @__PURE__ */ new Set()) {
|
|
14954
|
+
const primitive = PRIMITIVE_KINDS[type.kind];
|
|
14955
|
+
if (primitive !== void 0) {
|
|
14956
|
+
return { lua: entries2([`kind = ${quote2(primitive)}`]), unreifiable: null };
|
|
14957
|
+
}
|
|
14958
|
+
if (type.kind === "string-literal") {
|
|
14959
|
+
return { lua: entries2([`kind = 'literal'`, `value = ${quote2(type.value)}`]), unreifiable: null };
|
|
14960
|
+
}
|
|
14961
|
+
if (type.kind === "boolean-literal" || type.kind === "number-literal") {
|
|
14962
|
+
return { lua: entries2([`kind = 'literal'`, `value = ${String(type.value)}`]), unreifiable: null };
|
|
14963
|
+
}
|
|
14964
|
+
if (type.kind === "optional") {
|
|
14965
|
+
return combine([describe(context, type.element, seen)], (values) => entries2([`kind = 'optional'`, `element = ${values[0] ?? ""}`]));
|
|
14966
|
+
}
|
|
14967
|
+
if (type.kind === "array") {
|
|
14968
|
+
return combine([describe(context, type.element, seen)], (values) => entries2([`kind = 'array'`, `element = ${values[0] ?? ""}`]));
|
|
14969
|
+
}
|
|
14970
|
+
if (type.kind === "map") {
|
|
14971
|
+
return combine(
|
|
14972
|
+
[describe(context, type.key, seen), describe(context, type.value, seen)],
|
|
14973
|
+
(values) => entries2([`kind = 'map'`, `key = ${values[0] ?? ""}`, `value = ${values[1] ?? ""}`])
|
|
14974
|
+
);
|
|
14975
|
+
}
|
|
14976
|
+
if (type.kind === "union") {
|
|
14977
|
+
return combine(
|
|
14978
|
+
type.options.map((option) => describe(context, option, seen)),
|
|
14979
|
+
(values) => entries2([`kind = 'union'`, `options = ${entries2(values)}`])
|
|
14980
|
+
);
|
|
14981
|
+
}
|
|
14982
|
+
if (type.kind === "record") {
|
|
14983
|
+
const members = [...type.members];
|
|
14984
|
+
const parts = members.map(([, member]) => describe(context, member, seen));
|
|
14985
|
+
return combine(parts, (values) => {
|
|
14986
|
+
const rendered = members.map(([key], index) => entries2([`key = ${quote2(key)}`, `value = ${values[index] ?? ""}`]));
|
|
14987
|
+
return entries2([`kind = 'record'`, `name = ${quote2(type.name)}`, `members = ${entries2(rendered)}`]);
|
|
14988
|
+
});
|
|
14989
|
+
}
|
|
14990
|
+
if (type.kind === "named") {
|
|
14991
|
+
return namedDescriptor(context, type, type.name, seen);
|
|
14992
|
+
}
|
|
14993
|
+
return failed(type);
|
|
14994
|
+
}
|
|
14995
|
+
function classDescriptor(context, className, fields) {
|
|
14996
|
+
const parts = fields.map((field2) => describe(context, field2.type));
|
|
14997
|
+
return combine(parts, (values) => {
|
|
14998
|
+
const rendered = fields.map((field2, index) => entries2([`key = ${quote2(field2.name)}`, `value = ${values[index] ?? ""}`]));
|
|
14999
|
+
return entries2([`kind = 'record'`, `name = ${quote2(className)}`, `members = ${entries2(rendered)}`]);
|
|
15000
|
+
});
|
|
15001
|
+
}
|
|
15002
|
+
|
|
14186
15003
|
// ../compiler/src/checker/decorators.ts
|
|
14187
15004
|
function memberExpression(field2) {
|
|
14188
15005
|
return {
|
|
@@ -14248,6 +15065,46 @@ function validateDecorators(context, decorators, target) {
|
|
|
14248
15065
|
function decoratorFor(name, classDecorators, fieldDecorators) {
|
|
14249
15066
|
return fieldDecorators.find((decorator) => decorator.name === name) ?? classDecorators.find((decorator) => decorator.name === name) ?? null;
|
|
14250
15067
|
}
|
|
15068
|
+
function staticMethod(statement, name, returnAnnotation, kind, descriptor) {
|
|
15069
|
+
const value = { name: "value", annotation: typeName("table", statement), isVararg: false, position: statement.position };
|
|
15070
|
+
return {
|
|
15071
|
+
kind: "class-method",
|
|
15072
|
+
name,
|
|
15073
|
+
isConstructor: false,
|
|
15074
|
+
isSynthetic: true,
|
|
15075
|
+
isStatic: true,
|
|
15076
|
+
parameters: [value],
|
|
15077
|
+
returnAnnotation,
|
|
15078
|
+
body: [],
|
|
15079
|
+
decorators: [],
|
|
15080
|
+
generated: { kind, fields: [], descriptor },
|
|
15081
|
+
position: statement.position
|
|
15082
|
+
};
|
|
15083
|
+
}
|
|
15084
|
+
function expandValidated(context, statement, classDecorators, fieldTypes, occupied, generated) {
|
|
15085
|
+
const found = classDecorators.find((decorator) => decorator.name === "Validated");
|
|
15086
|
+
if (found === void 0) {
|
|
15087
|
+
return;
|
|
15088
|
+
}
|
|
15089
|
+
const fields = [...fieldTypes].map(([field2, type]) => ({ name: field2.name, type, position: field2.position }));
|
|
15090
|
+
const described = classDescriptor(context, statement.name, fields);
|
|
15091
|
+
if (described.lua === null) {
|
|
15092
|
+
const message = `Decorator "@Validated" cannot check type "${described.unreifiable}", which has no runtime shape. Remove the field, or narrow it to a checkable type.`;
|
|
15093
|
+
context.report("check-unreifiable-type", message, found.position);
|
|
15094
|
+
return;
|
|
15095
|
+
}
|
|
15096
|
+
for (const [name, kind, annotation] of [
|
|
15097
|
+
["validate", "validate", typeName(statement.name, statement)],
|
|
15098
|
+
["matches", "matches", typeName("boolean", statement)]
|
|
15099
|
+
]) {
|
|
15100
|
+
if (occupied.has(name)) {
|
|
15101
|
+
context.report("check-decorator-conflict", `Decorator "@Validated" would generate "${name}", which is already declared by "${occupied.get(name)}".`, found.position);
|
|
15102
|
+
continue;
|
|
15103
|
+
}
|
|
15104
|
+
occupied.set(name, statement.name);
|
|
15105
|
+
generated.push(staticMethod(statement, name, annotation, kind, described.lua));
|
|
15106
|
+
}
|
|
15107
|
+
}
|
|
14251
15108
|
function expandClassDecorators(context, statement, fieldTypes) {
|
|
14252
15109
|
const generated = [];
|
|
14253
15110
|
const occupied = new Map(statement.members.filter((member) => member.kind === "class-method").map((member) => [member.name, member.name]));
|
|
@@ -14309,6 +15166,7 @@ function expandClassDecorators(context, statement, fieldTypes) {
|
|
|
14309
15166
|
Serializable: ["toTable", typeName("table", statement), "serializable"],
|
|
14310
15167
|
Deserialize: ["fromTable", typeName("void", statement), "deserialize"]
|
|
14311
15168
|
};
|
|
15169
|
+
expandValidated(context, statement, classDecorators, fieldTypes, occupied, generated);
|
|
14312
15170
|
for (const [decorator, [name, returnAnnotation, kind]] of Object.entries(classMethods)) {
|
|
14313
15171
|
const found = classDecorators.find((value) => value.name === decorator);
|
|
14314
15172
|
if (found !== void 0 && !occupied.has(name)) {
|
|
@@ -17567,6 +18425,9 @@ function checkEventUsage(context, expression) {
|
|
|
17567
18425
|
return;
|
|
17568
18426
|
}
|
|
17569
18427
|
const name = eventName(expression);
|
|
18428
|
+
if (name !== null) {
|
|
18429
|
+
context.noteEventReference(name);
|
|
18430
|
+
}
|
|
17570
18431
|
const custom = name === null ? null : context.declarations.lookupEvent(name);
|
|
17571
18432
|
const declared = name === null ? null : eventEnvironment(name);
|
|
17572
18433
|
if (name === null || custom?.environment === "shared" || custom?.environment === context.environment || declared === null || declared === context.environment) {
|
|
@@ -17709,6 +18570,105 @@ function specializeEventCall(context, expression, signature) {
|
|
|
17709
18570
|
return candidate === null ? null : specialize(signature, candidate);
|
|
17710
18571
|
}
|
|
17711
18572
|
|
|
18573
|
+
// ../compiler/src/checker/method-receiver.ts
|
|
18574
|
+
var SELF_PARAMETER = "self";
|
|
18575
|
+
function resolveNonNominalMethod(receiver, method) {
|
|
18576
|
+
if (receiver.kind !== "record") {
|
|
18577
|
+
return null;
|
|
18578
|
+
}
|
|
18579
|
+
const member = receiver.members.get(method);
|
|
18580
|
+
return member !== void 0 && member.kind === "function" ? member : null;
|
|
18581
|
+
}
|
|
18582
|
+
function withoutSelfParameter(signature) {
|
|
18583
|
+
const names = signature.parameterNames;
|
|
18584
|
+
if (names === void 0 || names[0] !== SELF_PARAMETER) {
|
|
18585
|
+
return signature;
|
|
18586
|
+
}
|
|
18587
|
+
const minimum = Math.max(0, signature.minimumArguments - 1);
|
|
18588
|
+
return createFunction(signature.parameters.slice(1), signature.returnType, minimum, signature.isVariadic, names.slice(1), signature.variadicType);
|
|
18589
|
+
}
|
|
18590
|
+
|
|
18591
|
+
// ../compiler/src/checker/resource-exports.ts
|
|
18592
|
+
var RESOURCE_LOOKUP = "getResourceFromName";
|
|
18593
|
+
var EXPORTS_TABLE = "exports";
|
|
18594
|
+
var CALL_FUNCTION = "call";
|
|
18595
|
+
function literalString(expression) {
|
|
18596
|
+
return expression?.kind === "string-literal" ? expression.value : null;
|
|
18597
|
+
}
|
|
18598
|
+
function resourceOfLookup(expression) {
|
|
18599
|
+
if (expression?.kind !== "call-expression" || expression.callee.kind !== "identifier" || expression.callee.name !== RESOURCE_LOOKUP) {
|
|
18600
|
+
return null;
|
|
18601
|
+
}
|
|
18602
|
+
return literalString(expression.args[0]);
|
|
18603
|
+
}
|
|
18604
|
+
function resourceOfExports(expression) {
|
|
18605
|
+
if (expression.kind === "member-expression" && expression.object.kind === "identifier" && expression.object.name === EXPORTS_TABLE) {
|
|
18606
|
+
return expression.property;
|
|
18607
|
+
}
|
|
18608
|
+
if (expression.kind !== "index-expression" || expression.object.kind !== "identifier" || expression.object.name !== EXPORTS_TABLE) {
|
|
18609
|
+
return null;
|
|
18610
|
+
}
|
|
18611
|
+
return literalString(expression.index);
|
|
18612
|
+
}
|
|
18613
|
+
function abiType(context, text) {
|
|
18614
|
+
const scanned = scan(text);
|
|
18615
|
+
if (scanned.diagnostics.length > 0) {
|
|
18616
|
+
return ANY_TYPE;
|
|
18617
|
+
}
|
|
18618
|
+
try {
|
|
18619
|
+
return context.resolveAnnotation(parseTypeAnnotation(new TokenStream(scanned.tokens)));
|
|
18620
|
+
} catch {
|
|
18621
|
+
return ANY_TYPE;
|
|
18622
|
+
}
|
|
18623
|
+
}
|
|
18624
|
+
function signatureOf(context, entry) {
|
|
18625
|
+
const parameters = entry.parameters.map((parameter) => abiType(context, parameter.type));
|
|
18626
|
+
const names = entry.parameters.map((parameter) => parameter.name);
|
|
18627
|
+
const minimum = Math.min(Math.max(entry.minimumArguments, 0), parameters.length);
|
|
18628
|
+
return createFunction(parameters, abiType(context, entry.returns), minimum, entry.variadic, names);
|
|
18629
|
+
}
|
|
18630
|
+
function checkSide(context, entry, resource, expression) {
|
|
18631
|
+
if (canReference(context.environment, entry.side)) {
|
|
18632
|
+
return;
|
|
18633
|
+
}
|
|
18634
|
+
const message = `Export "${entry.name}" of resource "${resource}" is ${entry.side}-only and cannot be called from a "${context.environment}" file.`;
|
|
18635
|
+
context.report("check-resource-export-side", message, expression.position);
|
|
18636
|
+
}
|
|
18637
|
+
function checkKnownExport(context, resource, name, args, expression) {
|
|
18638
|
+
const entry = lookupAbiExport(context.contracts, resource, name);
|
|
18639
|
+
if (entry === null) {
|
|
18640
|
+
const known = context.contracts.find((contract) => contract.resource === resource)?.exports.map((declared) => `"${declared.name}"`).join(", ");
|
|
18641
|
+
const hint = known === void 0 || known.length === 0 ? " It exports nothing." : ` It exports ${known}.`;
|
|
18642
|
+
checkValueList(context, args);
|
|
18643
|
+
context.report("check-unknown-resource-export", `Resource "${resource}" has no export "${name}".${hint}`, expression.position);
|
|
18644
|
+
return ANY_TYPE;
|
|
18645
|
+
}
|
|
18646
|
+
checkSide(context, entry, resource, expression);
|
|
18647
|
+
return checkSignature(context, args, signatureOf(context, entry), expression.position);
|
|
18648
|
+
}
|
|
18649
|
+
function checkResourceCall(context, expression) {
|
|
18650
|
+
if (context.contracts.length === 0) {
|
|
18651
|
+
return null;
|
|
18652
|
+
}
|
|
18653
|
+
if (expression.method !== null) {
|
|
18654
|
+
const resource2 = resourceOfExports(expression.callee);
|
|
18655
|
+
if (resource2 === null || !knowsResource(context.contracts, resource2)) {
|
|
18656
|
+
return null;
|
|
18657
|
+
}
|
|
18658
|
+
return checkKnownExport(context, resource2, expression.method, expression.args, expression);
|
|
18659
|
+
}
|
|
18660
|
+
if (expression.callee.kind !== "identifier" || expression.callee.name !== CALL_FUNCTION) {
|
|
18661
|
+
return null;
|
|
18662
|
+
}
|
|
18663
|
+
const resource = resourceOfLookup(expression.args[0]);
|
|
18664
|
+
const name = literalString(expression.args[1]);
|
|
18665
|
+
if (resource === null || name === null || !knowsResource(context.contracts, resource)) {
|
|
18666
|
+
return null;
|
|
18667
|
+
}
|
|
18668
|
+
checkValueList(context, expression.args.slice(0, 2));
|
|
18669
|
+
return checkKnownExport(context, resource, name, expression.args.slice(2), expression);
|
|
18670
|
+
}
|
|
18671
|
+
|
|
17712
18672
|
// ../mta-types/src/library-members.ts
|
|
17713
18673
|
var MATH_MEMBERS = {
|
|
17714
18674
|
abs: fn([NUMBER], NUMBER, 1),
|
|
@@ -17983,12 +18943,13 @@ function resolveStaticMember(context, className, expression) {
|
|
|
17983
18943
|
context.report("check-unknown-member", `Class "${className}" has no static member "${expression.property}".${hint}`, expression.position);
|
|
17984
18944
|
return ANY_TYPE;
|
|
17985
18945
|
}
|
|
17986
|
-
function resolveNamedMember(context,
|
|
18946
|
+
function resolveNamedMember(context, receiver, expression) {
|
|
18947
|
+
const name = receiver.name;
|
|
17987
18948
|
const enumeration = context.declarations.lookupEnum(name);
|
|
17988
18949
|
if (enumeration !== null) {
|
|
17989
18950
|
return checkEnumMember(context, name, enumeration.members, expression);
|
|
17990
18951
|
}
|
|
17991
|
-
const member = context
|
|
18952
|
+
const member = memberOf(context, receiver, expression.property);
|
|
17992
18953
|
if (member !== null) {
|
|
17993
18954
|
if (member.deprecated === true) {
|
|
17994
18955
|
context.warn("check-deprecated-use", `Member "${expression.property}" is deprecated.`, expression.position);
|
|
@@ -18023,6 +18984,29 @@ function nativeConstructor(context, name) {
|
|
|
18023
18984
|
const constructor = symbol.type.members.get(NATIVE_CONSTRUCTOR);
|
|
18024
18985
|
return constructor === void 0 || constructor.kind !== "function" ? null : constructor;
|
|
18025
18986
|
}
|
|
18987
|
+
function constructorParameters(context, className) {
|
|
18988
|
+
const constructor = context.declarations.lookupMember(className, "constructor");
|
|
18989
|
+
return constructor?.type.kind === "function" ? constructor.type.parameters : [];
|
|
18990
|
+
}
|
|
18991
|
+
function resolveConstructorArguments(context, expression, typeParameters) {
|
|
18992
|
+
const explicit = expression.typeArguments.map((argument) => context.resolveAnnotation(argument));
|
|
18993
|
+
if (typeParameters.length === 0) {
|
|
18994
|
+
if (explicit.length > 0) {
|
|
18995
|
+
context.report("check-generic-arity", `Class "${expression.className}" does not accept type arguments.`, expression.position);
|
|
18996
|
+
}
|
|
18997
|
+
return [];
|
|
18998
|
+
}
|
|
18999
|
+
if (explicit.length === typeParameters.length) {
|
|
19000
|
+
return explicit;
|
|
19001
|
+
}
|
|
19002
|
+
if (explicit.length > 0) {
|
|
19003
|
+
const expected = typeParameters.length === 1 ? "1 type argument" : `${typeParameters.length} type arguments`;
|
|
19004
|
+
context.report("check-generic-arity", `Class "${expression.className}" expects ${expected} but received ${explicit.length}.`, expression.position);
|
|
19005
|
+
return typeParameters.map((unused, index) => explicit[index] ?? ANY_TYPE);
|
|
19006
|
+
}
|
|
19007
|
+
const argumentTypes = expression.args.map((argument) => checkExpression(context, argument));
|
|
19008
|
+
return inferTypeArguments(typeParameters, constructorParameters(context, expression.className), argumentTypes);
|
|
19009
|
+
}
|
|
18026
19010
|
function checkNewExpression(context, expression) {
|
|
18027
19011
|
if (context.declarations.lookupClass(expression.className) === null) {
|
|
18028
19012
|
const constructor2 = nativeConstructor(context, expression.className);
|
|
@@ -18041,13 +19025,20 @@ function checkNewExpression(context, expression) {
|
|
|
18041
19025
|
const subject = pending === expression.className ? `Class "${pending}"` : `Class "${expression.className}" extends "${pending}", which`;
|
|
18042
19026
|
context.report("check-class-before-declaration", `${subject} is declared further down this file, so it does not exist yet at this point.`, expression.position);
|
|
18043
19027
|
}
|
|
19028
|
+
const info = context.declarations.lookupClass(expression.className);
|
|
19029
|
+
const typeArguments = resolveConstructorArguments(context, expression, info?.typeParameters ?? []);
|
|
19030
|
+
checkTypeConstraints(context, expression.className, typeArguments, expression.position);
|
|
18044
19031
|
const constructor = context.declarations.lookupMember(expression.className, "constructor");
|
|
18045
19032
|
if (constructor !== null && constructor.type.kind === "function") {
|
|
18046
|
-
|
|
19033
|
+
const substitutions = info === null ? /* @__PURE__ */ new Map() : classSubstitutions(info, typeArguments);
|
|
19034
|
+
const signature = substituteType(constructor.type, substitutions);
|
|
19035
|
+
if (signature.kind === "function") {
|
|
19036
|
+
checkSignature(context, expression.args, signature, expression.position);
|
|
19037
|
+
}
|
|
18047
19038
|
} else {
|
|
18048
19039
|
expression.args.forEach((argument) => checkExpression(context, argument));
|
|
18049
19040
|
}
|
|
18050
|
-
return createNamed(expression.className);
|
|
19041
|
+
return createNamed(expression.className, typeArguments);
|
|
18051
19042
|
}
|
|
18052
19043
|
function resolveSuperMethod(context, expression) {
|
|
18053
19044
|
const frame = context.currentClassMethod();
|
|
@@ -18136,14 +19127,14 @@ function isShapeOption(context, option) {
|
|
|
18136
19127
|
}
|
|
18137
19128
|
return context.declarations.lookupInterface(option.name) !== null || context.declarations.lookupClass(option.name) !== null;
|
|
18138
19129
|
}
|
|
18139
|
-
function
|
|
19130
|
+
function memberOf2(context, option, property) {
|
|
18140
19131
|
if (option.kind === "record") {
|
|
18141
19132
|
return option.members.get(property) ?? null;
|
|
18142
19133
|
}
|
|
18143
19134
|
return option.kind === "named" ? context.declarations.lookupMember(option.name, property)?.type ?? null : null;
|
|
18144
19135
|
}
|
|
18145
19136
|
function optionMemberType(context, option, property) {
|
|
18146
|
-
return isShapeOption(context, option) ?
|
|
19137
|
+
return isShapeOption(context, option) ? memberOf2(context, option, property) : null;
|
|
18147
19138
|
}
|
|
18148
19139
|
function resolveUnionMember(context, type, expression) {
|
|
18149
19140
|
if (!type.options.every((option) => isShapeOption(context, option))) {
|
|
@@ -18152,7 +19143,7 @@ function resolveUnionMember(context, type, expression) {
|
|
|
18152
19143
|
const resolved2 = [];
|
|
18153
19144
|
const missing2 = [];
|
|
18154
19145
|
for (const option of type.options) {
|
|
18155
|
-
const member =
|
|
19146
|
+
const member = memberOf2(context, option, expression.property);
|
|
18156
19147
|
if (member === null) {
|
|
18157
19148
|
missing2.push(`"${typeToString(option)}"`);
|
|
18158
19149
|
} else {
|
|
@@ -18319,26 +19310,6 @@ function conditionFacts(context, condition) {
|
|
|
18319
19310
|
collectFacts(context, condition, facts);
|
|
18320
19311
|
return facts;
|
|
18321
19312
|
}
|
|
18322
|
-
function alwaysExits(body) {
|
|
18323
|
-
const last = body[body.length - 1];
|
|
18324
|
-
if (last === void 0) {
|
|
18325
|
-
return false;
|
|
18326
|
-
}
|
|
18327
|
-
if (last.kind === "return-statement" || last.kind === "break-statement") {
|
|
18328
|
-
return true;
|
|
18329
|
-
}
|
|
18330
|
-
if (last.kind !== "if-statement" || last.alternate === null) {
|
|
18331
|
-
return false;
|
|
18332
|
-
}
|
|
18333
|
-
return last.clauses.every((clause) => alwaysExits(clause.body)) && alwaysExits(last.alternate);
|
|
18334
|
-
}
|
|
18335
|
-
function guardFacts(context, statement) {
|
|
18336
|
-
const [clause] = statement.kind === "if-statement" ? statement.clauses : [];
|
|
18337
|
-
if (statement.kind !== "if-statement" || statement.alternate !== null || statement.clauses.length !== 1 || clause === void 0) {
|
|
18338
|
-
return /* @__PURE__ */ new Map();
|
|
18339
|
-
}
|
|
18340
|
-
return alwaysExits(clause.body) ? negatedFacts(context, clause.condition) : /* @__PURE__ */ new Map();
|
|
18341
|
-
}
|
|
18342
19313
|
function negatedFacts(context, condition) {
|
|
18343
19314
|
const facts = /* @__PURE__ */ new Map();
|
|
18344
19315
|
if (condition.kind === "binary-expression" && condition.operator === "or") {
|
|
@@ -18369,23 +19340,58 @@ var ITERATOR_FUNCTIONS = /* @__PURE__ */ new Set(["pairs", "ipairs"]);
|
|
|
18369
19340
|
function checkBlock(context, body) {
|
|
18370
19341
|
context.binder.pushScope();
|
|
18371
19342
|
checkStatements(context, body);
|
|
19343
|
+
const declared = context.binder.currentScopeNames();
|
|
18372
19344
|
context.binder.popScope();
|
|
19345
|
+
for (const name of declared) {
|
|
19346
|
+
context.forgetNarrowing(name);
|
|
19347
|
+
}
|
|
18373
19348
|
}
|
|
18374
|
-
function
|
|
18375
|
-
|
|
19349
|
+
function branch(context, entry, facts, body) {
|
|
19350
|
+
const state = cloneFlow(entry);
|
|
19351
|
+
applyFacts(state, facts);
|
|
19352
|
+
context.setFlow(state);
|
|
18376
19353
|
checkBlock(context, body);
|
|
18377
|
-
context.
|
|
19354
|
+
return context.flowState;
|
|
18378
19355
|
}
|
|
18379
19356
|
function checkIf(context, clauses, alternate) {
|
|
19357
|
+
const exits = [];
|
|
19358
|
+
let current = context.flowState;
|
|
18380
19359
|
for (const clause of clauses) {
|
|
19360
|
+
context.setFlow(current);
|
|
18381
19361
|
checkExpression(context, clause.condition);
|
|
18382
|
-
|
|
19362
|
+
const taken = conditionFacts(context, clause.condition);
|
|
19363
|
+
const skipped = cloneFlow(current);
|
|
19364
|
+
applyFacts(skipped, negatedFacts(context, clause.condition));
|
|
19365
|
+
exits.push(branch(context, current, taken, clause.body));
|
|
19366
|
+
current = skipped;
|
|
18383
19367
|
}
|
|
18384
19368
|
if (alternate === null) {
|
|
18385
|
-
|
|
19369
|
+
exits.push(current);
|
|
19370
|
+
} else {
|
|
19371
|
+
context.setFlow(current);
|
|
19372
|
+
checkBlock(context, alternate);
|
|
19373
|
+
exits.push(context.flowState);
|
|
18386
19374
|
}
|
|
18387
|
-
|
|
18388
|
-
|
|
19375
|
+
context.setFlow(joinFlows(exits));
|
|
19376
|
+
}
|
|
19377
|
+
function checkLoopBody(context, body, facts) {
|
|
19378
|
+
forgetAssignedPaths(context, body);
|
|
19379
|
+
const entry = context.flowState;
|
|
19380
|
+
const state = cloneFlow(entry);
|
|
19381
|
+
applyFacts(state, facts);
|
|
19382
|
+
context.setFlow(state);
|
|
19383
|
+
checkBlock(context, body);
|
|
19384
|
+
context.setFlow(entry);
|
|
19385
|
+
}
|
|
19386
|
+
function checkWhile(context, statement) {
|
|
19387
|
+
forgetAssignedPaths(context, statement.body);
|
|
19388
|
+
checkExpression(context, statement.condition);
|
|
19389
|
+
checkLoopBody(context, statement.body, conditionFacts(context, statement.condition));
|
|
19390
|
+
context.applyFlowFacts(negatedFacts(context, statement.condition));
|
|
19391
|
+
}
|
|
19392
|
+
function checkRepeat(context, statement) {
|
|
19393
|
+
checkLoopBody(context, statement.body, /* @__PURE__ */ new Map());
|
|
19394
|
+
checkExpression(context, statement.condition);
|
|
18389
19395
|
}
|
|
18390
19396
|
function checkNumericFor(context, statement) {
|
|
18391
19397
|
const bounds = [statement.start, statement.limit, statement.step].filter((value) => value !== null);
|
|
@@ -18396,10 +19402,13 @@ function checkNumericFor(context, statement) {
|
|
|
18396
19402
|
}
|
|
18397
19403
|
}
|
|
18398
19404
|
forgetAssignedPaths(context, statement.body);
|
|
19405
|
+
const entry = context.flowState;
|
|
19406
|
+
context.setFlow(cloneFlow(entry));
|
|
18399
19407
|
context.binder.pushScope();
|
|
18400
19408
|
context.binder.declare({ name: statement.variable.name, type: NUMBER_TYPE, isLocal: true, position: statement.variable.position, origin: "local" });
|
|
18401
19409
|
checkStatements(context, statement.body);
|
|
18402
19410
|
context.binder.popScope();
|
|
19411
|
+
context.setFlow(entry);
|
|
18403
19412
|
}
|
|
18404
19413
|
function iteratedTypes(context, iterators) {
|
|
18405
19414
|
const [iterator] = iterators;
|
|
@@ -18420,6 +19429,8 @@ function checkGenericFor(context, statement) {
|
|
|
18420
19429
|
statement.iterators.forEach((iterator) => checkExpression(context, iterator));
|
|
18421
19430
|
const iterated = iteratedTypes(context, statement.iterators);
|
|
18422
19431
|
forgetAssignedPaths(context, statement.body);
|
|
19432
|
+
const entry = context.flowState;
|
|
19433
|
+
context.setFlow(cloneFlow(entry));
|
|
18423
19434
|
context.binder.pushScope();
|
|
18424
19435
|
statement.variables.forEach((variable, index) => {
|
|
18425
19436
|
const inferred = iterated[index] ?? ANY_TYPE;
|
|
@@ -18428,6 +19439,7 @@ function checkGenericFor(context, statement) {
|
|
|
18428
19439
|
});
|
|
18429
19440
|
checkStatements(context, statement.body);
|
|
18430
19441
|
context.binder.popScope();
|
|
19442
|
+
context.setFlow(entry);
|
|
18431
19443
|
}
|
|
18432
19444
|
|
|
18433
19445
|
// ../compiler/src/checker/events.ts
|
|
@@ -18488,15 +19500,19 @@ function minimumArguments(context, parameters) {
|
|
|
18488
19500
|
}
|
|
18489
19501
|
function buildFunctionType(context, parameters, returnAnnotation, contextual = null) {
|
|
18490
19502
|
const isVariadic = parameters.some((parameter) => parameter.isVararg);
|
|
18491
|
-
const
|
|
18492
|
-
|
|
19503
|
+
const named2 = parameters.filter((parameter) => !parameter.isVararg);
|
|
19504
|
+
const types = named2.map((parameter, index) => parameter.annotation === null ? contextual?.parameters[index] ?? ANY_TYPE : context.resolveAnnotation(parameter.annotation));
|
|
19505
|
+
const names = named2.map((parameter) => parameter.name);
|
|
19506
|
+
return createFunction(types, context.resolveAnnotation(returnAnnotation), minimumArguments(context, parameters), isVariadic, names);
|
|
18493
19507
|
}
|
|
18494
|
-
function checkFunctionBody(context, parameters, returnAnnotation, body, signature,
|
|
19508
|
+
function checkFunctionBody(context, parameters, returnAnnotation, body, signature, selfType2) {
|
|
18495
19509
|
forgetAssignedPaths(context, body);
|
|
19510
|
+
const entry = context.flowState;
|
|
19511
|
+
context.setFlow(cloneFlow(entry));
|
|
18496
19512
|
context.binder.pushScope();
|
|
18497
19513
|
context.pushReturnType(returnAnnotation === null ? null : context.resolveAnnotation(returnAnnotation));
|
|
18498
|
-
if (
|
|
18499
|
-
context.binder.declare({ name: "self", type:
|
|
19514
|
+
if (selfType2 !== null) {
|
|
19515
|
+
context.binder.declare({ name: "self", type: selfType2, isLocal: true, position: ORIGIN });
|
|
18500
19516
|
}
|
|
18501
19517
|
let parameterIndex = 0;
|
|
18502
19518
|
for (const parameter of parameters) {
|
|
@@ -18512,6 +19528,7 @@ function checkFunctionBody(context, parameters, returnAnnotation, body, signatur
|
|
|
18512
19528
|
signature.returnType = inferred;
|
|
18513
19529
|
}
|
|
18514
19530
|
context.binder.popScope();
|
|
19531
|
+
context.setFlow(entry);
|
|
18515
19532
|
}
|
|
18516
19533
|
function valueAt(values, valueTypes, index) {
|
|
18517
19534
|
if (values[index] !== void 0) {
|
|
@@ -18535,6 +19552,9 @@ function checkLocal(context, statement) {
|
|
|
18535
19552
|
context.expectAssignable(valueType, declared, value.position, `Variable "${declaration.name}"`);
|
|
18536
19553
|
}
|
|
18537
19554
|
context.binder.declare({ name: declaration.name, type: declared, isLocal: true, position: declaration.position, origin: "local" });
|
|
19555
|
+
if (value !== void 0) {
|
|
19556
|
+
recordAssignedFact(context, declaration.name, valueType, declared);
|
|
19557
|
+
}
|
|
18538
19558
|
});
|
|
18539
19559
|
}
|
|
18540
19560
|
function checkCompoundOperand(context, operator, type, position2) {
|
|
@@ -18548,6 +19568,15 @@ function checkCompoundOperand(context, operator, type, position2) {
|
|
|
18548
19568
|
context.report("check-invalid-operand", `Operator "${operator}" cannot be applied to "${typeToString(type)}".`, position2);
|
|
18549
19569
|
}
|
|
18550
19570
|
}
|
|
19571
|
+
function recordAssignedFact(context, path, valueType, declared) {
|
|
19572
|
+
if (path === null || valueType.kind === "any") {
|
|
19573
|
+
return;
|
|
19574
|
+
}
|
|
19575
|
+
const fact = assignmentFact(widenInferred(valueType), declared);
|
|
19576
|
+
if (fact !== null) {
|
|
19577
|
+
context.applyFlowFacts(/* @__PURE__ */ new Map([[path, fact]]));
|
|
19578
|
+
}
|
|
19579
|
+
}
|
|
18551
19580
|
function checkAssignment(context, statement) {
|
|
18552
19581
|
const valueTypes = checkValueList(context, statement.values);
|
|
18553
19582
|
statement.targets.forEach((target, index) => {
|
|
@@ -18583,6 +19612,7 @@ function checkAssignment(context, statement) {
|
|
|
18583
19612
|
if (value !== void 0) {
|
|
18584
19613
|
context.expectAssignable(valueType, declared, value.position, "Assignment");
|
|
18585
19614
|
}
|
|
19615
|
+
recordAssignedFact(context, pathOf2(target), valueType, declared);
|
|
18586
19616
|
});
|
|
18587
19617
|
}
|
|
18588
19618
|
function checkFunctionDeclaration(context, statement) {
|
|
@@ -18661,18 +19691,19 @@ function checkStatement(context, statement) {
|
|
|
18661
19691
|
case "function-declaration":
|
|
18662
19692
|
return checkFunctionDeclaration(context, statement);
|
|
18663
19693
|
case "return-statement":
|
|
18664
|
-
|
|
19694
|
+
checkReturn(context, statement);
|
|
19695
|
+
context.markUnreachable();
|
|
19696
|
+
return;
|
|
19697
|
+
case "break-statement":
|
|
19698
|
+
case "continue-statement":
|
|
19699
|
+
context.markUnreachable();
|
|
19700
|
+
return;
|
|
18665
19701
|
case "do-statement":
|
|
18666
19702
|
return checkBlock(context, statement.body);
|
|
18667
19703
|
case "while-statement":
|
|
18668
|
-
|
|
18669
|
-
forgetAssignedPaths(context, statement.body);
|
|
18670
|
-
return checkBlock(context, statement.body);
|
|
19704
|
+
return checkWhile(context, statement);
|
|
18671
19705
|
case "repeat-statement":
|
|
18672
|
-
|
|
18673
|
-
checkBlock(context, statement.body);
|
|
18674
|
-
checkExpression(context, statement.condition);
|
|
18675
|
-
return;
|
|
19706
|
+
return checkRepeat(context, statement);
|
|
18676
19707
|
case "if-statement":
|
|
18677
19708
|
return checkIf(context, statement.clauses, statement.alternate);
|
|
18678
19709
|
case "numeric-for-statement":
|
|
@@ -18702,18 +19733,8 @@ function checkStatement(context, statement) {
|
|
|
18702
19733
|
}
|
|
18703
19734
|
}
|
|
18704
19735
|
function checkStatements(context, statements) {
|
|
18705
|
-
let guards = 0;
|
|
18706
19736
|
for (const statement of statements) {
|
|
18707
19737
|
checkStatement(context, statement);
|
|
18708
|
-
const facts = guardFacts(context, statement);
|
|
18709
|
-
if (facts.size > 0) {
|
|
18710
|
-
context.pushNarrowing(facts);
|
|
18711
|
-
guards += 1;
|
|
18712
|
-
}
|
|
18713
|
-
}
|
|
18714
|
-
while (guards > 0) {
|
|
18715
|
-
context.popNarrowing();
|
|
18716
|
-
guards -= 1;
|
|
18717
19738
|
}
|
|
18718
19739
|
}
|
|
18719
19740
|
|
|
@@ -18788,7 +19809,7 @@ function checkMember(context, expression) {
|
|
|
18788
19809
|
if (objectType.kind === "map" && isAssignable(STRING_TYPE, objectType.key)) {
|
|
18789
19810
|
return context.record(expression, objectType.value);
|
|
18790
19811
|
}
|
|
18791
|
-
const named2 = objectType.kind === "named" ? resolveNamedMember(context, objectType
|
|
19812
|
+
const named2 = objectType.kind === "named" ? resolveNamedMember(context, objectType, expression) : null;
|
|
18792
19813
|
if (named2 !== null) {
|
|
18793
19814
|
return context.record(expression, named2);
|
|
18794
19815
|
}
|
|
@@ -18845,10 +19866,14 @@ function isLegacySuperCall(expression) {
|
|
|
18845
19866
|
}
|
|
18846
19867
|
function checkMethodCall(context, expression, method, receiver) {
|
|
18847
19868
|
if (receiver.kind !== "named") {
|
|
18848
|
-
|
|
18849
|
-
|
|
19869
|
+
const signature = resolveNonNominalMethod(receiver, method);
|
|
19870
|
+
if (signature === null) {
|
|
19871
|
+
checkValueList(context, expression.args);
|
|
19872
|
+
return ANY_TYPE;
|
|
19873
|
+
}
|
|
19874
|
+
return checkSignature(context, expression.args, withoutSelfParameter(signature), expression.position);
|
|
18850
19875
|
}
|
|
18851
|
-
const declared = context
|
|
19876
|
+
const declared = memberOf(context, receiver, method);
|
|
18852
19877
|
if (declared?.type.kind === "function") {
|
|
18853
19878
|
if (declared.deprecated === true) {
|
|
18854
19879
|
context.warn("check-deprecated-use", `Member "${method}" is deprecated.`, expression.position);
|
|
@@ -18867,6 +19892,10 @@ function checkMethodCall(context, expression, method, receiver) {
|
|
|
18867
19892
|
return checkSignature(context, expression.args, member.type, expression.position);
|
|
18868
19893
|
}
|
|
18869
19894
|
function checkCall(context, expression) {
|
|
19895
|
+
const contracted = checkResourceCall(context, expression);
|
|
19896
|
+
if (contracted !== null) {
|
|
19897
|
+
return context.record(expression, contracted);
|
|
19898
|
+
}
|
|
18870
19899
|
if (isLegacySuperCall(expression)) {
|
|
18871
19900
|
checkValueList(context, expression.args);
|
|
18872
19901
|
context.report("check-invalid-super", 'Call "super(...)" directly instead of "self:super(...)".', expression.position);
|
|
@@ -18967,6 +19996,9 @@ function checkMultiValueExpression(context, expression, expected = null) {
|
|
|
18967
19996
|
if (symbol === null) {
|
|
18968
19997
|
checkGlobalReference(context, expression.name, expression.position);
|
|
18969
19998
|
}
|
|
19999
|
+
if (symbol === null || symbol.isLocal !== true) {
|
|
20000
|
+
context.noteGlobalReference(expression.name);
|
|
20001
|
+
}
|
|
18970
20002
|
return context.record(expression, context.narrowedType(expression.name) ?? symbol?.type ?? ANY_TYPE);
|
|
18971
20003
|
}
|
|
18972
20004
|
case "member-expression":
|
|
@@ -19079,6 +20111,11 @@ function registerMembers(context, info, statement) {
|
|
|
19079
20111
|
for (const member of [...statement.members, ...generated]) {
|
|
19080
20112
|
const type = member.kind === "class-field" ? fieldTypes.get(member) ?? ANY_TYPE : member.isSynthetic ? syntheticMethodType(context, member, fieldTypes) : buildFunctionType(context, member.parameters, member.returnAnnotation);
|
|
19081
20113
|
const decorators = member.decorators.map((decorator) => decorator.name);
|
|
20114
|
+
if (member.kind === "class-method" && isMetamethodMember(member)) {
|
|
20115
|
+
checkMetamethod(context, info.name, member, type);
|
|
20116
|
+
continue;
|
|
20117
|
+
}
|
|
20118
|
+
reportRejectedMetamethod(context, info.name, member);
|
|
19082
20119
|
const space = member.isStatic ? info.statics : info.members;
|
|
19083
20120
|
space.set(member.name, {
|
|
19084
20121
|
name: member.name,
|
|
@@ -19106,9 +20143,22 @@ function declareBuilder(context, info, statement) {
|
|
|
19106
20143
|
}
|
|
19107
20144
|
}
|
|
19108
20145
|
members.set("build", { name: "build", type: { kind: "function", parameters: [], minimumArguments: 0, isVariadic: false, returnType: createNamed(statement.name) }, isMethod: true, position: statement.position });
|
|
19109
|
-
context.declarations.declareClass({
|
|
20146
|
+
context.declarations.declareClass({
|
|
20147
|
+
name,
|
|
20148
|
+
typeParameters: [],
|
|
20149
|
+
typeConstraints: [],
|
|
20150
|
+
superClass: null,
|
|
20151
|
+
superArguments: [],
|
|
20152
|
+
interfaces: [],
|
|
20153
|
+
members,
|
|
20154
|
+
statics: /* @__PURE__ */ new Map(),
|
|
20155
|
+
position: statement.position
|
|
20156
|
+
});
|
|
19110
20157
|
context.declareModuleGlobal({ name, type: createNamed(name), isLocal: false, position: statement.position });
|
|
19111
20158
|
}
|
|
20159
|
+
function selfType(info) {
|
|
20160
|
+
return createNamed(info.name, info.typeParameters.map((parameter) => createNamed(parameter)));
|
|
20161
|
+
}
|
|
19112
20162
|
function checkMethodBody(context, info, member) {
|
|
19113
20163
|
const explicitSelf = member.parameters.find((parameter) => parameter.name === "self");
|
|
19114
20164
|
if (explicitSelf !== void 0) {
|
|
@@ -19123,7 +20173,7 @@ function checkMethodBody(context, info, member) {
|
|
|
19123
20173
|
return;
|
|
19124
20174
|
}
|
|
19125
20175
|
context.pushClassMethod({ className: info.name, methodName: member.name });
|
|
19126
|
-
checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature,
|
|
20176
|
+
checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, selfType(info));
|
|
19127
20177
|
context.popClassMethod();
|
|
19128
20178
|
}
|
|
19129
20179
|
function resolveSuperClass(context, statement) {
|
|
@@ -19146,6 +20196,10 @@ function resolveSuperClass(context, statement) {
|
|
|
19146
20196
|
context.report("check-unknown-class", message, statement.position);
|
|
19147
20197
|
return null;
|
|
19148
20198
|
}
|
|
20199
|
+
function resolveClassHeader(context, info, statement) {
|
|
20200
|
+
info.superArguments = statement.superClassArguments.map((argument) => context.resolveAnnotation(argument));
|
|
20201
|
+
info.typeConstraints = statement.typeConstraints.map((constraint) => constraint === null ? null : context.resolveAnnotation(constraint));
|
|
20202
|
+
}
|
|
19149
20203
|
function declareClassInfo(context, statement) {
|
|
19150
20204
|
if (context.declarations.lookupClass(statement.name) !== null) {
|
|
19151
20205
|
context.report("check-duplicate-class", `Class "${statement.name}" is already defined.`, statement.position);
|
|
@@ -19157,12 +20211,16 @@ function declareClassInfo(context, statement) {
|
|
|
19157
20211
|
}
|
|
19158
20212
|
const info = {
|
|
19159
20213
|
name: statement.name,
|
|
20214
|
+
typeParameters: statement.typeParameters,
|
|
20215
|
+
typeConstraints: [],
|
|
19160
20216
|
superClass: null,
|
|
20217
|
+
superArguments: [],
|
|
19161
20218
|
interfaces: statement.interfaces,
|
|
19162
20219
|
members: /* @__PURE__ */ new Map(),
|
|
19163
20220
|
statics: /* @__PURE__ */ new Map(),
|
|
19164
20221
|
position: statement.position
|
|
19165
20222
|
};
|
|
20223
|
+
context.noteTypeParameters(statement.typeParameters);
|
|
19166
20224
|
context.declarations.declareClass(info);
|
|
19167
20225
|
context.declareModuleGlobal({ name: info.name, type: createNamed(info.name), isLocal: false, position: statement.position });
|
|
19168
20226
|
return info;
|
|
@@ -19173,6 +20231,7 @@ function declareLocalClass(context, statement) {
|
|
|
19173
20231
|
return null;
|
|
19174
20232
|
}
|
|
19175
20233
|
info.superClass = resolveSuperClass(context, statement);
|
|
20234
|
+
resolveClassHeader(context, info, statement);
|
|
19176
20235
|
return info;
|
|
19177
20236
|
}
|
|
19178
20237
|
function checkClassDeclaration(context, statement) {
|
|
@@ -19286,6 +20345,7 @@ function predeclareModule(context, body) {
|
|
|
19286
20345
|
const declared = declareHeaders(context, body);
|
|
19287
20346
|
for (const entry of declared) {
|
|
19288
20347
|
entry.info.superClass = resolveSuperClass(context, entry.statement);
|
|
20348
|
+
resolveClassHeader(context, entry.info, entry.statement);
|
|
19289
20349
|
}
|
|
19290
20350
|
for (const entry of declared) {
|
|
19291
20351
|
breakCycle(context, entry.info);
|
|
@@ -19345,17 +20405,23 @@ function externalReferences(context) {
|
|
|
19345
20405
|
}
|
|
19346
20406
|
return external;
|
|
19347
20407
|
}
|
|
20408
|
+
function referencedNames(context, external) {
|
|
20409
|
+
const events = [...context.eventReferences].map((name) => `${EVENT_NAME_PREFIX}${name}`);
|
|
20410
|
+
return /* @__PURE__ */ new Set([...external.keys(), ...context.globalReferences, ...context.unknownTypes.keys(), ...events]);
|
|
20411
|
+
}
|
|
19348
20412
|
function check(program2, mode, environment = DEFAULT_ENVIRONMENT, options = {}) {
|
|
19349
20413
|
const ambient = options.ambient ?? EMPTY_AMBIENT;
|
|
19350
20414
|
const project = options.project ?? EMPTY_PROJECT_DECLARATIONS;
|
|
19351
20415
|
const context = new CheckContext(mode, environment, ambient, project, options.oop === true);
|
|
19352
20416
|
context.isDeclarationFile = options.isDeclarationFile === true;
|
|
20417
|
+
context.contracts = options.contracts ?? [];
|
|
19353
20418
|
const structure = context.isDeclarationFile ? checkDeclarationFile(program2.body) : checkJumps(program2.body);
|
|
19354
20419
|
const directives = collectDirectives(program2.body, { isDeclarationFile: context.isDeclarationFile });
|
|
19355
20420
|
predeclareModule(context, program2.body);
|
|
19356
20421
|
checkStatements(context, program2.body);
|
|
19357
20422
|
reportUnknownTypes(context);
|
|
19358
20423
|
reportUnusedSymbols(context, options.noUnusedLocals === true, options.noUnusedParameters === true);
|
|
20424
|
+
const external = externalReferences(context);
|
|
19359
20425
|
return {
|
|
19360
20426
|
diagnostics: sortDiagnostics([...structure, ...directives.diagnostics, ...context.diagnostics]),
|
|
19361
20427
|
types: context.types,
|
|
@@ -19364,7 +20430,9 @@ function check(program2, mode, environment = DEFAULT_ENVIRONMENT, options = {})
|
|
|
19364
20430
|
declarations: context.declarations,
|
|
19365
20431
|
aliases: context.binder.resolvedAliases(),
|
|
19366
20432
|
declaredGlobals: context.declaredGlobals,
|
|
19367
|
-
|
|
20433
|
+
moduleGlobals: context.moduleGlobals,
|
|
20434
|
+
externalReferences: external,
|
|
20435
|
+
referencedNames: referencedNames(context, external),
|
|
19368
20436
|
directives: directives.directives,
|
|
19369
20437
|
mode,
|
|
19370
20438
|
environment,
|
|
@@ -19491,8 +20559,8 @@ function typeOf(state, expression) {
|
|
|
19491
20559
|
function indentLine(state, text) {
|
|
19492
20560
|
return `${INDENT2.repeat(state.indent)}${text}`;
|
|
19493
20561
|
}
|
|
19494
|
-
function markSource(state,
|
|
19495
|
-
const marker = { sourceLine };
|
|
20562
|
+
function markSource(state, sourceLine2, symbol = state.symbol) {
|
|
20563
|
+
const marker = { sourceLine: sourceLine2 };
|
|
19496
20564
|
if (symbol !== void 0) {
|
|
19497
20565
|
marker.symbol = symbol;
|
|
19498
20566
|
}
|
|
@@ -19762,6 +20830,13 @@ function emitGeneratedMethod(state, className, member) {
|
|
|
19762
20830
|
end
|
|
19763
20831
|
end`;
|
|
19764
20832
|
}
|
|
20833
|
+
if (member.generated?.kind === "validate" || member.generated?.kind === "matches") {
|
|
20834
|
+
const helper = member.generated.kind === "validate" ? "__luam_validate" : "__luam_matches";
|
|
20835
|
+
requireHelper(state, "validate");
|
|
20836
|
+
return `${member.name} = function(value)
|
|
20837
|
+
return ${helper}(value, ${member.generated.descriptor ?? "{ kind = 'table' }"})
|
|
20838
|
+
end`;
|
|
20839
|
+
}
|
|
19765
20840
|
if (member.generated?.kind === "to-string") {
|
|
19766
20841
|
const values = names.map((name) => `'${name}=' .. tostring(self.${name})`).join(" .. ', ' .. ");
|
|
19767
20842
|
const body = values.length === 0 ? "''" : values;
|
|
@@ -19797,24 +20872,24 @@ ${names.map((name) => ` self.${name} = values.${name}`).join("\n")}
|
|
|
19797
20872
|
end`;
|
|
19798
20873
|
}
|
|
19799
20874
|
function emitMembers(state, statement) {
|
|
19800
|
-
const
|
|
20875
|
+
const entries3 = [];
|
|
19801
20876
|
state.indent += 1;
|
|
19802
20877
|
for (const member of statement.members) {
|
|
19803
20878
|
if (member.kind === "class-method") {
|
|
19804
|
-
|
|
20879
|
+
entries3.push(indentLine(state, `${markSource(state, member.position.line, `${statement.name}${member.isStatic ? "." : ":"}${member.name}`)}${emitMethod(state, statement.name, member)}`));
|
|
19805
20880
|
continue;
|
|
19806
20881
|
}
|
|
19807
20882
|
if (member.decorators.some((decorator) => decorator.name === "Lazy")) {
|
|
19808
20883
|
continue;
|
|
19809
20884
|
}
|
|
19810
20885
|
const value = member.value === null ? "nil" : emitExpression(state, member.value);
|
|
19811
|
-
|
|
20886
|
+
entries3.push(indentLine(state, `${markSource(state, member.position.line, statement.name)}${member.name} = ${value}`));
|
|
19812
20887
|
}
|
|
19813
20888
|
for (const generated of state.generatedMembers.get(statement) ?? []) {
|
|
19814
|
-
|
|
20889
|
+
entries3.push(indentLine(state, `${markSource(state, generated.position.line, `${statement.name}:${generated.name}`)}${emitMethod(state, statement.name, generated)}`));
|
|
19815
20890
|
}
|
|
19816
20891
|
state.indent -= 1;
|
|
19817
|
-
return
|
|
20892
|
+
return entries3;
|
|
19818
20893
|
}
|
|
19819
20894
|
function emitClassHeader(statement) {
|
|
19820
20895
|
const name = emitString(statement.name);
|
|
@@ -19840,15 +20915,15 @@ ${assignments}
|
|
|
19840
20915
|
return value
|
|
19841
20916
|
end`);
|
|
19842
20917
|
state.indent += 1;
|
|
19843
|
-
const
|
|
20918
|
+
const entries3 = methods.map((method) => indentLine(state, method));
|
|
19844
20919
|
state.indent -= 1;
|
|
19845
|
-
return [`class '${name}' {`,
|
|
20920
|
+
return [`class '${name}' {`, entries3.join(",\n"), "}"].join("\n");
|
|
19846
20921
|
}
|
|
19847
20922
|
function emitClassDeclaration(state, statement) {
|
|
19848
20923
|
requireHelper(state, "class");
|
|
19849
20924
|
const header = emitClassHeader(statement);
|
|
19850
|
-
const
|
|
19851
|
-
const declaration =
|
|
20925
|
+
const entries3 = emitMembers(state, statement);
|
|
20926
|
+
const declaration = entries3.length === 0 ? `${header} {}` : [`${header} {`, entries3.join(",\n"), indentLine(state, "}")].join("\n");
|
|
19852
20927
|
const builder = emitBuilder(state, statement);
|
|
19853
20928
|
return builder === null ? declaration : `${declaration}
|
|
19854
20929
|
${builder}`;
|
|
@@ -20786,6 +21861,7 @@ function compile(source, options = {}) {
|
|
|
20786
21861
|
const isDeclarationFile = options.filePath !== void 0 && isDeclarationPath(options.filePath);
|
|
20787
21862
|
const checked = check(parsed.program, mode, environment, {
|
|
20788
21863
|
ambient: options.ambient ?? EMPTY_AMBIENT,
|
|
21864
|
+
contracts: options.contracts ?? [],
|
|
20789
21865
|
project: options.project ?? EMPTY_PROJECT_DECLARATIONS,
|
|
20790
21866
|
isDeclarationFile,
|
|
20791
21867
|
oop: settings.oop,
|
|
@@ -20799,7 +21875,9 @@ function compile(source, options = {}) {
|
|
|
20799
21875
|
environment,
|
|
20800
21876
|
declarations: ownDeclarations(checked.declarations, options.ambient),
|
|
20801
21877
|
declaredGlobals: checked.declaredGlobals,
|
|
21878
|
+
moduleGlobals: checked.moduleGlobals,
|
|
20802
21879
|
externalReferences: checked.externalReferences,
|
|
21880
|
+
referencedNames: checked.referencedNames,
|
|
20803
21881
|
directives: checked.directives,
|
|
20804
21882
|
topLevelReturn: findTopLevelReturn(parsed.program.body),
|
|
20805
21883
|
events: checked.events
|
|
@@ -20890,6 +21968,199 @@ function fingerprintDeclarations(declarations) {
|
|
|
20890
21968
|
const events = declarations.events.map(eventText);
|
|
20891
21969
|
return hashString([...classes, ...interfaces, ...enums, ...globals, ...events].sort(compareText).join(";"));
|
|
20892
21970
|
}
|
|
21971
|
+
function appendText(entries3, name, text) {
|
|
21972
|
+
const existing = entries3.get(name);
|
|
21973
|
+
entries3.set(name, existing === void 0 ? text : `${existing}|${text}`);
|
|
21974
|
+
}
|
|
21975
|
+
function fingerprintByName(declarations) {
|
|
21976
|
+
const entries3 = /* @__PURE__ */ new Map();
|
|
21977
|
+
for (const info of declarations.classes) {
|
|
21978
|
+
appendText(entries3, info.name, classText(info));
|
|
21979
|
+
}
|
|
21980
|
+
for (const info of declarations.interfaces) {
|
|
21981
|
+
appendText(entries3, info.name, interfaceText(info));
|
|
21982
|
+
}
|
|
21983
|
+
for (const info of declarations.enums) {
|
|
21984
|
+
appendText(entries3, info.name, enumText(info));
|
|
21985
|
+
}
|
|
21986
|
+
for (const info of declarations.globals) {
|
|
21987
|
+
appendText(entries3, info.name, globalText(info));
|
|
21988
|
+
}
|
|
21989
|
+
for (const info of declarations.events) {
|
|
21990
|
+
appendText(entries3, `${EVENT_NAME_PREFIX}${info.name}`, eventText(info));
|
|
21991
|
+
}
|
|
21992
|
+
return new Map([...entries3].map(([name, text]) => [name, hashString(text)]));
|
|
21993
|
+
}
|
|
21994
|
+
|
|
21995
|
+
// ../compiler/src/project/dependency-graph.ts
|
|
21996
|
+
var MAX_CLOSURE_SIZE = 512;
|
|
21997
|
+
function indexOwners(nodes, environment) {
|
|
21998
|
+
const owners = /* @__PURE__ */ new Map();
|
|
21999
|
+
for (const node of nodes) {
|
|
22000
|
+
if (!canReference(environment, node.environment)) {
|
|
22001
|
+
continue;
|
|
22002
|
+
}
|
|
22003
|
+
for (const name of node.provides.keys()) {
|
|
22004
|
+
const existing = owners.get(name);
|
|
22005
|
+
if (existing === void 0) {
|
|
22006
|
+
owners.set(name, [node]);
|
|
22007
|
+
continue;
|
|
22008
|
+
}
|
|
22009
|
+
existing.push(node);
|
|
22010
|
+
}
|
|
22011
|
+
}
|
|
22012
|
+
return owners;
|
|
22013
|
+
}
|
|
22014
|
+
function walk3(node, owners) {
|
|
22015
|
+
const visited = /* @__PURE__ */ new Set();
|
|
22016
|
+
const pending = [...node.requires, ...node.provides.keys()];
|
|
22017
|
+
while (pending.length > 0) {
|
|
22018
|
+
const name = pending.pop();
|
|
22019
|
+
if (name === void 0 || visited.has(name)) {
|
|
22020
|
+
continue;
|
|
22021
|
+
}
|
|
22022
|
+
visited.add(name);
|
|
22023
|
+
if (visited.size > MAX_CLOSURE_SIZE) {
|
|
22024
|
+
return null;
|
|
22025
|
+
}
|
|
22026
|
+
for (const owner of owners.get(name) ?? []) {
|
|
22027
|
+
for (const required of owner.requires) {
|
|
22028
|
+
if (!visited.has(required)) {
|
|
22029
|
+
pending.push(required);
|
|
22030
|
+
}
|
|
22031
|
+
}
|
|
22032
|
+
}
|
|
22033
|
+
}
|
|
22034
|
+
return visited;
|
|
22035
|
+
}
|
|
22036
|
+
function ownerText(owners, name) {
|
|
22037
|
+
const list = owners.get(name) ?? [];
|
|
22038
|
+
return `${name}=${list.map((owner) => `${owner.path}:${owner.provides.get(name) ?? ""}`).sort().join("+")}`;
|
|
22039
|
+
}
|
|
22040
|
+
function createDependencyGraph(nodes) {
|
|
22041
|
+
const byPath = new Map(nodes.map((node) => [node.path, node]));
|
|
22042
|
+
const indexes = /* @__PURE__ */ new Map();
|
|
22043
|
+
const closures = /* @__PURE__ */ new Map();
|
|
22044
|
+
const keys = /* @__PURE__ */ new Map();
|
|
22045
|
+
function ownersFor(environment) {
|
|
22046
|
+
const cached = indexes.get(environment);
|
|
22047
|
+
if (cached !== void 0) {
|
|
22048
|
+
return cached;
|
|
22049
|
+
}
|
|
22050
|
+
const owners = indexOwners(nodes, environment);
|
|
22051
|
+
indexes.set(environment, owners);
|
|
22052
|
+
return owners;
|
|
22053
|
+
}
|
|
22054
|
+
function closureOf(path) {
|
|
22055
|
+
if (closures.has(path)) {
|
|
22056
|
+
return closures.get(path) ?? null;
|
|
22057
|
+
}
|
|
22058
|
+
const node = byPath.get(path);
|
|
22059
|
+
const closure = node === void 0 ? null : walk3(node, ownersFor(node.environment));
|
|
22060
|
+
closures.set(path, closure);
|
|
22061
|
+
return closure;
|
|
22062
|
+
}
|
|
22063
|
+
function keyOf(path) {
|
|
22064
|
+
if (keys.has(path)) {
|
|
22065
|
+
return keys.get(path) ?? null;
|
|
22066
|
+
}
|
|
22067
|
+
const node = byPath.get(path);
|
|
22068
|
+
const closure = closureOf(path);
|
|
22069
|
+
if (node === void 0 || closure === null) {
|
|
22070
|
+
keys.set(path, null);
|
|
22071
|
+
return null;
|
|
22072
|
+
}
|
|
22073
|
+
const owners = ownersFor(node.environment);
|
|
22074
|
+
const key = hashString([...closure].sort().map((name) => ownerText(owners, name)).join(";"));
|
|
22075
|
+
keys.set(path, key);
|
|
22076
|
+
return key;
|
|
22077
|
+
}
|
|
22078
|
+
return {
|
|
22079
|
+
closureOf,
|
|
22080
|
+
keyOf,
|
|
22081
|
+
dependentsOf: (names) => {
|
|
22082
|
+
const dependents = /* @__PURE__ */ new Set();
|
|
22083
|
+
for (const node of nodes) {
|
|
22084
|
+
const closure = closureOf(node.path);
|
|
22085
|
+
if (closure === null) {
|
|
22086
|
+
dependents.add(node.path);
|
|
22087
|
+
continue;
|
|
22088
|
+
}
|
|
22089
|
+
for (const name of names) {
|
|
22090
|
+
if (closure.has(name)) {
|
|
22091
|
+
dependents.add(node.path);
|
|
22092
|
+
break;
|
|
22093
|
+
}
|
|
22094
|
+
}
|
|
22095
|
+
}
|
|
22096
|
+
return dependents;
|
|
22097
|
+
},
|
|
22098
|
+
providedBy: (paths) => {
|
|
22099
|
+
const names = /* @__PURE__ */ new Set();
|
|
22100
|
+
for (const path of paths) {
|
|
22101
|
+
for (const name of byPath.get(path)?.provides.keys() ?? []) {
|
|
22102
|
+
names.add(name);
|
|
22103
|
+
}
|
|
22104
|
+
}
|
|
22105
|
+
return names;
|
|
22106
|
+
}
|
|
22107
|
+
};
|
|
22108
|
+
}
|
|
22109
|
+
|
|
22110
|
+
// ../compiler/src/project/ambient-scope.ts
|
|
22111
|
+
function optionsKey(options) {
|
|
22112
|
+
return `${options.strict ? "strict" : ""}|${options.oop ? "oop" : ""}|${options.noUnusedLocals ? "nul" : ""}|${options.noUnusedParameters ? "nup" : ""}`;
|
|
22113
|
+
}
|
|
22114
|
+
function declaresNothing(declarations) {
|
|
22115
|
+
const { classes, interfaces, enums, globals, events } = declarations;
|
|
22116
|
+
return classes.length === 0 && interfaces.length === 0 && enums.length === 0 && globals.length === 0 && events.length === 0;
|
|
22117
|
+
}
|
|
22118
|
+
function sharedEnums(collected) {
|
|
22119
|
+
const declared = new Set(collected.flatMap((entry) => entry.declarations.enums.map((enumeration) => enumeration.name)));
|
|
22120
|
+
if (declared.size === 0) {
|
|
22121
|
+
return declared;
|
|
22122
|
+
}
|
|
22123
|
+
return new Set(collected.flatMap((entry) => entry.externalReferences.filter((name) => declared.has(name))));
|
|
22124
|
+
}
|
|
22125
|
+
function baseKey(context) {
|
|
22126
|
+
const mode = context.development ? "development" : "release";
|
|
22127
|
+
const contracts = context.contracts.map((contract) => serializeAbi(contract)).join(";");
|
|
22128
|
+
return `${optionsKey(context.options)}|${mode}|${JSON.stringify(context.project.globals)}|${[...context.references].sort().join(",")}|${hashString(contracts)}`;
|
|
22129
|
+
}
|
|
22130
|
+
function environmentKeys(collected) {
|
|
22131
|
+
const keys = {};
|
|
22132
|
+
for (const environment of ALL_ENVIRONMENTS) {
|
|
22133
|
+
const visible = collected.filter((entry) => canReference(environment, entry.environment));
|
|
22134
|
+
keys[environment] = hashString(visible.map((entry) => `${entry.path}=${entry.fingerprint}`).join(";"));
|
|
22135
|
+
}
|
|
22136
|
+
return keys;
|
|
22137
|
+
}
|
|
22138
|
+
function createResolver(collected) {
|
|
22139
|
+
const visible = /* @__PURE__ */ new Map();
|
|
22140
|
+
const merged = /* @__PURE__ */ new Map();
|
|
22141
|
+
for (const environment of ALL_ENVIRONMENTS) {
|
|
22142
|
+
const entries3 = collected.filter((entry) => canReference(environment, entry.environment) && !declaresNothing(entry.declarations));
|
|
22143
|
+
visible.set(environment, entries3);
|
|
22144
|
+
merged.set(environment, mergeAmbient(entries3.map((entry) => entry.declarations)));
|
|
22145
|
+
}
|
|
22146
|
+
return (entry) => {
|
|
22147
|
+
if (declaresNothing(entry.declarations)) {
|
|
22148
|
+
return merged.get(entry.environment) ?? EMPTY_AMBIENT;
|
|
22149
|
+
}
|
|
22150
|
+
const entries3 = visible.get(entry.environment) ?? [];
|
|
22151
|
+
return mergeAmbient(entries3.filter((candidate) => candidate.path !== entry.path).map((candidate) => candidate.declarations));
|
|
22152
|
+
};
|
|
22153
|
+
}
|
|
22154
|
+
function createAmbientScope(collected, context) {
|
|
22155
|
+
const graph = createDependencyGraph(collected);
|
|
22156
|
+
const base = baseKey(context);
|
|
22157
|
+
const fallback = environmentKeys(collected);
|
|
22158
|
+
return {
|
|
22159
|
+
graph,
|
|
22160
|
+
keyFor: (entry) => hashString(`${base}|${graph.keyOf(entry.path) ?? fallback[entry.environment]}`),
|
|
22161
|
+
resolve: createResolver(collected)
|
|
22162
|
+
};
|
|
22163
|
+
}
|
|
20893
22164
|
|
|
20894
22165
|
// ../compiler/src/project/module-graph.ts
|
|
20895
22166
|
function buildSymbolTable(modules) {
|
|
@@ -20950,53 +22221,13 @@ function validateModuleReferences(modules) {
|
|
|
20950
22221
|
function flattenDiagnostics(modules) {
|
|
20951
22222
|
return modules.flatMap((module) => module.diagnostics.map((diagnostic) => ({ path: module.path, diagnostic })));
|
|
20952
22223
|
}
|
|
20953
|
-
function optionsKey(options) {
|
|
20954
|
-
return `${options.strict ? "strict" : ""}|${options.oop ? "oop" : ""}|${options.noUnusedLocals ? "nul" : ""}|${options.noUnusedParameters ? "nup" : ""}`;
|
|
20955
|
-
}
|
|
20956
|
-
function sharedEnums(collected) {
|
|
20957
|
-
const declared = new Set(collected.flatMap((entry) => entry.declarations.enums.map((enumeration) => enumeration.name)));
|
|
20958
|
-
if (declared.size === 0) {
|
|
20959
|
-
return declared;
|
|
20960
|
-
}
|
|
20961
|
-
return new Set(collected.flatMap((entry) => entry.externalReferences.filter((name) => declared.has(name))));
|
|
20962
|
-
}
|
|
20963
|
-
function ambientKeys(collected, context) {
|
|
20964
|
-
const keys = {};
|
|
20965
|
-
const { project, references, options } = context;
|
|
20966
|
-
const mode = context.development ? "development" : "release";
|
|
20967
|
-
const projectKey = `${optionsKey(options)}|${mode}|${JSON.stringify(project.globals)}|${[...references].sort().join(",")}`;
|
|
20968
|
-
for (const environment of ALL_ENVIRONMENTS) {
|
|
20969
|
-
const visible = collected.filter((entry) => canReference(environment, entry.environment));
|
|
20970
|
-
keys[environment] = hashString(`${projectKey}|${visible.map((entry) => `${entry.path}=${entry.fingerprint}`).join(";")}`);
|
|
20971
|
-
}
|
|
20972
|
-
return keys;
|
|
20973
|
-
}
|
|
20974
|
-
function declaresNothing(declarations) {
|
|
20975
|
-
const { classes, interfaces, enums, globals, events } = declarations;
|
|
20976
|
-
return classes.length === 0 && interfaces.length === 0 && enums.length === 0 && globals.length === 0 && events.length === 0;
|
|
20977
|
-
}
|
|
20978
|
-
function createAmbientResolver(collected) {
|
|
20979
|
-
const visible = /* @__PURE__ */ new Map();
|
|
20980
|
-
const merged = /* @__PURE__ */ new Map();
|
|
20981
|
-
for (const environment of ALL_ENVIRONMENTS) {
|
|
20982
|
-
const entries2 = collected.filter((entry) => canReference(environment, entry.environment) && !declaresNothing(entry.declarations));
|
|
20983
|
-
visible.set(environment, entries2);
|
|
20984
|
-
merged.set(environment, mergeAmbient(entries2.map((entry) => entry.declarations)));
|
|
20985
|
-
}
|
|
20986
|
-
return (entry) => {
|
|
20987
|
-
if (declaresNothing(entry.declarations)) {
|
|
20988
|
-
return merged.get(entry.environment) ?? EMPTY_AMBIENT;
|
|
20989
|
-
}
|
|
20990
|
-
const entries2 = visible.get(entry.environment) ?? [];
|
|
20991
|
-
return mergeAmbient(entries2.filter((candidate) => candidate.path !== entry.path).map((candidate) => candidate.declarations));
|
|
20992
|
-
};
|
|
20993
|
-
}
|
|
20994
22224
|
function compileModule(file, ambient, context) {
|
|
20995
22225
|
const result = compile(file.source, {
|
|
20996
22226
|
filePath: file.path,
|
|
20997
22227
|
...file.environment === void 0 ? {} : { environment: file.environment },
|
|
20998
22228
|
ambient,
|
|
20999
|
-
|
|
22229
|
+
contracts: context.contracts,
|
|
22230
|
+
project: isTestPath(file.path) ? context.testProject : context.project,
|
|
21000
22231
|
projectReferences: context.projectReferences,
|
|
21001
22232
|
compilerOptions: context.compilerOptions,
|
|
21002
22233
|
development: context.development
|
|
@@ -21009,7 +22240,7 @@ function compileModule(file, ambient, context) {
|
|
|
21009
22240
|
requiredHelpers: result.requiredHelpers,
|
|
21010
22241
|
declaredGlobals: result.declaredGlobals,
|
|
21011
22242
|
externalReferences: result.externalReferences,
|
|
21012
|
-
contributions: toContributions(result.directives, result.environment),
|
|
22243
|
+
contributions: toContributions(result.directives, result.environment, result.moduleGlobals),
|
|
21013
22244
|
diagnostics: result.diagnostics,
|
|
21014
22245
|
lines: result.lines,
|
|
21015
22246
|
topLevelReturn: result.topLevelReturn,
|
|
@@ -21050,20 +22281,22 @@ function createProjectCache() {
|
|
|
21050
22281
|
environment: result.environment,
|
|
21051
22282
|
declarations: result.declarations,
|
|
21052
22283
|
externalReferences: [...result.externalReferences.keys()],
|
|
21053
|
-
fingerprint: fingerprintDeclarations(result.declarations)
|
|
22284
|
+
fingerprint: fingerprintDeclarations(result.declarations),
|
|
22285
|
+
provides: fingerprintByName(result.declarations),
|
|
22286
|
+
requires: result.referencedNames
|
|
21054
22287
|
};
|
|
21055
22288
|
declarationCache.set(file.path, entry);
|
|
21056
22289
|
return entry;
|
|
21057
22290
|
}
|
|
21058
22291
|
function moduleFor(pair, context, reused) {
|
|
21059
22292
|
const { file, entry } = pair;
|
|
21060
|
-
const ambientKey = context.
|
|
22293
|
+
const ambientKey = context.scope.keyFor(entry);
|
|
21061
22294
|
const cached = moduleCache.get(file.path);
|
|
21062
22295
|
if (cached !== void 0 && cached.hash === entry.hash && cached.ambientKey === ambientKey) {
|
|
21063
22296
|
reused.count += 1;
|
|
21064
22297
|
return cached.module;
|
|
21065
22298
|
}
|
|
21066
|
-
const module = compileModule(file, context.resolve(entry), context);
|
|
22299
|
+
const module = compileModule(file, context.scope.resolve(entry), context);
|
|
21067
22300
|
moduleCache.set(file.path, { hash: entry.hash, ambientKey, module });
|
|
21068
22301
|
return module;
|
|
21069
22302
|
}
|
|
@@ -21077,10 +22310,12 @@ function createProjectCache() {
|
|
|
21077
22310
|
const collected = pairs.map((pair) => pair.entry);
|
|
21078
22311
|
const projectReferences = sharedEnums(collected);
|
|
21079
22312
|
const development = options.development === true;
|
|
22313
|
+
const contracts = options.contracts ?? [];
|
|
21080
22314
|
const context = {
|
|
21081
|
-
|
|
21082
|
-
|
|
22315
|
+
scope: createAmbientScope(collected, { project, references: projectReferences, options: compilerOptions, development, contracts }),
|
|
22316
|
+
contracts,
|
|
21083
22317
|
project,
|
|
22318
|
+
testProject: { globals: [...project.globals, ...TEST_DECLARATIONS] },
|
|
21084
22319
|
projectReferences,
|
|
21085
22320
|
compilerOptions,
|
|
21086
22321
|
development
|
|
@@ -21159,8 +22394,8 @@ function materialize(build, resource, includeMap) {
|
|
|
21159
22394
|
const bundled = materializeBundles(resource, build.bundles, helperContent);
|
|
21160
22395
|
return { ...build, scripts: bundled.scripts, map: includeMap ? bundled.map : null };
|
|
21161
22396
|
}
|
|
21162
|
-
function diagnosticSources(files,
|
|
21163
|
-
const paths = new Set(
|
|
22397
|
+
function diagnosticSources(files, entries3) {
|
|
22398
|
+
const paths = new Set(entries3.map((entry) => entry.path));
|
|
21164
22399
|
return new Map(files.filter((file) => paths.has(file.path)).map((file) => [file.path, file.source]));
|
|
21165
22400
|
}
|
|
21166
22401
|
function runCompile(root, config, options = {}) {
|
|
@@ -21168,10 +22403,12 @@ function runCompile(root, config, options = {}) {
|
|
|
21168
22403
|
const tracker = options.tracker ?? createPhaseTracker();
|
|
21169
22404
|
const started = performance.now();
|
|
21170
22405
|
tracker.begin("discovery");
|
|
21171
|
-
const excluded = [config.outDir];
|
|
22406
|
+
const excluded = [config.outDir, config.contracts];
|
|
21172
22407
|
const sources = discoverSources(root, config.sources, excluded);
|
|
21173
22408
|
const inputs = readProjectInputs(root, { assets: config.assets, environment: config.environment, excluded });
|
|
21174
|
-
const
|
|
22409
|
+
const files = [...sources.files, ...options.additionalFiles ?? []].sort((left, right) => left.path.localeCompare(right.path));
|
|
22410
|
+
const contracts = readDependencyContracts(root, config);
|
|
22411
|
+
const diagnostics = [...sources.diagnostics, ...inputs.diagnostics, ...contracts.diagnostics];
|
|
21175
22412
|
if (hasCliErrors(diagnostics)) {
|
|
21176
22413
|
tracker.end("failed");
|
|
21177
22414
|
return {
|
|
@@ -21184,13 +22421,15 @@ function runCompile(root, config, options = {}) {
|
|
|
21184
22421
|
environmentTemplate: null,
|
|
21185
22422
|
phases: tracker.durations(),
|
|
21186
22423
|
sources: /* @__PURE__ */ new Map(),
|
|
21187
|
-
map: null
|
|
22424
|
+
map: null,
|
|
22425
|
+
contract: null
|
|
21188
22426
|
};
|
|
21189
22427
|
}
|
|
21190
|
-
tracker.begin("compile",
|
|
22428
|
+
tracker.begin("compile", files.length);
|
|
21191
22429
|
const declarations = projectDeclarations(inputs.declared?.entries ?? null, config.environment.file);
|
|
21192
|
-
const project = cache3.compile(
|
|
22430
|
+
const project = cache3.compile(files, {
|
|
21193
22431
|
project: declarations,
|
|
22432
|
+
contracts: contracts.contracts,
|
|
21194
22433
|
compilerOptions: config.compilerOptions,
|
|
21195
22434
|
development: options.development === true,
|
|
21196
22435
|
onProgress: (event) => tracker.advance(event.item, event.index, event.total)
|
|
@@ -21211,19 +22450,20 @@ function runCompile(root, config, options = {}) {
|
|
|
21211
22450
|
build,
|
|
21212
22451
|
diagnostics,
|
|
21213
22452
|
fileDiagnostics: assembly.diagnostics,
|
|
21214
|
-
fileCount:
|
|
22453
|
+
fileCount: files.length,
|
|
21215
22454
|
durationMs: performance.now() - started,
|
|
21216
22455
|
stats: project.stats,
|
|
21217
22456
|
environmentTemplate: inputs.deployed === null ? null : renderEnvironmentTemplate(inputs.deployed),
|
|
21218
22457
|
phases: tracker.durations(),
|
|
21219
|
-
sources: diagnosticSources(
|
|
21220
|
-
map: build?.map ?? null
|
|
22458
|
+
sources: diagnosticSources(files, assembly.diagnostics),
|
|
22459
|
+
map: build?.map ?? null,
|
|
22460
|
+
contract: build === null ? null : buildResourceAbi(config.name, project.modules.flatMap((module) => module.contributions))
|
|
21221
22461
|
};
|
|
21222
22462
|
}
|
|
21223
22463
|
|
|
21224
22464
|
// src/build/resource-map-file.ts
|
|
21225
|
-
import { existsSync as existsSync5, mkdirSync as
|
|
21226
|
-
import { dirname as
|
|
22465
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
22466
|
+
import { dirname as dirname3, isAbsolute as isAbsolute3, join as join4, posix, relative as relative2, resolve as resolve9 } from "node:path";
|
|
21227
22467
|
var RESOURCE_MAP_SUFFIX = ".luam-map.json";
|
|
21228
22468
|
function isObject(value) {
|
|
21229
22469
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -21288,10 +22528,10 @@ function reason(error) {
|
|
|
21288
22528
|
return error instanceof Error ? error.message : String(error);
|
|
21289
22529
|
}
|
|
21290
22530
|
function resourceMapPath(root, config) {
|
|
21291
|
-
return
|
|
22531
|
+
return resolve9(root, config.outDir, `${config.name}${RESOURCE_MAP_SUFFIX}`);
|
|
21292
22532
|
}
|
|
21293
22533
|
function explicitResourceMapPath(root, path) {
|
|
21294
|
-
return isAbsolute3(path) ? path :
|
|
22534
|
+
return isAbsolute3(path) ? path : resolve9(root, path);
|
|
21295
22535
|
}
|
|
21296
22536
|
function writeResourceMap(root, config, map) {
|
|
21297
22537
|
const path = resourceMapPath(root, config);
|
|
@@ -21301,15 +22541,15 @@ function writeResourceMap(root, config, map) {
|
|
|
21301
22541
|
}
|
|
21302
22542
|
return;
|
|
21303
22543
|
}
|
|
21304
|
-
|
|
22544
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
21305
22545
|
const content = serializeResourceMap(map);
|
|
21306
|
-
if (!existsSync5(path) ||
|
|
21307
|
-
|
|
22546
|
+
if (!existsSync5(path) || readFileSync7(path, "utf8") !== content) {
|
|
22547
|
+
writeFileSync4(path, content, "utf8");
|
|
21308
22548
|
}
|
|
21309
22549
|
}
|
|
21310
22550
|
function readResourceMap(path) {
|
|
21311
22551
|
try {
|
|
21312
|
-
const value = JSON.parse(
|
|
22552
|
+
const value = JSON.parse(readFileSync7(path, "utf8"));
|
|
21313
22553
|
if (isObject(value) && Number.isInteger(value.version) && value.version !== RESOURCE_MAP_VERSION) {
|
|
21314
22554
|
return { map: null, error: `Resource map version ${String(value.version)} is not supported.` };
|
|
21315
22555
|
}
|
|
@@ -21323,13 +22563,13 @@ function isSearchable(name) {
|
|
|
21323
22563
|
return !name.startsWith(".") && !SKIPPED_DIRECTORIES.has(name);
|
|
21324
22564
|
}
|
|
21325
22565
|
function collectResourceMaps(directory, found) {
|
|
21326
|
-
let
|
|
22566
|
+
let entries3;
|
|
21327
22567
|
try {
|
|
21328
|
-
|
|
22568
|
+
entries3 = readdirSync2(directory, { withFileTypes: true });
|
|
21329
22569
|
} catch {
|
|
21330
22570
|
return;
|
|
21331
22571
|
}
|
|
21332
|
-
for (const entry of
|
|
22572
|
+
for (const entry of entries3) {
|
|
21333
22573
|
const absolute = join4(directory, entry.name);
|
|
21334
22574
|
if (entry.isDirectory()) {
|
|
21335
22575
|
if (isSearchable(entry.name)) {
|
|
@@ -21353,8 +22593,8 @@ function discoverResourceMap(root) {
|
|
|
21353
22593
|
}
|
|
21354
22594
|
|
|
21355
22595
|
// src/build/resource-writer.ts
|
|
21356
|
-
import { existsSync as existsSync7, mkdirSync as
|
|
21357
|
-
import { dirname as
|
|
22596
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
|
|
22597
|
+
import { dirname as dirname4, isAbsolute as isAbsolute4, relative as relative4, resolve as resolve11 } from "node:path";
|
|
21358
22598
|
|
|
21359
22599
|
// src/build/lua-tokens.ts
|
|
21360
22600
|
var WHITESPACE = /* @__PURE__ */ new Set([" ", " ", "\n", "\r", "\v", "\f"]);
|
|
@@ -21411,7 +22651,7 @@ function readLongBracket2(source, start, level, file) {
|
|
|
21411
22651
|
return end + closing.length;
|
|
21412
22652
|
}
|
|
21413
22653
|
function readShortString(source, start, file) {
|
|
21414
|
-
const
|
|
22654
|
+
const quote3 = source[start];
|
|
21415
22655
|
let index = start + 1;
|
|
21416
22656
|
while (index < source.length) {
|
|
21417
22657
|
const char = source[index];
|
|
@@ -21419,7 +22659,7 @@ function readShortString(source, start, file) {
|
|
|
21419
22659
|
index += 2;
|
|
21420
22660
|
continue;
|
|
21421
22661
|
}
|
|
21422
|
-
if (char ===
|
|
22662
|
+
if (char === quote3) {
|
|
21423
22663
|
return index + 1;
|
|
21424
22664
|
}
|
|
21425
22665
|
if (char === "\n") {
|
|
@@ -21558,7 +22798,7 @@ function minifyLuaFiles(files) {
|
|
|
21558
22798
|
|
|
21559
22799
|
// src/build/resource-prune.ts
|
|
21560
22800
|
import { existsSync as existsSync6, readdirSync as readdirSync3, rmdirSync, unlinkSync as unlinkSync2 } from "node:fs";
|
|
21561
|
-
import { join as join5, relative as relative3, resolve as
|
|
22801
|
+
import { join as join5, relative as relative3, resolve as resolve10 } from "node:path";
|
|
21562
22802
|
var GENERATED_MANIFEST = "meta.xml";
|
|
21563
22803
|
var GENERATED_EXTENSION = ".lua";
|
|
21564
22804
|
var PROTECTED = /* @__PURE__ */ new Set([ENVIRONMENT_FILE, ".env.local"]);
|
|
@@ -21575,14 +22815,14 @@ function isGenerated(relativePath2, options) {
|
|
|
21575
22815
|
return options.generatedRoots.some((root) => isUnderRoot(relativePath2, root));
|
|
21576
22816
|
}
|
|
21577
22817
|
function removeEmptyDirectories(targetDir, directory) {
|
|
21578
|
-
if (
|
|
22818
|
+
if (resolve10(directory) === resolve10(targetDir)) {
|
|
21579
22819
|
return;
|
|
21580
22820
|
}
|
|
21581
22821
|
if (readdirSync3(directory).length > 0) {
|
|
21582
22822
|
return;
|
|
21583
22823
|
}
|
|
21584
22824
|
rmdirSync(directory);
|
|
21585
|
-
removeEmptyDirectories(targetDir,
|
|
22825
|
+
removeEmptyDirectories(targetDir, resolve10(directory, ".."));
|
|
21586
22826
|
}
|
|
21587
22827
|
function pruneResource(targetDir, keep, options) {
|
|
21588
22828
|
if (!existsSync6(targetDir)) {
|
|
@@ -21635,7 +22875,7 @@ function resourceFiles(build) {
|
|
|
21635
22875
|
return files;
|
|
21636
22876
|
}
|
|
21637
22877
|
function resolveInside(targetDir, path) {
|
|
21638
|
-
const absolute =
|
|
22878
|
+
const absolute = resolve11(targetDir, path);
|
|
21639
22879
|
const inside = relative4(targetDir, absolute);
|
|
21640
22880
|
if (inside.startsWith("..") || isAbsolute4(inside)) {
|
|
21641
22881
|
throw new Error(`The generated file "${path}" resolves outside the resource directory "${targetDir}".`);
|
|
@@ -21643,11 +22883,11 @@ function resolveInside(targetDir, path) {
|
|
|
21643
22883
|
return absolute;
|
|
21644
22884
|
}
|
|
21645
22885
|
function writeIfChanged(absolute, content) {
|
|
21646
|
-
if (existsSync7(absolute) &&
|
|
22886
|
+
if (existsSync7(absolute) && readFileSync8(absolute).equals(content)) {
|
|
21647
22887
|
return false;
|
|
21648
22888
|
}
|
|
21649
|
-
|
|
21650
|
-
|
|
22889
|
+
mkdirSync4(dirname4(absolute), { recursive: true });
|
|
22890
|
+
writeFileSync5(absolute, content);
|
|
21651
22891
|
return true;
|
|
21652
22892
|
}
|
|
21653
22893
|
function writeEnvironmentFile(targetDir, template) {
|
|
@@ -21658,8 +22898,8 @@ function writeEnvironmentFile(targetDir, template) {
|
|
|
21658
22898
|
if (existsSync7(absolute)) {
|
|
21659
22899
|
return false;
|
|
21660
22900
|
}
|
|
21661
|
-
|
|
21662
|
-
|
|
22901
|
+
mkdirSync4(dirname4(absolute), { recursive: true });
|
|
22902
|
+
writeFileSync5(absolute, template, "utf8");
|
|
21663
22903
|
return true;
|
|
21664
22904
|
}
|
|
21665
22905
|
function writeResource(targetDir, build, options) {
|
|
@@ -21683,7 +22923,7 @@ function writeResource(targetDir, build, options) {
|
|
|
21683
22923
|
advance(path);
|
|
21684
22924
|
}
|
|
21685
22925
|
for (const asset of build.assets) {
|
|
21686
|
-
if (writeIfChanged(resolveInside(targetDir, asset.path),
|
|
22926
|
+
if (writeIfChanged(resolveInside(targetDir, asset.path), readFileSync8(resolve11(options.root, asset.source)))) {
|
|
21687
22927
|
written.push(asset.path);
|
|
21688
22928
|
advance(asset.path);
|
|
21689
22929
|
continue;
|
|
@@ -21800,16 +23040,16 @@ function reportBuildOutcome(context, outcome, action) {
|
|
|
21800
23040
|
}
|
|
21801
23041
|
|
|
21802
23042
|
// src/commands/resource-targets.ts
|
|
21803
|
-
import { isAbsolute as isAbsolute5, resolve as
|
|
23043
|
+
import { isAbsolute as isAbsolute5, resolve as resolve12 } from "node:path";
|
|
21804
23044
|
function resolveBuildTarget(root, config) {
|
|
21805
|
-
return
|
|
23045
|
+
return resolve12(root, config.outDir, config.name);
|
|
21806
23046
|
}
|
|
21807
23047
|
function resolveServerTarget(root, config) {
|
|
21808
23048
|
if (config.serverPath === null) {
|
|
21809
23049
|
return null;
|
|
21810
23050
|
}
|
|
21811
|
-
const serverRoot = isAbsolute5(config.serverPath) ? config.serverPath :
|
|
21812
|
-
return
|
|
23051
|
+
const serverRoot = isAbsolute5(config.serverPath) ? config.serverPath : resolve12(root, config.serverPath);
|
|
23052
|
+
return resolve12(serverRoot, config.resourcesDir, config.name);
|
|
21813
23053
|
}
|
|
21814
23054
|
|
|
21815
23055
|
// src/reporting/progress-renderer.ts
|
|
@@ -21854,7 +23094,7 @@ function createProgressRenderer(reporter, options = {}) {
|
|
|
21854
23094
|
paint(reporter.style.eraseLine());
|
|
21855
23095
|
dirty = false;
|
|
21856
23096
|
}
|
|
21857
|
-
function
|
|
23097
|
+
function render2(event) {
|
|
21858
23098
|
const columns = Math.max(20, reporter.capability.columns - 1);
|
|
21859
23099
|
const line2 = activeLine(reporter, event);
|
|
21860
23100
|
paint(`${reporter.style.eraseLine()}${line2.slice(0, columns)}`);
|
|
@@ -21874,7 +23114,7 @@ function createProgressRenderer(reporter, options = {}) {
|
|
|
21874
23114
|
if (frames > 0 && now() - lastPaintAt < throttleMs) {
|
|
21875
23115
|
return;
|
|
21876
23116
|
}
|
|
21877
|
-
|
|
23117
|
+
render2(event);
|
|
21878
23118
|
}
|
|
21879
23119
|
return {
|
|
21880
23120
|
listen: (event) => {
|
|
@@ -21932,6 +23172,9 @@ async function runBuildCommand(context, options = {}) {
|
|
|
21932
23172
|
return EXIT_DIAGNOSTICS;
|
|
21933
23173
|
}
|
|
21934
23174
|
writeResourceMap(context.root, context.config, writtenMap(outcome.map, minify));
|
|
23175
|
+
if (outcome.contract !== null && outcome.contract.exports.length > 0) {
|
|
23176
|
+
writeResourceContract(context.root, context.config, outcome.contract);
|
|
23177
|
+
}
|
|
21935
23178
|
tracker.end();
|
|
21936
23179
|
renderer.clear();
|
|
21937
23180
|
reportBuildOutcome(context, outcome, "Build");
|
|
@@ -21982,6 +23225,298 @@ function registerCheckCommand(program2, runtime) {
|
|
|
21982
23225
|
});
|
|
21983
23226
|
}
|
|
21984
23227
|
|
|
23228
|
+
// src/commands/config-command.ts
|
|
23229
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
|
|
23230
|
+
import { dirname as dirname5, relative as relative5, resolve as resolve13 } from "node:path";
|
|
23231
|
+
|
|
23232
|
+
// ../compiler/src/project/config-reader.ts
|
|
23233
|
+
var Reader = class {
|
|
23234
|
+
text;
|
|
23235
|
+
index = 0;
|
|
23236
|
+
problems = [];
|
|
23237
|
+
constructor(text) {
|
|
23238
|
+
this.text = text;
|
|
23239
|
+
}
|
|
23240
|
+
position() {
|
|
23241
|
+
const before = this.text.slice(0, this.index);
|
|
23242
|
+
const lines = before.split("\n");
|
|
23243
|
+
return { line: lines.length, column: (lines[lines.length - 1]?.length ?? 0) + 1 };
|
|
23244
|
+
}
|
|
23245
|
+
fail(message) {
|
|
23246
|
+
const { line: line2, column } = this.position();
|
|
23247
|
+
this.problems.push({ line: line2, column, message });
|
|
23248
|
+
return null;
|
|
23249
|
+
}
|
|
23250
|
+
skipComment() {
|
|
23251
|
+
if (!this.text.startsWith("--", this.index)) {
|
|
23252
|
+
return false;
|
|
23253
|
+
}
|
|
23254
|
+
if (this.text.startsWith("--[[", this.index)) {
|
|
23255
|
+
const end2 = this.text.indexOf("]]", this.index + 4);
|
|
23256
|
+
this.index = end2 === -1 ? this.text.length : end2 + 2;
|
|
23257
|
+
return true;
|
|
23258
|
+
}
|
|
23259
|
+
const end = this.text.indexOf("\n", this.index);
|
|
23260
|
+
this.index = end === -1 ? this.text.length : end + 1;
|
|
23261
|
+
return true;
|
|
23262
|
+
}
|
|
23263
|
+
skip() {
|
|
23264
|
+
for (; ; ) {
|
|
23265
|
+
const before = this.index;
|
|
23266
|
+
while (this.index < this.text.length && /\s/.test(this.text[this.index] ?? "")) {
|
|
23267
|
+
this.index += 1;
|
|
23268
|
+
}
|
|
23269
|
+
this.skipComment();
|
|
23270
|
+
if (this.index === before) {
|
|
23271
|
+
return;
|
|
23272
|
+
}
|
|
23273
|
+
}
|
|
23274
|
+
}
|
|
23275
|
+
done() {
|
|
23276
|
+
this.skip();
|
|
23277
|
+
return this.index >= this.text.length;
|
|
23278
|
+
}
|
|
23279
|
+
peek() {
|
|
23280
|
+
this.skip();
|
|
23281
|
+
return this.text[this.index] ?? "";
|
|
23282
|
+
}
|
|
23283
|
+
take(value) {
|
|
23284
|
+
this.skip();
|
|
23285
|
+
if (!this.text.startsWith(value, this.index)) {
|
|
23286
|
+
return false;
|
|
23287
|
+
}
|
|
23288
|
+
this.index += value.length;
|
|
23289
|
+
return true;
|
|
23290
|
+
}
|
|
23291
|
+
word() {
|
|
23292
|
+
this.skip();
|
|
23293
|
+
const match = /^[A-Za-z_][A-Za-z0-9_]*/.exec(this.text.slice(this.index));
|
|
23294
|
+
if (match === null) {
|
|
23295
|
+
return null;
|
|
23296
|
+
}
|
|
23297
|
+
this.index += match[0].length;
|
|
23298
|
+
return match[0];
|
|
23299
|
+
}
|
|
23300
|
+
literal() {
|
|
23301
|
+
this.skip();
|
|
23302
|
+
const rest = this.text.slice(this.index);
|
|
23303
|
+
const string = /^(\[\[[\s\S]*?\]\]|'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*")/.exec(rest);
|
|
23304
|
+
if (string !== null) {
|
|
23305
|
+
this.index += string[0].length;
|
|
23306
|
+
return "string";
|
|
23307
|
+
}
|
|
23308
|
+
const number = /^-?(?:0[xX][0-9a-fA-F]+|\d+\.?\d*(?:[eE][-+]?\d+)?|\.\d+)/.exec(rest);
|
|
23309
|
+
if (number !== null) {
|
|
23310
|
+
this.index += number[0].length;
|
|
23311
|
+
return "number";
|
|
23312
|
+
}
|
|
23313
|
+
const word = /^(true|false|nil)\b/.exec(rest);
|
|
23314
|
+
if (word === null) {
|
|
23315
|
+
return null;
|
|
23316
|
+
}
|
|
23317
|
+
this.index += word[0].length;
|
|
23318
|
+
return word[0] === "nil" ? "nil" : "boolean";
|
|
23319
|
+
}
|
|
23320
|
+
key() {
|
|
23321
|
+
this.skip();
|
|
23322
|
+
if (this.take("[")) {
|
|
23323
|
+
const rest = this.text.slice(this.index);
|
|
23324
|
+
const quoted2 = /^'([A-Za-z_][A-Za-z0-9_]*)'|^"([A-Za-z_][A-Za-z0-9_]*)"/.exec(rest);
|
|
23325
|
+
if (quoted2 === null) {
|
|
23326
|
+
return null;
|
|
23327
|
+
}
|
|
23328
|
+
this.index += quoted2[0].length;
|
|
23329
|
+
return this.take("]") && this.take("=") ? quoted2[1] ?? quoted2[2] ?? null : null;
|
|
23330
|
+
}
|
|
23331
|
+
const start = this.index;
|
|
23332
|
+
const word = this.word();
|
|
23333
|
+
if (word === null || !this.take("=") || this.peek() === "=") {
|
|
23334
|
+
this.index = start;
|
|
23335
|
+
return null;
|
|
23336
|
+
}
|
|
23337
|
+
return word;
|
|
23338
|
+
}
|
|
23339
|
+
};
|
|
23340
|
+
|
|
23341
|
+
// ../compiler/src/project/config-extractor.ts
|
|
23342
|
+
var GENERATED_MARKER = '# Generated by "luam config" from';
|
|
23343
|
+
var MAX_SOURCE_BYTES = 262144;
|
|
23344
|
+
var MAX_DEPTH2 = 8;
|
|
23345
|
+
var MAX_ENTRIES = 512;
|
|
23346
|
+
function renderRecord(fields) {
|
|
23347
|
+
return `{ ${fields.map(([name, type]) => `${name}: ${type}`).join(", ")} }`;
|
|
23348
|
+
}
|
|
23349
|
+
function render(value) {
|
|
23350
|
+
if (value.kind === "literal") {
|
|
23351
|
+
return value.type === "nil" ? "any" : value.type;
|
|
23352
|
+
}
|
|
23353
|
+
if (value.kind === "array") {
|
|
23354
|
+
return `${value.element}[]`;
|
|
23355
|
+
}
|
|
23356
|
+
return value.kind === "record" && value.fields.length > 0 ? renderRecord(value.fields) : "table";
|
|
23357
|
+
}
|
|
23358
|
+
function unify2(types) {
|
|
23359
|
+
const unique = [...new Set(types)];
|
|
23360
|
+
return unique.length === 1 ? unique[0] ?? "any" : "any";
|
|
23361
|
+
}
|
|
23362
|
+
function readTable2(reader, depth) {
|
|
23363
|
+
if (depth > MAX_DEPTH2) {
|
|
23364
|
+
return reader.fail(`A table nested deeper than ${MAX_DEPTH2} levels is not extracted.`);
|
|
23365
|
+
}
|
|
23366
|
+
const fields = [];
|
|
23367
|
+
const elements = [];
|
|
23368
|
+
for (let count = 0; ; count += 1) {
|
|
23369
|
+
if (reader.take("}")) {
|
|
23370
|
+
break;
|
|
23371
|
+
}
|
|
23372
|
+
if (count > MAX_ENTRIES) {
|
|
23373
|
+
return reader.fail(`A table with more than ${MAX_ENTRIES} entries is not extracted.`);
|
|
23374
|
+
}
|
|
23375
|
+
const key = reader.key();
|
|
23376
|
+
const value = readValue(reader, depth + 1);
|
|
23377
|
+
if (value === null) {
|
|
23378
|
+
return null;
|
|
23379
|
+
}
|
|
23380
|
+
if (key === null) {
|
|
23381
|
+
elements.push(render(value));
|
|
23382
|
+
} else {
|
|
23383
|
+
fields.push([key, render(value)]);
|
|
23384
|
+
}
|
|
23385
|
+
if (!reader.take(",") && !reader.take(";") && reader.peek() !== "}") {
|
|
23386
|
+
return reader.fail('Expected "," or "}" in a table.');
|
|
23387
|
+
}
|
|
23388
|
+
}
|
|
23389
|
+
if (fields.length > 0 && elements.length > 0) {
|
|
23390
|
+
return { kind: "table" };
|
|
23391
|
+
}
|
|
23392
|
+
return elements.length > 0 ? { kind: "array", element: unify2(elements) } : { kind: "record", fields };
|
|
23393
|
+
}
|
|
23394
|
+
function readValue(reader, depth) {
|
|
23395
|
+
if (reader.take("{")) {
|
|
23396
|
+
return readTable2(reader, depth);
|
|
23397
|
+
}
|
|
23398
|
+
const literal2 = reader.literal();
|
|
23399
|
+
if (literal2 === null) {
|
|
23400
|
+
return reader.fail("Only literals and table constructors are extracted. Declare this value by hand.");
|
|
23401
|
+
}
|
|
23402
|
+
if (reader.peek() === "." || reader.peek() === "(" || reader.peek() === "+") {
|
|
23403
|
+
return reader.fail("An expression is not extracted. Declare this value by hand.");
|
|
23404
|
+
}
|
|
23405
|
+
return { kind: "literal", type: literal2 };
|
|
23406
|
+
}
|
|
23407
|
+
function extractConfigDeclarations(source) {
|
|
23408
|
+
if (source.length > MAX_SOURCE_BYTES) {
|
|
23409
|
+
return { declarations: [], problems: [{ line: 1, column: 1, message: `A file larger than ${MAX_SOURCE_BYTES} bytes is not extracted.` }] };
|
|
23410
|
+
}
|
|
23411
|
+
const reader = new Reader(source);
|
|
23412
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
23413
|
+
while (!reader.done()) {
|
|
23414
|
+
reader.take("local");
|
|
23415
|
+
const name = reader.word();
|
|
23416
|
+
if (name === null || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || !reader.take("=")) {
|
|
23417
|
+
reader.fail("Only a top-level assignment of a literal or a table is extracted.");
|
|
23418
|
+
break;
|
|
23419
|
+
}
|
|
23420
|
+
const value = readValue(reader, 1);
|
|
23421
|
+
if (value === null) {
|
|
23422
|
+
break;
|
|
23423
|
+
}
|
|
23424
|
+
declarations.set(name, render(value));
|
|
23425
|
+
reader.take(";");
|
|
23426
|
+
}
|
|
23427
|
+
return { declarations: [...declarations].map(([name, type]) => ({ name, type })), problems: reader.problems };
|
|
23428
|
+
}
|
|
23429
|
+
function renderConfigDeclarations(source, declarations) {
|
|
23430
|
+
const lines = declarations.map((declaration) => `declare ${declaration.name}: ${declaration.type}`);
|
|
23431
|
+
return `${GENERATED_MARKER} "${source}". Do not edit.
|
|
23432
|
+
|
|
23433
|
+
${lines.join("\n")}
|
|
23434
|
+
`;
|
|
23435
|
+
}
|
|
23436
|
+
function isGeneratedDeclaration(text) {
|
|
23437
|
+
return text.startsWith(GENERATED_MARKER);
|
|
23438
|
+
}
|
|
23439
|
+
|
|
23440
|
+
// src/commands/config-command.ts
|
|
23441
|
+
var DEFAULT_CONFIG_SOURCE = "config.lua";
|
|
23442
|
+
var DEFAULT_CONFIG_OUTPUT = "config.d.luam";
|
|
23443
|
+
function escapes(root, path) {
|
|
23444
|
+
const inside = relative5(root, path);
|
|
23445
|
+
return inside.startsWith("..") || resolve13(root, inside) !== path;
|
|
23446
|
+
}
|
|
23447
|
+
function readSource2(root, source, reporter) {
|
|
23448
|
+
const path = resolve13(root, source);
|
|
23449
|
+
if (escapes(root, path)) {
|
|
23450
|
+
reporter.error(`Config declarations read only inside the project, but "${source}" points outside it.`);
|
|
23451
|
+
return null;
|
|
23452
|
+
}
|
|
23453
|
+
if (!existsSync8(path)) {
|
|
23454
|
+
reporter.error(`Config declarations found no file at "${source}".`);
|
|
23455
|
+
return null;
|
|
23456
|
+
}
|
|
23457
|
+
try {
|
|
23458
|
+
return readFileSync9(path, "utf8");
|
|
23459
|
+
} catch {
|
|
23460
|
+
reporter.error(`Config declarations could not read "${source}".`);
|
|
23461
|
+
return null;
|
|
23462
|
+
}
|
|
23463
|
+
}
|
|
23464
|
+
function writable(root, out, reporter) {
|
|
23465
|
+
const path = resolve13(root, out);
|
|
23466
|
+
if (escapes(root, path)) {
|
|
23467
|
+
reporter.error(`Config declarations write only inside the project, but "${out}" points outside it.`);
|
|
23468
|
+
return null;
|
|
23469
|
+
}
|
|
23470
|
+
if (existsSync8(path) && !isGeneratedDeclaration(readFileSync9(path, "utf8"))) {
|
|
23471
|
+
reporter.error(`"${out}" was not generated by this command and was left alone. Choose another path with --out.`);
|
|
23472
|
+
return null;
|
|
23473
|
+
}
|
|
23474
|
+
return path;
|
|
23475
|
+
}
|
|
23476
|
+
function runConfigCommand(root, reporter, options = {}) {
|
|
23477
|
+
const source = options.source ?? DEFAULT_CONFIG_SOURCE;
|
|
23478
|
+
const out = options.out ?? DEFAULT_CONFIG_OUTPUT;
|
|
23479
|
+
const text = readSource2(root, source, reporter);
|
|
23480
|
+
if (text === null) {
|
|
23481
|
+
return EXIT_DIAGNOSTICS;
|
|
23482
|
+
}
|
|
23483
|
+
const extracted = extractConfigDeclarations(text);
|
|
23484
|
+
for (const problem of extracted.problems) {
|
|
23485
|
+
reporter.warn(`${source}:${problem.line}:${problem.column} ${problem.message}`);
|
|
23486
|
+
}
|
|
23487
|
+
if (extracted.declarations.length === 0) {
|
|
23488
|
+
reporter.error(`Config declarations found nothing to declare in "${source}".`);
|
|
23489
|
+
return EXIT_DIAGNOSTICS;
|
|
23490
|
+
}
|
|
23491
|
+
const rendered = renderConfigDeclarations(source, extracted.declarations);
|
|
23492
|
+
if (options.write !== true) {
|
|
23493
|
+
reporter.info(rendered.trimEnd());
|
|
23494
|
+
reporter.info(`Config declarations would write ${extracted.declarations.length} name(s) to "${out}". Pass --write to save it.`);
|
|
23495
|
+
return EXIT_OK;
|
|
23496
|
+
}
|
|
23497
|
+
const path = writable(root, out, reporter);
|
|
23498
|
+
if (path === null) {
|
|
23499
|
+
return EXIT_DIAGNOSTICS;
|
|
23500
|
+
}
|
|
23501
|
+
mkdirSync5(dirname5(path), { recursive: true });
|
|
23502
|
+
writeFileSync6(path, rendered, "utf8");
|
|
23503
|
+
reporter.info(`Config declarations wrote ${extracted.declarations.length} name(s) to "${out}".`);
|
|
23504
|
+
return EXIT_OK;
|
|
23505
|
+
}
|
|
23506
|
+
|
|
23507
|
+
// src/cli/registry/config-registration.ts
|
|
23508
|
+
function registerConfigCommand(program2, runtime) {
|
|
23509
|
+
const command = program2.command("config").description('Derive a declaration file from the literal data in a native "config.lua".');
|
|
23510
|
+
addProjectOptions(command).addOption(valueOption("--source <path>", 'Native Lua file to read. Defaults to "config.lua".')).addOption(valueOption("--out <path>", 'Declaration file to write. Defaults to "config.d.luam".')).addOption(new Option("--write", "Write the declaration file instead of printing it."));
|
|
23511
|
+
command.action((options) => {
|
|
23512
|
+
runtime.exitCode = runConfigCommand(commandRoot(runtime, options), runtime.reporter, {
|
|
23513
|
+
source: options.source ?? null,
|
|
23514
|
+
out: options.out ?? null,
|
|
23515
|
+
write: options.write === true
|
|
23516
|
+
});
|
|
23517
|
+
});
|
|
23518
|
+
}
|
|
23519
|
+
|
|
21985
23520
|
// src/commands/ensure-runner.ts
|
|
21986
23521
|
function hasChanges(result) {
|
|
21987
23522
|
return result.written.length > 0 || result.removed.length > 0;
|
|
@@ -22006,7 +23541,7 @@ function restartResource(scope, serverConsole) {
|
|
|
22006
23541
|
reporter.success(`Restarted "${scope.context.config.name}" through the owned server console.`);
|
|
22007
23542
|
return true;
|
|
22008
23543
|
}
|
|
22009
|
-
function
|
|
23544
|
+
function failed2(outcome) {
|
|
22010
23545
|
return { ok: false, outcome, sync: null, restarted: false };
|
|
22011
23546
|
}
|
|
22012
23547
|
async function runOnce(scope, target, cache3, options) {
|
|
@@ -22029,7 +23564,7 @@ async function runOnce(scope, target, cache3, options) {
|
|
|
22029
23564
|
if (outcome.build === null) {
|
|
22030
23565
|
reportBuildOutcome(context, outcome, "Build");
|
|
22031
23566
|
reporter.warn("Skipping sync and restart because the build reported errors.");
|
|
22032
|
-
return
|
|
23567
|
+
return failed2(outcome);
|
|
22033
23568
|
}
|
|
22034
23569
|
const writeOptions = trackedWriteOptions(context.root, context.config, outcome.environmentTemplate, tracker);
|
|
22035
23570
|
reportBuildOutcome(context, outcome, "Build");
|
|
@@ -22077,8 +23612,8 @@ function reportRebuildSeparator(reporter, at = /* @__PURE__ */ new Date()) {
|
|
|
22077
23612
|
}
|
|
22078
23613
|
|
|
22079
23614
|
// src/watch/source-watcher.ts
|
|
22080
|
-
import { existsSync as
|
|
22081
|
-
import { relative as
|
|
23615
|
+
import { existsSync as existsSync9, watch } from "node:fs";
|
|
23616
|
+
import { relative as relative6, resolve as resolve14 } from "node:path";
|
|
22082
23617
|
var DEFAULT_DEBOUNCE_MS = 120;
|
|
22083
23618
|
function watchSources(root, sources, onChange, debounceMs = DEFAULT_DEBOUNCE_MS) {
|
|
22084
23619
|
const resolver = createSourceResolver(sources);
|
|
@@ -22094,12 +23629,12 @@ function watchSources(root, sources, onChange, debounceMs = DEFAULT_DEBOUNCE_MS)
|
|
|
22094
23629
|
}, debounceMs);
|
|
22095
23630
|
};
|
|
22096
23631
|
const isWatched = (directory, filename) => {
|
|
22097
|
-
const path = normalizePattern(
|
|
23632
|
+
const path = normalizePattern(relative6(root, resolve14(directory, filename)));
|
|
22098
23633
|
return path.endsWith(SOURCE_EXTENSION) && resolver.resolve(path).matches.length > 0;
|
|
22099
23634
|
};
|
|
22100
23635
|
for (const entry of resolver.roots) {
|
|
22101
|
-
const absolute =
|
|
22102
|
-
if (!
|
|
23636
|
+
const absolute = resolve14(root, entry);
|
|
23637
|
+
if (!existsSync9(absolute)) {
|
|
22103
23638
|
continue;
|
|
22104
23639
|
}
|
|
22105
23640
|
watchers.push(
|
|
@@ -22292,26 +23827,26 @@ function parseMtaLogLine(line2, activeResource, fallback = /* @__PURE__ */ new D
|
|
|
22292
23827
|
const level = severity?.[1] === void 0 ? "info" : logLevel(severity[1]) ?? "info";
|
|
22293
23828
|
const message = stamped.rest.replace(resource[0], "").replace(severity?.[0] ?? "", "").replace(/^\s*[:|-]?\s*/, "");
|
|
22294
23829
|
const sourcePath = resource[2];
|
|
22295
|
-
const
|
|
23830
|
+
const sourceLine2 = resource[3];
|
|
22296
23831
|
return {
|
|
22297
23832
|
timestamp: stamped.value,
|
|
22298
23833
|
environment: "server",
|
|
22299
23834
|
level,
|
|
22300
23835
|
message,
|
|
22301
23836
|
resource: activeResource,
|
|
22302
|
-
...sourcePath !== void 0 &&
|
|
23837
|
+
...sourcePath !== void 0 && sourceLine2 !== void 0 ? { source: { path: sourcePath, line: Number(sourceLine2) } } : {}
|
|
22303
23838
|
};
|
|
22304
23839
|
}
|
|
22305
23840
|
return { timestamp: stamped.value, environment: "server", level: "info", message: stamped.rest, resource: activeResource };
|
|
22306
23841
|
}
|
|
22307
23842
|
|
|
22308
23843
|
// src/logging/server-log-follower.ts
|
|
22309
|
-
import { closeSync, existsSync as
|
|
22310
|
-
import { isAbsolute as isAbsolute6, resolve as
|
|
23844
|
+
import { closeSync, existsSync as existsSync10, openSync, readSync, statSync as statSync4 } from "node:fs";
|
|
23845
|
+
import { isAbsolute as isAbsolute6, resolve as resolve15 } from "node:path";
|
|
22311
23846
|
var DEFAULT_POLL_INTERVAL_MS = 100;
|
|
22312
23847
|
function resolveServerLogPath(root, serverPath) {
|
|
22313
|
-
const server = isAbsolute6(serverPath) ? serverPath :
|
|
22314
|
-
return
|
|
23848
|
+
const server = isAbsolute6(serverPath) ? serverPath : resolve15(root, serverPath);
|
|
23849
|
+
return resolve15(server, "mods/deathmatch/logs/server.log");
|
|
22315
23850
|
}
|
|
22316
23851
|
function identity(path) {
|
|
22317
23852
|
const stats = statSync4(path);
|
|
@@ -22336,7 +23871,7 @@ function followServerLog(path, onLine, options = {}) {
|
|
|
22336
23871
|
let initialized = false;
|
|
22337
23872
|
let closed = false;
|
|
22338
23873
|
const poll = () => {
|
|
22339
|
-
if (closed || !
|
|
23874
|
+
if (closed || !existsSync10(path)) {
|
|
22340
23875
|
return;
|
|
22341
23876
|
}
|
|
22342
23877
|
try {
|
|
@@ -22452,9 +23987,9 @@ function connectServerConsoleInput(input, output, interrupt) {
|
|
|
22452
23987
|
|
|
22453
23988
|
// src/server/server-executable-resolver.ts
|
|
22454
23989
|
import { realpathSync, statSync as statSync5 } from "node:fs";
|
|
22455
|
-
import { isAbsolute as isAbsolute7, relative as
|
|
23990
|
+
import { isAbsolute as isAbsolute7, relative as relative7, resolve as resolve16 } from "node:path";
|
|
22456
23991
|
function isInside(root, path) {
|
|
22457
|
-
const pathFromRoot =
|
|
23992
|
+
const pathFromRoot = relative7(root, path);
|
|
22458
23993
|
return pathFromRoot.length === 0 || !pathFromRoot.startsWith("..") && !isAbsolute7(pathFromRoot);
|
|
22459
23994
|
}
|
|
22460
23995
|
function candidates2(platform) {
|
|
@@ -22467,14 +24002,14 @@ function candidates2(platform) {
|
|
|
22467
24002
|
throw new Error(`Local MTA server startup is not supported on platform "${platform}". Use Windows or Linux.`);
|
|
22468
24003
|
}
|
|
22469
24004
|
function resolveServerExecutable(options) {
|
|
22470
|
-
const serverRoot =
|
|
24005
|
+
const serverRoot = resolve16(options.root, options.serverPath);
|
|
22471
24006
|
const names = options.configured === null ? candidates2(options.platform ?? process.platform) : [options.configured];
|
|
22472
24007
|
const attempted = [];
|
|
22473
|
-
if (options.configured !== null && (isAbsolute7(options.configured) || !isInside(serverRoot,
|
|
24008
|
+
if (options.configured !== null && (isAbsolute7(options.configured) || !isInside(serverRoot, resolve16(serverRoot, options.configured)))) {
|
|
22474
24009
|
throw new Error(`Configured development.server.executable must stay inside serverPath but received "${options.configured}".`);
|
|
22475
24010
|
}
|
|
22476
24011
|
for (const name of names) {
|
|
22477
|
-
const path =
|
|
24012
|
+
const path = resolve16(serverRoot, name);
|
|
22478
24013
|
attempted.push(path);
|
|
22479
24014
|
try {
|
|
22480
24015
|
if (!statSync5(path).isFile()) {
|
|
@@ -22744,9 +24279,52 @@ function registerDevCommand(program2, runtime) {
|
|
|
22744
24279
|
});
|
|
22745
24280
|
}
|
|
22746
24281
|
|
|
24282
|
+
// src/testing/lua-interpreter.ts
|
|
24283
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
24284
|
+
var REQUIRED_LUA_VERSION = "Lua 5.1";
|
|
24285
|
+
var LUA_CANDIDATES = ["lua5.1", "lua51", "lua", "luajit"];
|
|
24286
|
+
var LUA_ENV_VARIABLE = "LUAM_LUA";
|
|
24287
|
+
var INSTALL_HINT = 'Install a Lua 5.1 interpreter and put it on PATH, or point "--lua" or the LUAM_LUA variable at one. LuaJIT reports "Lua 5.1" and is accepted.';
|
|
24288
|
+
function probeInterpreter(executable) {
|
|
24289
|
+
const result = spawnSync2(executable, ["-e", "io.write(_VERSION)"], { encoding: "utf8", shell: false, windowsHide: true });
|
|
24290
|
+
if (result.error !== void 0 || result.status !== 0) {
|
|
24291
|
+
return null;
|
|
24292
|
+
}
|
|
24293
|
+
return result.stdout.trim();
|
|
24294
|
+
}
|
|
24295
|
+
function findLuaInterpreter(request = {}) {
|
|
24296
|
+
const probe = request.probe ?? probeInterpreter;
|
|
24297
|
+
const pinned = request.explicit ?? request.env ?? null;
|
|
24298
|
+
const candidates3 = pinned === null ? LUA_CANDIDATES : [pinned];
|
|
24299
|
+
for (const executable of candidates3) {
|
|
24300
|
+
const version = probe(executable);
|
|
24301
|
+
if (version === REQUIRED_LUA_VERSION) {
|
|
24302
|
+
return { executable, version };
|
|
24303
|
+
}
|
|
24304
|
+
}
|
|
24305
|
+
return null;
|
|
24306
|
+
}
|
|
24307
|
+
function describeMissingInterpreter(request = {}) {
|
|
24308
|
+
const pinned = request.explicit ?? request.env ?? null;
|
|
24309
|
+
if (pinned !== null) {
|
|
24310
|
+
return `"${pinned}" is not a ${REQUIRED_LUA_VERSION} interpreter.`;
|
|
24311
|
+
}
|
|
24312
|
+
return `No ${REQUIRED_LUA_VERSION} interpreter was found on PATH (tried ${LUA_CANDIDATES.map((name) => `"${name}"`).join(", ")}).`;
|
|
24313
|
+
}
|
|
24314
|
+
|
|
22747
24315
|
// src/commands/doctor-command.ts
|
|
22748
|
-
function
|
|
24316
|
+
function reportInterpreter(reporter, probe) {
|
|
24317
|
+
const interpreter = findLuaInterpreter(probe === void 0 ? {} : { probe });
|
|
24318
|
+
if (interpreter === null) {
|
|
24319
|
+
reporter.warn(`No ${REQUIRED_LUA_VERSION} interpreter was found on PATH, so "luam test" cannot run.`);
|
|
24320
|
+
reporter.detail(INSTALL_HINT);
|
|
24321
|
+
return;
|
|
24322
|
+
}
|
|
24323
|
+
reporter.success(`"${interpreter.executable}" reports ${interpreter.version} and can run "luam test".`);
|
|
24324
|
+
}
|
|
24325
|
+
function runDoctorCommand(reporter, editorService, probe) {
|
|
22749
24326
|
reporter.success(`Luam CLI ${VERSION} is running on Node.js ${process.versions.node}.`);
|
|
24327
|
+
reportInterpreter(reporter, probe);
|
|
22750
24328
|
const editors = editorService.detect();
|
|
22751
24329
|
if (editors.length === 0) {
|
|
22752
24330
|
reporter.warn("No supported editor command was found on PATH.");
|
|
@@ -22792,7 +24370,7 @@ function registerEnsureCommand(program2, runtime) {
|
|
|
22792
24370
|
}
|
|
22793
24371
|
|
|
22794
24372
|
// src/cli/registry/init-registration.ts
|
|
22795
|
-
import { resolve as
|
|
24373
|
+
import { resolve as resolve18 } from "node:path";
|
|
22796
24374
|
|
|
22797
24375
|
// src/commands/init-command.ts
|
|
22798
24376
|
import { basename as basename2 } from "node:path";
|
|
@@ -22854,7 +24432,7 @@ function renderManifest(source, details) {
|
|
|
22854
24432
|
}
|
|
22855
24433
|
|
|
22856
24434
|
// src/scaffold/template-files.ts
|
|
22857
|
-
import { existsSync as
|
|
24435
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
|
|
22858
24436
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
22859
24437
|
|
|
22860
24438
|
// ../template/src/template.ts
|
|
@@ -22870,10 +24448,10 @@ function bundledTemplatePath(source) {
|
|
|
22870
24448
|
}
|
|
22871
24449
|
function resolveTemplatePath(source) {
|
|
22872
24450
|
const packaged = fileURLToPath2(resolveTemplateUrl(source));
|
|
22873
|
-
return
|
|
24451
|
+
return existsSync11(packaged) ? packaged : bundledTemplatePath(source);
|
|
22874
24452
|
}
|
|
22875
24453
|
function readTemplateSource(source) {
|
|
22876
|
-
return
|
|
24454
|
+
return readFileSync10(resolveTemplatePath(source), "utf8");
|
|
22877
24455
|
}
|
|
22878
24456
|
|
|
22879
24457
|
// src/scaffold/scaffold-plan.ts
|
|
@@ -22889,19 +24467,19 @@ function buildScaffoldPlan(details) {
|
|
|
22889
24467
|
}
|
|
22890
24468
|
|
|
22891
24469
|
// src/scaffold/scaffold-writer.ts
|
|
22892
|
-
import { existsSync as
|
|
22893
|
-
import { dirname as
|
|
24470
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
24471
|
+
import { dirname as dirname6, resolve as resolve17 } from "node:path";
|
|
22894
24472
|
function writeScaffold(targetDir, plan, force) {
|
|
22895
24473
|
const written = [];
|
|
22896
24474
|
const skipped = [];
|
|
22897
24475
|
for (const file of plan.files) {
|
|
22898
|
-
const absolute =
|
|
22899
|
-
if (!force &&
|
|
24476
|
+
const absolute = resolve17(targetDir, file.path);
|
|
24477
|
+
if (!force && existsSync12(absolute)) {
|
|
22900
24478
|
skipped.push(file.path);
|
|
22901
24479
|
continue;
|
|
22902
24480
|
}
|
|
22903
|
-
|
|
22904
|
-
|
|
24481
|
+
mkdirSync6(dirname6(absolute), { recursive: true });
|
|
24482
|
+
writeFileSync7(absolute, file.content, "utf8");
|
|
22905
24483
|
written.push(file.path);
|
|
22906
24484
|
}
|
|
22907
24485
|
return { written: written.sort((left, right) => left.localeCompare(right)), skipped: skipped.sort((left, right) => left.localeCompare(right)) };
|
|
@@ -22951,7 +24529,7 @@ function registerInitCommand(program2, runtime) {
|
|
|
22951
24529
|
command.addOption(cwdOption()).addOption(valueOption("--name <name>", "Resource name. Defaults to the destination directory name.")).addOption(new Option("--force", "Overwrite files that already exist.")).addOption(new Option("-y, --yes", "Accept the defaults without prompting.")).addOption(colorOption());
|
|
22952
24530
|
command.action(async (path, options) => {
|
|
22953
24531
|
const root = commandRoot(runtime, options);
|
|
22954
|
-
const target = path === void 0 ? root :
|
|
24532
|
+
const target = path === void 0 ? root : resolve18(root, path);
|
|
22955
24533
|
runtime.exitCode = await runInitCommand(target, runtime.logger, {
|
|
22956
24534
|
name: options.name ?? null,
|
|
22957
24535
|
force: options.force === true,
|
|
@@ -22969,7 +24547,7 @@ async function runSetupCommand(reporter, options) {
|
|
|
22969
24547
|
reporter.detail("Supported commands: code, code-insiders, cursor, codium, windsurf.");
|
|
22970
24548
|
return EXIT_DIAGNOSTICS;
|
|
22971
24549
|
}
|
|
22972
|
-
let
|
|
24550
|
+
let failed3 = false;
|
|
22973
24551
|
for (const editor of editors) {
|
|
22974
24552
|
if (options.editorService.hasExtension(editor)) {
|
|
22975
24553
|
reporter.success(`${editor.name}: Luam is already installed.`);
|
|
@@ -22988,12 +24566,12 @@ async function runSetupCommand(reporter, options) {
|
|
|
22988
24566
|
const result = await options.editorService.install(editor);
|
|
22989
24567
|
if (result.error !== null) {
|
|
22990
24568
|
reporter.error(`${editor.name}: installation failed. ${result.error}`);
|
|
22991
|
-
|
|
24569
|
+
failed3 = true;
|
|
22992
24570
|
continue;
|
|
22993
24571
|
}
|
|
22994
24572
|
reporter.success(`${editor.name}: installed from the ${result.source}.`);
|
|
22995
24573
|
}
|
|
22996
|
-
return
|
|
24574
|
+
return failed3 ? EXIT_DIAGNOSTICS : EXIT_OK;
|
|
22997
24575
|
}
|
|
22998
24576
|
|
|
22999
24577
|
// src/editor/installation-prompt.ts
|
|
@@ -23074,13 +24652,640 @@ function registerServerCommand(program2, runtime) {
|
|
|
23074
24652
|
});
|
|
23075
24653
|
}
|
|
23076
24654
|
|
|
24655
|
+
// src/testing/test-report.ts
|
|
24656
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
24657
|
+
import { resolve as resolve19 } from "node:path";
|
|
24658
|
+
function sourceLine(root, path, line2, cache3) {
|
|
24659
|
+
let lines = cache3.get(path);
|
|
24660
|
+
if (lines === void 0) {
|
|
24661
|
+
try {
|
|
24662
|
+
lines = readFileSync11(resolve19(root, path), "utf8").split(/\r?\n/);
|
|
24663
|
+
} catch {
|
|
24664
|
+
lines = [];
|
|
24665
|
+
}
|
|
24666
|
+
cache3.set(path, lines);
|
|
24667
|
+
}
|
|
24668
|
+
return lines[line2 - 1] ?? null;
|
|
24669
|
+
}
|
|
24670
|
+
function columnOf(text, symbol) {
|
|
24671
|
+
if (text === null) {
|
|
24672
|
+
return 1;
|
|
24673
|
+
}
|
|
24674
|
+
const symbolIndex = symbol === void 0 ? -1 : text.indexOf(symbol);
|
|
24675
|
+
if (symbolIndex >= 0) {
|
|
24676
|
+
return symbolIndex + 1;
|
|
24677
|
+
}
|
|
24678
|
+
const indent = /^\s*/.exec(text)?.[0].length ?? 0;
|
|
24679
|
+
return indent + 1;
|
|
24680
|
+
}
|
|
24681
|
+
function resolveTestPosition(root, map, result, cache3) {
|
|
24682
|
+
if (map === null || result.file === null || result.line === null) {
|
|
24683
|
+
return null;
|
|
24684
|
+
}
|
|
24685
|
+
const resolution = resolveResourcePosition(map, result.file, result.line);
|
|
24686
|
+
if (resolution.status !== "resolved") {
|
|
24687
|
+
return null;
|
|
24688
|
+
}
|
|
24689
|
+
const { file, line: line2, symbol } = resolution.position;
|
|
24690
|
+
return { path: file, line: line2, column: columnOf(sourceLine(root, file, line2, cache3), symbol) };
|
|
24691
|
+
}
|
|
24692
|
+
function formatLocation(root, map, result, cache3) {
|
|
24693
|
+
const position2 = resolveTestPosition(root, map, result, cache3);
|
|
24694
|
+
if (position2 !== null) {
|
|
24695
|
+
return `${position2.path}:${position2.line}:${position2.column}`;
|
|
24696
|
+
}
|
|
24697
|
+
return result.file === null || result.line === null ? "unknown position" : `${result.file}:${result.line}`;
|
|
24698
|
+
}
|
|
24699
|
+
function countResults(runs) {
|
|
24700
|
+
const results = runs.flatMap((run) => run.results);
|
|
24701
|
+
const passed = results.filter((result) => result.passed).length;
|
|
24702
|
+
return { passed, failed: results.length - passed };
|
|
24703
|
+
}
|
|
24704
|
+
function reportTestResults(reporter, runs, root, map, durationMs) {
|
|
24705
|
+
const cache3 = /* @__PURE__ */ new Map();
|
|
24706
|
+
const marker = (name) => reporter.style.marker(name);
|
|
24707
|
+
for (const run of runs) {
|
|
24708
|
+
for (const line2 of run.output) {
|
|
24709
|
+
reporter.detail(` ${line2}`);
|
|
24710
|
+
}
|
|
24711
|
+
for (const result of run.results) {
|
|
24712
|
+
const tone = result.passed ? "success" : "error";
|
|
24713
|
+
const label2 = `${result.environment} \xB7 ${result.name}`;
|
|
24714
|
+
reporter.raw(` ${reporter.style.paint(tone, marker(result.passed ? "success" : "failure"))} ${label2}`);
|
|
24715
|
+
if (!result.passed) {
|
|
24716
|
+
reporter.rawError(` ${formatLocation(root, map, result, cache3)} ${result.message}`);
|
|
24717
|
+
}
|
|
24718
|
+
}
|
|
24719
|
+
if (run.failure !== null) {
|
|
24720
|
+
reporter.error(`The ${run.environment} test run did not complete: ${run.failure}`);
|
|
24721
|
+
}
|
|
24722
|
+
}
|
|
24723
|
+
const totals = countResults(runs);
|
|
24724
|
+
const summary = `${pluralize(totals.passed, "test")} passed, ${totals.failed} failed in ${formatDuration(durationMs)}.`;
|
|
24725
|
+
const crashed = runs.some((run) => run.failure !== null);
|
|
24726
|
+
if (totals.failed === 0 && !crashed) {
|
|
24727
|
+
reporter.success(`Tests passed: ${summary}`);
|
|
24728
|
+
return true;
|
|
24729
|
+
}
|
|
24730
|
+
reporter.error(`Tests failed: ${summary}`);
|
|
24731
|
+
return false;
|
|
24732
|
+
}
|
|
24733
|
+
|
|
24734
|
+
// src/testing/test-discovery.ts
|
|
24735
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
24736
|
+
import { resolve as resolve20 } from "node:path";
|
|
24737
|
+
var DEFAULT_TEST_ENVIRONMENT = "shared";
|
|
24738
|
+
var SIDE_CONFLICT2 = "test-source-side-conflict";
|
|
24739
|
+
var UNREADABLE_TEST = "test-source-unreadable";
|
|
24740
|
+
function readTest(root, path, diagnostics) {
|
|
24741
|
+
try {
|
|
24742
|
+
return readFileSync12(resolve20(root, path), "utf8");
|
|
24743
|
+
} catch (error) {
|
|
24744
|
+
diagnostics.push(cliError(UNREADABLE_TEST, `The test file "${path}" could not be read: ${error instanceof Error ? error.message : String(error)}`));
|
|
24745
|
+
return null;
|
|
24746
|
+
}
|
|
24747
|
+
}
|
|
24748
|
+
function discoverTests(root, sources, excluded = []) {
|
|
24749
|
+
const resolver = createSourceResolver(sources);
|
|
24750
|
+
const tree = listProjectFiles(root, ["."], excluded);
|
|
24751
|
+
const diagnostics = tree.errors.map((message) => cliError(UNREADABLE_TEST, message));
|
|
24752
|
+
const files = [];
|
|
24753
|
+
for (const path of tree.files.filter(isTestPath)) {
|
|
24754
|
+
const resolution = resolver.resolve(path);
|
|
24755
|
+
if (resolution.matches.length > 1) {
|
|
24756
|
+
diagnostics.push(cliError(SIDE_CONFLICT2, `"${path}" is matched by more than one side: ${describeMatches(resolution.matches)}. Narrow the patterns.`));
|
|
24757
|
+
continue;
|
|
24758
|
+
}
|
|
24759
|
+
const source = readTest(root, path, diagnostics);
|
|
24760
|
+
if (source !== null) {
|
|
24761
|
+
files.push({ path, source, environment: resolution.environment ?? DEFAULT_TEST_ENVIRONMENT });
|
|
24762
|
+
}
|
|
24763
|
+
}
|
|
24764
|
+
return { files: files.sort((left, right) => left.path.localeCompare(right.path)), diagnostics };
|
|
24765
|
+
}
|
|
24766
|
+
|
|
24767
|
+
// src/testing/test-runner.ts
|
|
24768
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
24769
|
+
import { mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync8 } from "node:fs";
|
|
24770
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
24771
|
+
import { dirname as dirname7, join as join6 } from "node:path";
|
|
24772
|
+
|
|
24773
|
+
// src/testing/harness-assertions.ts
|
|
24774
|
+
var ASSERTIONS_SOURCE = String.raw`local SENTINEL = '##luam:test'
|
|
24775
|
+
local HARNESS = debug.getinfo(1, 'S').short_src
|
|
24776
|
+
|
|
24777
|
+
local tests = {}
|
|
24778
|
+
local groups = {}
|
|
24779
|
+
local beforeHooks = {}
|
|
24780
|
+
local afterHooks = {}
|
|
24781
|
+
local callLog = {}
|
|
24782
|
+
local behaviour = {}
|
|
24783
|
+
local stubNames = rawget(_G, '__luamTestNames') or {}
|
|
24784
|
+
|
|
24785
|
+
local function escape(value)
|
|
24786
|
+
local text = tostring(value)
|
|
24787
|
+
text = text:gsub('\\', '\\\\')
|
|
24788
|
+
text = text:gsub('\t', '\\t')
|
|
24789
|
+
text = text:gsub('\r', '\\r')
|
|
24790
|
+
text = text:gsub('\n', '\\n')
|
|
24791
|
+
return text
|
|
24792
|
+
end
|
|
24793
|
+
|
|
24794
|
+
local function emit(kind, ...)
|
|
24795
|
+
local parts = { SENTINEL, kind }
|
|
24796
|
+
for index = 1, select('#', ...) do
|
|
24797
|
+
parts[#parts + 1] = escape(select(index, ...))
|
|
24798
|
+
end
|
|
24799
|
+
io.write(table.concat(parts, '\t'))
|
|
24800
|
+
io.write('\n')
|
|
24801
|
+
io.stdout:flush()
|
|
24802
|
+
end
|
|
24803
|
+
|
|
24804
|
+
local function origin()
|
|
24805
|
+
local index = 1
|
|
24806
|
+
while true do
|
|
24807
|
+
local info = debug.getinfo(index, 'Sl')
|
|
24808
|
+
if info == nil then
|
|
24809
|
+
return '', ''
|
|
24810
|
+
end
|
|
24811
|
+
if info.what ~= 'C' and info.short_src ~= HARNESS then
|
|
24812
|
+
return info.short_src, info.currentline
|
|
24813
|
+
end
|
|
24814
|
+
index = index + 1
|
|
24815
|
+
end
|
|
24816
|
+
end
|
|
24817
|
+
|
|
24818
|
+
local function failure(message)
|
|
24819
|
+
local file, line = origin()
|
|
24820
|
+
return { luamTestFailure = true, message = message, file = file, line = line }
|
|
24821
|
+
end
|
|
24822
|
+
|
|
24823
|
+
local function fail(message)
|
|
24824
|
+
error(failure(message), 0)
|
|
24825
|
+
end
|
|
24826
|
+
|
|
24827
|
+
local function handler(err)
|
|
24828
|
+
if type(err) == 'table' and err.luamTestFailure then
|
|
24829
|
+
return err
|
|
24830
|
+
end
|
|
24831
|
+
return failure(tostring(err))
|
|
24832
|
+
end
|
|
24833
|
+
|
|
24834
|
+
local function attempt(body)
|
|
24835
|
+
local ok, err = xpcall(body, handler)
|
|
24836
|
+
if ok then
|
|
24837
|
+
return nil
|
|
24838
|
+
end
|
|
24839
|
+
return err
|
|
24840
|
+
end
|
|
24841
|
+
|
|
24842
|
+
local function describeValue(value)
|
|
24843
|
+
if type(value) == 'string' then
|
|
24844
|
+
return string.format('%q', value)
|
|
24845
|
+
end
|
|
24846
|
+
return tostring(value)
|
|
24847
|
+
end
|
|
24848
|
+
|
|
24849
|
+
local function deepEqual(left, right)
|
|
24850
|
+
if left == right then
|
|
24851
|
+
return true
|
|
24852
|
+
end
|
|
24853
|
+
if type(left) ~= 'table' or type(right) ~= 'table' then
|
|
24854
|
+
return false
|
|
24855
|
+
end
|
|
24856
|
+
for key, value in pairs(left) do
|
|
24857
|
+
if not deepEqual(value, right[key]) then
|
|
24858
|
+
return false
|
|
24859
|
+
end
|
|
24860
|
+
end
|
|
24861
|
+
for key in pairs(right) do
|
|
24862
|
+
if left[key] == nil then
|
|
24863
|
+
return false
|
|
24864
|
+
end
|
|
24865
|
+
end
|
|
24866
|
+
return true
|
|
24867
|
+
end
|
|
24868
|
+
|
|
24869
|
+
local function expect(value)
|
|
24870
|
+
local matchers = {}
|
|
24871
|
+
|
|
24872
|
+
function matchers.toBe(expected)
|
|
24873
|
+
if value ~= expected then
|
|
24874
|
+
fail('expected ' .. describeValue(expected) .. ', got ' .. describeValue(value))
|
|
24875
|
+
end
|
|
24876
|
+
end
|
|
24877
|
+
|
|
24878
|
+
function matchers.toNotBe(expected)
|
|
24879
|
+
if value == expected then
|
|
24880
|
+
fail('expected a value other than ' .. describeValue(expected))
|
|
24881
|
+
end
|
|
24882
|
+
end
|
|
24883
|
+
|
|
24884
|
+
function matchers.toEqual(expected)
|
|
24885
|
+
if not deepEqual(value, expected) then
|
|
24886
|
+
fail('expected ' .. describeValue(expected) .. ', got ' .. describeValue(value))
|
|
24887
|
+
end
|
|
24888
|
+
end
|
|
24889
|
+
|
|
24890
|
+
function matchers.toNotEqual(expected)
|
|
24891
|
+
if deepEqual(value, expected) then
|
|
24892
|
+
fail('expected a value other than ' .. describeValue(expected))
|
|
24893
|
+
end
|
|
24894
|
+
end
|
|
24895
|
+
|
|
24896
|
+
function matchers.toBeNil()
|
|
24897
|
+
if value ~= nil then
|
|
24898
|
+
fail('expected nil, got ' .. describeValue(value))
|
|
24899
|
+
end
|
|
24900
|
+
end
|
|
24901
|
+
|
|
24902
|
+
function matchers.toBeTruthy()
|
|
24903
|
+
if not value then
|
|
24904
|
+
fail('expected a truthy value, got ' .. describeValue(value))
|
|
24905
|
+
end
|
|
24906
|
+
end
|
|
24907
|
+
|
|
24908
|
+
function matchers.toBeFalsy()
|
|
24909
|
+
if value then
|
|
24910
|
+
fail('expected a falsy value, got ' .. describeValue(value))
|
|
24911
|
+
end
|
|
24912
|
+
end
|
|
24913
|
+
|
|
24914
|
+
function matchers.toContain(entry)
|
|
24915
|
+
if type(value) == 'string' then
|
|
24916
|
+
if string.find(value, tostring(entry), 1, true) == nil then
|
|
24917
|
+
fail('expected ' .. describeValue(value) .. ' to contain ' .. describeValue(entry))
|
|
24918
|
+
end
|
|
24919
|
+
return
|
|
24920
|
+
end
|
|
24921
|
+
if type(value) ~= 'table' then
|
|
24922
|
+
fail('expected a string or a table, got ' .. type(value))
|
|
24923
|
+
return
|
|
24924
|
+
end
|
|
24925
|
+
for _, candidate in pairs(value) do
|
|
24926
|
+
if deepEqual(candidate, entry) then
|
|
24927
|
+
return
|
|
24928
|
+
end
|
|
24929
|
+
end
|
|
24930
|
+
fail('expected the table to contain ' .. describeValue(entry))
|
|
24931
|
+
end
|
|
24932
|
+
|
|
24933
|
+
function matchers.toThrow(message)
|
|
24934
|
+
if type(value) ~= 'function' then
|
|
24935
|
+
fail('expected a function, got ' .. type(value))
|
|
24936
|
+
return
|
|
24937
|
+
end
|
|
24938
|
+
local ok, err = pcall(value)
|
|
24939
|
+
if ok then
|
|
24940
|
+
fail('expected the function to throw')
|
|
24941
|
+
return
|
|
24942
|
+
end
|
|
24943
|
+
if message ~= nil and string.find(tostring(err), message, 1, true) == nil then
|
|
24944
|
+
fail('expected the error to contain ' .. describeValue(message) .. ', got ' .. describeValue(tostring(err)))
|
|
24945
|
+
end
|
|
24946
|
+
end
|
|
24947
|
+
|
|
24948
|
+
return matchers
|
|
24949
|
+
end
|
|
24950
|
+
`;
|
|
24951
|
+
|
|
24952
|
+
// src/testing/harness-runner.ts
|
|
24953
|
+
var RUNNER_SOURCE = String.raw`local function fullName(name)
|
|
24954
|
+
if #groups == 0 then
|
|
24955
|
+
return name
|
|
24956
|
+
end
|
|
24957
|
+
return table.concat(groups, ' > ') .. ' > ' .. name
|
|
24958
|
+
end
|
|
24959
|
+
|
|
24960
|
+
local function snapshot(list)
|
|
24961
|
+
local copy = {}
|
|
24962
|
+
for index = 1, #list do
|
|
24963
|
+
copy[index] = list[index]
|
|
24964
|
+
end
|
|
24965
|
+
return copy
|
|
24966
|
+
end
|
|
24967
|
+
|
|
24968
|
+
local function describe(name, body)
|
|
24969
|
+
groups[#groups + 1] = name
|
|
24970
|
+
local beforeDepth = #beforeHooks
|
|
24971
|
+
local afterDepth = #afterHooks
|
|
24972
|
+
body()
|
|
24973
|
+
for index = #beforeHooks, beforeDepth + 1, -1 do
|
|
24974
|
+
beforeHooks[index] = nil
|
|
24975
|
+
end
|
|
24976
|
+
for index = #afterHooks, afterDepth + 1, -1 do
|
|
24977
|
+
afterHooks[index] = nil
|
|
24978
|
+
end
|
|
24979
|
+
groups[#groups] = nil
|
|
24980
|
+
end
|
|
24981
|
+
|
|
24982
|
+
local function test(name, body)
|
|
24983
|
+
tests[#tests + 1] = { name = fullName(name), body = body, before = snapshot(beforeHooks), after = snapshot(afterHooks) }
|
|
24984
|
+
end
|
|
24985
|
+
|
|
24986
|
+
local function beforeEach(body)
|
|
24987
|
+
beforeHooks[#beforeHooks + 1] = body
|
|
24988
|
+
end
|
|
24989
|
+
|
|
24990
|
+
local function afterEach(body)
|
|
24991
|
+
afterHooks[#afterHooks + 1] = body
|
|
24992
|
+
end
|
|
24993
|
+
|
|
24994
|
+
local function resetStubs()
|
|
24995
|
+
callLog = {}
|
|
24996
|
+
behaviour = {}
|
|
24997
|
+
end
|
|
24998
|
+
|
|
24999
|
+
local function record(name, ...)
|
|
25000
|
+
local entries = callLog[name]
|
|
25001
|
+
if entries == nil then
|
|
25002
|
+
entries = {}
|
|
25003
|
+
callLog[name] = entries
|
|
25004
|
+
end
|
|
25005
|
+
entries[#entries + 1] = { n = select('#', ...), ... }
|
|
25006
|
+
end
|
|
25007
|
+
|
|
25008
|
+
local function makeStub(name)
|
|
25009
|
+
return function(...)
|
|
25010
|
+
record(name, ...)
|
|
25011
|
+
local configured = behaviour[name]
|
|
25012
|
+
if configured == nil then
|
|
25013
|
+
return nil
|
|
25014
|
+
end
|
|
25015
|
+
if configured.kind == 'function' then
|
|
25016
|
+
return configured.body(...)
|
|
25017
|
+
end
|
|
25018
|
+
return configured.value
|
|
25019
|
+
end
|
|
25020
|
+
end
|
|
25021
|
+
|
|
25022
|
+
local mta = {}
|
|
25023
|
+
|
|
25024
|
+
function mta.stub(name, implementation)
|
|
25025
|
+
behaviour[name] = { kind = 'function', body = implementation }
|
|
25026
|
+
end
|
|
25027
|
+
|
|
25028
|
+
function mta.returns(name, value)
|
|
25029
|
+
behaviour[name] = { kind = 'value', value = value }
|
|
25030
|
+
end
|
|
25031
|
+
|
|
25032
|
+
function mta.calls(name)
|
|
25033
|
+
return callLog[name] or {}
|
|
25034
|
+
end
|
|
25035
|
+
|
|
25036
|
+
function mta.reset()
|
|
25037
|
+
resetStubs()
|
|
25038
|
+
end
|
|
25039
|
+
|
|
25040
|
+
local function runAll()
|
|
25041
|
+
local passed = 0
|
|
25042
|
+
local failed = 0
|
|
25043
|
+
for _, entry in ipairs(tests) do
|
|
25044
|
+
resetStubs()
|
|
25045
|
+
local result = nil
|
|
25046
|
+
for _, hook in ipairs(entry.before) do
|
|
25047
|
+
if result == nil then
|
|
25048
|
+
result = attempt(hook)
|
|
25049
|
+
end
|
|
25050
|
+
end
|
|
25051
|
+
if result == nil then
|
|
25052
|
+
result = attempt(entry.body)
|
|
25053
|
+
end
|
|
25054
|
+
for _, hook in ipairs(entry.after) do
|
|
25055
|
+
local hookResult = attempt(hook)
|
|
25056
|
+
if result == nil then
|
|
25057
|
+
result = hookResult
|
|
25058
|
+
end
|
|
25059
|
+
end
|
|
25060
|
+
if result == nil then
|
|
25061
|
+
passed = passed + 1
|
|
25062
|
+
emit('pass', entry.name)
|
|
25063
|
+
else
|
|
25064
|
+
failed = failed + 1
|
|
25065
|
+
emit('fail', entry.name, result.file, result.line, result.message)
|
|
25066
|
+
end
|
|
25067
|
+
end
|
|
25068
|
+
emit('done', passed, failed)
|
|
25069
|
+
return failed
|
|
25070
|
+
end
|
|
25071
|
+
|
|
25072
|
+
rawset(_G, 'describe', describe)
|
|
25073
|
+
rawset(_G, 'test', test)
|
|
25074
|
+
rawset(_G, 'beforeEach', beforeEach)
|
|
25075
|
+
rawset(_G, 'afterEach', afterEach)
|
|
25076
|
+
rawset(_G, 'expect', expect)
|
|
25077
|
+
rawset(_G, 'mta', mta)
|
|
25078
|
+
|
|
25079
|
+
setmetatable(_G, {
|
|
25080
|
+
__index = function(_, key)
|
|
25081
|
+
if stubNames[key] == nil then
|
|
25082
|
+
return nil
|
|
25083
|
+
end
|
|
25084
|
+
local stub = makeStub(key)
|
|
25085
|
+
rawset(_G, key, stub)
|
|
25086
|
+
return stub
|
|
25087
|
+
end,
|
|
25088
|
+
})
|
|
25089
|
+
|
|
25090
|
+
rawset(_G, '__luamTest', {
|
|
25091
|
+
discard = function()
|
|
25092
|
+
tests = {}
|
|
25093
|
+
groups = {}
|
|
25094
|
+
beforeHooks = {}
|
|
25095
|
+
afterHooks = {}
|
|
25096
|
+
end,
|
|
25097
|
+
run = runAll,
|
|
25098
|
+
})
|
|
25099
|
+
`;
|
|
25100
|
+
|
|
25101
|
+
// src/testing/harness-source.ts
|
|
25102
|
+
var HARNESS_FILE = "harness.lua";
|
|
25103
|
+
var ENTRY_PREFIX = "main-";
|
|
25104
|
+
var SENTINEL = "##luam:test";
|
|
25105
|
+
var HARNESS_SOURCE = `${ASSERTIONS_SOURCE}
|
|
25106
|
+
${RUNNER_SOURCE}`;
|
|
25107
|
+
function entryFile(environment) {
|
|
25108
|
+
return `${ENTRY_PREFIX}${environment}.lua`;
|
|
25109
|
+
}
|
|
25110
|
+
function stubbedGlobals(environment) {
|
|
25111
|
+
return globalsFor(environment).filter((declaration) => declaration.source === "mta" && declaration.type.kind === "function").map((declaration) => declaration.name).sort();
|
|
25112
|
+
}
|
|
25113
|
+
function entrySource(environment, preload, target) {
|
|
25114
|
+
const names = stubbedGlobals(environment).map((name) => ` ['${name}'] = true,`);
|
|
25115
|
+
const discard = preload.length === 0 ? [] : ["__luamTest.discard()"];
|
|
25116
|
+
const lines = [
|
|
25117
|
+
"__luamTestNames = {",
|
|
25118
|
+
...names,
|
|
25119
|
+
"}",
|
|
25120
|
+
`dofile('${HARNESS_FILE}')`,
|
|
25121
|
+
...preload.map((path) => `dofile('${path}')`),
|
|
25122
|
+
...discard,
|
|
25123
|
+
...target.map((path) => `dofile('${path}')`),
|
|
25124
|
+
"local failed = __luamTest.run()",
|
|
25125
|
+
"os.exit(failed == 0 and 0 or 1)"
|
|
25126
|
+
];
|
|
25127
|
+
return `${lines.join("\n")}
|
|
25128
|
+
`;
|
|
25129
|
+
}
|
|
25130
|
+
|
|
25131
|
+
// src/testing/test-runner.ts
|
|
25132
|
+
function runLua(executable, args, cwd) {
|
|
25133
|
+
const result = spawnSync3(executable, [...args], { cwd, encoding: "utf8", shell: false, windowsHide: true });
|
|
25134
|
+
return {
|
|
25135
|
+
status: result.status,
|
|
25136
|
+
stdout: result.stdout ?? "",
|
|
25137
|
+
stderr: result.stderr ?? "",
|
|
25138
|
+
failure: result.error === void 0 ? null : result.error.message
|
|
25139
|
+
};
|
|
25140
|
+
}
|
|
25141
|
+
function unescape(value) {
|
|
25142
|
+
return value.replace(/\\(.)/g, (match, character) => {
|
|
25143
|
+
if (character === "n") {
|
|
25144
|
+
return "\n";
|
|
25145
|
+
}
|
|
25146
|
+
if (character === "r") {
|
|
25147
|
+
return "\r";
|
|
25148
|
+
}
|
|
25149
|
+
return character === "t" ? " " : character;
|
|
25150
|
+
});
|
|
25151
|
+
}
|
|
25152
|
+
function parseLine(environment, line2) {
|
|
25153
|
+
const parts = line2.split(" ");
|
|
25154
|
+
if (parts[0] !== SENTINEL || parts[1] !== "pass" && parts[1] !== "fail") {
|
|
25155
|
+
return null;
|
|
25156
|
+
}
|
|
25157
|
+
const passed = parts[1] === "pass";
|
|
25158
|
+
const file = unescape(parts[3] ?? "");
|
|
25159
|
+
const parsedLine = Number(parts[4] ?? "");
|
|
25160
|
+
return {
|
|
25161
|
+
environment,
|
|
25162
|
+
name: unescape(parts[2] ?? ""),
|
|
25163
|
+
passed,
|
|
25164
|
+
file: file.length === 0 ? null : file,
|
|
25165
|
+
line: Number.isSafeInteger(parsedLine) && parsedLine > 0 ? parsedLine : null,
|
|
25166
|
+
message: unescape(parts[5] ?? "")
|
|
25167
|
+
};
|
|
25168
|
+
}
|
|
25169
|
+
function parseRunOutput(environment, stdout) {
|
|
25170
|
+
const results = [];
|
|
25171
|
+
const output = [];
|
|
25172
|
+
for (const line2 of stdout.split(/\r?\n/)) {
|
|
25173
|
+
const result = parseLine(environment, line2);
|
|
25174
|
+
if (result !== null) {
|
|
25175
|
+
results.push(result);
|
|
25176
|
+
continue;
|
|
25177
|
+
}
|
|
25178
|
+
if (line2.startsWith(SENTINEL) || line2.length === 0) {
|
|
25179
|
+
continue;
|
|
25180
|
+
}
|
|
25181
|
+
output.push(line2);
|
|
25182
|
+
}
|
|
25183
|
+
return { results, output };
|
|
25184
|
+
}
|
|
25185
|
+
function writeWorkspace(directory, scripts) {
|
|
25186
|
+
writeFileSync8(join6(directory, HARNESS_FILE), HARNESS_SOURCE, "utf8");
|
|
25187
|
+
for (const script of scripts) {
|
|
25188
|
+
const target = join6(directory, script.path);
|
|
25189
|
+
mkdirSync7(dirname7(target), { recursive: true });
|
|
25190
|
+
writeFileSync8(target, script.content, "utf8");
|
|
25191
|
+
}
|
|
25192
|
+
}
|
|
25193
|
+
function loadOrder(available, environment) {
|
|
25194
|
+
const shared = bundlePath("shared");
|
|
25195
|
+
const own = bundlePath(environment);
|
|
25196
|
+
const target = available.has(own) ? [own] : [];
|
|
25197
|
+
if (environment === "shared") {
|
|
25198
|
+
return { preload: [], target };
|
|
25199
|
+
}
|
|
25200
|
+
return { preload: available.has(shared) ? [shared] : [], target };
|
|
25201
|
+
}
|
|
25202
|
+
function runTests(request) {
|
|
25203
|
+
const spawn2 = request.spawn ?? runLua;
|
|
25204
|
+
const directory = mkdtempSync2(join6(tmpdir2(), "luam-test-"));
|
|
25205
|
+
const available = new Set(request.scripts.map((script) => script.path));
|
|
25206
|
+
try {
|
|
25207
|
+
writeWorkspace(directory, request.scripts);
|
|
25208
|
+
return ALL_ENVIRONMENTS.filter((environment) => request.environments.includes(environment)).map((environment) => {
|
|
25209
|
+
const { preload, target } = loadOrder(available, environment);
|
|
25210
|
+
const entry = entryFile(environment);
|
|
25211
|
+
writeFileSync8(join6(directory, entry), entrySource(environment, preload, target), "utf8");
|
|
25212
|
+
const execution = spawn2(request.executable, [entry], directory);
|
|
25213
|
+
const parsed = parseRunOutput(environment, execution.stdout);
|
|
25214
|
+
const failure3 = execution.failure ?? (parsed.results.length === 0 && execution.stderr.length > 0 ? execution.stderr.trim() : null);
|
|
25215
|
+
return { environment, results: parsed.results, output: parsed.output, failure: failure3 };
|
|
25216
|
+
});
|
|
25217
|
+
} finally {
|
|
25218
|
+
rmSync2(directory, { recursive: true, force: true });
|
|
25219
|
+
}
|
|
25220
|
+
}
|
|
25221
|
+
|
|
25222
|
+
// src/commands/test-command.ts
|
|
25223
|
+
function testEnvironments(build, paths) {
|
|
25224
|
+
const found = build.bundles.flatMap(
|
|
25225
|
+
(bundle) => bundle.members.flatMap((member) => member.kind === "module" && paths.has(member.module.source) ? [member.module.environment] : [])
|
|
25226
|
+
);
|
|
25227
|
+
return [...new Set(found)];
|
|
25228
|
+
}
|
|
25229
|
+
function runTestCommand(context, options = {}) {
|
|
25230
|
+
const reporter = commandReporter(context);
|
|
25231
|
+
const excluded = [context.config.outDir, context.config.contracts];
|
|
25232
|
+
const discovered = discoverTests(context.root, context.config.sources, excluded);
|
|
25233
|
+
reportCliDiagnostics(reporter, discovered.diagnostics);
|
|
25234
|
+
if (hasCliErrors(discovered.diagnostics)) {
|
|
25235
|
+
return EXIT_DIAGNOSTICS;
|
|
25236
|
+
}
|
|
25237
|
+
if (discovered.files.length === 0) {
|
|
25238
|
+
reporter.warn(`No "${TEST_EXTENSION}" files were found. Write one next to the module it covers.`);
|
|
25239
|
+
return EXIT_OK;
|
|
25240
|
+
}
|
|
25241
|
+
const request = { explicit: options.lua ?? null, env: options.env ?? null, ...options.probe === void 0 ? {} : { probe: options.probe } };
|
|
25242
|
+
const interpreter = findLuaInterpreter(request);
|
|
25243
|
+
if (interpreter === null) {
|
|
25244
|
+
reporter.error(describeMissingInterpreter(request));
|
|
25245
|
+
reporter.detail(INSTALL_HINT);
|
|
25246
|
+
return EXIT_USAGE;
|
|
25247
|
+
}
|
|
25248
|
+
const started = performance.now();
|
|
25249
|
+
const outcome = runCompile(context.root, context.config, { additionalFiles: discovered.files, development: true, layout: "bundle", map: true });
|
|
25250
|
+
reportCliDiagnostics(reporter, outcome.diagnostics);
|
|
25251
|
+
reportFileDiagnostics(reporter, outcome.fileDiagnostics, outcome.sources);
|
|
25252
|
+
if (outcome.build === null) {
|
|
25253
|
+
reporter.error("Tests failed: the project did not compile.");
|
|
25254
|
+
return EXIT_DIAGNOSTICS;
|
|
25255
|
+
}
|
|
25256
|
+
const paths = new Set(discovered.files.map((file) => file.path));
|
|
25257
|
+
const environments = testEnvironments(outcome.build, paths);
|
|
25258
|
+
const runs = runTests({
|
|
25259
|
+
executable: interpreter.executable,
|
|
25260
|
+
scripts: outcome.build.scripts,
|
|
25261
|
+
environments,
|
|
25262
|
+
...options.spawn === void 0 ? {} : { spawn: options.spawn }
|
|
25263
|
+
});
|
|
25264
|
+
return reportTestResults(reporter, runs, context.root, outcome.map, performance.now() - started) ? EXIT_OK : EXIT_DIAGNOSTICS;
|
|
25265
|
+
}
|
|
25266
|
+
|
|
25267
|
+
// src/cli/registry/test-registration.ts
|
|
25268
|
+
function registerTestCommand(program2, runtime) {
|
|
25269
|
+
const command = program2.command("test").description("Compile the project with its test files and run them on a Lua 5.1 interpreter.");
|
|
25270
|
+
addProjectOptions(command);
|
|
25271
|
+
command.option("--lua <path>", "Path to the Lua 5.1 interpreter that runs the tests.");
|
|
25272
|
+
command.action((options) => {
|
|
25273
|
+
const project = createProjectContext(runtime, "test", options);
|
|
25274
|
+
if (project.context === null) {
|
|
25275
|
+
runtime.exitCode = project.error;
|
|
25276
|
+
return;
|
|
25277
|
+
}
|
|
25278
|
+
runtime.exitCode = runTestCommand(project.context, { lua: options.lua ?? null, env: runtime.env[LUA_ENV_VARIABLE] ?? null });
|
|
25279
|
+
});
|
|
25280
|
+
}
|
|
25281
|
+
|
|
23077
25282
|
// src/commands/trace-command.ts
|
|
23078
|
-
import { existsSync as
|
|
25283
|
+
import { existsSync as existsSync13, readFileSync as readFileSync13 } from "node:fs";
|
|
23079
25284
|
function defaultMapPath(root, options, reporter) {
|
|
23080
25285
|
const loaded = loadManifest(root, { path: options.manifestPath, mode: manifestMode("trace"), env: options.env });
|
|
23081
25286
|
if (loaded.config !== null) {
|
|
23082
25287
|
const configured = resourceMapPath(root, loaded.config);
|
|
23083
|
-
if (
|
|
25288
|
+
if (existsSync13(configured)) {
|
|
23084
25289
|
return configured;
|
|
23085
25290
|
}
|
|
23086
25291
|
}
|
|
@@ -23100,7 +25305,7 @@ function readStandardInput() {
|
|
|
23100
25305
|
return "";
|
|
23101
25306
|
}
|
|
23102
25307
|
try {
|
|
23103
|
-
return
|
|
25308
|
+
return readFileSync13(0, "utf8");
|
|
23104
25309
|
} catch {
|
|
23105
25310
|
return "";
|
|
23106
25311
|
}
|
|
@@ -23134,30 +25339,30 @@ function runTraceCommand(root, reporter, options) {
|
|
|
23134
25339
|
reporter.detail('A generated line cannot be resolved there. Reproduce the error under "luam dev" or "luam ensure" for a source position.');
|
|
23135
25340
|
return EXIT_DIAGNOSTICS;
|
|
23136
25341
|
}
|
|
23137
|
-
let
|
|
25342
|
+
let failed3 = false;
|
|
23138
25343
|
for (const line2 of lines) {
|
|
23139
25344
|
const generated = parseTracePosition(line2);
|
|
23140
25345
|
if (generated === null) {
|
|
23141
25346
|
reporter.error(`Trace could not find a file and line in "${line2}".`);
|
|
23142
|
-
|
|
25347
|
+
failed3 = true;
|
|
23143
25348
|
continue;
|
|
23144
25349
|
}
|
|
23145
25350
|
const file = generatedFile(loaded.map.resource, generated.file);
|
|
23146
25351
|
const resolution = resolveResourcePosition(loaded.map, file, generated.line);
|
|
23147
25352
|
if (resolution.status === "unknown-version") {
|
|
23148
25353
|
reporter.error(`Resource map version ${resolution.version} is not supported.`);
|
|
23149
|
-
|
|
25354
|
+
failed3 = true;
|
|
23150
25355
|
continue;
|
|
23151
25356
|
}
|
|
23152
25357
|
if (resolution.status === "uncovered") {
|
|
23153
25358
|
reporter.error(`Resource map does not cover "${generated.file}:${generated.line}".`);
|
|
23154
|
-
|
|
25359
|
+
failed3 = true;
|
|
23155
25360
|
continue;
|
|
23156
25361
|
}
|
|
23157
25362
|
const symbol = resolution.position.symbol === void 0 ? "" : ` (${resolution.position.symbol})`;
|
|
23158
25363
|
reporter.raw(`${resolution.position.file}:${resolution.position.line}${symbol}`);
|
|
23159
25364
|
}
|
|
23160
|
-
return
|
|
25365
|
+
return failed3 ? EXIT_DIAGNOSTICS : EXIT_OK;
|
|
23161
25366
|
}
|
|
23162
25367
|
|
|
23163
25368
|
// src/cli/registry/trace-registration.ts
|
|
@@ -23178,12 +25383,14 @@ function registerTraceCommand(program2, runtime) {
|
|
|
23178
25383
|
var COMMAND_REGISTRARS = [
|
|
23179
25384
|
registerBuildCommand,
|
|
23180
25385
|
registerCheckCommand,
|
|
25386
|
+
registerConfigCommand,
|
|
23181
25387
|
registerDevCommand,
|
|
23182
25388
|
registerDoctorCommand,
|
|
23183
25389
|
registerEnsureCommand,
|
|
23184
25390
|
registerInitCommand,
|
|
23185
25391
|
registerServerCommand,
|
|
23186
25392
|
registerSetupCommand,
|
|
25393
|
+
registerTestCommand,
|
|
23187
25394
|
registerTraceCommand
|
|
23188
25395
|
];
|
|
23189
25396
|
|