@defold-typescript/transpiler 0.30.0 → 0.31.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.
Files changed (37) hide show
  1. package/dist/animation-sources.d.ts +6 -0
  2. package/dist/{sprite-animation-index.d.ts → component-animation-index.d.ts} +4 -4
  3. package/dist/config-key-index.d.ts +1 -0
  4. package/dist/gui-flipbook-index.d.ts +8 -0
  5. package/dist/gui-node-index.d.ts +21 -0
  6. package/dist/index.d.ts +19 -9
  7. package/dist/index.js +1329 -400
  8. package/dist/library-dependencies.d.ts +47 -0
  9. package/dist/scene-address-declaration.d.ts +4 -2
  10. package/dist/scene-collection-roles.d.ts +30 -0
  11. package/dist/scene-component-index.d.ts +20 -0
  12. package/dist/scene-documents.d.ts +3 -0
  13. package/dist/scene-naming-context.d.ts +66 -0
  14. package/dist/scene-object-path-index.d.ts +4 -1
  15. package/dist/url-address-literals.d.ts +10 -0
  16. package/dist/url-address-slots.d.ts +3 -1
  17. package/dist/url-cross-world-address.d.ts +41 -0
  18. package/dist/url-fragment-reachability.d.ts +3 -0
  19. package/package.json +2 -2
  20. package/src/animation-sources.ts +90 -0
  21. package/src/component-animation-index.ts +399 -0
  22. package/src/config-key-index.ts +28 -2
  23. package/src/gui-flipbook-index.ts +91 -0
  24. package/src/gui-node-index.ts +42 -4
  25. package/src/index.ts +52 -10
  26. package/src/library-dependencies.ts +337 -0
  27. package/src/scene-address-declaration.ts +29 -12
  28. package/src/scene-collection-roles.ts +267 -0
  29. package/src/scene-component-index.ts +74 -6
  30. package/src/scene-documents.ts +66 -6
  31. package/src/scene-naming-context.ts +130 -0
  32. package/src/scene-object-path-index.ts +172 -33
  33. package/src/url-address-literals.ts +115 -0
  34. package/src/url-address-slots.ts +23 -2
  35. package/src/url-cross-world-address.ts +109 -0
  36. package/src/url-fragment-reachability.ts +72 -93
  37. package/src/sprite-animation-index.ts +0 -241
package/dist/index.js CHANGED
@@ -1,30 +1,3 @@
1
- // src/config-key-index.ts
2
- function sectionNameOf(line) {
3
- const match = line.trim().match(/^\[(.+)\]$/);
4
- return match?.[1];
5
- }
6
- function buildConfigKeyIndex(gameProjectText) {
7
- const keys = new Set;
8
- let section;
9
- for (const line of gameProjectText.split(`
10
- `)) {
11
- const header = sectionNameOf(line);
12
- if (header !== undefined) {
13
- section = header;
14
- continue;
15
- }
16
- if (section === undefined)
17
- continue;
18
- const separator = line.indexOf("=");
19
- if (separator === -1)
20
- continue;
21
- const key = line.slice(0, separator).trim();
22
- if (key === "")
23
- continue;
24
- keys.add(`${section}.${key}`);
25
- }
26
- return keys;
27
- }
28
1
  // src/scene-text-format.ts
29
2
  class SceneTextFormatError extends Error {
30
3
  line;
@@ -180,77 +153,837 @@ function parseSceneTextFormat(text) {
180
153
  }
181
154
  return { fields, messages };
182
155
  }
183
- return parseBody(false, 1);
156
+ return parseBody(false, 1);
157
+ }
158
+
159
+ // src/animation-sources.ts
160
+ function resourceKey(resource) {
161
+ return resource.startsWith("/") ? resource.slice(1) : resource;
162
+ }
163
+ function normalizeDocumentKey(path) {
164
+ return path.replaceAll("\\", "/");
165
+ }
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) ?? [];
172
+ }
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);
179
+ }
180
+ }
181
+ return ids;
182
+ }
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)))
188
+ 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})`);
196
+ continue;
197
+ }
198
+ animationsByTileSet.set(displayPath, declaredAnimations(document));
199
+ }
200
+ return animationsByTileSet;
201
+ }
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
+ }
209
+ }
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)
242
+ 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})`);
250
+ continue;
251
+ }
252
+ if (isSprite) {
253
+ const tileSet = firstField(document, "tile_set");
254
+ if (tileSet !== undefined)
255
+ tileSetBySprite.set(displayPath, resourceKey(tileSet));
256
+ continue;
257
+ }
258
+ if (isModel) {
259
+ modelDocuments.add(displayPath);
260
+ const animations = firstField(document, "animations");
261
+ if (animations !== undefined)
262
+ animationSourceByModel.set(displayPath, animations);
263
+ continue;
264
+ }
265
+ entriesByAnimationSet.set(displayPath, animationSetEntries(document));
266
+ }
267
+ return {
268
+ animationsByTileSet,
269
+ tileSetBySprite,
270
+ modelDocuments,
271
+ animationSourceByModel,
272
+ entriesByAnimationSet
273
+ };
274
+ }
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;
300
+ }
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);
317
+ }
318
+ }
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;
325
+ }
326
+ sources.set(id, { kind: "model", path: resourceKey(animations) });
327
+ }
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);
354
+ }
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;
365
+ }
366
+ sources.set(id, { kind: "sprite", path: tileSet });
367
+ continue;
368
+ }
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;
381
+ }
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);
391
+ }
392
+ return resources;
393
+ }
394
+ function isGameObject(message) {
395
+ return message.messages.has("components") || message.messages.has("embedded_components");
396
+ }
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);
406
+ }
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})`);
415
+ }
416
+ }
417
+ }
418
+ for (const [name, nested] of message.messages) {
419
+ for (const child of nested) {
420
+ walk(child, name, displayPath);
421
+ }
422
+ }
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`);
430
+ return;
431
+ }
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`);
443
+ }
444
+ return;
445
+ }
446
+ function claim(object, displayPath) {
447
+ const scripts = scriptResourcesOf(object);
448
+ if (scripts.length === 0)
449
+ 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
+ }
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;
467
+ }
468
+ claimedBy.set(key, displayPath);
469
+ byScriptResource.set(key, animations);
470
+ sourceByScriptResource.set(key, declaringSources);
471
+ }
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})`);
481
+ }
482
+ }
483
+ return { byScriptResource, sourceByScriptResource, unresolved };
484
+ }
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;
492
+ }
493
+ // src/config-key-index.ts
494
+ function sectionNameOf(line) {
495
+ const match = line.trim().match(/^\[(.+)\]$/);
496
+ return match?.[1];
497
+ }
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;
507
+ }
508
+ if (section === undefined)
509
+ continue;
510
+ const separator = line.indexOf("=");
511
+ if (separator === -1)
512
+ continue;
513
+ const key = line.slice(0, separator).trim();
514
+ if (key === "")
515
+ continue;
516
+ keys.add(`${section}.${key}`);
517
+ }
518
+ return keys;
519
+ }
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;
528
+ }
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
+ }
540
+ return;
541
+ }
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;
549
+ }
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})`);
564
+ continue;
565
+ }
566
+ const [script] = document.fields.get("script") ?? [];
567
+ if (script === undefined)
568
+ continue;
569
+ const key = scriptKeyOf(script);
570
+ if (key === undefined)
571
+ 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`);
576
+ continue;
577
+ }
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
+ }
597
+ }
598
+ }
599
+ }
600
+ byScriptResource.set(key, declaredBy);
601
+ }
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;
625
+ continue;
626
+ }
627
+ if (resource !== undefined)
628
+ scripts.set(displayPath, resource);
629
+ }
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})`);
644
+ continue;
645
+ }
646
+ const key = guiScriptResourceOf(document);
647
+ if (key === undefined)
648
+ 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`);
653
+ continue;
654
+ }
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);
661
+ }
662
+ }
663
+ byScriptResource.set(key, ids);
664
+ }
665
+ return { byScriptResource, unresolved };
666
+ }
667
+ // src/input-action-index.ts
668
+ function buildInputActionIndex(documents) {
669
+ const actions = new Set;
670
+ for (const text of documents.values()) {
671
+ let document;
672
+ try {
673
+ document = parseSceneTextFormat(text);
674
+ } catch (error) {
675
+ if (!(error instanceof SceneTextFormatError))
676
+ throw error;
677
+ continue;
678
+ }
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
+ }
687
+ }
688
+ return actions;
689
+ }
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}/`));
695
+ }
696
+
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;
721
+ }
722
+ function isBuildOutput(displayPath) {
723
+ return displayPath.split("/").includes("build");
724
+ }
725
+ function isExcludedProjectPath(displayPath) {
726
+ return isBuildOutput(displayPath) || isDefignoredPath(displayPath);
727
+ }
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))
740
+ continue;
741
+ const text = host.readFile(filePath);
742
+ if (text === undefined) {
743
+ unreadable.push(`${displayPath}: could not be read`);
744
+ continue;
745
+ }
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`);
754
+ continue;
755
+ }
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)))
771
+ continue;
772
+ paths.push(`/${displayPath}`);
773
+ }
774
+ return new Set(paths.sort());
184
775
  }
