@defold-typescript/tstl-plugin 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.
@@ -1,3 +1,2 @@
1
- import { type BuildConfig } from "@defold-typescript/transpiler";
2
- import type { SceneReadHost } from "./scene-documents";
1
+ import { type BuildConfig, type SceneReadHost } from "@defold-typescript/transpiler";
3
2
  export declare function readBuildConfigFromHost(host: SceneReadHost, projectRoot: string): BuildConfig;
@@ -0,0 +1,49 @@
1
+ import ts from "typescript";
2
+ export type PluginFactory = (modules: {
3
+ typescript: typeof import("typescript");
4
+ }) => {
5
+ create(info: ts.server.PluginCreateInfo): ts.LanguageService;
6
+ };
7
+ export declare const SCENE_DOCUMENTS: Record<string, string>;
8
+ export declare const BOOTSTRAP_MAIN = "[bootstrap]\nmain_collection = /main.collectionc\n";
9
+ export declare function scriptObject(id: string, resource: string): string;
10
+ export declare function proxyObject(collection: string): string;
11
+ export declare const TWO_WORLD_DOCUMENTS: Record<string, string>;
12
+ export interface ProxyHost {
13
+ documents: Record<string, string>;
14
+ /** Listed by `readDirectory`, but answered `undefined` by `readFile`. */
15
+ unreadable: Set<string>;
16
+ directoryReads: string[][];
17
+ openWatchers: number;
18
+ fireDirectory(hostPath: string): void;
19
+ }
20
+ export interface CompletionSetup {
21
+ service: ts.LanguageService;
22
+ host: ProxyHost;
23
+ baseDisposeCalls(): number;
24
+ detailsCalls(): unknown[][];
25
+ }
26
+ export declare function completionSetup(options: {
27
+ source: string;
28
+ base: ts.WithMetadata<ts.CompletionInfo> | undefined;
29
+ /** Further program files beside the edited one, so a check can be scoped to it. */
30
+ sources?: Record<string, string>;
31
+ documents?: Record<string, string>;
32
+ /** Scene sources the host lists but cannot read, so a walk composes a real hole. */
33
+ unreadable?: readonly string[];
34
+ baseDiagnostics?: ts.Diagnostic[];
35
+ serverHost?: boolean;
36
+ watch?: boolean;
37
+ baseDispose?: boolean;
38
+ baseDetails?: ts.CompletionEntryDetails;
39
+ fileName?: string;
40
+ init?: PluginFactory;
41
+ }): CompletionSetup;
42
+ export declare function completionProxy(options: {
43
+ source: string;
44
+ base: ts.WithMetadata<ts.CompletionInfo> | undefined;
45
+ documents?: Record<string, string>;
46
+ serverHost?: boolean;
47
+ fileName?: string;
48
+ init?: PluginFactory;
49
+ }): ts.LanguageService;
package/dist/index.cjs ADDED
@@ -0,0 +1,6 @@
1
+ // tsserver resolves a plugin with Node10 resolution — `exports` is not
2
+ // consulted — and then rejects the loaded value unless it is itself a function.
3
+ // `require()` of the ESM entry yields the namespace object instead, so this
4
+ // CommonJS shim is what `main` points at: it unwraps the factory, and the ESM
5
+ // bundle stays the single build of the plugin.
6
+ module.exports = require("./index.js").default;
package/dist/index.js CHANGED
@@ -2,22 +2,31 @@
2
2
  import { readFileSync } from "node:fs";
3
3
  import { createRequire } from "node:module";
4
4
  import {
5
+ ANIMATION_ASSET_EXTENSIONS as ANIMATION_ASSET_EXTENSIONS2,
6
+ buildComponentAnimationIndex as buildComponentAnimationIndex2,
5
7
  buildConfigKeyIndex as buildConfigKeyIndex2,
8
+ buildGuiFlipbookIndex as buildGuiFlipbookIndex2,
6
9
  buildGuiNodeIndex as buildGuiNodeIndex2,
7
10
  buildInputActionIndex as buildInputActionIndex2,
8
- buildSceneComponentIndex as buildSceneComponentIndex2,
9
- buildSceneObjectPathIndex as buildSceneObjectPathIndex2,
10
- buildSpriteAnimationIndex as buildSpriteAnimationIndex2,
11
+ buildSceneObjectPathIndex as buildSceneObjectPathIndex3,
11
12
  componentIdOfSameObjectAddress as componentIdOfSameObjectAddress2,
12
- computeOutputRel as computeOutputRel2,
13
+ computeOutputRel as computeOutputRel3,
14
+ displayPathOf as displayPathOf4,
15
+ GAME_PROJECT_DOCUMENT as GAME_PROJECT_DOCUMENT3,
16
+ GUI_EXTENSIONS as GUI_EXTENSIONS3,
13
17
  getProgramDiagnostics,
18
+ INPUT_BINDING_EXTENSIONS as INPUT_BINDING_EXTENSIONS2,
14
19
  isAddressClass as isAddressClass2,
15
20
  isFragmentCaret as isFragmentCaret2,
21
+ PROJECT_EXTENSIONS as PROJECT_EXTENSIONS3,
16
22
  resolveClassifiedSlotAtPosition
17
23
  } from "@defold-typescript/transpiler";
