@defold-typescript/transpiler 0.5.2 → 0.5.4
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 +234 -49
- package/dist/message-dispatch-lowering.d.ts +4 -0
- package/dist/message-guard-lowering.d.ts +2 -0
- package/package.json +2 -2
- package/src/lifecycle-erasure.ts +32 -1
- package/src/message-dispatch-lowering.ts +155 -0
- package/src/message-guard-lowering.ts +54 -0
- package/src/session.ts +10 -1
- package/src/transpile.ts +15 -1
package/dist/index.js
CHANGED
|
@@ -2,28 +2,143 @@
|
|
|
2
2
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
3
3
|
import { createRequire as createRequire2 } from "node:module";
|
|
4
4
|
import * as path2 from "node:path";
|
|
5
|
-
import * as
|
|
5
|
+
import * as ts4 from "typescript";
|
|
6
6
|
import * as tstl2 from "typescript-to-lua";
|
|
7
7
|
|
|
8
8
|
// src/lifecycle-erasure.ts
|
|
9
|
-
import * as
|
|
9
|
+
import * as ts2 from "typescript";
|
|
10
10
|
import {
|
|
11
11
|
createAssignmentStatement,
|
|
12
|
+
createBinaryExpression as createBinaryExpression2,
|
|
13
|
+
createBlock as createBlock2,
|
|
14
|
+
createCallExpression as createCallExpression2,
|
|
15
|
+
createExpressionStatement,
|
|
16
|
+
createForInStatement,
|
|
17
|
+
createFunctionExpression as createFunctionExpression2,
|
|
18
|
+
createIdentifier as createIdentifier2,
|
|
19
|
+
createIfStatement as createIfStatement2,
|
|
20
|
+
createNilLiteral,
|
|
21
|
+
createReturnStatement,
|
|
22
|
+
createTableIndexExpression,
|
|
23
|
+
createVariableDeclarationStatement as createVariableDeclarationStatement2,
|
|
24
|
+
NodeFlags as NodeFlags2,
|
|
25
|
+
SyntaxKind as SyntaxKind4
|
|
26
|
+
} from "typescript-to-lua";
|
|
27
|
+
|
|
28
|
+
// src/message-dispatch-lowering.ts
|
|
29
|
+
import * as ts from "typescript";
|
|
30
|
+
import {
|
|
12
31
|
createBinaryExpression,
|
|
13
32
|
createBlock,
|
|
14
33
|
createCallExpression,
|
|
15
|
-
createExpressionStatement,
|
|
16
|
-
createForInStatement,
|
|
17
34
|
createFunctionExpression,
|
|
18
35
|
createIdentifier,
|
|
19
36
|
createIfStatement,
|
|
20
|
-
|
|
21
|
-
createReturnStatement,
|
|
22
|
-
createTableIndexExpression,
|
|
37
|
+
createStringLiteral,
|
|
23
38
|
createVariableDeclarationStatement,
|
|
24
39
|
NodeFlags,
|
|
25
40
|
SyntaxKind as SyntaxKind2
|
|
26
41
|
} from "typescript-to-lua";
|
|
42
|
+
var DISPATCH_MODULE = "@defold-typescript/types";
|
|
43
|
+
var DISPATCH_NAME = "onMessage";
|
|
44
|
+
var HANDLER_PARAM_TARGETS = ["self", "message", "sender"];
|
|
45
|
+
function resolvesToDispatchExport(callee, checker) {
|
|
46
|
+
let symbol = checker.getSymbolAtLocation(callee);
|
|
47
|
+
if (symbol === undefined) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
51
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
52
|
+
}
|
|
53
|
+
if (symbol.getName() !== DISPATCH_NAME) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
57
|
+
if (declaration === undefined) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return declaration.getSourceFile().fileName.includes(DISPATCH_MODULE);
|
|
61
|
+
}
|
|
62
|
+
function handlerName(property) {
|
|
63
|
+
const name = property.name;
|
|
64
|
+
if (name === undefined) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
|
|
68
|
+
return name.text;
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
function handlerFunction(property) {
|
|
73
|
+
if (ts.isMethodDeclaration(property) && property.body !== undefined) {
|
|
74
|
+
return property;
|
|
75
|
+
}
|
|
76
|
+
if (ts.isPropertyAssignment(property)) {
|
|
77
|
+
const initializer = property.initializer;
|
|
78
|
+
if (ts.isFunctionExpression(initializer) || ts.isArrowFunction(initializer)) {
|
|
79
|
+
return initializer;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
function isThisParameter(param) {
|
|
85
|
+
return ts.isIdentifier(param.name) && ts.identifierToKeywordKind(param.name) === ts.SyntaxKind.ThisKeyword;
|
|
86
|
+
}
|
|
87
|
+
function aliasStatements(fn) {
|
|
88
|
+
const params = fn.parameters.filter((param) => !isThisParameter(param));
|
|
89
|
+
const aliases = [];
|
|
90
|
+
params.forEach((param, index) => {
|
|
91
|
+
const engineName = HANDLER_PARAM_TARGETS[index];
|
|
92
|
+
if (engineName === undefined || !ts.isIdentifier(param.name)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (param.name.text !== engineName) {
|
|
96
|
+
aliases.push(createVariableDeclarationStatement(createIdentifier(param.name.text), createIdentifier(engineName)));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
return aliases;
|
|
100
|
+
}
|
|
101
|
+
function handlerBody(fn, context) {
|
|
102
|
+
if (ts.isBlock(fn.body)) {
|
|
103
|
+
return context.transformStatements(fn.body.statements);
|
|
104
|
+
}
|
|
105
|
+
return context.transformStatements([ts.factory.createExpressionStatement(fn.body)]);
|
|
106
|
+
}
|
|
107
|
+
var messageDispatchLoweringPlugin = {
|
|
108
|
+
visitors: {
|
|
109
|
+
[ts.SyntaxKind.CallExpression]: (node, context) => {
|
|
110
|
+
const [handlersArg] = node.arguments;
|
|
111
|
+
if (node.arguments.length === 1 && handlersArg !== undefined && ts.isObjectLiteralExpression(handlersArg) && resolvesToDispatchExport(node.expression, context.checker)) {
|
|
112
|
+
let chain;
|
|
113
|
+
const properties = [...handlersArg.properties];
|
|
114
|
+
for (let i = properties.length - 1;i >= 0; i--) {
|
|
115
|
+
const property = properties[i];
|
|
116
|
+
if (property === undefined) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const name = handlerName(property);
|
|
120
|
+
const fn = handlerFunction(property);
|
|
121
|
+
if (name === undefined || fn === undefined) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const condition = createBinaryExpression(createIdentifier("message_id"), createCallExpression(createIdentifier("hash"), [createStringLiteral(name)]), SyntaxKind2.EqualityOperator);
|
|
125
|
+
const body = [...aliasStatements(fn), ...handlerBody(fn, context)];
|
|
126
|
+
chain = createIfStatement(condition, createBlock(body), chain);
|
|
127
|
+
}
|
|
128
|
+
const params = [
|
|
129
|
+
createIdentifier("self"),
|
|
130
|
+
createIdentifier("message_id"),
|
|
131
|
+
createIdentifier("message"),
|
|
132
|
+
createIdentifier("sender")
|
|
133
|
+
];
|
|
134
|
+
return createFunctionExpression(createBlock(chain === undefined ? [] : [chain]), params, undefined, NodeFlags.Declaration, node);
|
|
135
|
+
}
|
|
136
|
+
return context.superTransformExpression(node);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// src/lifecycle-erasure.ts
|
|
27
142
|
var FACTORY_MODULE = "@defold-typescript/types";
|
|
28
143
|
var FACTORY_NAMES = new Set(["defineScript", "defineGuiScript", "defineRenderScript"]);
|
|
29
144
|
function resolvesToFactoryExport(callee, checker) {
|
|
@@ -31,7 +146,7 @@ function resolvesToFactoryExport(callee, checker) {
|
|
|
31
146
|
if (symbol === undefined) {
|
|
32
147
|
return false;
|
|
33
148
|
}
|
|
34
|
-
if (symbol.flags &
|
|
149
|
+
if (symbol.flags & ts2.SymbolFlags.Alias) {
|
|
35
150
|
symbol = checker.getAliasedSymbol(symbol);
|
|
36
151
|
}
|
|
37
152
|
if (!FACTORY_NAMES.has(symbol.getName())) {
|
|
@@ -43,42 +158,52 @@ function resolvesToFactoryExport(callee, checker) {
|
|
|
43
158
|
}
|
|
44
159
|
return declaration.getSourceFile().fileName.includes(FACTORY_MODULE);
|
|
45
160
|
}
|
|
46
|
-
function
|
|
47
|
-
return
|
|
161
|
+
function isThisParameter2(param) {
|
|
162
|
+
return ts2.isIdentifier(param.name) && ts2.identifierToKeywordKind(param.name) === ts2.SyntaxKind.ThisKeyword;
|
|
48
163
|
}
|
|
49
164
|
function hookName(property) {
|
|
50
165
|
const name = property.name;
|
|
51
166
|
if (name === undefined) {
|
|
52
167
|
return;
|
|
53
168
|
}
|
|
54
|
-
if (
|
|
169
|
+
if (ts2.isIdentifier(name) || ts2.isStringLiteral(name)) {
|
|
55
170
|
return name.text;
|
|
56
171
|
}
|
|
57
172
|
return;
|
|
58
173
|
}
|
|
59
174
|
function hookFunction(property) {
|
|
60
|
-
if (
|
|
175
|
+
if (ts2.isMethodDeclaration(property) && property.body !== undefined) {
|
|
61
176
|
return property;
|
|
62
177
|
}
|
|
63
|
-
if (
|
|
178
|
+
if (ts2.isPropertyAssignment(property)) {
|
|
64
179
|
const initializer = property.initializer;
|
|
65
|
-
if (
|
|
180
|
+
if (ts2.isFunctionExpression(initializer) || ts2.isArrowFunction(initializer)) {
|
|
66
181
|
return initializer;
|
|
67
182
|
}
|
|
68
183
|
}
|
|
69
184
|
return;
|
|
70
185
|
}
|
|
186
|
+
function dispatcherInitializer(property, checker) {
|
|
187
|
+
if (!ts2.isPropertyAssignment(property)) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const initializer = property.initializer;
|
|
191
|
+
if (!ts2.isCallExpression(initializer)) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
return resolvesToDispatchExport(initializer.expression, checker) ? initializer : undefined;
|
|
195
|
+
}
|
|
71
196
|
function transformHookBody(fn, context) {
|
|
72
|
-
if (
|
|
197
|
+
if (ts2.isBlock(fn.body)) {
|
|
73
198
|
return context.transformStatements(fn.body.statements);
|
|
74
199
|
}
|
|
75
200
|
return [createExpressionStatement(context.transformExpression(fn.body))];
|
|
76
201
|
}
|
|
77
202
|
function crossesFunctionBoundary(node) {
|
|
78
|
-
return
|
|
203
|
+
return ts2.isFunctionDeclaration(node) || ts2.isFunctionExpression(node) || ts2.isArrowFunction(node) || ts2.isMethodDeclaration(node) || ts2.isGetAccessorDeclaration(node) || ts2.isSetAccessorDeclaration(node);
|
|
79
204
|
}
|
|
80
205
|
function initReturnsValue(fn) {
|
|
81
|
-
if (!
|
|
206
|
+
if (!ts2.isBlock(fn.body)) {
|
|
82
207
|
return true;
|
|
83
208
|
}
|
|
84
209
|
let found = false;
|
|
@@ -86,61 +211,68 @@ function initReturnsValue(fn) {
|
|
|
86
211
|
if (found || crossesFunctionBoundary(node)) {
|
|
87
212
|
return;
|
|
88
213
|
}
|
|
89
|
-
if (
|
|
214
|
+
if (ts2.isReturnStatement(node) && node.expression !== undefined) {
|
|
90
215
|
found = true;
|
|
91
216
|
return;
|
|
92
217
|
}
|
|
93
|
-
|
|
218
|
+
ts2.forEachChild(node, visit);
|
|
94
219
|
};
|
|
95
|
-
|
|
220
|
+
ts2.forEachChild(fn.body, visit);
|
|
96
221
|
return found;
|
|
97
222
|
}
|
|
98
223
|
function initBuilderBody(fn, context) {
|
|
99
|
-
if (
|
|
224
|
+
if (ts2.isBlock(fn.body)) {
|
|
100
225
|
return context.transformStatements(fn.body.statements);
|
|
101
226
|
}
|
|
102
227
|
return [createReturnStatement([context.transformExpression(fn.body)])];
|
|
103
228
|
}
|
|
104
229
|
function emitInitMerge(fn, context, property) {
|
|
105
|
-
const builder =
|
|
106
|
-
const builderDecl =
|
|
107
|
-
const stateDecl =
|
|
108
|
-
const mergeAssignment = createAssignmentStatement(createTableIndexExpression(
|
|
109
|
-
const forIn = createForInStatement(
|
|
110
|
-
const guard =
|
|
111
|
-
const initFn =
|
|
112
|
-
return [builderDecl, createAssignmentStatement(
|
|
230
|
+
const builder = createFunctionExpression2(createBlock2(initBuilderBody(fn, context)), [], undefined, NodeFlags2.Declaration, fn);
|
|
231
|
+
const builderDecl = createVariableDeclarationStatement2(createIdentifier2("____init"), builder);
|
|
232
|
+
const stateDecl = createVariableDeclarationStatement2(createIdentifier2("____s"), createCallExpression2(createIdentifier2("____init"), []));
|
|
233
|
+
const mergeAssignment = createAssignmentStatement(createTableIndexExpression(createIdentifier2("self"), createIdentifier2("____k")), createIdentifier2("____v"));
|
|
234
|
+
const forIn = createForInStatement(createBlock2([mergeAssignment]), [createIdentifier2("____k"), createIdentifier2("____v")], [createCallExpression2(createIdentifier2("pairs"), [createIdentifier2("____s")])]);
|
|
235
|
+
const guard = createIfStatement2(createBinaryExpression2(createIdentifier2("____s"), createNilLiteral(), SyntaxKind4.InequalityOperator), createBlock2([forIn]));
|
|
236
|
+
const initFn = createFunctionExpression2(createBlock2([stateDecl, guard]), [createIdentifier2("self")], undefined, NodeFlags2.Declaration, fn);
|
|
237
|
+
return [builderDecl, createAssignmentStatement(createIdentifier2("init"), initFn, property)];
|
|
113
238
|
}
|
|
114
239
|
function eraseFactoryCall(expression, context) {
|
|
115
|
-
if (!
|
|
240
|
+
if (!ts2.isCallExpression(expression)) {
|
|
116
241
|
return;
|
|
117
242
|
}
|
|
118
243
|
if (!resolvesToFactoryExport(expression.expression, context.checker)) {
|
|
119
244
|
return;
|
|
120
245
|
}
|
|
121
246
|
const hooks = expression.arguments[0];
|
|
122
|
-
if (hooks === undefined || !
|
|
247
|
+
if (hooks === undefined || !ts2.isObjectLiteralExpression(hooks)) {
|
|
123
248
|
return;
|
|
124
249
|
}
|
|
125
250
|
const statements = [];
|
|
126
251
|
for (const property of hooks.properties) {
|
|
127
252
|
const name = hookName(property);
|
|
253
|
+
if (name === undefined) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
128
256
|
const fn = hookFunction(property);
|
|
129
|
-
if (
|
|
257
|
+
if (fn === undefined) {
|
|
258
|
+
const dispatch = dispatcherInitializer(property, context.checker);
|
|
259
|
+
if (dispatch !== undefined) {
|
|
260
|
+
statements.push(createAssignmentStatement(createIdentifier2(name), context.transformExpression(dispatch), property));
|
|
261
|
+
}
|
|
130
262
|
continue;
|
|
131
263
|
}
|
|
132
264
|
if (name === "init" && initReturnsValue(fn)) {
|
|
133
265
|
statements.push(...emitInitMerge(fn, context, property));
|
|
134
266
|
continue;
|
|
135
267
|
}
|
|
136
|
-
const params = fn.parameters.filter((param) => !
|
|
137
|
-
const fnExpression =
|
|
138
|
-
statements.push(createAssignmentStatement(
|
|
268
|
+
const params = fn.parameters.filter((param) => !isThisParameter2(param) && ts2.isIdentifier(param.name)).map((param) => context.transformExpression(param.name));
|
|
269
|
+
const fnExpression = createFunctionExpression2(createBlock2(transformHookBody(fn, context)), params, undefined, NodeFlags2.Declaration, fn);
|
|
270
|
+
statements.push(createAssignmentStatement(createIdentifier2(name), fnExpression, property));
|
|
139
271
|
}
|
|
140
272
|
return statements;
|
|
141
273
|
}
|
|
142
274
|
function isFactoryOnlyImport(node) {
|
|
143
|
-
if (!
|
|
275
|
+
if (!ts2.isStringLiteral(node.moduleSpecifier) || node.moduleSpecifier.text !== FACTORY_MODULE) {
|
|
144
276
|
return false;
|
|
145
277
|
}
|
|
146
278
|
const clause = node.importClause;
|
|
@@ -148,15 +280,15 @@ function isFactoryOnlyImport(node) {
|
|
|
148
280
|
return false;
|
|
149
281
|
}
|
|
150
282
|
const bindings = clause.namedBindings;
|
|
151
|
-
if (bindings === undefined || !
|
|
283
|
+
if (bindings === undefined || !ts2.isNamedImports(bindings)) {
|
|
152
284
|
return false;
|
|
153
285
|
}
|
|
154
286
|
return bindings.elements.every((element) => FACTORY_NAMES.has((element.propertyName ?? element.name).text));
|
|
155
287
|
}
|
|
156
288
|
var lifecycleErasurePlugin = {
|
|
157
289
|
visitors: {
|
|
158
|
-
[
|
|
159
|
-
[
|
|
290
|
+
[ts2.SyntaxKind.ExpressionStatement]: (node, context) => eraseFactoryCall(node.expression, context) ?? context.superTransformStatements(node),
|
|
291
|
+
[ts2.SyntaxKind.ExportAssignment]: (node, context) => {
|
|
160
292
|
if (!node.isExportEquals) {
|
|
161
293
|
const erased = eraseFactoryCall(node.expression, context);
|
|
162
294
|
if (erased !== undefined) {
|
|
@@ -165,7 +297,48 @@ var lifecycleErasurePlugin = {
|
|
|
165
297
|
}
|
|
166
298
|
return context.superTransformStatements(node);
|
|
167
299
|
},
|
|
168
|
-
[
|
|
300
|
+
[ts2.SyntaxKind.ImportDeclaration]: (node, context) => isFactoryOnlyImport(node) ? [] : context.superTransformStatements(node)
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// src/message-guard-lowering.ts
|
|
305
|
+
import * as ts3 from "typescript";
|
|
306
|
+
import {
|
|
307
|
+
createBinaryExpression as createBinaryExpression3,
|
|
308
|
+
createCallExpression as createCallExpression3,
|
|
309
|
+
createIdentifier as createIdentifier3,
|
|
310
|
+
SyntaxKind as SyntaxKind6
|
|
311
|
+
} from "typescript-to-lua";
|
|
312
|
+
var GUARD_MODULE = "@defold-typescript/types";
|
|
313
|
+
var GUARD_NAME = "isMessage";
|
|
314
|
+
function resolvesToGuardExport(callee, checker) {
|
|
315
|
+
let symbol = checker.getSymbolAtLocation(callee);
|
|
316
|
+
if (symbol === undefined) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
if (symbol.flags & ts3.SymbolFlags.Alias) {
|
|
320
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
321
|
+
}
|
|
322
|
+
if (symbol.getName() !== GUARD_NAME) {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
326
|
+
if (declaration === undefined) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
return declaration.getSourceFile().fileName.includes(GUARD_MODULE);
|
|
330
|
+
}
|
|
331
|
+
var messageGuardLoweringPlugin = {
|
|
332
|
+
visitors: {
|
|
333
|
+
[ts3.SyntaxKind.CallExpression]: (node, context) => {
|
|
334
|
+
const [messageIdArg, , expectedArg] = node.arguments;
|
|
335
|
+
if (node.arguments.length === 3 && messageIdArg !== undefined && expectedArg !== undefined && resolvesToGuardExport(node.expression, context.checker)) {
|
|
336
|
+
const messageId = context.transformExpression(messageIdArg);
|
|
337
|
+
const expected = context.transformExpression(expectedArg);
|
|
338
|
+
return createBinaryExpression3(messageId, createCallExpression3(createIdentifier3("hash"), [expected]), SyntaxKind6.EqualityOperator, node);
|
|
339
|
+
}
|
|
340
|
+
return context.superTransformExpression(node);
|
|
341
|
+
}
|
|
169
342
|
}
|
|
170
343
|
};
|
|
171
344
|
|
|
@@ -191,6 +364,8 @@ function buildAmbientFiles() {
|
|
|
191
364
|
"node_modules/@typescript-to-lua/language-extensions/index.d.ts": readFileSync(path.join(TSTL_LANG_EXT_ROOT, "index.d.ts"), "utf8"),
|
|
192
365
|
"node_modules/@defold-typescript/types/src/core-types.ts": readAmbient("src/core-types.ts"),
|
|
193
366
|
"node_modules/@defold-typescript/types/src/msg-overloads.d.ts": readAmbient("src/msg-overloads.d.ts"),
|
|
367
|
+
"node_modules/@defold-typescript/types/src/message-guard.d.ts": readAmbient("src/message-guard.d.ts"),
|
|
368
|
+
"node_modules/@defold-typescript/types/src/message-dispatch.d.ts": readAmbient("src/message-dispatch.d.ts"),
|
|
194
369
|
"node_modules/@defold-typescript/types/src/lifecycle.ts": readAmbient("src/lifecycle.ts"),
|
|
195
370
|
"node_modules/@defold-typescript/types/index.ts": [
|
|
196
371
|
'export { defineGuiScript, defineRenderScript, defineScript } from "./src/lifecycle";',
|
|
@@ -241,7 +416,12 @@ function transpileProject(input) {
|
|
|
241
416
|
luaTarget: tstl.LuaTarget.Lua54,
|
|
242
417
|
sourceMap: true,
|
|
243
418
|
skipLibCheck: true,
|
|
244
|
-
|
|
419
|
+
noImplicitSelf: true,
|
|
420
|
+
luaPlugins: [
|
|
421
|
+
{ plugin: lifecycleErasurePlugin },
|
|
422
|
+
{ plugin: messageGuardLoweringPlugin },
|
|
423
|
+
{ plugin: messageDispatchLoweringPlugin }
|
|
424
|
+
]
|
|
245
425
|
});
|
|
246
426
|
return collectOutputs(result.transpiledFiles, result.diagnostics, userKeys);
|
|
247
427
|
}
|
|
@@ -260,7 +440,12 @@ var COMPILER_OPTIONS = {
|
|
|
260
440
|
luaTarget: tstl2.LuaTarget.Lua54,
|
|
261
441
|
sourceMap: true,
|
|
262
442
|
skipLibCheck: true,
|
|
263
|
-
|
|
443
|
+
noImplicitSelf: true,
|
|
444
|
+
luaPlugins: [
|
|
445
|
+
{ plugin: lifecycleErasurePlugin },
|
|
446
|
+
{ plugin: messageGuardLoweringPlugin },
|
|
447
|
+
{ plugin: messageDispatchLoweringPlugin }
|
|
448
|
+
]
|
|
264
449
|
};
|
|
265
450
|
function normalizeSlashes(p) {
|
|
266
451
|
return p.replace(/\\/g, "/");
|
|
@@ -300,7 +485,7 @@ function createTranspileSession() {
|
|
|
300
485
|
if (cached) {
|
|
301
486
|
return cached;
|
|
302
487
|
}
|
|
303
|
-
const created =
|
|
488
|
+
const created = ts4.createSourceFile(fileName, content, ts4.ScriptTarget.Latest, false);
|
|
304
489
|
sourceFileCache.set(normalized, created);
|
|
305
490
|
return created;
|
|
306
491
|
}
|
|
@@ -319,7 +504,7 @@ function createTranspileSession() {
|
|
|
319
504
|
return cached;
|
|
320
505
|
}
|
|
321
506
|
const fileContent = readFileSync2(filePath, "utf8");
|
|
322
|
-
const created =
|
|
507
|
+
const created = ts4.createSourceFile(filePath, fileContent, ts4.ScriptTarget.Latest, false);
|
|
323
508
|
libCache.set(fileName, created);
|
|
324
509
|
return created;
|
|
325
510
|
}
|
|
@@ -332,10 +517,10 @@ function createTranspileSession() {
|
|
|
332
517
|
return AMBIENT_FILES[normalized];
|
|
333
518
|
}
|
|
334
519
|
const host = {
|
|
335
|
-
fileExists: (fileName) => mergedContent(normalizeSlashes(fileName)) !== undefined ||
|
|
520
|
+
fileExists: (fileName) => mergedContent(normalizeSlashes(fileName)) !== undefined || ts4.sys.fileExists(fileName),
|
|
336
521
|
getCanonicalFileName: (fileName) => fileName,
|
|
337
522
|
getCurrentDirectory: () => "",
|
|
338
|
-
getDefaultLibFileName:
|
|
523
|
+
getDefaultLibFileName: ts4.getDefaultLibFileName,
|
|
339
524
|
readFile: () => "",
|
|
340
525
|
getNewLine: () => `
|
|
341
526
|
`,
|
|
@@ -356,15 +541,15 @@ function createTranspileSession() {
|
|
|
356
541
|
}
|
|
357
542
|
const userKeys = new Set(userFiles.keys());
|
|
358
543
|
const rootNames = [...Object.keys(AMBIENT_FILES).map(normalizeSlashes), ...userKeys];
|
|
359
|
-
program =
|
|
360
|
-
const preEmitDiagnostics =
|
|
544
|
+
program = ts4.createProgram(rootNames, COMPILER_OPTIONS, host, program);
|
|
545
|
+
const preEmitDiagnostics = ts4.getPreEmitDiagnostics(program);
|
|
361
546
|
const collector = createOutputCollector();
|
|
362
547
|
const { diagnostics: transpileDiagnostics } = new tstl2.Transpiler().emit({
|
|
363
548
|
program,
|
|
364
549
|
writeFile: collector.writeFile
|
|
365
550
|
});
|
|
366
551
|
const diagnostics = [
|
|
367
|
-
...
|
|
552
|
+
...ts4.sortAndDeduplicateDiagnostics([...preEmitDiagnostics, ...transpileDiagnostics])
|
|
368
553
|
];
|
|
369
554
|
return collectOutputs(collector.files, diagnostics, userKeys);
|
|
370
555
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defold-typescript/transpiler",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "TypeScript-to-Lua build pipeline tuned for Defold's runtime.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"test": "bun test"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@defold-typescript/types": "0.5.
|
|
31
|
+
"@defold-typescript/types": "0.5.4",
|
|
32
32
|
"@typescript-to-lua/language-extensions": "1.19.0",
|
|
33
33
|
"typescript-to-lua": "^1.36.0"
|
|
34
34
|
}
|
package/src/lifecycle-erasure.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
SyntaxKind,
|
|
21
21
|
type TransformationContext,
|
|
22
22
|
} from "typescript-to-lua";
|
|
23
|
+
import { resolvesToDispatchExport } from "./message-dispatch-lowering";
|
|
23
24
|
|
|
24
25
|
const FACTORY_MODULE = "@defold-typescript/types";
|
|
25
26
|
const FACTORY_NAMES = new Set(["defineScript", "defineGuiScript", "defineRenderScript"]);
|
|
@@ -77,6 +78,20 @@ function hookFunction(property: ts.ObjectLiteralElementLike): HookFunction | und
|
|
|
77
78
|
return undefined;
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
function dispatcherInitializer(
|
|
82
|
+
property: ts.ObjectLiteralElementLike,
|
|
83
|
+
checker: ts.TypeChecker,
|
|
84
|
+
): ts.CallExpression | undefined {
|
|
85
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const initializer = property.initializer;
|
|
89
|
+
if (!ts.isCallExpression(initializer)) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
return resolvesToDispatchExport(initializer.expression, checker) ? initializer : undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
80
95
|
function transformHookBody(fn: HookFunction, context: TransformationContext): Statement[] {
|
|
81
96
|
if (ts.isBlock(fn.body)) {
|
|
82
97
|
return context.transformStatements(fn.body.statements);
|
|
@@ -187,8 +202,24 @@ function eraseFactoryCall(
|
|
|
187
202
|
const statements: Statement[] = [];
|
|
188
203
|
for (const property of hooks.properties) {
|
|
189
204
|
const name = hookName(property);
|
|
205
|
+
if (name === undefined) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
190
208
|
const fn = hookFunction(property);
|
|
191
|
-
if (
|
|
209
|
+
if (fn === undefined) {
|
|
210
|
+
// A hook whose initializer is a recognized `onMessage({...})` call: let it
|
|
211
|
+
// route through the message-dispatch lowering, which yields the flat
|
|
212
|
+
// `function(self, message_id, message, sender)` chunk, then bind it.
|
|
213
|
+
const dispatch = dispatcherInitializer(property, context.checker);
|
|
214
|
+
if (dispatch !== undefined) {
|
|
215
|
+
statements.push(
|
|
216
|
+
createAssignmentStatement(
|
|
217
|
+
createIdentifier(name),
|
|
218
|
+
context.transformExpression(dispatch),
|
|
219
|
+
property,
|
|
220
|
+
),
|
|
221
|
+
);
|
|
222
|
+
}
|
|
192
223
|
continue;
|
|
193
224
|
}
|
|
194
225
|
if (name === "init" && initReturnsValue(fn)) {
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import {
|
|
3
|
+
createBinaryExpression,
|
|
4
|
+
createBlock,
|
|
5
|
+
createCallExpression,
|
|
6
|
+
createFunctionExpression,
|
|
7
|
+
createIdentifier,
|
|
8
|
+
createIfStatement,
|
|
9
|
+
createStringLiteral,
|
|
10
|
+
createVariableDeclarationStatement,
|
|
11
|
+
type Expression,
|
|
12
|
+
type Identifier,
|
|
13
|
+
type IfStatement,
|
|
14
|
+
NodeFlags,
|
|
15
|
+
type Plugin,
|
|
16
|
+
type Statement,
|
|
17
|
+
SyntaxKind,
|
|
18
|
+
type TransformationContext,
|
|
19
|
+
} from "typescript-to-lua";
|
|
20
|
+
|
|
21
|
+
const DISPATCH_MODULE = "@defold-typescript/types";
|
|
22
|
+
const DISPATCH_NAME = "onMessage";
|
|
23
|
+
|
|
24
|
+
// The engine calls `on_message(self, message_id, message, sender)`; a handler's
|
|
25
|
+
// own params (in order) alias onto these three value-bearing names.
|
|
26
|
+
const HANDLER_PARAM_TARGETS = ["self", "message", "sender"] as const;
|
|
27
|
+
|
|
28
|
+
export function resolvesToDispatchExport(callee: ts.Expression, checker: ts.TypeChecker): boolean {
|
|
29
|
+
let symbol = checker.getSymbolAtLocation(callee);
|
|
30
|
+
if (symbol === undefined) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
34
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
35
|
+
}
|
|
36
|
+
if (symbol.getName() !== DISPATCH_NAME) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
40
|
+
if (declaration === undefined) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return declaration.getSourceFile().fileName.includes(DISPATCH_MODULE);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type HandlerFunction = (ts.MethodDeclaration | ts.FunctionExpression | ts.ArrowFunction) & {
|
|
47
|
+
body: ts.ConciseBody;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function handlerName(property: ts.ObjectLiteralElementLike): string | undefined {
|
|
51
|
+
const name = property.name;
|
|
52
|
+
if (name === undefined) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
|
|
56
|
+
return name.text;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function handlerFunction(property: ts.ObjectLiteralElementLike): HandlerFunction | undefined {
|
|
62
|
+
if (ts.isMethodDeclaration(property) && property.body !== undefined) {
|
|
63
|
+
return property as HandlerFunction;
|
|
64
|
+
}
|
|
65
|
+
if (ts.isPropertyAssignment(property)) {
|
|
66
|
+
const initializer = property.initializer;
|
|
67
|
+
if (ts.isFunctionExpression(initializer) || ts.isArrowFunction(initializer)) {
|
|
68
|
+
return initializer as HandlerFunction;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isThisParameter(param: ts.ParameterDeclaration): boolean {
|
|
75
|
+
return (
|
|
76
|
+
ts.isIdentifier(param.name) &&
|
|
77
|
+
ts.identifierToKeywordKind(param.name) === ts.SyntaxKind.ThisKeyword
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function aliasStatements(fn: HandlerFunction): Statement[] {
|
|
82
|
+
const params = fn.parameters.filter((param) => !isThisParameter(param));
|
|
83
|
+
const aliases: Statement[] = [];
|
|
84
|
+
params.forEach((param, index) => {
|
|
85
|
+
const engineName = HANDLER_PARAM_TARGETS[index];
|
|
86
|
+
if (engineName === undefined || !ts.isIdentifier(param.name)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (param.name.text !== engineName) {
|
|
90
|
+
aliases.push(
|
|
91
|
+
createVariableDeclarationStatement(
|
|
92
|
+
createIdentifier(param.name.text),
|
|
93
|
+
createIdentifier(engineName),
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return aliases;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function handlerBody(fn: HandlerFunction, context: TransformationContext): Statement[] {
|
|
102
|
+
if (ts.isBlock(fn.body)) {
|
|
103
|
+
return context.transformStatements(fn.body.statements);
|
|
104
|
+
}
|
|
105
|
+
return context.transformStatements([ts.factory.createExpressionStatement(fn.body)]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const messageDispatchLoweringPlugin: Plugin = {
|
|
109
|
+
visitors: {
|
|
110
|
+
[ts.SyntaxKind.CallExpression]: (node, context): Expression => {
|
|
111
|
+
const [handlersArg] = node.arguments;
|
|
112
|
+
if (
|
|
113
|
+
node.arguments.length === 1 &&
|
|
114
|
+
handlersArg !== undefined &&
|
|
115
|
+
ts.isObjectLiteralExpression(handlersArg) &&
|
|
116
|
+
resolvesToDispatchExport(node.expression, context.checker)
|
|
117
|
+
) {
|
|
118
|
+
let chain: IfStatement | undefined;
|
|
119
|
+
const properties = [...handlersArg.properties];
|
|
120
|
+
for (let i = properties.length - 1; i >= 0; i--) {
|
|
121
|
+
const property = properties[i];
|
|
122
|
+
if (property === undefined) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const name = handlerName(property);
|
|
126
|
+
const fn = handlerFunction(property);
|
|
127
|
+
if (name === undefined || fn === undefined) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const condition = createBinaryExpression(
|
|
131
|
+
createIdentifier("message_id"),
|
|
132
|
+
createCallExpression(createIdentifier("hash"), [createStringLiteral(name)]),
|
|
133
|
+
SyntaxKind.EqualityOperator,
|
|
134
|
+
);
|
|
135
|
+
const body = [...aliasStatements(fn), ...handlerBody(fn, context)];
|
|
136
|
+
chain = createIfStatement(condition, createBlock(body), chain);
|
|
137
|
+
}
|
|
138
|
+
const params: Identifier[] = [
|
|
139
|
+
createIdentifier("self"),
|
|
140
|
+
createIdentifier("message_id"),
|
|
141
|
+
createIdentifier("message"),
|
|
142
|
+
createIdentifier("sender"),
|
|
143
|
+
];
|
|
144
|
+
return createFunctionExpression(
|
|
145
|
+
createBlock(chain === undefined ? [] : [chain]),
|
|
146
|
+
params,
|
|
147
|
+
undefined,
|
|
148
|
+
NodeFlags.Declaration,
|
|
149
|
+
node,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return context.superTransformExpression(node);
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import {
|
|
3
|
+
createBinaryExpression,
|
|
4
|
+
createCallExpression,
|
|
5
|
+
createIdentifier,
|
|
6
|
+
type Expression,
|
|
7
|
+
type Plugin,
|
|
8
|
+
SyntaxKind,
|
|
9
|
+
} from "typescript-to-lua";
|
|
10
|
+
|
|
11
|
+
const GUARD_MODULE = "@defold-typescript/types";
|
|
12
|
+
const GUARD_NAME = "isMessage";
|
|
13
|
+
|
|
14
|
+
function resolvesToGuardExport(callee: ts.Expression, checker: ts.TypeChecker): boolean {
|
|
15
|
+
let symbol = checker.getSymbolAtLocation(callee);
|
|
16
|
+
if (symbol === undefined) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
20
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
21
|
+
}
|
|
22
|
+
if (symbol.getName() !== GUARD_NAME) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
26
|
+
if (declaration === undefined) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return declaration.getSourceFile().fileName.includes(GUARD_MODULE);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const messageGuardLoweringPlugin: Plugin = {
|
|
33
|
+
visitors: {
|
|
34
|
+
[ts.SyntaxKind.CallExpression]: (node, context): Expression => {
|
|
35
|
+
const [messageIdArg, , expectedArg] = node.arguments;
|
|
36
|
+
if (
|
|
37
|
+
node.arguments.length === 3 &&
|
|
38
|
+
messageIdArg !== undefined &&
|
|
39
|
+
expectedArg !== undefined &&
|
|
40
|
+
resolvesToGuardExport(node.expression, context.checker)
|
|
41
|
+
) {
|
|
42
|
+
const messageId = context.transformExpression(messageIdArg);
|
|
43
|
+
const expected = context.transformExpression(expectedArg);
|
|
44
|
+
return createBinaryExpression(
|
|
45
|
+
messageId,
|
|
46
|
+
createCallExpression(createIdentifier("hash"), [expected]),
|
|
47
|
+
SyntaxKind.EqualityOperator,
|
|
48
|
+
node,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return context.superTransformExpression(node);
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
package/src/session.ts
CHANGED
|
@@ -4,6 +4,8 @@ import * as path from "node:path";
|
|
|
4
4
|
import * as ts from "typescript";
|
|
5
5
|
import * as tstl from "typescript-to-lua";
|
|
6
6
|
import { lifecycleErasurePlugin } from "./lifecycle-erasure";
|
|
7
|
+
import { messageDispatchLoweringPlugin } from "./message-dispatch-lowering";
|
|
8
|
+
import { messageGuardLoweringPlugin } from "./message-guard-lowering";
|
|
7
9
|
import { AMBIENT_FILES, collectOutputs, type TranspileProjectResult } from "./transpile";
|
|
8
10
|
|
|
9
11
|
export interface TranspileSession {
|
|
@@ -19,7 +21,14 @@ const COMPILER_OPTIONS: tstl.CompilerOptions = {
|
|
|
19
21
|
// Don't cross-check the seeded ambient .d.ts surface against itself; only user
|
|
20
22
|
// files matter (mirrors transpileProject and the editor's skipLibCheck).
|
|
21
23
|
skipLibCheck: true,
|
|
22
|
-
|
|
24
|
+
// Defold scripts are not OO: free helper functions never receive a context,
|
|
25
|
+
// so suppress TSTL's implicit `self` parameter and the `_G` call-site filler.
|
|
26
|
+
noImplicitSelf: true,
|
|
27
|
+
luaPlugins: [
|
|
28
|
+
{ plugin: lifecycleErasurePlugin },
|
|
29
|
+
{ plugin: messageGuardLoweringPlugin },
|
|
30
|
+
{ plugin: messageDispatchLoweringPlugin },
|
|
31
|
+
],
|
|
23
32
|
};
|
|
24
33
|
|
|
25
34
|
function normalizeSlashes(p: string): string {
|
package/src/transpile.ts
CHANGED
|
@@ -4,6 +4,8 @@ import * as path from "node:path";
|
|
|
4
4
|
import type * as ts from "typescript";
|
|
5
5
|
import * as tstl from "typescript-to-lua";
|
|
6
6
|
import { lifecycleErasurePlugin } from "./lifecycle-erasure";
|
|
7
|
+
import { messageDispatchLoweringPlugin } from "./message-dispatch-lowering";
|
|
8
|
+
import { messageGuardLoweringPlugin } from "./message-guard-lowering";
|
|
7
9
|
|
|
8
10
|
export interface TranspileResult {
|
|
9
11
|
readonly lua: string;
|
|
@@ -56,6 +58,11 @@ function buildAmbientFiles(): Record<string, string> {
|
|
|
56
58
|
"node_modules/@defold-typescript/types/src/core-types.ts": readAmbient("src/core-types.ts"),
|
|
57
59
|
"node_modules/@defold-typescript/types/src/msg-overloads.d.ts":
|
|
58
60
|
readAmbient("src/msg-overloads.d.ts"),
|
|
61
|
+
"node_modules/@defold-typescript/types/src/message-guard.d.ts":
|
|
62
|
+
readAmbient("src/message-guard.d.ts"),
|
|
63
|
+
"node_modules/@defold-typescript/types/src/message-dispatch.d.ts": readAmbient(
|
|
64
|
+
"src/message-dispatch.d.ts",
|
|
65
|
+
),
|
|
59
66
|
"node_modules/@defold-typescript/types/src/lifecycle.ts": readAmbient("src/lifecycle.ts"),
|
|
60
67
|
// Mirror the consumer-facing re-exports of the real package index so a user
|
|
61
68
|
// file can `import { defineScript }` and `import type { Hash, Vector3 }`.
|
|
@@ -131,7 +138,14 @@ export function transpileProject(input: TranspileProjectInput): TranspileProject
|
|
|
131
138
|
// Don't cross-check the seeded ambient .d.ts surface against itself; we only
|
|
132
139
|
// care about diagnostics on user files (mirrors the editor's skipLibCheck).
|
|
133
140
|
skipLibCheck: true,
|
|
134
|
-
|
|
141
|
+
// Defold scripts are not OO: free helper functions never receive a context,
|
|
142
|
+
// so suppress TSTL's implicit `self` parameter and the `_G` call-site filler.
|
|
143
|
+
noImplicitSelf: true,
|
|
144
|
+
luaPlugins: [
|
|
145
|
+
{ plugin: lifecycleErasurePlugin },
|
|
146
|
+
{ plugin: messageGuardLoweringPlugin },
|
|
147
|
+
{ plugin: messageDispatchLoweringPlugin },
|
|
148
|
+
],
|
|
135
149
|
});
|
|
136
150
|
|
|
137
151
|
return collectOutputs(result.transpiledFiles, result.diagnostics, userKeys);
|