@defold-typescript/transpiler 0.35.0 → 0.36.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;
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;
661
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 };
929
- }
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`);
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 === "")
936
1318
  continue;
937
- }
938
- resolved.push({ key, url });
939
- urlByKey.set(key, url);
1319
+ keys.add(`${section}.${key}`);
940
1320
  }
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, "/"))
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;
947
1330
  continue;
948
- const separator = relative.indexOf("/");
949
- if (separator <= 0)
1331
+ }
1332
+ if (current !== section)
950
1333
  continue;
951
- const key = relative.slice(0, separator);
952
- const url = urlByKey.get(key);
953
- if (url === undefined) {
954
- stray.add(key);
1334
+ const separator = line.indexOf("=");
1335
+ if (separator === -1)
955
1336
  continue;
956
- }
957
- const bucket = filesByKey.get(key);
958
- if (bucket === undefined)
959
- filesByKey.set(key, [filePath]);
960
- else
961
- bucket.push(filePath);
1337
+ if (line.slice(0, separator).trim() !== key)
1338
+ continue;
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;
1505
+ const key = resourceKey(script);
1506
+ return key === SCRIPT_SUFFIX2 ? undefined : key;
1507
+ }
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);
1050
1559
  }
1051
- if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
1052
- return name.text;
1053
- }
1054
- return;
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);
1055
1573
  }
1056
- function handlerFunction(property) {
1057
- if (ts.isMethodDeclaration(property) && property.body !== undefined) {
1058
- return property;
1059
- }
1060
- if (ts.isPropertyAssignment(property)) {
1061
- const initializer = property.initializer;
1062
- if (ts.isFunctionExpression(initializer) || ts.isArrowFunction(initializer)) {
1063
- return initializer;
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());
1733
+ }
1734
+
1735
+ // src/library-dependencies.ts
1736
+ function isProjectHeader(line) {
1737
+ return line.trim() === "[project]";
1191
1738
  }
1192
- function crossesFunctionBoundary(node) {
1193
- return ts2.isFunctionDeclaration(node) || ts2.isFunctionExpression(node) || ts2.isArrowFunction(node) || ts2.isMethodDeclaration(node) || ts2.isGetAccessorDeclaration(node) || ts2.isSetAccessorDeclaration(node);
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;
1775
+ }
1776
+ return wrapper;
1212
1777
  }
1213
- function initBuilderBody(fn, context) {
1214
- if (ts2.isBlock(fn.body)) {
1215
- return context.transformStatements(fn.body.statements);
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]";
1784
+ continue;
1785
+ }
1786
+ if (!inLibrary) {
1787
+ continue;
1788
+ }
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
+ }
1216
1793
  }
1217
- return [createReturnStatement([context.transformExpression(fn.body)])];
1794
+ return [];
1218
1795
  }
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)];
1796
+ function isContainedResourcePath(path) {
1797
+ if (path.length === 0 || path.includes("\\")) {
1798
+ return false;
1799
+ }
1800
+ return path.split("/").every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
1231
1801
  }
1232
- function emitPropertyRegistrations(property, context) {
1233
- if (!ts2.isPropertyAssignment(property) || !ts2.isObjectLiteralExpression(property.initializer)) {
1234
- return [];
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
+ };
1235
1809
  }
1236
- const registrations = [];
1237
- for (const member of property.initializer.properties) {
1238
- if (!ts2.isPropertyAssignment(member)) {
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
+ };
1817
+ }
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)) {
1239
1825
  continue;
1240
1826
  }
1241
- const key = member.name;
1242
- if (!ts2.isIdentifier(key) && !ts2.isStringLiteral(key)) {
1827
+ const dot = path.lastIndexOf(".");
1828
+ if (dot === -1 || !LIBRARY_SCENE_EXTENSIONS.includes(path.slice(dot).toLowerCase())) {
1243
1829
  continue;
1244
1830
  }
1245
- registrations.push(createExpressionStatement(createCallExpression2(createTableIndexExpression(createIdentifier2("go"), createStringLiteral2("property")), [createStringLiteral2(key.text), context.transformExpression(member.initializer)])));
1831
+ if (!isContainedResourcePath(path)) {
1832
+ refused.push(entry);
1833
+ continue;
1834
+ }
1835
+ shared.push({ entry, path });
1246
1836
  }
1247
- return registrations;
1837
+ shared.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
1838
+ return { shared, reasons: [], refused };
1248
1839
  }
1249
- function eraseFactoryCall(expression, context) {
1250
- if (!ts2.isCallExpression(expression)) {
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 {
1251
1850
  return;
1252
1851
  }
1253
- if (!resolvesToFactoryExport(expression.expression, context.checker)) {
1852
+ const entries = parsed?.dependencies;
1853
+ if (!Array.isArray(entries))
1254
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);
1255
1860
  }
1256
- const hooks = expression.arguments[0];
1257
- if (hooks === undefined || !ts2.isObjectLiteralExpression(hooks)) {
1258
- return;
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 };
1259
1880
  }
1260
- const registrations = [];
1261
- const statements = [];
1262
- for (const property of hooks.properties) {
1263
- const name = hookName(property);
1264
- if (name === undefined) {
1265
- continue;
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`);
1266
1885
  }
1267
- if (name === "properties") {
1268
- registrations.push(...emitPropertyRegistrations(property, context));
1886
+ return { documents, unreadable };
1887
+ }
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`);
1269
1894
  continue;
1270
1895
  }
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
- }
1896
+ resolved.push({ key, url });
1897
+ urlByKey.set(key, url);
1898
+ }
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, "/"))
1277
1905
  continue;
1278
- }
1279
- if (name === "init" && initReturnsValue(fn)) {
1280
- statements.push(...emitInitMerge(fn, context, property));
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);
1281
1913
  continue;
1282
1914
  }
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));
1286
- }
1287
- return [...registrations, ...statements];
1288
- }
1289
- function isFactoryOnlyImport(node) {
1290
- if (!ts2.isStringLiteral(node.moduleSpecifier) || !FACTORY_SPECIFIERS.has(node.moduleSpecifier.text)) {
1291
- return false;
1292
- }
1293
- const clause = node.importClause;
1294
- if (clause === undefined || clause.name !== undefined) {
1295
- return false;
1296
- }
1297
- if (clause.isTypeOnly) {
1298
- return false;
1915
+ const bucket = filesByKey.get(key);
1916
+ if (bucket === undefined)
1917
+ filesByKey.set(key, [filePath]);
1918
+ else
1919
+ bucket.push(filePath);
1299
1920
  }
1300
- const bindings = clause.namedBindings;
1301
- if (bindings === undefined || !ts2.isNamedImports(bindings)) {
1302
- return false;
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;
1931
+ }
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
+ }
1303
1940
  }
1304
- const runtime = bindings.elements.filter((element) => !element.isTypeOnly);
1305
- if (runtime.length === 0) {
1306
- return false;
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`);
1307
1943
  }