18
24
 
19
25
  // src/build-config.ts
20
- import { DEFAULT_INCLUDE, parseBuildConfig } from "@defold-typescript/transpiler";
26
+ import {
27
+ DEFAULT_INCLUDE,
28
+ parseBuildConfig
29
+ } from "@defold-typescript/transpiler";
21
30
  var ALONGSIDE = { outDir: undefined, include: DEFAULT_INCLUDE };
22
31
  function readBuildConfigFromHost(host, projectRoot) {
23
32
  const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
@@ -78,63 +87,26 @@ function buildWholeLiteralCompletionEntries(input) {
78
87
  });
79
88
  }
80
89
 
81
- // src/scene-documents.ts
82
- import { isDefignoredPath } from "@defold-typescript/transpiler";
83
- var SCENE_EXTENSIONS = [".go", ".collection"];
84
- var GUI_EXTENSIONS = [".gui"];
85
- var INPUT_BINDING_EXTENSIONS = [".input_binding"];
86
- var ANIMATION_ASSET_EXTENSIONS = [".atlas", ".tilesource", ".sprite"];
87
- var PROJECT_EXTENSIONS = [".project"];
88
- var GAME_PROJECT_DOCUMENT = "game.project";
89
- function displayPathOf(projectRoot, filePath) {
90
- const path = filePath.replace(/\\/g, "/");
91
- const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
92
- return path.startsWith(`${root}/`) ? path.slice(root.length + 1) : path;
93
- }
94
- function isBuildOutput(displayPath) {
95
- return displayPath.split("/").includes("build");
96
- }
97
- function isExcludedProjectPath(displayPath) {
98
- return isBuildOutput(displayPath) || isDefignoredPath(displayPath);
99
- }
100
- function readSceneDocuments(host, projectRoot, extensions = SCENE_EXTENSIONS) {
101
- const documents = new Map;
102
- const unreadable = [];
103
- const paths = [];
104
- if (!host.readDirectory) {
105
- unreadable.push("the editor host cannot enumerate project files, so no scene source was read");
106
- return { documents, unreadable, paths };
107
- }
108
- for (const filePath of host.readDirectory(projectRoot, extensions)) {
109
- const displayPath = displayPathOf(projectRoot, filePath);
110
- if (isExcludedProjectPath(displayPath))
111
- continue;
112
- const text = host.readFile(filePath);
113
- if (text === undefined) {
114
- unreadable.push(`${displayPath}: could not be read`);
115
- continue;
116
- }
117
- documents.set(displayPath, text);
118
- paths.push(filePath);
119
- }
120
- return { documents, unreadable, paths };
121
- }
122
- function listProjectResourcePaths(host, projectRoot, extensions) {
123
- if (!host.readDirectory)
124
- return new Set;
125
- const paths = [];
126
- for (const filePath of host.readDirectory(projectRoot, extensions)) {
127
- const displayPath = displayPathOf(projectRoot, filePath);
128
- if (isExcludedProjectPath(displayPath))
129
- continue;
130
- if (!extensions.some((extension) => displayPath.endsWith(extension)))
131
- continue;
132
- paths.push(`/${displayPath}`);
133
- }
134
- return new Set(paths.sort());
135
- }
90
+ // src/scene-diagnostics.ts
91
+ import {
92
+ checkCrossWorldAddresses,
93
+ checkUrlFragmentReachability
94
+ } from "@defold-typescript/transpiler";
136
95
 
137
96
  // src/scene-index-cache.ts
97
+ import {
98
+ buildSceneCollectionRoles,
99
+ buildSceneComponentIndex,
100
+ COLLECTION_REFERENCE_EXTENSIONS,
101
+ displayPathOf,
102
+ GAME_PROJECT_DOCUMENT,
103
+ isExcludedProjectPath,
104
+ LIBRARY_DEPENDENCY_ROOT,
105
+ listProjectResourcePaths,
106
+ PROJECT_EXTENSIONS,
107
+ readSceneDocuments,
108
+ SCENE_EXTENSIONS
109
+ } from "@defold-typescript/transpiler";
138
110
  function cacheKey(extensions) {
139
111
  return [...extensions].sort().join("|");
140
112
  }
