@defold-typescript/transpiler 0.5.0 → 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 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
- NodeFlags
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defold-typescript/transpiler",
3
- "version": "0.5.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.5.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
  }
@@ -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);