185
776
 
186
- // src/gui-node-index.ts
187
- var SCRIPT_SUFFIX = ".gui_script";
188
- function scriptKeyOf(script) {
189
- if (!script.endsWith(SCRIPT_SUFFIX))
190
- return;
191
- const key = script.startsWith("/") ? script.slice(1) : script;
192
- return key === SCRIPT_SUFFIX ? undefined : key;
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());
783
+ }
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] });
800
+ }
801
+ }
802
+ return deps;
193
803
  }
194
- function buildGuiNodeIndex(documents) {
195
- const byScriptResource = new Map;
196
- const claimedBy = new Map;
197
- const unresolved = [];
198
- for (const [displayPath, text] of documents) {
199
- let document;
200
- try {
201
- document = parseSceneTextFormat(text);
202
- } catch (error) {
203
- if (!(error instanceof SceneTextFormatError))
204
- throw error;
205
- unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
804
+ function archiveWrapperOf(entries) {
805
+ let wrapper;
806
+ for (const entry of entries) {
807
+ const separator = entry.indexOf("/");
808
+ if (separator <= 0) {
809
+ return;
810
+ }
811
+ const segment = entry.slice(0, separator);
812
+ if (wrapper === undefined) {
813
+ wrapper = segment;
814
+ } else if (wrapper !== segment) {
815
+ return;
816
+ }
817
+ }
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]";
206
826
  continue;
207
827
  }
208
- const [script] = document.fields.get("script") ?? [];
209
- if (script === undefined)
828
+ if (!inLibrary) {
210
829
  continue;
211
- const key = scriptKeyOf(script);
212
- if (key === undefined)
830
+ }
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);
834
+ }
835
+ }
836
+ return [];
837
+ }
838
+ function isContainedResourcePath(path) {
839
+ if (path.length === 0 || path.includes("\\")) {
840
+ return false;
841
+ }
842
+ return path.split("/").every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
843
+ }
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
+ };
851
+ }
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
+ };
859
+ }
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)) {
213
867
  continue;
214
- const owner = claimedBy.get(key);
215
- if (owner !== undefined) {
216
- byScriptResource.delete(key);
217
- unresolved.push(`${key}: claimed by both ${owner} and ${displayPath}, so its node ids are ambiguous`);
868
+ }
869
+ const dot = path.lastIndexOf(".");
870
+ if (dot === -1 || !LIBRARY_SCENE_EXTENSIONS.includes(path.slice(dot).toLowerCase())) {
218
871
  continue;
219
872
  }
220
- claimedBy.set(key, displayPath);
221
- const ids = new Set;
222
- for (const message of document.messages.get("nodes") ?? []) {
223
- for (const id of message.fields.get("id") ?? []) {
224
- if (id !== "")
225
- ids.add(id);
226
- }
873
+ if (!isContainedResourcePath(path)) {
874
+ refused.push(entry);
875
+ continue;
227
876
  }
228
- byScriptResource.set(key, ids);
877
+ shared.push({ entry, path });
229
878
  }
230
- return { byScriptResource, unresolved };
879
+ shared.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
880
+ return { shared, reasons: [], refused };
231
881
  }
232
- // src/input-action-index.ts
233
- function buildInputActionIndex(documents) {
234
- const actions = new Set;
235
- for (const text of documents.values()) {
236
- let document;
237
- try {
238
- document = parseSceneTextFormat(text);
239
- } catch (error) {
240
- if (!(error instanceof SceneTextFormatError))
241
- throw error;
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}`;
886
+ }
887
+ function readMaterializedKeys(manifestText) {
888
+ let parsed;
889
+ try {
890
+ parsed = JSON.parse(manifestText);
891
+ } catch {
892
+ return;
893
+ }
894
+ const entries = parsed?.dependencies;
895
+ if (!Array.isArray(entries))
896
+ 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;
904
+ }
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`);
927
+ }
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`);
242
936
  continue;
243
937
  }
244
- for (const triggers of document.messages.values()) {
245
- for (const trigger of triggers) {
246
- for (const action of trigger.fields.get("action") ?? []) {
247
- if (action !== "")
248
- actions.add(action);
249
- }
938
+ resolved.push({ key, url });
939
+ urlByKey.set(key, url);
940
+ }
941
+ const root = joinProject(projectRoot, LIBRARY_DEPENDENCY_ROOT);
942
+ const filesByKey = new Map;
943
+ const stray = new Set;
944
+ for (const filePath of host.readDirectory(root, extensions)) {
945
+ const relative = displayPathOf(root, filePath);
946
+ if (relative === filePath.replace(/\\/g, "/"))
947
+ continue;
948
+ const separator = relative.indexOf("/");
949
+ if (separator <= 0)
950
+ continue;
951
+ const key = relative.slice(0, separator);
952
+ const url = urlByKey.get(key);
953
+ if (url === undefined) {
954
+ stray.add(key);
955
+ continue;
956
+ }
957
+ const bucket = filesByKey.get(key);
958
+ if (bucket === undefined)
959
+ filesByKey.set(key, [filePath]);
960
+ else
961
+ bucket.push(filePath);
962
+ }
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;
973
+ }
974
+ const text = host.readFile(hostPath);
975
+ if (text === undefined) {
976
+ unreadable.push(`${displayPath}: could not be read from ${url}`);
977
+ continue;
250
978
  }
979
+ claimed.set(displayPath, url);
980
+ documents.push({ displayPath, hostPath, url, text });
251
981
  }
252
982
  }
253
- return actions;
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`);
985
+ }
986
+ return { documents, unreadable };
254
987
  }
255
988
  // src/main-entry-factory-imports.ts
256
989
  import * as ts3 from "typescript";
@@ -826,25 +1559,30 @@ function getProgramDiagnostics(program, sourceFile) {
826
1559
  });
827
1560
  return diagnostics.filter((d) => d.file !== undefined && !isAmbient(d.file.fileName));
828
1561
  }
829
- // src/project-resources.ts
830
- var SCAFFOLDED_DEFIGNORE_LINES = ["/node_modules", "/.defold-types", "/.vscode"];
831
- function isDefignoredPath(displayPath) {
832
- const rooted = `/${displayPath.replace(/^\/+/, "")}`;
833
- return SCAFFOLDED_DEFIGNORE_LINES.some((line) => rooted === line || rooted.startsWith(`${line}/`));
834
- }
835
1562
  // src/scene-component-index.ts
836
1563
  var COMPONENT_BLOCKS = new Set(["components", "embedded_components"]);
837
1564
  var EMBEDDING_BLOCKS = new Set(["embedded_instances", "embedded_components"]);