@@ -162,6 +134,9 @@ function createSceneIndexCache(host, projectRoot) {
162
134
  }
163
135
  function affectsCache(hostPath) {
164
136
  const displayPath = displayPathOf(projectRoot, hostPath);
137
+ if (displayPath === LIBRARY_DEPENDENCY_ROOT || displayPath.startsWith(`${LIBRARY_DEPENDENCY_ROOT}/`)) {
138
+ return true;
139
+ }
165
140
  if (isExcludedProjectPath(displayPath))
166
141
  return false;
167
142
  for (const extension of servedExtensions) {
@@ -247,25 +222,145 @@ function createSceneIndexCache(host, projectRoot) {
247
222
  }
248
223
  };
249
224
  }
225
+ function sceneComponentIndexOf(cache) {
226
+ return cache.derived("component-index", () => buildSceneComponentIndex(cache.documents().documents));
227
+ }
228
+ function sceneCollectionRolesOf(cache) {
229
+ return cache.derived("collection-roles", () => buildSceneCollectionRoles({
230
+ documents: cache.documents().documents,
231
+ references: cache.documents(COLLECTION_REFERENCE_EXTENSIONS).documents,
232
+ gameProject: cache.documents(PROJECT_EXTENSIONS).documents.get(GAME_PROJECT_DOCUMENT)
233
+ }));
234
+ }
235
+
236
+ // src/scene-relative-addresses.ts
237
+ import {
238
+ buildSceneObjectPathIndex,
239
+ buildScriptNamingContexts,
240
+ computeOutputRel,
241
+ displayPathOf as displayPathOf2,
242
+ GUI_EXTENSIONS,
243
+ guiScriptResourcesOf,
244
+ relativeAddressesFrom
245
+ } from "@defold-typescript/transpiler";
246
+ var CONTEXTS = "relative:naming-contexts";
247
+ function projectContexts(cache) {
248
+ return cache.derived(CONTEXTS, () => {
249
+ const index = buildSceneObjectPathIndex(cache.documents().documents, sceneCollectionRolesOf(cache));
250
+ const guiScripts = guiScriptResourcesOf(cache.documents(GUI_EXTENSIONS).documents);
251
+ return { index, byScriptResource: buildScriptNamingContexts(index, guiScripts) };
252
+ });
253
+ }
254
+ function projectObjectIndex(cache) {
255
+ return projectContexts(cache).index;
256
+ }
257
+ function relativeUniverseFor(cache, fileName) {
258
+ return cache.derived(`relative-universe:${fileName}`, () => {
259
+ const { index, byScriptResource } = projectContexts(cache);
260
+ const config = readBuildConfigFromHost(cache.host, cache.projectRoot);
261
+ const rel = displayPathOf2(cache.projectRoot, fileName);
262
+ const byObject = new Map;
263
+ for (const kind of ["script", "gui-script"]) {
264
+ for (const context of byScriptResource.get(computeOutputRel(rel, config, kind)) ?? []) {
265
+ byObject.set(context.object, context);
266
+ }
267
+ }
268
+ const contexts = [...byObject.values()].sort((a, b) => a.object.localeCompare(b.object));
269
+ const paths = new Set;
270
+ const addresses = new Set;
271
+ const contextsByEntry = new Map;
272
+ for (const context of contexts) {
273
+ const relative = relativeAddressesFrom(index, context);
274
+ for (const entry of relative.paths) {
275
+ paths.add(entry);
276
+ contextsByEntry.set(entry, [...contextsByEntry.get(entry) ?? [], context.object]);
277
+ }
278
+ for (const entry of relative.addresses) {
279
+ addresses.add(entry);
280
+ contextsByEntry.set(entry, [...contextsByEntry.get(entry) ?? [], context.object]);
281
+ }
282
+ }
283
+ for (const objects of contextsByEntry.values())
284
+ objects.sort();
285
+ return { contexts, paths, addresses, contextsByEntry };
286
+ });
287
+ }
288
+ function worldsFor(cache, fileName) {
289
+ const sockets = new Set;
290
+ for (const context of relativeUniverseFor(cache, fileName).contexts) {
291
+ sockets.add(context.socket);
292
+ }
293
+ return [...sockets];
294
+ }
295
+ function offersBareWorld(universe) {
296
+ return universe.contexts.length === 0 || universe.contexts.some((context) => context.socket === undefined);
297
+ }
298
+
299
+ // src/scene-diagnostics.ts
300
+ var DEFOLD_DIAGNOSTIC_SOURCE = "defold-typescript";
301
+ var UNREACHABLE_FRAGMENT_CODE = 9000101;
302
+ var FOREIGN_SOCKET_CODE = 9000102;
303
+ function suggestion(typescript, file, finding, code) {
304
+ return {
305
+ file,
306
+ start: finding.start,
307
+ length: finding.length,
308
+ messageText: finding.message,
309
+ category: typescript.DiagnosticCategory.Suggestion,
310
+ source: DEFOLD_DIAGNOSTIC_SOURCE,
311
+ code
312
+ };
313
+ }
314
+ function sceneReachabilityDiagnostics(input) {
315
+ const { ts: typescript, program, table, cache, fileName } = input;
316
+ const sourceFile = program.getSourceFile(fileName);
317
+ if (!sourceFile) {
318
+ return [];
319
+ }
320
+ const index = sceneComponentIndexOf(cache);
321
+ const report = checkUrlFragmentReachability({
322
+ program,
323
+ table,
324
+ index: { ids: index.ids, incomplete: [...index.incomplete, ...cache.documents().unreadable] },
325
+ objects: projectObjectIndex(cache),
326
+ sourceFiles: [sourceFile]
327
+ });
328
+ const fragments = report.kind === "suppressed" ? [] : report.findings.map((finding) => suggestion(typescript, sourceFile, finding, UNREACHABLE_FRAGMENT_CODE));
329
+ const crossWorld = checkCrossWorldAddresses({
330
+ program,
331
+ table,
332
+ worldsOf: (name) => worldsFor(cache, name),
333
+ sourceFiles: [sourceFile]
334
+ }).map((finding) => suggestion(typescript, sourceFile, finding, FOREIGN_SOCKET_CODE));
335
+ return [...fragments, ...crossWorld];
336
+ }
250
337
 
