@defold-typescript/transpiler 0.29.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 +21 -8
  7. package/dist/index.js +1317 -289
  8. package/dist/library-dependencies.d.ts +47 -0
  9. package/dist/scene-address-declaration.d.ts +14 -0
  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 +21 -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 +66 -10
  26. package/src/library-dependencies.ts +337 -0
  27. package/src/scene-address-declaration.ts +74 -0
  28. package/src/scene-collection-roles.ts +267 -0
  29. package/src/scene-component-index.ts +74 -6
  30. package/src/scene-documents.ts +186 -0
  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;
@@ -183,19 +156,404 @@ function parseSceneTextFormat(text) {
183
156
  return parseBody(false, 1);
184
157
  }
185
158
 
186
- // src/gui-node-index.ts
187
- var SCRIPT_SUFFIX = ".gui_script";
188
- function scriptKeyOf(script) {
189
- if (!script.endsWith(SCRIPT_SUFFIX))
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)
190
279
  return;
191
- const key = script.startsWith("/") ? script.slice(1) : script;
192
- return key === SCRIPT_SUFFIX ? undefined : key;
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
+ }
193
318
  }
194
- function buildGuiNodeIndex(documents) {
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);
195
400
  const byScriptResource = new Map;
401
+ const sourceByScriptResource = new Map;
196
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) {
197
551
  const unresolved = [];
198
- for (const [displayPath, text] of documents) {
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);
199
557
  let document;
200
558
  try {
201
559
  document = parseSceneTextFormat(text);
@@ -212,6 +570,83 @@ function buildGuiNodeIndex(documents) {
212
570
  if (key === undefined)
213
571
  continue;
214
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);
215
650
  if (owner !== undefined) {
216
651
  byScriptResource.delete(key);
217
652
  unresolved.push(`${key}: claimed by both ${owner} and ${displayPath}, so its node ids are ambiguous`);
@@ -252,6 +687,304 @@ function buildInputActionIndex(documents) {
252
687
  }
253
688
  return actions;
254
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());
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());
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;
803
+ }
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]";
826
+ continue;
827
+ }
828
+ if (!inLibrary) {
829
+ continue;
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)) {
867
+ continue;
868
+ }
869
+ const dot = path.lastIndexOf(".");
870
+ if (dot === -1 || !LIBRARY_SCENE_EXTENSIONS.includes(path.slice(dot).toLowerCase())) {
871
+ continue;
872
+ }
873
+ if (!isContainedResourcePath(path)) {
874
+ refused.push(entry);
875
+ continue;
876
+ }
877
+ shared.push({ entry, path });
878
+ }
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}`;
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`);
936
+ continue;
937
+ }
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;
978
+ }
979
+ claimed.set(displayPath, url);
980
+ documents.push({ displayPath, hostPath, url, text });
981
+ }
982
+ }
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 };
987
+ }
255
988
  // src/main-entry-factory-imports.ts
256
989
  import * as ts3 from "typescript";
257
990
 
@@ -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;
@@ -876,23 +1624,238 @@ function buildSceneComponentIndex(documents) {
876
1624
  }
877
1625
  return { ids, incomplete };
878
1626
  }
1627
+
879
1628
  // src/scene-object-path-index.ts
880
1629
  var LEAF_BLOCKS = ["instances", "embedded_instances"];
