@defold-typescript/transpiler 0.50.1 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +331 -117
- package/dist/lifecycle-erasure.d.ts +1 -0
- package/dist/physics-event-guard-lowering.d.ts +2 -0
- package/dist/transpile.d.ts +3 -0
- package/package.json +2 -2
- package/src/hsm-builtin.ts +2 -2
- package/src/index.ts +1 -0
- package/src/lifecycle-erasure.ts +233 -8
- package/src/physics-event-guard-lowering.ts +56 -0
- package/src/program-diagnostics.ts +2 -0
- package/src/session.ts +2 -0
- package/src/timers-runtime.ts +2 -2
- package/src/transpile.ts +50 -12
|
@@ -3,6 +3,7 @@ import { type Plugin } from "typescript-to-lua";
|
|
|
3
3
|
export declare const FACTORY_MODULE = "@defold-typescript/types";
|
|
4
4
|
export declare const FACTORY_NAMES: Set<string>;
|
|
5
5
|
export declare function resolvesToFactoryExport(callee: ts.Expression, checker: ts.TypeChecker): boolean;
|
|
6
|
+
export declare const RESOURCE_PROPERTY_MARKERS: ReadonlySet<string>;
|
|
6
7
|
export interface PropertyMember {
|
|
7
8
|
readonly name: string;
|
|
8
9
|
readonly initializer: ts.Expression;
|
package/dist/transpile.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export interface TranspileProjectResult {
|
|
|
22
22
|
readonly lualib?: string;
|
|
23
23
|
readonly timersRuntime?: string;
|
|
24
24
|
readonly hsmModules?: Readonly<Record<string, string>>;
|
|
25
|
+
readonly hsmSourceMaps?: Readonly<Record<string, string>>;
|
|
25
26
|
}
|
|
26
27
|
export declare const AMBIENT_FILES: Readonly<Record<string, string>>;
|
|
27
28
|
interface CollectableFile {
|
|
@@ -42,5 +43,7 @@ export declare function transpileProject(input: TranspileProjectInput): Transpil
|
|
|
42
43
|
* lualib bundle.
|
|
43
44
|
*/
|
|
44
45
|
export declare function compileHsmModules(): Readonly<Record<string, string>>;
|
|
46
|
+
/** The source map of every module `compileHsmModules` returns, under the same keys. */
|
|
47
|
+
export declare function compileHsmSourceMaps(): Readonly<Record<string, string>>;
|
|
45
48
|
export declare function transpile(source: string): TranspileResult;
|
|
46
49
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defold-typescript/transpiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.0",
|
|
4
4
|
"description": "TypeScript-to-Lua build pipeline tuned for Defold's runtime.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"test": "bun test"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@defold-typescript/types": "0.
|
|
38
|
+
"@defold-typescript/types": "0.52.0",
|
|
39
39
|
"@typescript-to-lua/language-extensions": "1.19.0",
|
|
40
40
|
"typescript": "6.0.2",
|
|
41
41
|
"typescript-to-lua": "^1.36.0"
|
package/src/hsm-builtin.ts
CHANGED
|
@@ -7,8 +7,8 @@ import { scanEmittedRequires } from "./lua-require-scan";
|
|
|
7
7
|
|
|
8
8
|
// `@defold-typescript/types/hsm` is the `index` module; every other module is a
|
|
9
9
|
// subpath of it. Each lowers to a require of `defold_typescript_hsm.<module>`,
|
|
10
|
-
// which the CLI
|
|
11
|
-
// runtime.
|
|
10
|
+
// which the CLI respells to the module's place in its runtime folder, the way it
|
|
11
|
+
// handles the timers runtime.
|
|
12
12
|
export const HSM_MODULE_SPECIFIER = "@defold-typescript/types/hsm";
|
|
13
13
|
export const HSM_REQUIRE_ROOT = "defold_typescript_hsm";
|
|
14
14
|
|
package/src/index.ts
CHANGED
|
@@ -46,6 +46,7 @@ export {
|
|
|
46
46
|
readGameProjectDependencies,
|
|
47
47
|
readLibrarySceneDocuments,
|
|
48
48
|
} from "./library-dependencies";
|
|
49
|
+
export { scanEmittedRequires } from "./lua-require-scan";
|
|
49
50
|
export { findMainEntryFactoryImports } from "./main-entry-factory-imports";
|
|
50
51
|
export type { BuildConfig, ScriptKind, SourceOutputKind } from "./output-paths";
|
|
51
52
|
export {
|
package/src/lifecycle-erasure.ts
CHANGED
|
@@ -35,22 +35,29 @@ const FACTORY_SPECIFIERS = new Set([
|
|
|
35
35
|
`${FACTORY_MODULE}/render-script`,
|
|
36
36
|
]);
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
// The lifecycle factory a callee resolves to, aliases followed, or `undefined`
|
|
39
|
+
// when it is not one of this package's factory exports.
|
|
40
|
+
function factoryExportName(callee: ts.Expression, checker: ts.TypeChecker): string | undefined {
|
|
39
41
|
let symbol = checker.getSymbolAtLocation(callee);
|
|
40
42
|
if (symbol === undefined) {
|
|
41
|
-
return
|
|
43
|
+
return undefined;
|
|
42
44
|
}
|
|
43
45
|
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
44
46
|
symbol = checker.getAliasedSymbol(symbol);
|
|
45
47
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
const name = symbol.getName();
|
|
49
|
+
if (!FACTORY_NAMES.has(name)) {
|
|
50
|
+
return undefined;
|
|
48
51
|
}
|
|
49
52
|
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
50
53
|
if (declaration === undefined) {
|
|
51
|
-
return
|
|
54
|
+
return undefined;
|
|
52
55
|
}
|
|
53
|
-
return declaration.getSourceFile().fileName.includes(FACTORY_MODULE);
|
|
56
|
+
return declaration.getSourceFile().fileName.includes(FACTORY_MODULE) ? name : undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function resolvesToFactoryExport(callee: ts.Expression, checker: ts.TypeChecker): boolean {
|
|
60
|
+
return factoryExportName(callee, checker) !== undefined;
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
function isThisParameter(param: ts.ParameterDeclaration): boolean {
|
|
@@ -208,6 +215,20 @@ function emitInitMerge(
|
|
|
208
215
|
return [builderDecl, createAssignmentStatement(createIdentifier("init"), initFn, property)];
|
|
209
216
|
}
|
|
210
217
|
|
|
218
|
+
// Build-time markers, never engine functions: bob reads `resource.<kind>("path")`
|
|
219
|
+
// only as a `go.property` default (`LuaScanner.java:923-933` at defold
|
|
220
|
+
// `a81dbb04`), and the engine's `resource` table registers none of them
|
|
221
|
+
// (`script_resource.cpp:3447-3463` in 1.14.0). Called anywhere else, each is nil.
|
|
222
|
+
export const RESOURCE_PROPERTY_MARKERS: ReadonlySet<string> = new Set([
|
|
223
|
+
"resource.atlas",
|
|
224
|
+
"resource.buffer",
|
|
225
|
+
"resource.font",
|
|
226
|
+
"resource.material",
|
|
227
|
+
"resource.render_target",
|
|
228
|
+
"resource.texture",
|
|
229
|
+
"resource.tile_source",
|
|
230
|
+
]);
|
|
231
|
+
|
|
211
232
|
export interface PropertyMember {
|
|
212
233
|
readonly name: string;
|
|
213
234
|
readonly initializer: ts.Expression;
|
|
@@ -275,14 +296,171 @@ function emitPropertyRegistrations(
|
|
|
275
296
|
);
|
|
276
297
|
}
|
|
277
298
|
|
|
299
|
+
// Defold builds `go.property` only in `.script` files, so a GUI or render
|
|
300
|
+
// script's `properties` block has nothing it could lower to.
|
|
301
|
+
function scriptOnlyProperties(
|
|
302
|
+
property: ts.ObjectLiteralElementLike,
|
|
303
|
+
factory: string,
|
|
304
|
+
): ts.Diagnostic {
|
|
305
|
+
const file = property.getSourceFile();
|
|
306
|
+
const start = property.getStart(file);
|
|
307
|
+
return {
|
|
308
|
+
file,
|
|
309
|
+
start,
|
|
310
|
+
length: property.getEnd() - start,
|
|
311
|
+
category: ts.DiagnosticCategory.Error,
|
|
312
|
+
code: 0,
|
|
313
|
+
messageText: `\`${factory}\` cannot declare \`properties\`: Defold builds \`go.property\` only in \`.script\` files. Declare the property in a \`defineScript\` and send its value with \`msg.post\`.`,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// The outermost expression that only wraps `node` in parentheses or a type-only
|
|
318
|
+
// form, so `(resource.atlas("/a.atlas") as Hash)` sits where the bare call would.
|
|
319
|
+
function outermostWrapper(node: ts.Expression): ts.Expression {
|
|
320
|
+
let current = node;
|
|
321
|
+
while (
|
|
322
|
+
ts.isParenthesizedExpression(current.parent) ||
|
|
323
|
+
ts.isAsExpression(current.parent) ||
|
|
324
|
+
ts.isSatisfiesExpression(current.parent) ||
|
|
325
|
+
ts.isNonNullExpression(current.parent) ||
|
|
326
|
+
ts.isTypeAssertionExpression(current.parent)
|
|
327
|
+
) {
|
|
328
|
+
current = current.parent;
|
|
329
|
+
}
|
|
330
|
+
return current;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// The inverse of `outermostWrapper`: the expression a run of parentheses and
|
|
334
|
+
// type-only forms wraps, which TSTL emits in place of the whole run.
|
|
335
|
+
function innermostExpression(node: ts.Expression): ts.Expression {
|
|
336
|
+
let current = node;
|
|
337
|
+
while (
|
|
338
|
+
ts.isParenthesizedExpression(current) ||
|
|
339
|
+
ts.isAsExpression(current) ||
|
|
340
|
+
ts.isSatisfiesExpression(current) ||
|
|
341
|
+
ts.isNonNullExpression(current) ||
|
|
342
|
+
ts.isTypeAssertionExpression(current)
|
|
343
|
+
) {
|
|
344
|
+
current = current.expression;
|
|
345
|
+
}
|
|
346
|
+
return current;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Keyed by the declaration's fully qualified name: only a symbol declared in an
|
|
350
|
+
// ambient `declare global` block carries the `global.` prefix, so a project's
|
|
351
|
+
// own `resource` never matches.
|
|
352
|
+
function globalName(symbol: ts.Symbol | undefined, checker: ts.TypeChecker): string | undefined {
|
|
353
|
+
if (symbol === undefined) {
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
const fqn = checker.getFullyQualifiedName(symbol);
|
|
357
|
+
return fqn.startsWith("global.") ? fqn.slice("global.".length) : undefined;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function calledGlobal(call: ts.CallExpression, checker: ts.TypeChecker): string | undefined {
|
|
361
|
+
const declaration = checker.getResolvedSignature(call)?.declaration;
|
|
362
|
+
if (
|
|
363
|
+
declaration === undefined ||
|
|
364
|
+
!ts.isFunctionDeclaration(declaration) ||
|
|
365
|
+
declaration.name === undefined
|
|
366
|
+
) {
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
return globalName(checker.getSymbolAtLocation(declaration.name), checker);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// A `properties` default of a factory call, or the default argument of a
|
|
373
|
+
// direct `go.property` call: the two places bob reads a marker.
|
|
374
|
+
function isPropertyDefault(call: ts.CallExpression, checker: ts.TypeChecker): boolean {
|
|
375
|
+
const value = outermostWrapper(call);
|
|
376
|
+
const parent = value.parent;
|
|
377
|
+
if (ts.isCallExpression(parent)) {
|
|
378
|
+
return parent.arguments[1] === value && calledGlobal(parent, checker) === "go.property";
|
|
379
|
+
}
|
|
380
|
+
const properties = parent?.parent?.parent;
|
|
381
|
+
if (
|
|
382
|
+
properties === undefined ||
|
|
383
|
+
!ts.isPropertyAssignment(properties) ||
|
|
384
|
+
!isPropertiesHook(properties)
|
|
385
|
+
) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
const hooks = properties.parent;
|
|
389
|
+
return (
|
|
390
|
+
ts.isObjectLiteralExpression(hooks) &&
|
|
391
|
+
factoryHooks(hooks.parent as ts.Expression, checker) === hooks &&
|
|
392
|
+
propertyMembers(properties).some((member) => member.initializer === value)
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function misplacedMarker(node: ts.Expression, marker: string): ts.Diagnostic {
|
|
397
|
+
const file = node.getSourceFile();
|
|
398
|
+
const start = node.getStart(file);
|
|
399
|
+
return {
|
|
400
|
+
file,
|
|
401
|
+
start,
|
|
402
|
+
length: node.getEnd() - start,
|
|
403
|
+
category: ts.DiagnosticCategory.Error,
|
|
404
|
+
code: 0,
|
|
405
|
+
messageText: `\`${marker}\` is a build-time marker: Defold's build reads it only in a \`go.property\` default, and the engine binds no such function. Use it as a \`defineScript\` \`properties\` default.`,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Bob reads a default as a marker only when the emitted text is
|
|
410
|
+
// `resource.<kind>("<path>")` or `resource.<kind>()`: the object token must be
|
|
411
|
+
// `resource` with a dot `NAME` key, and the first argument a string literal or
|
|
412
|
+
// absent (`LuaScanner.java:801-822`, `:923-933` and `:493-510` at defold
|
|
413
|
+
// `a81dbb04`). The receiver test is textual like bob's; the marker resolution
|
|
414
|
+
// has already excluded a project's own `resource`.
|
|
415
|
+
function readsAsMarker(call: ts.CallExpression): boolean {
|
|
416
|
+
const callee = innermostExpression(call.expression);
|
|
417
|
+
if (ts.isOptionalChain(call) || ts.isOptionalChain(callee)) {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
if (
|
|
421
|
+
!ts.isPropertyAccessExpression(callee) &&
|
|
422
|
+
!(
|
|
423
|
+
ts.isElementAccessExpression(callee) &&
|
|
424
|
+
ts.isStringLiteralLike(innermostExpression(callee.argumentExpression))
|
|
425
|
+
)
|
|
426
|
+
) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
const receiver = innermostExpression(callee.expression);
|
|
430
|
+
if (!ts.isIdentifier(receiver) || receiver.text !== "resource") {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
const [path] = call.arguments;
|
|
434
|
+
return path === undefined || ts.isStringLiteralLike(innermostExpression(path));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function unreadableMarker(node: ts.CallExpression, marker: string): ts.Diagnostic {
|
|
438
|
+
const file = node.getSourceFile();
|
|
439
|
+
const start = node.getStart(file);
|
|
440
|
+
return {
|
|
441
|
+
file,
|
|
442
|
+
start,
|
|
443
|
+
length: node.getEnd() - start,
|
|
444
|
+
category: ts.DiagnosticCategory.Error,
|
|
445
|
+
code: 0,
|
|
446
|
+
messageText: `\`${marker}\` in a property default must be written \`${marker}("<path>")\`, with a string literal path: Defold's build reads no other form.`,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function isCallee(node: ts.Expression): boolean {
|
|
451
|
+
const callee = outermostWrapper(node);
|
|
452
|
+
return ts.isCallExpression(callee.parent) && callee.parent.expression === callee;
|
|
453
|
+
}
|
|
454
|
+
|
|
278
455
|
function eraseFactoryCall(
|
|
279
456
|
expression: ts.Expression,
|
|
280
457
|
context: TransformationContext,
|
|
281
458
|
): Statement[] | undefined {
|
|
282
459
|
const hooks = factoryHooks(expression, context.checker);
|
|
283
|
-
if (hooks === undefined) {
|
|
460
|
+
if (hooks === undefined || !ts.isCallExpression(expression)) {
|
|
284
461
|
return undefined;
|
|
285
462
|
}
|
|
463
|
+
const factory = factoryExportName(expression.expression, context.checker);
|
|
286
464
|
const registrations: Statement[] = [];
|
|
287
465
|
const statements: Statement[] = [];
|
|
288
466
|
for (const property of hooks.properties) {
|
|
@@ -291,7 +469,11 @@ function eraseFactoryCall(
|
|
|
291
469
|
continue;
|
|
292
470
|
}
|
|
293
471
|
if (name === "properties") {
|
|
294
|
-
|
|
472
|
+
if (factory === "defineScript") {
|
|
473
|
+
registrations.push(...emitPropertyRegistrations(property, context));
|
|
474
|
+
} else if (factory !== undefined && propertyMembers(property).length > 0) {
|
|
475
|
+
context.diagnostics.push(scriptOnlyProperties(property, factory));
|
|
476
|
+
}
|
|
295
477
|
continue;
|
|
296
478
|
}
|
|
297
479
|
const fn = hookFunction(property);
|
|
@@ -374,5 +556,48 @@ export const lifecycleErasurePlugin: Plugin = {
|
|
|
374
556
|
},
|
|
375
557
|
[ts.SyntaxKind.ImportDeclaration]: (node, context) =>
|
|
376
558
|
isFactoryOnlyImport(node) ? [] : context.superTransformStatements(node),
|
|
559
|
+
// TSTL's optional-chain lowering also visits nodes it synthesizes, which have
|
|
560
|
+
// no parent and no source span; only parsed nodes are checked.
|
|
561
|
+
[ts.SyntaxKind.CallExpression]: (node, context) => {
|
|
562
|
+
const marker = node.parent === undefined ? undefined : calledGlobal(node, context.checker);
|
|
563
|
+
if (marker !== undefined && RESOURCE_PROPERTY_MARKERS.has(marker)) {
|
|
564
|
+
if (!isPropertyDefault(node, context.checker)) {
|
|
565
|
+
context.diagnostics.push(misplacedMarker(node, marker));
|
|
566
|
+
} else if (!readsAsMarker(node)) {
|
|
567
|
+
context.diagnostics.push(unreadableMarker(node, marker));
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return context.superTransformExpression(node);
|
|
571
|
+
},
|
|
572
|
+
[ts.SyntaxKind.PropertyAccessExpression]: (node, context) => {
|
|
573
|
+
if (node.parent !== undefined && !isCallee(node)) {
|
|
574
|
+
const marker = globalName(context.checker.getSymbolAtLocation(node.name), context.checker);
|
|
575
|
+
if (marker !== undefined && RESOURCE_PROPERTY_MARKERS.has(marker)) {
|
|
576
|
+
context.diagnostics.push(misplacedMarker(node, marker));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return context.superTransformExpression(node);
|
|
580
|
+
},
|
|
581
|
+
[ts.SyntaxKind.ElementAccessExpression]: (node, context) => {
|
|
582
|
+
if (node.parent !== undefined && !isCallee(node)) {
|
|
583
|
+
// The checker resolves a string key only when it is the direct argument,
|
|
584
|
+
// so a wrapped key is looked up by name on the receiver's type, as the
|
|
585
|
+
// checker does for a direct one.
|
|
586
|
+
const key = innermostExpression(node.argumentExpression);
|
|
587
|
+
const marker = ts.isStringLiteralLike(key)
|
|
588
|
+
? globalName(
|
|
589
|
+
context.checker.getPropertyOfType(
|
|
590
|
+
context.checker.getTypeAtLocation(node.expression),
|
|
591
|
+
key.text,
|
|
592
|
+
),
|
|
593
|
+
context.checker,
|
|
594
|
+
)
|
|
595
|
+
: undefined;
|
|
596
|
+
if (marker !== undefined && RESOURCE_PROPERTY_MARKERS.has(marker)) {
|
|
597
|
+
context.diagnostics.push(misplacedMarker(node, marker));
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return context.superTransformExpression(node);
|
|
601
|
+
},
|
|
377
602
|
},
|
|
378
603
|
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import {
|
|
3
|
+
createBinaryExpression,
|
|
4
|
+
createCallExpression,
|
|
5
|
+
createIdentifier,
|
|
6
|
+
createStringLiteral,
|
|
7
|
+
createTableIndexExpression,
|
|
8
|
+
type Expression,
|
|
9
|
+
type Plugin,
|
|
10
|
+
SyntaxKind,
|
|
11
|
+
} from "typescript-to-lua";
|
|
12
|
+
|
|
13
|
+
const GUARD_MODULE = "@defold-typescript/types";
|
|
14
|
+
const GUARD_NAME = "isPhysicsEvent";
|
|
15
|
+
|
|
16
|
+
function resolvesToGuardExport(callee: ts.Expression, checker: ts.TypeChecker): boolean {
|
|
17
|
+
let symbol = checker.getSymbolAtLocation(callee);
|
|
18
|
+
if (symbol === undefined) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
22
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
23
|
+
}
|
|
24
|
+
if (symbol.getName() !== GUARD_NAME) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
28
|
+
if (declaration === undefined) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return declaration.getSourceFile().fileName.includes(GUARD_MODULE);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const physicsEventGuardLoweringPlugin: Plugin = {
|
|
35
|
+
visitors: {
|
|
36
|
+
[ts.SyntaxKind.CallExpression]: (node, context): Expression => {
|
|
37
|
+
const [eventArg, nameArg] = node.arguments;
|
|
38
|
+
if (
|
|
39
|
+
node.arguments.length === 2 &&
|
|
40
|
+
eventArg !== undefined &&
|
|
41
|
+
nameArg !== undefined &&
|
|
42
|
+
resolvesToGuardExport(node.expression, context.checker)
|
|
43
|
+
) {
|
|
44
|
+
const event = context.transformExpression(eventArg);
|
|
45
|
+
const name = context.transformExpression(nameArg);
|
|
46
|
+
return createBinaryExpression(
|
|
47
|
+
createTableIndexExpression(event, createStringLiteral("type")),
|
|
48
|
+
createCallExpression(createIdentifier("hash"), [name]),
|
|
49
|
+
SyntaxKind.EqualityOperator,
|
|
50
|
+
node,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return context.superTransformExpression(node);
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -3,6 +3,7 @@ import * as tstl from "typescript-to-lua";
|
|
|
3
3
|
import { lifecycleErasurePlugin } from "./lifecycle-erasure";
|
|
4
4
|
import { messageDispatchLoweringPlugin } from "./message-dispatch-lowering";
|
|
5
5
|
import { messageGuardLoweringPlugin } from "./message-guard-lowering";
|
|
6
|
+
import { physicsEventGuardLoweringPlugin } from "./physics-event-guard-lowering";
|
|
6
7
|
import { timersLoweringPlugin } from "./timers-lowering";
|
|
7
8
|
import { typeApplicationErasurePlugin } from "./type-application-erasure";
|
|
8
9
|
import { windowEventGuardLoweringPlugin } from "./window-event-guard-lowering";
|
|
@@ -13,6 +14,7 @@ const LUA_PLUGINS: tstl.Plugin[] = [
|
|
|
13
14
|
lifecycleErasurePlugin,
|
|
14
15
|
messageGuardLoweringPlugin,
|
|
15
16
|
windowEventGuardLoweringPlugin,
|
|
17
|
+
physicsEventGuardLoweringPlugin,
|
|
16
18
|
messageDispatchLoweringPlugin,
|
|
17
19
|
timersLoweringPlugin,
|
|
18
20
|
typeApplicationErasurePlugin,
|
package/src/session.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { hsmLoweringPlugin } from "./hsm-builtin";
|
|
|
9
9
|
import { lifecycleErasurePlugin } from "./lifecycle-erasure";
|
|
10
10
|
import { messageDispatchLoweringPlugin } from "./message-dispatch-lowering";
|
|
11
11
|
import { messageGuardLoweringPlugin } from "./message-guard-lowering";
|
|
12
|
+
import { physicsEventGuardLoweringPlugin } from "./physics-event-guard-lowering";
|
|
12
13
|
import { timersLoweringPlugin } from "./timers-lowering";
|
|
13
14
|
import { AMBIENT_FILES, collectOutputs, type TranspileProjectResult } from "./transpile";
|
|
14
15
|
import { typeApplicationErasurePlugin } from "./type-application-erasure";
|
|
@@ -50,6 +51,7 @@ function compilerOptions(companionEmit: CompanionEmitter): tstl.CompilerOptions
|
|
|
50
51
|
{ plugin: editorScriptErasurePlugin },
|
|
51
52
|
{ plugin: messageGuardLoweringPlugin },
|
|
52
53
|
{ plugin: windowEventGuardLoweringPlugin },
|
|
54
|
+
{ plugin: physicsEventGuardLoweringPlugin },
|
|
53
55
|
{ plugin: messageDispatchLoweringPlugin },
|
|
54
56
|
{ plugin: timersLoweringPlugin },
|
|
55
57
|
{ plugin: hsmLoweringPlugin },
|
package/src/timers-runtime.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The importable polyfill specifier (matched by the type checker and the
|
|
2
2
|
// lowering pass) and the flat Lua require name the import is rewritten to. The
|
|
3
|
-
// CLI writes the runtime
|
|
4
|
-
//
|
|
3
|
+
// CLI writes the runtime into its runtime folder beside `lualib_bundle` and
|
|
4
|
+
// respells every `require("defold_typescript_timers")` to that path.
|
|
5
5
|
export const TIMERS_MODULE_SPECIFIER = "@defold-typescript/types/timers";
|
|
6
6
|
export const TIMERS_REQUIRE_NAME = "defold_typescript_timers";
|
|
7
7
|
|
package/src/transpile.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { lifecycleErasurePlugin } from "./lifecycle-erasure";
|
|
|
21
21
|
import { scanEmittedRequires } from "./lua-require-scan";
|
|
22
22
|
import { messageDispatchLoweringPlugin } from "./message-dispatch-lowering";
|
|
23
23
|
import { messageGuardLoweringPlugin } from "./message-guard-lowering";
|
|
24
|
+
import { physicsEventGuardLoweringPlugin } from "./physics-event-guard-lowering";
|
|
24
25
|
import { importsTimersModule, timersLoweringPlugin } from "./timers-lowering";
|
|
25
26
|
import { TIMERS_RUNTIME } from "./timers-runtime";
|
|
26
27
|
import { typeApplicationErasurePlugin } from "./type-application-erasure";
|
|
@@ -62,23 +63,27 @@ export interface TranspileProjectResult {
|
|
|
62
63
|
// TSTL synthesizes this bundle (no user source) whenever a lualib feature is
|
|
63
64
|
// registered — either by a user source using one (`Object.keys`, spread,
|
|
64
65
|
// `__TS__TypeOf`, ...) or by the timers import lowering, whose generated
|
|
65
|
-
// runtime requires the bundle. The emitted `require("lualib_bundle")`
|
|
66
|
-
//
|
|
66
|
+
// runtime requires the bundle. The emitted `require("lualib_bundle")` is an
|
|
67
|
+
// internal name the CLI respells to where it writes the bundle. Absent when
|
|
67
68
|
// neither trigger fires.
|
|
68
69
|
readonly lualib?: string;
|
|
69
70
|
// Hand-authored runtime Lua for the `@defold-typescript/types/timers`
|
|
70
71
|
// polyfills (`setTimeout`/`wait`/...). Unlike `defineScript`, the import is
|
|
71
|
-
// not erased — it lowers to `require("defold_typescript_timers")`,
|
|
72
|
-
//
|
|
73
|
-
// the module (pay-for-use).
|
|
72
|
+
// not erased — it lowers to `require("defold_typescript_timers")`, an
|
|
73
|
+
// internal name the CLI respells to where it writes this. Present only when
|
|
74
|
+
// a user file imports the module (pay-for-use).
|
|
74
75
|
readonly timersRuntime?: string;
|
|
75
76
|
// The compiled `hsm` modules the program requires, keyed by module name
|
|
76
77
|
// (`index`, `defold`, ...). An hsm import lowers to
|
|
77
|
-
// `require("defold_typescript_hsm.<module>")`,
|
|
78
|
-
//
|
|
79
|
-
// (pay-for-use)
|
|
80
|
-
// at is not in the user's project.
|
|
78
|
+
// `require("defold_typescript_hsm.<module>")`, an internal name the CLI
|
|
79
|
+
// respells to where it writes each one. Present only when some chunk requires one
|
|
80
|
+
// (pay-for-use).
|
|
81
81
|
readonly hsmModules?: Readonly<Record<string, string>>;
|
|
82
|
+
// The source map of each module in `hsmModules`, under the same keys. Its
|
|
83
|
+
// `sources` names `<module>.ts` with an empty `sourceRoot`: only the CLI knows
|
|
84
|
+
// where the module is written and where the hsm source sits, so it sets the
|
|
85
|
+
// root that joins them.
|
|
86
|
+
readonly hsmSourceMaps?: Readonly<Record<string, string>>;
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
function flattenDiagnosticMessage(
|
|
@@ -312,6 +317,7 @@ export function collectOutputs(
|
|
|
312
317
|
}
|
|
313
318
|
|
|
314
319
|
const hsm = hsmClosure([...Object.values(lua), ...Object.values(companions)], compileHsmModules);
|
|
320
|
+
const hsmSourceMaps = hsm === undefined ? undefined : selectHsmSourceMaps(Object.keys(hsm));
|
|
315
321
|
|
|
316
322
|
return {
|
|
317
323
|
lua,
|
|
@@ -321,9 +327,22 @@ export function collectOutputs(
|
|
|
321
327
|
...(lualib !== undefined ? { lualib } : {}),
|
|
322
328
|
...(timersImported ? { timersRuntime: TIMERS_RUNTIME } : {}),
|
|
323
329
|
...(hsm !== undefined ? { hsmModules: hsm } : {}),
|
|
330
|
+
...(hsmSourceMaps !== undefined ? { hsmSourceMaps } : {}),
|
|
324
331
|
};
|
|
325
332
|
}
|
|
326
333
|
|
|
334
|
+
function selectHsmSourceMaps(names: readonly string[]): Record<string, string> {
|
|
335
|
+
const maps = compileHsmSourceMaps();
|
|
336
|
+
const selected: Record<string, string> = {};
|
|
337
|
+
for (const name of names) {
|
|
338
|
+
const map = maps[name];
|
|
339
|
+
if (map !== undefined) {
|
|
340
|
+
selected[name] = map;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return selected;
|
|
344
|
+
}
|
|
345
|
+
|
|
327
346
|
function transpileVirtual(
|
|
328
347
|
files: Readonly<Record<string, string>>,
|
|
329
348
|
companionEmit: ReturnType<typeof createCompanionEmitPlugin>,
|
|
@@ -356,6 +375,7 @@ function transpileVirtual(
|
|
|
356
375
|
{ plugin: editorScriptErasurePlugin },
|
|
357
376
|
{ plugin: messageGuardLoweringPlugin },
|
|
358
377
|
{ plugin: windowEventGuardLoweringPlugin },
|
|
378
|
+
{ plugin: physicsEventGuardLoweringPlugin },
|
|
359
379
|
{ plugin: messageDispatchLoweringPlugin },
|
|
360
380
|
{ plugin: timersLoweringPlugin },
|
|
361
381
|
{ plugin: hsmLoweringPlugin },
|
|
@@ -380,7 +400,12 @@ export function transpileProject(input: TranspileProjectInput): TranspileProject
|
|
|
380
400
|
);
|
|
381
401
|
}
|
|
382
402
|
|
|
383
|
-
|
|
403
|
+
interface CompiledHsm {
|
|
404
|
+
readonly lua: Readonly<Record<string, string>>;
|
|
405
|
+
readonly sourceMaps: Readonly<Record<string, string>>;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
let compiledHsm: CompiledHsm | undefined;
|
|
384
409
|
|
|
385
410
|
function requiresLualib(lua: string): boolean {
|
|
386
411
|
return scanEmittedRequires(lua).some(({ path: requirePath }) => requirePath === "lualib_bundle");
|
|
@@ -396,6 +421,15 @@ function requiresLualib(lua: string): boolean {
|
|
|
396
421
|
* lualib bundle.
|
|
397
422
|
*/
|
|
398
423
|
export function compileHsmModules(): Readonly<Record<string, string>> {
|
|
424
|
+
return compileHsm().lua;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** The source map of every module `compileHsmModules` returns, under the same keys. */
|
|
428
|
+
export function compileHsmSourceMaps(): Readonly<Record<string, string>> {
|
|
429
|
+
return compileHsm().sourceMaps;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function compileHsm(): CompiledHsm {
|
|
399
433
|
if (compiledHsm !== undefined) {
|
|
400
434
|
return compiledHsm;
|
|
401
435
|
}
|
|
@@ -415,6 +449,7 @@ export function compileHsmModules(): Readonly<Record<string, string>> {
|
|
|
415
449
|
(d) => `${d.file?.fileName ?? "(project)"}: ${flattenDiagnosticMessage(d.messageText)}`,
|
|
416
450
|
);
|
|
417
451
|
const lua: Record<string, string> = {};
|
|
452
|
+
const sourceMaps: Record<string, string> = {};
|
|
418
453
|
for (const file of result.transpiledFiles) {
|
|
419
454
|
if (isLualibBundle(file)) {
|
|
420
455
|
continue;
|
|
@@ -428,14 +463,17 @@ export function compileHsmModules(): Readonly<Record<string, string>> {
|
|
|
428
463
|
problems.push(`the compiled ${name} module requires lualib_bundle`);
|
|
429
464
|
}
|
|
430
465
|
lua[name] = file.lua;
|
|
466
|
+
if (typeof file.luaSourceMap === "string") {
|
|
467
|
+
sourceMaps[name] = file.luaSourceMap;
|
|
468
|
+
}
|
|
431
469
|
}
|
|
432
470
|
if (problems.length > 0) {
|
|
433
471
|
throw new Error(
|
|
434
472
|
`@defold-typescript/transpiler: the hsm library does not compile:\n ${problems.join("\n ")}`,
|
|
435
473
|
);
|
|
436
474
|
}
|
|
437
|
-
compiledHsm = lua;
|
|
438
|
-
return
|
|
475
|
+
compiledHsm = { lua, sourceMaps };
|
|
476
|
+
return compiledHsm;
|
|
439
477
|
}
|
|
440
478
|
|
|
441
479
|
export function transpile(source: string): TranspileResult {
|