@defold-typescript/transpiler 0.37.0 → 0.38.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.js
CHANGED
|
@@ -56,7 +56,7 @@ function resolvesToDispatchExport(callee, checker) {
|
|
|
56
56
|
}
|
|
57
57
|
return declaration.getSourceFile().fileName.includes(DISPATCH_MODULE);
|
|
58
58
|
}
|
|
59
|
-
function handlerName(property) {
|
|
59
|
+
function handlerName(property, checker) {
|
|
60
60
|
const name = property.name;
|
|
61
61
|
if (name === undefined) {
|
|
62
62
|
return;
|
|
@@ -64,8 +64,29 @@ function handlerName(property) {
|
|
|
64
64
|
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
|
|
65
65
|
return name.text;
|
|
66
66
|
}
|
|
67
|
+
if (ts.isNumericLiteral(name)) {
|
|
68
|
+
return String(Number(name.text));
|
|
69
|
+
}
|
|
70
|
+
if (ts.isComputedPropertyName(name)) {
|
|
71
|
+
const type = checker.getTypeAtLocation(name.expression);
|
|
72
|
+
if (type.isStringLiteral() || type.isNumberLiteral()) {
|
|
73
|
+
return String(type.value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
67
76
|
return;
|
|
68
77
|
}
|
|
78
|
+
function unlowerableHandler(node, text) {
|
|
79
|
+
const file = node.getSourceFile();
|
|
80
|
+
const start = node.getStart(file);
|
|
81
|
+
return {
|
|
82
|
+
file,
|
|
83
|
+
start,
|
|
84
|
+
length: node.getEnd() - start,
|
|
85
|
+
category: ts.DiagnosticCategory.Error,
|
|
86
|
+
code: 0,
|
|
87
|
+
messageText: `${DISPATCH_NAME} cannot lower \`${text}\`: pass an object literal whose keys are an identifier, a quoted or numeric literal, or a computed literal constant, each with its handler written inline.`
|
|
88
|
+
};
|
|
89
|
+
}
|
|
69
90
|
function handlerFunction(property) {
|
|
70
91
|
if (ts.isMethodDeclaration(property) && property.body !== undefined) {
|
|
71
92
|
return property;
|
|
@@ -104,8 +125,11 @@ function handlerBody(fn, context) {
|
|
|
104
125
|
var messageDispatchLoweringPlugin = {
|
|
105
126
|
visitors: {
|
|
106
127
|
[ts.SyntaxKind.CallExpression]: (node, context) => {
|
|
128
|
+
if (!resolvesToDispatchExport(node.expression, context.checker)) {
|
|
129
|
+
return context.superTransformExpression(node);
|
|
130
|
+
}
|
|
107
131
|
const [handlersArg] = node.arguments;
|
|
108
|
-
if (node.arguments.length === 1 && handlersArg !== undefined && ts.isObjectLiteralExpression(handlersArg)
|
|
132
|
+
if (node.arguments.length === 1 && handlersArg !== undefined && ts.isObjectLiteralExpression(handlersArg)) {
|
|
109
133
|
let chain;
|
|
110
134
|
const properties = [...handlersArg.properties];
|
|
111
135
|
for (let i = properties.length - 1;i >= 0; i--) {
|
|
@@ -113,9 +137,11 @@ var messageDispatchLoweringPlugin = {
|
|
|
113
137
|
if (property === undefined) {
|
|
114
138
|
continue;
|
|
115
139
|
}
|
|
116
|
-
const name = handlerName(property);
|
|
140
|
+
const name = handlerName(property, context.checker);
|
|
117
141
|
const fn = handlerFunction(property);
|
|
118
142
|
if (name === undefined || fn === undefined) {
|
|
143
|
+
const offending = property.name ?? property;
|
|
144
|
+
context.diagnostics.push(unlowerableHandler(property, offending.getText()));
|
|
119
145
|
continue;
|
|
120
146
|
}
|
|
121
147
|
const condition = createBinaryExpression(createIdentifier("message_id"), createCallExpression(createIdentifier("hash"), [createStringLiteral(name)]), SyntaxKind2.EqualityOperator);
|
|
@@ -130,6 +156,8 @@ var messageDispatchLoweringPlugin = {
|
|
|
130
156
|
];
|
|
131
157
|
return createFunctionExpression(createBlock(chain === undefined ? [] : [chain]), params, undefined, NodeFlags.Declaration, node);
|
|
132
158
|
}
|
|
159
|
+
const offending = handlersArg ?? node;
|
|
160
|
+
context.diagnostics.push(unlowerableHandler(offending, offending.getText()));
|
|
133
161
|
return context.superTransformExpression(node);
|
|
134
162
|
}
|
|
135
163
|
}
|
|
@@ -2432,20 +2460,71 @@ function componentAddressesOf(documents, paths) {
|
|
|
2432
2460
|
}
|
|
2433
2461
|
return keys;
|
|
2434
2462
|
}
|
|
2435
|
-
function
|
|
2436
|
-
|
|
2463
|
+
function bareScriptsOf(documents, paths) {
|
|
2464
|
+
const agreed = new Map;
|
|
2465
|
+
const record = (id, resource) => {
|
|
2466
|
+
if (!agreed.has(id))
|
|
2467
|
+
agreed.set(id, resource);
|
|
2468
|
+
else if (agreed.get(id) !== resource)
|
|
2469
|
+
agreed.set(id, undefined);
|
|
2470
|
+
};
|
|
2471
|
+
for (const [path, ids] of paths.componentsOf) {
|
|
2472
|
+
const resources = paths.componentResourcesOf.get(path);
|
|
2473
|
+
for (const id of ids)
|
|
2474
|
+
record(id, resources?.get(id));
|
|
2475
|
+
}
|
|
2476
|
+
for (const [displayPath, text] of documents) {
|
|
2477
|
+
let declarations;
|
|
2478
|
+
try {
|
|
2479
|
+
declarations = collectComponentDeclarations(parseSceneTextFormat(text), displayPath, []);
|
|
2480
|
+
} catch (error) {
|
|
2481
|
+
if (!(error instanceof SceneTextFormatError))
|
|
2482
|
+
throw error;
|
|
2483
|
+
continue;
|
|
2484
|
+
}
|
|
2485
|
+
for (const id of declarations.ids)
|
|
2486
|
+
record(id, declarations.resources.get(id));
|
|
2487
|
+
}
|
|
2488
|
+
return agreed;
|
|
2489
|
+
}
|
|
2490
|
+
function componentValuesOf(documents, paths, scriptModules) {
|
|
2491
|
+
const values = new Map;
|
|
2492
|
+
const moduleOf = (resource) => resource === undefined ? undefined : scriptModules.get(resource);
|
|
2493
|
+
const keys = componentAddressesOf(documents, paths);
|
|
2494
|
+
if (scriptModules.size === 0) {
|
|
2495
|
+
for (const key of keys)
|
|
2496
|
+
values.set(key, undefined);
|
|
2497
|
+
return values;
|
|
2498
|
+
}
|
|
2499
|
+
const bare = bareScriptsOf(documents, paths);
|
|
2500
|
+
for (const key of keys) {
|
|
2501
|
+
const hash = key.indexOf("#");
|
|
2502
|
+
const path = key.slice(0, hash);
|
|
2503
|
+
const id = key.slice(hash + 1);
|
|
2504
|
+
values.set(key, moduleOf(path === "" ? bare.get(id) : paths.componentResourcesOf.get(path)?.get(id)));
|
|
2505
|
+
}
|
|
2506
|
+
return values;
|
|
2507
|
+
}
|
|
2508
|
+
function bodyOf(values) {
|
|
2509
|
+
if (values.size === 0)
|
|
2437
2510
|
return "{}";
|
|
2438
|
-
const members =
|
|
2439
|
-
`
|
|
2511
|
+
const members = [...values].map(([key, specifier]) => {
|
|
2512
|
+
const value = specifier === undefined ? "true" : `typeof import(${JSON.stringify(specifier)}).default`;
|
|
2513
|
+
return ` ${JSON.stringify(key)}: ${value};
|
|
2514
|
+
`;
|
|
2515
|
+
}).sort().join("");
|
|
2440
2516
|
return `{
|
|
2441
2517
|
${members} }`;
|
|
2442
2518
|
}
|
|
2443
|
-
function
|
|
2519
|
+
function trueValues(keys) {
|
|
2520
|
+
return new Map([...keys].map((key) => [key, undefined]));
|
|
2521
|
+
}
|
|
2522
|
+
function buildSceneAddressDeclaration(documents, roles, scriptModules = new Map) {
|
|
2444
2523
|
const index = buildSceneObjectPathIndex(documents, roles);
|
|
2445
|
-
const components =
|
|
2524
|
+
const components = componentValuesOf(documents, index, scriptModules);
|
|
2446
2525
|
return `${BANNER}
|
|
2447
2526
|
declare global {
|
|
2448
|
-
interface SceneGameObjectAddresses ${bodyOf(
|
|
2527
|
+
interface SceneGameObjectAddresses ${bodyOf(trueValues(index.paths))}
|
|
2449
2528
|
|
|
2450
2529
|
interface SceneComponentAddresses ${bodyOf(components)}
|
|
2451
2530
|
}
|
|
@@ -10,5 +10,11 @@ import type { SceneCollectionRoles } from "./scene-collection-roles";
|
|
|
10
10
|
* byte-identical file, and a document map built in a different order emits the
|
|
11
11
|
* same bytes as well; a `Set` iteration order leaking into the output would
|
|
12
12
|
* make every regeneration rewrite the file and re-check the whole program.
|
|
13
|
+
*
|
|
14
|
+
* `scriptModules` maps a script resource a scene names to the import specifier
|
|
15
|
+
* of the program source that compiles to it, relative to the declaration file.
|
|
16
|
+
* A component address whose resource maps is written as that module's default
|
|
17
|
+
* export, so `msg.post` can read the script's messages off it; every other
|
|
18
|
+
* address stays `true`. Omitted, every address is `true`.
|
|
13
19
|
*/
|
|
14
|
-
export declare function buildSceneAddressDeclaration(documents: ReadonlyMap<string, string>, roles: SceneCollectionRoles): string;
|
|
20
|
+
export declare function buildSceneAddressDeclaration(documents: ReadonlyMap<string, string>, roles: SceneCollectionRoles, scriptModules?: ReadonlyMap<string, string>): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defold-typescript/transpiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.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.38.0",
|
|
39
39
|
"@typescript-to-lua/language-extensions": "1.19.0",
|
|
40
40
|
"typescript-to-lua": "^1.36.0"
|
|
41
41
|
}
|
|
@@ -47,7 +47,10 @@ type HandlerFunction = (ts.MethodDeclaration | ts.FunctionExpression | ts.ArrowF
|
|
|
47
47
|
body: ts.ConciseBody;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
function handlerName(
|
|
50
|
+
function handlerName(
|
|
51
|
+
property: ts.ObjectLiteralElementLike,
|
|
52
|
+
checker: ts.TypeChecker,
|
|
53
|
+
): string | undefined {
|
|
51
54
|
const name = property.name;
|
|
52
55
|
if (name === undefined) {
|
|
53
56
|
return undefined;
|
|
@@ -55,9 +58,31 @@ function handlerName(property: ts.ObjectLiteralElementLike): string | undefined
|
|
|
55
58
|
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
|
|
56
59
|
return name.text;
|
|
57
60
|
}
|
|
61
|
+
if (ts.isNumericLiteral(name)) {
|
|
62
|
+
return String(Number(name.text));
|
|
63
|
+
}
|
|
64
|
+
if (ts.isComputedPropertyName(name)) {
|
|
65
|
+
const type = checker.getTypeAtLocation(name.expression);
|
|
66
|
+
if (type.isStringLiteral() || type.isNumberLiteral()) {
|
|
67
|
+
return String(type.value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
58
70
|
return undefined;
|
|
59
71
|
}
|
|
60
72
|
|
|
73
|
+
function unlowerableHandler(node: ts.Node, text: string): ts.Diagnostic {
|
|
74
|
+
const file = node.getSourceFile();
|
|
75
|
+
const start = node.getStart(file);
|
|
76
|
+
return {
|
|
77
|
+
file,
|
|
78
|
+
start,
|
|
79
|
+
length: node.getEnd() - start,
|
|
80
|
+
category: ts.DiagnosticCategory.Error,
|
|
81
|
+
code: 0,
|
|
82
|
+
messageText: `${DISPATCH_NAME} cannot lower \`${text}\`: pass an object literal whose keys are an identifier, a quoted or numeric literal, or a computed literal constant, each with its handler written inline.`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
61
86
|
function handlerFunction(property: ts.ObjectLiteralElementLike): HandlerFunction | undefined {
|
|
62
87
|
if (ts.isMethodDeclaration(property) && property.body !== undefined) {
|
|
63
88
|
return property as HandlerFunction;
|
|
@@ -108,12 +133,14 @@ function handlerBody(fn: HandlerFunction, context: TransformationContext): State
|
|
|
108
133
|
export const messageDispatchLoweringPlugin: Plugin = {
|
|
109
134
|
visitors: {
|
|
110
135
|
[ts.SyntaxKind.CallExpression]: (node, context): Expression => {
|
|
136
|
+
if (!resolvesToDispatchExport(node.expression, context.checker)) {
|
|
137
|
+
return context.superTransformExpression(node);
|
|
138
|
+
}
|
|
111
139
|
const [handlersArg] = node.arguments;
|
|
112
140
|
if (
|
|
113
141
|
node.arguments.length === 1 &&
|
|
114
142
|
handlersArg !== undefined &&
|
|
115
|
-
ts.isObjectLiteralExpression(handlersArg)
|
|
116
|
-
resolvesToDispatchExport(node.expression, context.checker)
|
|
143
|
+
ts.isObjectLiteralExpression(handlersArg)
|
|
117
144
|
) {
|
|
118
145
|
let chain: IfStatement | undefined;
|
|
119
146
|
const properties = [...handlersArg.properties];
|
|
@@ -122,9 +149,11 @@ export const messageDispatchLoweringPlugin: Plugin = {
|
|
|
122
149
|
if (property === undefined) {
|
|
123
150
|
continue;
|
|
124
151
|
}
|
|
125
|
-
const name = handlerName(property);
|
|
152
|
+
const name = handlerName(property, context.checker);
|
|
126
153
|
const fn = handlerFunction(property);
|
|
127
154
|
if (name === undefined || fn === undefined) {
|
|
155
|
+
const offending = property.name ?? property;
|
|
156
|
+
context.diagnostics.push(unlowerableHandler(property, offending.getText()));
|
|
128
157
|
continue;
|
|
129
158
|
}
|
|
130
159
|
const condition = createBinaryExpression(
|
|
@@ -149,6 +178,8 @@ export const messageDispatchLoweringPlugin: Plugin = {
|
|
|
149
178
|
node,
|
|
150
179
|
);
|
|
151
180
|
}
|
|
181
|
+
const offending = handlersArg ?? node;
|
|
182
|
+
context.diagnostics.push(unlowerableHandler(offending, offending.getText()));
|
|
152
183
|
return context.superTransformExpression(node);
|
|
153
184
|
},
|
|
154
185
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SceneCollectionRoles } from "./scene-collection-roles";
|
|
2
|
-
import { buildSceneComponentIndex } from "./scene-component-index";
|
|
2
|
+
import { buildSceneComponentIndex, collectComponentDeclarations } from "./scene-component-index";
|
|
3
3
|
import { buildSceneObjectPathIndex, type SceneObjectPathIndex } from "./scene-object-path-index";
|
|
4
|
+
import { parseSceneTextFormat, SceneTextFormatError } from "./scene-text-format";
|
|
4
5
|
|
|
5
6
|
const BANNER = `// Generated from this project's scenes by \`defold-typescript scene-types\`.
|
|
6
7
|
// Do not edit: every run rewrites it from the .go/.collection sources.
|
|
@@ -28,19 +29,88 @@ function componentAddressesOf(
|
|
|
28
29
|
return keys;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
// What a component address resolves to: `true` for an address that names no
|
|
33
|
+
// script this program compiles, else the specifier of the script module it hosts.
|
|
34
|
+
type AddressValues = ReadonlyMap<string, string | undefined>;
|
|
35
|
+
|
|
36
|
+
// A bare `#id` is typed only when every declaration of that id anywhere in the
|
|
37
|
+
// project names the same mapped script. The per-object walk covers each object a
|
|
38
|
+
// world reaches; the per-document pass covers what it cannot — a factory
|
|
39
|
+
// prototype with no static path, an embedded component under the same id — so a
|
|
40
|
+
// bare address never borrows one object's script for another's component.
|
|
41
|
+
function bareScriptsOf(
|
|
42
|
+
documents: ReadonlyMap<string, string>,
|
|
43
|
+
paths: SceneObjectPathIndex,
|
|
44
|
+
): Map<string, string | undefined> {
|
|
45
|
+
const agreed = new Map<string, string | undefined>();
|
|
46
|
+
const record = (id: string, resource: string | undefined): void => {
|
|
47
|
+
if (!agreed.has(id)) agreed.set(id, resource);
|
|
48
|
+
else if (agreed.get(id) !== resource) agreed.set(id, undefined);
|
|
49
|
+
};
|
|
50
|
+
for (const [path, ids] of paths.componentsOf) {
|
|
51
|
+
const resources = paths.componentResourcesOf.get(path);
|
|
52
|
+
for (const id of ids) record(id, resources?.get(id));
|
|
53
|
+
}
|
|
54
|
+
for (const [displayPath, text] of documents) {
|
|
55
|
+
let declarations: ReturnType<typeof collectComponentDeclarations>;
|
|
56
|
+
try {
|
|
57
|
+
declarations = collectComponentDeclarations(parseSceneTextFormat(text), displayPath, []);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (!(error instanceof SceneTextFormatError)) throw error;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
for (const id of declarations.ids) record(id, declarations.resources.get(id));
|
|
63
|
+
}
|
|
64
|
+
return agreed;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function componentValuesOf(
|
|
68
|
+
documents: ReadonlyMap<string, string>,
|
|
69
|
+
paths: SceneObjectPathIndex,
|
|
70
|
+
scriptModules: ReadonlyMap<string, string>,
|
|
71
|
+
): AddressValues {
|
|
72
|
+
const values = new Map<string, string | undefined>();
|
|
73
|
+
const moduleOf = (resource: string | undefined): string | undefined =>
|
|
74
|
+
resource === undefined ? undefined : scriptModules.get(resource);
|
|
75
|
+
const keys = componentAddressesOf(documents, paths);
|
|
76
|
+
if (scriptModules.size === 0) {
|
|
77
|
+
for (const key of keys) values.set(key, undefined);
|
|
78
|
+
return values;
|
|
79
|
+
}
|
|
80
|
+
const bare = bareScriptsOf(documents, paths);
|
|
81
|
+
for (const key of keys) {
|
|
82
|
+
const hash = key.indexOf("#");
|
|
83
|
+
const path = key.slice(0, hash);
|
|
84
|
+
const id = key.slice(hash + 1);
|
|
85
|
+
values.set(
|
|
86
|
+
key,
|
|
87
|
+
moduleOf(path === "" ? bare.get(id) : paths.componentResourcesOf.get(path)?.get(id)),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return values;
|
|
91
|
+
}
|
|
92
|
+
|
|
31
93
|
// An interface with no members closes on the same line: an empty body written
|
|
32
94
|
// across two lines is what a formatter would collapse, and the generated file
|
|
33
95
|
// has to survive a consumer's formatter run byte-identical or every regeneration
|
|
34
96
|
// looks like a change.
|
|
35
|
-
function bodyOf(
|
|
36
|
-
if (
|
|
37
|
-
const members =
|
|
38
|
-
.map((key) =>
|
|
97
|
+
function bodyOf(values: AddressValues): string {
|
|
98
|
+
if (values.size === 0) return "{}";
|
|
99
|
+
const members = [...values]
|
|
100
|
+
.map(([key, specifier]) => {
|
|
101
|
+
const value =
|
|
102
|
+
specifier === undefined ? "true" : `typeof import(${JSON.stringify(specifier)}).default`;
|
|
103
|
+
return ` ${JSON.stringify(key)}: ${value};\n`;
|
|
104
|
+
})
|
|
39
105
|
.sort()
|
|
40
106
|
.join("");
|
|
41
107
|
return `{\n${members} }`;
|
|
42
108
|
}
|
|
43
109
|
|
|
110
|
+
function trueValues(keys: Iterable<string>): AddressValues {
|
|
111
|
+
return new Map([...keys].map((key) => [key, undefined]));
|
|
112
|
+
}
|
|
113
|
+
|
|
44
114
|
/**
|
|
45
115
|
* The global augmentation that fills `SceneGameObjectAddresses` and
|
|
46
116
|
* `SceneComponentAddresses` from already-read scene sources — keys are
|
|
@@ -52,19 +122,26 @@ function bodyOf(keys: readonly string[]): string {
|
|
|
52
122
|
* byte-identical file, and a document map built in a different order emits the
|
|
53
123
|
* same bytes as well; a `Set` iteration order leaking into the output would
|
|
54
124
|
* make every regeneration rewrite the file and re-check the whole program.
|
|
125
|
+
*
|
|
126
|
+
* `scriptModules` maps a script resource a scene names to the import specifier
|
|
127
|
+
* of the program source that compiles to it, relative to the declaration file.
|
|
128
|
+
* A component address whose resource maps is written as that module's default
|
|
129
|
+
* export, so `msg.post` can read the script's messages off it; every other
|
|
130
|
+
* address stays `true`. Omitted, every address is `true`.
|
|
55
131
|
*/
|
|
56
132
|
export function buildSceneAddressDeclaration(
|
|
57
133
|
documents: ReadonlyMap<string, string>,
|
|
58
134
|
roles: SceneCollectionRoles,
|
|
135
|
+
scriptModules: ReadonlyMap<string, string> = new Map(),
|
|
59
136
|
): string {
|
|
60
137
|
// One walk for both key sets: the qualified component addresses are the path
|
|
61
138
|
// universe joined to its own prototypes, not a second index over the same files.
|
|
62
139
|
const index = buildSceneObjectPathIndex(documents, roles);
|
|
63
|
-
const components =
|
|
140
|
+
const components = componentValuesOf(documents, index, scriptModules);
|
|
64
141
|
|
|
65
142
|
return `${BANNER}
|
|
66
143
|
declare global {
|
|
67
|
-
interface SceneGameObjectAddresses ${bodyOf(
|
|
144
|
+
interface SceneGameObjectAddresses ${bodyOf(trueValues(index.paths))}
|
|
68
145
|
|
|
69
146
|
interface SceneComponentAddresses ${bodyOf(components)}
|
|
70
147
|
}
|