@defold-typescript/transpiler 0.30.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/animation-sources.d.ts +6 -0
- package/dist/{sprite-animation-index.d.ts → component-animation-index.d.ts} +4 -4
- package/dist/config-key-index.d.ts +1 -0
- package/dist/gui-flipbook-index.d.ts +8 -0
- package/dist/gui-node-index.d.ts +21 -0
- package/dist/index.d.ts +19 -9
- package/dist/index.js +1326 -396
- package/dist/library-dependencies.d.ts +47 -0
- package/dist/scene-address-declaration.d.ts +4 -2
- package/dist/scene-collection-roles.d.ts +30 -0
- package/dist/scene-component-index.d.ts +20 -0
- package/dist/scene-documents.d.ts +3 -0
- package/dist/scene-naming-context.d.ts +66 -0
- package/dist/scene-object-path-index.d.ts +4 -1
- package/dist/url-address-literals.d.ts +10 -0
- package/dist/url-address-slots.d.ts +3 -1
- package/dist/url-cross-world-address.d.ts +41 -0
- package/dist/url-fragment-reachability.d.ts +3 -0
- package/package.json +2 -2
- package/src/animation-sources.ts +90 -0
- package/src/component-animation-index.ts +399 -0
- package/src/config-key-index.ts +28 -2
- package/src/gui-flipbook-index.ts +91 -0
- package/src/gui-node-index.ts +42 -4
- package/src/index.ts +52 -10
- package/src/library-dependencies.ts +337 -0
- package/src/scene-address-declaration.ts +29 -12
- package/src/scene-collection-roles.ts +267 -0
- package/src/scene-component-index.ts +74 -6
- package/src/scene-documents.ts +66 -6
- package/src/scene-naming-context.ts +130 -0
- package/src/scene-object-path-index.ts +172 -33
- package/src/transpile.ts +3 -0
- package/src/url-address-literals.ts +115 -0
- package/src/url-address-slots.ts +23 -2
- package/src/url-cross-world-address.ts +109 -0
- package/src/url-fragment-reachability.ts +72 -93
- 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;
|
|
@@ -178,79 +151,839 @@ function parseSceneTextFormat(text) {
|
|
|
178
151
|
}
|
|
179
152
|
fail(`expected \`:\` or \`{\` after \`${key}\``, keyLine);
|
|
180
153
|
}
|
|
181
|
-
return { fields, messages };
|
|
154
|
+
return { fields, messages };
|
|
155
|
+
}
|
|
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);
|
|
182
748
|
}
|
|
183
|
-
|
|
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/
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
209
|
-
if (script === undefined)
|
|
828
|
+
if (!inLibrary) {
|
|
210
829
|
continue;
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
877
|
+
shared.push({ entry, path });
|
|
229
878
|
}
|
|
230
|
-
|
|
879
|
+
shared.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
880
|
+
return { shared, reasons: [], refused };
|
|
231
881
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
841
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
|
1630
|
+
function resourceKey3(resource) {
|
|
883
1631
|
return resource.startsWith("/") ? resource.slice(1) : resource;
|
|
884
1632
|
}
|
|
885
|
-
function
|
|
1633
|
+
function firstField2(message, name) {
|
|
886
1634
|
return (message.fields.get(name) ?? [])[0];
|
|
887
1635
|
}
|
|
888
|
-
function
|
|
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
|
-
|
|
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)
|
|
@@ -926,51 +1687,115 @@ function buildSceneObjectPathIndex(documents) {
|
|
|
926
1687
|
if (document === undefined)
|
|
927
1688
|
return new Map;
|
|
928
1689
|
inProgress.add(displayPath);
|
|
929
|
-
const
|
|
1690
|
+
const paths = new Map;
|
|
930
1691
|
for (const blockName of LEAF_BLOCKS) {
|
|
931
|
-
for (const block of
|
|
932
|
-
const id =
|
|
933
|
-
if (id
|
|
934
|
-
|
|
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
|
+
paths.set(`/${id}`, { declarer: displayPath, components, resources });
|
|
935
1718
|
}
|
|
936
1719
|
}
|
|
937
|
-
for (const block of
|
|
938
|
-
const id =
|
|
939
|
-
const collection =
|
|
1720
|
+
for (const block of childrenOf3(document, "collection_instances")) {
|
|
1721
|
+
const id = firstField2(block, "id");
|
|
1722
|
+
const collection = firstField2(block, "collection");
|
|
940
1723
|
if (id === undefined || id === "" || collection === undefined)
|
|
941
1724
|
continue;
|
|
942
|
-
const key =
|
|
1725
|
+
const key = resourceKey3(collection);
|
|
943
1726
|
if (!parsed.has(key)) {
|
|
944
1727
|
incomplete.push(`${displayPath}: the collection instance "${id}" names ${collection}, which is not among the project's readable scene documents`);
|
|
945
1728
|
continue;
|
|
946
1729
|
}
|
|
947
|
-
for (const [nested,
|
|
948
|
-
|
|
1730
|
+
for (const [nested, leaf] of pathsOf(key)) {
|
|
1731
|
+
paths.set(`/${id}${nested}`, leaf);
|
|
949
1732
|
}
|
|
950
1733
|
}
|
|
951
1734
|
inProgress.delete(displayPath);
|
|
952
|
-
composed.set(displayPath,
|
|
953
|
-
return
|
|
954
|
-
}
|
|
1735
|
+
composed.set(displayPath, paths);
|
|
1736
|
+
return paths;
|
|
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]);
|
|
955
1745
|
const paths = new Set;
|
|
956
1746
|
const declaredIn = new Map;
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
for (const [
|
|
962
|
-
|
|
963
|
-
|
|
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 [path, leaf] of pathsOf(displayPath)) {
|
|
1752
|
+
const key = `${prefix}${path}`;
|
|
1753
|
+
paths.add(key);
|
|
1754
|
+
const declarers = declaredIn.get(key);
|
|
964
1755
|
if (declarers === undefined) {
|
|
965
|
-
declaredIn.set(
|
|
966
|
-
} else if (!declarers.includes(declarer)) {
|
|
967
|
-
declarers.push(declarer);
|
|
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
|
+
}
|
|
968
1780
|
}
|
|
969
1781
|
}
|
|
970
1782
|
}
|
|
971
1783
|
for (const declarers of declaredIn.values())
|
|
972
1784
|
declarers.sort();
|
|
973
|
-
|
|
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
|
+
};
|
|
974
1799
|
}
|
|
975
1800
|
|
|
976
1801
|
// src/scene-address-declaration.ts
|
|
@@ -981,8 +1806,13 @@ var BANNER = `// Generated from this project's scenes by \`defold-typescript sce
|
|
|
981
1806
|
// \`@defold-typescript/types\` stay widened with \`(string & {})\`, so these keys
|
|
982
1807
|
// only add completions — an address composed at runtime is never rejected.
|
|
983
1808
|
`;
|
|
984
|
-
function componentAddressesOf(documents) {
|
|
985
|
-
|
|
1809
|
+
function componentAddressesOf(documents, paths) {
|
|
1810
|
+
const keys = [...buildSceneComponentIndex(documents).ids].map((id) => `#${id}`);
|
|
1811
|
+
for (const [path, ids] of paths.componentsOf) {
|
|
1812
|
+
for (const id of ids)
|
|
1813
|
+
keys.push(`${path}#${id}`);
|
|
1814
|
+
}
|
|
1815
|
+
return keys;
|
|
986
1816
|
}
|
|
987
1817
|
function bodyOf(keys) {
|
|
988
1818
|
if (keys.length === 0)
|
|
@@ -992,12 +1822,12 @@ function bodyOf(keys) {
|
|
|
992
1822
|
return `{
|
|
993
1823
|
${members} }`;
|
|
994
1824
|
}
|
|
995
|
-
function buildSceneAddressDeclaration(documents) {
|
|
996
|
-
const
|
|
997
|
-
const components = componentAddressesOf(documents);
|
|
1825
|
+
function buildSceneAddressDeclaration(documents, roles) {
|
|
1826
|
+
const index = buildSceneObjectPathIndex(documents, roles);
|
|
1827
|
+
const components = componentAddressesOf(documents, index);
|
|
998
1828
|
return `${BANNER}
|
|
999
1829
|
declare global {
|
|
1000
|
-
interface SceneGameObjectAddresses ${bodyOf(paths)}
|
|
1830
|
+
interface SceneGameObjectAddresses ${bodyOf([...index.paths])}
|
|
1001
1831
|
|
|
1002
1832
|
interface SceneComponentAddresses ${bodyOf(components)}
|
|
1003
1833
|
}
|
|
@@ -1005,59 +1835,218 @@ declare global {
|
|
|
1005
1835
|
export {};
|
|
1006
1836
|
`;
|
|
1007
1837
|
}
|
|
1008
|
-
// src/scene-
|
|
1009
|
-
var
|
|
1010
|
-
var
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
const path2 = filePath.replace(/\\/g, "/");
|
|
1017
|
-
const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1018
|
-
return path2.startsWith(`${root}/`) ? path2.slice(root.length + 1) : path2;
|
|
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;
|
|
1019
1846
|
}
|
|
1020
|
-
function
|
|
1021
|
-
return
|
|
1847
|
+
function collectionKey(resource) {
|
|
1848
|
+
return resourceKey4(resource).replace(/\.collectionc$/, ".collection");
|
|
1022
1849
|
}
|
|
1023
|
-
function
|
|
1024
|
-
return
|
|
1850
|
+
function firstField3(message, name) {
|
|
1851
|
+
return (message.fields.get(name) ?? [])[0];
|
|
1025
1852
|
}
|
|
1026
|
-
function
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
+
}
|
|
1033
1868
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
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));
|
|
1039
1887
|
if (text === undefined) {
|
|
1040
|
-
|
|
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
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
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");
|
|
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);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
for (const displayPath of parsed.keys()) {
|
|
1988
|
+
if (!isCollection(displayPath))
|
|
1989
|
+
continue;
|
|
1990
|
+
if (displayPath === bootstrap || sockets.has(displayPath) || instanced.has(displayPath) || factoryPrototypes.has(displayPath)) {
|
|
1041
1991
|
continue;
|
|
1042
1992
|
}
|
|
1043
|
-
|
|
1044
|
-
paths.push(filePath);
|
|
1993
|
+
incomplete.push(`${displayPath}: is reached by no bootstrap, proxy, instance or factory reference, so which world its objects live in is unknown`);
|
|
1045
1994
|
}
|
|
1046
|
-
return {
|
|
1995
|
+
return { bootstrap, sockets, instanced, factoryPrototypes, incomplete };
|
|
1047
1996
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
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));
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
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))
|
|
1055
2040
|
continue;
|
|
1056
|
-
|
|
2041
|
+
const relative = key.slice(context.prefix.length);
|
|
2042
|
+
if (relative === "")
|
|
1057
2043
|
continue;
|
|
1058
|
-
paths.
|
|
2044
|
+
paths.add(relative);
|
|
2045
|
+
for (const id of index.componentsOf.get(key) ?? []) {
|
|
2046
|
+
addresses.add(`${relative}#${id}`);
|
|
2047
|
+
}
|
|
1059
2048
|
}
|
|
1060
|
-
return
|
|
2049
|
+
return { paths, addresses };
|
|
1061
2050
|
}
|
|
1062
2051
|
// src/session.ts
|
|
1063
2052
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
@@ -1219,6 +2208,7 @@ function buildAmbientFiles() {
|
|
|
1219
2208
|
"node_modules/@defold-typescript/types/src/core-types.ts": readAmbient("src/core-types.ts"),
|
|
1220
2209
|
"node_modules/@defold-typescript/types/src/engine-globals.d.ts": readAmbient("src/engine-globals.d.ts"),
|
|
1221
2210
|
"node_modules/@defold-typescript/types/src/go-overloads.d.ts": readAmbient("src/go-overloads.d.ts"),
|
|
2211
|
+
"node_modules/@defold-typescript/types/src/custom-messages.d.ts": readAmbient("src/custom-messages.d.ts"),
|
|
1222
2212
|
"node_modules/@defold-typescript/types/src/msg-overloads.d.ts": readAmbient("src/msg-overloads.d.ts"),
|
|
1223
2213
|
"node_modules/@defold-typescript/types/src/message-guard.d.ts": readAmbient("src/message-guard.d.ts"),
|
|
1224
2214
|
"node_modules/@defold-typescript/types/src/window-event-guard.d.ts": readAmbient("src/window-event-guard.d.ts"),
|
|
@@ -1484,179 +2474,6 @@ function createTranspileSession() {
|
|
|
1484
2474
|
getProgram: () => program
|
|
1485
2475
|
};
|
|
1486
2476
|
}
|
|
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
2477
|
// src/url-address-slots.ts
|
|
1661
2478
|
import * as ts11 from "typescript";
|
|
1662
2479
|
var ADDRESS_CLASSES = {
|
|
@@ -1748,6 +2565,10 @@ function slotThroughHashedCall(checker, table, literal) {
|
|
|
1748
2565
|
const ascended = classifiedEntryOfArgument(checker, table, call);
|
|
1749
2566
|
return ascended?.entry.fqn === "hash" ? undefined : ascended;
|
|
1750
2567
|
}
|
|
2568
|
+
function addressEntryOfArgument(checker, table, argument) {
|
|
2569
|
+
const entry = classifiedEntryOfArgument(checker, table, argument)?.entry;
|
|
2570
|
+
return entry !== undefined && isAddressClass(entry.class) ? entry : undefined;
|
|
2571
|
+
}
|
|
1751
2572
|
function addressClassOfArgument(checker, table, argument) {
|
|
1752
2573
|
return classifiedEntryOfArgument(checker, table, argument)?.entry.class ?? "none";
|
|
1753
2574
|
}
|
|
@@ -1801,10 +2622,14 @@ function resolveClassifiedSlotAtPosition(input) {
|
|
|
1801
2622
|
textStart,
|
|
1802
2623
|
fragmentStart: hash === -1 ? -1 : textStart + hash + 1,
|
|
1803
2624
|
...addressText === undefined ? {} : { addressText },
|
|
2625
|
+
...classified.entry.nodeParameter === undefined ? {} : { nodeParameter: classified.entry.nodeParameter },
|
|
1804
2626
|
...classified.entry.resourceExtensions === undefined ? {} : { resourceExtensions: classified.entry.resourceExtensions }
|
|
1805
2627
|
};
|
|
1806
2628
|
}
|
|
1807
|
-
// src/url-
|
|
2629
|
+
// src/url-cross-world-address.ts
|
|
2630
|
+
import * as ts13 from "typescript";
|
|
2631
|
+
|
|
2632
|
+
// src/url-address-literals.ts
|
|
1808
2633
|
import * as ts12 from "typescript";
|
|
1809
2634
|
function isAmbient2(fileName) {
|
|
1810
2635
|
return /[\\/]node_modules[\\/]/.test(fileName) || /(^|[\\/])lib\.[^\\/]*\.d\.ts$/.test(fileName);
|
|
@@ -1855,8 +2680,89 @@ function hashedSourceOfType(checker, type, canonical) {
|
|
|
1855
2680
|
const [source] = checker.getTypeArguments(type);
|
|
1856
2681
|
return source?.isStringLiteral() ? source.value : undefined;
|
|
1857
2682
|
}
|
|
2683
|
+
function staticAddressTextOf(checker, node, canonical) {
|
|
2684
|
+
return ts12.isStringLiteralLike(node) ? node.text : hashedSourceOfType(checker, checker.getTypeAtLocation(node), canonical);
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
// src/url-cross-world-address.ts
|
|
2688
|
+
function slotRejectsForeignSocket(entry) {
|
|
2689
|
+
return entry.socketScope === "same-world";
|
|
2690
|
+
}
|
|
2691
|
+
function worldPhrase(sockets) {
|
|
2692
|
+
const named = [...new Set(sockets)].map((socket) => socket === undefined ? "the bootstrap world" : `"${socket}"`);
|
|
2693
|
+
return named.join(" and ");
|
|
2694
|
+
}
|
|
2695
|
+
function checkCrossWorldAddresses(input) {
|
|
2696
|
+
const { program, table, worldsOf, sourceFiles } = input;
|
|
2697
|
+
const checker = program.getTypeChecker();
|
|
2698
|
+
const canonical = canonicalHashSymbols(checker, program);
|
|
2699
|
+
const findings = [];
|
|
2700
|
+
const targets = sourceFiles ?? program.getSourceFiles();
|
|
2701
|
+
for (const sourceFile of targets) {
|
|
2702
|
+
if (isAmbient2(sourceFile.fileName))
|
|
2703
|
+
continue;
|
|
2704
|
+
const worlds = worldsOf(sourceFile.fileName);
|
|
2705
|
+
if (worlds.length === 0)
|
|
2706
|
+
continue;
|
|
2707
|
+
const visit = (node) => {
|
|
2708
|
+
if (ts13.isExpression(node)) {
|
|
2709
|
+
const entry = addressEntryOfArgument(checker, table, node);
|
|
2710
|
+
if (entry !== undefined && slotRejectsForeignSocket(entry)) {
|
|
2711
|
+
const address = staticAddressTextOf(checker, node, canonical);
|
|
2712
|
+
const socket = address === undefined ? undefined : socketOfAddress(address);
|
|
2713
|
+
if (address !== undefined && socket !== undefined && worlds.every((world) => world !== socket)) {
|
|
2714
|
+
findings.push({
|
|
2715
|
+
fileName: sourceFile.fileName,
|
|
2716
|
+
start: node.getStart(sourceFile),
|
|
2717
|
+
length: node.getWidth(sourceFile),
|
|
2718
|
+
address,
|
|
2719
|
+
socket,
|
|
2720
|
+
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`
|
|
2721
|
+
});
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
ts13.forEachChild(node, visit);
|
|
2726
|
+
};
|
|
2727
|
+
ts13.forEachChild(sourceFile, visit);
|
|
2728
|
+
}
|
|
2729
|
+
return findings;
|
|
2730
|
+
}
|
|
2731
|
+
// src/url-fragment-reachability.ts
|
|
2732
|
+
import * as ts14 from "typescript";
|
|
2733
|
+
function pathOfKey(key) {
|
|
2734
|
+
const socket = socketOfAddress(key);
|
|
2735
|
+
return socket === undefined ? key : key.slice(socket.length + 1);
|
|
2736
|
+
}
|
|
2737
|
+
function declaredComponentsOf(objects, path) {
|
|
2738
|
+
if (objects === undefined || path === "")
|
|
2739
|
+
return;
|
|
2740
|
+
if (objects.incomplete.length > 0)
|
|
2741
|
+
return;
|
|
2742
|
+
if (socketOfAddress(path) !== undefined) {
|
|
2743
|
+
return objects.paths.has(path) ? objects.componentsOf.get(path) : undefined;
|
|
2744
|
+
}
|
|
2745
|
+
if (!path.startsWith("/"))
|
|
2746
|
+
return;
|
|
2747
|
+
const declared = new Set;
|
|
2748
|
+
let known = false;
|
|
2749
|
+
for (const key of objects.paths) {
|
|
2750
|
+
if (pathOfKey(key) !== path)
|
|
2751
|
+
continue;
|
|
2752
|
+
const owned = objects.componentsOf.get(key);
|
|
2753
|
+
if (owned === undefined)
|
|
2754
|
+
return;
|
|
2755
|
+
known = true;
|
|
2756
|
+
for (const id of owned)
|
|
2757
|
+
declared.add(id);
|
|
2758
|
+
}
|
|
2759
|
+
return known ? [...declared].sort() : undefined;
|
|
2760
|
+
}
|
|
2761
|
+
function declaredPhrase(ids) {
|
|
2762
|
+
return ids.length === 0 ? "it declares no components at all" : `it declares ${ids.map((id) => `"${id}"`).join(", ")}`;
|
|
2763
|
+
}
|
|
1858
2764
|
function checkUrlFragmentReachability(input) {
|
|
1859
|
-
const { program, table, index, sourceFiles } = input;
|
|
2765
|
+
const { program, table, index, objects, sourceFiles } = input;
|
|
1860
2766
|
if (index.incomplete.length > 0) {
|
|
1861
2767
|
return { kind: "suppressed", reasons: index.incomplete };
|
|
1862
2768
|
}
|
|
@@ -1868,64 +2774,88 @@ function checkUrlFragmentReachability(input) {
|
|
|
1868
2774
|
if (isAmbient2(sourceFile.fileName))
|
|
1869
2775
|
continue;
|
|
1870
2776
|
const visit = (node) => {
|
|
1871
|
-
if (
|
|
1872
|
-
const text =
|
|
2777
|
+
if (ts14.isExpression(node) && isAddressClass(addressClassOfArgument(checker, table, node))) {
|
|
2778
|
+
const text = staticAddressTextOf(checker, node, canonical);
|
|
1873
2779
|
if (text !== undefined) {
|
|
1874
2780
|
const hash = text.indexOf("#");
|
|
1875
2781
|
const fragment = hash === -1 ? "" : text.slice(hash + 1);
|
|
1876
|
-
|
|
2782
|
+
const declared = fragment === "" ? undefined : declaredComponentsOf(objects, text.slice(0, hash));
|
|
2783
|
+
const reachable = declared === undefined ? index.ids.has(fragment) : declared.includes(fragment);
|
|
2784
|
+
if (fragment !== "" && !reachable) {
|
|
2785
|
+
const path = text.slice(0, hash);
|
|
1877
2786
|
findings.push({
|
|
1878
2787
|
fileName: sourceFile.fileName,
|
|
1879
2788
|
start: node.getStart(sourceFile),
|
|
1880
2789
|
length: node.getWidth(sourceFile),
|
|
1881
2790
|
fragment,
|
|
1882
|
-
message: `no \`.go\` or \`.collection\` in this project declares a component with the id ` + `"${fragment}", so this address cannot resolve at runtime`
|
|
2791
|
+
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 "${path}" declares no component with the id "${fragment}" ` + `(${declaredPhrase(declared)}), so this address cannot resolve at runtime`
|
|
1883
2792
|
});
|
|
1884
2793
|
}
|
|
1885
2794
|
}
|
|
1886
2795
|
}
|
|
1887
|
-
|
|
2796
|
+
ts14.forEachChild(node, visit);
|
|
1888
2797
|
};
|
|
1889
|
-
|
|
2798
|
+
ts14.forEachChild(sourceFile, visit);
|
|
1890
2799
|
}
|
|
1891
2800
|
return { kind: "checked", findings };
|
|
1892
2801
|
}
|
|
1893
2802
|
export {
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
stripIncludeBase,
|
|
1897
|
-
resolveClassifiedSlotAtPosition,
|
|
1898
|
-
readSceneDocuments,
|
|
1899
|
-
parseSceneTextFormat,
|
|
1900
|
-
parseBuildConfig,
|
|
1901
|
-
listProjectResourcePaths,
|
|
1902
|
-
isFragmentCaret,
|
|
1903
|
-
isExcludedProjectPath,
|
|
1904
|
-
isDefignoredPath,
|
|
1905
|
-
isAddressClass,
|
|
1906
|
-
getProgramDiagnostics,
|
|
1907
|
-
findMainEntryFactoryImports,
|
|
1908
|
-
displayPathOf,
|
|
1909
|
-
createTranspileSession,
|
|
1910
|
-
computeOutputRel,
|
|
1911
|
-
componentIdOfSameObjectAddress,
|
|
1912
|
-
checkUrlFragmentReachability,
|
|
1913
|
-
buildSpriteAnimationIndex,
|
|
1914
|
-
buildSceneObjectPathIndex,
|
|
1915
|
-
buildSceneComponentIndex,
|
|
1916
|
-
buildSceneAddressDeclaration,
|
|
1917
|
-
buildInputActionIndex,
|
|
1918
|
-
buildGuiNodeIndex,
|
|
1919
|
-
buildConfigKeyIndex,
|
|
1920
|
-
addressClassOfArgument,
|
|
1921
|
-
SceneTextFormatError,
|
|
1922
|
-
SCRIPT_SUFFIX_BY_KIND,
|
|
1923
|
-
SCENE_EXTENSIONS,
|
|
1924
|
-
SCAFFOLDED_DEFIGNORE_LINES,
|
|
1925
|
-
PROJECT_EXTENSIONS,
|
|
1926
|
-
INPUT_BINDING_EXTENSIONS,
|
|
1927
|
-
GUI_EXTENSIONS,
|
|
1928
|
-
GAME_PROJECT_DOCUMENT,
|
|
2803
|
+
ANIMATION_ASSET_EXTENSIONS,
|
|
2804
|
+
COLLECTION_REFERENCE_EXTENSIONS,
|
|
1929
2805
|
DEFAULT_INCLUDE,
|
|
1930
|
-
|
|
2806
|
+
GAME_PROJECT_DOCUMENT,
|
|
2807
|
+
GUI_EXTENSIONS,
|
|
2808
|
+
INPUT_BINDING_EXTENSIONS,
|
|
2809
|
+
LIBRARY_DEPENDENCY_ROOT,
|
|
2810
|
+
LIBRARY_SCENE_EXTENSIONS,
|
|
2811
|
+
PROJECT_EXTENSIONS,
|
|
2812
|
+
SCAFFOLDED_DEFIGNORE_LINES,
|
|
2813
|
+
SCENE_EXTENSIONS,
|
|
2814
|
+
SCRIPT_SUFFIX_BY_KIND,
|
|
2815
|
+
SceneTextFormatError,
|
|
2816
|
+
addressClassOfArgument,
|
|
2817
|
+
addressEntryOfArgument,
|
|
2818
|
+
archiveWrapperOf,
|
|
2819
|
+
buildComponentAnimationIndex,
|
|
2820
|
+
buildConfigKeyIndex,
|
|
2821
|
+
buildGuiFlipbookIndex,
|
|
2822
|
+
buildGuiNodeIndex,
|
|
2823
|
+
buildInputActionIndex,
|
|
2824
|
+
buildSceneAddressDeclaration,
|
|
2825
|
+
buildSceneCollectionRoles,
|
|
2826
|
+
buildSceneComponentIndex,
|
|
2827
|
+
buildSceneObjectPathIndex,
|
|
2828
|
+
buildScriptNamingContexts,
|
|
2829
|
+
checkCrossWorldAddresses,
|
|
2830
|
+
checkUrlFragmentReachability,
|
|
2831
|
+
collectComponentDeclarations,
|
|
2832
|
+
collectComponentIds,
|
|
2833
|
+
componentIdOfSameObjectAddress,
|
|
2834
|
+
computeOutputRel,
|
|
2835
|
+
createTranspileSession,
|
|
2836
|
+
displayPathOf,
|
|
2837
|
+
findMainEntryFactoryImports,
|
|
2838
|
+
getProgramDiagnostics,
|
|
2839
|
+
guiScriptResourceOf,
|
|
2840
|
+
guiScriptResourcesOf,
|
|
2841
|
+
isAddressClass,
|
|
2842
|
+
isContainedResourcePath,
|
|
2843
|
+
isDefignoredPath,
|
|
2844
|
+
isExcludedProjectPath,
|
|
2845
|
+
isFragmentCaret,
|
|
2846
|
+
libraryIncludedEntries,
|
|
2847
|
+
listProjectResourcePaths,
|
|
2848
|
+
parseBuildConfig,
|
|
2849
|
+
parseSceneTextFormat,
|
|
2850
|
+
readGameProjectDependencies,
|
|
2851
|
+
readGameProjectSetting,
|
|
2852
|
+
readLibrarySceneDocuments,
|
|
2853
|
+
readSceneDocuments,
|
|
2854
|
+
relativeAddressesFrom,
|
|
2855
|
+
resolveClassifiedSlotAtPosition,
|
|
2856
|
+
slotRejectsForeignSocket,
|
|
2857
|
+
socketOfAddress,
|
|
2858
|
+
stripIncludeBase,
|
|
2859
|
+
transpile,
|
|
2860
|
+
transpileProject
|
|
1931
2861
|
};
|