251
338
  // src/scene-provenance.ts
252
339
  import {
340
+ ANIMATION_ASSET_EXTENSIONS,
341
+ buildComponentAnimationIndex,
253
342
  buildConfigKeyIndex,
343
+ buildGuiFlipbookIndex,
254
344
  buildGuiNodeIndex,
255
345
  buildInputActionIndex,
256
- buildSceneComponentIndex,
257
- buildSceneObjectPathIndex,
258
- buildSpriteAnimationIndex,
346
+ buildSceneComponentIndex as buildSceneComponentIndex2,
347
+ buildSceneObjectPathIndex as buildSceneObjectPathIndex2,
259
348
  componentIdOfSameObjectAddress,
260
- computeOutputRel,
349
+ computeOutputRel as computeOutputRel2,
350
+ displayPathOf as displayPathOf3,
351
+ GAME_PROJECT_DOCUMENT as GAME_PROJECT_DOCUMENT2,
352
+ GUI_EXTENSIONS as GUI_EXTENSIONS2,
353
+ INPUT_BINDING_EXTENSIONS,
261
354
  isAddressClass,
262
- isFragmentCaret
355
+ isFragmentCaret,
356
+ PROJECT_EXTENSIONS as PROJECT_EXTENSIONS2
263
357
  } from "@defold-typescript/transpiler";
264
358
  var COMPONENT_PROVENANCE = "provenance:component-ids";
265
359
  var ACTION_PROVENANCE = "provenance:input-actions";
266
360
  var GUI_PROVENANCE = "provenance:gui-nodes";
267
361
  var PATH_PROVENANCE = "provenance:object-paths";