838
- function collect(message, blockName, depth, displayPath, ids, incomplete) {
1565
+ function resourceKey2(resource) {
1566
+ return resource.startsWith("/") ? resource.slice(1) : resource;
1567
+ }
1568
+ function collect(message, blockName, depth, displayPath, declarations, incomplete) {
839
1569
  if (COMPONENT_BLOCKS.has(blockName)) {
840
- for (const id of message.fields.get("id") ?? []) {
841
- ids.add(id);
1570
+ const declared = message.fields.get("id") ?? [];
1571
+ for (const id of declared) {
1572
+ declarations.ids.add(id);
1573
+ }
1574
+ if (blockName === "components") {
1575
+ const [id] = declared;
1576
+ const [component] = message.fields.get("component") ?? [];
1577
+ if (id !== undefined && id !== "" && component !== undefined) {
1578
+ declarations.resources.set(id, resourceKey2(component));
1579
+ }
842
1580
  }
843
1581
  }
844
1582
  if (EMBEDDING_BLOCKS.has(blockName)) {
845
1583
  for (const payload of message.fields.get("data") ?? []) {
846
1584
  try {
847
- collect(parseSceneTextFormat(payload), "", depth + 1, displayPath, ids, incomplete);
1585
+ collect(parseSceneTextFormat(payload), "", depth + 1, displayPath, declarations, incomplete);
848
1586
  } catch (error) {
849
1587
  if (!(error instanceof SceneTextFormatError))
850
1588
  throw error;
@@ -854,10 +1592,18 @@ function collect(message, blockName, depth, displayPath, ids, incomplete) {
854
1592
  }
855
1593
  for (const [name, nested] of message.messages) {
856
1594
  for (const child of nested) {
857
- collect(child, name, depth, displayPath, ids, incomplete);
1595
+ collect(child, name, depth, displayPath, declarations, incomplete);
858
1596
  }
859
1597
  }
860
1598
  }
1599
+ function collectComponentDeclarations(message, displayPath, incomplete, blockName = "") {
1600
+ const declarations = { ids: new Set, resources: new Map };
1601
+ collect(message, blockName, 0, displayPath, declarations, incomplete);
1602
+ return declarations;
1603
+ }
1604
+ function collectComponentIds(message, displayPath, incomplete, blockName = "") {
1605
+ return collectComponentDeclarations(message, displayPath, incomplete, blockName).ids;
1606
+ }
861
1607
  function buildSceneComponentIndex(documents) {
862
1608
  const ids = new Set;
863
1609
  const incomplete = [];
@@ -867,7 +1613,9 @@ function buildSceneComponentIndex(documents) {
867
1613
  }
868
1614
  for (const [displayPath, text] of documents) {
869
1615
  try {
870
- collect(parseSceneTextFormat(text), "", 0, displayPath, ids, incomplete);
1616
+ for (const id of collectComponentIds(parseSceneTextFormat(text), displayPath, incomplete)) {
1617
+ ids.add(id);
1618
+ }
871
1619
  } catch (error) {
872
1620
  if (!(error instanceof SceneTextFormatError))
873
1621
  throw error;
@@ -879,20 +1627,27 @@ function buildSceneComponentIndex(documents) {
879
1627
 
880
1628
  // src/scene-object-path-index.ts
881
1629
  var LEAF_BLOCKS = ["instances", "embedded_instances"];
882
- function resourceKey(resource) {
1630
+ function resourceKey3(resource) {
883
1631
  return resource.startsWith("/") ? resource.slice(1) : resource;
884
1632
  }
885
- function firstField(message, name) {
1633
+ function firstField2(message, name) {
886
1634
  return (message.fields.get(name) ?? [])[0];
887
1635
  }
888
- function childrenOf(message, name) {
1636
+ function childrenOf3(message, name) {
889
1637
  return message.messages.get(name) ?? [];
890
1638
  }
891
- function buildSceneObjectPathIndex(documents) {
1639
+ function buildSceneObjectPathIndex(documents, roles) {
892
1640
  const incomplete = [];
893
1641
  if (documents.size === 0) {
894
1642
  incomplete.push("no scene sources were read, so no game-object path can be proven absent");
895
- return { paths: new Set, declaredIn: new Map, incomplete };
1643
+ incomplete.push(...roles.incomplete);
1644
+ return {
1645
+ paths: new Set,
1646
+ declaredIn: new Map,
1647
+ componentsOf: new Map,
1648
+ componentResourcesOf: new Map,
1649
+ incomplete
1650
+ };
896
1651
  }
897
1652
  const parsed = new Map;
898
1653
  for (const [displayPath, text] of documents) {
@@ -904,16 +1659,22 @@ function buildSceneObjectPathIndex(documents) {
904
1659
  incomplete.push(`${displayPath}: could not be parsed (${error.message})`);
905
1660
  }
906
1661
  }
907
- const instanced = new Set;
908
- for (const document of parsed.values()) {
909
- for (const block of childrenOf(document, "collection_instances")) {
910
- const collection = firstField(block, "collection");
911
- if (collection !== undefined)
912
- instanced.add(resourceKey(collection));
913
- }
914
- }
915
1662
  const composed = new Map;
916
1663
  const inProgress = new Set;
1664
+ const prototypeDeclarations = new Map;
1665
+ function declarationsOfPrototype(key) {
1666
+ const done = prototypeDeclarations.get(key);
1667
+ if (done !== undefined || prototypeDeclarations.has(key))
1668
+ return done;
1669
+ const document = parsed.get(key);
1670
+ let declarations;
1671
+ if (document !== undefined) {
1672
+ const collected = collectComponentDeclarations(document, key, incomplete);
1673
+ declarations = { components: [...collected.ids].sort(), resources: collected.resources };
1674
+ }
1675
+ prototypeDeclarations.set(key, declarations);
1676
+ return declarations;
1677
+ }
917
1678
  function pathsOf(displayPath) {
918
1679
  const done = composed.get(displayPath);
919
1680
  if (done !== undefined)
@@ -928,136 +1689,364 @@ function buildSceneObjectPathIndex(documents) {
928
1689
  inProgress.add(displayPath);
929
1690
  const paths2 = new Map;
930
1691
  for (const blockName of LEAF_BLOCKS) {
931
- for (const block of childrenOf(document, blockName)) {
932
- const id = firstField(block, "id");
933
- if (id !== undefined && id !== "")
934
- paths2.set(`/${id}`, displayPath);
1692
+ for (const block of childrenOf3(document, blockName)) {
1693
+ const id = firstField2(block, "id");
1694
+ if (id === undefined || id === "")
1695
+ continue;
1696
+ let components;
1697
+ let resources;
1698
+ if (blockName === "embedded_instances") {
1699
+ const before = incomplete.length;
1700
+ const declarations = collectComponentDeclarations(block, displayPath, incomplete, blockName);
1701
+ if (incomplete.length === before) {
1702
+ components = [...declarations.ids].sort();
1703
+ resources = declarations.resources;
1704
+ }
1705
+ } else {
1706
+ const prototype = firstField2(block, "prototype");
1707
+ if (prototype === undefined) {
1708
+ incomplete.push(`${displayPath}: the instance "${id}" declares no prototype, so the components it owns cannot be read`);
1709
+ } else if (parsed.has(resourceKey3(prototype))) {
1710
+ const declarations = declarationsOfPrototype(resourceKey3(prototype));
1711
+ components = declarations?.components;
1712
+ resources = declarations?.resources;
1713
+ } else {
1714
+ incomplete.push(`${displayPath}: the instance "${id}" names ${prototype}, which is not among the project's readable scene documents`);
1715
+ }
1716
+ }
1717
+ paths2.set(`/${id}`, { declarer: displayPath, components, resources });
1718
+ }
1719
+ }
1720
+ for (const block of childrenOf3(document, "collection_instances")) {
1721
+ const id = firstField2(block, "id");
1722
+ const collection = firstField2(block, "collection");
1723
+ if (id === undefined || id === "" || collection === undefined)
1724
+ continue;
1725
+ const key = resourceKey3(collection);
1726
+ if (!parsed.has(key)) {
1727
+ incomplete.push(`${displayPath}: the collection instance "${id}" names ${collection}, which is not among the project's readable scene documents`);
1728
+ continue;
1729
+ }
1730
+ for (const [nested, leaf] of pathsOf(key)) {
1731
+ paths2.set(`/${id}${nested}`, leaf);
1732
+ }
1733
+ }
1734
+ inProgress.delete(displayPath);
1735
+ composed.set(displayPath, paths2);
1736
+ return paths2;
1737
+ }
1738
+ for (const displayPath of parsed.keys())
1739
+ pathsOf(displayPath);
1740
+ const worlds = [];
1741
+ if (roles.bootstrap !== undefined)
1742
+ worlds.push(["", roles.bootstrap]);
1743
+ for (const [displayPath, socket] of roles.sockets)
1744
+ worlds.push([`${socket}:`, displayPath]);
1745
+ const paths = new Set;
1746
+ const declaredIn = new Map;
1747
+ const componentsOf = new Map;
1748
+ const componentResourcesOf = new Map;
1749
+ const withheld = new Set;
1750
+ for (const [prefix, displayPath] of worlds) {
1751
+ for (const [path2, leaf] of pathsOf(displayPath)) {
1752
+ const key = `${prefix}${path2}`;
1753
+ paths.add(key);
1754
+ const declarers = declaredIn.get(key);
1755
+ if (declarers === undefined) {
1756
+ declaredIn.set(key, [leaf.declarer]);
1757
+ } else if (!declarers.includes(leaf.declarer)) {
1758
+ declarers.push(leaf.declarer);
1759
+ }
1760
+ if (leaf.components === undefined || leaf.resources === undefined) {
1761
+ withheld.add(key);
1762
+ continue;
1763
+ }
1764
+ const owned = componentsOf.get(key);
1765
+ if (owned === undefined) {
1766
+ componentsOf.set(key, [...leaf.components]);
1767
+ } else {
1768
+ for (const id of leaf.components)
1769
+ if (!owned.includes(id))
1770
+ owned.push(id);
1771
+ }
1772
+ const referenced = componentResourcesOf.get(key);
1773
+ if (referenced === undefined) {
1774
+ componentResourcesOf.set(key, new Map(leaf.resources));
1775
+ } else {
1776
+ for (const [id, resource] of leaf.resources) {
1777
+ if (!referenced.has(id))
1778
+ referenced.set(id, resource);
1779
+ }
1780
+ }
1781
+ }
1782
+ }
1783
+ for (const declarers of declaredIn.values())
1784
+ declarers.sort();
1785
+ for (const key of withheld) {
1786
+ componentsOf.delete(key);
1787
+ componentResourcesOf.delete(key);
1788
+ }
1789
+ for (const owned of componentsOf.values())
1790
+ owned.sort();
1791
+ incomplete.push(...roles.incomplete);
1792
+ return {
1793
+ paths,
1794
+ declaredIn,
1795
+ componentsOf,
1796
+ componentResourcesOf,
1797
+ incomplete: [...new Set(incomplete)]
1798
+ };
1799
+ }
1800
+
1801
+ // src/scene-address-declaration.ts
1802
+ var BANNER = `// Generated from this project's scenes by \`defold-typescript scene-types\`.
1803
+ // Do not edit: every run rewrites it from the .go/.collection sources.
1804
+ //
1805
+ // Each key is an address the project declares. The aliases in
1806
+ // \`@defold-typescript/types\` stay widened with \`(string & {})\`, so these keys
1807
+ // only add completions — an address composed at runtime is never rejected.
1808
+ `;
1809
+ function componentAddressesOf(documents, paths) {
1810
+ const keys = [...buildSceneComponentIndex(documents).ids].map((id) => `#${id}`);
1811
+ for (const [path2, ids] of paths.componentsOf) {
1812
+ for (const id of ids)
1813
+ keys.push(`${path2}#${id}`);
1814
+ }
1815
+ return keys;
1816
+ }
1817
+ function bodyOf(keys) {
1818
+ if (keys.length === 0)
1819
+ return "{}";
1820
+ const members = keys.map((key) => ` ${JSON.stringify(key)}: true;
1821
+ `).sort().join("");
1822
+ return `{
1823
+ ${members} }`;
1824
+ }
1825
+ function buildSceneAddressDeclaration(documents, roles) {
1826
+ const index = buildSceneObjectPathIndex(documents, roles);
1827
+ const components = componentAddressesOf(documents, index);
1828
+ return `${BANNER}
1829
+ declare global {
1830
+ interface SceneGameObjectAddresses ${bodyOf([...index.paths])}
1831
+
1832
+ interface SceneComponentAddresses ${bodyOf(components)}
1833
+ }
1834
+
1835
+ export {};
1836
+ `;
1837
+ }
1838
+ // src/scene-collection-roles.ts
1839
+ var EMBEDDING_BLOCKS2 = new Set(["embedded_instances", "embedded_components"]);
1840
+ var REFERENCE_KINDS = [
1841
+ { extension: ".collectionproxy", type: "collectionproxy", field: "collection" },
1842
+ { extension: ".collectionfactory", type: "collectionfactory", field: "prototype" }
1843
+ ];
1844
+ function resourceKey4(resource) {
1845
+ return resource.startsWith("/") ? resource.slice(1) : resource;
1846
+ }
1847
+ function collectionKey(resource) {
1848
+ return resourceKey4(resource).replace(/\.collectionc$/, ".collection");
1849
+ }
1850
+ function firstField3(message, name) {
1851
+ return (message.fields.get(name) ?? [])[0];
1852
+ }
1853
+ function isCollection(displayPath) {
1854
+ return displayPath.endsWith(".collection");
1855
+ }
1856
+ function buildSceneCollectionRoles(input) {
1857
+ const { documents, references, gameProject } = input;
1858
+ const incomplete = [];
1859
+ const parsed = new Map;
1860
+ for (const [displayPath, text] of documents) {
1861
+ try {
1862
+ parsed.set(displayPath, parseSceneTextFormat(text));
1863
+ } catch (error) {
1864
+ if (!(error instanceof SceneTextFormatError))
1865
+ throw error;
1866
+ incomplete.push(`${displayPath}: could not be parsed (${error.message})`);
1867
+ }
1868
+ }
1869
+ const instanced = new Set;
1870
+ for (const document of parsed.values()) {
1871
+ for (const block of document.messages.get("collection_instances") ?? []) {
1872
+ const collection = firstField3(block, "collection");
1873
+ if (collection !== undefined)
1874
+ instanced.add(resourceKey4(collection));
1875
+ }
1876
+ }
1877
+ const proxied = new Set;
1878
+ const factoryPrototypes = new Set;
1879
+ function record(kind, resource) {
1880
+ (kind.type === "collectionproxy" ? proxied : factoryPrototypes).add(collectionKey(resource));
1881
+ }
1882
+ function followReferenceDocument(displayPath, resource) {
1883
+ const kind = REFERENCE_KINDS.find((candidate) => resource.endsWith(candidate.extension));
1884
+ if (kind === undefined)
1885
+ return;
1886
+ const text = references.get(resourceKey4(resource));
1887
+ if (text === undefined) {
1888
+ incomplete.push(`${displayPath}: the component ${resource} is not among the project's readable ${kind.extension} documents, so the collection it names is unclassified`);
1889
+ return;
1890
+ }
1891
+ let document;
1892
+ try {
1893
+ document = parseSceneTextFormat(text);
1894
+ } catch (error) {
1895
+ if (!(error instanceof SceneTextFormatError))
1896
+ throw error;
1897
+ incomplete.push(`${resourceKey4(resource)}: could not be parsed (${error.message})`);
1898
+ return;
1899
+ }
1900
+ const target = firstField3(document, kind.field);
1901
+ if (target === undefined) {
1902
+ incomplete.push(`${resourceKey4(resource)}: declares no ${kind.field}:, so the collection it names is unclassified`);
1903
+ return;
1904
+ }
1905
+ record(kind, target);
1906
+ }
1907
+ function walk(message, blockName, displayPath, depth) {
1908
+ if (blockName === "components") {
1909
+ const component = firstField3(message, "component");
1910
+ if (component !== undefined)
1911
+ followReferenceDocument(displayPath, component);
1912
+ }
1913
+ const payloads = [];
1914
+ if (EMBEDDING_BLOCKS2.has(blockName)) {
1915
+ for (const payload of message.fields.get("data") ?? []) {
1916
+ try {
1917
+ payloads.push(parseSceneTextFormat(payload));
1918
+ } catch (error) {
1919
+ if (!(error instanceof SceneTextFormatError))
1920
+ throw error;
1921
+ incomplete.push(`${displayPath}: embedded payload at depth ${depth + 1} could not be parsed (${error.message})`);
1922
+ }
935
1923
  }
936
1924
  }
937
- for (const block of childrenOf(document, "collection_instances")) {
938
- const id = firstField(block, "id");
939
- const collection = firstField(block, "collection");
940
- if (id === undefined || id === "" || collection === undefined)
941
- continue;
942
- const key = resourceKey(collection);
943
- if (!parsed.has(key)) {
944
- incomplete.push(`${displayPath}: the collection instance "${id}" names ${collection}, which is not among the project's readable scene documents`);
945
- continue;
946
- }
947
- for (const [nested, declarer] of pathsOf(key)) {
948
- paths2.set(`/${id}${nested}`, declarer);
1925
+ if (blockName === "embedded_components") {
1926
+ const type = firstField3(message, "type");
1927
+ const kind = REFERENCE_KINDS.find((candidate) => candidate.type === type);
1928
+ if (kind !== undefined) {
1929
+ for (const payload of payloads) {
1930
+ const target = firstField3(payload, kind.field);
1931
+ if (target === undefined) {
1932
+ incomplete.push(`${displayPath}: the embedded ${kind.type} declares no ${kind.field}:, so the collection it names is unclassified`);
1933
+ continue;
1934
+ }
1935
+ record(kind, target);
1936
+ }
1937
+ }
1938
+ }
1939
+ for (const payload of payloads)
1940
+ walk(payload, "", displayPath, depth + 1);
1941
+ for (const [name, nested] of message.messages) {
1942
+ for (const child of nested)
1943
+ walk(child, name, displayPath, depth);
1944
+ }
1945
+ }
1946
+ for (const [displayPath, document] of parsed)
1947
+ walk(document, "", displayPath, 0);
1948
+ const sockets = new Map;
1949
+ const byName = new Map;
1950
+ for (const displayPath of [...proxied].sort()) {
1951
+ const document = parsed.get(displayPath);
1952
+ if (document === undefined) {
1953
+ incomplete.push(`${displayPath}: is opened as a collection proxy but is not among the project's readable scene documents, so its world has no name`);
1954
+ continue;
1955
+ }
1956
+ const name = firstField3(document, "name");
1957
+ if (name === undefined || name === "") {
1958
+ incomplete.push(`${displayPath}: is opened as a collection proxy but declares no name:, so its world cannot be addressed`);
1959
+ continue;
1960
+ }
1961
+ sockets.set(displayPath, name);
1962
+ byName.set(name, [...byName.get(name) ?? [], displayPath]);
1963
+ }
1964
+ for (const [name, holders] of byName) {
1965
+ if (holders.length > 1) {
1966
+ incomplete.push(`${holders.join(", ")}: all declare name: "${name}", so an address under that socket does not say which world it means`);
1967
+ }
1968
+ }
1969
+ const hasCollections = [...parsed.keys()].some(isCollection);
1970
+ let bootstrap;
1971
+ if (gameProject === undefined) {
1972
+ if (hasCollections) {
1973
+ incomplete.push("game.project could not be read, so no collection is known to be the bootstrap world and no address is offered unqualified");
1974
+ }
1975
+ } else {
1976
+ const main = readGameProjectSetting(gameProject, "bootstrap", "main_collection");
1977
+ if (main === undefined) {
1978
+ if (hasCollections) {
1979
+ incomplete.push("game.project declares no [bootstrap] main_collection, so no collection is the bootstrap world");
949
1980
  }
1981
+ } else if (!parsed.has(collectionKey(main))) {
1982
+ incomplete.push(`game.project names ${main} as [bootstrap] main_collection, which is not among the project's readable scene documents`);
1983
+ } else {
1984
+ bootstrap = collectionKey(main);
950
1985
  }
951
- inProgress.delete(displayPath);
952
- composed.set(displayPath, paths2);
953
- return paths2;
954
1986
  }
955
- const paths = new Set;
956
- const declaredIn = new Map;
957
1987
  for (const displayPath of parsed.keys()) {
958
- const own = pathsOf(displayPath);
959
- if (instanced.has(displayPath))
1988
+ if (!isCollection(displayPath))
1989
+ continue;
1990
+ if (displayPath === bootstrap || sockets.has(displayPath) || instanced.has(displayPath) || factoryPrototypes.has(displayPath)) {
960
1991
  continue;
961
- for (const [path2, declarer] of own) {
962
- paths.add(path2);
963
- const declarers = declaredIn.get(path2);
964
- if (declarers === undefined) {
965
- declaredIn.set(path2, [declarer]);
966
- } else if (!declarers.includes(declarer)) {
967
- declarers.push(declarer);
968
- }
969
1992
  }
1993
+ incomplete.push(`${displayPath}: is reached by no bootstrap, proxy, instance or factory reference, so which world its objects live in is unknown`);
970
1994
  }
971
- for (const declarers of declaredIn.values())
972
- declarers.sort();
973
- return { paths, declaredIn, incomplete };
974
- }
975
-
976
- // src/scene-address-declaration.ts
977
- var BANNER = `// Generated from this project's scenes by \`defold-typescript scene-types\`.
978
- // Do not edit: every run rewrites it from the .go/.collection sources.
979
- //
980
- // Each key is an address the project declares. The aliases in
981
- // \`@defold-typescript/types\` stay widened with \`(string & {})\`, so these keys
982
- // only add completions — an address composed at runtime is never rejected.
983
- `;
984
- function componentAddressesOf(documents) {
985
- return [...buildSceneComponentIndex(documents).ids].map((id) => `#${id}`);
986
- }
987
- function bodyOf(keys) {
988
- if (keys.length === 0)
989
- return "{}";
990
- const members = keys.map((key) => ` ${JSON.stringify(key)}: true;
991
- `).sort().join("");
992
- return `{
993
- ${members} }`;
994
- }
995
- function buildSceneAddressDeclaration(documents) {
996
- const paths = [...buildSceneObjectPathIndex(documents).paths];
997
- const components = componentAddressesOf(documents);
998
- return `${BANNER}
999
- declare global {
1000
- interface SceneGameObjectAddresses ${bodyOf(paths)}
1001
-
1002
- interface SceneComponentAddresses ${bodyOf(components)}
1003
- }
1004
-
1005
- export {};
1006
- `;
1007
- }
1008
- // src/scene-documents.ts
1009
- var SCENE_EXTENSIONS = [".go", ".collection"];
1010
- var GUI_EXTENSIONS = [".gui"];
1011
- var INPUT_BINDING_EXTENSIONS = [".input_binding"];
1012
- var ANIMATION_ASSET_EXTENSIONS = [".atlas", ".tilesource", ".sprite"];
1013
- var PROJECT_EXTENSIONS = [".project"];
1014
- var GAME_PROJECT_DOCUMENT = "game.project";
1015
- function displayPathOf(projectRoot, filePath) {
1016
- const path2 = filePath.replace(/\\/g, "/");
1017
- const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
1018
- return path2.startsWith(`${root}/`) ? path2.slice(root.length + 1) : path2;
1995
+ return { bootstrap, sockets, instanced, factoryPrototypes, incomplete };
1019
1996
  }
1020
- function isBuildOutput(displayPath) {
1021
- return displayPath.split("/").includes("build");
1997
+ // src/scene-naming-context.ts
1998
+ var SCRIPT_SUFFIX4 = ".script";
1999
+ var GUI_SUFFIX = ".gui";
2000
+ function socketOfAddress(address) {
2001
+ const pathStart = address.indexOf("/");
2002
+ const fragmentStart = address.indexOf("#");
2003
+ const ends = [pathStart, fragmentStart, address.length].filter((end) => end >= 0);
2004
+ const headEnd = Math.min(...ends);
2005
+ const delimiter = address.indexOf(":");
2006
+ return delimiter > 0 && delimiter < headEnd ? address.slice(0, delimiter) : undefined;
1022
2007
  }
1023
- function isExcludedProjectPath(displayPath) {
1024
- return isBuildOutput(displayPath) || isDefignoredPath(displayPath);
2008
+ function contextOf(object) {
2009
+ return {
2010
+ object,
2011
+ socket: socketOfAddress(object),
2012
+ prefix: object.slice(0, object.lastIndexOf("/") + 1)
2013
+ };
1025
2014
  }
1026
- function readSceneDocuments(host, projectRoot, extensions = SCENE_EXTENSIONS) {
1027
- const documents = new Map;
1028
- const unreadable = [];
1029
- const paths = [];
1030
- if (!host.readDirectory) {
1031
- unreadable.push("the editor host cannot enumerate project files, so no scene source was read");
1032
- return { documents, unreadable, paths };
1033
- }
1034
- for (const filePath of host.readDirectory(projectRoot, extensions)) {
1035
- const displayPath = displayPathOf(projectRoot, filePath);
1036
- if (isExcludedProjectPath(displayPath))
1037
- continue;
1038
- const text = host.readFile(filePath);
1039
- if (text === undefined) {
1040
- unreadable.push(`${displayPath}: could not be read`);
1041
- continue;
2015
+ function buildScriptNamingContexts(index, guiScripts) {
2016
+ const contexts = new Map;
2017
+ for (const [object, resources] of index.componentResourcesOf) {
2018
+ for (const resource of resources.values()) {
2019
+ const script = resource.endsWith(SCRIPT_SUFFIX4) ? resource : resource.endsWith(GUI_SUFFIX) ? guiScripts.get(resource) : undefined;
2020
+ if (script === undefined)
2021
+ continue;
2022
+ const hosted = contexts.get(script);
2023
+ if (hosted === undefined) {
2024
+ contexts.set(script, [contextOf(object)]);
2025
+ } else if (!hosted.some((context) => context.object === object)) {
2026
+ hosted.push(contextOf(object));
2027
+ }
1042
2028
  }
1043
- documents.set(displayPath, text);
1044
- paths.push(filePath);
1045
2029
  }
1046
- return { documents, unreadable, paths };
2030
+ for (const hosted of contexts.values()) {
2031
+ hosted.sort((a, b) => a.object.localeCompare(b.object));
2032
+ }
2033
+ return contexts;
1047
2034
  }
1048
- function listProjectResourcePaths(host, projectRoot, extensions) {
1049
- if (!host.readDirectory)
1050
- return new Set;
1051
- const paths = [];
1052
- for (const filePath of host.readDirectory(projectRoot, extensions)) {
1053
- const displayPath = displayPathOf(projectRoot, filePath);
1054
- if (isExcludedProjectPath(displayPath))
2035
+ function relativeAddressesFrom(index, context) {
2036
+ const paths = new Set;
2037
+ const addresses = new Set;
2038
+ for (const key of index.paths) {
2039
+ if (!key.startsWith(context.prefix))
1055
2040
  continue;
1056
- if (!extensions.some((extension) => displayPath.endsWith(extension)))
2041
+ const relative = key.slice(context.prefix.length);
2042
+ if (relative === "")
1057
2043
  continue;
1058
- paths.push(`/${displayPath}`);
2044
+ paths.add(relative);
2045
+ for (const id of index.componentsOf.get(key) ?? []) {
2046
+ addresses.add(`${relative}#${id}`);
2047
+ }
1059
2048
  }
1060
- return new Set(paths.sort());
2049
+ return { paths, addresses };
1061
2050
  }
1062
2051
  // src/session.ts
1063
2052
  import { readFileSync as readFileSync2 } from "node:fs";
@@ -1484,179 +2473,6 @@ function createTranspileSession() {
1484
2473
  getProgram: () => program
1485
2474
  };
1486
2475
  }
1487
- // src/sprite-animation-index.ts
1488
- function resourceKey2(resource) {
1489
- return resource.startsWith("/") ? resource.slice(1) : resource;
1490
- }
1491
- function normalizeDocumentKey(path4) {
1492
- return path4.replaceAll("\\", "/");
1493
- }
1494
- var SCRIPT_SUFFIX2 = ".script";
1495
- var SPRITE_SUFFIX = ".sprite";
1496
- function firstField2(message, name) {
1497
- return (message.fields.get(name) ?? [])[0];
1498
- }
1499
- function childrenOf2(message, name) {
1500
- return message.messages.get(name) ?? [];
1501
- }
1502
- function declaredAnimations(document) {
1503
- const ids = new Set;
1504
- for (const animation of childrenOf2(document, "animations")) {
1505
- for (const id of animation.fields.get("id") ?? []) {
1506
- if (id !== "")
1507
- ids.add(id);
1508
- }
1509
- }
1510
- return ids;
1511
- }
1512
- function readAssets(assets, unresolved) {
1513
- const animationsByTileSet = new Map;
1514
- const tileSetBySprite = new Map;
1515
- for (const [path4, text] of assets) {
1516
- const displayPath = normalizeDocumentKey(path4);
1517
- let document;
1518
- try {
1519
- document = parseSceneTextFormat(text);
1520
- } catch (error) {
1521
- if (!(error instanceof SceneTextFormatError))
1522
- throw error;
1523
- unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
1524
- continue;
1525
- }
1526
- if (displayPath.endsWith(SPRITE_SUFFIX)) {
1527
- const tileSet = firstField2(document, "tile_set");
1528
- if (tileSet !== undefined)
1529
- tileSetBySprite.set(displayPath, resourceKey2(tileSet));
1530
- continue;
1531
- }
1532
- animationsByTileSet.set(displayPath, declaredAnimations(document));
1533
- }
1534
- return { animationsByTileSet, tileSetBySprite };
1535
- }
1536
- function spriteTileSets(object, displayPath, assets, unresolved) {
1537
- const tileSets = new Map;
1538
- for (const embedded of childrenOf2(object, "embedded_components")) {
1539
- if (firstField2(embedded, "type") !== "sprite")
1540
- continue;
1541
- const id = firstField2(embedded, "id");
1542
- const payload = firstField2(embedded, "data");
1543
- if (id === undefined || payload === undefined)
1544
- continue;
1545
- try {
1546
- const tileSet = firstField2(parseSceneTextFormat(payload), "tile_set");
1547
- if (tileSet !== undefined)
1548
- tileSets.set(id, resourceKey2(tileSet));
1549
- } catch (error) {
1550
- if (!(error instanceof SceneTextFormatError))
1551
- throw error;
1552
- unresolved.push(`${displayPath}: the embedded sprite "${id}" could not be read (${error.message})`);
1553
- }
1554
- }
1555
- for (const referenced of childrenOf2(object, "components")) {
1556
- const component = firstField2(referenced, "component");
1557
- const id = firstField2(referenced, "id");
1558
- if (id === undefined || component === undefined || !component.endsWith(SPRITE_SUFFIX))
1559
- continue;
1560
- const tileSet = assets.tileSetBySprite.get(resourceKey2(component));
1561
- if (tileSet === undefined) {
1562
- unresolved.push(`${displayPath}: the sprite component "${id}" names ${component}, which is not among the project's sprite documents`);
1563
- continue;
1564
- }
1565
- tileSets.set(id, tileSet);
1566
- }
1567
- return tileSets;
1568
- }
1569
- function scriptResourcesOf(object) {
1570
- const resources = [];
1571
- for (const component of childrenOf2(object, "components")) {
1572
- const resource = firstField2(component, "component");
1573
- if (resource === undefined || !resource.endsWith(SCRIPT_SUFFIX2))
1574
- continue;
1575
- const key = resourceKey2(resource);
1576
- if (key !== SCRIPT_SUFFIX2.slice(1))
1577
- resources.push(key);
1578
- }
1579
- return resources;
1580
- }
1581
- function isGameObject(message) {
1582
- return message.messages.has("components") || message.messages.has("embedded_components");
1583
- }
1584
- function buildSpriteAnimationIndex(input) {
1585
- const unresolved = [];
1586
- const assets = readAssets(input.assets, unresolved);
1587
- const byScriptResource = new Map;
1588
- const tileSetByScriptResource = new Map;
1589
- const claimedBy = new Map;
1590
- function walk(message, blockName, displayPath) {
1591
- if (isGameObject(message)) {
1592
- claim(message, displayPath);
1593
- }
1594
- if (blockName === "embedded_instances") {
1595
- for (const payload of message.fields.get("data") ?? []) {
1596
- try {
1597
- walk(parseSceneTextFormat(payload), "", displayPath);
1598
- } catch (error) {
1599
- if (!(error instanceof SceneTextFormatError))
1600
- throw error;
1601
- unresolved.push(`${displayPath}: an embedded game object could not be read (${error.message})`);
1602
- }
1603
- }
1604
- }
1605
- for (const [name, nested] of message.messages) {
1606
- for (const child of nested) {
1607
- walk(child, name, displayPath);
1608
- }
1609
- }
1610
- }
1611
- function claim(object, displayPath) {
1612
- const scripts = scriptResourcesOf(object);
1613
- if (scripts.length === 0)
1614
- return;
1615
- const tileSets = spriteTileSets(object, displayPath, assets, unresolved);
1616
- const animations = new Map;
1617
- const declaringTileSets = new Map;
1618
- for (const [id, tileSet] of tileSets) {
1619
- const declared = assets.animationsByTileSet.get(tileSet);
1620
- if (declared === undefined) {
1621
- unresolved.push(`${displayPath}: the sprite component "${id}" names the tile source /${tileSet}, which is not among the project's asset documents`);
1622
- continue;
1623
- }
1624
- animations.set(id, declared);
1625
- declaringTileSets.set(id, tileSet);
1626
- }
1627
- for (const key of scripts) {
1628
- const owner = claimedBy.get(key);
1629
- if (owner !== undefined) {
1630
- byScriptResource.delete(key);
1631
- tileSetByScriptResource.delete(key);
1632
- unresolved.push(`${key}: claimed by both ${owner} and ${displayPath}, so its sprite animations are ambiguous`);
1633
- continue;
1634
- }
1635
- claimedBy.set(key, displayPath);
1636
- byScriptResource.set(key, animations);
1637
- tileSetByScriptResource.set(key, declaringTileSets);
1638
- }
1639
- }
1640
- for (const [path4, text] of input.scenes) {
1641
- const displayPath = normalizeDocumentKey(path4);
1642
- try {
1643
- walk(parseSceneTextFormat(text), "", displayPath);
1644
- } catch (error) {
1645
- if (!(error instanceof SceneTextFormatError))
1646
- throw error;
1647
- unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
1648
- }
1649
- }
1650
- return { byScriptResource, tileSetByScriptResource, unresolved };
1651
- }
1652
- function componentIdOfSameObjectAddress(address) {
1653
- if (!address.startsWith("#"))
1654
- return;
1655
- const id = address.slice(1);
1656
- if (id === "" || id.includes("/") || id.includes("#"))
1657
- return;
1658
- return id;
1659
- }
1660
2476
  // src/url-address-slots.ts
1661
2477
  import * as ts11 from "typescript";
1662
2478
  var ADDRESS_CLASSES = {
@@ -1748,6 +2564,10 @@ function slotThroughHashedCall(checker, table, literal) {
1748
2564
  const ascended = classifiedEntryOfArgument(checker, table, call);
1749
2565
  return ascended?.entry.fqn === "hash" ? undefined : ascended;
1750
2566
  }
2567
+ function addressEntryOfArgument(checker, table, argument) {
2568
+ const entry = classifiedEntryOfArgument(checker, table, argument)?.entry;
2569
+ return entry !== undefined && isAddressClass(entry.class) ? entry : undefined;
2570
+ }
1751
2571
  function addressClassOfArgument(checker, table, argument) {
1752
2572
  return classifiedEntryOfArgument(checker, table, argument)?.entry.class ?? "none";
1753
2573
  }
@@ -1801,10 +2621,14 @@ function resolveClassifiedSlotAtPosition(input) {
1801
2621
  textStart,
1802
2622
  fragmentStart: hash === -1 ? -1 : textStart + hash + 1,
1803
2623
  ...addressText === undefined ? {} : { addressText },
2624
+ ...classified.entry.nodeParameter === undefined ? {} : { nodeParameter: classified.entry.nodeParameter },
1804
2625
  ...classified.entry.resourceExtensions === undefined ? {} : { resourceExtensions: classified.entry.resourceExtensions }
1805
2626
  };
1806
2627
  }
1807
- // src/url-fragment-reachability.ts
2628
+ // src/url-cross-world-address.ts
2629
+ import * as ts13 from "typescript";
2630
+
2631
+ // src/url-address-literals.ts
1808
2632
  import * as ts12 from "typescript";
1809
2633
  function isAmbient2(fileName) {
1810
2634
  return /[\\/]node_modules[\\/]/.test(fileName) || /(^|[\\/])lib\.[^\\/]*\.d\.ts$/.test(fileName);
@@ -1855,8 +2679,89 @@ function hashedSourceOfType(checker, type, canonical) {
1855
2679
  const [source] = checker.getTypeArguments(type);
1856
2680
  return source?.isStringLiteral() ? source.value : undefined;
1857
2681
  }
2682
+ function staticAddressTextOf(checker, node, canonical) {
2683
+ return ts12.isStringLiteralLike(node) ? node.text : hashedSourceOfType(checker, checker.getTypeAtLocation(node), canonical);
2684
+ }
2685
+
2686
+ // src/url-cross-world-address.ts
2687
+ function slotRejectsForeignSocket(entry) {
2688
+ return entry.socketScope === "same-world";
2689
+ }
2690
+ function worldPhrase(sockets) {
2691
+ const named = [...new Set(sockets)].map((socket) => socket === undefined ? "the bootstrap world" : `"${socket}"`);
2692
+ return named.join(" and ");
2693
+ }
2694
+ function checkCrossWorldAddresses(input) {
2695
+ const { program, table, worldsOf, sourceFiles } = input;
2696
+ const checker = program.getTypeChecker();
2697
+ const canonical = canonicalHashSymbols(checker, program);
2698
+ const findings = [];
2699
+ const targets = sourceFiles ?? program.getSourceFiles();
2700
+ for (const sourceFile of targets) {
2701
+ if (isAmbient2(sourceFile.fileName))
2702
+ continue;
2703
+ const worlds = worldsOf(sourceFile.fileName);
2704
+ if (worlds.length === 0)
2705
+ continue;
2706
+ const visit = (node) => {
2707
+ if (ts13.isExpression(node)) {
2708
+ const entry = addressEntryOfArgument(checker, table, node);
2709
+ if (entry !== undefined && slotRejectsForeignSocket(entry)) {
2710
+ const address = staticAddressTextOf(checker, node, canonical);
2711
+ const socket = address === undefined ? undefined : socketOfAddress(address);
2712
+ if (address !== undefined && socket !== undefined && worlds.every((world) => world !== socket)) {
2713
+ findings.push({
2714
+ fileName: sourceFile.fileName,
2715
+ start: node.getStart(sourceFile),
2716
+ length: node.getWidth(sourceFile),
2717
+ address,
2718
+ socket,
2719
+ message: `this address names the world "${socket}", but \`${entry.fqn}\` resolves it in ` + `the world the script runs in (${worldPhrase(worlds)}), so it cannot resolve at ` + `runtime — only \`msg.post\` and \`msg.url\` cross a collection proxy`
2720
+ });
2721
+ }
2722
+ }
2723
+ }
2724
+ ts13.forEachChild(node, visit);
2725
+ };
2726
+ ts13.forEachChild(sourceFile, visit);
2727
+ }
2728
+ return findings;
2729
+ }
2730
+ // src/url-fragment-reachability.ts
2731
+ import * as ts14 from "typescript";
2732
+ function pathOfKey(key) {
2733
+ const socket = socketOfAddress(key);
2734
+ return socket === undefined ? key : key.slice(socket.length + 1);
2735
+ }
2736
+ function declaredComponentsOf(objects, path4) {
2737
+ if (objects === undefined || path4 === "")
2738
+ return;
2739
+ if (objects.incomplete.length > 0)
2740
+ return;
2741
+ if (socketOfAddress(path4) !== undefined) {
2742
+ return objects.paths.has(path4) ? objects.componentsOf.get(path4) : undefined;
2743
+ }
2744
+ if (!path4.startsWith("/"))
2745
+ return;
2746
+ const declared = new Set;
2747
+ let known = false;
2748
+ for (const key of objects.paths) {
2749
+ if (pathOfKey(key) !== path4)
2750
+ continue;
2751
+ const owned = objects.componentsOf.get(key);
2752
+ if (owned === undefined)
2753
+ return;
2754
+ known = true;
2755
+ for (const id of owned)
2756
+ declared.add(id);
2757
+ }
2758
+ return known ? [...declared].sort() : undefined;
2759
+ }
2760
+ function declaredPhrase(ids) {
2761
+ return ids.length === 0 ? "it declares no components at all" : `it declares ${ids.map((id) => `"${id}"`).join(", ")}`;
2762
+ }
1858
2763
  function checkUrlFragmentReachability(input) {
1859
- const { program, table, index, sourceFiles } = input;
2764
+ const { program, table, index, objects, sourceFiles } = input;
1860
2765
  if (index.incomplete.length > 0) {
1861
2766
  return { kind: "suppressed", reasons: index.incomplete };
1862
2767
  }
@@ -1868,25 +2773,28 @@ function checkUrlFragmentReachability(input) {
1868
2773
  if (isAmbient2(sourceFile.fileName))
1869
2774
  continue;
1870
2775
  const visit = (node) => {
1871
- if (ts12.isExpression(node) && isAddressClass(addressClassOfArgument(checker, table, node))) {
1872
- const text = ts12.isStringLiteralLike(node) ? node.text : hashedSourceOfType(checker, checker.getTypeAtLocation(node), canonical);
2776
+ if (ts14.isExpression(node) && isAddressClass(addressClassOfArgument(checker, table, node))) {
2777
+ const text = staticAddressTextOf(checker, node, canonical);
1873
2778
  if (text !== undefined) {
1874
2779
  const hash = text.indexOf("#");
1875
2780
  const fragment = hash === -1 ? "" : text.slice(hash + 1);
1876
- if (fragment !== "" && !index.ids.has(fragment)) {
2781
+ const declared = fragment === "" ? undefined : declaredComponentsOf(objects, text.slice(0, hash));
2782
+ const reachable = declared === undefined ? index.ids.has(fragment) : declared.includes(fragment);
2783
+ if (fragment !== "" && !reachable) {
2784
+ const path4 = text.slice(0, hash);
1877
2785
  findings.push({
1878
2786
  fileName: sourceFile.fileName,
1879
2787
  start: node.getStart(sourceFile),
1880
2788
  length: node.getWidth(sourceFile),
1881
2789
  fragment,
1882
- message: `no \`.go\` or \`.collection\` in this project declares a component with the id ` + `"${fragment}", so this address cannot resolve at runtime`
2790
+ message: declared === undefined ? `no \`.go\` or \`.collection\` in this project declares a component with the id ` + `"${fragment}", so this address cannot resolve at runtime` : `the game object "${path4}" declares no component with the id "${fragment}" ` + `(${declaredPhrase(declared)}), so this address cannot resolve at runtime`
1883
2791
  });
1884
2792
  }
1885
2793
  }
1886
2794
  }
1887
- ts12.forEachChild(node, visit);
2795
+ ts14.forEachChild(node, visit);
1888
2796
  };
1889
- ts12.forEachChild(sourceFile, visit);
2797
+ ts14.forEachChild(sourceFile, visit);
1890
2798
  }
1891
2799
  return { kind: "checked", findings };
1892
2800
  }
@@ -1894,38 +2802,59 @@ export {
1894
2802
  transpileProject,
1895
2803
  transpile,
1896
2804
  stripIncludeBase,
2805
+ socketOfAddress,
2806
+ slotRejectsForeignSocket,
1897
2807
  resolveClassifiedSlotAtPosition,
2808
+ relativeAddressesFrom,
1898
2809
  readSceneDocuments,
2810
+ readLibrarySceneDocuments,
2811
+ readGameProjectSetting,
2812
+ readGameProjectDependencies,
1899
2813
  parseSceneTextFormat,
1900
2814
  parseBuildConfig,
1901
2815
  listProjectResourcePaths,
2816
+ libraryIncludedEntries,
1902
2817
  isFragmentCaret,
1903
2818
  isExcludedProjectPath,
1904
2819
  isDefignoredPath,
2820
+ isContainedResourcePath,
1905
2821
  isAddressClass,
2822
+ guiScriptResourcesOf,
2823
+ guiScriptResourceOf,
1906
2824
  getProgramDiagnostics,
1907
2825
  findMainEntryFactoryImports,
1908
2826
  displayPathOf,
1909
2827
  createTranspileSession,
1910
2828
  computeOutputRel,
1911
2829
  componentIdOfSameObjectAddress,
2830
+ collectComponentIds,
2831
+ collectComponentDeclarations,
1912
2832
  checkUrlFragmentReachability,
1913
- buildSpriteAnimationIndex,
2833
+ checkCrossWorldAddresses,
2834
+ buildScriptNamingContexts,
1914
2835
  buildSceneObjectPathIndex,
1915
2836
  buildSceneComponentIndex,
2837
+ buildSceneCollectionRoles,
1916
2838
  buildSceneAddressDeclaration,
1917
2839
  buildInputActionIndex,
1918
2840
  buildGuiNodeIndex,
2841
+ buildGuiFlipbookIndex,
1919
2842
  buildConfigKeyIndex,
2843
+ buildComponentAnimationIndex,
2844
+ archiveWrapperOf,
2845
+ addressEntryOfArgument,
1920
2846
  addressClassOfArgument,
1921
2847
  SceneTextFormatError,
1922
2848
  SCRIPT_SUFFIX_BY_KIND,
1923
2849
  SCENE_EXTENSIONS,
1924
2850
  SCAFFOLDED_DEFIGNORE_LINES,
1925
2851
  PROJECT_EXTENSIONS,
2852
+ LIBRARY_SCENE_EXTENSIONS,
2853
+ LIBRARY_DEPENDENCY_ROOT,
1926
2854
  INPUT_BINDING_EXTENSIONS,
1927
2855
  GUI_EXTENSIONS,
1928
2856
  GAME_PROJECT_DOCUMENT,
1929
2857
  DEFAULT_INCLUDE,
2858
+ COLLECTION_REFERENCE_EXTENSIONS,
1930
2859
  ANIMATION_ASSET_EXTENSIONS
1931
2860
  };