@defold-typescript/transpiler 0.4.3 → 0.5.1
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 +81 -13
- package/package.json +2 -2
- package/src/lifecycle-erasure.ts +99 -0
- package/src/session.ts +3 -0
- package/src/transpile.ts +42 -19
package/dist/index.js
CHANGED
|
@@ -9,11 +9,20 @@ import * as tstl2 from "typescript-to-lua";
|
|
|
9
9
|
import * as ts from "typescript";
|
|
10
10
|
import {
|
|
11
11
|
createAssignmentStatement,
|
|
12
|
+
createBinaryExpression,
|
|
12
13
|
createBlock,
|
|
14
|
+
createCallExpression,
|
|
13
15
|
createExpressionStatement,
|
|
16
|
+
createForInStatement,
|
|
14
17
|
createFunctionExpression,
|
|
15
18
|
createIdentifier,
|
|
16
|
-
|
|
19
|
+
createIfStatement,
|
|
20
|
+
createNilLiteral,
|
|
21
|
+
createReturnStatement,
|
|
22
|
+
createTableIndexExpression,
|
|
23
|
+
createVariableDeclarationStatement,
|
|
24
|
+
NodeFlags,
|
|
25
|
+
SyntaxKind as SyntaxKind2
|
|
17
26
|
} from "typescript-to-lua";
|
|
18
27
|
var FACTORY_MODULE = "@defold-typescript/types";
|
|
19
28
|
var FACTORY_NAMES = new Set(["defineScript", "defineGuiScript", "defineRenderScript"]);
|
|
@@ -65,6 +74,43 @@ function transformHookBody(fn, context) {
|
|
|
65
74
|
}
|
|
66
75
|
return [createExpressionStatement(context.transformExpression(fn.body))];
|
|
67
76
|
}
|
|
77
|
+
function crossesFunctionBoundary(node) {
|
|
78
|
+
return ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node) || ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node);
|
|
79
|
+
}
|
|
80
|
+
function initReturnsValue(fn) {
|
|
81
|
+
if (!ts.isBlock(fn.body)) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
let found = false;
|
|
85
|
+
const visit = (node) => {
|
|
86
|
+
if (found || crossesFunctionBoundary(node)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (ts.isReturnStatement(node) && node.expression !== undefined) {
|
|
90
|
+
found = true;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
ts.forEachChild(node, visit);
|
|
94
|
+
};
|
|
95
|
+
ts.forEachChild(fn.body, visit);
|
|
96
|
+
return found;
|
|
97
|
+
}
|
|
98
|
+
function initBuilderBody(fn, context) {
|
|
99
|
+
if (ts.isBlock(fn.body)) {
|
|
100
|
+
return context.transformStatements(fn.body.statements);
|
|
101
|
+
}
|
|
102
|
+
return [createReturnStatement([context.transformExpression(fn.body)])];
|
|
103
|
+
}
|
|
104
|
+
function emitInitMerge(fn, context, property) {
|
|
105
|
+
const builder = createFunctionExpression(createBlock(initBuilderBody(fn, context)), [], undefined, NodeFlags.Declaration, fn);
|
|
106
|
+
const builderDecl = createVariableDeclarationStatement(createIdentifier("____init"), builder);
|
|
107
|
+
const stateDecl = createVariableDeclarationStatement(createIdentifier("____s"), createCallExpression(createIdentifier("____init"), []));
|
|
108
|
+
const mergeAssignment = createAssignmentStatement(createTableIndexExpression(createIdentifier("self"), createIdentifier("____k")), createIdentifier("____v"));
|
|
109
|
+
const forIn = createForInStatement(createBlock([mergeAssignment]), [createIdentifier("____k"), createIdentifier("____v")], [createCallExpression(createIdentifier("pairs"), [createIdentifier("____s")])]);
|
|
110
|
+
const guard = createIfStatement(createBinaryExpression(createIdentifier("____s"), createNilLiteral(), SyntaxKind2.InequalityOperator), createBlock([forIn]));
|
|
111
|
+
const initFn = createFunctionExpression(createBlock([stateDecl, guard]), [createIdentifier("self")], undefined, NodeFlags.Declaration, fn);
|
|
112
|
+
return [builderDecl, createAssignmentStatement(createIdentifier("init"), initFn, property)];
|
|
113
|
+
}
|
|
68
114
|
function eraseFactoryCall(expression, context) {
|
|
69
115
|
if (!ts.isCallExpression(expression)) {
|
|
70
116
|
return;
|
|
@@ -83,6 +129,10 @@ function eraseFactoryCall(expression, context) {
|
|
|
83
129
|
if (name === undefined || fn === undefined) {
|
|
84
130
|
continue;
|
|
85
131
|
}
|
|
132
|
+
if (name === "init" && initReturnsValue(fn)) {
|
|
133
|
+
statements.push(...emitInitMerge(fn, context, property));
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
86
136
|
const params = fn.parameters.filter((param) => !isThisParameter(param) && ts.isIdentifier(param.name)).map((param) => context.transformExpression(param.name));
|
|
87
137
|
const fnExpression = createFunctionExpression(createBlock(transformHookBody(fn, context)), params, undefined, NodeFlags.Declaration, fn);
|
|
88
138
|
statements.push(createAssignmentStatement(createIdentifier(name), fnExpression, property));
|
|
@@ -120,7 +170,7 @@ var lifecycleErasurePlugin = {
|
|
|
120
170
|
};
|
|
121
171
|
|
|
122
172
|
// src/transpile.ts
|
|
123
|
-
import { readFileSync } from "node:fs";
|
|
173
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
124
174
|
import { createRequire } from "node:module";
|
|
125
175
|
import * as path from "node:path";
|
|
126
176
|
import * as tstl from "typescript-to-lua";
|
|
@@ -136,18 +186,34 @@ var TSTL_LANG_EXT_ROOT = path.dirname(requireFromHere.resolve("@typescript-to-lu
|
|
|
136
186
|
function readAmbient(rel) {
|
|
137
187
|
return readFileSync(path.join(TYPES_PKG_ROOT, rel), "utf8");
|
|
138
188
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
189
|
+
function buildAmbientFiles() {
|
|
190
|
+
const files = {
|
|
191
|
+
"node_modules/@typescript-to-lua/language-extensions/index.d.ts": readFileSync(path.join(TSTL_LANG_EXT_ROOT, "index.d.ts"), "utf8"),
|
|
192
|
+
"node_modules/@defold-typescript/types/src/core-types.ts": readAmbient("src/core-types.ts"),
|
|
193
|
+
"node_modules/@defold-typescript/types/src/msg-overloads.d.ts": readAmbient("src/msg-overloads.d.ts"),
|
|
194
|
+
"node_modules/@defold-typescript/types/src/lifecycle.ts": readAmbient("src/lifecycle.ts"),
|
|
195
|
+
"node_modules/@defold-typescript/types/index.ts": [
|
|
196
|
+
'export { defineGuiScript, defineRenderScript, defineScript } from "./src/lifecycle";',
|
|
197
|
+
'export type { GuiScriptHooks, InputAction, InputTouch, RenderScriptHooks, ScriptHooks } from "./src/lifecycle";',
|
|
198
|
+
'export type { Hash, Matrix4, Quaternion, Url, Vector, Vector3, Vector4 } from "./src/core-types";',
|
|
199
|
+
""
|
|
200
|
+
].join(`
|
|
201
|
+
`),
|
|
202
|
+
"node_modules/@defold-typescript/types/script.d.ts": `export {};
|
|
203
|
+
`,
|
|
204
|
+
"node_modules/@defold-typescript/types/gui-script.d.ts": `export {};
|
|
205
|
+
`,
|
|
206
|
+
"node_modules/@defold-typescript/types/render-script.d.ts": `export {};
|
|
149
207
|
`
|
|
150
|
-
};
|
|
208
|
+
};
|
|
209
|
+
for (const entry of readdirSync(path.join(TYPES_PKG_ROOT, "generated"))) {
|
|
210
|
+
if (entry.endsWith(".d.ts")) {
|
|
211
|
+
files[`node_modules/@defold-typescript/types/generated/${entry}`] = readAmbient(`generated/${entry}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return files;
|
|
215
|
+
}
|
|
216
|
+
var AMBIENT_FILES = buildAmbientFiles();
|
|
151
217
|
function collectOutputs(transpiledFiles, diagnostics, userKeys) {
|
|
152
218
|
const lua = {};
|
|
153
219
|
const sourceMaps = {};
|
|
@@ -174,6 +240,7 @@ function transpileProject(input) {
|
|
|
174
240
|
const result = tstl.transpileVirtualProject(merged, {
|
|
175
241
|
luaTarget: tstl.LuaTarget.Lua54,
|
|
176
242
|
sourceMap: true,
|
|
243
|
+
skipLibCheck: true,
|
|
177
244
|
luaPlugins: [{ plugin: lifecycleErasurePlugin }]
|
|
178
245
|
});
|
|
179
246
|
return collectOutputs(result.transpiledFiles, result.diagnostics, userKeys);
|
|
@@ -192,6 +259,7 @@ var requireFromHere2 = createRequire2(import.meta.url);
|
|
|
192
259
|
var COMPILER_OPTIONS = {
|
|
193
260
|
luaTarget: tstl2.LuaTarget.Lua54,
|
|
194
261
|
sourceMap: true,
|
|
262
|
+
skipLibCheck: true,
|
|
195
263
|
luaPlugins: [{ plugin: lifecycleErasurePlugin }]
|
|
196
264
|
};
|
|
197
265
|
function normalizeSlashes(p) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defold-typescript/transpiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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.
|
|
31
|
+
"@defold-typescript/types": "0.5.1",
|
|
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
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import * as ts from "typescript";
|
|
2
2
|
import {
|
|
3
3
|
createAssignmentStatement,
|
|
4
|
+
createBinaryExpression,
|
|
4
5
|
createBlock,
|
|
6
|
+
createCallExpression,
|
|
5
7
|
createExpressionStatement,
|
|
8
|
+
createForInStatement,
|
|
6
9
|
createFunctionExpression,
|
|
7
10
|
createIdentifier,
|
|
11
|
+
createIfStatement,
|
|
12
|
+
createNilLiteral,
|
|
13
|
+
createReturnStatement,
|
|
14
|
+
createTableIndexExpression,
|
|
15
|
+
createVariableDeclarationStatement,
|
|
8
16
|
type Identifier,
|
|
9
17
|
NodeFlags,
|
|
10
18
|
type Plugin,
|
|
11
19
|
type Statement,
|
|
20
|
+
SyntaxKind,
|
|
12
21
|
type TransformationContext,
|
|
13
22
|
} from "typescript-to-lua";
|
|
14
23
|
|
|
@@ -75,6 +84,92 @@ function transformHookBody(fn: HookFunction, context: TransformationContext): St
|
|
|
75
84
|
return [createExpressionStatement(context.transformExpression(fn.body))];
|
|
76
85
|
}
|
|
77
86
|
|
|
87
|
+
function crossesFunctionBoundary(node: ts.Node): boolean {
|
|
88
|
+
return (
|
|
89
|
+
ts.isFunctionDeclaration(node) ||
|
|
90
|
+
ts.isFunctionExpression(node) ||
|
|
91
|
+
ts.isArrowFunction(node) ||
|
|
92
|
+
ts.isMethodDeclaration(node) ||
|
|
93
|
+
ts.isGetAccessorDeclaration(node) ||
|
|
94
|
+
ts.isSetAccessorDeclaration(node)
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function initReturnsValue(fn: HookFunction): boolean {
|
|
99
|
+
if (!ts.isBlock(fn.body)) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
let found = false;
|
|
103
|
+
const visit = (node: ts.Node): void => {
|
|
104
|
+
if (found || crossesFunctionBoundary(node)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (ts.isReturnStatement(node) && node.expression !== undefined) {
|
|
108
|
+
found = true;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
ts.forEachChild(node, visit);
|
|
112
|
+
};
|
|
113
|
+
ts.forEachChild(fn.body, visit);
|
|
114
|
+
return found;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function initBuilderBody(fn: HookFunction, context: TransformationContext): Statement[] {
|
|
118
|
+
if (ts.isBlock(fn.body)) {
|
|
119
|
+
return context.transformStatements(fn.body.statements);
|
|
120
|
+
}
|
|
121
|
+
return [createReturnStatement([context.transformExpression(fn.body)])];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// init-merge: Defold owns `self` (a userdata-backed table) and a script can
|
|
125
|
+
// populate but not replace it, so a returning `init(): TSelf` can't be emitted
|
|
126
|
+
// verbatim. Wrap the body in a `____init` builder and merge its result onto the
|
|
127
|
+
// engine `self`; a `nil` return (stateless script) merges nothing.
|
|
128
|
+
function emitInitMerge(
|
|
129
|
+
fn: HookFunction,
|
|
130
|
+
context: TransformationContext,
|
|
131
|
+
property: ts.ObjectLiteralElementLike,
|
|
132
|
+
): Statement[] {
|
|
133
|
+
const builder = createFunctionExpression(
|
|
134
|
+
createBlock(initBuilderBody(fn, context)),
|
|
135
|
+
[],
|
|
136
|
+
undefined,
|
|
137
|
+
NodeFlags.Declaration,
|
|
138
|
+
fn,
|
|
139
|
+
);
|
|
140
|
+
const builderDecl = createVariableDeclarationStatement(createIdentifier("____init"), builder);
|
|
141
|
+
|
|
142
|
+
const stateDecl = createVariableDeclarationStatement(
|
|
143
|
+
createIdentifier("____s"),
|
|
144
|
+
createCallExpression(createIdentifier("____init"), []),
|
|
145
|
+
);
|
|
146
|
+
const mergeAssignment = createAssignmentStatement(
|
|
147
|
+
createTableIndexExpression(createIdentifier("self"), createIdentifier("____k")),
|
|
148
|
+
createIdentifier("____v"),
|
|
149
|
+
);
|
|
150
|
+
const forIn = createForInStatement(
|
|
151
|
+
createBlock([mergeAssignment]),
|
|
152
|
+
[createIdentifier("____k"), createIdentifier("____v")],
|
|
153
|
+
[createCallExpression(createIdentifier("pairs"), [createIdentifier("____s")])],
|
|
154
|
+
);
|
|
155
|
+
const guard = createIfStatement(
|
|
156
|
+
createBinaryExpression(
|
|
157
|
+
createIdentifier("____s"),
|
|
158
|
+
createNilLiteral(),
|
|
159
|
+
SyntaxKind.InequalityOperator,
|
|
160
|
+
),
|
|
161
|
+
createBlock([forIn]),
|
|
162
|
+
);
|
|
163
|
+
const initFn = createFunctionExpression(
|
|
164
|
+
createBlock([stateDecl, guard]),
|
|
165
|
+
[createIdentifier("self")],
|
|
166
|
+
undefined,
|
|
167
|
+
NodeFlags.Declaration,
|
|
168
|
+
fn,
|
|
169
|
+
);
|
|
170
|
+
return [builderDecl, createAssignmentStatement(createIdentifier("init"), initFn, property)];
|
|
171
|
+
}
|
|
172
|
+
|
|
78
173
|
function eraseFactoryCall(
|
|
79
174
|
expression: ts.Expression,
|
|
80
175
|
context: TransformationContext,
|
|
@@ -96,6 +191,10 @@ function eraseFactoryCall(
|
|
|
96
191
|
if (name === undefined || fn === undefined) {
|
|
97
192
|
continue;
|
|
98
193
|
}
|
|
194
|
+
if (name === "init" && initReturnsValue(fn)) {
|
|
195
|
+
statements.push(...emitInitMerge(fn, context, property));
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
99
198
|
const params: Identifier[] = fn.parameters
|
|
100
199
|
.filter((param) => !isThisParameter(param) && ts.isIdentifier(param.name))
|
|
101
200
|
.map((param) => context.transformExpression(param.name as ts.Identifier) as Identifier);
|
package/src/session.ts
CHANGED
|
@@ -16,6 +16,9 @@ const requireFromHere = createRequire(import.meta.url);
|
|
|
16
16
|
const COMPILER_OPTIONS: tstl.CompilerOptions = {
|
|
17
17
|
luaTarget: tstl.LuaTarget.Lua54,
|
|
18
18
|
sourceMap: true,
|
|
19
|
+
// Don't cross-check the seeded ambient .d.ts surface against itself; only user
|
|
20
|
+
// files matter (mirrors transpileProject and the editor's skipLibCheck).
|
|
21
|
+
skipLibCheck: true,
|
|
19
22
|
luaPlugins: [{ plugin: lifecycleErasurePlugin }],
|
|
20
23
|
};
|
|
21
24
|
|
package/src/transpile.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type * as ts from "typescript";
|
|
@@ -47,24 +47,44 @@ function readAmbient(rel: string): string {
|
|
|
47
47
|
return readFileSync(path.join(TYPES_PKG_ROOT, rel), "utf8");
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
50
|
+
function buildAmbientFiles(): Record<string, string> {
|
|
51
|
+
const files: Record<string, string> = {
|
|
52
|
+
"node_modules/@typescript-to-lua/language-extensions/index.d.ts": readFileSync(
|
|
53
|
+
path.join(TSTL_LANG_EXT_ROOT, "index.d.ts"),
|
|
54
|
+
"utf8",
|
|
55
|
+
),
|
|
56
|
+
"node_modules/@defold-typescript/types/src/core-types.ts": readAmbient("src/core-types.ts"),
|
|
57
|
+
"node_modules/@defold-typescript/types/src/msg-overloads.d.ts":
|
|
58
|
+
readAmbient("src/msg-overloads.d.ts"),
|
|
59
|
+
"node_modules/@defold-typescript/types/src/lifecycle.ts": readAmbient("src/lifecycle.ts"),
|
|
60
|
+
// Mirror the consumer-facing re-exports of the real package index so a user
|
|
61
|
+
// file can `import { defineScript }` and `import type { Hash, Vector3 }`.
|
|
62
|
+
"node_modules/@defold-typescript/types/index.ts": [
|
|
63
|
+
'export { defineGuiScript, defineRenderScript, defineScript } from "./src/lifecycle";',
|
|
64
|
+
'export type { GuiScriptHooks, InputAction, InputTouch, RenderScriptHooks, ScriptHooks } from "./src/lifecycle";',
|
|
65
|
+
'export type { Hash, Matrix4, Quaternion, Url, Vector, Vector3, Vector4 } from "./src/core-types";',
|
|
66
|
+
"",
|
|
67
|
+
].join("\n"),
|
|
68
|
+
// Per-kind subpath entrypoints exist as package exports for the editor.
|
|
69
|
+
// Their namespaces are already seeded ambiently below, so the transpiler
|
|
70
|
+
// only needs the specifiers to resolve — an empty module is enough.
|
|
71
|
+
"node_modules/@defold-typescript/types/script.d.ts": "export {};\n",
|
|
72
|
+
"node_modules/@defold-typescript/types/gui-script.d.ts": "export {};\n",
|
|
73
|
+
"node_modules/@defold-typescript/types/render-script.d.ts": "export {};\n",
|
|
74
|
+
};
|
|
75
|
+
// Seed every generated namespace so real multi-namespace user code (sprite,
|
|
76
|
+
// physics, label, ...) resolves — not just the historical vmath/msg/go subset.
|
|
77
|
+
for (const entry of readdirSync(path.join(TYPES_PKG_ROOT, "generated"))) {
|
|
78
|
+
if (entry.endsWith(".d.ts")) {
|
|
79
|
+
files[`node_modules/@defold-typescript/types/generated/${entry}`] = readAmbient(
|
|
80
|
+
`generated/${entry}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return files;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const AMBIENT_FILES: Readonly<Record<string, string>> = buildAmbientFiles();
|
|
68
88
|
|
|
69
89
|
interface CollectableFile {
|
|
70
90
|
readonly sourceFiles: readonly ts.SourceFile[];
|
|
@@ -108,6 +128,9 @@ export function transpileProject(input: TranspileProjectInput): TranspileProject
|
|
|
108
128
|
const result = tstl.transpileVirtualProject(merged, {
|
|
109
129
|
luaTarget: tstl.LuaTarget.Lua54,
|
|
110
130
|
sourceMap: true,
|
|
131
|
+
// Don't cross-check the seeded ambient .d.ts surface against itself; we only
|
|
132
|
+
// care about diagnostics on user files (mirrors the editor's skipLibCheck).
|
|
133
|
+
skipLibCheck: true,
|
|
111
134
|
luaPlugins: [{ plugin: lifecycleErasurePlugin }],
|
|
112
135
|
});
|
|
113
136
|
|