268
- var ANIMATION_PROVENANCE = "provenance:sprite-animations";
362
+ var ANIMATION_PROVENANCE = "provenance:component-animations";
363
+ var GUI_ANIMATION_PROVENANCE = "provenance:gui-flipbook";
269
364
  function byDeclaringDocument(documents, idsOf) {
270
365
  const declarers = new Map;
271
366
  for (const [displayPath, text] of documents) {
@@ -309,49 +404,73 @@ function byClaimedScript(documents) {
309
404
  return byResource;
310
405
  }
311
406
  function componentProvenance(cache) {
312
- return cache.derived(COMPONENT_PROVENANCE, () => byDeclaringDocument(cache.documents().documents, (narrowed) => buildSceneComponentIndex(narrowed).ids));
407
+ return cache.derived(COMPONENT_PROVENANCE, () => byDeclaringDocument(cache.documents().documents, (narrowed) => buildSceneComponentIndex2(narrowed).ids));
313
408
  }
314
409
  function actionProvenance(cache) {
315
410
  return cache.derived(ACTION_PROVENANCE, () => byDeclaringDocument(cache.documents(INPUT_BINDING_EXTENSIONS).documents, (narrowed) => buildInputActionIndex(narrowed)));
316
411
  }
317
412
  function nodeProvenance(cache, fileName) {
318
- const byResource = cache.derived(GUI_PROVENANCE, () => byClaimedScript(cache.documents(GUI_EXTENSIONS).documents));
413
+ const byResource = cache.derived(GUI_PROVENANCE, () => byClaimedScript(cache.documents(GUI_EXTENSIONS2).documents));
319
414
  const config = readBuildConfigFromHost(cache.host, cache.projectRoot);
320
- const resource = computeOutputRel(displayPathOf(cache.projectRoot, fileName), config, "gui-script");
415
+ const resource = computeOutputRel2(displayPathOf3(cache.projectRoot, fileName), config, "gui-script");
321
416
  return byResource.get(resource) ?? new Map;
322
417
  }
323
418
  function pathProvenance(cache) {
324
- return cache.derived(PATH_PROVENANCE, () => buildSceneObjectPathIndex(cache.documents().documents).declaredIn);
419
+ return cache.derived(PATH_PROVENANCE, () => buildSceneObjectPathIndex2(cache.documents().documents, sceneCollectionRolesOf(cache)).declaredIn);
420
+ }
421
+ function guiAnimationProvenance(input) {
422
+ const { cache, fileName, entryName } = input;
423
+ const index = cache.derived(GUI_ANIMATION_PROVENANCE, () => buildGuiFlipbookIndex({
424
+ scenes: cache.documents(GUI_EXTENSIONS2).documents,
425
+ assets: cache.documents(ANIMATION_ASSET_EXTENSIONS).documents
426
+ }));
427
+ const config = readBuildConfigFromHost(cache.host, cache.projectRoot);
428
+ const resource = computeOutputRel2(displayPathOf3(cache.projectRoot, fileName), config, "gui-script");
429
+ const declarers = index.byScriptResource.get(resource)?.get(entryName);
430
+ return declarers === undefined ? [] : [...declarers].sort();
325
431
  }
326
432
  function animationProvenance(input) {
327
433
  const { slot, cache, fileName, entryName } = input;
434
+ if (slot.nodeParameter !== undefined) {
435
+ return guiAnimationProvenance({ cache, fileName, entryName });
436
+ }
328
437
  const component = componentIdOfSameObjectAddress(slot.addressText ?? "");
329
438
  if (component === undefined)
330
439
  return [];
331
- const index = cache.derived(ANIMATION_PROVENANCE, () => buildSpriteAnimationIndex({
440
+ const index = cache.derived(ANIMATION_PROVENANCE, () => buildComponentAnimationIndex({
332
441
  scenes: cache.documents().documents,
333
442
  assets: cache.documents(ANIMATION_ASSET_EXTENSIONS).documents
334
443
  }));
335
444
  const config = readBuildConfigFromHost(cache.host, cache.projectRoot);
336
- const resource = computeOutputRel(displayPathOf(cache.projectRoot, fileName), config, "script");
337
- const tileSet = index.tileSetByScriptResource.get(resource)?.get(component);
338
- if (tileSet === undefined)
339
- return [];
340
- return index.byScriptResource.get(resource)?.get(component)?.has(entryName) ? [tileSet] : [];
445
+ const resource = computeOutputRel2(displayPathOf3(cache.projectRoot, fileName), config, "script");
446
+ const source = index.sourceByScriptResource.get(resource)?.get(component)?.get(entryName);
447
+ return source === undefined ? [] : [source];
341
448
  }
342
449
  function constantProvenance(slot, cache, entryName) {
343
450
  if (slot.class === "config-key") {
344
- const text = cache.documents(PROJECT_EXTENSIONS).documents.get(GAME_PROJECT_DOCUMENT);
451
+ const text = cache.documents(PROJECT_EXTENSIONS2).documents.get(GAME_PROJECT_DOCUMENT2);
345
452
  if (text === undefined)
346
453
  return [];
347
454
  const keys = cache.derived("config-keys", () => buildConfigKeyIndex(text));
348
- return keys.has(entryName) ? [GAME_PROJECT_DOCUMENT] : [];
455
+ return keys.has(entryName) ? [GAME_PROJECT_DOCUMENT2] : [];
349
456
  }
350
457
  const extensions = slot.resourceExtensions;
351
458
  if (extensions === undefined || extensions.length === 0)
352
459
  return [];
353
460
  return cache.resourcePaths(extensions).has(entryName) ? [entryName.replace(/^\//, "")] : [];
354
461
  }
462
+ function resolveRelativeEntryProvenance(input) {
463
+ const { slot, position, cache, fileName, entryName } = input;
464
+ if (!isAddressClass(slot.class) || isFragmentCaret(slot, position))
465
+ return;
466
+ const universe = relativeUniverseFor(cache, fileName);
467
+ const resolvesFrom = universe.contextsByEntry.get(entryName);
468
+ if (resolvesFrom === undefined)
469
+ return;
470
+ const others = universe.contexts.map((context) => context.object).filter((object) => !resolvesFrom.includes(object));
471
+ const relative = `Relative to ${resolvesFrom.join(", ")}`;
472
+ return others.length === 0 ? relative : `${relative}; not from ${others.join(", ")}`;
473
+ }
355
474
  function resolveEntryProvenance(input) {
356
475
  const { slot, position, cache, fileName, entryName } = input;
357
476
  if (isAddressClass(slot.class)) {
@@ -395,40 +514,60 @@ function componentEntries(slot, position, cache, baseEntries) {
395
514
  }
396
515
  return buildSceneCompletionEntries({
397
516
  slot,
398
- ids: cache.derived("component-ids", () => buildSceneComponentIndex2(cache.documents().documents).ids),
517
+ ids: sceneComponentIndexOf(cache).ids,
399
518
  baseEntries
400
519
  });
401
520
  }
402
- function objectPathEntries(slot, position, cache, baseEntries) {
521
+ function objectPathEntries(slot, position, cache, fileName, baseEntries) {
403
522
  if (isFragmentCaret2(slot, position)) {
404
523
  return [];
405
524
  }
406
- return buildAddressPathCompletionEntries({
407
- slot,
408
- paths: cache.derived("object-paths", () => buildSceneObjectPathIndex2(cache.documents().documents).paths),
409
- baseEntries
410
- });
525
+ const universe = relativeUniverseFor(cache, fileName);
526
+ const bare = offersBareWorld(universe);
527
+ const absolute = cache.derived("object-paths", () => buildSceneObjectPathIndex3(cache.documents().documents, sceneCollectionRolesOf(cache)).paths);
528
+ const paths = new Set;
529
+ for (const key of absolute) {
530
+ if (bare || !key.startsWith("/"))
531
+ paths.add(key);
532
+ }
533
+ for (const key of universe.paths)
534
+ paths.add(key);
535
+ const entries = buildAddressPathCompletionEntries({ slot, paths, baseEntries });
536
+ if (slot.fragmentStart === -1) {
537
+ entries.push(...buildWholeLiteralCompletionEntries({ slot, ids: universe.addresses, baseEntries }));
538
+ }
539
+ return entries;
411
540
  }
412
541
  function nodeEntries(slot, cache, fileName, baseEntries) {
413
542
  const { projectRoot } = cache;
414
- const index = cache.derived("gui-nodes", () => buildGuiNodeIndex2(cache.documents(GUI_EXTENSIONS).documents));
543
+ const index = cache.derived("gui-nodes", () => buildGuiNodeIndex2(cache.documents(GUI_EXTENSIONS3).documents));
415
544
  const config = readBuildConfigFromHost(cache.host, projectRoot);
416
- const resource = computeOutputRel2(displayPathOf(projectRoot, fileName), config, "gui-script");
545
+ const resource = computeOutputRel3(displayPathOf4(projectRoot, fileName), config, "gui-script");
417
546
  const ids = index.byScriptResource.get(resource);
418
547
  return ids === undefined ? [] : buildWholeLiteralCompletionEntries({ slot, ids, baseEntries });
419
548
  }
420
549
  function animationEntries(slot, cache, fileName, baseEntries) {
550
+ const { projectRoot } = cache;
551
+ if (slot.nodeParameter !== undefined) {
552
+ const index2 = cache.derived("gui-flipbook", () => buildGuiFlipbookIndex2({
553
+ scenes: cache.documents(GUI_EXTENSIONS3).documents,
554
+ assets: cache.documents(ANIMATION_ASSET_EXTENSIONS2).documents
555
+ }));
556
+ const config2 = readBuildConfigFromHost(cache.host, projectRoot);
557
+ const resource2 = computeOutputRel3(displayPathOf4(projectRoot, fileName), config2, "gui-script");
558
+ const declared = index2.byScriptResource.get(resource2);
559
+ return declared === undefined ? [] : buildWholeLiteralCompletionEntries({ slot, ids: new Set(declared.keys()), baseEntries });
560
+ }
421
561
  const component = componentIdOfSameObjectAddress2(slot.addressText ?? "");
422
562
  if (component === undefined) {
423
563
  return [];
424
564
  }
425
- const { projectRoot } = cache;
426
- const index = cache.derived("sprite-animations", () => buildSpriteAnimationIndex2({
565
+ const index = cache.derived("component-animations", () => buildComponentAnimationIndex2({
427
566
  scenes: cache.documents().documents,
428
- assets: cache.documents(ANIMATION_ASSET_EXTENSIONS).documents
567
+ assets: cache.documents(ANIMATION_ASSET_EXTENSIONS2).documents
429
568
  }));
430
569
  const config = readBuildConfigFromHost(cache.host, projectRoot);
431
- const resource = computeOutputRel2(displayPathOf(projectRoot, fileName), config, "script");
570
+ const resource = computeOutputRel3(displayPathOf4(projectRoot, fileName), config, "script");
432
571
  const ids = index.byScriptResource.get(resource)?.get(component);
433
572
  return ids === undefined ? [] : buildWholeLiteralCompletionEntries({ slot, ids, baseEntries });
434
573
  }
@@ -441,7 +580,7 @@ function resourceEntries(slot, cache, baseEntries) {
441
580
  return buildWholeLiteralCompletionEntries({ slot, ids, baseEntries });
442
581
  }
443
582
  function configKeyEntries(slot, cache, baseEntries) {
444
- const text = cache.documents(PROJECT_EXTENSIONS).documents.get(GAME_PROJECT_DOCUMENT);
583
+ const text = cache.documents(PROJECT_EXTENSIONS3).documents.get(GAME_PROJECT_DOCUMENT3);
445
584
  if (text === undefined) {
446
585
  return [];
447
586
  }
@@ -449,21 +588,16 @@ function configKeyEntries(slot, cache, baseEntries) {
449
588
  return buildWholeLiteralCompletionEntries({ slot, ids, baseEntries });
450
589
  }
451
590
  function actionIdEntries(slot, cache, baseEntries) {
452
- const ids = cache.derived("input-actions", () => buildInputActionIndex2(cache.documents(INPUT_BINDING_EXTENSIONS).documents));
591
+ const ids = cache.derived("input-actions", () => buildInputActionIndex2(cache.documents(INPUT_BINDING_EXTENSIONS2).documents));
453
592
  return buildWholeLiteralCompletionEntries({ slot, ids, baseEntries });
454
593
  }
455
- function provenancePanel(entryName, declaredIn) {
594
+ function provenancePanel(entryName, documentation) {
456
595
  return {
457
596
  name: entryName,
458
597
  kind: CONTRIBUTED_ENTRY_KIND,
459
598
  kindModifiers: "",
460
599
  displayParts: [{ kind: "stringLiteral", text: JSON.stringify(entryName) }],
461
- documentation: [
462
- {
463
- kind: "text",
464
- text: `Declared in ${declaredIn.join(", ")}`
465
- }
466
- ],
600
+ documentation: [{ kind: "text", text: documentation }],
467
601
  source: [{ kind: "text", text: DEFOLD_COMPLETION_SOURCE }]
468
602
  };
469
603
  }
@@ -501,8 +635,18 @@ function init(modules) {
501
635
  if (!slot) {
502
636
  return forward();
503
637
  }
638
+ const relative = resolveRelativeEntryProvenance({
639
+ slot,
640
+ position,
641
+ cache,
642
+ fileName,
643
+ entryName
644
+ });
645
+ if (relative !== undefined) {
646
+ return provenancePanel(entryName, relative);
647
+ }
504
648
  const declaredIn = resolveEntryProvenance({ slot, position, cache, fileName, entryName });
505
- return declaredIn.length === 0 ? forward() : provenancePanel(entryName, declaredIn);
649
+ return declaredIn.length === 0 ? forward() : provenancePanel(entryName, `Declared in ${declaredIn.join(", ")}`);
506
650
  };
507
651
  proxy.getSemanticDiagnostics = (fileName) => {
508
652
  const prior = base.getSemanticDiagnostics(fileName);
@@ -511,7 +655,9 @@ function init(modules) {
511
655
  return prior;
512
656
  }
513
657
  const transpiler = getProgramDiagnostics(program, program.getSourceFile(fileName)).map((diagnostic) => ({ ...diagnostic, category: ts.DiagnosticCategory.Suggestion }));
514
- return [...prior, ...transpiler];
658
+ const table = loadUrlParameterTable();
659
+ const scene = cache && table ? sceneReachabilityDiagnostics({ ts, program, table, cache, fileName }) : [];
660
+ return [...prior, ...transpiler, ...scene];
515
661
  };
516
662
  proxy.getCompletionsAtPosition = (fileName, position, options, formattingSettings) => {
517
663
  const prior = base.getCompletionsAtPosition(fileName, position, options, formattingSettings);
@@ -530,7 +676,7 @@ function init(modules) {
530
676
  const baseEntries = prior?.entries ?? [];
531
677
  const entries = isAddressClass2(slot.class) ? [
532
678
  ...componentEntries(slot, position, cache, baseEntries),
533
- ...objectPathEntries(slot, position, cache, baseEntries)
679
+ ...objectPathEntries(slot, position, cache, fileName, baseEntries)
534
680
  ] : slot.class === "gui-node" ? nodeEntries(slot, cache, fileName, baseEntries) : slot.class === "animation" ? animationEntries(slot, cache, fileName, baseEntries) : slot.class === "resource-path" ? resourceEntries(slot, cache, baseEntries) : slot.class === "config-key" ? configKeyEntries(slot, cache, baseEntries) : slot.class === "action-id" ? actionIdEntries(slot, cache, baseEntries) : [];
535
681
  if (entries.length === 0) {
536
682
  return prior;
@@ -0,0 +1,29 @@
1
+ import type { UrlParameterTable } from "@defold-typescript/types";
2
+ import type * as ts from "typescript";
3
+ import { type SceneIndexCache } from "./scene-index-cache";
4
+ /** The `source` an editor renders beside the message, and `--json` never sees. */
5
+ export declare const DEFOLD_DIAGNOSTIC_SOURCE = "defold-typescript";
6
+ /**
7
+ * The address findings the editor surfaces on the file being edited, as
8
+ * advisory suggestions.
9
+ *
10
+ * Every path that cannot produce one answers with an empty array. A suppressed
11
+ * reachability report is the important case: there is no file span on which a
12
+ * "the check did not run" notice could honestly sit, and a speculative squiggle
13
+ * is worse than none — the build says so in prose instead, where a whole-project
14
+ * line has somewhere to go.
15
+ *
16
+ * The *whole* hole is one index, composed the way `sceneIndexForBuild` composes
17
+ * it: what the parse could not read plus what the walk could not reach. The
18
+ * completion path deliberately reads a partial universe — a suggestion claims
19
+ * nothing about what is absent — but a finding does, so a component declared
20
+ * only by an unresolved dependency suppresses the check rather than reporting
21
+ * the address as unreachable.
22
+ */
23
+ export declare function sceneReachabilityDiagnostics(input: {
24
+ ts: typeof import("typescript");
25
+ program: ts.Program;
26
+ table: UrlParameterTable;
27
+ cache: SceneIndexCache;
28
+ fileName: string;
29
+ }): ts.Diagnostic[];
@@ -1,5 +1,5 @@
1
+ import { readSceneDocuments, type SceneCollectionRoles, type SceneComponentIndex, type SceneReadHost } from "@defold-typescript/transpiler";
1
2
  import type * as ts from "typescript";
2
- import { readSceneDocuments, type SceneReadHost } from "./scene-documents";
3
3
  export interface SceneWatchHost extends SceneReadHost {
4
4
  watchFile?(path: string, callback: ts.FileWatcherCallback, pollingInterval?: number): ts.FileWatcher;
5
5
  watchDirectory?(path: string, callback: ts.DirectoryWatcherCallback, recursive?: boolean): ts.FileWatcher;
@@ -13,3 +13,5 @@ export interface SceneIndexCache {
13
13
  dispose(): void;
14
14
  }
15
15
  export declare function createSceneIndexCache(host: SceneWatchHost, projectRoot: string): SceneIndexCache;
16
+ export declare function sceneComponentIndexOf(cache: SceneIndexCache): SceneComponentIndex;
17
+ export declare function sceneCollectionRolesOf(cache: SceneIndexCache): SceneCollectionRoles;
@@ -1,5 +1,24 @@
1
1
  import { type ClassifiedSlot } from "@defold-typescript/transpiler";
2
- import type { SceneIndexCache } from "./scene-index-cache";
2
+ import { type SceneIndexCache } from "./scene-index-cache";
3
+ /**
4
+ * The sentence a relative entry's panel carries, or `undefined` where the entry
5
+ * is not one — a fragment caret, a slot that names no address, or a name this
6
+ * file's relative universe does not offer.
7
+ *
8
+ * A relative address is valid from the object hosting the script and nowhere
9
+ * else, so the objects it resolves from *are* its provenance: naming a declaring
10
+ * file instead would answer a question the author is not asking. Where the entry
11
+ * resolves from only some of the contexts this script runs in, the others are
12
+ * named too — a sometimes-valid entry that read as an always-valid one is the
13
+ * wrong-suggestion class this panel exists to close.
14
+ */
15
+ export declare function resolveRelativeEntryProvenance(input: {
16
+ slot: ClassifiedSlot;
17
+ position: number;
18
+ cache: SceneIndexCache;
19
+ fileName: string;
20
+ entryName: string;
21
+ }): string | undefined;
3
22
  export declare function resolveEntryProvenance(input: {
4
23
  slot: ClassifiedSlot;
5
24
  position: number;