881
- function resourceKey(resource) {
1630
+ function resourceKey3(resource) {
1631
+ return resource.startsWith("/") ? resource.slice(1) : resource;
1632
+ }
1633
+ function firstField2(message, name) {
1634
+ return (message.fields.get(name) ?? [])[0];
1635
+ }
1636
+ function childrenOf3(message, name) {
1637
+ return message.messages.get(name) ?? [];
1638
+ }
1639
+ function buildSceneObjectPathIndex(documents, roles) {
1640
+ const incomplete = [];
1641
+ if (documents.size === 0) {
1642
+ incomplete.push("no scene sources were read, so no game-object path can be proven absent");
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
+ };
1651
+ }
1652
+ const parsed = new Map;
1653
+ for (const [displayPath, text] of documents) {
1654
+ try {
1655
+ parsed.set(displayPath, parseSceneTextFormat(text));
1656
+ } catch (error) {
1657
+ if (!(error instanceof SceneTextFormatError))
1658
+ throw error;
1659
+ incomplete.push(`${displayPath}: could not be parsed (${error.message})`);
1660
+ }
1661
+ }
1662
+ const composed = new Map;
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
+ }
1678
+ function pathsOf(displayPath) {
1679
+ const done = composed.get(displayPath);
1680
+ if (done !== undefined)
1681
+ return done;
1682
+ if (inProgress.has(displayPath)) {
1683
+ incomplete.push(`${displayPath}: is instanced inside itself through a cycle of collection references, so its paths cannot be composed`);
1684
+ return new Map;
1685
+ }
1686
+ const document = parsed.get(displayPath);
1687
+ if (document === undefined)
1688
+ return new Map;
1689
+ inProgress.add(displayPath);
1690
+ const paths2 = new Map;
1691
+ for (const blockName of LEAF_BLOCKS) {
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) {
882
1845
  return resource.startsWith("/") ? resource.slice(1) : resource;
883
1846
  }
884
- function firstField(message, name) {
1847
+ function collectionKey(resource) {
1848
+ return resourceKey4(resource).replace(/\.collectionc$/, ".collection");
1849
+ }
1850
+ function firstField3(message, name) {
885
1851
  return (message.fields.get(name) ?? [])[0];
886
1852
  }
887
- function childrenOf(message, name) {
888
- return message.messages.get(name) ?? [];
1853
+ function isCollection(displayPath) {
1854
+ return displayPath.endsWith(".collection");
889
1855
  }
890
- function buildSceneObjectPathIndex(documents) {
1856
+ function buildSceneCollectionRoles(input) {
1857
+ const { documents, references, gameProject } = input;
891
1858
  const incomplete = [];
892
- if (documents.size === 0) {
893
- incomplete.push("no scene sources were read, so no game-object path can be proven absent");
894
- return { paths: new Set, declaredIn: new Map, incomplete };
895
- }
896
1859
  const parsed = new Map;
897
1860
  for (const [displayPath, text] of documents) {
898
1861
  try {
@@ -905,71 +1868,185 @@ function buildSceneObjectPathIndex(documents) {
905
1868
  }
906
1869
  const instanced = new Set;
907
1870
  for (const document of parsed.values()) {
908
- for (const block of childrenOf(document, "collection_instances")) {
909
- const collection = firstField(block, "collection");
1871
+ for (const block of document.messages.get("collection_instances") ?? []) {
1872
+ const collection = firstField3(block, "collection");
910
1873
  if (collection !== undefined)
911
- instanced.add(resourceKey(collection));
1874
+ instanced.add(resourceKey4(collection));
912
1875
  }
913
1876
  }
914
- const composed = new Map;
915
- const inProgress = new Set;
916
- function pathsOf(displayPath) {
917
- const done = composed.get(displayPath);
918
- if (done !== undefined)
919
- return done;
920
- if (inProgress.has(displayPath)) {
921
- incomplete.push(`${displayPath}: is instanced inside itself through a cycle of collection references, so its paths cannot be composed`);
922
- return new Map;
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;
923
1890
  }
924
- const document = parsed.get(displayPath);
925
- if (document === undefined)
926
- return new Map;
927
- inProgress.add(displayPath);
928
- const paths2 = new Map;
929
- for (const blockName of LEAF_BLOCKS) {
930
- for (const block of childrenOf(document, blockName)) {
931
- const id = firstField(block, "id");
932
- if (id !== undefined && id !== "")
933
- paths2.set(`/${id}`, displayPath);
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
+ }
934
1923
  }
935
1924
  }
936
- for (const block of childrenOf(document, "collection_instances")) {
937
- const id = firstField(block, "id");
938
- const collection = firstField(block, "collection");
939
- if (id === undefined || id === "" || collection === undefined)
940
- continue;
941
- const key = resourceKey(collection);
942
- if (!parsed.has(key)) {
943
- incomplete.push(`${displayPath}: the collection instance "${id}" names ${collection}, which is not among the project's readable scene documents`);
944
- continue;
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
+ }
945
1937
  }
946
- for (const [nested, declarer] of pathsOf(key)) {
947
- paths2.set(`/${id}${nested}`, declarer);
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");
948
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);
949
1985
  }
950
- inProgress.delete(displayPath);
951
- composed.set(displayPath, paths2);
952
- return paths2;
953
1986
  }
954
- const paths = new Set;
955
- const declaredIn = new Map;
956
1987
  for (const displayPath of parsed.keys()) {
957
- const own = pathsOf(displayPath);
958
- if (instanced.has(displayPath))
1988
+ if (!isCollection(displayPath))
959
1989
  continue;
960
- for (const [path2, declarer] of own) {
961
- paths.add(path2);
962
- const declarers = declaredIn.get(path2);
963
- if (declarers === undefined) {
964
- declaredIn.set(path2, [declarer]);
965
- } else if (!declarers.includes(declarer)) {
966
- declarers.push(declarer);
1990
+ if (displayPath === bootstrap || sockets.has(displayPath) || instanced.has(displayPath) || factoryPrototypes.has(displayPath)) {
1991
+ continue;
1992
+ }
1993
+ incomplete.push(`${displayPath}: is reached by no bootstrap, proxy, instance or factory reference, so which world its objects live in is unknown`);
1994
+ }
1995
+ return { bootstrap, sockets, instanced, factoryPrototypes, incomplete };
1996
+ }
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;
2007
+ }
2008
+ function contextOf(object) {
2009
+ return {
2010
+ object,
2011
+ socket: socketOfAddress(object),
2012
+ prefix: object.slice(0, object.lastIndexOf("/") + 1)
2013
+ };
2014
+ }
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));
967
2027
  }
968
2028
  }
969
2029
  }
970
- for (const declarers of declaredIn.values())
971
- declarers.sort();
972
- return { paths, declaredIn, incomplete };
2030
+ for (const hosted of contexts.values()) {
2031
+ hosted.sort((a, b) => a.object.localeCompare(b.object));
2032
+ }
2033
+ return contexts;
2034
+ }
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))
2040
+ continue;
2041
+ const relative = key.slice(context.prefix.length);
2042
+ if (relative === "")
2043
+ continue;
2044
+ paths.add(relative);
2045
+ for (const id of index.componentsOf.get(key) ?? []) {
2046
+ addresses.add(`${relative}#${id}`);
2047
+ }
2048
+ }
2049
+ return { paths, addresses };
973
2050
  }
