@defold-typescript/transpiler 0.35.0 → 0.37.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
@@ -1,1342 +1,1963 @@
1
- // src/scene-text-format.ts
2
- class SceneTextFormatError extends Error {
3
- line;
4
- constructor(message, line) {
5
- super(`${message} (line ${line})`);
6
- this.name = "SceneTextFormatError";
7
- this.line = line;
1
+ // src/companion-closure.ts
2
+ import * as ts3 from "typescript";
3
+
4
+ // src/lifecycle-erasure.ts
5
+ import * as ts2 from "typescript";
6
+ import {
7
+ createAssignmentStatement,
8
+ createBinaryExpression as createBinaryExpression2,
9
+ createBlock as createBlock2,
10
+ createCallExpression as createCallExpression2,
11
+ createExpressionStatement,
12
+ createForInStatement,
13
+ createFunctionExpression as createFunctionExpression2,
14
+ createIdentifier as createIdentifier2,
15
+ createIfStatement as createIfStatement2,
16
+ createNilLiteral,
17
+ createReturnStatement,
18
+ createStringLiteral as createStringLiteral2,
19
+ createTableIndexExpression,
20
+ createVariableDeclarationStatement as createVariableDeclarationStatement2,
21
+ NodeFlags as NodeFlags2,
22
+ SyntaxKind as SyntaxKind4
23
+ } from "typescript-to-lua";
24
+
25
+ // src/message-dispatch-lowering.ts
26
+ import * as ts from "typescript";
27
+ import {
28
+ createBinaryExpression,
29
+ createBlock,
30
+ createCallExpression,
31
+ createFunctionExpression,
32
+ createIdentifier,
33
+ createIfStatement,
34
+ createStringLiteral,
35
+ createVariableDeclarationStatement,
36
+ NodeFlags,
37
+ SyntaxKind as SyntaxKind2
38
+ } from "typescript-to-lua";
39
+ var DISPATCH_MODULE = "@defold-typescript/types";
40
+ var DISPATCH_NAME = "onMessage";
41
+ var HANDLER_PARAM_TARGETS = ["self", "message", "sender"];
42
+ function resolvesToDispatchExport(callee, checker) {
43
+ let symbol = checker.getSymbolAtLocation(callee);
44
+ if (symbol === undefined) {
45
+ return false;
8
46
  }
9
- }
10
- var IDENTIFIER_PART = /[A-Za-z0-9_]/;
11
- function append(target, key, value) {
12
- const existing = target.get(key);
13
- if (existing === undefined) {
14
- target.set(key, [value]);
15
- return;
47
+ if (symbol.flags & ts.SymbolFlags.Alias) {
48
+ symbol = checker.getAliasedSymbol(symbol);
16
49
  }
17
- existing.push(value);
50
+ if (symbol.getName() !== DISPATCH_NAME) {
51
+ return false;
52
+ }
53
+ const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
54
+ if (declaration === undefined) {
55
+ return false;
56
+ }
57
+ return declaration.getSourceFile().fileName.includes(DISPATCH_MODULE);
18
58
  }
19
- function parseSceneTextFormat(text) {
20
- let pos = 0;
21
- let line = 1;
22
- function fail(message, at) {
23
- throw new SceneTextFormatError(message, at);
59
+ function handlerName(property) {
60
+ const name = property.name;
61
+ if (name === undefined) {
62
+ return;
24
63
  }
25
- function advance() {
26
- const ch = text.charAt(pos);
27
- pos += 1;
28
- if (ch === `
29
- `)
30
- line += 1;
31
- return ch;
64
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
65
+ return name.text;
32
66
  }
33
- function skipWhitespace() {
34
- while (pos < text.length) {
35
- const ch = text.charAt(pos);
36
- if (ch !== " " && ch !== "\t" && ch !== "\r" && ch !== `
37
- `)
38
- return;
39
- advance();
40
- }
67
+ return;
68
+ }
69
+ function handlerFunction(property) {
70
+ if (ts.isMethodDeclaration(property) && property.body !== undefined) {
71
+ return property;
41
72
  }
42
- function skipInlineWhitespace() {
43
- while (pos < text.length) {
44
- const ch = text.charAt(pos);
45
- if (ch !== " " && ch !== "\t")
46
- return;
47
- pos += 1;
73
+ if (ts.isPropertyAssignment(property)) {
74
+ const initializer = property.initializer;
75
+ if (ts.isFunctionExpression(initializer) || ts.isArrowFunction(initializer)) {
76
+ return initializer;
48
77
  }
49
78
  }
50
- function readIdentifier(at) {
51
- const start = pos;
52
- while (pos < text.length && IDENTIFIER_PART.test(text.charAt(pos))) {
53
- pos += 1;
79
+ return;
80
+ }
81
+ function isThisParameter(param) {
82
+ return ts.isIdentifier(param.name) && ts.identifierToKeywordKind(param.name) === ts.SyntaxKind.ThisKeyword;
83
+ }
84
+ function aliasStatements(fn) {
85
+ const params = fn.parameters.filter((param) => !isThisParameter(param));
86
+ const aliases = [];
87
+ params.forEach((param, index) => {
88
+ const engineName = HANDLER_PARAM_TARGETS[index];
89
+ if (engineName === undefined || !ts.isIdentifier(param.name)) {
90
+ return;
54
91
  }
55
- if (pos === start) {
56
- fail(`unexpected \`${text.charAt(start)}\``, at);
92
+ if (param.name.text !== engineName) {
93
+ aliases.push(createVariableDeclarationStatement(createIdentifier(param.name.text), createIdentifier(engineName)));
57
94
  }
58
- return text.slice(start, pos);
95
+ });
96
+ return aliases;
97
+ }
98
+ function handlerBody(fn, context) {
99
+ if (ts.isBlock(fn.body)) {
100
+ return context.transformStatements(fn.body.statements);
59
101
  }
60
- function readQuotedScalar() {
61
- let value = "";
62
- for (;; ) {
63
- const openedAt = line;
64
- advance();
65
- let closed = false;
66
- while (pos < text.length) {
67
- const ch = advance();
68
- if (ch === "\\") {
69
- if (pos >= text.length)
70
- break;
71
- const escaped = advance();
72
- if (escaped === '"')
73
- value += '"';
74
- else if (escaped === "\\")
75
- value += "\\";
76
- else if (escaped === "n")
77
- value += `
78
- `;
79
- else if (escaped === "r")
80
- value += "\r";
81
- else if (escaped === "t")
82
- value += "\t";
83
- else
84
- value += `\\${escaped}`;
85
- continue;
86
- }
87
- if (ch === '"') {
88
- closed = true;
89
- break;
102
+ return context.transformStatements([ts.factory.createExpressionStatement(fn.body)]);
103
+ }
104
+ var messageDispatchLoweringPlugin = {
105
+ visitors: {
106
+ [ts.SyntaxKind.CallExpression]: (node, context) => {
107
+ const [handlersArg] = node.arguments;
108
+ if (node.arguments.length === 1 && handlersArg !== undefined && ts.isObjectLiteralExpression(handlersArg) && resolvesToDispatchExport(node.expression, context.checker)) {
109
+ let chain;
110
+ const properties = [...handlersArg.properties];
111
+ for (let i = properties.length - 1;i >= 0; i--) {
112
+ const property = properties[i];
113
+ if (property === undefined) {
114
+ continue;
115
+ }
116
+ const name = handlerName(property);
117
+ const fn = handlerFunction(property);
118
+ if (name === undefined || fn === undefined) {
119
+ continue;
120
+ }
121
+ const condition = createBinaryExpression(createIdentifier("message_id"), createCallExpression(createIdentifier("hash"), [createStringLiteral(name)]), SyntaxKind2.EqualityOperator);
122
+ const body = [...aliasStatements(fn), ...handlerBody(fn, context)];
123
+ chain = createIfStatement(condition, createBlock(body), chain);
90
124
  }
91
- if (ch === `
92
- `)
93
- break;
94
- value += ch;
95
- }
96
- if (!closed) {
97
- fail("unterminated string literal", openedAt);
98
- }
99
- const resumePos = pos;
100
- const resumeLine = line;
101
- skipWhitespace();
102
- if (text.charAt(pos) === '"') {
103
- continue;
125
+ const params = [
126
+ createIdentifier("self"),
127
+ createIdentifier("message_id"),
128
+ createIdentifier("message"),
129
+ createIdentifier("sender")
130
+ ];
131
+ return createFunctionExpression(createBlock(chain === undefined ? [] : [chain]), params, undefined, NodeFlags.Declaration, node);
104
132
  }
105
- pos = resumePos;
106
- line = resumeLine;
107
- return value;
133
+ return context.superTransformExpression(node);
108
134
  }
109
135
  }
110
- function readUnquotedScalar() {
111
- const newline = text.indexOf(`
112
- `, pos);
113
- const end = newline === -1 ? text.length : newline;
114
- const raw = text.slice(pos, end);
115
- pos = end;
116
- return raw.trim();
136
+ };
137
+
138
+ // src/lifecycle-erasure.ts
139
+ var FACTORY_MODULE = "@defold-typescript/types";
140
+ var FACTORY_NAMES = new Set(["defineScript", "defineGuiScript", "defineRenderScript"]);
141
+ var FACTORY_SPECIFIERS = new Set([
142
+ FACTORY_MODULE,
143
+ `${FACTORY_MODULE}/script`,
144
+ `${FACTORY_MODULE}/gui-script`,
145
+ `${FACTORY_MODULE}/render-script`
146
+ ]);
147
+ function resolvesToFactoryExport(callee, checker) {
148
+ let symbol = checker.getSymbolAtLocation(callee);
149
+ if (symbol === undefined) {
150
+ return false;
117
151
  }
118
- function parseBody(nested, openedAt) {
119
- const fields = new Map;
120
- const messages = new Map;
121
- for (;; ) {
122
- skipWhitespace();
123
- if (pos >= text.length) {
124
- if (nested) {
125
- fail("unclosed `{`", openedAt);
126
- }
127
- break;
128
- }
129
- if (text.charAt(pos) === "}") {
130
- if (!nested) {
131
- fail("unexpected `}`", line);
132
- }
133
- advance();
134
- break;
135
- }
136
- const keyLine = line;
137
- const key = readIdentifier(keyLine);
138
- skipInlineWhitespace();
139
- const delimiter = text.charAt(pos);
140
- if (delimiter === "{") {
141
- const braceLine = line;
142
- advance();
143
- append(messages, key, parseBody(true, braceLine));
144
- continue;
145
- }
146
- if (delimiter === ":") {
147
- advance();
148
- skipInlineWhitespace();
149
- append(fields, key, text.charAt(pos) === '"' ? readQuotedScalar() : readUnquotedScalar());
150
- continue;
151
- }
152
- fail(`expected \`:\` or \`{\` after \`${key}\``, keyLine);
152
+ if (symbol.flags & ts2.SymbolFlags.Alias) {
153
+ symbol = checker.getAliasedSymbol(symbol);
154
+ }
155
+ if (!FACTORY_NAMES.has(symbol.getName())) {
156
+ return false;
157
+ }
158
+ const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
159
+ if (declaration === undefined) {
160
+ return false;
161
+ }
162
+ return declaration.getSourceFile().fileName.includes(FACTORY_MODULE);
163
+ }
164
+ function isThisParameter2(param) {
165
+ return ts2.isIdentifier(param.name) && ts2.identifierToKeywordKind(param.name) === ts2.SyntaxKind.ThisKeyword;
166
+ }
167
+ function hookName(property) {
168
+ const name = property.name;
169
+ if (name === undefined) {
170
+ return;
171
+ }
172
+ if (ts2.isIdentifier(name) || ts2.isStringLiteral(name)) {
173
+ return name.text;
174
+ }
175
+ return;
176
+ }
177
+ function hookFunction(property) {
178
+ if (ts2.isMethodDeclaration(property) && property.body !== undefined) {
179
+ return property;
180
+ }
181
+ if (ts2.isPropertyAssignment(property)) {
182
+ const initializer = property.initializer;
183
+ if (ts2.isFunctionExpression(initializer) || ts2.isArrowFunction(initializer)) {
184
+ return initializer;
153
185
  }
154
- return { fields, messages };
155
186
  }
156
- return parseBody(false, 1);
187
+ return;
157
188
  }
158
-
159
- // src/animation-sources.ts
160
- function resourceKey(resource) {
161
- return resource.startsWith("/") ? resource.slice(1) : resource;
189
+ function dispatcherInitializer(property, checker) {
190
+ if (!ts2.isPropertyAssignment(property)) {
191
+ return;
192
+ }
193
+ const initializer = property.initializer;
194
+ if (!ts2.isCallExpression(initializer)) {
195
+ return;
196
+ }
197
+ return resolvesToDispatchExport(initializer.expression, checker) ? initializer : undefined;
162
198
  }
163
- function normalizeDocumentKey(path) {
164
- return path.replaceAll("\\", "/");
199
+ function transformHookBody(fn, context) {
200
+ if (ts2.isBlock(fn.body)) {
201
+ return context.transformStatements(fn.body.statements);
202
+ }
203
+ return [createExpressionStatement(context.transformExpression(fn.body))];
165
204
  }
166
- var SPRITE_SUFFIX = ".sprite";
167
- var MODEL_SUFFIX = ".model";
168
- var ANIMATION_SET_SUFFIX = ".animationset";
169
- var HOP_ONLY_SUFFIXES = [SPRITE_SUFFIX, MODEL_SUFFIX, ANIMATION_SET_SUFFIX];
170
- function childrenOf(message, name) {
171
- return message.messages.get(name) ?? [];
205
+ function crossesFunctionBoundary(node) {
206
+ return ts2.isFunctionDeclaration(node) || ts2.isFunctionExpression(node) || ts2.isArrowFunction(node) || ts2.isMethodDeclaration(node) || ts2.isGetAccessorDeclaration(node) || ts2.isSetAccessorDeclaration(node);
172
207
  }
173
- function declaredAnimations(document) {
174
- const ids = new Set;
175
- for (const animation of childrenOf(document, "animations")) {
176
- for (const id of animation.fields.get("id") ?? []) {
177
- if (id !== "")
178
- ids.add(id);
208
+ function initReturnsValue(fn) {
209
+ if (!ts2.isBlock(fn.body)) {
210
+ return true;
211
+ }
212
+ let found = false;
213
+ const visit = (node) => {
214
+ if (found || crossesFunctionBoundary(node)) {
215
+ return;
216
+ }
217
+ if (ts2.isReturnStatement(node) && node.expression !== undefined) {
218
+ found = true;
219
+ return;
179
220
  }
221
+ ts2.forEachChild(node, visit);
222
+ };
223
+ ts2.forEachChild(fn.body, visit);
224
+ return found;
225
+ }
226
+ function initBuilderBody(fn, context) {
227
+ if (ts2.isBlock(fn.body)) {
228
+ return context.transformStatements(fn.body.statements);
180
229
  }
181
- return ids;
230
+ return [createReturnStatement([context.transformExpression(fn.body)])];
182
231
  }
183
- function readAnimationSources(assets, unresolved) {
184
- const animationsByTileSet = new Map;
185
- for (const [path, text] of assets) {
186
- const displayPath = normalizeDocumentKey(path);
187
- if (HOP_ONLY_SUFFIXES.some((suffix) => displayPath.endsWith(suffix)))
232
+ function emitInitMerge(fn, context, property) {
233
+ const authorParam = fn.parameters.find((param) => !isThisParameter2(param) && ts2.isIdentifier(param.name));
234
+ const builderParams = authorParam === undefined ? [] : [context.transformExpression(authorParam.name)];
235
+ const builder = createFunctionExpression2(createBlock2(initBuilderBody(fn, context)), builderParams, undefined, NodeFlags2.Declaration, fn);
236
+ const builderDecl = createVariableDeclarationStatement2(createIdentifier2("____init"), builder);
237
+ const builderArgs = authorParam === undefined ? [] : [createIdentifier2("self")];
238
+ const stateDecl = createVariableDeclarationStatement2(createIdentifier2("____s"), createCallExpression2(createIdentifier2("____init"), builderArgs));
239
+ const mergeAssignment = createAssignmentStatement(createTableIndexExpression(createIdentifier2("self"), createIdentifier2("____k")), createIdentifier2("____v"));
240
+ const forIn = createForInStatement(createBlock2([mergeAssignment]), [createIdentifier2("____k"), createIdentifier2("____v")], [createCallExpression2(createIdentifier2("pairs"), [createIdentifier2("____s")])]);
241
+ const guard = createIfStatement2(createBinaryExpression2(createIdentifier2("____s"), createNilLiteral(), SyntaxKind4.InequalityOperator), createBlock2([forIn]));
242
+ const initFn = createFunctionExpression2(createBlock2([stateDecl, guard]), [createIdentifier2("self")], undefined, NodeFlags2.Declaration, fn);
243
+ return [builderDecl, createAssignmentStatement(createIdentifier2("init"), initFn, property)];
244
+ }
245
+ function emitPropertyRegistrations(property, context) {
246
+ if (!ts2.isPropertyAssignment(property) || !ts2.isObjectLiteralExpression(property.initializer)) {
247
+ return [];
248
+ }
249
+ const registrations = [];
250
+ for (const member of property.initializer.properties) {
251
+ if (!ts2.isPropertyAssignment(member)) {
188
252
  continue;
189
- let document;
190
- try {
191
- document = parseSceneTextFormat(text);
192
- } catch (error) {
193
- if (!(error instanceof SceneTextFormatError))
194
- throw error;
195
- unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
253
+ }
254
+ const key = member.name;
255
+ if (!ts2.isIdentifier(key) && !ts2.isStringLiteral(key)) {
196
256
  continue;
197
257
  }
198
- animationsByTileSet.set(displayPath, declaredAnimations(document));
258
+ registrations.push(createExpressionStatement(createCallExpression2(createTableIndexExpression(createIdentifier2("go"), createStringLiteral2("property")), [createStringLiteral2(key.text), context.transformExpression(member.initializer)])));
199
259
  }
200
- return animationsByTileSet;
260
+ return registrations;
201
261
  }
202
- function animationSetEntries(document) {
203
- const entries = [];
204
- for (const entry of childrenOf(document, "animations")) {
205
- for (const animation of entry.fields.get("animation") ?? []) {
206
- if (animation !== "")
207
- entries.push(animation);
208
- }
262
+ function eraseFactoryCall(expression, context) {
263
+ if (!ts2.isCallExpression(expression)) {
264
+ return;
209
265
  }
210
- return entries;
211
- }
212
-
213
- // src/component-animation-index.ts
214
- var SCRIPT_SUFFIX = ".script";
215
- var SPRITE_SUFFIX2 = ".sprite";
216
- var MODEL_SUFFIX2 = ".model";
217
- var ANIMATION_SET_SUFFIX2 = ".animationset";
218
- var MESH_SUFFIXES = [".gltf", ".glb"];
219
- function firstField(message, name) {
220
- return (message.fields.get(name) ?? [])[0];
221
- }
222
- function childrenOf2(message, name) {
223
- return message.messages.get(name) ?? [];
224
- }
225
- function basename(resource) {
226
- const file = resource.slice(resource.lastIndexOf("/") + 1);
227
- const dot = file.lastIndexOf(".");
228
- return dot === -1 ? file : file.slice(0, dot);
229
- }
230
- function readAssets(assets, unresolved) {
231
- const animationsByTileSet = readAnimationSources(assets, unresolved);
232
- const tileSetBySprite = new Map;
233
- const modelDocuments = new Set;
234
- const animationSourceByModel = new Map;
235
- const entriesByAnimationSet = new Map;
236
- for (const [path, text] of assets) {
237
- const displayPath = normalizeDocumentKey(path);
238
- const isSprite = displayPath.endsWith(SPRITE_SUFFIX2);
239
- const isModel = displayPath.endsWith(MODEL_SUFFIX2);
240
- const isAnimationSet = displayPath.endsWith(ANIMATION_SET_SUFFIX2);
241
- if (!isSprite && !isModel && !isAnimationSet)
266
+ if (!resolvesToFactoryExport(expression.expression, context.checker)) {
267
+ return;
268
+ }
269
+ const hooks = expression.arguments[0];
270
+ if (hooks === undefined || !ts2.isObjectLiteralExpression(hooks)) {
271
+ return;
272
+ }
273
+ const registrations = [];
274
+ const statements = [];
275
+ for (const property of hooks.properties) {
276
+ const name = hookName(property);
277
+ if (name === undefined) {
242
278
  continue;
243
- let document;
244
- try {
245
- document = parseSceneTextFormat(text);
246
- } catch (error) {
247
- if (!(error instanceof SceneTextFormatError))
248
- throw error;
249
- unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
279
+ }
280
+ if (name === "properties") {
281
+ registrations.push(...emitPropertyRegistrations(property, context));
250
282
  continue;
251
283
  }
252
- if (isSprite) {
253
- const tileSet = firstField(document, "tile_set");
254
- if (tileSet !== undefined)
255
- tileSetBySprite.set(displayPath, resourceKey(tileSet));
284
+ const fn = hookFunction(property);
285
+ if (fn === undefined) {
286
+ const dispatch = dispatcherInitializer(property, context.checker);
287
+ if (dispatch !== undefined) {
288
+ statements.push(createAssignmentStatement(createIdentifier2(name), context.transformExpression(dispatch), property));
289
+ }
256
290
  continue;
257
291
  }
258
- if (isModel) {
259
- modelDocuments.add(displayPath);
260
- const animations = firstField(document, "animations");
261
- if (animations !== undefined)
262
- animationSourceByModel.set(displayPath, animations);
292
+ if (name === "init" && initReturnsValue(fn)) {
293
+ statements.push(...emitInitMerge(fn, context, property));
263
294
  continue;
264
295
  }
265
- entriesByAnimationSet.set(displayPath, animationSetEntries(document));
296
+ const params = fn.parameters.filter((param) => !isThisParameter2(param) && ts2.isIdentifier(param.name)).map((param) => context.transformExpression(param.name));
297
+ const fnExpression = createFunctionExpression2(createBlock2(transformHookBody(fn, context)), params, undefined, NodeFlags2.Declaration, fn);
298
+ statements.push(createAssignmentStatement(createIdentifier2(name), fnExpression, property));
266
299
  }
267
- return {
268
- animationsByTileSet,
269
- tileSetBySprite,
270
- modelDocuments,
271
- animationSourceByModel,
272
- entriesByAnimationSet
273
- };
300
+ return [...registrations, ...statements];
274
301
  }
275
- function resolveAnimationSet(input) {
276
- const { path, prefix, stack, assets, unresolved } = input;
277
- const entries = assets.entriesByAnimationSet.get(path);
278
- if (entries === undefined)
279
- return;
280
- stack.add(path);
281
- try {
282
- const ids = new Map;
283
- for (const entry of entries) {
284
- const key = resourceKey(entry);
285
- if (MESH_SUFFIXES.some((suffix) => key.endsWith(suffix))) {
286
- ids.set(prefix + basename(key), path);
287
- continue;
288
- }
289
- if (!key.endsWith(ANIMATION_SET_SUFFIX2)) {
290
- unresolved.push(`${path}: the entry ${entry} is not a .gltf, .glb or .animationset, so it declares no animation id`);
291
- continue;
292
- }
293
- if (!assets.entriesByAnimationSet.has(key)) {
294
- unresolved.push(`${path}: the entry ${entry} is not among the project's animation set documents`);
295
- return;
296
- }
297
- if (stack.has(key)) {
298
- unresolved.push(`${path}: the entry ${entry} re-enters an animation set already being read, so the chain is cyclic`);
299
- return;
302
+ function isFactoryOnlyImport(node) {
303
+ if (!ts2.isStringLiteral(node.moduleSpecifier) || !FACTORY_SPECIFIERS.has(node.moduleSpecifier.text)) {
304
+ return false;
305
+ }
306
+ const clause = node.importClause;
307
+ if (clause === undefined || clause.name !== undefined) {
308
+ return false;
309
+ }
310
+ if (clause.isTypeOnly) {
311
+ return false;
312
+ }
313
+ const bindings = clause.namedBindings;
314
+ if (bindings === undefined || !ts2.isNamedImports(bindings)) {
315
+ return false;
316
+ }
317
+ const runtime = bindings.elements.filter((element) => !element.isTypeOnly);
318
+ if (runtime.length === 0) {
319
+ return false;
320
+ }
321
+ return runtime.every((element) => FACTORY_NAMES.has((element.propertyName ?? element.name).text));
322
+ }
323
+ var lifecycleErasurePlugin = {
324
+ visitors: {
325
+ [ts2.SyntaxKind.ExpressionStatement]: (node, context) => eraseFactoryCall(node.expression, context) ?? context.superTransformStatements(node),
326
+ [ts2.SyntaxKind.ExportAssignment]: (node, context) => {
327
+ if (!node.isExportEquals) {
328
+ const erased = eraseFactoryCall(node.expression, context);
329
+ if (erased !== undefined) {
330
+ return erased;
331
+ }
300
332
  }
301
- const nested = resolveAnimationSet({
302
- path: key,
303
- prefix: `${prefix + basename(key)}/`,
304
- stack,
305
- assets,
306
- unresolved
307
- });
308
- if (nested === undefined)
309
- return;
310
- for (const [id, declaring] of nested)
311
- if (!ids.has(id))
312
- ids.set(id, declaring);
313
- }
314
- return ids;
315
- } finally {
316
- stack.delete(path);
333
+ return context.superTransformStatements(node);
334
+ },
335
+ [ts2.SyntaxKind.ImportDeclaration]: (node, context) => isFactoryOnlyImport(node) ? [] : context.superTransformStatements(node)
317
336
  }
337
+ };
338
+
339
+ // src/companion-closure.ts
340
+ var COMPANION_INTERNAL_FIELD = "____closure";
341
+ function isAssignmentOperator(kind) {
342
+ return kind >= ts3.SyntaxKind.FirstAssignment && kind <= ts3.SyntaxKind.LastAssignment;
318
343
  }
319
- function componentAnimationSources(object, displayPath, assets, unresolved) {
320
- const sources = new Map;
321
- function claimModel(id, animations) {
322
- if (!animations.endsWith(ANIMATION_SET_SUFFIX2)) {
323
- unresolved.push(`${displayPath}: the model component "${id}" names ${animations} rather than an animation set, so none of its animation ids are established`);
324
- return;
344
+ function isTypePosition(node) {
345
+ let current = node.parent;
346
+ while (current !== undefined && !ts3.isSourceFile(current)) {
347
+ if (ts3.isTypeNode(current)) {
348
+ return true;
325
349
  }
326
- sources.set(id, { kind: "model", path: resourceKey(animations) });
350
+ current = current.parent;
327
351
  }
328
- for (const embedded of childrenOf2(object, "embedded_components")) {
329
- const type = firstField(embedded, "type");
330
- if (type !== "sprite" && type !== "model")
331
- continue;
332
- const id = firstField(embedded, "id");
333
- const payload = firstField(embedded, "data");
334
- if (id === undefined || payload === undefined)
335
- continue;
336
- let document;
337
- try {
338
- document = parseSceneTextFormat(payload);
339
- } catch (error) {
340
- if (!(error instanceof SceneTextFormatError))
341
- throw error;
342
- unresolved.push(`${displayPath}: the embedded ${type} "${id}" could not be read (${error.message})`);
343
- continue;
344
- }
345
- if (type === "sprite") {
346
- const tileSet = firstField(document, "tile_set");
347
- if (tileSet !== undefined)
348
- sources.set(id, { kind: "sprite", path: resourceKey(tileSet) });
349
- continue;
350
- }
351
- const animations = firstField(document, "animations");
352
- if (animations !== undefined)
353
- claimModel(id, animations);
352
+ return false;
353
+ }
354
+ function isDeclarationName(node) {
355
+ const parent = node.parent;
356
+ if (parent === undefined) {
357
+ return false;
354
358
  }
355
- for (const referenced of childrenOf2(object, "components")) {
356
- const component = firstField(referenced, "component");
357
- const id = firstField(referenced, "id");
358
- if (id === undefined || component === undefined)
359
- continue;
360
- if (component.endsWith(SPRITE_SUFFIX2)) {
361
- const tileSet = assets.tileSetBySprite.get(resourceKey(component));
362
- if (tileSet === undefined) {
363
- unresolved.push(`${displayPath}: the sprite component "${id}" names ${component}, which is not among the project's sprite documents`);
364
- continue;
359
+ if (ts3.isPropertyAccessExpression(parent)) {
360
+ return parent.name === node;
361
+ }
362
+ if (ts3.isQualifiedName(parent)) {
363
+ return parent.right === node;
364
+ }
365
+ if ((ts3.isBindingElement(parent) || ts3.isImportSpecifier(parent) || ts3.isExportSpecifier(parent)) && parent.propertyName === node) {
366
+ return true;
367
+ }
368
+ const named = parent;
369
+ if (named.name !== node) {
370
+ return false;
371
+ }
372
+ return ts3.isVariableDeclaration(parent) || ts3.isFunctionDeclaration(parent) || ts3.isClassDeclaration(parent) || ts3.isEnumDeclaration(parent) || ts3.isEnumMember(parent) || ts3.isParameter(parent) || ts3.isBindingElement(parent) || ts3.isImportClause(parent) || ts3.isImportSpecifier(parent) || ts3.isNamespaceImport(parent) || ts3.isPropertyAssignment(parent) || ts3.isPropertyDeclaration(parent) || ts3.isPropertySignature(parent) || ts3.isMethodDeclaration(parent) || ts3.isMethodSignature(parent) || ts3.isTypeAliasDeclaration(parent) || ts3.isInterfaceDeclaration(parent) || ts3.isGetAccessor(parent) || ts3.isSetAccessor(parent);
373
+ }
374
+ function forEachReference(root, checker, visit) {
375
+ const walk = (node) => {
376
+ if (ts3.isIdentifier(node) && !isDeclarationName(node) && !isTypePosition(node)) {
377
+ const symbol = resolveReferencedSymbol(node, checker);
378
+ if (symbol !== undefined) {
379
+ visit(node, symbol);
365
380
  }
366
- sources.set(id, { kind: "sprite", path: tileSet });
367
- continue;
368
381
  }
369
- if (!component.endsWith(MODEL_SUFFIX2))
370
- continue;
371
- const resource = resourceKey(component);
372
- if (!assets.modelDocuments.has(resource)) {
373
- unresolved.push(`${displayPath}: the model component "${id}" names ${component}, which is not among the project's model documents`);
374
- continue;
375
- }
376
- const animations = assets.animationSourceByModel.get(resource);
377
- if (animations !== undefined)
378
- claimModel(id, animations);
379
- }
380
- return sources;
382
+ ts3.forEachChild(node, walk);
383
+ };
384
+ walk(root);
381
385
  }
382
- function scriptResourcesOf(object) {
383
- const resources = [];
384
- for (const component of childrenOf2(object, "components")) {
385
- const resource = firstField(component, "component");
386
- if (resource === undefined || !resource.endsWith(SCRIPT_SUFFIX))
387
- continue;
388
- const key = resourceKey(resource);
389
- if (key !== SCRIPT_SUFFIX.slice(1))
390
- resources.push(key);
386
+ function resolveReferencedSymbol(node, checker) {
387
+ const parent = node.parent;
388
+ if (parent !== undefined && ts3.isShorthandPropertyAssignment(parent) && parent.name === node) {
389
+ return checker.getShorthandAssignmentValueSymbol(parent) ?? checker.getSymbolAtLocation(node);
391
390
  }
392
- return resources;
393
- }
394
- function isGameObject(message) {
395
- return message.messages.has("components") || message.messages.has("embedded_components");
391
+ return checker.getSymbolAtLocation(node);
396
392
  }
397
- function buildComponentAnimationIndex(input) {
398
- const unresolved = [];
399
- const assets = readAssets(input.assets, unresolved);
400
- const byScriptResource = new Map;
401
- const sourceByScriptResource = new Map;
402
- const claimedBy = new Map;
403
- function walk(message, blockName, displayPath) {
404
- if (isGameObject(message)) {
405
- claim(message, displayPath);
393
+ function forEachAssignmentTarget(root, checker, visit) {
394
+ const classify = (target) => {
395
+ if (ts3.isParenthesizedExpression(target)) {
396
+ classify(target.expression);
397
+ return;
406
398
  }
407
- if (blockName === "embedded_instances") {
408
- for (const payload of message.fields.get("data") ?? []) {
409
- try {
410
- walk(parseSceneTextFormat(payload), "", displayPath);
411
- } catch (error) {
412
- if (!(error instanceof SceneTextFormatError))
413
- throw error;
414
- unresolved.push(`${displayPath}: an embedded game object could not be read (${error.message})`);
399
+ if (ts3.isPropertyAccessExpression(target) || ts3.isElementAccessExpression(target)) {
400
+ return;
401
+ }
402
+ if (ts3.isArrayLiteralExpression(target)) {
403
+ for (const element of target.elements) {
404
+ if (ts3.isOmittedExpression(element)) {
405
+ continue;
406
+ }
407
+ if (ts3.isSpreadElement(element)) {
408
+ classify(element.expression);
409
+ } else if (ts3.isBinaryExpression(element) && element.operatorToken.kind === ts3.SyntaxKind.EqualsToken) {
410
+ classify(element.left);
411
+ } else {
412
+ classify(element);
415
413
  }
416
414
  }
415
+ return;
417
416
  }
418
- for (const [name, nested] of message.messages) {
419
- for (const child of nested) {
420
- walk(child, name, displayPath);
417
+ if (ts3.isObjectLiteralExpression(target)) {
418
+ for (const property of target.properties) {
419
+ if (ts3.isPropertyAssignment(property)) {
420
+ classify(property.initializer);
421
+ } else if (ts3.isSpreadAssignment(property)) {
422
+ classify(property.expression);
423
+ } else if (ts3.isShorthandPropertyAssignment(property)) {
424
+ const symbol = checker.getShorthandAssignmentValueSymbol(property);
425
+ if (symbol !== undefined) {
426
+ visit(property.name, symbol);
427
+ }
428
+ }
421
429
  }
430
+ return;
422
431
  }
423
- }
424
- function idsOf(id, source, displayPath) {
425
- if (source.kind === "sprite") {
426
- const declared = assets.animationsByTileSet.get(source.path);
427
- if (declared !== undefined)
428
- return new Map([...declared].map((name) => [name, source.path]));
429
- unresolved.push(`${displayPath}: the sprite component "${id}" names the tile source /${source.path}, which is not among the project's asset documents`);
432
+ if (ts3.isIdentifier(target)) {
433
+ const symbol = checker.getSymbolAtLocation(target);
434
+ if (symbol !== undefined) {
435
+ visit(target, symbol);
436
+ }
437
+ }
438
+ };
439
+ const walk = (node) => {
440
+ if (ts3.isBinaryExpression(node) && isAssignmentOperator(node.operatorToken.kind)) {
441
+ classify(node.left);
442
+ } else if ((ts3.isPrefixUnaryExpression(node) || ts3.isPostfixUnaryExpression(node)) && (node.operator === ts3.SyntaxKind.PlusPlusToken || node.operator === ts3.SyntaxKind.MinusMinusToken)) {
443
+ classify(node.operand);
444
+ } else if ((ts3.isForInStatement(node) || ts3.isForOfStatement(node)) && !ts3.isVariableDeclarationList(node.initializer)) {
445
+ classify(node.initializer);
446
+ }
447
+ ts3.forEachChild(node, walk);
448
+ };
449
+ walk(root);
450
+ }
451
+ function collectTopLevel(sourceFile, checker) {
452
+ const declarations = new Map;
453
+ let order = 0;
454
+ const add = (name, statement, node) => {
455
+ if (name === undefined || !ts3.isIdentifier(name)) {
430
456
  return;
431
457
  }
432
- const resolved = resolveAnimationSet({
433
- path: source.path,
434
- prefix: "",
435
- stack: new Set,
436
- assets,
437
- unresolved
438
- });
439
- if (resolved !== undefined)
440
- return resolved;
441
- if (!assets.entriesByAnimationSet.has(source.path)) {
442
- unresolved.push(`${displayPath}: the model component "${id}" names the animation set /${source.path}, which is not among the project's asset documents`);
458
+ const symbol = checker.getSymbolAtLocation(name);
459
+ if (symbol === undefined || declarations.has(symbol)) {
460
+ return;
443
461
  }
444
- return;
445
- }
446
- function claim(object, displayPath) {
447
- const scripts = scriptResourcesOf(object);
448
- if (scripts.length === 0)
462
+ declarations.set(symbol, { name: name.text, statement, node, order: order++ });
463
+ };
464
+ const addBinding = (name, statement, node) => {
465
+ if (ts3.isIdentifier(name)) {
466
+ add(name, statement, node);
449
467
  return;
450
- const sources = componentAnimationSources(object, displayPath, assets, unresolved);
451
- const animations = new Map;
452
- const declaringSources = new Map;
453
- for (const [id, source] of sources) {
454
- const resolved = idsOf(id, source, displayPath);
455
- if (resolved === undefined)
456
- continue;
457
- animations.set(id, new Set(resolved.keys()));
458
- declaringSources.set(id, resolved);
459
468
  }
460
- for (const key of scripts) {
461
- const owner = claimedBy.get(key);
462
- if (owner !== undefined) {
463
- byScriptResource.delete(key);
464
- sourceByScriptResource.delete(key);
465
- unresolved.push(`${key}: claimed by both ${owner} and ${displayPath}, so its component animations are ambiguous`);
466
- continue;
469
+ for (const element of name.elements) {
470
+ if (ts3.isBindingElement(element)) {
471
+ addBinding(element.name, statement, node);
467
472
  }
468
- claimedBy.set(key, displayPath);
469
- byScriptResource.set(key, animations);
470
- sourceByScriptResource.set(key, declaringSources);
471
473
  }
472
- }
473
- for (const [path, text] of input.scenes) {
474
- const displayPath = normalizeDocumentKey(path);
475
- try {
476
- walk(parseSceneTextFormat(text), "", displayPath);
477
- } catch (error) {
478
- if (!(error instanceof SceneTextFormatError))
479
- throw error;
480
- unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
474
+ };
475
+ for (const statement of sourceFile.statements) {
476
+ if (ts3.isVariableStatement(statement)) {
477
+ for (const declaration of statement.declarationList.declarations) {
478
+ addBinding(declaration.name, statement, declaration);
479
+ }
480
+ } else if (ts3.isFunctionDeclaration(statement) || ts3.isClassDeclaration(statement) || ts3.isEnumDeclaration(statement)) {
481
+ add(statement.name, statement, statement);
482
+ } else if (ts3.isImportDeclaration(statement)) {
483
+ const clause = statement.importClause;
484
+ if (clause === undefined) {
485
+ continue;
486
+ }
487
+ add(clause.name, statement, statement);
488
+ const bindings = clause.namedBindings;
489
+ if (bindings === undefined) {
490
+ continue;
491
+ }
492
+ if (ts3.isNamespaceImport(bindings)) {
493
+ add(bindings.name, statement, statement);
494
+ } else {
495
+ for (const element of bindings.elements) {
496
+ add(element.name, statement, statement);
497
+ }
498
+ }
481
499
  }
482
500
  }
483
- return { byScriptResource, sourceByScriptResource, unresolved };
501
+ return declarations;
484
502
  }
485
- function componentIdOfSameObjectAddress(address) {
486
- if (!address.startsWith("#"))
487
- return;
488
- const id = address.slice(1);
489
- if (id === "" || id.includes("/") || id.includes("#"))
490
- return;
491
- return id;
503
+ function hasExportModifier(statement) {
504
+ return ts3.canHaveModifiers(statement) && (ts3.getModifiers(statement) ?? []).some((modifier) => modifier.kind === ts3.SyntaxKind.ExportKeyword);
492
505
  }
493
- // src/config-key-index.ts
494
- function sectionNameOf(line) {
495
- const match = line.trim().match(/^\[(.+)\]$/);
496
- return match?.[1];
506
+ function isValueSymbol(symbol, checker) {
507
+ if (symbol === undefined) {
508
+ return false;
509
+ }
510
+ const resolved = symbol.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
511
+ return (resolved.flags & ts3.SymbolFlags.Value) !== 0;
497
512
  }
498
- function buildConfigKeyIndex(gameProjectText) {
499
- const keys = new Set;
500
- let section;
501
- for (const line of gameProjectText.split(`
502
- `)) {
503
- const header = sectionNameOf(line);
504
- if (header !== undefined) {
505
- section = header;
506
- continue;
513
+ function collectExports(sourceFile, checker, topLevel) {
514
+ const exported = [];
515
+ const seen = new Set;
516
+ const take = (symbol, name, nameNode) => {
517
+ const declaration = symbol && topLevel.get(symbol);
518
+ if (symbol === undefined || declaration === undefined) {
519
+ return;
507
520
  }
508
- if (section === undefined)
509
- continue;
510
- const separator = line.indexOf("=");
511
- if (separator === -1)
521
+ if (!isValueSymbol(symbol, checker)) {
522
+ return;
523
+ }
524
+ const key = `${declaration.order}\x00${name}`;
525
+ if (seen.has(key)) {
526
+ return;
527
+ }
528
+ seen.add(key);
529
+ exported.push({ symbol, name, nameNode });
530
+ };
531
+ const takeLocal = (symbol, name) => {
532
+ const declaration = symbol && topLevel.get(symbol);
533
+ if (declaration === undefined) {
534
+ return;
535
+ }
536
+ take(symbol, name.text, declaration.node);
537
+ };
538
+ for (const statement of sourceFile.statements) {
539
+ if (ts3.isExportDeclaration(statement)) {
540
+ if (statement.moduleSpecifier !== undefined || statement.isTypeOnly) {
541
+ continue;
542
+ }
543
+ const clause = statement.exportClause;
544
+ if (clause === undefined || !ts3.isNamedExports(clause)) {
545
+ continue;
546
+ }
547
+ for (const element of clause.elements) {
548
+ if (element.isTypeOnly) {
549
+ continue;
550
+ }
551
+ take(checker.getExportSpecifierLocalTargetSymbol(element), element.name.text, element.name);
552
+ }
512
553
  continue;
513
- const key = line.slice(0, separator).trim();
514
- if (key === "")
554
+ }
555
+ if (!hasExportModifier(statement)) {
515
556
  continue;
516
- keys.add(`${section}.${key}`);
557
+ }
558
+ if (ts3.isVariableStatement(statement)) {
559
+ for (const declaration of statement.declarationList.declarations) {
560
+ forEachDeclaredName(declaration.name, (name) => takeLocal(checker.getSymbolAtLocation(name), name));
561
+ }
562
+ } else if (ts3.isFunctionDeclaration(statement) || ts3.isClassDeclaration(statement) || ts3.isEnumDeclaration(statement)) {
563
+ if (statement.name !== undefined) {
564
+ takeLocal(checker.getSymbolAtLocation(statement.name), statement.name);
565
+ }
566
+ }
517
567
  }
518
- return keys;
568
+ return exported;
519
569
  }
520
- function readGameProjectSetting(gameProjectText, section, key) {
521
- let current;
522
- for (const line of gameProjectText.split(`
523
- `)) {
524
- const header = sectionNameOf(line);
525
- if (header !== undefined) {
526
- current = header;
527
- continue;
570
+ function forEachDeclaredName(name, visit) {
571
+ if (ts3.isIdentifier(name)) {
572
+ visit(name);
573
+ return;
574
+ }
575
+ for (const element of name.elements) {
576
+ if (ts3.isBindingElement(element)) {
577
+ forEachDeclaredName(element.name, visit);
528
578
  }
529
- if (current !== section)
530
- continue;
531
- const separator = line.indexOf("=");
532
- if (separator === -1)
533
- continue;
534
- if (line.slice(0, separator).trim() !== key)
535
- continue;
536
- const value = line.slice(separator + 1).trim();
537
- if (value !== "")
538
- return value;
539
579
  }
540
- return;
541
580
  }
542
- // src/gui-flipbook-index.ts
543
- var SCRIPT_SUFFIX2 = ".gui_script";
544
- function scriptKeyOf(script) {
545
- if (!script.endsWith(SCRIPT_SUFFIX2))
546
- return;
547
- const key = resourceKey(script);
548
- return key === SCRIPT_SUFFIX2 ? undefined : key;
581
+ function isLifecycleFactoryCall(expression, checker) {
582
+ return ts3.isCallExpression(expression) && resolvesToFactoryExport(expression.expression, checker);
549
583
  }
550
- function buildGuiFlipbookIndex(input) {
551
- const unresolved = [];
552
- const animationsBySource = readAnimationSources(input.assets, unresolved);
553
- const byScriptResource = new Map;
554
- const claimedBy = new Map;
555
- for (const [path, text] of input.scenes) {
556
- const displayPath = normalizeDocumentKey(path);
557
- let document;
558
- try {
559
- document = parseSceneTextFormat(text);
560
- } catch (error) {
561
- if (!(error instanceof SceneTextFormatError))
562
- throw error;
563
- unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
584
+ function isEffectFreeExpression(expression) {
585
+ if (expression === undefined) {
586
+ return true;
587
+ }
588
+ if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
589
+ return isEffectFreeExpression(expression.expression);
590
+ }
591
+ if (ts3.isIdentifier(expression) || ts3.isLiteralExpression(expression)) {
592
+ return true;
593
+ }
594
+ switch (expression.kind) {
595
+ case ts3.SyntaxKind.TrueKeyword:
596
+ case ts3.SyntaxKind.FalseKeyword:
597
+ case ts3.SyntaxKind.NullKeyword:
598
+ case ts3.SyntaxKind.ThisKeyword:
599
+ return true;
600
+ default:
601
+ break;
602
+ }
603
+ if (ts3.isFunctionExpression(expression) || ts3.isArrowFunction(expression)) {
604
+ return true;
605
+ }
606
+ if (ts3.isPrefixUnaryExpression(expression)) {
607
+ return expression.operator !== ts3.SyntaxKind.PlusPlusToken && expression.operator !== ts3.SyntaxKind.MinusMinusToken && isEffectFreeExpression(expression.operand);
608
+ }
609
+ if (ts3.isBinaryExpression(expression)) {
610
+ return !isAssignmentOperator(expression.operatorToken.kind) && isEffectFreeExpression(expression.left) && isEffectFreeExpression(expression.right);
611
+ }
612
+ if (ts3.isConditionalExpression(expression)) {
613
+ return isEffectFreeExpression(expression.condition) && isEffectFreeExpression(expression.whenTrue) && isEffectFreeExpression(expression.whenFalse);
614
+ }
615
+ if (ts3.isArrayLiteralExpression(expression)) {
616
+ return expression.elements.every(isEffectFreeExpression);
617
+ }
618
+ if (ts3.isObjectLiteralExpression(expression)) {
619
+ return expression.properties.every((property) => {
620
+ if (ts3.isPropertyAssignment(property)) {
621
+ return isEffectFreeExpression(property.initializer);
622
+ }
623
+ return ts3.isShorthandPropertyAssignment(property) || ts3.isMethodDeclaration(property);
624
+ });
625
+ }
626
+ return false;
627
+ }
628
+ function isEffectFreeStatement(statement, checker) {
629
+ if (ts3.isImportDeclaration(statement) || ts3.isInterfaceDeclaration(statement) || ts3.isTypeAliasDeclaration(statement) || ts3.isFunctionDeclaration(statement) || ts3.isExportDeclaration(statement)) {
630
+ return true;
631
+ }
632
+ if (ts3.isVariableStatement(statement)) {
633
+ return statement.declarationList.declarations.every((declaration) => isEffectFreeExpression(declaration.initializer));
634
+ }
635
+ if (ts3.isExpressionStatement(statement)) {
636
+ return isLifecycleFactoryCall(statement.expression, checker) || isEffectFreeExpression(statement.expression);
637
+ }
638
+ if (ts3.isExportAssignment(statement) && !statement.isExportEquals) {
639
+ return isLifecycleFactoryCall(statement.expression, checker) || isEffectFreeExpression(statement.expression);
640
+ }
641
+ return false;
642
+ }
643
+ function siteOf(node, role) {
644
+ const sourceFile = node.getSourceFile();
645
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
646
+ return { role, line: line + 1, column: character + 1 };
647
+ }
648
+ function formatSites(sites) {
649
+ return sites.map((site) => `${site.role} at ${site.line}:${site.column}`).join(", ");
650
+ }
651
+ function bySourcePosition(a, b) {
652
+ return a.line === b.line ? a.column - b.column : a.line - b.line;
653
+ }
654
+ function computeCompanionClosure(sourceFile, checker) {
655
+ const topLevel = collectTopLevel(sourceFile, checker);
656
+ const exportEntries = collectExports(sourceFile, checker, topLevel);
657
+ const siblingsOf = new Map;
658
+ for (const [symbol, declaration] of topLevel) {
659
+ siblingsOf.set(declaration.statement, [
660
+ ...siblingsOf.get(declaration.statement) ?? [],
661
+ symbol
662
+ ]);
663
+ }
664
+ const memberSymbols = new Set;
665
+ const importBindingSymbols = new Set;
666
+ const closureStatements = new Set;
667
+ const queue = exportEntries.map((entry) => entry.symbol);
668
+ while (queue.length > 0) {
669
+ const symbol = queue.pop();
670
+ if (symbol === undefined || memberSymbols.has(symbol)) {
564
671
  continue;
565
672
  }
566
- const [script] = document.fields.get("script") ?? [];
567
- if (script === undefined)
568
- continue;
569
- const key = scriptKeyOf(script);
570
- if (key === undefined)
673
+ memberSymbols.add(symbol);
674
+ const declaration = topLevel.get(symbol);
675
+ if (declaration === undefined) {
571
676
  continue;
572
- const owner = claimedBy.get(key);
573
- if (owner !== undefined) {
574
- byScriptResource.delete(key);
575
- unresolved.push(`${key}: claimed by both ${owner} and ${displayPath}, so its flipbook animations are ambiguous`);
677
+ }
678
+ if (ts3.isImportDeclaration(declaration.statement)) {
679
+ importBindingSymbols.add(symbol);
576
680
  continue;
577
681
  }
578
- claimedBy.set(key, displayPath);
579
- const declaredBy = new Map;
580
- for (const message of document.messages.get("textures") ?? []) {
581
- for (const texture of message.fields.get("texture") ?? []) {
582
- if (texture === "")
583
- continue;
584
- const source = resourceKey(texture);
585
- const ids = animationsBySource.get(source);
586
- if (ids === undefined) {
587
- unresolved.push(`${displayPath}: the texture ${texture} is not among the project's asset documents`);
588
- continue;
589
- }
590
- for (const id of ids) {
591
- const declarers = declaredBy.get(id);
592
- if (declarers) {
593
- declarers.add(source);
594
- } else {
595
- declaredBy.set(id, new Set([source]));
596
- }
682
+ if (!closureStatements.has(declaration.statement)) {
683
+ closureStatements.add(declaration.statement);
684
+ for (const sibling of siblingsOf.get(declaration.statement) ?? []) {
685
+ if (!memberSymbols.has(sibling)) {
686
+ queue.push(sibling);
597
687
  }
598
688
  }
599
689
  }
600
- byScriptResource.set(key, declaredBy);
690
+ forEachReference(declaration.node, checker, (_node, referenced) => {
691
+ if (topLevel.has(referenced) && !memberSymbols.has(referenced)) {
692
+ queue.push(referenced);
693
+ }
694
+ });
601
695
  }
602
- return { byScriptResource, unresolved };
603
- }
604
- // src/gui-node-index.ts
605
- var SCRIPT_SUFFIX3 = ".gui_script";
606
- function scriptKeyOf2(script) {
607
- if (!script.endsWith(SCRIPT_SUFFIX3))
608
- return;
609
- const key = script.startsWith("/") ? script.slice(1) : script;
610
- return key === SCRIPT_SUFFIX3 ? undefined : key;
611
- }
612
- function guiScriptResourceOf(document) {
613
- const [script] = document.fields.get("script") ?? [];
614
- return script === undefined ? undefined : scriptKeyOf2(script);
615
- }
616
- function guiScriptResourcesOf(documents) {
617
- const scripts = new Map;
618
- for (const [displayPath, text] of documents) {
619
- let resource;
620
- try {
621
- resource = guiScriptResourceOf(parseSceneTextFormat(text));
622
- } catch (error) {
623
- if (!(error instanceof SceneTextFormatError))
624
- throw error;
696
+ const splits = closureStatements.size > 0;
697
+ for (const statement of sourceFile.statements) {
698
+ if (!ts3.isExportDeclaration(statement) || statement.isTypeOnly) {
625
699
  continue;
626
700
  }
627
- if (resource !== undefined)
628
- scripts.set(displayPath, resource);
701
+ if (statement.moduleSpecifier !== undefined) {
702
+ if (splits) {
703
+ closureStatements.add(statement);
704
+ }
705
+ continue;
706
+ }
707
+ const clause = statement.exportClause;
708
+ if (clause === undefined || !ts3.isNamedExports(clause)) {
709
+ continue;
710
+ }
711
+ const readsMember = clause.elements.some((element) => {
712
+ if (element.isTypeOnly) {
713
+ return false;
714
+ }
715
+ const local = checker.getExportSpecifierLocalTargetSymbol(element);
716
+ return local !== undefined && memberSymbols.has(local);
717
+ });
718
+ if (readsMember) {
719
+ closureStatements.add(statement);
720
+ }
629
721
  }
630
- return scripts;
631
- }
632
- function buildGuiNodeIndex(documents) {
633
- const byScriptResource = new Map;
634
- const claimedBy = new Map;
635
- const unresolved = [];
636
- for (const [displayPath, text] of documents) {
637
- let document;
638
- try {
639
- document = parseSceneTextFormat(text);
640
- } catch (error) {
641
- if (!(error instanceof SceneTextFormatError))
642
- throw error;
643
- unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
722
+ const mutations = new Map;
723
+ forEachAssignmentTarget(sourceFile, checker, (node, symbol) => {
724
+ if (!memberSymbols.has(symbol)) {
725
+ return;
726
+ }
727
+ mutations.set(symbol, [...mutations.get(symbol) ?? [], node]);
728
+ });
729
+ const scriptReferences = new Map;
730
+ for (const statement of sourceFile.statements) {
731
+ if (closureStatements.has(statement) || ts3.isExportDeclaration(statement)) {
644
732
  continue;
645
733
  }
646
- const key = guiScriptResourceOf(document);
647
- if (key === undefined)
734
+ forEachReference(statement, checker, (node, symbol) => {
735
+ if (memberSymbols.has(symbol)) {
736
+ scriptReferences.set(symbol, [...scriptReferences.get(symbol) ?? [], node]);
737
+ }
738
+ });
739
+ }
740
+ const ordered = [...memberSymbols].flatMap((symbol) => {
741
+ const declaration = topLevel.get(symbol);
742
+ return declaration === undefined ? [] : [{ symbol, declaration }];
743
+ }).sort((a, b) => a.declaration.order - b.declaration.order);
744
+ const exportSet = new Set(exportEntries.map((entry) => entry.symbol));
745
+ const violations = [];
746
+ for (const { symbol, declaration } of ordered) {
747
+ const mutated = mutations.get(symbol);
748
+ const referenced = scriptReferences.get(symbol);
749
+ if (mutated === undefined || referenced === undefined) {
750
+ continue;
751
+ }
752
+ const sites = [
753
+ ...mutated.map((node) => siteOf(node, "reassigned")),
754
+ ...referenced.map((node) => siteOf(node, "read by the script"))
755
+ ].filter((site, index, all) => all.findIndex((other) => other.line === site.line && other.column === site.column) === index).sort(bySourcePosition);
756
+ violations.push({
757
+ kind: "shared-mutable",
758
+ member: declaration.name,
759
+ sites,
760
+ message: `"${declaration.name}" is reassigned and is also used outside the companion, ` + `so the script's copy would never see the update (${formatSites(sites)}). ` + "Move that state into a source with no lifecycle factory."
761
+ });
762
+ }
763
+ for (const entry of exportEntries) {
764
+ if (entry.name !== COMPANION_INTERNAL_FIELD) {
648
765
  continue;
649
- const owner = claimedBy.get(key);
650
- if (owner !== undefined) {
651
- byScriptResource.delete(key);
652
- unresolved.push(`${key}: claimed by both ${owner} and ${displayPath}, so its node ids are ambiguous`);
766
+ }
767
+ const sites = [siteOf(entry.nameNode, "exported")];
768
+ violations.push({
769
+ kind: "reserved-internal-name",
770
+ member: entry.name,
771
+ sites,
772
+ message: `"${COMPANION_INTERNAL_FIELD}" is reserved for the companion's own internals ` + `and cannot be exported (${formatSites(sites)}). Rename the export.`
773
+ });
774
+ }
775
+ for (const statement of sourceFile.statements) {
776
+ if (closureStatements.has(statement) || isEffectFreeStatement(statement, checker)) {
653
777
  continue;
654
778
  }
655
- claimedBy.set(key, displayPath);
656
- const ids = new Set;
657
- for (const message of document.messages.get("nodes") ?? []) {
658
- for (const id of message.fields.get("id") ?? []) {
659
- if (id !== "")
660
- ids.add(id);
779
+ const start = statement.getStart(sourceFile);
780
+ const hoisted = ordered.find((entry) => entry.declaration.statement.getStart(sourceFile) > start);
781
+ if (hoisted === undefined) {
782
+ continue;
783
+ }
784
+ const sites = [
785
+ siteOf(statement, "runs in the script"),
786
+ siteOf(hoisted.declaration.node, "moves into the companion")
787
+ ];
788
+ violations.push({
789
+ kind: "initialization-order",
790
+ member: hoisted.declaration.name,
791
+ sites,
792
+ message: `top-level work precedes "${hoisted.declaration.name}", which the companion would run first, ` + `reversing their order (${formatSites(sites)}). ` + "Move that work below the companion's declarations, or into a lifecycle hook."
793
+ });
794
+ }
795
+ return {
796
+ members: ordered.map(({ declaration }) => declaration.name),
797
+ exports: exportEntries.map((entry) => entry.name),
798
+ statements: sourceFile.statements.filter((statement) => closureStatements.has(statement)),
799
+ importBindings: ordered.filter(({ symbol }) => importBindingSymbols.has(symbol)).map(({ declaration }) => declaration.name),
800
+ internals: ordered.filter(({ symbol }) => !exportSet.has(symbol) && scriptReferences.has(symbol)).map(({ declaration }) => declaration.name),
801
+ violations
802
+ };
803
+ }
804
+ // src/scene-text-format.ts
805
+ class SceneTextFormatError extends Error {
806
+ line;
807
+ constructor(message, line) {
808
+ super(`${message} (line ${line})`);
809
+ this.name = "SceneTextFormatError";
810
+ this.line = line;
811
+ }
812
+ }
813
+ var IDENTIFIER_PART = /[A-Za-z0-9_]/;
814
+ function append(target, key, value) {
815
+ const existing = target.get(key);
816
+ if (existing === undefined) {
817
+ target.set(key, [value]);
818
+ return;
819
+ }
820
+ existing.push(value);
821
+ }
822
+ function parseSceneTextFormat(text) {
823
+ let pos = 0;
824
+ let line = 1;
825
+ function fail(message, at) {
826
+ throw new SceneTextFormatError(message, at);
827
+ }
828
+ function advance() {
829
+ const ch = text.charAt(pos);
830
+ pos += 1;
831
+ if (ch === `
832
+ `)
833
+ line += 1;
834
+ return ch;
835
+ }
836
+ function skipWhitespace() {
837
+ while (pos < text.length) {
838
+ const ch = text.charAt(pos);
839
+ if (ch !== " " && ch !== "\t" && ch !== "\r" && ch !== `
840
+ `)
841
+ return;
842
+ advance();
843
+ }
844
+ }
845
+ function skipInlineWhitespace() {
846
+ while (pos < text.length) {
847
+ const ch = text.charAt(pos);
848
+ if (ch !== " " && ch !== "\t")
849
+ return;
850
+ pos += 1;
851
+ }
852
+ }
853
+ function readIdentifier(at) {
854
+ const start = pos;
855
+ while (pos < text.length && IDENTIFIER_PART.test(text.charAt(pos))) {
856
+ pos += 1;
857
+ }
858
+ if (pos === start) {
859
+ fail(`unexpected \`${text.charAt(start)}\``, at);
860
+ }
861
+ return text.slice(start, pos);
862
+ }
863
+ function readQuotedScalar() {
864
+ let value = "";
865
+ for (;; ) {
866
+ const openedAt = line;
867
+ advance();
868
+ let closed = false;
869
+ while (pos < text.length) {
870
+ const ch = advance();
871
+ if (ch === "\\") {
872
+ if (pos >= text.length)
873
+ break;
874
+ const escaped = advance();
875
+ if (escaped === '"')
876
+ value += '"';
877
+ else if (escaped === "\\")
878
+ value += "\\";
879
+ else if (escaped === "n")
880
+ value += `
881
+ `;
882
+ else if (escaped === "r")
883
+ value += "\r";
884
+ else if (escaped === "t")
885
+ value += "\t";
886
+ else
887
+ value += `\\${escaped}`;
888
+ continue;
889
+ }
890
+ if (ch === '"') {
891
+ closed = true;
892
+ break;
893
+ }
894
+ if (ch === `
895
+ `)
896
+ break;
897
+ value += ch;
661
898
  }
899
+ if (!closed) {
900
+ fail("unterminated string literal", openedAt);
901
+ }
902
+ const resumePos = pos;
903
+ const resumeLine = line;
904
+ skipWhitespace();
905
+ if (text.charAt(pos) === '"') {
906
+ continue;
907
+ }
908
+ pos = resumePos;
909
+ line = resumeLine;
910
+ return value;
911
+ }
912
+ }
913
+ function readUnquotedScalar() {
914
+ const newline = text.indexOf(`
915
+ `, pos);
916
+ const end = newline === -1 ? text.length : newline;
917
+ const raw = text.slice(pos, end);
918
+ pos = end;
919
+ return raw.trim();
920
+ }
921
+ function parseBody(nested, openedAt) {
922
+ const fields = new Map;
923
+ const messages = new Map;
924
+ for (;; ) {
925
+ skipWhitespace();
926
+ if (pos >= text.length) {
927
+ if (nested) {
928
+ fail("unclosed `{`", openedAt);
929
+ }
930
+ break;
931
+ }
932
+ if (text.charAt(pos) === "}") {
933
+ if (!nested) {
934
+ fail("unexpected `}`", line);
935
+ }
936
+ advance();
937
+ break;
938
+ }
939
+ const keyLine = line;
940
+ const key = readIdentifier(keyLine);
941
+ skipInlineWhitespace();
942
+ const delimiter = text.charAt(pos);
943
+ if (delimiter === "{") {
944
+ const braceLine = line;
945
+ advance();
946
+ append(messages, key, parseBody(true, braceLine));
947
+ continue;
948
+ }
949
+ if (delimiter === ":") {
950
+ advance();
951
+ skipInlineWhitespace();
952
+ append(fields, key, text.charAt(pos) === '"' ? readQuotedScalar() : readUnquotedScalar());
953
+ continue;
954
+ }
955
+ fail(`expected \`:\` or \`{\` after \`${key}\``, keyLine);
956
+ }
957
+ return { fields, messages };
958
+ }
959
+ return parseBody(false, 1);
960
+ }
961
+
962
+ // src/animation-sources.ts
963
+ function resourceKey(resource) {
964
+ return resource.startsWith("/") ? resource.slice(1) : resource;
965
+ }
966
+ function normalizeDocumentKey(path) {
967
+ return path.replaceAll("\\", "/");
968
+ }
969
+ var SPRITE_SUFFIX = ".sprite";
970
+ var MODEL_SUFFIX = ".model";
971
+ var ANIMATION_SET_SUFFIX = ".animationset";
972
+ var HOP_ONLY_SUFFIXES = [SPRITE_SUFFIX, MODEL_SUFFIX, ANIMATION_SET_SUFFIX];
973
+ function childrenOf(message, name) {
974
+ return message.messages.get(name) ?? [];
975
+ }
976
+ function declaredAnimations(document) {
977
+ const ids = new Set;
978
+ for (const animation of childrenOf(document, "animations")) {
979
+ for (const id of animation.fields.get("id") ?? []) {
980
+ if (id !== "")
981
+ ids.add(id);
662
982
  }
663
- byScriptResource.set(key, ids);
664
983
  }
665
- return { byScriptResource, unresolved };
984
+ return ids;
666
985
  }
667
- // src/input-action-index.ts
668
- function buildInputActionIndex(documents) {
669
- const actions = new Set;
670
- for (const text of documents.values()) {
986
+ function readAnimationSources(assets, unresolved) {
987
+ const animationsByTileSet = new Map;
988
+ for (const [path, text] of assets) {
989
+ const displayPath = normalizeDocumentKey(path);
990
+ if (HOP_ONLY_SUFFIXES.some((suffix) => displayPath.endsWith(suffix)))
991
+ continue;
671
992
  let document;
672
993
  try {
673
994
  document = parseSceneTextFormat(text);
674
995
  } catch (error) {
675
996
  if (!(error instanceof SceneTextFormatError))
676
997
  throw error;
998
+ unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
677
999
  continue;
678
1000
  }
679
- for (const triggers of document.messages.values()) {
680
- for (const trigger of triggers) {
681
- for (const action of trigger.fields.get("action") ?? []) {
682
- if (action !== "")
683
- actions.add(action);
684
- }
685
- }
686
- }
1001
+ animationsByTileSet.set(displayPath, declaredAnimations(document));
687
1002
  }
688
- return actions;
1003
+ return animationsByTileSet;
689
1004
  }
690
- // src/project-resources.ts
691
- var SCAFFOLDED_DEFIGNORE_LINES = ["/node_modules", "/.defold-types", "/.vscode"];
692
- function isDefignoredPath(displayPath) {
693
- const rooted = `/${displayPath.replace(/^\/+/, "")}`;
694
- return SCAFFOLDED_DEFIGNORE_LINES.some((line) => rooted === line || rooted.startsWith(`${line}/`));
1005
+ function animationSetEntries(document) {
1006
+ const entries = [];
1007
+ for (const entry of childrenOf(document, "animations")) {
1008
+ for (const animation of entry.fields.get("animation") ?? []) {
1009
+ if (animation !== "")
1010
+ entries.push(animation);
1011
+ }
1012
+ }
1013
+ return entries;
695
1014
  }
696
1015
 
697
- // src/scene-documents.ts
698
- var SCENE_EXTENSIONS = [".go", ".collection"];
699
- var GUI_EXTENSIONS = [".gui"];
700
- var INPUT_BINDING_EXTENSIONS = [".input_binding"];
701
- var COLLECTION_REFERENCE_EXTENSIONS = [".collectionproxy", ".collectionfactory"];
702
- var ANIMATION_ASSET_EXTENSIONS = [
703
- ".atlas",
704
- ".tilesource",
705
- ".sprite",
706
- ".model",
707
- ".animationset"
708
- ];
709
- var LIBRARY_SCENE_EXTENSIONS = [
710
- ...SCENE_EXTENSIONS,
711
- ...GUI_EXTENSIONS,
712
- ...ANIMATION_ASSET_EXTENSIONS,
713
- ...COLLECTION_REFERENCE_EXTENSIONS
714
- ];
715
- var PROJECT_EXTENSIONS = [".project"];
716
- var GAME_PROJECT_DOCUMENT = "game.project";
717
- function displayPathOf(projectRoot, filePath) {
718
- const path = filePath.replace(/\\/g, "/");
719
- const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
720
- return path.startsWith(`${root}/`) ? path.slice(root.length + 1) : path;
1016
+ // src/component-animation-index.ts
1017
+ var SCRIPT_SUFFIX = ".script";
1018
+ var SPRITE_SUFFIX2 = ".sprite";
1019
+ var MODEL_SUFFIX2 = ".model";
1020
+ var ANIMATION_SET_SUFFIX2 = ".animationset";
1021
+ var MESH_SUFFIXES = [".gltf", ".glb"];
1022
+ function firstField(message, name) {
1023
+ return (message.fields.get(name) ?? [])[0];
721
1024
  }
722
- function isBuildOutput(displayPath) {
723
- return displayPath.split("/").includes("build");
1025
+ function childrenOf2(message, name) {
1026
+ return message.messages.get(name) ?? [];
724
1027
  }
725
- function isExcludedProjectPath(displayPath) {
726
- return isBuildOutput(displayPath) || isDefignoredPath(displayPath);
1028
+ function basename(resource) {
1029
+ const file = resource.slice(resource.lastIndexOf("/") + 1);
1030
+ const dot = file.lastIndexOf(".");
1031
+ return dot === -1 ? file : file.slice(0, dot);
727
1032
  }
728
- function readSceneDocuments(host, projectRoot, extensions = SCENE_EXTENSIONS) {
729
- const documents = new Map;
730
- const origins = new Map;
731
- const unreadable = [];
732
- const paths = [];
733
- if (!host.readDirectory) {
734
- unreadable.push("the editor host cannot enumerate project files, so no scene source was read");
735
- return { documents, origins, unreadable, paths };
736
- }
737
- for (const filePath of host.readDirectory(projectRoot, extensions)) {
738
- const displayPath = displayPathOf(projectRoot, filePath);
739
- if (isExcludedProjectPath(displayPath))
1033
+ function readAssets(assets, unresolved) {
1034
+ const animationsByTileSet = readAnimationSources(assets, unresolved);
1035
+ const tileSetBySprite = new Map;
1036
+ const modelDocuments = new Set;
1037
+ const animationSourceByModel = new Map;
1038
+ const entriesByAnimationSet = new Map;
1039
+ for (const [path, text] of assets) {
1040
+ const displayPath = normalizeDocumentKey(path);
1041
+ const isSprite = displayPath.endsWith(SPRITE_SUFFIX2);
1042
+ const isModel = displayPath.endsWith(MODEL_SUFFIX2);
1043
+ const isAnimationSet = displayPath.endsWith(ANIMATION_SET_SUFFIX2);
1044
+ if (!isSprite && !isModel && !isAnimationSet)
740
1045
  continue;
741
- const text = host.readFile(filePath);
742
- if (text === undefined) {
743
- unreadable.push(`${displayPath}: could not be read`);
1046
+ let document;
1047
+ try {
1048
+ document = parseSceneTextFormat(text);
1049
+ } catch (error) {
1050
+ if (!(error instanceof SceneTextFormatError))
1051
+ throw error;
1052
+ unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
744
1053
  continue;
745
1054
  }
746
- documents.set(displayPath, text);
747
- paths.push(filePath);
748
- }
749
- const library = readLibrarySceneDocuments(host, projectRoot, extensions);
750
- unreadable.push(...library.unreadable);
751
- for (const { displayPath, hostPath, url, text } of library.documents) {
752
- if (documents.has(displayPath)) {
753
- unreadable.push(`${displayPath}: also declared by ${url}; the project's own file is used`);
1055
+ if (isSprite) {
1056
+ const tileSet = firstField(document, "tile_set");
1057
+ if (tileSet !== undefined)
1058
+ tileSetBySprite.set(displayPath, resourceKey(tileSet));
754
1059
  continue;
755
1060
  }
756
- documents.set(displayPath, text);
757
- origins.set(displayPath, url);
758
- paths.push(hostPath);
759
- }
760
- return { documents, origins, unreadable, paths };
761
- }
762
- function listProjectResourcePaths(host, projectRoot, extensions) {
763
- if (!host.readDirectory)
764
- return new Set;
765
- const paths = [];
766
- for (const filePath of host.readDirectory(projectRoot, extensions)) {
767
- const displayPath = displayPathOf(projectRoot, filePath);
768
- if (isExcludedProjectPath(displayPath))
769
- continue;
770
- if (!extensions.some((extension) => displayPath.endsWith(extension)))
1061
+ if (isModel) {
1062
+ modelDocuments.add(displayPath);
1063
+ const animations = firstField(document, "animations");
1064
+ if (animations !== undefined)
1065
+ animationSourceByModel.set(displayPath, animations);
771
1066
  continue;
772
- paths.push(`/${displayPath}`);
1067
+ }
1068
+ entriesByAnimationSet.set(displayPath, animationSetEntries(document));
773
1069
  }
774
- return new Set(paths.sort());
775
- }
776
-
777
- // src/library-dependencies.ts
778
- function isProjectHeader(line) {
779
- return line.trim() === "[project]";
780
- }
781
- function isSectionHeader(line) {
782
- return /^\[.+\]\s*$/.test(line.trim());
1070
+ return {
1071
+ animationsByTileSet,
1072
+ tileSetBySprite,
1073
+ modelDocuments,
1074
+ animationSourceByModel,
1075
+ entriesByAnimationSet
1076
+ };
783
1077
  }
784
- function readGameProjectDependencies(gameProjectText) {
785
- const lines = gameProjectText.split(`
786
- `);
787
- const deps = [];
788
- let inProject = false;
789
- for (const line of lines) {
790
- if (isSectionHeader(line)) {
791
- inProject = isProjectHeader(line);
792
- continue;
793
- }
794
- if (!inProject) {
795
- continue;
796
- }
797
- const match = line.match(/^dependencies#(\d+)\s*=\s*(.+)$/);
798
- if (match?.[1] !== undefined && match[2] !== undefined) {
799
- deps.push({ index: Number(match[1]), url: match[2] });
1078
+ function resolveAnimationSet(input) {
1079
+ const { path, prefix, stack, assets, unresolved } = input;
1080
+ const entries = assets.entriesByAnimationSet.get(path);
1081
+ if (entries === undefined)
1082
+ return;
1083
+ stack.add(path);
1084
+ try {
1085
+ const ids = new Map;
1086
+ for (const entry of entries) {
1087
+ const key = resourceKey(entry);
1088
+ if (MESH_SUFFIXES.some((suffix) => key.endsWith(suffix))) {
1089
+ ids.set(prefix + basename(key), path);
1090
+ continue;
1091
+ }
1092
+ if (!key.endsWith(ANIMATION_SET_SUFFIX2)) {
1093
+ unresolved.push(`${path}: the entry ${entry} is not a .gltf, .glb or .animationset, so it declares no animation id`);
1094
+ continue;
1095
+ }
1096
+ if (!assets.entriesByAnimationSet.has(key)) {
1097
+ unresolved.push(`${path}: the entry ${entry} is not among the project's animation set documents`);
1098
+ return;
1099
+ }
1100
+ if (stack.has(key)) {
1101
+ unresolved.push(`${path}: the entry ${entry} re-enters an animation set already being read, so the chain is cyclic`);
1102
+ return;
1103
+ }
1104
+ const nested = resolveAnimationSet({
1105
+ path: key,
1106
+ prefix: `${prefix + basename(key)}/`,
1107
+ stack,
1108
+ assets,
1109
+ unresolved
1110
+ });
1111
+ if (nested === undefined)
1112
+ return;
1113
+ for (const [id, declaring] of nested)
1114
+ if (!ids.has(id))
1115
+ ids.set(id, declaring);
800
1116
  }
1117
+ return ids;
1118
+ } finally {
1119
+ stack.delete(path);
801
1120
  }
802
- return deps;
803
1121
  }
804
- function archiveWrapperOf(entries) {
805
- let wrapper;
806
- for (const entry of entries) {
807
- const separator = entry.indexOf("/");
808
- if (separator <= 0) {
1122
+ function componentAnimationSources(object, displayPath, assets, unresolved) {
1123
+ const sources = new Map;
1124
+ function claimModel(id, animations) {
1125
+ if (!animations.endsWith(ANIMATION_SET_SUFFIX2)) {
1126
+ unresolved.push(`${displayPath}: the model component "${id}" names ${animations} rather than an animation set, so none of its animation ids are established`);
809
1127
  return;
810
1128
  }
811
- const segment = entry.slice(0, separator);
812
- if (wrapper === undefined) {
813
- wrapper = segment;
814
- } else if (wrapper !== segment) {
815
- return;
1129
+ sources.set(id, { kind: "model", path: resourceKey(animations) });
1130
+ }
1131
+ for (const embedded of childrenOf2(object, "embedded_components")) {
1132
+ const type = firstField(embedded, "type");
1133
+ if (type !== "sprite" && type !== "model")
1134
+ continue;
1135
+ const id = firstField(embedded, "id");
1136
+ const payload = firstField(embedded, "data");
1137
+ if (id === undefined || payload === undefined)
1138
+ continue;
1139
+ let document;
1140
+ try {
1141
+ document = parseSceneTextFormat(payload);
1142
+ } catch (error) {
1143
+ if (!(error instanceof SceneTextFormatError))
1144
+ throw error;
1145
+ unresolved.push(`${displayPath}: the embedded ${type} "${id}" could not be read (${error.message})`);
1146
+ continue;
1147
+ }
1148
+ if (type === "sprite") {
1149
+ const tileSet = firstField(document, "tile_set");
1150
+ if (tileSet !== undefined)
1151
+ sources.set(id, { kind: "sprite", path: resourceKey(tileSet) });
1152
+ continue;
816
1153
  }
1154
+ const animations = firstField(document, "animations");
1155
+ if (animations !== undefined)
1156
+ claimModel(id, animations);
817
1157
  }
818
- return wrapper;
819
- }
820
- function readLibraryIncludeDirs(gameProjectText) {
821
- let inLibrary = false;
822
- for (const line of gameProjectText.split(`
823
- `)) {
824
- if (isSectionHeader(line)) {
825
- inLibrary = line.trim() === "[library]";
1158
+ for (const referenced of childrenOf2(object, "components")) {
1159
+ const component = firstField(referenced, "component");
1160
+ const id = firstField(referenced, "id");
1161
+ if (id === undefined || component === undefined)
826
1162
  continue;
827
- }
828
- if (!inLibrary) {
1163
+ if (component.endsWith(SPRITE_SUFFIX2)) {
1164
+ const tileSet = assets.tileSetBySprite.get(resourceKey(component));
1165
+ if (tileSet === undefined) {
1166
+ unresolved.push(`${displayPath}: the sprite component "${id}" names ${component}, which is not among the project's sprite documents`);
1167
+ continue;
1168
+ }
1169
+ sources.set(id, { kind: "sprite", path: tileSet });
829
1170
  continue;
830
1171
  }
831
- const match = line.match(/^include_dirs\s*=\s*(.*)$/);
832
- if (match?.[1] !== undefined) {
833
- return match[1].split(",").map((dir) => dir.trim()).filter((dir) => dir.length > 0);
1172
+ if (!component.endsWith(MODEL_SUFFIX2))
1173
+ continue;
1174
+ const resource = resourceKey(component);
1175
+ if (!assets.modelDocuments.has(resource)) {
1176
+ unresolved.push(`${displayPath}: the model component "${id}" names ${component}, which is not among the project's model documents`);
1177
+ continue;
834
1178
  }
1179
+ const animations = assets.animationSourceByModel.get(resource);
1180
+ if (animations !== undefined)
1181
+ claimModel(id, animations);
835
1182
  }
836
- return [];
1183
+ return sources;
837
1184
  }
838
- function isContainedResourcePath(path) {
839
- if (path.length === 0 || path.includes("\\")) {
840
- return false;
1185
+ function scriptResourcesOf(object) {
1186
+ const resources = [];
1187
+ for (const component of childrenOf2(object, "components")) {
1188
+ const resource = firstField(component, "component");
1189
+ if (resource === undefined || !resource.endsWith(SCRIPT_SUFFIX))
1190
+ continue;
1191
+ const key = resourceKey(resource);
1192
+ if (key !== SCRIPT_SUFFIX.slice(1))
1193
+ resources.push(key);
841
1194
  }
842
- return path.split("/").every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
1195
+ return resources;
843
1196
  }
844
- function libraryIncludedEntries(entries, gameProjectText) {
845
- if (gameProjectText === undefined) {
846
- return {
847
- shared: [],
848
- reasons: ["ships no game.project, so it declares no [library] include_dirs"],
849
- refused: []
850
- };
1197
+ function isGameObject(message) {
1198
+ return message.messages.has("components") || message.messages.has("embedded_components");
1199
+ }
1200
+ function buildComponentAnimationIndex(input) {
1201
+ const unresolved = [];
1202
+ const assets = readAssets(input.assets, unresolved);
1203
+ const byScriptResource = new Map;
1204
+ const sourceByScriptResource = new Map;
1205
+ const claimedBy = new Map;
1206
+ function walk(message, blockName, displayPath) {
1207
+ if (isGameObject(message)) {
1208
+ claim(message, displayPath);
1209
+ }
1210
+ if (blockName === "embedded_instances") {
1211
+ for (const payload of message.fields.get("data") ?? []) {
1212
+ try {
1213
+ walk(parseSceneTextFormat(payload), "", displayPath);
1214
+ } catch (error) {
1215
+ if (!(error instanceof SceneTextFormatError))
1216
+ throw error;
1217
+ unresolved.push(`${displayPath}: an embedded game object could not be read (${error.message})`);
1218
+ }
1219
+ }
1220
+ }
1221
+ for (const [name, nested] of message.messages) {
1222
+ for (const child of nested) {
1223
+ walk(child, name, displayPath);
1224
+ }
1225
+ }
851
1226
  }
852
- const includeDirs = new Set(readLibraryIncludeDirs(gameProjectText));
853
- if (includeDirs.size === 0) {
854
- return {
855
- shared: [],
856
- reasons: ["its game.project declares no [library] include_dirs"],
857
- refused: []
858
- };
1227
+ function idsOf(id, source, displayPath) {
1228
+ if (source.kind === "sprite") {
1229
+ const declared = assets.animationsByTileSet.get(source.path);
1230
+ if (declared !== undefined)
1231
+ return new Map([...declared].map((name) => [name, source.path]));
1232
+ unresolved.push(`${displayPath}: the sprite component "${id}" names the tile source /${source.path}, which is not among the project's asset documents`);
1233
+ return;
1234
+ }
1235
+ const resolved = resolveAnimationSet({
1236
+ path: source.path,
1237
+ prefix: "",
1238
+ stack: new Set,
1239
+ assets,
1240
+ unresolved
1241
+ });
1242
+ if (resolved !== undefined)
1243
+ return resolved;
1244
+ if (!assets.entriesByAnimationSet.has(source.path)) {
1245
+ unresolved.push(`${displayPath}: the model component "${id}" names the animation set /${source.path}, which is not among the project's asset documents`);
1246
+ }
1247
+ return;
859
1248
  }
860
- const wrapper = archiveWrapperOf(entries);
861
- const shared = [];
862
- const refused = [];
863
- for (const entry of entries) {
864
- const path = wrapper === undefined ? entry : entry.slice(wrapper.length + 1);
865
- const [head] = path.split("/");
866
- if (head === undefined || !includeDirs.has(head)) {
867
- continue;
1249
+ function claim(object, displayPath) {
1250
+ const scripts = scriptResourcesOf(object);
1251
+ if (scripts.length === 0)
1252
+ return;
1253
+ const sources = componentAnimationSources(object, displayPath, assets, unresolved);
1254
+ const animations = new Map;
1255
+ const declaringSources = new Map;
1256
+ for (const [id, source] of sources) {
1257
+ const resolved = idsOf(id, source, displayPath);
1258
+ if (resolved === undefined)
1259
+ continue;
1260
+ animations.set(id, new Set(resolved.keys()));
1261
+ declaringSources.set(id, resolved);
868
1262
  }
869
- const dot = path.lastIndexOf(".");
870
- if (dot === -1 || !LIBRARY_SCENE_EXTENSIONS.includes(path.slice(dot).toLowerCase())) {
871
- continue;
1263
+ for (const key of scripts) {
1264
+ const owner = claimedBy.get(key);
1265
+ if (owner !== undefined) {
1266
+ byScriptResource.delete(key);
1267
+ sourceByScriptResource.delete(key);
1268
+ unresolved.push(`${key}: claimed by both ${owner} and ${displayPath}, so its component animations are ambiguous`);
1269
+ continue;
1270
+ }
1271
+ claimedBy.set(key, displayPath);
1272
+ byScriptResource.set(key, animations);
1273
+ sourceByScriptResource.set(key, declaringSources);
872
1274
  }
873
- if (!isContainedResourcePath(path)) {
874
- refused.push(entry);
875
- continue;
1275
+ }
1276
+ for (const [path, text] of input.scenes) {
1277
+ const displayPath = normalizeDocumentKey(path);
1278
+ try {
1279
+ walk(parseSceneTextFormat(text), "", displayPath);
1280
+ } catch (error) {
1281
+ if (!(error instanceof SceneTextFormatError))
1282
+ throw error;
1283
+ unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
876
1284
  }
877
- shared.push({ entry, path });
878
1285
  }
879
- shared.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
880
- return { shared, reasons: [], refused };
881
- }
882
- var LIBRARY_DEPENDENCY_ROOT = ".defold-types/dependencies";
883
- var LIBRARY_DEPENDENCY_MANIFEST = `${LIBRARY_DEPENDENCY_ROOT}/dependencies.json`;
884
- function joinProject(projectRoot, relative) {
885
- return `${projectRoot.replace(/\\/g, "/").replace(/\/+$/, "")}/${relative}`;
1286
+ return { byScriptResource, sourceByScriptResource, unresolved };
886
1287
  }
887
- function readMaterializedKeys(manifestText) {
888
- let parsed;
889
- try {
890
- parsed = JSON.parse(manifestText);
891
- } catch {
1288
+ function componentIdOfSameObjectAddress(address) {
1289
+ if (!address.startsWith("#"))
892
1290
  return;
893
- }
894
- const entries = parsed?.dependencies;
895
- if (!Array.isArray(entries))
1291
+ const id = address.slice(1);
1292
+ if (id === "" || id.includes("/") || id.includes("#"))
896
1293
  return;
897
- const keyByUrl = new Map;
898
- for (const entry of entries) {
899
- const { key, url } = entry ?? {};
900
- if (typeof key === "string" && typeof url === "string")
901
- keyByUrl.set(url, key);
902
- }
903
- return keyByUrl;
1294
+ return id;
904
1295
  }
905
- function readLibrarySceneDocuments(host, projectRoot, extensions = LIBRARY_SCENE_EXTENSIONS) {
906
- const documents = [];
907
- const unreadable = [];
908
- if (!host.readDirectory)
909
- return { documents, unreadable };
910
- const gameProjectText = host.readFile(joinProject(projectRoot, GAME_PROJECT_DOCUMENT));
911
- if (gameProjectText === undefined)
912
- return { documents, unreadable };
913
- const declared = [...readGameProjectDependencies(gameProjectText)].sort((a, b) => a.index - b.index);
914
- if (declared.length === 0)
915
- return { documents, unreadable };
916
- const manifestText = host.readFile(joinProject(projectRoot, LIBRARY_DEPENDENCY_MANIFEST));
917
- if (manifestText === undefined) {
918
- for (const { url } of declared) {
919
- unreadable.push(`${url}: is declared by game.project but ${LIBRARY_DEPENDENCY_MANIFEST} is absent`);
920
- }
921
- return { documents, unreadable };
922
- }
923
- const keyByUrl = readMaterializedKeys(manifestText);
924
- if (keyByUrl === undefined) {
925
- for (const { url } of declared) {
926
- unreadable.push(`${url}: is declared by game.project but ${LIBRARY_DEPENDENCY_MANIFEST} is not a readable dependency manifest`);
1296
+ // src/config-key-index.ts
1297
+ function sectionNameOf(line) {
1298
+ const match = line.trim().match(/^\[(.+)\]$/);
1299
+ return match?.[1];
1300
+ }
1301
+ function buildConfigKeyIndex(gameProjectText) {
1302
+ const keys = new Set;
1303
+ let section;
1304
+ for (const line of gameProjectText.split(`
1305
+ `)) {
1306
+ const header = sectionNameOf(line);
1307
+ if (header !== undefined) {
1308
+ section = header;
1309
+ continue;
927
1310
  }
928
- return { documents, unreadable };
1311
+ if (section === undefined)
1312
+ continue;
1313
+ const separator = line.indexOf("=");
1314
+ if (separator === -1)
1315
+ continue;
1316
+ const key = line.slice(0, separator).trim();
1317
+ if (key === "")
1318
+ continue;
1319
+ keys.add(`${section}.${key}`);
929
1320
  }
930
- const resolved = [];
931
- const urlByKey = new Map;
932
- for (const { url } of declared) {
933
- const key = keyByUrl.get(url);
934
- if (key === undefined) {
935
- unreadable.push(`${url}: is declared by game.project but was not materialized by the last resolve`);
1321
+ return keys;
1322
+ }
1323
+ function readGameProjectSetting(gameProjectText, section, key) {
1324
+ let current;
1325
+ for (const line of gameProjectText.split(`
1326
+ `)) {
1327
+ const header = sectionNameOf(line);
1328
+ if (header !== undefined) {
1329
+ current = header;
936
1330
  continue;
937
1331
  }
938
- resolved.push({ key, url });
939
- urlByKey.set(key, url);
940
- }
941
- const root = joinProject(projectRoot, LIBRARY_DEPENDENCY_ROOT);
942
- const filesByKey = new Map;
943
- const stray = new Set;
944
- for (const filePath of host.readDirectory(root, extensions)) {
945
- const relative = displayPathOf(root, filePath);
946
- if (relative === filePath.replace(/\\/g, "/"))
1332
+ if (current !== section)
947
1333
  continue;
948
- const separator = relative.indexOf("/");
949
- if (separator <= 0)
1334
+ const separator = line.indexOf("=");
1335
+ if (separator === -1)
950
1336
  continue;
951
- const key = relative.slice(0, separator);
952
- const url = urlByKey.get(key);
953
- if (url === undefined) {
954
- stray.add(key);
1337
+ if (line.slice(0, separator).trim() !== key)
955
1338
  continue;
956
- }
957
- const bucket = filesByKey.get(key);
958
- if (bucket === undefined)
959
- filesByKey.set(key, [filePath]);
960
- else
961
- bucket.push(filePath);
1339
+ const value = line.slice(separator + 1).trim();
1340
+ if (value !== "")
1341
+ return value;
962
1342
  }
963
- const claimed = new Map;
964
- for (const { key, url } of resolved) {
965
- for (const hostPath of (filesByKey.get(key) ?? []).sort()) {
966
- const displayPath = displayPathOf(root, hostPath).slice(key.length + 1);
967
- if (displayPath === "")
968
- continue;
969
- const owner = claimed.get(displayPath);
970
- if (owner !== undefined) {
971
- unreadable.push(`${displayPath}: also declared by ${url}; ${owner}'s file is used`);
972
- continue;
1343
+ return;
1344
+ }
1345
+ // src/lua-require-scan.ts
1346
+ var PLACEHOLDER = "\x00";
1347
+ function longBracketLevel(src, open) {
1348
+ let i = open + 1;
1349
+ let level = 0;
1350
+ while (src[i] === "=") {
1351
+ level++;
1352
+ i++;
1353
+ }
1354
+ return src[i] === "[" ? level : undefined;
1355
+ }
1356
+ function skipLongBracket(src, open, level) {
1357
+ const close = `]${"=".repeat(level)}]`;
1358
+ const end = src.indexOf(close, open + level + 2);
1359
+ return end === -1 ? src.length : end + close.length;
1360
+ }
1361
+ function mask(lua) {
1362
+ const literals = [];
1363
+ let text = "";
1364
+ let i = 0;
1365
+ while (i < lua.length) {
1366
+ const c = lua[i];
1367
+ if (c === "-" && lua[i + 1] === "-") {
1368
+ const level = lua[i + 2] === "[" ? longBracketLevel(lua, i + 2) : undefined;
1369
+ if (level !== undefined) {
1370
+ i = skipLongBracket(lua, i + 2, level);
1371
+ } else {
1372
+ const newline = lua.indexOf(`
1373
+ `, i);
1374
+ i = newline === -1 ? lua.length : newline;
973
1375
  }
974
- const text = host.readFile(hostPath);
975
- if (text === undefined) {
976
- unreadable.push(`${displayPath}: could not be read from ${url}`);
1376
+ text += " ";
1377
+ continue;
1378
+ }
1379
+ if (c === '"' || c === "'") {
1380
+ let j = i + 1;
1381
+ let value = "";
1382
+ while (j < lua.length) {
1383
+ const ch = lua[j];
1384
+ if (ch === "\\") {
1385
+ const next = lua[j + 1] ?? "";
1386
+ value += next === "n" ? `
1387
+ ` : next === "t" ? "\t" : next;
1388
+ j += 2;
1389
+ continue;
1390
+ }
1391
+ if (ch === c) {
1392
+ j++;
1393
+ break;
1394
+ }
1395
+ if (ch === `
1396
+ `) {
1397
+ break;
1398
+ }
1399
+ value += ch;
1400
+ j++;
1401
+ }
1402
+ text += `${PLACEHOLDER}${literals.length}${PLACEHOLDER}`;
1403
+ literals.push({ value, start: i, end: j });
1404
+ i = j;
1405
+ continue;
1406
+ }
1407
+ if (c === "[") {
1408
+ const level = longBracketLevel(lua, i);
1409
+ if (level !== undefined) {
1410
+ const end = skipLongBracket(lua, i, level);
1411
+ const close = `]${"=".repeat(level)}]`;
1412
+ const inner = lua.slice(i + level + 2, Math.max(i + level + 2, end - close.length));
1413
+ text += `${PLACEHOLDER}${literals.length}${PLACEHOLDER}`;
1414
+ literals.push({ value: inner, start: i, end });
1415
+ i = end;
977
1416
  continue;
978
1417
  }
979
- claimed.set(displayPath, url);
980
- documents.push({ displayPath, hostPath, url, text });
981
1418
  }
1419
+ text += c;
1420
+ i++;
982
1421
  }
983
- for (const key of [...stray].sort()) {
984
- unreadable.push(`${LIBRARY_DEPENDENCY_ROOT}/${key}: is materialized but no longer declared by game.project, so its scenes are left out`);
1422
+ return { text, literals };
1423
+ }
1424
+ var REQUIRE_RE = new RegExp(String.raw`(?<![A-Za-z0-9_.])require\s*\(\s*${PLACEHOLDER}(\d+)${PLACEHOLDER}\s*\)`, "g");
1425
+ function scanEmittedRequires(lua) {
1426
+ const { text, literals } = mask(lua);
1427
+ const sites = [];
1428
+ for (const match of text.matchAll(REQUIRE_RE)) {
1429
+ const literal = literals[Number(match[1])];
1430
+ if (literal === undefined) {
1431
+ continue;
1432
+ }
1433
+ sites.push({ path: literal.value, start: literal.start, end: literal.end });
985
1434
  }
986
- return { documents, unreadable };
1435
+ return sites;
987
1436
  }
988
- // src/main-entry-factory-imports.ts
989
- import * as ts3 from "typescript";
990
1437
 
991
- // src/lifecycle-erasure.ts
992
- import * as ts2 from "typescript";
993
- import {
994
- createAssignmentStatement,
995
- createBinaryExpression as createBinaryExpression2,
996
- createBlock as createBlock2,
997
- createCallExpression as createCallExpression2,
998
- createExpressionStatement,
999
- createForInStatement,
1000
- createFunctionExpression as createFunctionExpression2,
1001
- createIdentifier as createIdentifier2,
1002
- createIfStatement as createIfStatement2,
1003
- createNilLiteral,
1004
- createReturnStatement,
1005
- createStringLiteral as createStringLiteral2,
1006
- createTableIndexExpression,
1007
- createVariableDeclarationStatement as createVariableDeclarationStatement2,
1008
- NodeFlags as NodeFlags2,
1009
- SyntaxKind as SyntaxKind4
1010
- } from "typescript-to-lua";
1438
+ // src/timers-runtime.ts
1439
+ var TIMERS_MODULE_SPECIFIER = "@defold-typescript/types/timers";
1440
+ var TIMERS_REQUIRE_NAME = "defold_typescript_timers";
1441
+ var TIMERS_RUNTIME = `local ____lualib = require("lualib_bundle")
1442
+ local __TS__Promise = ____lualib.__TS__Promise
1443
+ local __TS__New = ____lualib.__TS__New
1011
1444
 
1012
- // src/message-dispatch-lowering.ts
1013
- import * as ts from "typescript";
1014
- import {
1015
- createBinaryExpression,
1016
- createBlock,
1017
- createCallExpression,
1018
- createFunctionExpression,
1019
- createIdentifier,
1020
- createIfStatement,
1021
- createStringLiteral,
1022
- createVariableDeclarationStatement,
1023
- NodeFlags,
1024
- SyntaxKind as SyntaxKind2
1025
- } from "typescript-to-lua";
1026
- var DISPATCH_MODULE = "@defold-typescript/types";
1027
- var DISPATCH_NAME = "onMessage";
1028
- var HANDLER_PARAM_TARGETS = ["self", "message", "sender"];
1029
- function resolvesToDispatchExport(callee, checker) {
1030
- let symbol = checker.getSymbolAtLocation(callee);
1031
- if (symbol === undefined) {
1032
- return false;
1033
- }
1034
- if (symbol.flags & ts.SymbolFlags.Alias) {
1035
- symbol = checker.getAliasedSymbol(symbol);
1036
- }
1037
- if (symbol.getName() !== DISPATCH_NAME) {
1038
- return false;
1039
- }
1040
- const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
1041
- if (declaration === undefined) {
1042
- return false;
1445
+ local function setTimeout(callback, delayMs)
1446
+ return timer.delay(delayMs / 1000, false, function()
1447
+ callback()
1448
+ end)
1449
+ end
1450
+
1451
+ local function setInterval(callback, delayMs)
1452
+ return timer.delay(delayMs / 1000, true, function()
1453
+ callback()
1454
+ end)
1455
+ end
1456
+
1457
+ local function clearTimeout(handle)
1458
+ timer.cancel(handle)
1459
+ end
1460
+
1461
+ local function clearInterval(handle)
1462
+ timer.cancel(handle)
1463
+ end
1464
+
1465
+ local function wait(seconds)
1466
+ return __TS__New(__TS__Promise, function(_, resolve)
1467
+ timer.delay(seconds, false, function()
1468
+ resolve()
1469
+ end)
1470
+ end)
1471
+ end
1472
+
1473
+ return {
1474
+ setTimeout = setTimeout,
1475
+ setInterval = setInterval,
1476
+ clearTimeout = clearTimeout,
1477
+ clearInterval = clearInterval,
1478
+ wait = wait,
1479
+ }
1480
+ `;
1481
+
1482
+ // src/emitted-requires.ts
1483
+ var LUALIB_REQUIRE_NAME = "lualib_bundle";
1484
+ var GENERATED_RUNTIME_REQUIRES = new Set([
1485
+ LUALIB_REQUIRE_NAME,
1486
+ TIMERS_REQUIRE_NAME
1487
+ ]);
1488
+ function findEmittedRequires(lua) {
1489
+ const found = [];
1490
+ const seen = new Set;
1491
+ for (const { path } of scanEmittedRequires(lua)) {
1492
+ if (GENERATED_RUNTIME_REQUIRES.has(path) || seen.has(path)) {
1493
+ continue;
1494
+ }
1495
+ seen.add(path);
1496
+ found.push(path);
1043
1497
  }
1044
- return declaration.getSourceFile().fileName.includes(DISPATCH_MODULE);
1498
+ return found;
1045
1499
  }
1046
- function handlerName(property) {
1047
- const name = property.name;
1048
- if (name === undefined) {
1500
+ // src/gui-flipbook-index.ts
1501
+ var SCRIPT_SUFFIX2 = ".gui_script";
1502
+ function scriptKeyOf(script) {
1503
+ if (!script.endsWith(SCRIPT_SUFFIX2))
1049
1504
  return;
1050
- }
1051
- if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
1052
- return name.text;
1053
- }
1054
- return;
1505
+ const key = resourceKey(script);
1506
+ return key === SCRIPT_SUFFIX2 ? undefined : key;
1055
1507
  }
1056
- function handlerFunction(property) {
1057
- if (ts.isMethodDeclaration(property) && property.body !== undefined) {
1058
- return property;
1508
+ function buildGuiFlipbookIndex(input) {
1509
+ const unresolved = [];
1510
+ const animationsBySource = readAnimationSources(input.assets, unresolved);
1511
+ const byScriptResource = new Map;
1512
+ const claimedBy = new Map;
1513
+ for (const [path, text] of input.scenes) {
1514
+ const displayPath = normalizeDocumentKey(path);
1515
+ let document;
1516
+ try {
1517
+ document = parseSceneTextFormat(text);
1518
+ } catch (error) {
1519
+ if (!(error instanceof SceneTextFormatError))
1520
+ throw error;
1521
+ unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
1522
+ continue;
1523
+ }
1524
+ const [script] = document.fields.get("script") ?? [];
1525
+ if (script === undefined)
1526
+ continue;
1527
+ const key = scriptKeyOf(script);
1528
+ if (key === undefined)
1529
+ continue;
1530
+ const owner = claimedBy.get(key);
1531
+ if (owner !== undefined) {
1532
+ byScriptResource.delete(key);
1533
+ unresolved.push(`${key}: claimed by both ${owner} and ${displayPath}, so its flipbook animations are ambiguous`);
1534
+ continue;
1535
+ }
1536
+ claimedBy.set(key, displayPath);
1537
+ const declaredBy = new Map;
1538
+ for (const message of document.messages.get("textures") ?? []) {
1539
+ for (const texture of message.fields.get("texture") ?? []) {
1540
+ if (texture === "")
1541
+ continue;
1542
+ const source = resourceKey(texture);
1543
+ const ids = animationsBySource.get(source);
1544
+ if (ids === undefined) {
1545
+ unresolved.push(`${displayPath}: the texture ${texture} is not among the project's asset documents`);
1546
+ continue;
1547
+ }
1548
+ for (const id of ids) {
1549
+ const declarers = declaredBy.get(id);
1550
+ if (declarers) {
1551
+ declarers.add(source);
1552
+ } else {
1553
+ declaredBy.set(id, new Set([source]));
1554
+ }
1555
+ }
1556
+ }
1557
+ }
1558
+ byScriptResource.set(key, declaredBy);
1059
1559
  }
1060
- if (ts.isPropertyAssignment(property)) {
1061
- const initializer = property.initializer;
1062
- if (ts.isFunctionExpression(initializer) || ts.isArrowFunction(initializer)) {
1063
- return initializer;
1560
+ return { byScriptResource, unresolved };
1561
+ }
1562
+ // src/gui-node-index.ts
1563
+ var SCRIPT_SUFFIX3 = ".gui_script";
1564
+ function scriptKeyOf2(script) {
1565
+ if (!script.endsWith(SCRIPT_SUFFIX3))
1566
+ return;
1567
+ const key = script.startsWith("/") ? script.slice(1) : script;
1568
+ return key === SCRIPT_SUFFIX3 ? undefined : key;
1569
+ }
1570
+ function guiScriptResourceOf(document) {
1571
+ const [script] = document.fields.get("script") ?? [];
1572
+ return script === undefined ? undefined : scriptKeyOf2(script);
1573
+ }
1574
+ function guiScriptResourcesOf(documents) {
1575
+ const scripts = new Map;
1576
+ for (const [displayPath, text] of documents) {
1577
+ let resource;
1578
+ try {
1579
+ resource = guiScriptResourceOf(parseSceneTextFormat(text));
1580
+ } catch (error) {
1581
+ if (!(error instanceof SceneTextFormatError))
1582
+ throw error;
1583
+ continue;
1064
1584
  }
1585
+ if (resource !== undefined)
1586
+ scripts.set(displayPath, resource);
1065
1587
  }
1066
- return;
1067
- }
1068
- function isThisParameter(param) {
1069
- return ts.isIdentifier(param.name) && ts.identifierToKeywordKind(param.name) === ts.SyntaxKind.ThisKeyword;
1588
+ return scripts;
1070
1589
  }
1071
- function aliasStatements(fn) {
1072
- const params = fn.parameters.filter((param) => !isThisParameter(param));
1073
- const aliases = [];
1074
- params.forEach((param, index) => {
1075
- const engineName = HANDLER_PARAM_TARGETS[index];
1076
- if (engineName === undefined || !ts.isIdentifier(param.name)) {
1077
- return;
1590
+ function buildGuiNodeIndex(documents) {
1591
+ const byScriptResource = new Map;
1592
+ const claimedBy = new Map;
1593
+ const unresolved = [];
1594
+ for (const [displayPath, text] of documents) {
1595
+ let document;
1596
+ try {
1597
+ document = parseSceneTextFormat(text);
1598
+ } catch (error) {
1599
+ if (!(error instanceof SceneTextFormatError))
1600
+ throw error;
1601
+ unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
1602
+ continue;
1078
1603
  }
1079
- if (param.name.text !== engineName) {
1080
- aliases.push(createVariableDeclarationStatement(createIdentifier(param.name.text), createIdentifier(engineName)));
1604
+ const key = guiScriptResourceOf(document);
1605
+ if (key === undefined)
1606
+ continue;
1607
+ const owner = claimedBy.get(key);
1608
+ if (owner !== undefined) {
1609
+ byScriptResource.delete(key);
1610
+ unresolved.push(`${key}: claimed by both ${owner} and ${displayPath}, so its node ids are ambiguous`);
1611
+ continue;
1081
1612
  }
1082
- });
1083
- return aliases;
1084
- }
1085
- function handlerBody(fn, context) {
1086
- if (ts.isBlock(fn.body)) {
1087
- return context.transformStatements(fn.body.statements);
1613
+ claimedBy.set(key, displayPath);
1614
+ const ids = new Set;
1615
+ for (const message of document.messages.get("nodes") ?? []) {
1616
+ for (const id of message.fields.get("id") ?? []) {
1617
+ if (id !== "")
1618
+ ids.add(id);
1619
+ }
1620
+ }
1621
+ byScriptResource.set(key, ids);
1088
1622
  }
1089
- return context.transformStatements([ts.factory.createExpressionStatement(fn.body)]);
1623
+ return { byScriptResource, unresolved };
1090
1624
  }
1091
- var messageDispatchLoweringPlugin = {
1092
- visitors: {
1093
- [ts.SyntaxKind.CallExpression]: (node, context) => {
1094
- const [handlersArg] = node.arguments;
1095
- if (node.arguments.length === 1 && handlersArg !== undefined && ts.isObjectLiteralExpression(handlersArg) && resolvesToDispatchExport(node.expression, context.checker)) {
1096
- let chain;
1097
- const properties = [...handlersArg.properties];
1098
- for (let i = properties.length - 1;i >= 0; i--) {
1099
- const property = properties[i];
1100
- if (property === undefined) {
1101
- continue;
1102
- }
1103
- const name = handlerName(property);
1104
- const fn = handlerFunction(property);
1105
- if (name === undefined || fn === undefined) {
1106
- continue;
1107
- }
1108
- const condition = createBinaryExpression(createIdentifier("message_id"), createCallExpression(createIdentifier("hash"), [createStringLiteral(name)]), SyntaxKind2.EqualityOperator);
1109
- const body = [...aliasStatements(fn), ...handlerBody(fn, context)];
1110
- chain = createIfStatement(condition, createBlock(body), chain);
1625
+ // src/input-action-index.ts
1626
+ function buildInputActionIndex(documents) {
1627
+ const actions = new Set;
1628
+ for (const text of documents.values()) {
1629
+ let document;
1630
+ try {
1631
+ document = parseSceneTextFormat(text);
1632
+ } catch (error) {
1633
+ if (!(error instanceof SceneTextFormatError))
1634
+ throw error;
1635
+ continue;
1636
+ }
1637
+ for (const triggers of document.messages.values()) {
1638
+ for (const trigger of triggers) {
1639
+ for (const action of trigger.fields.get("action") ?? []) {
1640
+ if (action !== "")
1641
+ actions.add(action);
1111
1642
  }
1112
- const params = [
1113
- createIdentifier("self"),
1114
- createIdentifier("message_id"),
1115
- createIdentifier("message"),
1116
- createIdentifier("sender")
1117
- ];
1118
- return createFunctionExpression(createBlock(chain === undefined ? [] : [chain]), params, undefined, NodeFlags.Declaration, node);
1119
1643
  }
1120
- return context.superTransformExpression(node);
1121
1644
  }
1122
1645
  }
1123
- };
1646
+ return actions;
1647
+ }
1648
+ // src/project-resources.ts
1649
+ var SCAFFOLDED_DEFIGNORE_LINES = ["/node_modules", "/.defold-types", "/.vscode"];
1650
+ function isDefignoredPath(displayPath) {
1651
+ const rooted = `/${displayPath.replace(/^\/+/, "")}`;
1652
+ return SCAFFOLDED_DEFIGNORE_LINES.some((line) => rooted === line || rooted.startsWith(`${line}/`));
1653
+ }
1124
1654
 
1125
- // src/lifecycle-erasure.ts
1126
- var FACTORY_MODULE = "@defold-typescript/types";
1127
- var FACTORY_NAMES = new Set(["defineScript", "defineGuiScript", "defineRenderScript"]);
1128
- var FACTORY_SPECIFIERS = new Set([
1129
- FACTORY_MODULE,
1130
- `${FACTORY_MODULE}/script`,
1131
- `${FACTORY_MODULE}/gui-script`,
1132
- `${FACTORY_MODULE}/render-script`
1133
- ]);
1134
- function resolvesToFactoryExport(callee, checker) {
1135
- let symbol = checker.getSymbolAtLocation(callee);
1136
- if (symbol === undefined) {
1137
- return false;
1138
- }
1139
- if (symbol.flags & ts2.SymbolFlags.Alias) {
1140
- symbol = checker.getAliasedSymbol(symbol);
1141
- }
1142
- if (!FACTORY_NAMES.has(symbol.getName())) {
1143
- return false;
1144
- }
1145
- const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
1146
- if (declaration === undefined) {
1147
- return false;
1148
- }
1149
- return declaration.getSourceFile().fileName.includes(FACTORY_MODULE);
1655
+ // src/scene-documents.ts
1656
+ var SCENE_EXTENSIONS = [".go", ".collection"];
1657
+ var GUI_EXTENSIONS = [".gui"];
1658
+ var INPUT_BINDING_EXTENSIONS = [".input_binding"];
1659
+ var COLLECTION_REFERENCE_EXTENSIONS = [".collectionproxy", ".collectionfactory"];
1660
+ var ANIMATION_ASSET_EXTENSIONS = [
1661
+ ".atlas",
1662
+ ".tilesource",
1663
+ ".sprite",
1664
+ ".model",
1665
+ ".animationset"
1666
+ ];
1667
+ var LIBRARY_SCENE_EXTENSIONS = [
1668
+ ...SCENE_EXTENSIONS,
1669
+ ...GUI_EXTENSIONS,
1670
+ ...ANIMATION_ASSET_EXTENSIONS,
1671
+ ...COLLECTION_REFERENCE_EXTENSIONS
1672
+ ];
1673
+ var PROJECT_EXTENSIONS = [".project"];
1674
+ var GAME_PROJECT_DOCUMENT = "game.project";
1675
+ function displayPathOf(projectRoot, filePath) {
1676
+ const path = filePath.replace(/\\/g, "/");
1677
+ const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
1678
+ return path.startsWith(`${root}/`) ? path.slice(root.length + 1) : path;
1150
1679
  }
1151
- function isThisParameter2(param) {
1152
- return ts2.isIdentifier(param.name) && ts2.identifierToKeywordKind(param.name) === ts2.SyntaxKind.ThisKeyword;
1680
+ function isBuildOutput(displayPath) {
1681
+ return displayPath.split("/").includes("build");
1153
1682
  }
1154
- function hookName(property) {
1155
- const name = property.name;
1156
- if (name === undefined) {
1157
- return;
1158
- }
1159
- if (ts2.isIdentifier(name) || ts2.isStringLiteral(name)) {
1160
- return name.text;
1161
- }
1162
- return;
1683
+ function isExcludedProjectPath(displayPath) {
1684
+ return isBuildOutput(displayPath) || isDefignoredPath(displayPath);
1163
1685
  }
1164
- function hookFunction(property) {
1165
- if (ts2.isMethodDeclaration(property) && property.body !== undefined) {
1166
- return property;
1686
+ function readSceneDocuments(host, projectRoot, extensions = SCENE_EXTENSIONS) {
1687
+ const documents = new Map;
1688
+ const origins = new Map;
1689
+ const unreadable = [];
1690
+ const paths = [];
1691
+ if (!host.readDirectory) {
1692
+ unreadable.push("the editor host cannot enumerate project files, so no scene source was read");
1693
+ return { documents, origins, unreadable, paths };
1167
1694
  }
1168
- if (ts2.isPropertyAssignment(property)) {
1169
- const initializer = property.initializer;
1170
- if (ts2.isFunctionExpression(initializer) || ts2.isArrowFunction(initializer)) {
1171
- return initializer;
1695
+ for (const filePath of host.readDirectory(projectRoot, extensions)) {
1696
+ const displayPath = displayPathOf(projectRoot, filePath);
1697
+ if (isExcludedProjectPath(displayPath))
1698
+ continue;
1699
+ const text = host.readFile(filePath);
1700
+ if (text === undefined) {
1701
+ unreadable.push(`${displayPath}: could not be read`);
1702
+ continue;
1172
1703
  }
1704
+ documents.set(displayPath, text);
1705
+ paths.push(filePath);
1173
1706
  }
1174
- return;
1175
- }
1176
- function dispatcherInitializer(property, checker) {
1177
- if (!ts2.isPropertyAssignment(property)) {
1178
- return;
1179
- }
1180
- const initializer = property.initializer;
1181
- if (!ts2.isCallExpression(initializer)) {
1182
- return;
1707
+ const library = readLibrarySceneDocuments(host, projectRoot, extensions);
1708
+ unreadable.push(...library.unreadable);
1709
+ for (const { displayPath, hostPath, url, text } of library.documents) {
1710
+ if (documents.has(displayPath)) {
1711
+ unreadable.push(`${displayPath}: also declared by ${url}; the project's own file is used`);
1712
+ continue;
1713
+ }
1714
+ documents.set(displayPath, text);
1715
+ origins.set(displayPath, url);
1716
+ paths.push(hostPath);
1183
1717
  }
1184
- return resolvesToDispatchExport(initializer.expression, checker) ? initializer : undefined;
1718
+ return { documents, origins, unreadable, paths };
1185
1719
  }
1186
- function transformHookBody(fn, context) {
1187
- if (ts2.isBlock(fn.body)) {
1188
- return context.transformStatements(fn.body.statements);
1720
+ function listProjectResourcePaths(host, projectRoot, extensions) {
1721
+ if (!host.readDirectory)
1722
+ return new Set;
1723
+ const paths = [];
1724
+ for (const filePath of host.readDirectory(projectRoot, extensions)) {
1725
+ const displayPath = displayPathOf(projectRoot, filePath);
1726
+ if (isExcludedProjectPath(displayPath))
1727
+ continue;
1728
+ if (!extensions.some((extension) => displayPath.endsWith(extension)))
1729
+ continue;
1730
+ paths.push(`/${displayPath}`);
1189
1731
  }
1190
- return [createExpressionStatement(context.transformExpression(fn.body))];
1732
+ return new Set(paths.sort());
1191
1733
  }
1192
- function crossesFunctionBoundary(node) {
1193
- return ts2.isFunctionDeclaration(node) || ts2.isFunctionExpression(node) || ts2.isArrowFunction(node) || ts2.isMethodDeclaration(node) || ts2.isGetAccessorDeclaration(node) || ts2.isSetAccessorDeclaration(node);
1734
+
1735
+ // src/library-dependencies.ts
1736
+ function isProjectHeader(line) {
1737
+ return line.trim() === "[project]";
1738
+ }
1739
+ function isSectionHeader(line) {
1740
+ return /^\[.+\]\s*$/.test(line.trim());
1194
1741
  }
1195
- function initReturnsValue(fn) {
1196
- if (!ts2.isBlock(fn.body)) {
1197
- return true;
1742
+ function readGameProjectDependencies(gameProjectText) {
1743
+ const lines = gameProjectText.split(`
1744
+ `);
1745
+ const deps = [];
1746
+ let inProject = false;
1747
+ for (const line of lines) {
1748
+ if (isSectionHeader(line)) {
1749
+ inProject = isProjectHeader(line);
1750
+ continue;
1751
+ }
1752
+ if (!inProject) {
1753
+ continue;
1754
+ }
1755
+ const match = line.match(/^dependencies#(\d+)\s*=\s*(.+)$/);
1756
+ if (match?.[1] !== undefined && match[2] !== undefined) {
1757
+ deps.push({ index: Number(match[1]), url: match[2] });
1758
+ }
1198
1759
  }
1199
- let found = false;
1200
- const visit = (node) => {
1201
- if (found || crossesFunctionBoundary(node)) {
1760
+ return deps;
1761
+ }
1762
+ function archiveWrapperOf(entries) {
1763
+ let wrapper;
1764
+ for (const entry of entries) {
1765
+ const separator = entry.indexOf("/");
1766
+ if (separator <= 0) {
1202
1767
  return;
1203
1768
  }
1204
- if (ts2.isReturnStatement(node) && node.expression !== undefined) {
1205
- found = true;
1769
+ const segment = entry.slice(0, separator);
1770
+ if (wrapper === undefined) {
1771
+ wrapper = segment;
1772
+ } else if (wrapper !== segment) {
1206
1773
  return;
1207
1774
  }
1208
- ts2.forEachChild(node, visit);
1209
- };
1210
- ts2.forEachChild(fn.body, visit);
1211
- return found;
1212
- }
1213
- function initBuilderBody(fn, context) {
1214
- if (ts2.isBlock(fn.body)) {
1215
- return context.transformStatements(fn.body.statements);
1216
1775
  }
1217
- return [createReturnStatement([context.transformExpression(fn.body)])];
1218
- }
1219
- function emitInitMerge(fn, context, property) {
1220
- const authorParam = fn.parameters.find((param) => !isThisParameter2(param) && ts2.isIdentifier(param.name));
1221
- const builderParams = authorParam === undefined ? [] : [context.transformExpression(authorParam.name)];
1222
- const builder = createFunctionExpression2(createBlock2(initBuilderBody(fn, context)), builderParams, undefined, NodeFlags2.Declaration, fn);
1223
- const builderDecl = createVariableDeclarationStatement2(createIdentifier2("____init"), builder);
1224
- const builderArgs = authorParam === undefined ? [] : [createIdentifier2("self")];
1225
- const stateDecl = createVariableDeclarationStatement2(createIdentifier2("____s"), createCallExpression2(createIdentifier2("____init"), builderArgs));
1226
- const mergeAssignment = createAssignmentStatement(createTableIndexExpression(createIdentifier2("self"), createIdentifier2("____k")), createIdentifier2("____v"));
1227
- const forIn = createForInStatement(createBlock2([mergeAssignment]), [createIdentifier2("____k"), createIdentifier2("____v")], [createCallExpression2(createIdentifier2("pairs"), [createIdentifier2("____s")])]);
1228
- const guard = createIfStatement2(createBinaryExpression2(createIdentifier2("____s"), createNilLiteral(), SyntaxKind4.InequalityOperator), createBlock2([forIn]));
1229
- const initFn = createFunctionExpression2(createBlock2([stateDecl, guard]), [createIdentifier2("self")], undefined, NodeFlags2.Declaration, fn);
1230
- return [builderDecl, createAssignmentStatement(createIdentifier2("init"), initFn, property)];
1776
+ return wrapper;
1231
1777
  }
1232
- function emitPropertyRegistrations(property, context) {
1233
- if (!ts2.isPropertyAssignment(property) || !ts2.isObjectLiteralExpression(property.initializer)) {
1234
- return [];
1235
- }
1236
- const registrations = [];
1237
- for (const member of property.initializer.properties) {
1238
- if (!ts2.isPropertyAssignment(member)) {
1778
+ function readLibraryIncludeDirs(gameProjectText) {
1779
+ let inLibrary = false;
1780
+ for (const line of gameProjectText.split(`
1781
+ `)) {
1782
+ if (isSectionHeader(line)) {
1783
+ inLibrary = line.trim() === "[library]";
1239
1784
  continue;
1240
1785
  }
1241
- const key = member.name;
1242
- if (!ts2.isIdentifier(key) && !ts2.isStringLiteral(key)) {
1786
+ if (!inLibrary) {
1243
1787
  continue;
1244
1788
  }
1245
- registrations.push(createExpressionStatement(createCallExpression2(createTableIndexExpression(createIdentifier2("go"), createStringLiteral2("property")), [createStringLiteral2(key.text), context.transformExpression(member.initializer)])));
1789
+ const match = line.match(/^include_dirs\s*=\s*(.*)$/);
1790
+ if (match?.[1] !== undefined) {
1791
+ return match[1].split(",").map((dir) => dir.trim()).filter((dir) => dir.length > 0);
1792
+ }
1246
1793
  }
1247
- return registrations;
1794
+ return [];
1248
1795
  }
1249
- function eraseFactoryCall(expression, context) {
1250
- if (!ts2.isCallExpression(expression)) {
1251
- return;
1796
+ function isContainedResourcePath(path) {
1797
+ if (path.length === 0 || path.includes("\\")) {
1798
+ return false;
1252
1799
  }
1253
- if (!resolvesToFactoryExport(expression.expression, context.checker)) {
1254
- return;
1800
+ return path.split("/").every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
1801
+ }
1802
+ function libraryIncludedEntries(entries, gameProjectText) {
1803
+ if (gameProjectText === undefined) {
1804
+ return {
1805
+ shared: [],
1806
+ reasons: ["ships no game.project, so it declares no [library] include_dirs"],
1807
+ refused: []
1808
+ };
1255
1809
  }
1256
- const hooks = expression.arguments[0];
1257
- if (hooks === undefined || !ts2.isObjectLiteralExpression(hooks)) {
1258
- return;
1810
+ const includeDirs = new Set(readLibraryIncludeDirs(gameProjectText));
1811
+ if (includeDirs.size === 0) {
1812
+ return {
1813
+ shared: [],
1814
+ reasons: ["its game.project declares no [library] include_dirs"],
1815
+ refused: []
1816
+ };
1259
1817
  }
1260
- const registrations = [];
1261
- const statements = [];
1262
- for (const property of hooks.properties) {
1263
- const name = hookName(property);
1264
- if (name === undefined) {
1265
- continue;
1266
- }
1267
- if (name === "properties") {
1268
- registrations.push(...emitPropertyRegistrations(property, context));
1818
+ const wrapper = archiveWrapperOf(entries);
1819
+ const shared = [];
1820
+ const refused = [];
1821
+ for (const entry of entries) {
1822
+ const path = wrapper === undefined ? entry : entry.slice(wrapper.length + 1);
1823
+ const [head] = path.split("/");
1824
+ if (head === undefined || !includeDirs.has(head)) {
1269
1825
  continue;
1270
1826
  }
1271
- const fn = hookFunction(property);
1272
- if (fn === undefined) {
1273
- const dispatch = dispatcherInitializer(property, context.checker);
1274
- if (dispatch !== undefined) {
1275
- statements.push(createAssignmentStatement(createIdentifier2(name), context.transformExpression(dispatch), property));
1276
- }
1827
+ const dot = path.lastIndexOf(".");
1828
+ if (dot === -1 || !LIBRARY_SCENE_EXTENSIONS.includes(path.slice(dot).toLowerCase())) {
1277
1829
  continue;
1278
1830
  }
1279
- if (name === "init" && initReturnsValue(fn)) {
1280
- statements.push(...emitInitMerge(fn, context, property));
1831
+ if (!isContainedResourcePath(path)) {
1832
+ refused.push(entry);
1281
1833
  continue;
1282
1834
  }
1283
- const params = fn.parameters.filter((param) => !isThisParameter2(param) && ts2.isIdentifier(param.name)).map((param) => context.transformExpression(param.name));
1284
- const fnExpression = createFunctionExpression2(createBlock2(transformHookBody(fn, context)), params, undefined, NodeFlags2.Declaration, fn);
1285
- statements.push(createAssignmentStatement(createIdentifier2(name), fnExpression, property));
1835
+ shared.push({ entry, path });
1286
1836
  }
1287
- return [...registrations, ...statements];
1837
+ shared.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
1838
+ return { shared, reasons: [], refused };
1288
1839
  }
1289
- function isFactoryOnlyImport(node) {
1290
- if (!ts2.isStringLiteral(node.moduleSpecifier) || !FACTORY_SPECIFIERS.has(node.moduleSpecifier.text)) {
1291
- return false;
1840
+ var LIBRARY_DEPENDENCY_ROOT = ".defold-types/dependencies";
1841
+ var LIBRARY_DEPENDENCY_MANIFEST = `${LIBRARY_DEPENDENCY_ROOT}/dependencies.json`;
1842
+ function joinProject(projectRoot, relative) {
1843
+ return `${projectRoot.replace(/\\/g, "/").replace(/\/+$/, "")}/${relative}`;
1844
+ }
1845
+ function readMaterializedKeys(manifestText) {
1846
+ let parsed;
1847
+ try {
1848
+ parsed = JSON.parse(manifestText);
1849
+ } catch {
1850
+ return;
1292
1851
  }
1293
- const clause = node.importClause;
1294
- if (clause === undefined || clause.name !== undefined) {
1295
- return false;
1852
+ const entries = parsed?.dependencies;
1853
+ if (!Array.isArray(entries))
1854
+ return;
1855
+ const keyByUrl = new Map;
1856
+ for (const entry of entries) {
1857
+ const { key, url } = entry ?? {};
1858
+ if (typeof key === "string" && typeof url === "string")
1859
+ keyByUrl.set(url, key);
1296
1860
  }
1297
- if (clause.isTypeOnly) {
1298
- return false;
1861
+ return keyByUrl;
1862
+ }
1863
+ function readLibrarySceneDocuments(host, projectRoot, extensions = LIBRARY_SCENE_EXTENSIONS) {
1864
+ const documents = [];
1865
+ const unreadable = [];
1866
+ if (!host.readDirectory)
1867
+ return { documents, unreadable };
1868
+ const gameProjectText = host.readFile(joinProject(projectRoot, GAME_PROJECT_DOCUMENT));
1869
+ if (gameProjectText === undefined)
1870
+ return { documents, unreadable };
1871
+ const declared = [...readGameProjectDependencies(gameProjectText)].sort((a, b) => a.index - b.index);
1872
+ if (declared.length === 0)
1873
+ return { documents, unreadable };
1874
+ const manifestText = host.readFile(joinProject(projectRoot, LIBRARY_DEPENDENCY_MANIFEST));
1875
+ if (manifestText === undefined) {
1876
+ for (const { url } of declared) {
1877
+ unreadable.push(`${url}: is declared by game.project but ${LIBRARY_DEPENDENCY_MANIFEST} is absent`);
1878
+ }
1879
+ return { documents, unreadable };
1299
1880
  }
1300
- const bindings = clause.namedBindings;
1301
- if (bindings === undefined || !ts2.isNamedImports(bindings)) {
1302
- return false;
1881
+ const keyByUrl = readMaterializedKeys(manifestText);
1882
+ if (keyByUrl === undefined) {
1883
+ for (const { url } of declared) {
1884
+ unreadable.push(`${url}: is declared by game.project but ${LIBRARY_DEPENDENCY_MANIFEST} is not a readable dependency manifest`);
1885
+ }
1886
+ return { documents, unreadable };
1303
1887
  }
1304
- const runtime = bindings.elements.filter((element) => !element.isTypeOnly);
1305
- if (runtime.length === 0) {
1306
- return false;
1888
+ const resolved = [];
1889
+ const urlByKey = new Map;
1890
+ for (const { url } of declared) {
1891
+ const key = keyByUrl.get(url);
1892
+ if (key === undefined) {
1893
+ unreadable.push(`${url}: is declared by game.project but was not materialized by the last resolve`);
1894
+ continue;
1895
+ }
1896
+ resolved.push({ key, url });
1897
+ urlByKey.set(key, url);
1307
1898
  }
1308
- return runtime.every((element) => FACTORY_NAMES.has((element.propertyName ?? element.name).text));
1309
- }
1310
- var lifecycleErasurePlugin = {
1311
- visitors: {
1312
- [ts2.SyntaxKind.ExpressionStatement]: (node, context) => eraseFactoryCall(node.expression, context) ?? context.superTransformStatements(node),
1313
- [ts2.SyntaxKind.ExportAssignment]: (node, context) => {
1314
- if (!node.isExportEquals) {
1315
- const erased = eraseFactoryCall(node.expression, context);
1316
- if (erased !== undefined) {
1317
- return erased;
1318
- }
1899
+ const root = joinProject(projectRoot, LIBRARY_DEPENDENCY_ROOT);
1900
+ const filesByKey = new Map;
1901
+ const stray = new Set;
1902
+ for (const filePath of host.readDirectory(root, extensions)) {
1903
+ const relative = displayPathOf(root, filePath);
1904
+ if (relative === filePath.replace(/\\/g, "/"))
1905
+ continue;
1906
+ const separator = relative.indexOf("/");
1907
+ if (separator <= 0)
1908
+ continue;
1909
+ const key = relative.slice(0, separator);
1910
+ const url = urlByKey.get(key);
1911
+ if (url === undefined) {
1912
+ stray.add(key);
1913
+ continue;
1914
+ }
1915
+ const bucket = filesByKey.get(key);
1916
+ if (bucket === undefined)
1917
+ filesByKey.set(key, [filePath]);
1918
+ else
1919
+ bucket.push(filePath);
1920
+ }
1921
+ const claimed = new Map;
1922
+ for (const { key, url } of resolved) {
1923
+ for (const hostPath of (filesByKey.get(key) ?? []).sort()) {
1924
+ const displayPath = displayPathOf(root, hostPath).slice(key.length + 1);
1925
+ if (displayPath === "")
1926
+ continue;
1927
+ const owner = claimed.get(displayPath);
1928
+ if (owner !== undefined) {
1929
+ unreadable.push(`${displayPath}: also declared by ${url}; ${owner}'s file is used`);
1930
+ continue;
1319
1931
  }
1320
- return context.superTransformStatements(node);
1321
- },
1322
- [ts2.SyntaxKind.ImportDeclaration]: (node, context) => isFactoryOnlyImport(node) ? [] : context.superTransformStatements(node)
1932
+ const text = host.readFile(hostPath);
1933
+ if (text === undefined) {
1934
+ unreadable.push(`${displayPath}: could not be read from ${url}`);
1935
+ continue;
1936
+ }
1937
+ claimed.set(displayPath, url);
1938
+ documents.push({ displayPath, hostPath, url, text });
1939
+ }
1323
1940
  }
1324
- };
1325
-
1941
+ for (const key of [...stray].sort()) {
1942
+ unreadable.push(`${LIBRARY_DEPENDENCY_ROOT}/${key}: is materialized but no longer declared by game.project, so its scenes are left out`);
1943
+ }
1944
+ return { documents, unreadable };
1945
+ }
1326
1946
  // src/main-entry-factory-imports.ts
1947
+ import * as ts4 from "typescript";
1327
1948
  function findMainEntryFactoryImports(fileName, source) {
1328
- const sourceFile = ts3.createSourceFile(fileName, source, ts3.ScriptTarget.Latest, true);
1949
+ const sourceFile = ts4.createSourceFile(fileName, source, ts4.ScriptTarget.Latest, true);
1329
1950
  const factories = [];
1330
1951
  for (const statement of sourceFile.statements) {
1331
- if (!ts3.isImportDeclaration(statement)) {
1952
+ if (!ts4.isImportDeclaration(statement)) {
1332
1953
  continue;
1333
1954
  }
1334
1955
  const specifier = statement.moduleSpecifier;
1335
- if (!ts3.isStringLiteral(specifier) || specifier.text !== FACTORY_MODULE) {
1956
+ if (!ts4.isStringLiteral(specifier) || specifier.text !== FACTORY_MODULE) {
1336
1957
  continue;
1337
1958
  }
1338
1959
  const bindings = statement.importClause?.namedBindings;
1339
- if (bindings === undefined || !ts3.isNamedImports(bindings)) {
1960
+ if (bindings === undefined || !ts4.isNamedImports(bindings)) {
1340
1961
  continue;
1341
1962
  }
1342
1963
  for (const element of bindings.elements) {
@@ -1372,6 +1993,17 @@ function stripIncludeBase(pattern) {
1372
1993
  const lastSlash = upToWildcard.lastIndexOf("/");
1373
1994
  return lastSlash === -1 ? "" : upToWildcard.slice(0, lastSlash + 1);
1374
1995
  }
1996
+ var ABSOLUTE_SPELLING_RE = /^(\/|\\\\|[A-Za-z]:[\\/])/;
1997
+ function projectRelativeBase(pattern) {
1998
+ if (ABSOLUTE_SPELLING_RE.test(pattern)) {
1999
+ return;
2000
+ }
2001
+ const base = path.posix.normalize(stripIncludeBase(pattern.split("\\").join("/")));
2002
+ if (base === ".." || base.startsWith("../")) {
2003
+ return;
2004
+ }
2005
+ return base === "." || base === "./" ? "" : base;
2006
+ }
1375
2007
  function relUnderOutDir(rel, config) {
1376
2008
  const { outDir, include } = config;
1377
2009
  if (outDir === undefined || outDir === "" || outDir === ".") {
@@ -1381,6 +2013,15 @@ function relUnderOutDir(rel, config) {
1381
2013
  const relUnderBase = rel.slice(includeBase.length);
1382
2014
  return path.posix.join(outDir, relUnderBase);
1383
2015
  }
2016
+ function requirePathForRel(rel) {
2017
+ const segments = rel.split("/").filter((segment) => segment !== "" && segment !== "." && segment !== "..");
2018
+ const last = segments.length - 1;
2019
+ const lastSegment = segments[last];
2020
+ if (lastSegment !== undefined) {
2021
+ segments[last] = lastSegment.replace(/\.[^.]*$/, "");
2022
+ }
2023
+ return segments.map((segment) => segment.replace(/\./g, "_")).join(".");
2024
+ }
1384
2025
  function computeOutputRel(rel, config, kind) {
1385
2026
  const baseRel = relUnderOutDir(rel, config);
1386
2027
  if (kind === "module") {
@@ -1389,16 +2030,16 @@ function computeOutputRel(rel, config, kind) {
1389
2030
  return baseRel.replace(/\.ts$/, SCRIPT_SUFFIX_BY_KIND[kind]);
1390
2031
  }
1391
2032
  // src/program-diagnostics.ts
1392
- import * as ts7 from "typescript";
2033
+ import * as ts8 from "typescript";
1393
2034
  import * as tstl from "typescript-to-lua";
1394
2035
 
1395
2036
  // src/message-guard-lowering.ts
1396
- import * as ts4 from "typescript";
2037
+ import * as ts5 from "typescript";
1397
2038
  import {
1398
2039
  createBinaryExpression as createBinaryExpression3,
1399
2040
  createCallExpression as createCallExpression3,
1400
2041
  createIdentifier as createIdentifier3,
1401
- SyntaxKind as SyntaxKind6
2042
+ SyntaxKind as SyntaxKind7
1402
2043
  } from "typescript-to-lua";
1403
2044
  var GUARD_MODULE = "@defold-typescript/types";
1404
2045
  var GUARD_NAME = "isMessage";
@@ -1407,7 +2048,7 @@ function resolvesToGuardExport(callee, checker) {
1407
2048
  if (symbol === undefined) {
1408
2049
  return false;
1409
2050
  }
1410
- if (symbol.flags & ts4.SymbolFlags.Alias) {
2051
+ if (symbol.flags & ts5.SymbolFlags.Alias) {
1411
2052
  symbol = checker.getAliasedSymbol(symbol);
1412
2053
  }
1413
2054
  if (symbol.getName() !== GUARD_NAME) {
@@ -1421,12 +2062,12 @@ function resolvesToGuardExport(callee, checker) {
1421
2062
  }
1422
2063
  var messageGuardLoweringPlugin = {
1423
2064
  visitors: {
1424
- [ts4.SyntaxKind.CallExpression]: (node, context) => {
2065
+ [ts5.SyntaxKind.CallExpression]: (node, context) => {
1425
2066
  const [messageIdArg, , expectedArg] = node.arguments;
1426
2067
  if (node.arguments.length === 3 && messageIdArg !== undefined && expectedArg !== undefined && resolvesToGuardExport(node.expression, context.checker)) {
1427
2068
  const messageId = context.transformExpression(messageIdArg);
1428
2069
  const expected = context.transformExpression(expectedArg);
1429
- return createBinaryExpression3(messageId, createCallExpression3(createIdentifier3("hash"), [expected]), SyntaxKind6.EqualityOperator, node);
2070
+ return createBinaryExpression3(messageId, createCallExpression3(createIdentifier3("hash"), [expected]), SyntaxKind7.EqualityOperator, node);
1430
2071
  }
1431
2072
  return context.superTransformExpression(node);
1432
2073
  }
@@ -1434,77 +2075,34 @@ var messageGuardLoweringPlugin = {
1434
2075
  };
1435
2076
 
1436
2077
  // src/timers-lowering.ts
1437
- import * as ts5 from "typescript";
1438
-
1439
- // src/timers-runtime.ts
1440
- var TIMERS_MODULE_SPECIFIER = "@defold-typescript/types/timers";
1441
- var TIMERS_REQUIRE_NAME = "defold_typescript_timers";
1442
- var TIMERS_RUNTIME = `local ____lualib = require("lualib_bundle")
1443
- local __TS__Promise = ____lualib.__TS__Promise
1444
- local __TS__New = ____lualib.__TS__New
1445
-
1446
- local function setTimeout(callback, delayMs)
1447
- return timer.delay(delayMs / 1000, false, function()
1448
- callback()
1449
- end)
1450
- end
1451
-
1452
- local function setInterval(callback, delayMs)
1453
- return timer.delay(delayMs / 1000, true, function()
1454
- callback()
1455
- end)
1456
- end
1457
-
1458
- local function clearTimeout(handle)
1459
- timer.cancel(handle)
1460
- end
1461
-
1462
- local function clearInterval(handle)
1463
- timer.cancel(handle)
1464
- end
1465
-
1466
- local function wait(seconds)
1467
- return __TS__New(__TS__Promise, function(_, resolve)
1468
- timer.delay(seconds, false, function()
1469
- resolve()
1470
- end)
1471
- end)
1472
- end
1473
-
1474
- return {
1475
- setTimeout = setTimeout,
1476
- setInterval = setInterval,
1477
- clearTimeout = clearTimeout,
1478
- clearInterval = clearInterval,
1479
- wait = wait,
1480
- }
1481
- `;
1482
-
1483
- // src/timers-lowering.ts
2078
+ import * as ts6 from "typescript";
2079
+ import { LuaLibFeature } from "typescript-to-lua";
1484
2080
  function isTimersSpecifier(node) {
1485
- return ts5.isStringLiteral(node) && node.text === TIMERS_MODULE_SPECIFIER;
2081
+ return ts6.isStringLiteral(node) && node.text === TIMERS_MODULE_SPECIFIER;
1486
2082
  }
1487
2083
  function importsTimersModule(sourceFile) {
1488
- return sourceFile.statements.some((statement) => ts5.isImportDeclaration(statement) && isTimersSpecifier(statement.moduleSpecifier));
2084
+ return sourceFile.statements.some((statement) => ts6.isImportDeclaration(statement) && isTimersSpecifier(statement.moduleSpecifier));
1489
2085
  }
1490
2086
  var TIMERS_REQUIRE_PATH = `@NoResolution:${TIMERS_REQUIRE_NAME}`;
1491
2087
  var timersLoweringPlugin = {
1492
2088
  visitors: {
1493
- [ts5.SyntaxKind.ImportDeclaration]: (node, context) => {
2089
+ [ts6.SyntaxKind.ImportDeclaration]: (node, context) => {
1494
2090
  if (!isTimersSpecifier(node.moduleSpecifier)) {
1495
2091
  return context.superTransformStatements(node);
1496
2092
  }
1497
- const rewritten = ts5.factory.updateImportDeclaration(node, node.modifiers, node.importClause, ts5.factory.createStringLiteral(TIMERS_REQUIRE_PATH), node.attributes);
2093
+ context.usedLuaLibFeatures.add(LuaLibFeature.Promise);
2094
+ context.usedLuaLibFeatures.add(LuaLibFeature.New);
2095
+ const rewritten = ts6.factory.updateImportDeclaration(node, node.modifiers, node.importClause, ts6.factory.createStringLiteral(TIMERS_REQUIRE_PATH), node.attributes);
1498
2096
  return context.superTransformStatements(rewritten);
1499
2097
  }
1500
2098
  }
1501
2099
  };
1502
2100
 
1503
2101
  // src/window-event-guard-lowering.ts
1504
- import * as ts6 from "typescript";
2102
+ import * as ts7 from "typescript";
1505
2103
  import {
1506
2104
  createBinaryExpression as createBinaryExpression4,
1507
- SyntaxKind as SyntaxKind9
2105
+ SyntaxKind as SyntaxKind10
1508
2106
  } from "typescript-to-lua";
1509
2107
  var GUARD_MODULE2 = "@defold-typescript/types";
1510
2108
  var GUARD_NAME2 = "isWindowEvent";
@@ -1513,7 +2111,7 @@ function resolvesToGuardExport2(callee, checker) {
1513
2111
  if (symbol === undefined) {
1514
2112
  return false;
1515
2113
  }
1516
- if (symbol.flags & ts6.SymbolFlags.Alias) {
2114
+ if (symbol.flags & ts7.SymbolFlags.Alias) {
1517
2115
  symbol = checker.getAliasedSymbol(symbol);
1518
2116
  }
1519
2117
  if (symbol.getName() !== GUARD_NAME2) {
@@ -1527,12 +2125,12 @@ function resolvesToGuardExport2(callee, checker) {
1527
2125
  }
1528
2126
  var windowEventGuardLoweringPlugin = {
1529
2127
  visitors: {
1530
- [ts6.SyntaxKind.CallExpression]: (node, context) => {
2128
+ [ts7.SyntaxKind.CallExpression]: (node, context) => {
1531
2129
  const [eventArg, , expectedArg] = node.arguments;
1532
2130
  if (node.arguments.length === 3 && eventArg !== undefined && expectedArg !== undefined && resolvesToGuardExport2(node.expression, context.checker)) {
1533
2131
  const event = context.transformExpression(eventArg);
1534
2132
  const expected = context.transformExpression(expectedArg);
1535
- return createBinaryExpression4(event, expected, SyntaxKind9.EqualityOperator, node);
2133
+ return createBinaryExpression4(event, expected, SyntaxKind10.EqualityOperator, node);
1536
2134
  }
1537
2135
  return context.superTransformExpression(node);
1538
2136
  }
@@ -1552,13 +2150,33 @@ function isAmbient(fileName) {
1552
2150
  return /[\\/]node_modules[\\/]/.test(fileName) || /(^|[\\/])lib\.[^\\/]*\.d\.ts$/.test(fileName);
1553
2151
  }
1554
2152
  function getProgramDiagnostics(program, sourceFile) {
1555
- const { diagnostics } = tstl.getProgramTranspileResult(ts7.sys, noopWriteFile, {
2153
+ const { diagnostics } = tstl.getProgramTranspileResult(ts8.sys, noopWriteFile, {
1556
2154
  program,
1557
2155
  plugins: LUA_PLUGINS,
1558
2156
  ...sourceFile ? { sourceFiles: [sourceFile] } : {}
1559
2157
  });
1560
2158
  return diagnostics.filter((d) => d.file !== undefined && !isAmbient(d.file.fileName));
1561
2159
  }
2160
+ // src/require-rewrite.ts
2161
+ function quote(value) {
2162
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
2163
+ }
2164
+ function rewriteEmittedRequires(lua, rewrites) {
2165
+ if (rewrites.size === 0) {
2166
+ return lua;
2167
+ }
2168
+ let out = "";
2169
+ let cursor = 0;
2170
+ for (const { path, start, end } of scanEmittedRequires(lua)) {
2171
+ const mapped = rewrites.get(path);
2172
+ if (mapped === undefined) {
2173
+ continue;
2174
+ }
2175
+ out += lua.slice(cursor, start) + quote(mapped);
2176
+ cursor = end;
2177
+ }
2178
+ return cursor === 0 ? lua : out + lua.slice(cursor);
2179
+ }
1562
2180
  // src/scene-component-index.ts
1563
2181
  var COMPONENT_BLOCKS = new Set(["components", "embedded_components"]);
1564
2182
  var EMBEDDING_BLOCKS = new Set(["embedded_instances", "embedded_components"]);
@@ -2052,13 +2670,441 @@ function relativeAddressesFrom(index, context) {
2052
2670
  import { readFileSync as readFileSync2 } from "node:fs";
2053
2671
  import { createRequire as createRequire2 } from "node:module";
2054
2672
  import * as path3 from "node:path";
2055
- import * as ts10 from "typescript";
2673
+ import * as ts12 from "typescript";
2056
2674
  import * as tstl3 from "typescript-to-lua";
2057
2675
 
2676
+ // src/companion-emit.ts
2677
+ import * as ts9 from "typescript";
2678
+ import {
2679
+ createAssignmentStatement as createAssignmentStatement2,
2680
+ createCallExpression as createCallExpression4,
2681
+ createFile,
2682
+ createIdentifier as createIdentifier4,
2683
+ createReturnStatement as createReturnStatement2,
2684
+ createStringLiteral as createStringLiteral3,
2685
+ createTableExpression,
2686
+ createTableFieldExpression,
2687
+ createTableIndexExpression as createTableIndexExpression2,
2688
+ createVariableDeclarationStatement as createVariableDeclarationStatement3,
2689
+ isBlock as isBlock3,
2690
+ isCallExpression as isCallExpression4,
2691
+ isDoStatement,
2692
+ isForInStatement as isForInStatement2,
2693
+ isForStatement,
2694
+ isFunctionDefinition,
2695
+ isFunctionExpression as isFunctionExpression4,
2696
+ isIdentifier as isIdentifier4,
2697
+ isMethodCallExpression,
2698
+ isRepeatStatement,
2699
+ isReturnStatement as isReturnStatement2,
2700
+ isStringLiteral as isStringLiteral6,
2701
+ isTableExpression,
2702
+ isVariableDeclarationStatement,
2703
+ LuaPrinter
2704
+ } from "typescript-to-lua";
2705
+ var EXPORTS_NAME = "____exports";
2706
+ function exportsIdentifier() {
2707
+ return createIdentifier4(EXPORTS_NAME);
2708
+ }
2709
+ function isRuntimeScriptSource(sourceFile, checker) {
2710
+ return sourceFile.statements.some((statement) => {
2711
+ const expression = ts9.isExpressionStatement(statement) ? statement.expression : ts9.isExportAssignment(statement) && !statement.isExportEquals ? statement.expression : undefined;
2712
+ return expression !== undefined && ts9.isCallExpression(expression) && resolvesToFactoryExport(expression.expression, checker);
2713
+ });
2714
+ }
2715
+ function planFor(sourceFile, checker) {
2716
+ if (sourceFile.isDeclarationFile || !isRuntimeScriptSource(sourceFile, checker)) {
2717
+ return;
2718
+ }
2719
+ const closure = computeCompanionClosure(sourceFile, checker);
2720
+ if (closure.statements.length === 0 || closure.violations.length > 0) {
2721
+ return;
2722
+ }
2723
+ return {
2724
+ closureStarts: new Set(closure.statements.map((statement) => statement.pos)),
2725
+ internals: closure.internals,
2726
+ importBindings: closure.importBindings,
2727
+ requirePath: requirePathForRel(sourceFile.fileName)
2728
+ };
2729
+ }
2730
+ function isLuaNode(value) {
2731
+ return typeof value === "object" && value !== null && typeof value.kind === "number";
2732
+ }
2733
+ function forEachLuaNode(root, visit) {
2734
+ visit(root);
2735
+ for (const value of Object.values(root)) {
2736
+ if (Array.isArray(value)) {
2737
+ for (const element of value) {
2738
+ if (isLuaNode(element)) {
2739
+ forEachLuaNode(element, visit);
2740
+ }
2741
+ }
2742
+ } else if (isLuaNode(value)) {
2743
+ forEachLuaNode(value, visit);
2744
+ }
2745
+ }
2746
+ }
2747
+ function declaredNames(statement) {
2748
+ return isVariableDeclarationStatement(statement) ? statement.left.map((name) => name.text) : [];
2749
+ }
2750
+ function freeNames(statements) {
2751
+ const free = new Set;
2752
+ const scopes = [new Set];
2753
+ function declare(name) {
2754
+ scopes[scopes.length - 1]?.add(name);
2755
+ }
2756
+ function isBound(name) {
2757
+ return scopes.some((scope) => scope.has(name));
2758
+ }
2759
+ function inScope(run) {
2760
+ scopes.push(new Set);
2761
+ run();
2762
+ scopes.pop();
2763
+ }
2764
+ function visitEach(nodes) {
2765
+ for (const node of nodes ?? []) {
2766
+ visit(node);
2767
+ }
2768
+ }
2769
+ function visit(node) {
2770
+ if (node === undefined) {
2771
+ return;
2772
+ }
2773
+ if (isIdentifier4(node)) {
2774
+ if (!isBound(node.text)) {
2775
+ free.add(node.text);
2776
+ }
2777
+ return;
2778
+ }
2779
+ if (isVariableDeclarationStatement(node)) {
2780
+ if (isFunctionDefinition(node)) {
2781
+ const [name] = node.left;
2782
+ if (name !== undefined) {
2783
+ declare(name.text);
2784
+ }
2785
+ visit(node.right[0]);
2786
+ return;
2787
+ }
2788
+ visitEach(node.right);
2789
+ for (const name of node.left) {
2790
+ declare(name.text);
2791
+ }
2792
+ return;
2793
+ }
2794
+ if (isFunctionExpression4(node)) {
2795
+ inScope(() => {
2796
+ for (const param of node.params ?? []) {
2797
+ declare(param.text);
2798
+ }
2799
+ visit(node.body);
2800
+ });
2801
+ return;
2802
+ }
2803
+ if (isForStatement(node)) {
2804
+ visit(node.controlVariableInitializer);
2805
+ visit(node.limitExpression);
2806
+ visit(node.stepExpression);
2807
+ inScope(() => {
2808
+ declare(node.controlVariable.text);
2809
+ visit(node.body);
2810
+ });
2811
+ return;
2812
+ }
2813
+ if (isForInStatement2(node)) {
2814
+ visitEach(node.expressions);
2815
+ inScope(() => {
2816
+ for (const name of node.names) {
2817
+ declare(name.text);
2818
+ }
2819
+ visit(node.body);
2820
+ });
2821
+ return;
2822
+ }
2823
+ if (isRepeatStatement(node)) {
2824
+ inScope(() => {
2825
+ visitEach(node.body.statements);
2826
+ visit(node.condition);
2827
+ });
2828
+ return;
2829
+ }
2830
+ if (isBlock3(node) || isDoStatement(node)) {
2831
+ inScope(() => visitEach(node.statements));
2832
+ return;
2833
+ }
2834
+ if (isMethodCallExpression(node)) {
2835
+ visit(node.prefixExpression);
2836
+ visitEach(node.params);
2837
+ return;
2838
+ }
2839
+ for (const value of Object.values(node)) {
2840
+ if (Array.isArray(value)) {
2841
+ for (const element of value) {
2842
+ if (isLuaNode(element)) {
2843
+ visit(element);
2844
+ }
2845
+ }
2846
+ } else if (isLuaNode(value)) {
2847
+ visit(value);
2848
+ }
2849
+ }
2850
+ }
2851
+ for (const statement of statements) {
2852
+ for (const name of declaredNames(statement)) {
2853
+ declare(name);
2854
+ }
2855
+ }
2856
+ for (const statement of statements) {
2857
+ visit(statement);
2858
+ }
2859
+ return free;
2860
+ }
2861
+ function resolvedRequirePaths(sourceFile, checker) {
2862
+ const paths = new Map;
2863
+ for (const statement of sourceFile.statements) {
2864
+ const specifier = ts9.isImportDeclaration(statement) || ts9.isExportDeclaration(statement) ? statement.moduleSpecifier : undefined;
2865
+ if (specifier === undefined || !ts9.isStringLiteral(specifier)) {
2866
+ continue;
2867
+ }
2868
+ const target = checker.getSymbolAtLocation(specifier)?.declarations?.find((declaration) => ts9.isSourceFile(declaration));
2869
+ if (target === undefined || target.isDeclarationFile) {
2870
+ continue;
2871
+ }
2872
+ paths.set(specifier.text, requirePathForRel(target.fileName));
2873
+ }
2874
+ return paths;
2875
+ }
2876
+ function resolveMovedRequires(statements, paths) {
2877
+ for (const statement of statements) {
2878
+ forEachLuaNode(statement, (node) => {
2879
+ if (!isCallExpression4(node) || !isIdentifier4(node.expression)) {
2880
+ return;
2881
+ }
2882
+ if (node.expression.text !== "require" || node.params.length !== 1) {
2883
+ return;
2884
+ }
2885
+ const [argument] = node.params;
2886
+ if (argument === undefined || !isStringLiteral6(argument)) {
2887
+ return;
2888
+ }
2889
+ const resolved = paths.get(argument.value);
2890
+ if (resolved !== undefined) {
2891
+ argument.value = resolved;
2892
+ }
2893
+ });
2894
+ }
2895
+ }
2896
+ function withResolvedRequire(statement, paths) {
2897
+ if (!isVariableDeclarationStatement(statement) || statement.left.length !== 1) {
2898
+ return statement;
2899
+ }
2900
+ const name = statement.left[0];
2901
+ const [value] = statement.right ?? [];
2902
+ if (name === undefined || value === undefined || !isCallExpression4(value)) {
2903
+ return statement;
2904
+ }
2905
+ if (!isIdentifier4(value.expression) || value.expression.text !== "require") {
2906
+ return statement;
2907
+ }
2908
+ const [argument] = value.params;
2909
+ if (argument === undefined || !isStringLiteral6(argument)) {
2910
+ return statement;
2911
+ }
2912
+ const resolved = paths.get(argument.value);
2913
+ if (resolved === undefined) {
2914
+ return statement;
2915
+ }
2916
+ return createVariableDeclarationStatement3(createIdentifier4(name.text), createCallExpression4(createIdentifier4("require"), [createStringLiteral3(resolved)]));
2917
+ }
2918
+ function copiedPrelude(halves, importBindings) {
2919
+ const needed = new Set(importBindings);
2920
+ for (const name of freeNames(halves.companion)) {
2921
+ needed.add(name);
2922
+ }
2923
+ const alreadyCarried = new Set(halves.companion);
2924
+ const candidates = halves.script.filter((statement) => isVariableDeclarationStatement(statement) && !alreadyCarried.has(statement));
2925
+ const copied = new Set;
2926
+ let grew = true;
2927
+ while (grew) {
2928
+ grew = false;
2929
+ for (const statement of candidates) {
2930
+ if (copied.has(statement) || !declaredNames(statement).some((name) => needed.has(name))) {
2931
+ continue;
2932
+ }
2933
+ copied.add(statement);
2934
+ for (const name of freeNames([statement])) {
2935
+ needed.add(name);
2936
+ }
2937
+ grew = true;
2938
+ }
2939
+ }
2940
+ return halves.script.filter((statement) => copied.has(statement));
2941
+ }
2942
+ function namesIdentifier(statement, names) {
2943
+ let found = false;
2944
+ forEachLuaNode(statement, (node) => {
2945
+ if (isIdentifier4(node) && names.has(node.text)) {
2946
+ found = true;
2947
+ }
2948
+ });
2949
+ return found;
2950
+ }
2951
+ function isExportsTableInit(statement) {
2952
+ if (!isVariableDeclarationStatement(statement) || statement.left.length !== 1) {
2953
+ return false;
2954
+ }
2955
+ const [single] = statement.right ?? [];
2956
+ return statement.left[0]?.text === EXPORTS_NAME && single !== undefined && isTableExpression(single);
2957
+ }
2958
+ function isExportsReturn(statement) {
2959
+ if (!isReturnStatement2(statement) || statement.expressions.length !== 1) {
2960
+ return false;
2961
+ }
2962
+ const [single] = statement.expressions;
2963
+ return single !== undefined && isIdentifier4(single) && single.text === EXPORTS_NAME;
2964
+ }
2965
+ function isHoistedNameDeclaration(statement) {
2966
+ return isVariableDeclarationStatement(statement) && statement.right === undefined;
2967
+ }
2968
+ function internalsField(name) {
2969
+ return createTableIndexExpression2(createTableIndexExpression2(exportsIdentifier(), createStringLiteral3(COMPANION_INTERNAL_FIELD)), createStringLiteral3(name));
2970
+ }
2971
+ function requireInsertionPoint(script, reads) {
2972
+ let index = 0;
2973
+ while (index < script.length) {
2974
+ const statement = script[index];
2975
+ if (statement === undefined || !isVariableDeclarationStatement(statement)) {
2976
+ break;
2977
+ }
2978
+ if (namesIdentifier(statement, reads)) {
2979
+ break;
2980
+ }
2981
+ index++;
2982
+ }
2983
+ return index;
2984
+ }
2985
+ function split(statements, owned) {
2986
+ const script = [];
2987
+ const companion = [];
2988
+ for (const statement of statements) {
2989
+ if (isExportsTableInit(statement) || isExportsReturn(statement)) {
2990
+ continue;
2991
+ }
2992
+ if (owned.has(statement)) {
2993
+ companion.push(statement);
2994
+ continue;
2995
+ }
2996
+ if (isHoistedNameDeclaration(statement)) {
2997
+ companion.push(statement);
2998
+ }
2999
+ script.push(statement);
3000
+ }
3001
+ return { script, companion };
3002
+ }
3003
+ function createCompanionEmitPlugin() {
3004
+ const plans = new Map;
3005
+ const owned = new Map;
3006
+ const companions = new Map;
3007
+ function planOf(sourceFile, checker) {
3008
+ const key = sourceFile.fileName;
3009
+ if (!plans.has(key)) {
3010
+ plans.set(key, planFor(sourceFile, checker));
3011
+ }
3012
+ return plans.get(key);
3013
+ }
3014
+ function capture(node, context) {
3015
+ const statements = context.superTransformStatements(node);
3016
+ const sourceFile = context.sourceFile;
3017
+ const plan = planOf(sourceFile, context.checker);
3018
+ if (plan === undefined || !plan.closureStarts.has(node.pos)) {
3019
+ return statements;
3020
+ }
3021
+ let bucket = owned.get(sourceFile.fileName);
3022
+ if (bucket === undefined) {
3023
+ bucket = new Set;
3024
+ owned.set(sourceFile.fileName, bucket);
3025
+ }
3026
+ for (const statement of statements) {
3027
+ bucket.add(statement);
3028
+ }
3029
+ return statements;
3030
+ }
3031
+ const visitors = {
3032
+ [ts9.SyntaxKind.VariableStatement]: capture,
3033
+ [ts9.SyntaxKind.FunctionDeclaration]: capture,
3034
+ [ts9.SyntaxKind.ClassDeclaration]: capture,
3035
+ [ts9.SyntaxKind.EnumDeclaration]: capture,
3036
+ [ts9.SyntaxKind.ExportDeclaration]: capture
3037
+ };
3038
+ let configuredNoResolvePaths = [];
3039
+ const plugin = {
3040
+ visitors,
3041
+ beforeTransform(_program, options) {
3042
+ configuredNoResolvePaths = options.noResolvePaths ?? [];
3043
+ },
3044
+ afterPrint(program, options, emitHost, result) {
3045
+ const emitted = [];
3046
+ for (const file of result) {
3047
+ const sourceFile = file.sourceFiles?.[0];
3048
+ if (sourceFile === undefined || file.luaAst === undefined) {
3049
+ continue;
3050
+ }
3051
+ const plan = plans.get(sourceFile.fileName);
3052
+ const bucket = owned.get(sourceFile.fileName);
3053
+ if (plan === undefined || bucket === undefined || bucket.size === 0) {
3054
+ continue;
3055
+ }
3056
+ emitCompanion(program, emitHost, file, sourceFile, plan, bucket, companions);
3057
+ emitted.push(plan.requirePath);
3058
+ }
3059
+ if (emitted.length > 0) {
3060
+ options.noResolvePaths = [...configuredNoResolvePaths, ...emitted];
3061
+ }
3062
+ }
3063
+ };
3064
+ return { plugin, companions };
3065
+ }
3066
+ function emitCompanion(program, emitHost, file, sourceFile, plan, owned, companions) {
3067
+ const luaAst = file.luaAst;
3068
+ if (luaAst === undefined) {
3069
+ return;
3070
+ }
3071
+ const halves = split(luaAst.statements, owned);
3072
+ const requirePaths = resolvedRequirePaths(sourceFile, program.getTypeChecker());
3073
+ resolveMovedRequires(halves.companion, requirePaths);
3074
+ const closureTable = createTableExpression(plan.internals.map((name) => createTableFieldExpression(createIdentifier4(name), createStringLiteral3(name))));
3075
+ const companionStatements = [
3076
+ createVariableDeclarationStatement3(exportsIdentifier(), createTableExpression()),
3077
+ ...copiedPrelude(halves, plan.importBindings).map((statement) => withResolvedRequire(statement, requirePaths)),
3078
+ ...halves.companion,
3079
+ ...plan.internals.length > 0 ? [
3080
+ createAssignmentStatement2(createTableIndexExpression2(exportsIdentifier(), createStringLiteral3(COMPANION_INTERNAL_FIELD)), closureTable)
3081
+ ] : [],
3082
+ createReturnStatement2([exportsIdentifier()])
3083
+ ];
3084
+ const reads = new Set([EXPORTS_NAME, ...plan.internals]);
3085
+ const insertAt = requireInsertionPoint(halves.script, reads);
3086
+ const scriptStatements = [
3087
+ ...halves.script.slice(0, insertAt),
3088
+ createVariableDeclarationStatement3(exportsIdentifier(), createCallExpression4(createIdentifier4("require"), [createStringLiteral3(plan.requirePath)])),
3089
+ ...plan.internals.map((name) => createVariableDeclarationStatement3(createIdentifier4(name), internalsField(name))),
3090
+ ...halves.script.slice(insertAt),
3091
+ createReturnStatement2([exportsIdentifier()])
3092
+ ];
3093
+ const features = new Set(luaAst.luaLibFeatures);
3094
+ const printer = new LuaPrinter(emitHost, program, sourceFile.fileName);
3095
+ companions.set(sourceFile.fileName, printer.print(createFile(companionStatements, new Set(features), luaAst.trivia, sourceFile)).code);
3096
+ const scriptFile = createFile(scriptStatements, features, luaAst.trivia, sourceFile);
3097
+ const printed = new LuaPrinter(emitHost, program, sourceFile.fileName).print(scriptFile);
3098
+ file.luaAst = scriptFile;
3099
+ file.code = printed.code;
3100
+ file.sourceMap = printed.sourceMap;
3101
+ file.sourceMapNode = printed.sourceMapNode;
3102
+ }
3103
+
2058
3104
  // src/editor-script-erasure.ts
2059
- import * as ts8 from "typescript";
3105
+ import * as ts10 from "typescript";
2060
3106
  import {
2061
- createReturnStatement as createReturnStatement2
3107
+ createReturnStatement as createReturnStatement3
2062
3108
  } from "typescript-to-lua";
2063
3109
  var EDITOR_FACTORY_MODULE = "@defold-typescript/types";
2064
3110
  var EDITOR_SCRIPT_FACTORY_NAME = "defineEditorScript";
@@ -2076,7 +3122,7 @@ function resolveEditorFactoryExport(callee, checker) {
2076
3122
  if (symbol === undefined) {
2077
3123
  return;
2078
3124
  }
2079
- if (symbol.flags & ts8.SymbolFlags.Alias) {
3125
+ if (symbol.flags & ts10.SymbolFlags.Alias) {
2080
3126
  symbol = checker.getAliasedSymbol(symbol);
2081
3127
  }
2082
3128
  const name = symbol.getName();
@@ -2090,7 +3136,7 @@ function resolveEditorFactoryExport(callee, checker) {
2090
3136
  return declaration.getSourceFile().fileName.includes(EDITOR_FACTORY_MODULE) ? name : undefined;
2091
3137
  }
2092
3138
  function lowerEditorFactoryCall(expression, context) {
2093
- if (!ts8.isCallExpression(expression)) {
3139
+ if (!ts10.isCallExpression(expression)) {
2094
3140
  return;
2095
3141
  }
2096
3142
  if (resolveEditorFactoryExport(expression.expression, context.checker) !== EDITOR_SCRIPT_FACTORY_NAME) {
@@ -2100,10 +3146,10 @@ function lowerEditorFactoryCall(expression, context) {
2100
3146
  if (module === undefined) {
2101
3147
  return;
2102
3148
  }
2103
- return [createReturnStatement2([context.transformExpression(module)])];
3149
+ return [createReturnStatement3([context.transformExpression(module)])];
2104
3150
  }
2105
3151
  function isEditorFactoryOnlyImport(node) {
2106
- if (!ts8.isStringLiteral(node.moduleSpecifier) || !EDITOR_FACTORY_SPECIFIERS.has(node.moduleSpecifier.text)) {
3152
+ if (!ts10.isStringLiteral(node.moduleSpecifier) || !EDITOR_FACTORY_SPECIFIERS.has(node.moduleSpecifier.text)) {
2107
3153
  return false;
2108
3154
  }
2109
3155
  const clause = node.importClause;
@@ -2114,7 +3160,7 @@ function isEditorFactoryOnlyImport(node) {
2114
3160
  return false;
2115
3161
  }
2116
3162
  const bindings = clause.namedBindings;
2117
- if (bindings === undefined || !ts8.isNamedImports(bindings)) {
3163
+ if (bindings === undefined || !ts10.isNamedImports(bindings)) {
2118
3164
  return false;
2119
3165
  }
2120
3166
  const runtime = bindings.elements.filter((element) => !element.isTypeOnly);
@@ -2125,7 +3171,7 @@ function isEditorFactoryOnlyImport(node) {
2125
3171
  }
2126
3172
  var editorScriptErasurePlugin = {
2127
3173
  visitors: {
2128
- [ts8.SyntaxKind.ExportAssignment]: (node, context) => {
3174
+ [ts10.SyntaxKind.ExportAssignment]: (node, context) => {
2129
3175
  if (!node.isExportEquals) {
2130
3176
  const lowered = lowerEditorFactoryCall(node.expression, context);
2131
3177
  if (lowered !== undefined) {
@@ -2134,14 +3180,14 @@ var editorScriptErasurePlugin = {
2134
3180
  }
2135
3181
  return context.superTransformStatements(node);
2136
3182
  },
2137
- [ts8.SyntaxKind.CallExpression]: (node, context) => {
3183
+ [ts10.SyntaxKind.CallExpression]: (node, context) => {
2138
3184
  const command = node.arguments[0];
2139
3185
  if (command !== undefined && resolveEditorFactoryExport(node.expression, context.checker) === EDITOR_COMMAND_FACTORY_NAME) {
2140
3186
  return context.transformExpression(command);
2141
3187
  }
2142
3188
  return context.superTransformExpression(node);
2143
3189
  },
2144
- [ts8.SyntaxKind.ImportDeclaration]: (node, context) => isEditorFactoryOnlyImport(node) ? [] : context.superTransformStatements(node)
3190
+ [ts10.SyntaxKind.ImportDeclaration]: (node, context) => isEditorFactoryOnlyImport(node) ? [] : context.superTransformStatements(node)
2145
3191
  }
2146
3192
  };
2147
3193
 
@@ -2152,17 +3198,17 @@ import * as path2 from "node:path";
2152
3198
  import * as tstl2 from "typescript-to-lua";
2153
3199
 
2154
3200
  // src/go-property-direct-call.ts
2155
- import * as ts9 from "typescript";
3201
+ import * as ts11 from "typescript";
2156
3202
  var GO_PROPERTY_DIRECT_CALL_MESSAGE = "`go.property` called directly is deprecated; declare the property in `defineScript({ properties })` so it types onto `self` (the transpiler emits the registration for you).";
2157
3203
  function findDirectGoPropertyCalls(sourceFile) {
2158
3204
  const calls = [];
2159
3205
  const visit = (node) => {
2160
- if (ts9.isCallExpression(node) && ts9.isPropertyAccessExpression(node.expression) && node.expression.name.text === "property" && ts9.isIdentifier(node.expression.expression) && node.expression.expression.text === "go") {
3206
+ if (ts11.isCallExpression(node) && ts11.isPropertyAccessExpression(node.expression) && node.expression.name.text === "property" && ts11.isIdentifier(node.expression.expression) && node.expression.expression.text === "go") {
2161
3207
  calls.push(node);
2162
3208
  }
2163
- ts9.forEachChild(node, visit);
3209
+ ts11.forEachChild(node, visit);
2164
3210
  };
2165
- ts9.forEachChild(sourceFile, visit);
3211
+ ts11.forEachChild(sourceFile, visit);
2166
3212
  return calls;
2167
3213
  }
2168
3214
 
@@ -2261,9 +3307,10 @@ var LUALIB_BUNDLE_NAME = "lualib_bundle.lua";
2261
3307
  function isLualibBundle(file) {
2262
3308
  return file.sourceFiles.length === 0 && file.outPath.endsWith(LUALIB_BUNDLE_NAME);
2263
3309
  }
2264
- function collectOutputs(transpiledFiles, diagnostics, userKeys) {
3310
+ function collectOutputs(transpiledFiles, diagnostics, userKeys, emittedCompanions = new Map) {
2265
3311
  const lua = {};
2266
3312
  const sourceMaps = {};
3313
+ const companions = {};
2267
3314
  let lualib;
2268
3315
  for (const file of transpiledFiles) {
2269
3316
  if (isLualibBundle(file) && typeof file.lua === "string") {
@@ -2278,6 +3325,10 @@ function collectOutputs(transpiledFiles, diagnostics, userKeys) {
2278
3325
  if (typeof file.luaSourceMap === "string") {
2279
3326
  sourceMaps[userSource.fileName] = file.luaSourceMap;
2280
3327
  }
3328
+ const companion = emittedCompanions.get(userSource.fileName);
3329
+ if (companion !== undefined) {
3330
+ companions[userSource.fileName] = companion;
3331
+ }
2281
3332
  }
2282
3333
  const collectedDiagnostics = diagnostics.map((d) => {
2283
3334
  const fileName = d.file?.fileName;
@@ -2315,6 +3366,7 @@ function collectOutputs(transpiledFiles, diagnostics, userKeys) {
2315
3366
  lua,
2316
3367
  sourceMaps,
2317
3368
  diagnostics: collectedDiagnostics,
3369
+ ...Object.keys(companions).length > 0 ? { companions } : {},
2318
3370
  ...lualib !== undefined ? { lualib } : {},
2319
3371
  ...timersImported ? { timersRuntime: TIMERS_RUNTIME } : {}
2320
3372
  };
@@ -2322,6 +3374,7 @@ function collectOutputs(transpiledFiles, diagnostics, userKeys) {
2322
3374
  function transpileProject(input) {
2323
3375
  const userKeys = new Set(Object.keys(input.files));
2324
3376
  const merged = { ...AMBIENT_FILES, ...input.files };
3377
+ const companionEmit = createCompanionEmitPlugin();
2325
3378
  const result = tstl2.transpileVirtualProject(merged, {
2326
3379
  luaTarget: tstl2.LuaTarget.Lua51,
2327
3380
  sourceMap: true,
@@ -2334,10 +3387,11 @@ function transpileProject(input) {
2334
3387
  { plugin: messageGuardLoweringPlugin },
2335
3388
  { plugin: windowEventGuardLoweringPlugin },
2336
3389
  { plugin: messageDispatchLoweringPlugin },
2337
- { plugin: timersLoweringPlugin }
3390
+ { plugin: timersLoweringPlugin },
3391
+ { plugin: companionEmit.plugin }
2338
3392
  ]
2339
3393
  });
2340
- return collectOutputs(result.transpiledFiles, result.diagnostics, userKeys);
3394
+ return collectOutputs(result.transpiledFiles, result.diagnostics, userKeys, companionEmit.companions);
2341
3395
  }
2342
3396
  function transpile(source) {
2343
3397
  const project = transpileProject({ files: { "main.ts": source } });
@@ -2350,21 +3404,24 @@ function transpile(source) {
2350
3404
 
2351
3405
  // src/session.ts
2352
3406
  var requireFromHere2 = createRequire2(import.meta.url);
2353
- var COMPILER_OPTIONS = {
2354
- luaTarget: tstl3.LuaTarget.Lua51,
2355
- sourceMap: true,
2356
- lib: ["lib.es2022.d.ts"],
2357
- skipLibCheck: true,
2358
- noImplicitSelf: true,
2359
- luaPlugins: [
2360
- { plugin: lifecycleErasurePlugin },
2361
- { plugin: editorScriptErasurePlugin },
2362
- { plugin: messageGuardLoweringPlugin },
2363
- { plugin: windowEventGuardLoweringPlugin },
2364
- { plugin: messageDispatchLoweringPlugin },
2365
- { plugin: timersLoweringPlugin }
2366
- ]
2367
- };
3407
+ function compilerOptions(companionEmit) {
3408
+ return {
3409
+ luaTarget: tstl3.LuaTarget.Lua51,
3410
+ sourceMap: true,
3411
+ lib: ["lib.es2022.d.ts"],
3412
+ skipLibCheck: true,
3413
+ noImplicitSelf: true,
3414
+ luaPlugins: [
3415
+ { plugin: lifecycleErasurePlugin },
3416
+ { plugin: editorScriptErasurePlugin },
3417
+ { plugin: messageGuardLoweringPlugin },
3418
+ { plugin: windowEventGuardLoweringPlugin },
3419
+ { plugin: messageDispatchLoweringPlugin },
3420
+ { plugin: timersLoweringPlugin },
3421
+ { plugin: companionEmit.plugin }
3422
+ ]
3423
+ };
3424
+ }
2368
3425
  function normalizeSlashes(p) {
2369
3426
  return p.replace(/\\/g, "/");
2370
3427
  }
@@ -2403,7 +3460,7 @@ function createTranspileSession() {
2403
3460
  if (cached) {
2404
3461
  return cached;
2405
3462
  }
2406
- const created = ts10.createSourceFile(fileName, content, ts10.ScriptTarget.Latest, false);
3463
+ const created = ts12.createSourceFile(fileName, content, ts12.ScriptTarget.Latest, false);
2407
3464
  sourceFileCache.set(normalized, created);
2408
3465
  return created;
2409
3466
  }
@@ -2422,7 +3479,7 @@ function createTranspileSession() {
2422
3479
  return cached;
2423
3480
  }
2424
3481
  const fileContent = readFileSync2(filePath, "utf8");
2425
- const created = ts10.createSourceFile(filePath, fileContent, ts10.ScriptTarget.Latest, false);
3482
+ const created = ts12.createSourceFile(filePath, fileContent, ts12.ScriptTarget.Latest, false);
2426
3483
  libCache.set(fileName, created);
2427
3484
  return created;
2428
3485
  }
@@ -2435,10 +3492,10 @@ function createTranspileSession() {
2435
3492
  return AMBIENT_FILES[normalized];
2436
3493
  }
2437
3494
  const host = {
2438
- fileExists: (fileName) => mergedContent(normalizeSlashes(fileName)) !== undefined || ts10.sys.fileExists(fileName),
3495
+ fileExists: (fileName) => mergedContent(normalizeSlashes(fileName)) !== undefined || ts12.sys.fileExists(fileName),
2439
3496
  getCanonicalFileName: (fileName) => fileName,
2440
3497
  getCurrentDirectory: () => "",
2441
- getDefaultLibFileName: ts10.getDefaultLibFileName,
3498
+ getDefaultLibFileName: ts12.getDefaultLibFileName,
2442
3499
  readFile: () => "",
2443
3500
  getNewLine: () => `
2444
3501
  `,
@@ -2459,17 +3516,18 @@ function createTranspileSession() {
2459
3516
  }
2460
3517
  const userKeys = new Set(userFiles.keys());
2461
3518
  const rootNames = [...Object.keys(AMBIENT_FILES).map(normalizeSlashes), ...userKeys];
2462
- program = ts10.createProgram(rootNames, COMPILER_OPTIONS, host, program);
2463
- const preEmitDiagnostics = ts10.getPreEmitDiagnostics(program);
3519
+ const companionEmit = createCompanionEmitPlugin();
3520
+ program = ts12.createProgram(rootNames, compilerOptions(companionEmit), host, program);
3521
+ const preEmitDiagnostics = ts12.getPreEmitDiagnostics(program);
2464
3522
  const collector = createOutputCollector();
2465
3523
  const { diagnostics: transpileDiagnostics } = new tstl3.Transpiler().emit({
2466
3524
  program,
2467
3525
  writeFile: collector.writeFile
2468
3526
  });
2469
3527
  const diagnostics = [
2470
- ...ts10.sortAndDeduplicateDiagnostics([...preEmitDiagnostics, ...transpileDiagnostics])
3528
+ ...ts12.sortAndDeduplicateDiagnostics([...preEmitDiagnostics, ...transpileDiagnostics])
2471
3529
  ];
2472
- return collectOutputs(collector.files, diagnostics, userKeys);
3530
+ return collectOutputs(collector.files, diagnostics, userKeys, companionEmit.companions);
2473
3531
  }
2474
3532
  return {
2475
3533
  update,
@@ -2477,7 +3535,7 @@ function createTranspileSession() {
2477
3535
  };
2478
3536
  }
2479
3537
  // src/url-address-slots.ts
2480
- import * as ts11 from "typescript";
3538
+ import * as ts13 from "typescript";
2481
3539
  var ADDRESS_CLASSES = {
2482
3540
  "game-object": true,
2483
3541
  component: true,
@@ -2496,7 +3554,7 @@ function isFragmentCaret(slot, position) {
2496
3554
  return slot.fragmentStart !== -1 && position >= slot.fragmentStart;
2497
3555
  }
2498
3556
  function tableKey(checker, callee) {
2499
- const symbol = checker.getSymbolAtLocation(ts11.isIdentifier(callee) ? callee : callee.name);
3557
+ const symbol = checker.getSymbolAtLocation(ts13.isIdentifier(callee) ? callee : callee.name);
2500
3558
  if (!symbol)
2501
3559
  return;
2502
3560
  const fqn = checker.getFullyQualifiedName(symbol);
@@ -2504,43 +3562,43 @@ function tableKey(checker, callee) {
2504
3562
  }
2505
3563
  function parameterName(checker, call, index) {
2506
3564
  const declaration = checker.getResolvedSignature(call)?.declaration;
2507
- if (!declaration || !ts11.isFunctionLike(declaration))
3565
+ if (!declaration || !ts13.isFunctionLike(declaration))
2508
3566
  return;
2509
3567
  const parameter = declaration.parameters[index];
2510
- if (!parameter || parameter.dotDotDotToken || !ts11.isIdentifier(parameter.name))
3568
+ if (!parameter || parameter.dotDotDotToken || !ts13.isIdentifier(parameter.name))
2511
3569
  return;
2512
3570
  return parameter.name.text;
2513
3571
  }
2514
3572
  var EQUALITY_OPERATORS = new Set([
2515
- ts11.SyntaxKind.EqualsEqualsEqualsToken,
2516
- ts11.SyntaxKind.ExclamationEqualsEqualsToken,
2517
- ts11.SyntaxKind.EqualsEqualsToken,
2518
- ts11.SyntaxKind.ExclamationEqualsToken
3573
+ ts13.SyntaxKind.EqualsEqualsEqualsToken,
3574
+ ts13.SyntaxKind.ExclamationEqualsEqualsToken,
3575
+ ts13.SyntaxKind.EqualsEqualsToken,
3576
+ ts13.SyntaxKind.ExclamationEqualsToken
2519
3577
  ]);
2520
3578
  function comparisonSatisfied(checker, entry, call) {
2521
3579
  if (entry.comparedParameter === undefined)
2522
3580
  return true;
2523
3581
  let node = call;
2524
- while (ts11.isParenthesizedExpression(node.parent))
3582
+ while (ts13.isParenthesizedExpression(node.parent))
2525
3583
  node = node.parent;
2526
3584
  const comparison = node.parent;
2527
- if (!ts11.isBinaryExpression(comparison))
3585
+ if (!ts13.isBinaryExpression(comparison))
2528
3586
  return false;
2529
3587
  if (!EQUALITY_OPERATORS.has(comparison.operatorToken.kind))
2530
3588
  return false;
2531
3589
  const other = comparison.left === node ? comparison.right : comparison.left;
2532
3590
  const symbol = checker.getSymbolAtLocation(other);
2533
- return (symbol?.declarations ?? []).some((declaration) => ts11.isParameter(declaration) && ts11.isIdentifier(declaration.name) && declaration.name.text === entry.comparedParameter);
3591
+ return (symbol?.declarations ?? []).some((declaration) => ts13.isParameter(declaration) && ts13.isIdentifier(declaration.name) && declaration.name.text === entry.comparedParameter);
2534
3592
  }
2535
3593
  function classifiedEntryOfArgument(checker, table, argument) {
2536
3594
  const call = argument.parent;
2537
- if (!ts11.isCallExpression(call))
3595
+ if (!ts13.isCallExpression(call))
2538
3596
  return;
2539
3597
  const argumentIndex = call.arguments.indexOf(argument);
2540
3598
  if (argumentIndex === -1)
2541
3599
  return;
2542
3600
  const callee = call.expression;
2543
- if (!ts11.isPropertyAccessExpression(callee) && !ts11.isIdentifier(callee))
3601
+ if (!ts13.isPropertyAccessExpression(callee) && !ts13.isIdentifier(callee))
2544
3602
  return;
2545
3603
  const fqn = tableKey(checker, callee);
2546
3604
  if (fqn === undefined)
@@ -2557,10 +3615,10 @@ function classifiedEntryOfArgument(checker, table, argument) {
2557
3615
  }
2558
3616
  function slotThroughHashedCall(checker, table, literal) {
2559
3617
  const call = literal.parent;
2560
- if (!ts11.isCallExpression(call))
3618
+ if (!ts13.isCallExpression(call))
2561
3619
  return;
2562
3620
  const callee = call.expression;
2563
- if (!ts11.isPropertyAccessExpression(callee) && !ts11.isIdentifier(callee))
3621
+ if (!ts13.isPropertyAccessExpression(callee) && !ts13.isIdentifier(callee))
2564
3622
  return;
2565
3623
  if (tableKey(checker, callee) !== "hash")
2566
3624
  return;
@@ -2578,24 +3636,24 @@ function addressTextOf(checker, entry, call) {
2578
3636
  if (entry.addressParameter === undefined)
2579
3637
  return;
2580
3638
  const declaration = checker.getResolvedSignature(call)?.declaration;
2581
- if (!declaration || !ts11.isFunctionLike(declaration))
3639
+ if (!declaration || !ts13.isFunctionLike(declaration))
2582
3640
  return;
2583
- const index = declaration.parameters.findIndex((parameter) => !parameter.dotDotDotToken && ts11.isIdentifier(parameter.name) && parameter.name.text === entry.addressParameter);
3641
+ const index = declaration.parameters.findIndex((parameter) => !parameter.dotDotDotToken && ts13.isIdentifier(parameter.name) && parameter.name.text === entry.addressParameter);
2584
3642
  if (index === -1)
2585
3643
  return;
2586
3644
  const argument = call.arguments[index];
2587
- return argument && ts11.isStringLiteralLike(argument) ? argument.text : undefined;
3645
+ return argument && ts13.isStringLiteralLike(argument) ? argument.text : undefined;
2588
3646
  }
2589
3647
  function innermostLiteralAt(sourceFile, position) {
2590
3648
  let found;
2591
3649
  const visit = (node) => {
2592
3650
  if (position < node.getStart(sourceFile) || position > node.getEnd())
2593
3651
  return;
2594
- if (ts11.isStringLiteralLike(node))
3652
+ if (ts13.isStringLiteralLike(node))
2595
3653
  found = node;
2596
- ts11.forEachChild(node, visit);
3654
+ ts13.forEachChild(node, visit);
2597
3655
  };
2598
- ts11.forEachChild(sourceFile, visit);
3656
+ ts13.forEachChild(sourceFile, visit);
2599
3657
  return found;
2600
3658
  }
2601
3659
  function resolveClassifiedSlotAtPosition(input) {
@@ -2629,23 +3687,23 @@ function resolveClassifiedSlotAtPosition(input) {
2629
3687
  };
2630
3688
  }
2631
3689
  // src/url-cross-world-address.ts
2632
- import * as ts13 from "typescript";
3690
+ import * as ts15 from "typescript";
2633
3691
 
2634
3692
  // src/url-address-literals.ts
2635
- import * as ts12 from "typescript";
3693
+ import * as ts14 from "typescript";
2636
3694
  function isAmbient2(fileName) {
2637
3695
  return /[\\/]node_modules[\\/]/.test(fileName) || /(^|[\\/])lib\.[^\\/]*\.d\.ts$/.test(fileName);
2638
3696
  }
2639
3697
  function referenceTargetSymbol(type) {
2640
- if ((type.flags & ts12.TypeFlags.Object) === 0)
3698
+ if ((type.flags & ts14.TypeFlags.Object) === 0)
2641
3699
  return;
2642
- if ((type.objectFlags & ts12.ObjectFlags.Reference) === 0)
3700
+ if ((type.objectFlags & ts14.ObjectFlags.Reference) === 0)
2643
3701
  return;
2644
3702
  return type.target.symbol;
2645
3703
  }
2646
3704
  function ambientHashAliasTargets(checker, anchor) {
2647
3705
  const targets = new Set;
2648
- for (const symbol of checker.getSymbolsInScope(anchor, ts12.SymbolFlags.Type)) {
3706
+ for (const symbol of checker.getSymbolsInScope(anchor, ts14.SymbolFlags.Type)) {
2649
3707
  if (symbol.name !== "Hash")
2650
3708
  continue;
2651
3709
  const target = referenceTargetSymbol(checker.getDeclaredTypeOfSymbol(symbol));
@@ -2661,7 +3719,7 @@ function canonicalHashSymbols(checker, program) {
2661
3719
  if (!anchor)
2662
3720
  return canonical;
2663
3721
  const aliased = ambientHashAliasTargets(checker, anchor);
2664
- for (const symbol of checker.getSymbolsInScope(anchor, ts12.SymbolFlags.Function)) {
3722
+ for (const symbol of checker.getSymbolsInScope(anchor, ts14.SymbolFlags.Function)) {
2665
3723
  if (symbol.name !== "hash")
2666
3724
  continue;
2667
3725
  const declaration = symbol.declarations?.[0];
@@ -2683,7 +3741,7 @@ function hashedSourceOfType(checker, type, canonical) {
2683
3741
  return source?.isStringLiteral() ? source.value : undefined;
2684
3742
  }
2685
3743
  function staticAddressTextOf(checker, node, canonical) {
2686
- return ts12.isStringLiteralLike(node) ? node.text : hashedSourceOfType(checker, checker.getTypeAtLocation(node), canonical);
3744
+ return ts14.isStringLiteralLike(node) ? node.text : hashedSourceOfType(checker, checker.getTypeAtLocation(node), canonical);
2687
3745
  }
2688
3746
 
2689
3747
  // src/url-cross-world-address.ts
@@ -2707,7 +3765,7 @@ function checkCrossWorldAddresses(input) {
2707
3765
  if (worlds.length === 0)
2708
3766
  continue;
2709
3767
  const visit = (node) => {
2710
- if (ts13.isExpression(node)) {
3768
+ if (ts15.isExpression(node)) {
2711
3769
  const entry = addressEntryOfArgument(checker, table, node);
2712
3770
  if (entry !== undefined && slotRejectsForeignSocket(entry)) {
2713
3771
  const address = staticAddressTextOf(checker, node, canonical);
@@ -2724,14 +3782,14 @@ function checkCrossWorldAddresses(input) {
2724
3782
  }
2725
3783
  }
2726
3784
  }
2727
- ts13.forEachChild(node, visit);
3785
+ ts15.forEachChild(node, visit);
2728
3786
  };
2729
- ts13.forEachChild(sourceFile, visit);
3787
+ ts15.forEachChild(sourceFile, visit);
2730
3788
  }
2731
3789
  return findings;
2732
3790
  }
2733
3791
  // src/url-fragment-reachability.ts
2734
- import * as ts14 from "typescript";
3792
+ import * as ts16 from "typescript";
2735
3793
  function pathOfKey(key) {
2736
3794
  const socket = socketOfAddress(key);
2737
3795
  return socket === undefined ? key : key.slice(socket.length + 1);
@@ -2776,7 +3834,7 @@ function checkUrlFragmentReachability(input) {
2776
3834
  if (isAmbient2(sourceFile.fileName))
2777
3835
  continue;
2778
3836
  const visit = (node) => {
2779
- if (ts14.isExpression(node) && isAddressClass(addressClassOfArgument(checker, table, node))) {
3837
+ if (ts16.isExpression(node) && isAddressClass(addressClassOfArgument(checker, table, node))) {
2780
3838
  const text = staticAddressTextOf(checker, node, canonical);
2781
3839
  if (text !== undefined) {
2782
3840
  const hash = text.indexOf("#");
@@ -2795,26 +3853,29 @@ function checkUrlFragmentReachability(input) {
2795
3853
  }
2796
3854
  }
2797
3855
  }
2798
- ts14.forEachChild(node, visit);
3856
+ ts16.forEachChild(node, visit);
2799
3857
  };
2800
- ts14.forEachChild(sourceFile, visit);
3858
+ ts16.forEachChild(sourceFile, visit);
2801
3859
  }
2802
3860
  return { kind: "checked", findings };
2803
3861
  }
2804
3862
  export {
2805
3863
  ANIMATION_ASSET_EXTENSIONS,
2806
3864
  COLLECTION_REFERENCE_EXTENSIONS,
3865
+ COMPANION_INTERNAL_FIELD,
2807
3866
  DEFAULT_INCLUDE,
2808
3867
  GAME_PROJECT_DOCUMENT,
2809
3868
  GUI_EXTENSIONS,
2810
3869
  INPUT_BINDING_EXTENSIONS,
2811
3870
  LIBRARY_DEPENDENCY_ROOT,
2812
3871
  LIBRARY_SCENE_EXTENSIONS,
3872
+ LUALIB_REQUIRE_NAME,
2813
3873
  PROJECT_EXTENSIONS,
2814
3874
  SCAFFOLDED_DEFIGNORE_LINES,
2815
3875
  SCENE_EXTENSIONS,
2816
3876
  SCRIPT_SUFFIX_BY_KIND,
2817
3877
  SceneTextFormatError,
3878
+ TIMERS_REQUIRE_NAME,
2818
3879
  addressClassOfArgument,
2819
3880
  addressEntryOfArgument,
2820
3881
  archiveWrapperOf,
@@ -2833,9 +3894,11 @@ export {
2833
3894
  collectComponentDeclarations,
2834
3895
  collectComponentIds,
2835
3896
  componentIdOfSameObjectAddress,
3897
+ computeCompanionClosure,
2836
3898
  computeOutputRel,
2837
3899
  createTranspileSession,
2838
3900
  displayPathOf,
3901
+ findEmittedRequires,
2839
3902
  findMainEntryFactoryImports,
2840
3903
  getProgramDiagnostics,
2841
3904
  guiScriptResourceOf,
@@ -2849,12 +3912,15 @@ export {
2849
3912
  listProjectResourcePaths,
2850
3913
  parseBuildConfig,
2851
3914
  parseSceneTextFormat,
3915
+ projectRelativeBase,
2852
3916
  readGameProjectDependencies,
2853
3917
  readGameProjectSetting,
2854
3918
  readLibrarySceneDocuments,
2855
3919
  readSceneDocuments,
2856
3920
  relativeAddressesFrom,
3921
+ requirePathForRel,
2857
3922
  resolveClassifiedSlotAtPosition,
3923
+ rewriteEmittedRequires,
2858
3924
  slotRejectsForeignSocket,
2859
3925
  socketOfAddress,
2860
3926
  stripIncludeBase,