1308
- return runtime.every((element) => FACTORY_NAMES.has((element.propertyName ?? element.name).text));
1944
+ return { documents, unreadable };
1309
1945
  }
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
- }
1319
- }
1320
- return context.superTransformStatements(node);
1321
- },
1322
- [ts2.SyntaxKind.ImportDeclaration]: (node, context) => isFactoryOnlyImport(node) ? [] : context.superTransformStatements(node)
1323
- }
1324
- };
1325
-
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) {
@@ -1381,6 +2002,15 @@ function relUnderOutDir(rel, config) {
1381
2002
  const relUnderBase = rel.slice(includeBase.length);
1382
2003
  return path.posix.join(outDir, relUnderBase);
1383
2004
  }
2005
+ function requirePathForRel(rel) {
2006
+ const segments = rel.split("/").filter((segment) => segment !== "" && segment !== "." && segment !== "..");
2007
+ const last = segments.length - 1;
2008
+ const lastSegment = segments[last];
2009
+ if (lastSegment !== undefined) {
2010
+ segments[last] = lastSegment.replace(/\.[^.]*$/, "");
2011
+ }
2012
+ return segments.map((segment) => segment.replace(/\./g, "_")).join(".");
2013
+ }
1384
2014
  function computeOutputRel(rel, config, kind) {
1385
2015
  const baseRel = relUnderOutDir(rel, config);
1386
2016
  if (kind === "module") {
@@ -1389,16 +2019,16 @@ function computeOutputRel(rel, config, kind) {
1389
2019
  return baseRel.replace(/\.ts$/, SCRIPT_SUFFIX_BY_KIND[kind]);
1390
2020
  }
1391
2021
  // src/program-diagnostics.ts
1392
- import * as ts7 from "typescript";
2022
+ import * as ts8 from "typescript";
1393
2023
  import * as tstl from "typescript-to-lua";
1394
2024
 
1395
2025
  // src/message-guard-lowering.ts
1396
- import * as ts4 from "typescript";
2026
+ import * as ts5 from "typescript";
1397
2027
  import {
1398
2028
  createBinaryExpression as createBinaryExpression3,
1399
2029
  createCallExpression as createCallExpression3,
1400
2030
  createIdentifier as createIdentifier3,
1401
- SyntaxKind as SyntaxKind6
2031
+ SyntaxKind as SyntaxKind7
1402
2032
  } from "typescript-to-lua";
1403
2033
  var GUARD_MODULE = "@defold-typescript/types";
1404
2034
  var GUARD_NAME = "isMessage";
@@ -1407,7 +2037,7 @@ function resolvesToGuardExport(callee, checker) {
1407
2037
  if (symbol === undefined) {
1408
2038
  return false;
1409
2039
  }
1410
- if (symbol.flags & ts4.SymbolFlags.Alias) {
2040
+ if (symbol.flags & ts5.SymbolFlags.Alias) {
1411
2041
  symbol = checker.getAliasedSymbol(symbol);
1412
2042
  }
1413
2043
  if (symbol.getName() !== GUARD_NAME) {
@@ -1421,12 +2051,12 @@ function resolvesToGuardExport(callee, checker) {
1421
2051
  }
1422
2052
  var messageGuardLoweringPlugin = {
1423
2053
  visitors: {
1424
- [ts4.SyntaxKind.CallExpression]: (node, context) => {
2054
+ [ts5.SyntaxKind.CallExpression]: (node, context) => {
1425
2055
  const [messageIdArg, , expectedArg] = node.arguments;
1426
2056
  if (node.arguments.length === 3 && messageIdArg !== undefined && expectedArg !== undefined && resolvesToGuardExport(node.expression, context.checker)) {
1427
2057
  const messageId = context.transformExpression(messageIdArg);
1428
2058
  const expected = context.transformExpression(expectedArg);
1429
- return createBinaryExpression3(messageId, createCallExpression3(createIdentifier3("hash"), [expected]), SyntaxKind6.EqualityOperator, node);
2059
+ return createBinaryExpression3(messageId, createCallExpression3(createIdentifier3("hash"), [expected]), SyntaxKind7.EqualityOperator, node);
1430
2060
  }
1431
2061
  return context.superTransformExpression(node);
1432
2062
  }
@@ -1434,77 +2064,34 @@ var messageGuardLoweringPlugin = {
1434
2064
  };
1435
2065
 
1436
2066
  // 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
2067
+ import * as ts6 from "typescript";
2068
+ import { LuaLibFeature } from "typescript-to-lua";
1484
2069
  function isTimersSpecifier(node) {
1485
- return ts5.isStringLiteral(node) && node.text === TIMERS_MODULE_SPECIFIER;
2070
+ return ts6.isStringLiteral(node) && node.text === TIMERS_MODULE_SPECIFIER;
1486
2071
  }
1487
2072
  function importsTimersModule(sourceFile) {
1488
- return sourceFile.statements.some((statement) => ts5.isImportDeclaration(statement) && isTimersSpecifier(statement.moduleSpecifier));
2073
+ return sourceFile.statements.some((statement) => ts6.isImportDeclaration(statement) && isTimersSpecifier(statement.moduleSpecifier));
1489
2074
  }
1490
2075
  var TIMERS_REQUIRE_PATH = `@NoResolution:${TIMERS_REQUIRE_NAME}`;
1491
2076
  var timersLoweringPlugin = {
1492
2077
  visitors: {
1493
- [ts5.SyntaxKind.ImportDeclaration]: (node, context) => {
2078
+ [ts6.SyntaxKind.ImportDeclaration]: (node, context) => {
1494
2079
  if (!isTimersSpecifier(node.moduleSpecifier)) {
1495
2080
  return context.superTransformStatements(node);
1496
2081
  }
1497
- const rewritten = ts5.factory.updateImportDeclaration(node, node.modifiers, node.importClause, ts5.factory.createStringLiteral(TIMERS_REQUIRE_PATH), node.attributes);
2082
+ context.usedLuaLibFeatures.add(LuaLibFeature.Promise);
2083
+ context.usedLuaLibFeatures.add(LuaLibFeature.New);
2084
+ const rewritten = ts6.factory.updateImportDeclaration(node, node.modifiers, node.importClause, ts6.factory.createStringLiteral(TIMERS_REQUIRE_PATH), node.attributes);
1498
2085
  return context.superTransformStatements(rewritten);
1499
2086
  }
1500
2087
  }
1501
2088
  };
1502
2089
 
1503
2090
  // src/window-event-guard-lowering.ts
1504
- import * as ts6 from "typescript";
2091
+ import * as ts7 from "typescript";
1505
2092
  import {
1506
2093
  createBinaryExpression as createBinaryExpression4,
1507
- SyntaxKind as SyntaxKind9
2094
+ SyntaxKind as SyntaxKind10
1508
2095
  } from "typescript-to-lua";
1509
2096
  var GUARD_MODULE2 = "@defold-typescript/types";
1510
2097
  var GUARD_NAME2 = "isWindowEvent";
@@ -1513,7 +2100,7 @@ function resolvesToGuardExport2(callee, checker) {
1513
2100
  if (symbol === undefined) {
1514
2101
  return false;
1515
2102
  }
1516
- if (symbol.flags & ts6.SymbolFlags.Alias) {
2103
+ if (symbol.flags & ts7.SymbolFlags.Alias) {
1517
2104
  symbol = checker.getAliasedSymbol(symbol);
1518
2105
  }
1519
2106
  if (symbol.getName() !== GUARD_NAME2) {
@@ -1527,12 +2114,12 @@ function resolvesToGuardExport2(callee, checker) {
1527
2114
  }
1528
2115
  var windowEventGuardLoweringPlugin = {
1529
2116
  visitors: {
1530
- [ts6.SyntaxKind.CallExpression]: (node, context) => {
2117
+ [ts7.SyntaxKind.CallExpression]: (node, context) => {
1531
2118
  const [eventArg, , expectedArg] = node.arguments;
1532
2119
  if (node.arguments.length === 3 && eventArg !== undefined && expectedArg !== undefined && resolvesToGuardExport2(node.expression, context.checker)) {
1533
2120
  const event = context.transformExpression(eventArg);
1534
2121
  const expected = context.transformExpression(expectedArg);
1535
- return createBinaryExpression4(event, expected, SyntaxKind9.EqualityOperator, node);
2122
+ return createBinaryExpression4(event, expected, SyntaxKind10.EqualityOperator, node);
1536
2123
  }
1537
2124
  return context.superTransformExpression(node);
1538
2125
  }
@@ -1552,13 +2139,33 @@ function isAmbient(fileName) {
1552
2139
  return /[\\/]node_modules[\\/]/.test(fileName) || /(^|[\\/])lib\.[^\\/]*\.d\.ts$/.test(fileName);
1553
2140
  }
1554
2141
  function getProgramDiagnostics(program, sourceFile) {
1555
- const { diagnostics } = tstl.getProgramTranspileResult(ts7.sys, noopWriteFile, {
2142
+ const { diagnostics } = tstl.getProgramTranspileResult(ts8.sys, noopWriteFile, {
1556
2143
  program,
1557
2144
  plugins: LUA_PLUGINS,
1558
2145
  ...sourceFile ? { sourceFiles: [sourceFile] } : {}
1559
2146
  });
1560
2147
  return diagnostics.filter((d) => d.file !== undefined && !isAmbient(d.file.fileName));
1561
2148
  }
2149
+ // src/require-rewrite.ts
2150
+ function quote(value) {
2151
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
2152
+ }
2153
+ function rewriteEmittedRequires(lua, rewrites) {
2154
+ if (rewrites.size === 0) {
2155
+ return lua;
2156
+ }
2157
+ let out = "";
2158
+ let cursor = 0;
2159
+ for (const { path, start, end } of scanEmittedRequires(lua)) {
2160
+ const mapped = rewrites.get(path);
2161
+ if (mapped === undefined) {
2162
+ continue;
2163
+ }
2164
+ out += lua.slice(cursor, start) + quote(mapped);
2165
+ cursor = end;
2166
+ }
2167
+ return cursor === 0 ? lua : out + lua.slice(cursor);
2168
+ }
1562
2169
  // src/scene-component-index.ts
1563
2170
  var COMPONENT_BLOCKS = new Set(["components", "embedded_components"]);
1564
2171
  var EMBEDDING_BLOCKS = new Set(["embedded_instances", "embedded_components"]);
@@ -2052,13 +2659,441 @@ function relativeAddressesFrom(index, context) {
2052
2659
  import { readFileSync as readFileSync2 } from "node:fs";
2053
2660
  import { createRequire as createRequire2 } from "node:module";
2054
2661
  import * as path3 from "node:path";
2055
- import * as ts10 from "typescript";
2662
+ import * as ts12 from "typescript";
2056
2663
  import * as tstl3 from "typescript-to-lua";
2057
2664
 
2665
+ // src/companion-emit.ts
2666
+ import * as ts9 from "typescript";
2667
+ import {
2668
+ createAssignmentStatement as createAssignmentStatement2,
2669
+ createCallExpression as createCallExpression4,
2670
+ createFile,
2671
+ createIdentifier as createIdentifier4,
2672
+ createReturnStatement as createReturnStatement2,
2673
+ createStringLiteral as createStringLiteral3,
2674
+ createTableExpression,
2675
+ createTableFieldExpression,
2676
+ createTableIndexExpression as createTableIndexExpression2,
2677
+ createVariableDeclarationStatement as createVariableDeclarationStatement3,
2678
+ isBlock as isBlock3,
2679
+ isCallExpression as isCallExpression4,
2680
+ isDoStatement,
2681
+ isForInStatement as isForInStatement2,
2682
+ isForStatement,
2683
+ isFunctionDefinition,
2684
+ isFunctionExpression as isFunctionExpression4,
2685
+ isIdentifier as isIdentifier4,
2686
+ isMethodCallExpression,
2687
+ isRepeatStatement,
2688
+ isReturnStatement as isReturnStatement2,
2689
+ isStringLiteral as isStringLiteral6,
2690
+ isTableExpression,
2691
+ isVariableDeclarationStatement,
2692
+ LuaPrinter
2693
+ } from "typescript-to-lua";
2694
+ var EXPORTS_NAME = "____exports";
2695
+ function exportsIdentifier() {
2696
+ return createIdentifier4(EXPORTS_NAME);
2697
+ }
2698
+ function isRuntimeScriptSource(sourceFile, checker) {
2699
+ return sourceFile.statements.some((statement) => {
2700
+ const expression = ts9.isExpressionStatement(statement) ? statement.expression : ts9.isExportAssignment(statement) && !statement.isExportEquals ? statement.expression : undefined;
2701
+ return expression !== undefined && ts9.isCallExpression(expression) && resolvesToFactoryExport(expression.expression, checker);
2702
+ });
2703
+ }
2704
+ function planFor(sourceFile, checker) {
2705
+ if (sourceFile.isDeclarationFile || !isRuntimeScriptSource(sourceFile, checker)) {
2706
+ return;
2707
+ }
2708
+ const closure = computeCompanionClosure(sourceFile, checker);
2709
+ if (closure.statements.length === 0 || closure.violations.length > 0) {
2710
+ return;
2711
+ }
2712
+ return {
2713
+ closureStarts: new Set(closure.statements.map((statement) => statement.pos)),
2714
+ internals: closure.internals,
2715
+ importBindings: closure.importBindings,
2716
+ requirePath: requirePathForRel(sourceFile.fileName)
2717
+ };
2718
+ }
2719
+ function isLuaNode(value) {
2720
+ return typeof value === "object" && value !== null && typeof value.kind === "number";
2721
+ }
2722
+ function forEachLuaNode(root, visit) {
2723
+ visit(root);
2724
+ for (const value of Object.values(root)) {
2725
+ if (Array.isArray(value)) {
2726
+ for (const element of value) {
2727
+ if (isLuaNode(element)) {
2728
+ forEachLuaNode(element, visit);
2729
+ }
2730
+ }
2731
+ } else if (isLuaNode(value)) {
2732
+ forEachLuaNode(value, visit);
2733
+ }
2734
+ }
2735
+ }
2736
+ function declaredNames(statement) {
2737
+ return isVariableDeclarationStatement(statement) ? statement.left.map((name) => name.text) : [];
2738
+ }
2739
+ function freeNames(statements) {
2740
+ const free = new Set;
2741
+ const scopes = [new Set];
2742
+ function declare(name) {
2743
+ scopes[scopes.length - 1]?.add(name);
2744
+ }
2745
+ function isBound(name) {
2746
+ return scopes.some((scope) => scope.has(name));
2747
+ }
2748
+ function inScope(run) {
2749
+ scopes.push(new Set);
2750
+ run();
2751
+ scopes.pop();
2752
+ }
2753
+ function visitEach(nodes) {
2754
+ for (const node of nodes ?? []) {
2755
+ visit(node);
2756
+ }
2757
+ }
2758
+ function visit(node) {
2759
+ if (node === undefined) {
2760
+ return;
2761
+ }
2762
+ if (isIdentifier4(node)) {
2763
+ if (!isBound(node.text)) {
2764
+ free.add(node.text);
2765
+ }
2766
+ return;
2767
+ }
2768
+ if (isVariableDeclarationStatement(node)) {
2769
+ if (isFunctionDefinition(node)) {
2770
+ const [name] = node.left;
2771
+ if (name !== undefined) {
2772
+ declare(name.text);
2773
+ }
2774
+ visit(node.right[0]);
2775
+ return;
2776
+ }
2777
+ visitEach(node.right);
2778
+ for (const name of node.left) {
2779
+ declare(name.text);
2780
+ }
2781
+ return;
2782
+ }
2783
+ if (isFunctionExpression4(node)) {
2784
+ inScope(() => {
2785
+ for (const param of node.params ?? []) {
2786
+ declare(param.text);
2787
+ }
2788
+ visit(node.body);
2789
+ });
2790
+ return;
2791
+ }
2792
+ if (isForStatement(node)) {
2793
+ visit(node.controlVariableInitializer);
2794
+ visit(node.limitExpression);
2795
+ visit(node.stepExpression);
2796
+ inScope(() => {
2797
+ declare(node.controlVariable.text);
2798
+ visit(node.body);
2799
+ });
2800
+ return;
2801
+ }
2802
+ if (isForInStatement2(node)) {
2803
+ visitEach(node.expressions);
2804
+ inScope(() => {
2805
+ for (const name of node.names) {
2806
+ declare(name.text);
2807
+ }
2808
+ visit(node.body);
2809
+ });
2810
+ return;
2811
+ }
2812
+ if (isRepeatStatement(node)) {
2813
+ inScope(() => {
2814
+ visitEach(node.body.statements);
2815
+ visit(node.condition);
2816
+ });
2817
+ return;
2818
+ }
2819
+ if (isBlock3(node) || isDoStatement(node)) {
2820
+ inScope(() => visitEach(node.statements));
2821
+ return;
2822
+ }
2823
+ if (isMethodCallExpression(node)) {
2824
+ visit(node.prefixExpression);
2825
+ visitEach(node.params);
2826
+ return;
2827
+ }
2828
+ for (const value of Object.values(node)) {
2829
+ if (Array.isArray(value)) {
2830
+ for (const element of value) {
2831
+ if (isLuaNode(element)) {
2832
+ visit(element);
2833
+ }
2834
+ }
2835
+ } else if (isLuaNode(value)) {
2836
+ visit(value);
2837
+ }
2838
+ }
2839
+ }
2840
+ for (const statement of statements) {
2841
+ for (const name of declaredNames(statement)) {
2842
+ declare(name);
2843
+ }
2844
+ }
2845
+ for (const statement of statements) {
2846
+ visit(statement);
2847
+ }
2848
+ return free;
2849
+ }
2850
+ function resolvedRequirePaths(sourceFile, checker) {
2851
+ const paths = new Map;
2852
+ for (const statement of sourceFile.statements) {
2853
+ const specifier = ts9.isImportDeclaration(statement) || ts9.isExportDeclaration(statement) ? statement.moduleSpecifier : undefined;
2854
+ if (specifier === undefined || !ts9.isStringLiteral(specifier)) {
2855
+ continue;
2856
+ }
2857
+ const target = checker.getSymbolAtLocation(specifier)?.declarations?.find((declaration) => ts9.isSourceFile(declaration));
2858
+ if (target === undefined || target.isDeclarationFile) {
2859
+ continue;
2860
+ }
2861
+ paths.set(specifier.text, requirePathForRel(target.fileName));
2862
+ }
2863
+ return paths;
2864
+ }
2865
+ function resolveMovedRequires(statements, paths) {
2866
+ for (const statement of statements) {
2867
+ forEachLuaNode(statement, (node) => {
2868
+ if (!isCallExpression4(node) || !isIdentifier4(node.expression)) {
2869
+ return;
2870
+ }
2871
+ if (node.expression.text !== "require" || node.params.length !== 1) {
2872
+ return;
2873
+ }
2874
+ const [argument] = node.params;
2875
+ if (argument === undefined || !isStringLiteral6(argument)) {
2876
+ return;
2877
+ }
2878
+ const resolved = paths.get(argument.value);
2879
+ if (resolved !== undefined) {
2880
+ argument.value = resolved;
2881
+ }
2882
+ });
2883
+ }
2884
+ }
2885
+ function withResolvedRequire(statement, paths) {
2886
+ if (!isVariableDeclarationStatement(statement) || statement.left.length !== 1) {
2887
+ return statement;
2888
+ }
2889
+ const name = statement.left[0];
2890
+ const [value] = statement.right ?? [];
2891
+ if (name === undefined || value === undefined || !isCallExpression4(value)) {
2892
+ return statement;
2893
+ }
2894
+ if (!isIdentifier4(value.expression) || value.expression.text !== "require") {
2895
+ return statement;
2896
+ }
2897
+ const [argument] = value.params;
2898
+ if (argument === undefined || !isStringLiteral6(argument)) {
2899
+ return statement;
2900
+ }
2901
+ const resolved = paths.get(argument.value);
2902
+ if (resolved === undefined) {
2903
+ return statement;
2904
+ }
2905
+ return createVariableDeclarationStatement3(createIdentifier4(name.text), createCallExpression4(createIdentifier4("require"), [createStringLiteral3(resolved)]));
2906
+ }
2907
+ function copiedPrelude(halves, importBindings) {
2908
+ const needed = new Set(importBindings);
2909
+ for (const name of freeNames(halves.companion)) {
2910
+ needed.add(name);
2911
+ }
2912
+ const alreadyCarried = new Set(halves.companion);
2913
+ const candidates = halves.script.filter((statement) => isVariableDeclarationStatement(statement) && !alreadyCarried.has(statement));
2914
+ const copied = new Set;
2915
+ let grew = true;
2916
+ while (grew) {
2917
+ grew = false;
2918
+ for (const statement of candidates) {
2919
+ if (copied.has(statement) || !declaredNames(statement).some((name) => needed.has(name))) {
2920
+ continue;
2921
+ }
2922
+ copied.add(statement);
2923
+ for (const name of freeNames([statement])) {
2924
+ needed.add(name);
2925
+ }
2926
+ grew = true;
2927
+ }
2928
+ }
2929
+ return halves.script.filter((statement) => copied.has(statement));
2930
+ }
2931
+ function namesIdentifier(statement, names) {
2932
+ let found = false;
2933
+ forEachLuaNode(statement, (node) => {
2934
+ if (isIdentifier4(node) && names.has(node.text)) {
2935
+ found = true;
2936
+ }
2937
+ });
2938
+ return found;
2939
+ }
2940
+ function isExportsTableInit(statement) {
2941
+ if (!isVariableDeclarationStatement(statement) || statement.left.length !== 1) {
2942
+ return false;
2943
+ }
2944
+ const [single] = statement.right ?? [];
2945
+ return statement.left[0]?.text === EXPORTS_NAME && single !== undefined && isTableExpression(single);
2946
+ }
2947
+ function isExportsReturn(statement) {
2948
+ if (!isReturnStatement2(statement) || statement.expressions.length !== 1) {
2949
+ return false;
2950
+ }
2951
+ const [single] = statement.expressions;
2952
+ return single !== undefined && isIdentifier4(single) && single.text === EXPORTS_NAME;
2953
+ }
2954
+ function isHoistedNameDeclaration(statement) {
2955
+ return isVariableDeclarationStatement(statement) && statement.right === undefined;
2956
+ }
2957
+ function internalsField(name) {
2958
+ return createTableIndexExpression2(createTableIndexExpression2(exportsIdentifier(), createStringLiteral3(COMPANION_INTERNAL_FIELD)), createStringLiteral3(name));
2959
+ }
2960
+ function requireInsertionPoint(script, reads) {
2961
+ let index = 0;
2962
+ while (index < script.length) {
2963
+ const statement = script[index];
2964
+ if (statement === undefined || !isVariableDeclarationStatement(statement)) {
2965
+ break;
2966
+ }
2967
+ if (namesIdentifier(statement, reads)) {
2968
+ break;
2969
+ }
2970
+ index++;
2971
+ }
2972
+ return index;
2973
+ }
2974
+ function split(statements, owned) {
2975
+ const script = [];
2976
+ const companion = [];
2977
+ for (const statement of statements) {
2978
+ if (isExportsTableInit(statement) || isExportsReturn(statement)) {
2979
+ continue;
2980
+ }
2981
+ if (owned.has(statement)) {
2982
+ companion.push(statement);
2983
+ continue;
2984
+ }
2985
+ if (isHoistedNameDeclaration(statement)) {
2986
+ companion.push(statement);
2987
+ }
2988
+ script.push(statement);
2989
+ }
2990
+ return { script, companion };
2991
+ }
2992
+ function createCompanionEmitPlugin() {
2993
+ const plans = new Map;
2994
+ const owned = new Map;
2995
+ const companions = new Map;
2996
+ function planOf(sourceFile, checker) {
2997
+ const key = sourceFile.fileName;
2998
+ if (!plans.has(key)) {
2999
+ plans.set(key, planFor(sourceFile, checker));
3000
+ }
3001
+ return plans.get(key);
3002
+ }
3003
+ function capture(node, context) {
3004
+ const statements = context.superTransformStatements(node);
3005
+ const sourceFile = context.sourceFile;
3006
+ const plan = planOf(sourceFile, context.checker);
3007
+ if (plan === undefined || !plan.closureStarts.has(node.pos)) {
3008
+ return statements;
3009
+ }
3010
+ let bucket = owned.get(sourceFile.fileName);
3011
+ if (bucket === undefined) {
3012
+ bucket = new Set;
3013
+ owned.set(sourceFile.fileName, bucket);
3014
+ }
3015
+ for (const statement of statements) {
3016
+ bucket.add(statement);
3017
+ }
3018
+ return statements;
3019
+ }
3020
+ const visitors = {
3021
+ [ts9.SyntaxKind.VariableStatement]: capture,
3022
+ [ts9.SyntaxKind.FunctionDeclaration]: capture,
3023
+ [ts9.SyntaxKind.ClassDeclaration]: capture,
3024
+ [ts9.SyntaxKind.EnumDeclaration]: capture,
3025
+ [ts9.SyntaxKind.ExportDeclaration]: capture
3026
+ };
3027
+ let configuredNoResolvePaths = [];
3028
+ const plugin = {
3029
+ visitors,
3030
+ beforeTransform(_program, options) {
3031
+ configuredNoResolvePaths = options.noResolvePaths ?? [];
3032
+ },
3033
+ afterPrint(program, options, emitHost, result) {
3034
+ const emitted = [];
3035
+ for (const file of result) {
3036
+ const sourceFile = file.sourceFiles?.[0];
3037
+ if (sourceFile === undefined || file.luaAst === undefined) {
3038
+ continue;
3039
+ }
3040
+ const plan = plans.get(sourceFile.fileName);
3041
+ const bucket = owned.get(sourceFile.fileName);
3042
+ if (plan === undefined || bucket === undefined || bucket.size === 0) {
3043
+ continue;
3044
+ }
3045
+ emitCompanion(program, emitHost, file, sourceFile, plan, bucket, companions);
3046
+ emitted.push(plan.requirePath);
3047
+ }
3048
+ if (emitted.length > 0) {
3049
+ options.noResolvePaths = [...configuredNoResolvePaths, ...emitted];
3050
+ }
3051
+ }
3052
+ };
3053
+ return { plugin, companions };
3054
+ }
3055
+ function emitCompanion(program, emitHost, file, sourceFile, plan, owned, companions) {
3056
+ const luaAst = file.luaAst;
3057
+ if (luaAst === undefined) {
3058
+ return;
3059
+ }
3060
+ const halves = split(luaAst.statements, owned);
3061
+ const requirePaths = resolvedRequirePaths(sourceFile, program.getTypeChecker());
3062
+ resolveMovedRequires(halves.companion, requirePaths);
3063
+ const closureTable = createTableExpression(plan.internals.map((name) => createTableFieldExpression(createIdentifier4(name), createStringLiteral3(name))));
3064
+ const companionStatements = [
3065
+ createVariableDeclarationStatement3(exportsIdentifier(), createTableExpression()),
3066
+ ...copiedPrelude(halves, plan.importBindings).map((statement) => withResolvedRequire(statement, requirePaths)),
3067
+ ...halves.companion,
3068
+ ...plan.internals.length > 0 ? [
3069
+ createAssignmentStatement2(createTableIndexExpression2(exportsIdentifier(), createStringLiteral3(COMPANION_INTERNAL_FIELD)), closureTable)
3070
+ ] : [],
3071
+ createReturnStatement2([exportsIdentifier()])
3072
+ ];
3073
+ const reads = new Set([EXPORTS_NAME, ...plan.internals]);
3074
+ const insertAt = requireInsertionPoint(halves.script, reads);
3075
+ const scriptStatements = [
3076
+ ...halves.script.slice(0, insertAt),
3077
+ createVariableDeclarationStatement3(exportsIdentifier(), createCallExpression4(createIdentifier4("require"), [createStringLiteral3(plan.requirePath)])),
3078
+ ...plan.internals.map((name) => createVariableDeclarationStatement3(createIdentifier4(name), internalsField(name))),
3079
+ ...halves.script.slice(insertAt),
3080
+ createReturnStatement2([exportsIdentifier()])
3081
+ ];
3082
+ const features = new Set(luaAst.luaLibFeatures);
3083
+ const printer = new LuaPrinter(emitHost, program, sourceFile.fileName);
3084
+ companions.set(sourceFile.fileName, printer.print(createFile(companionStatements, new Set(features), luaAst.trivia, sourceFile)).code);
3085
+ const scriptFile = createFile(scriptStatements, features, luaAst.trivia, sourceFile);
3086
+ const printed = new LuaPrinter(emitHost, program, sourceFile.fileName).print(scriptFile);
3087
+ file.luaAst = scriptFile;
3088
+ file.code = printed.code;
3089
+ file.sourceMap = printed.sourceMap;
3090
+ file.sourceMapNode = printed.sourceMapNode;
3091
+ }
3092
+
2058
3093
  // src/editor-script-erasure.ts
2059
- import * as ts8 from "typescript";
3094
+ import * as ts10 from "typescript";
2060
3095
  import {
2061
- createReturnStatement as createReturnStatement2
3096
+ createReturnStatement as createReturnStatement3
2062
3097
  } from "typescript-to-lua";
2063
3098
  var EDITOR_FACTORY_MODULE = "@defold-typescript/types";
2064
3099
  var EDITOR_SCRIPT_FACTORY_NAME = "defineEditorScript";
@@ -2076,7 +3111,7 @@ function resolveEditorFactoryExport(callee, checker) {
2076
3111
  if (symbol === undefined) {
2077
3112
  return;
2078
3113
  }
2079
- if (symbol.flags & ts8.SymbolFlags.Alias) {
3114
+ if (symbol.flags & ts10.SymbolFlags.Alias) {
2080
3115
  symbol = checker.getAliasedSymbol(symbol);
2081
3116
  }
2082
3117
  const name = symbol.getName();
@@ -2090,7 +3125,7 @@ function resolveEditorFactoryExport(callee, checker) {
2090
3125
  return declaration.getSourceFile().fileName.includes(EDITOR_FACTORY_MODULE) ? name : undefined;
2091
3126
  }
2092
3127
  function lowerEditorFactoryCall(expression, context) {
2093
- if (!ts8.isCallExpression(expression)) {
3128
+ if (!ts10.isCallExpression(expression)) {
2094
3129
  return;
2095
3130
  }
2096
3131
  if (resolveEditorFactoryExport(expression.expression, context.checker) !== EDITOR_SCRIPT_FACTORY_NAME) {
@@ -2100,10 +3135,10 @@ function lowerEditorFactoryCall(expression, context) {
2100
3135
  if (module === undefined) {
2101
3136
  return;
2102
3137
  }
2103
- return [createReturnStatement2([context.transformExpression(module)])];
3138
+ return [createReturnStatement3([context.transformExpression(module)])];
2104
3139
  }
2105
3140
  function isEditorFactoryOnlyImport(node) {
2106
- if (!ts8.isStringLiteral(node.moduleSpecifier) || !EDITOR_FACTORY_SPECIFIERS.has(node.moduleSpecifier.text)) {
3141
+ if (!ts10.isStringLiteral(node.moduleSpecifier) || !EDITOR_FACTORY_SPECIFIERS.has(node.moduleSpecifier.text)) {
2107
3142
  return false;
2108
3143
  }
2109
3144
  const clause = node.importClause;
@@ -2114,7 +3149,7 @@ function isEditorFactoryOnlyImport(node) {
2114
3149
  return false;
2115
3150
  }
2116
3151
  const bindings = clause.namedBindings;
2117
- if (bindings === undefined || !ts8.isNamedImports(bindings)) {
3152
+ if (bindings === undefined || !ts10.isNamedImports(bindings)) {
2118
3153
  return false;
2119
3154
  }
2120
3155
  const runtime = bindings.elements.filter((element) => !element.isTypeOnly);
@@ -2125,7 +3160,7 @@ function isEditorFactoryOnlyImport(node) {
2125
3160
  }
2126
3161
  var editorScriptErasurePlugin = {
2127
3162
  visitors: {
2128
- [ts8.SyntaxKind.ExportAssignment]: (node, context) => {
3163
+ [ts10.SyntaxKind.ExportAssignment]: (node, context) => {
2129
3164
  if (!node.isExportEquals) {
2130
3165
  const lowered = lowerEditorFactoryCall(node.expression, context);
2131
3166
  if (lowered !== undefined) {
@@ -2134,14 +3169,14 @@ var editorScriptErasurePlugin = {
2134
3169
  }
2135
3170
  return context.superTransformStatements(node);
2136
3171
  },
2137
- [ts8.SyntaxKind.CallExpression]: (node, context) => {
3172
+ [ts10.SyntaxKind.CallExpression]: (node, context) => {
2138
3173
  const command = node.arguments[0];
2139
3174
  if (command !== undefined && resolveEditorFactoryExport(node.expression, context.checker) === EDITOR_COMMAND_FACTORY_NAME) {
2140
3175
  return context.transformExpression(command);
2141
3176
  }
2142
3177
  return context.superTransformExpression(node);
2143
3178
  },
2144
- [ts8.SyntaxKind.ImportDeclaration]: (node, context) => isEditorFactoryOnlyImport(node) ? [] : context.superTransformStatements(node)
3179
+ [ts10.SyntaxKind.ImportDeclaration]: (node, context) => isEditorFactoryOnlyImport(node) ? [] : context.superTransformStatements(node)
2145
3180
  }
2146
3181
  };
2147
3182
 
@@ -2152,17 +3187,17 @@ import * as path2 from "node:path";
2152
3187
  import * as tstl2 from "typescript-to-lua";
2153
3188
 
2154
3189
  // src/go-property-direct-call.ts
2155
- import * as ts9 from "typescript";
3190
+ import * as ts11 from "typescript";
2156
3191
  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
3192
  function findDirectGoPropertyCalls(sourceFile) {
2158
3193
  const calls = [];
2159
3194
  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") {
3195
+ if (ts11.isCallExpression(node) && ts11.isPropertyAccessExpression(node.expression) && node.expression.name.text === "property" && ts11.isIdentifier(node.expression.expression) && node.expression.expression.text === "go") {
2161
3196
  calls.push(node);
2162
3197
  }
2163
- ts9.forEachChild(node, visit);
3198
+ ts11.forEachChild(node, visit);
2164
3199
  };
2165
- ts9.forEachChild(sourceFile, visit);
3200
+ ts11.forEachChild(sourceFile, visit);
2166
3201
  return calls;
2167
3202
  }
2168
3203
 
@@ -2261,9 +3296,10 @@ var LUALIB_BUNDLE_NAME = "lualib_bundle.lua";
2261
3296
  function isLualibBundle(file) {
2262
3297
  return file.sourceFiles.length === 0 && file.outPath.endsWith(LUALIB_BUNDLE_NAME);
2263
3298
  }
2264
- function collectOutputs(transpiledFiles, diagnostics, userKeys) {
3299
+ function collectOutputs(transpiledFiles, diagnostics, userKeys, emittedCompanions = new Map) {
2265
3300
  const lua = {};
2266
3301
  const sourceMaps = {};
3302
+ const companions = {};
2267
3303
  let lualib;
2268
3304
  for (const file of transpiledFiles) {
2269
3305
  if (isLualibBundle(file) && typeof file.lua === "string") {
@@ -2278,6 +3314,10 @@ function collectOutputs(transpiledFiles, diagnostics, userKeys) {
2278
3314
  if (typeof file.luaSourceMap === "string") {
2279
3315
  sourceMaps[userSource.fileName] = file.luaSourceMap;
2280
3316
  }
3317
+ const companion = emittedCompanions.get(userSource.fileName);
3318
+ if (companion !== undefined) {
3319
+ companions[userSource.fileName] = companion;
3320
+ }
2281
3321
  }
2282
3322
  const collectedDiagnostics = diagnostics.map((d) => {
2283
3323
  const fileName = d.file?.fileName;
@@ -2315,6 +3355,7 @@ function collectOutputs(transpiledFiles, diagnostics, userKeys) {
2315
3355
  lua,
2316
3356
  sourceMaps,
2317
3357
  diagnostics: collectedDiagnostics,
3358
+ ...Object.keys(companions).length > 0 ? { companions } : {},
2318
3359
  ...lualib !== undefined ? { lualib } : {},
2319
3360
  ...timersImported ? { timersRuntime: TIMERS_RUNTIME } : {}
2320
3361
  };
@@ -2322,6 +3363,7 @@ function collectOutputs(transpiledFiles, diagnostics, userKeys) {
2322
3363
  function transpileProject(input) {
2323
3364
  const userKeys = new Set(Object.keys(input.files));
2324
3365
  const merged = { ...AMBIENT_FILES, ...input.files };
3366
+ const companionEmit = createCompanionEmitPlugin();
2325
3367
  const result = tstl2.transpileVirtualProject(merged, {
2326
3368
  luaTarget: tstl2.LuaTarget.Lua51,
2327
3369
  sourceMap: true,
@@ -2334,10 +3376,11 @@ function transpileProject(input) {
2334
3376
  { plugin: messageGuardLoweringPlugin },
2335
3377
  { plugin: windowEventGuardLoweringPlugin },
2336
3378
  { plugin: messageDispatchLoweringPlugin },
2337
- { plugin: timersLoweringPlugin }
3379
+ { plugin: timersLoweringPlugin },
3380
+ { plugin: companionEmit.plugin }
2338
3381
  ]
2339
3382
  });
2340
- return collectOutputs(result.transpiledFiles, result.diagnostics, userKeys);
3383
+ return collectOutputs(result.transpiledFiles, result.diagnostics, userKeys, companionEmit.companions);
2341
3384
  }
2342
3385
  function transpile(source) {
2343
3386
  const project = transpileProject({ files: { "main.ts": source } });
@@ -2350,21 +3393,24 @@ function transpile(source) {
2350
3393
 
2351
3394
  // src/session.ts
2352
3395
  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
- };
3396
+ function compilerOptions(companionEmit) {
3397
+ return {
3398
+ luaTarget: tstl3.LuaTarget.Lua51,
3399
+ sourceMap: true,
3400
+ lib: ["lib.es2022.d.ts"],
3401
+ skipLibCheck: true,
3402
+ noImplicitSelf: true,
3403
+ luaPlugins: [
3404
+ { plugin: lifecycleErasurePlugin },
3405
+ { plugin: editorScriptErasurePlugin },
3406
+ { plugin: messageGuardLoweringPlugin },
3407
+ { plugin: windowEventGuardLoweringPlugin },
3408
+ { plugin: messageDispatchLoweringPlugin },
3409
+ { plugin: timersLoweringPlugin },
3410
+ { plugin: companionEmit.plugin }
3411
+ ]
3412
+ };
3413
+ }
2368
3414
  function normalizeSlashes(p) {
2369
3415
  return p.replace(/\\/g, "/");
2370
3416
  }
@@ -2403,7 +3449,7 @@ function createTranspileSession() {
2403
3449
  if (cached) {
2404
3450
  return cached;
2405
3451
  }
2406
- const created = ts10.createSourceFile(fileName, content, ts10.ScriptTarget.Latest, false);
3452
+ const created = ts12.createSourceFile(fileName, content, ts12.ScriptTarget.Latest, false);
2407
3453
  sourceFileCache.set(normalized, created);
2408
3454
  return created;
2409
3455
  }
@@ -2422,7 +3468,7 @@ function createTranspileSession() {
2422
3468
  return cached;
2423
3469
  }
2424
3470
  const fileContent = readFileSync2(filePath, "utf8");
2425
- const created = ts10.createSourceFile(filePath, fileContent, ts10.ScriptTarget.Latest, false);
3471
+ const created = ts12.createSourceFile(filePath, fileContent, ts12.ScriptTarget.Latest, false);
2426
3472
  libCache.set(fileName, created);
2427
3473
  return created;
2428
3474
  }
@@ -2435,10 +3481,10 @@ function createTranspileSession() {
2435
3481
  return AMBIENT_FILES[normalized];
2436
3482
  }
2437
3483
  const host = {
2438
- fileExists: (fileName) => mergedContent(normalizeSlashes(fileName)) !== undefined || ts10.sys.fileExists(fileName),
3484
+ fileExists: (fileName) => mergedContent(normalizeSlashes(fileName)) !== undefined || ts12.sys.fileExists(fileName),
2439
3485
  getCanonicalFileName: (fileName) => fileName,
2440
3486
  getCurrentDirectory: () => "",
2441
- getDefaultLibFileName: ts10.getDefaultLibFileName,
3487
+ getDefaultLibFileName: ts12.getDefaultLibFileName,
2442
3488
  readFile: () => "",
2443
3489
  getNewLine: () => `
2444
3490
  `,
@@ -2459,17 +3505,18 @@ function createTranspileSession() {
2459
3505
  }
2460
3506
  const userKeys = new Set(userFiles.keys());
2461
3507
  const rootNames = [...Object.keys(AMBIENT_FILES).map(normalizeSlashes), ...userKeys];
2462
- program = ts10.createProgram(rootNames, COMPILER_OPTIONS, host, program);
2463
- const preEmitDiagnostics = ts10.getPreEmitDiagnostics(program);
3508
+ const companionEmit = createCompanionEmitPlugin();
3509
+ program = ts12.createProgram(rootNames, compilerOptions(companionEmit), host, program);
3510
+ const preEmitDiagnostics = ts12.getPreEmitDiagnostics(program);
2464
3511
  const collector = createOutputCollector();
2465
3512
  const { diagnostics: transpileDiagnostics } = new tstl3.Transpiler().emit({
2466
3513
  program,
2467
3514
  writeFile: collector.writeFile
2468
3515
  });
2469
3516
  const diagnostics = [
2470
- ...ts10.sortAndDeduplicateDiagnostics([...preEmitDiagnostics, ...transpileDiagnostics])
3517
+ ...ts12.sortAndDeduplicateDiagnostics([...preEmitDiagnostics, ...transpileDiagnostics])
2471
3518
  ];
2472
- return collectOutputs(collector.files, diagnostics, userKeys);
3519
+ return collectOutputs(collector.files, diagnostics, userKeys, companionEmit.companions);
2473
3520
  }
2474
3521
  return {
2475
3522
  update,
@@ -2477,7 +3524,7 @@ function createTranspileSession() {
2477
3524
  };
2478
3525
  }
2479
3526
  // src/url-address-slots.ts
2480
- import * as ts11 from "typescript";
3527
+ import * as ts13 from "typescript";
2481
3528
  var ADDRESS_CLASSES = {
2482
3529
  "game-object": true,
2483
3530
  component: true,
@@ -2496,7 +3543,7 @@ function isFragmentCaret(slot, position) {
2496
3543
  return slot.fragmentStart !== -1 && position >= slot.fragmentStart;
2497
3544
  }
2498
3545
  function tableKey(checker, callee) {
2499
- const symbol = checker.getSymbolAtLocation(ts11.isIdentifier(callee) ? callee : callee.name);
3546
+ const symbol = checker.getSymbolAtLocation(ts13.isIdentifier(callee) ? callee : callee.name);
2500
3547
  if (!symbol)
2501
3548
  return;
2502
3549
  const fqn = checker.getFullyQualifiedName(symbol);
@@ -2504,43 +3551,43 @@ function tableKey(checker, callee) {
2504
3551
  }
2505
3552
  function parameterName(checker, call, index) {
2506
3553
  const declaration = checker.getResolvedSignature(call)?.declaration;
2507
- if (!declaration || !ts11.isFunctionLike(declaration))
3554
+ if (!declaration || !ts13.isFunctionLike(declaration))
2508
3555
  return;
2509
3556
  const parameter = declaration.parameters[index];
2510
- if (!parameter || parameter.dotDotDotToken || !ts11.isIdentifier(parameter.name))
3557
+ if (!parameter || parameter.dotDotDotToken || !ts13.isIdentifier(parameter.name))
2511
3558
  return;
2512
3559
  return parameter.name.text;
2513
3560
  }
2514
3561
  var EQUALITY_OPERATORS = new Set([
2515
- ts11.SyntaxKind.EqualsEqualsEqualsToken,
2516
- ts11.SyntaxKind.ExclamationEqualsEqualsToken,
2517
- ts11.SyntaxKind.EqualsEqualsToken,
2518
- ts11.SyntaxKind.ExclamationEqualsToken
3562
+ ts13.SyntaxKind.EqualsEqualsEqualsToken,
3563
+ ts13.SyntaxKind.ExclamationEqualsEqualsToken,
3564
+ ts13.SyntaxKind.EqualsEqualsToken,
3565
+ ts13.SyntaxKind.ExclamationEqualsToken
2519
3566
  ]);
2520
3567
  function comparisonSatisfied(checker, entry, call) {
2521
3568
  if (entry.comparedParameter === undefined)
2522
3569
  return true;
2523
3570
  let node = call;
2524
- while (ts11.isParenthesizedExpression(node.parent))
3571
+ while (ts13.isParenthesizedExpression(node.parent))
2525
3572
  node = node.parent;
2526
3573
  const comparison = node.parent;
2527
- if (!ts11.isBinaryExpression(comparison))
3574
+ if (!ts13.isBinaryExpression(comparison))
2528
3575
  return false;
2529
3576
  if (!EQUALITY_OPERATORS.has(comparison.operatorToken.kind))
2530
3577
  return false;
2531
3578
  const other = comparison.left === node ? comparison.right : comparison.left;
2532
3579
  const symbol = checker.getSymbolAtLocation(other);
2533
- return (symbol?.declarations ?? []).some((declaration) => ts11.isParameter(declaration) && ts11.isIdentifier(declaration.name) && declaration.name.text === entry.comparedParameter);
3580
+ return (symbol?.declarations ?? []).some((declaration) => ts13.isParameter(declaration) && ts13.isIdentifier(declaration.name) && declaration.name.text === entry.comparedParameter);
2534
3581
  }
2535
3582
  function classifiedEntryOfArgument(checker, table, argument) {
2536
3583
  const call = argument.parent;
2537
- if (!ts11.isCallExpression(call))
3584
+ if (!ts13.isCallExpression(call))
2538
3585
  return;
2539
3586
  const argumentIndex = call.arguments.indexOf(argument);
2540
3587
  if (argumentIndex === -1)
2541
3588
  return;
2542
3589
  const callee = call.expression;
2543
- if (!ts11.isPropertyAccessExpression(callee) && !ts11.isIdentifier(callee))
3590
+ if (!ts13.isPropertyAccessExpression(callee) && !ts13.isIdentifier(callee))
2544
3591
  return;
2545
3592
  const fqn = tableKey(checker, callee);
2546
3593
  if (fqn === undefined)
@@ -2557,10 +3604,10 @@ function classifiedEntryOfArgument(checker, table, argument) {
2557
3604
  }
2558
3605
  function slotThroughHashedCall(checker, table, literal) {
2559
3606
  const call = literal.parent;
2560
- if (!ts11.isCallExpression(call))
3607
+ if (!ts13.isCallExpression(call))
2561
3608
  return;
2562
3609
  const callee = call.expression;
2563
- if (!ts11.isPropertyAccessExpression(callee) && !ts11.isIdentifier(callee))
3610
+ if (!ts13.isPropertyAccessExpression(callee) && !ts13.isIdentifier(callee))
2564
3611
  return;
2565
3612
  if (tableKey(checker, callee) !== "hash")
2566
3613
  return;
@@ -2578,24 +3625,24 @@ function addressTextOf(checker, entry, call) {
2578
3625
  if (entry.addressParameter === undefined)
2579
3626
  return;
2580
3627
  const declaration = checker.getResolvedSignature(call)?.declaration;
2581
- if (!declaration || !ts11.isFunctionLike(declaration))
3628
+ if (!declaration || !ts13.isFunctionLike(declaration))
2582
3629
  return;
2583
- const index = declaration.parameters.findIndex((parameter) => !parameter.dotDotDotToken && ts11.isIdentifier(parameter.name) && parameter.name.text === entry.addressParameter);
3630
+ const index = declaration.parameters.findIndex((parameter) => !parameter.dotDotDotToken && ts13.isIdentifier(parameter.name) && parameter.name.text === entry.addressParameter);
2584
3631
  if (index === -1)
2585
3632
  return;
2586
3633
  const argument = call.arguments[index];
2587
- return argument && ts11.isStringLiteralLike(argument) ? argument.text : undefined;
3634
+ return argument && ts13.isStringLiteralLike(argument) ? argument.text : undefined;
2588
3635
  }
2589
3636
  function innermostLiteralAt(sourceFile, position) {
2590
3637
  let found;
2591
3638
  const visit = (node) => {
2592
3639
  if (position < node.getStart(sourceFile) || position > node.getEnd())
2593
3640
  return;
2594
- if (ts11.isStringLiteralLike(node))
3641
+ if (ts13.isStringLiteralLike(node))
2595
3642
  found = node;
2596
- ts11.forEachChild(node, visit);
3643
+ ts13.forEachChild(node, visit);
2597
3644
  };
2598
- ts11.forEachChild(sourceFile, visit);
3645
+ ts13.forEachChild(sourceFile, visit);
2599
3646
  return found;
2600
3647
  }
2601
3648
  function resolveClassifiedSlotAtPosition(input) {
@@ -2629,23 +3676,23 @@ function resolveClassifiedSlotAtPosition(input) {
2629
3676
  };
2630
3677
  }
2631
3678
  // src/url-cross-world-address.ts
2632
- import * as ts13 from "typescript";
3679
+ import * as ts15 from "typescript";
2633
3680
 
2634
3681
  // src/url-address-literals.ts
2635
- import * as ts12 from "typescript";
3682
+ import * as ts14 from "typescript";
2636
3683
  function isAmbient2(fileName) {
2637
3684
  return /[\\/]node_modules[\\/]/.test(fileName) || /(^|[\\/])lib\.[^\\/]*\.d\.ts$/.test(fileName);
2638
3685
  }
2639
3686
  function referenceTargetSymbol(type) {
2640
- if ((type.flags & ts12.TypeFlags.Object) === 0)
3687
+ if ((type.flags & ts14.TypeFlags.Object) === 0)
2641
3688
  return;
2642
- if ((type.objectFlags & ts12.ObjectFlags.Reference) === 0)
3689
+ if ((type.objectFlags & ts14.ObjectFlags.Reference) === 0)
2643
3690
  return;
2644
3691
  return type.target.symbol;
2645
3692
  }
2646
3693
  function ambientHashAliasTargets(checker, anchor) {
2647
3694
  const targets = new Set;
2648
- for (const symbol of checker.getSymbolsInScope(anchor, ts12.SymbolFlags.Type)) {
3695
+ for (const symbol of checker.getSymbolsInScope(anchor, ts14.SymbolFlags.Type)) {
2649
3696
  if (symbol.name !== "Hash")
2650
3697
  continue;
2651
3698
  const target = referenceTargetSymbol(checker.getDeclaredTypeOfSymbol(symbol));
@@ -2661,7 +3708,7 @@ function canonicalHashSymbols(checker, program) {
2661
3708
  if (!anchor)
2662
3709
  return canonical;
2663
3710
  const aliased = ambientHashAliasTargets(checker, anchor);
2664
- for (const symbol of checker.getSymbolsInScope(anchor, ts12.SymbolFlags.Function)) {
3711
+ for (const symbol of checker.getSymbolsInScope(anchor, ts14.SymbolFlags.Function)) {
2665
3712
  if (symbol.name !== "hash")
2666
3713
  continue;
2667
3714
  const declaration = symbol.declarations?.[0];
@@ -2683,7 +3730,7 @@ function hashedSourceOfType(checker, type, canonical) {
2683
3730
  return source?.isStringLiteral() ? source.value : undefined;
2684
3731
  }
2685
3732
  function staticAddressTextOf(checker, node, canonical) {
2686
- return ts12.isStringLiteralLike(node) ? node.text : hashedSourceOfType(checker, checker.getTypeAtLocation(node), canonical);
3733
+ return ts14.isStringLiteralLike(node) ? node.text : hashedSourceOfType(checker, checker.getTypeAtLocation(node), canonical);
2687
3734
  }
2688
3735
 
2689
3736
  // src/url-cross-world-address.ts
@@ -2707,7 +3754,7 @@ function checkCrossWorldAddresses(input) {
2707
3754
  if (worlds.length === 0)
2708
3755
  continue;
2709
3756
  const visit = (node) => {
2710
- if (ts13.isExpression(node)) {
3757
+ if (ts15.isExpression(node)) {
2711
3758
  const entry = addressEntryOfArgument(checker, table, node);
2712
3759
  if (entry !== undefined && slotRejectsForeignSocket(entry)) {
2713
3760
  const address = staticAddressTextOf(checker, node, canonical);
@@ -2724,14 +3771,14 @@ function checkCrossWorldAddresses(input) {
2724
3771
  }
2725
3772
  }
2726
3773
  }
2727
- ts13.forEachChild(node, visit);
3774
+ ts15.forEachChild(node, visit);
2728
3775
  };
2729
- ts13.forEachChild(sourceFile, visit);
3776
+ ts15.forEachChild(sourceFile, visit);
2730
3777
  }
2731
3778
  return findings;
2732
3779
  }
2733
3780
  // src/url-fragment-reachability.ts
2734
- import * as ts14 from "typescript";
3781
+ import * as ts16 from "typescript";
2735
3782
  function pathOfKey(key) {
2736
3783
  const socket = socketOfAddress(key);
2737
3784
  return socket === undefined ? key : key.slice(socket.length + 1);
@@ -2776,7 +3823,7 @@ function checkUrlFragmentReachability(input) {
2776
3823
  if (isAmbient2(sourceFile.fileName))
2777
3824
  continue;
2778
3825
  const visit = (node) => {
2779
- if (ts14.isExpression(node) && isAddressClass(addressClassOfArgument(checker, table, node))) {
3826
+ if (ts16.isExpression(node) && isAddressClass(addressClassOfArgument(checker, table, node))) {
2780
3827
  const text = staticAddressTextOf(checker, node, canonical);
2781
3828
  if (text !== undefined) {
2782
3829
  const hash = text.indexOf("#");
@@ -2795,26 +3842,29 @@ function checkUrlFragmentReachability(input) {
2795
3842
  }
2796
3843
  }
2797
3844
  }
2798
- ts14.forEachChild(node, visit);
3845
+ ts16.forEachChild(node, visit);
2799
3846
  };
2800
- ts14.forEachChild(sourceFile, visit);
3847
+ ts16.forEachChild(sourceFile, visit);
2801
3848
  }
2802
3849
  return { kind: "checked", findings };
2803
3850
  }
2804
3851
  export {
2805
3852
  ANIMATION_ASSET_EXTENSIONS,
2806
3853
  COLLECTION_REFERENCE_EXTENSIONS,
3854
+ COMPANION_INTERNAL_FIELD,
2807
3855
  DEFAULT_INCLUDE,
2808
3856
  GAME_PROJECT_DOCUMENT,
2809
3857
  GUI_EXTENSIONS,
2810
3858
  INPUT_BINDING_EXTENSIONS,
2811
3859
  LIBRARY_DEPENDENCY_ROOT,
2812
3860
  LIBRARY_SCENE_EXTENSIONS,
3861
+ LUALIB_REQUIRE_NAME,
2813
3862
  PROJECT_EXTENSIONS,
2814
3863
  SCAFFOLDED_DEFIGNORE_LINES,
2815
3864
  SCENE_EXTENSIONS,
2816
3865
  SCRIPT_SUFFIX_BY_KIND,
2817
3866
  SceneTextFormatError,
3867
+ TIMERS_REQUIRE_NAME,
2818
3868
  addressClassOfArgument,
2819
3869
  addressEntryOfArgument,
2820
3870
  archiveWrapperOf,
@@ -2833,9 +3883,11 @@ export {
2833
3883
  collectComponentDeclarations,
2834
3884
  collectComponentIds,
2835
3885
  componentIdOfSameObjectAddress,
3886
+ computeCompanionClosure,
2836
3887
  computeOutputRel,
2837
3888
  createTranspileSession,
2838
3889
  displayPathOf,
3890
+ findEmittedRequires,
2839
3891
  findMainEntryFactoryImports,
2840
3892
  getProgramDiagnostics,
2841
3893
  guiScriptResourceOf,
@@ -2854,7 +3906,9 @@ export {
2854
3906
  readLibrarySceneDocuments,
2855
3907
  readSceneDocuments,
2856
3908
  relativeAddressesFrom,
3909
+ requirePathForRel,
2857
3910
  resolveClassifiedSlotAtPosition,
3911
+ rewriteEmittedRequires,
2858
3912
  slotRejectsForeignSocket,
2859
3913
  socketOfAddress,
2860
3914
  stripIncludeBase,