974
2051
  // src/session.ts
975
2052
  import { readFileSync as readFileSync2 } from "node:fs";
@@ -1396,179 +2473,6 @@ function createTranspileSession() {
1396
2473
  getProgram: () => program
1397
2474
  };
1398
2475
  }
1399
- // src/sprite-animation-index.ts
1400
- function resourceKey2(resource) {
1401
- return resource.startsWith("/") ? resource.slice(1) : resource;
1402
- }
1403
- function normalizeDocumentKey(path4) {
1404
- return path4.replaceAll("\\", "/");
1405
- }
1406
- var SCRIPT_SUFFIX2 = ".script";
1407
- var SPRITE_SUFFIX = ".sprite";
1408
- function firstField2(message, name) {
1409
- return (message.fields.get(name) ?? [])[0];
1410
- }
1411
- function childrenOf2(message, name) {
1412
- return message.messages.get(name) ?? [];
1413
- }
1414
- function declaredAnimations(document) {
1415
- const ids = new Set;
1416
- for (const animation of childrenOf2(document, "animations")) {
1417
- for (const id of animation.fields.get("id") ?? []) {
1418
- if (id !== "")
1419
- ids.add(id);
1420
- }
1421
- }
1422
- return ids;
1423
- }
1424
- function readAssets(assets, unresolved) {
1425
- const animationsByTileSet = new Map;
1426
- const tileSetBySprite = new Map;
1427
- for (const [path4, text] of assets) {
1428
- const displayPath = normalizeDocumentKey(path4);
1429
- let document;
1430
- try {
1431
- document = parseSceneTextFormat(text);
1432
- } catch (error) {
1433
- if (!(error instanceof SceneTextFormatError))
1434
- throw error;
1435
- unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
1436
- continue;
1437
- }
1438
- if (displayPath.endsWith(SPRITE_SUFFIX)) {
1439
- const tileSet = firstField2(document, "tile_set");
1440
- if (tileSet !== undefined)
1441
- tileSetBySprite.set(displayPath, resourceKey2(tileSet));
1442
- continue;
1443
- }
1444
- animationsByTileSet.set(displayPath, declaredAnimations(document));
1445
- }
1446
- return { animationsByTileSet, tileSetBySprite };
1447
- }
1448
- function spriteTileSets(object, displayPath, assets, unresolved) {
1449
- const tileSets = new Map;
1450
- for (const embedded of childrenOf2(object, "embedded_components")) {
1451
- if (firstField2(embedded, "type") !== "sprite")
1452
- continue;
1453
- const id = firstField2(embedded, "id");
1454
- const payload = firstField2(embedded, "data");
1455
- if (id === undefined || payload === undefined)
1456
- continue;
1457
- try {
1458
- const tileSet = firstField2(parseSceneTextFormat(payload), "tile_set");
1459
- if (tileSet !== undefined)
1460
- tileSets.set(id, resourceKey2(tileSet));
1461
- } catch (error) {
1462
- if (!(error instanceof SceneTextFormatError))
1463
- throw error;
1464
- unresolved.push(`${displayPath}: the embedded sprite "${id}" could not be read (${error.message})`);
1465
- }
1466
- }
1467
- for (const referenced of childrenOf2(object, "components")) {
1468
- const component = firstField2(referenced, "component");
1469
- const id = firstField2(referenced, "id");
1470
- if (id === undefined || component === undefined || !component.endsWith(SPRITE_SUFFIX))
1471
- continue;
1472
- const tileSet = assets.tileSetBySprite.get(resourceKey2(component));
1473
- if (tileSet === undefined) {
1474
- unresolved.push(`${displayPath}: the sprite component "${id}" names ${component}, which is not among the project's sprite documents`);
1475
- continue;
1476
- }
1477
- tileSets.set(id, tileSet);
1478
- }
1479
- return tileSets;
1480
- }
1481
- function scriptResourcesOf(object) {
1482
- const resources = [];
1483
- for (const component of childrenOf2(object, "components")) {
1484
- const resource = firstField2(component, "component");
1485
- if (resource === undefined || !resource.endsWith(SCRIPT_SUFFIX2))
1486
- continue;
1487
- const key = resourceKey2(resource);
1488
- if (key !== SCRIPT_SUFFIX2.slice(1))
1489
- resources.push(key);
1490
- }
1491
- return resources;
1492
- }
1493
- function isGameObject(message) {
1494
- return message.messages.has("components") || message.messages.has("embedded_components");
1495
- }
1496
- function buildSpriteAnimationIndex(input) {
1497
- const unresolved = [];
1498
- const assets = readAssets(input.assets, unresolved);
1499
- const byScriptResource = new Map;
1500
- const tileSetByScriptResource = new Map;
1501
- const claimedBy = new Map;
1502
- function walk(message, blockName, displayPath) {
1503
- if (isGameObject(message)) {
1504
- claim(message, displayPath);
1505
- }
1506
- if (blockName === "embedded_instances") {
1507
- for (const payload of message.fields.get("data") ?? []) {
1508
- try {
1509
- walk(parseSceneTextFormat(payload), "", displayPath);
1510
- } catch (error) {
1511
- if (!(error instanceof SceneTextFormatError))
1512
- throw error;
1513
- unresolved.push(`${displayPath}: an embedded game object could not be read (${error.message})`);
1514
- }
1515
- }
1516
- }
1517
- for (const [name, nested] of message.messages) {
1518
- for (const child of nested) {
1519
- walk(child, name, displayPath);
1520
- }
1521
- }
1522
- }
1523
- function claim(object, displayPath) {
1524
- const scripts = scriptResourcesOf(object);
1525
- if (scripts.length === 0)
1526
- return;
1527
- const tileSets = spriteTileSets(object, displayPath, assets, unresolved);
1528
- const animations = new Map;
1529
- const declaringTileSets = new Map;
1530
- for (const [id, tileSet] of tileSets) {
1531
- const declared = assets.animationsByTileSet.get(tileSet);
1532
- if (declared === undefined) {
1533
- unresolved.push(`${displayPath}: the sprite component "${id}" names the tile source /${tileSet}, which is not among the project's asset documents`);
1534
- continue;
1535
- }
1536
- animations.set(id, declared);
1537
- declaringTileSets.set(id, tileSet);
1538
- }
1539
- for (const key of scripts) {
1540
- const owner = claimedBy.get(key);
1541
- if (owner !== undefined) {
1542
- byScriptResource.delete(key);
1543
- tileSetByScriptResource.delete(key);
1544
- unresolved.push(`${key}: claimed by both ${owner} and ${displayPath}, so its sprite animations are ambiguous`);
1545
- continue;
1546
- }
1547
- claimedBy.set(key, displayPath);
1548
- byScriptResource.set(key, animations);
1549
- tileSetByScriptResource.set(key, declaringTileSets);
1550
- }
1551
- }
1552
- for (const [path4, text] of input.scenes) {
1553
- const displayPath = normalizeDocumentKey(path4);
1554
- try {
1555
- walk(parseSceneTextFormat(text), "", displayPath);
1556
- } catch (error) {
1557
- if (!(error instanceof SceneTextFormatError))
1558
- throw error;
1559
- unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
1560
- }
1561
- }
1562
- return { byScriptResource, tileSetByScriptResource, unresolved };
1563
- }
1564
- function componentIdOfSameObjectAddress(address) {
1565
- if (!address.startsWith("#"))
1566
- return;
1567
- const id = address.slice(1);
1568
- if (id === "" || id.includes("/") || id.includes("#"))
1569
- return;
1570
- return id;
1571
- }
1572
2476
  // src/url-address-slots.ts
1573
2477
  import * as ts11 from "typescript";
1574
2478
  var ADDRESS_CLASSES = {
@@ -1660,6 +2564,10 @@ function slotThroughHashedCall(checker, table, literal) {
1660
2564
  const ascended = classifiedEntryOfArgument(checker, table, call);
1661
2565
  return ascended?.entry.fqn === "hash" ? undefined : ascended;
1662
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
+ }
1663
2571
  function addressClassOfArgument(checker, table, argument) {
1664
2572
  return classifiedEntryOfArgument(checker, table, argument)?.entry.class ?? "none";
1665
2573
  }
@@ -1713,10 +2621,14 @@ function resolveClassifiedSlotAtPosition(input) {
1713
2621
  textStart,
1714
2622
  fragmentStart: hash === -1 ? -1 : textStart + hash + 1,
1715
2623
  ...addressText === undefined ? {} : { addressText },
2624
+ ...classified.entry.nodeParameter === undefined ? {} : { nodeParameter: classified.entry.nodeParameter },
1716
2625
  ...classified.entry.resourceExtensions === undefined ? {} : { resourceExtensions: classified.entry.resourceExtensions }
1717
2626
  };
1718
2627
  }
1719
- // 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
1720
2632
  import * as ts12 from "typescript";
1721
2633
  function isAmbient2(fileName) {
1722
2634
  return /[\\/]node_modules[\\/]/.test(fileName) || /(^|[\\/])lib\.[^\\/]*\.d\.ts$/.test(fileName);
@@ -1767,8 +2679,89 @@ function hashedSourceOfType(checker, type, canonical) {
1767
2679
  const [source] = checker.getTypeArguments(type);
1768
2680
  return source?.isStringLiteral() ? source.value : undefined;
1769
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
+ }
1770
2763
  function checkUrlFragmentReachability(input) {
1771
- const { program, table, index, sourceFiles } = input;
2764
+ const { program, table, index, objects, sourceFiles } = input;
1772
2765
  if (index.incomplete.length > 0) {
1773
2766
  return { kind: "suppressed", reasons: index.incomplete };
1774
2767
  }
@@ -1780,25 +2773,28 @@ function checkUrlFragmentReachability(input) {
1780
2773
  if (isAmbient2(sourceFile.fileName))
1781
2774
  continue;
1782
2775
  const visit = (node) => {
1783
- if (ts12.isExpression(node) && isAddressClass(addressClassOfArgument(checker, table, node))) {
1784
- 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);
1785
2778
  if (text !== undefined) {
1786
2779
  const hash = text.indexOf("#");
1787
2780
  const fragment = hash === -1 ? "" : text.slice(hash + 1);
1788
- 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);
1789
2785
  findings.push({
1790
2786
  fileName: sourceFile.fileName,
1791
2787
  start: node.getStart(sourceFile),
1792
2788
  length: node.getWidth(sourceFile),
1793
2789
  fragment,
1794
- 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`
1795
2791
  });
1796
2792
  }
1797
2793
  }
1798
2794
  }
1799
- ts12.forEachChild(node, visit);
2795
+ ts14.forEachChild(node, visit);
1800
2796
  };
1801
- ts12.forEachChild(sourceFile, visit);
2797
+ ts14.forEachChild(sourceFile, visit);
1802
2798
  }
1803
2799
  return { kind: "checked", findings };
1804
2800
  }
@@ -1806,27 +2802,59 @@ export {
1806
2802
  transpileProject,
1807
2803
  transpile,
1808
2804
  stripIncludeBase,
2805
+ socketOfAddress,
2806
+ slotRejectsForeignSocket,
1809
2807
  resolveClassifiedSlotAtPosition,
2808
+ relativeAddressesFrom,
2809
+ readSceneDocuments,
2810
+ readLibrarySceneDocuments,
2811
+ readGameProjectSetting,
2812
+ readGameProjectDependencies,
1810
2813
  parseSceneTextFormat,
1811
2814
  parseBuildConfig,
2815
+ listProjectResourcePaths,
2816
+ libraryIncludedEntries,
1812
2817
  isFragmentCaret,
2818
+ isExcludedProjectPath,
1813
2819
  isDefignoredPath,
2820
+ isContainedResourcePath,
1814
2821
  isAddressClass,
2822
+ guiScriptResourcesOf,
2823
+ guiScriptResourceOf,
1815
2824
  getProgramDiagnostics,
1816
2825
  findMainEntryFactoryImports,
2826
+ displayPathOf,
1817
2827
  createTranspileSession,
1818
2828
  computeOutputRel,
1819
2829
  componentIdOfSameObjectAddress,
2830
+ collectComponentIds,
2831
+ collectComponentDeclarations,
1820
2832
  checkUrlFragmentReachability,
1821
- buildSpriteAnimationIndex,
2833
+ checkCrossWorldAddresses,
2834
+ buildScriptNamingContexts,
1822
2835
  buildSceneObjectPathIndex,
1823
2836
  buildSceneComponentIndex,
2837
+ buildSceneCollectionRoles,
2838
+ buildSceneAddressDeclaration,
1824
2839
  buildInputActionIndex,
1825
2840
  buildGuiNodeIndex,
2841
+ buildGuiFlipbookIndex,
1826
2842
  buildConfigKeyIndex,
2843
+ buildComponentAnimationIndex,
2844
+ archiveWrapperOf,
2845
+ addressEntryOfArgument,
1827
2846
  addressClassOfArgument,
1828
2847
  SceneTextFormatError,
1829
2848
  SCRIPT_SUFFIX_BY_KIND,
2849
+ SCENE_EXTENSIONS,
1830
2850
  SCAFFOLDED_DEFIGNORE_LINES,
1831
- DEFAULT_INCLUDE
2851
+ PROJECT_EXTENSIONS,
2852
+ LIBRARY_SCENE_EXTENSIONS,
2853
+ LIBRARY_DEPENDENCY_ROOT,
2854
+ INPUT_BINDING_EXTENSIONS,
2855
+ GUI_EXTENSIONS,
2856
+ GAME_PROJECT_DOCUMENT,
2857
+ DEFAULT_INCLUDE,
2858
+ COLLECTION_REFERENCE_EXTENSIONS,
2859
+ ANIMATION_ASSET_EXTENSIONS
1832
2860
  };