@defold-typescript/tstl-plugin 0.23.0 → 0.25.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.
@@ -0,0 +1,3 @@
1
+ import { type BuildConfig } from "@defold-typescript/transpiler";
2
+ import type { SceneReadHost } from "./scene-documents";
3
+ export declare function readBuildConfigFromHost(host: SceneReadHost, projectRoot: string): BuildConfig;
package/dist/index.js CHANGED
@@ -1,5 +1,472 @@
1
1
  // src/index.ts
2
- import { getProgramDiagnostics } from "@defold-typescript/transpiler";
2
+ import { readFileSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import {
5
+ buildConfigKeyIndex as buildConfigKeyIndex2,
6
+ buildGuiNodeIndex as buildGuiNodeIndex2,
7
+ buildInputActionIndex as buildInputActionIndex2,
8
+ buildSceneComponentIndex as buildSceneComponentIndex2,
9
+ buildSceneObjectPathIndex as buildSceneObjectPathIndex2,
10
+ buildSpriteAnimationIndex as buildSpriteAnimationIndex2,
11
+ componentIdOfSameObjectAddress as componentIdOfSameObjectAddress2,
12
+ computeOutputRel as computeOutputRel2,
13
+ getProgramDiagnostics,
14
+ isAddressClass as isAddressClass2,
15
+ isFragmentCaret as isFragmentCaret2,
16
+ resolveClassifiedSlotAtPosition
17
+ } from "@defold-typescript/transpiler";
18
+
19
+ // src/build-config.ts
20
+ import { DEFAULT_INCLUDE, parseBuildConfig } from "@defold-typescript/transpiler";
21
+ var ALONGSIDE = { outDir: undefined, include: DEFAULT_INCLUDE };
22
+ function readBuildConfigFromHost(host, projectRoot) {
23
+ const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
24
+ const text = host.readFile(`${root}/tsconfig.json`);
25
+ if (text === undefined) {
26
+ return ALONGSIDE;
27
+ }
28
+ try {
29
+ return parseBuildConfig(text);
30
+ } catch {
31
+ return ALONGSIDE;
32
+ }
33
+ }
34
+
35
+ // src/scene-completions.ts
36
+ var CONTRIBUTED_ENTRY_KIND = "string";
37
+ function keyAboveAll(baseEntries) {
38
+ return `${baseEntries.reduce((greatest, base) => base.sortText > greatest ? base.sortText : greatest, "zz")}0`;
39
+ }
40
+ var DEFOLD_COMPLETION_SOURCE = "defold-typescript/scene";
41
+ function entriesFor(ids, offeredName, baseEntries, replacementSpan) {
42
+ const offered = new Set(baseEntries.map((base) => base.name));
43
+ const sortText = keyAboveAll(baseEntries);
44
+ return [...ids].sort().filter((id) => !offered.has(offeredName(id))).map((id) => ({
45
+ name: id,
46
+ kind: CONTRIBUTED_ENTRY_KIND,
47
+ kindModifiers: "",
48
+ sortText,
49
+ replacementSpan,
50
+ source: DEFOLD_COMPLETION_SOURCE
51
+ }));
52
+ }
53
+ function buildSceneCompletionEntries(input) {
54
+ const { slot, ids, baseEntries } = input;
55
+ if (slot.fragmentStart === -1)
56
+ return [];
57
+ const fragmentOffset = slot.fragmentStart - slot.textStart;
58
+ const prefix = slot.text.slice(0, fragmentOffset);
59
+ return entriesFor(ids, (id) => `${prefix}${id}`, baseEntries, {
60
+ start: slot.fragmentStart,
61
+ length: slot.text.length - fragmentOffset
62
+ });
63
+ }
64
+ function buildAddressPathCompletionEntries(input) {
65
+ const { slot, paths, baseEntries } = input;
66
+ const length = slot.fragmentStart === -1 ? slot.text.length : slot.fragmentStart - slot.textStart - 1;
67
+ const suffix = slot.text.slice(length);
68
+ return entriesFor(paths, (path) => `${path}${suffix}`, baseEntries, {
69
+ start: slot.textStart,
70
+ length
71
+ });
72
+ }
73
+ function buildWholeLiteralCompletionEntries(input) {
74
+ const { slot, ids, baseEntries } = input;
75
+ return entriesFor(ids, (id) => id, baseEntries, {
76
+ start: slot.textStart,
77
+ length: slot.text.length
78
+ });
79
+ }
80
+
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
+ }
136
+
137
+ // src/scene-index-cache.ts
138
+ function cacheKey(extensions) {
139
+ return [...extensions].sort().join("|");
140
+ }
141
+ function createSceneIndexCache(host, projectRoot) {
142
+ const watchDirectory = host.watchDirectory;
143
+ const watchFile = host.watchFile;
144
+ const enabled = typeof watchDirectory === "function" && typeof watchFile === "function";
145
+ const documentCache = new Map;
146
+ const resourceCache = new Map;
147
+ const derivedCache = new Map;
148
+ const fileWatchers = new Map;
149
+ const servedExtensions = new Set;
150
+ let directoryWatcher;
151
+ let disposed = false;
152
+ function closeFileWatchers() {
153
+ for (const watcher of fileWatchers.values())
154
+ watcher.close();
155
+ fileWatchers.clear();
156
+ }
157
+ function invalidate() {
158
+ documentCache.clear();
159
+ resourceCache.clear();
160
+ derivedCache.clear();
161
+ closeFileWatchers();
162
+ }
163
+ function affectsCache(hostPath) {
164
+ const displayPath = displayPathOf(projectRoot, hostPath);
165
+ if (isExcludedProjectPath(displayPath))
166
+ return false;
167
+ for (const extension of servedExtensions) {
168
+ if (displayPath.endsWith(extension))
169
+ return true;
170
+ }
171
+ return false;
172
+ }
173
+ function onEvent(hostPath) {
174
+ if (disposed)
175
+ return;
176
+ if (affectsCache(hostPath))
177
+ invalidate();
178
+ }
179
+ function active() {
180
+ if (!enabled || disposed)
181
+ return false;
182
+ if (!directoryWatcher && watchDirectory) {
183
+ directoryWatcher = watchDirectory(projectRoot, onEvent, true);
184
+ }
185
+ return true;
186
+ }
187
+ function serve(extensions) {
188
+ for (const extension of extensions)
189
+ servedExtensions.add(extension);
190
+ }
191
+ function watchReadFiles(paths) {
192
+ if (!watchFile)
193
+ return;
194
+ for (const path of paths) {
195
+ if (fileWatchers.has(path))
196
+ continue;
197
+ fileWatchers.set(path, watchFile(path, (fileName) => onEvent(fileName)));
198
+ }
199
+ }
200
+ return {
201
+ host,
202
+ projectRoot,
203
+ documents(extensions = SCENE_EXTENSIONS) {
204
+ if (!active())
205
+ return readSceneDocuments(host, projectRoot, extensions);
206
+ const key = cacheKey(extensions);
207
+ const cached = documentCache.get(key);
208
+ if (cached)
209
+ return cached;
210
+ const result = readSceneDocuments(host, projectRoot, extensions);
211
+ documentCache.set(key, result);
212
+ serve(extensions);
213
+ watchReadFiles(result.paths);
214
+ return result;
215
+ },
216
+ resourcePaths(extensions) {
217
+ if (!active())
218
+ return listProjectResourcePaths(host, projectRoot, extensions);
219
+ const key = cacheKey(extensions);
220
+ const cached = resourceCache.get(key);
221
+ if (cached)
222
+ return cached;
223
+ const result = listProjectResourcePaths(host, projectRoot, extensions);
224
+ resourceCache.set(key, result);
225
+ serve(extensions);
226
+ return result;
227
+ },
228
+ derived(key, compute) {
229
+ if (!active())
230
+ return compute();
231
+ if (derivedCache.has(key))
232
+ return derivedCache.get(key);
233
+ const value = compute();
234
+ derivedCache.set(key, value);
235
+ return value;
236
+ },
237
+ dispose() {
238
+ if (disposed)
239
+ return;
240
+ disposed = true;
241
+ directoryWatcher?.close();
242
+ directoryWatcher = undefined;
243
+ closeFileWatchers();
244
+ documentCache.clear();
245
+ resourceCache.clear();
246
+ derivedCache.clear();
247
+ }
248
+ };
249
+ }
250
+
251
+ // src/scene-provenance.ts
252
+ import {
253
+ buildConfigKeyIndex,
254
+ buildGuiNodeIndex,
255
+ buildInputActionIndex,
256
+ buildSceneComponentIndex,
257
+ buildSceneObjectPathIndex,
258
+ buildSpriteAnimationIndex,
259
+ componentIdOfSameObjectAddress,
260
+ computeOutputRel,
261
+ isAddressClass,
262
+ isFragmentCaret
263
+ } from "@defold-typescript/transpiler";
264
+ var COMPONENT_PROVENANCE = "provenance:component-ids";
265
+ var ACTION_PROVENANCE = "provenance:input-actions";
266
+ var GUI_PROVENANCE = "provenance:gui-nodes";
267
+ var PATH_PROVENANCE = "provenance:object-paths";
268
+ var ANIMATION_PROVENANCE = "provenance:sprite-animations";
269
+ function byDeclaringDocument(documents, idsOf) {
270
+ const declarers = new Map;
271
+ for (const [displayPath, text] of documents) {
272
+ for (const id of idsOf(new Map([[displayPath, text]]))) {
273
+ const paths = declarers.get(id);
274
+ if (paths) {
275
+ paths.push(displayPath);
276
+ } else {
277
+ declarers.set(id, [displayPath]);
278
+ }
279
+ }
280
+ }
281
+ for (const paths of declarers.values())
282
+ paths.sort();
283
+ return declarers;
284
+ }
285
+ function byClaimedScript(documents) {
286
+ const byResource = new Map;
287
+ for (const [displayPath, text] of documents) {
288
+ const index = buildGuiNodeIndex(new Map([[displayPath, text]]));
289
+ for (const [resource, ids] of index.byScriptResource) {
290
+ let declarers = byResource.get(resource);
291
+ if (!declarers) {
292
+ declarers = new Map;
293
+ byResource.set(resource, declarers);
294
+ }
295
+ for (const id of ids) {
296
+ const paths = declarers.get(id);
297
+ if (paths) {
298
+ paths.push(displayPath);
299
+ } else {
300
+ declarers.set(id, [displayPath]);
301
+ }
302
+ }
303
+ }
304
+ }
305
+ for (const declarers of byResource.values()) {
306
+ for (const paths of declarers.values())
307
+ paths.sort();
308
+ }
309
+ return byResource;
310
+ }
311
+ function componentProvenance(cache) {
312
+ return cache.derived(COMPONENT_PROVENANCE, () => byDeclaringDocument(cache.documents().documents, (narrowed) => buildSceneComponentIndex(narrowed).ids));
313
+ }
314
+ function actionProvenance(cache) {
315
+ return cache.derived(ACTION_PROVENANCE, () => byDeclaringDocument(cache.documents(INPUT_BINDING_EXTENSIONS).documents, (narrowed) => buildInputActionIndex(narrowed)));
316
+ }
317
+ function nodeProvenance(cache, fileName) {
318
+ const byResource = cache.derived(GUI_PROVENANCE, () => byClaimedScript(cache.documents(GUI_EXTENSIONS).documents));
319
+ const config = readBuildConfigFromHost(cache.host, cache.projectRoot);
320
+ const resource = computeOutputRel(displayPathOf(cache.projectRoot, fileName), config, "gui-script");
321
+ return byResource.get(resource) ?? new Map;
322
+ }
323
+ function pathProvenance(cache) {
324
+ return cache.derived(PATH_PROVENANCE, () => buildSceneObjectPathIndex(cache.documents().documents).declaredIn);
325
+ }
326
+ function animationProvenance(input) {
327
+ const { slot, cache, fileName, entryName } = input;
328
+ const component = componentIdOfSameObjectAddress(slot.addressText ?? "");
329
+ if (component === undefined)
330
+ return [];
331
+ const index = cache.derived(ANIMATION_PROVENANCE, () => buildSpriteAnimationIndex({
332
+ scenes: cache.documents().documents,
333
+ assets: cache.documents(ANIMATION_ASSET_EXTENSIONS).documents
334
+ }));
335
+ 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] : [];
341
+ }
342
+ function constantProvenance(slot, cache, entryName) {
343
+ if (slot.class === "config-key") {
344
+ const text = cache.documents(PROJECT_EXTENSIONS).documents.get(GAME_PROJECT_DOCUMENT);
345
+ if (text === undefined)
346
+ return [];
347
+ const keys = cache.derived("config-keys", () => buildConfigKeyIndex(text));
348
+ return keys.has(entryName) ? [GAME_PROJECT_DOCUMENT] : [];
349
+ }
350
+ const extensions = slot.resourceExtensions;
351
+ if (extensions === undefined || extensions.length === 0)
352
+ return [];
353
+ return cache.resourcePaths(extensions).has(entryName) ? [entryName.replace(/^\//, "")] : [];
354
+ }
355
+ function resolveEntryProvenance(input) {
356
+ const { slot, position, cache, fileName, entryName } = input;
357
+ if (isAddressClass(slot.class)) {
358
+ if (!isFragmentCaret(slot, position)) {
359
+ return pathProvenance(cache).get(entryName) ?? [];
360
+ }
361
+ return componentProvenance(cache).get(entryName) ?? [];
362
+ }
363
+ if (slot.class === "gui-node") {
364
+ return nodeProvenance(cache, fileName).get(entryName) ?? [];
365
+ }
366
+ if (slot.class === "action-id") {
367
+ return actionProvenance(cache).get(entryName) ?? [];
368
+ }
369
+ if (slot.class === "animation") {
370
+ return animationProvenance({ slot, cache, fileName, entryName });
371
+ }
372
+ if (slot.class === "config-key" || slot.class === "resource-path") {
373
+ return constantProvenance(slot, cache, entryName);
374
+ }
375
+ return [];
376
+ }
377
+
378
+ // src/index.ts
379
+ var requireFromHere = createRequire(import.meta.url);
380
+ var urlParameterTable;
381
+ function loadUrlParameterTable() {
382
+ if (!urlParameterTable) {
383
+ try {
384
+ const path = requireFromHere.resolve("@defold-typescript/types/url-parameters.json");
385
+ urlParameterTable = JSON.parse(readFileSync(path, "utf8"));
386
+ } catch {
387
+ return;
388
+ }
389
+ }
390
+ return urlParameterTable;
391
+ }
392
+ function componentEntries(slot, position, cache, baseEntries) {
393
+ if (!isFragmentCaret2(slot, position)) {
394
+ return [];
395
+ }
396
+ return buildSceneCompletionEntries({
397
+ slot,
398
+ ids: cache.derived("component-ids", () => buildSceneComponentIndex2(cache.documents().documents).ids),
399
+ baseEntries
400
+ });
401
+ }
402
+ function objectPathEntries(slot, position, cache, baseEntries) {
403
+ if (isFragmentCaret2(slot, position)) {
404
+ return [];
405
+ }
406
+ return buildAddressPathCompletionEntries({
407
+ slot,
408
+ paths: cache.derived("object-paths", () => buildSceneObjectPathIndex2(cache.documents().documents).paths),
409
+ baseEntries
410
+ });
411
+ }
412
+ function nodeEntries(slot, cache, fileName, baseEntries) {
413
+ const { projectRoot } = cache;
414
+ const index = cache.derived("gui-nodes", () => buildGuiNodeIndex2(cache.documents(GUI_EXTENSIONS).documents));
415
+ const config = readBuildConfigFromHost(cache.host, projectRoot);
416
+ const resource = computeOutputRel2(displayPathOf(projectRoot, fileName), config, "gui-script");
417
+ const ids = index.byScriptResource.get(resource);
418
+ return ids === undefined ? [] : buildWholeLiteralCompletionEntries({ slot, ids, baseEntries });
419
+ }
420
+ function animationEntries(slot, cache, fileName, baseEntries) {
421
+ const component = componentIdOfSameObjectAddress2(slot.addressText ?? "");
422
+ if (component === undefined) {
423
+ return [];
424
+ }
425
+ const { projectRoot } = cache;
426
+ const index = cache.derived("sprite-animations", () => buildSpriteAnimationIndex2({
427
+ scenes: cache.documents().documents,
428
+ assets: cache.documents(ANIMATION_ASSET_EXTENSIONS).documents
429
+ }));
430
+ const config = readBuildConfigFromHost(cache.host, projectRoot);
431
+ const resource = computeOutputRel2(displayPathOf(projectRoot, fileName), config, "script");
432
+ const ids = index.byScriptResource.get(resource)?.get(component);
433
+ return ids === undefined ? [] : buildWholeLiteralCompletionEntries({ slot, ids, baseEntries });
434
+ }
435
+ function resourceEntries(slot, cache, baseEntries) {
436
+ const extensions = slot.resourceExtensions;
437
+ if (extensions === undefined || extensions.length === 0) {
438
+ return [];
439
+ }
440
+ const ids = cache.resourcePaths(extensions);
441
+ return buildWholeLiteralCompletionEntries({ slot, ids, baseEntries });
442
+ }
443
+ function configKeyEntries(slot, cache, baseEntries) {
444
+ const text = cache.documents(PROJECT_EXTENSIONS).documents.get(GAME_PROJECT_DOCUMENT);
445
+ if (text === undefined) {
446
+ return [];
447
+ }
448
+ const ids = cache.derived("config-keys", () => buildConfigKeyIndex2(text));
449
+ return buildWholeLiteralCompletionEntries({ slot, ids, baseEntries });
450
+ }
451
+ function actionIdEntries(slot, cache, baseEntries) {
452
+ const ids = cache.derived("input-actions", () => buildInputActionIndex2(cache.documents(INPUT_BINDING_EXTENSIONS).documents));
453
+ return buildWholeLiteralCompletionEntries({ slot, ids, baseEntries });
454
+ }
455
+ function provenancePanel(entryName, declaredIn) {
456
+ return {
457
+ name: entryName,
458
+ kind: CONTRIBUTED_ENTRY_KIND,
459
+ kindModifiers: "",
460
+ displayParts: [{ kind: "stringLiteral", text: JSON.stringify(entryName) }],
461
+ documentation: [
462
+ {
463
+ kind: "text",
464
+ text: `Declared in ${declaredIn.join(", ")}`
465
+ }
466
+ ],
467
+ source: [{ kind: "text", text: DEFOLD_COMPLETION_SOURCE }]
468
+ };
469
+ }
3
470
  function init(modules) {
4
471
  const ts = modules.typescript;
5
472
  function create(info) {
@@ -13,6 +480,30 @@ function init(modules) {
13
480
  writable[key] = (...args) => fn.apply(base, args);
14
481
  }
15
482
  }
483
+ const serverHost = info.serverHost;
484
+ const cache = serverHost?.readDirectory ? createSceneIndexCache(serverHost, info.project.getCurrentDirectory()) : undefined;
485
+ proxy.dispose = () => {
486
+ cache?.dispose();
487
+ const disposeBase = base.dispose;
488
+ disposeBase?.call(base);
489
+ };
490
+ proxy.getCompletionEntryDetails = (fileName, position, entryName, formatOptions, source, preferences, data) => {
491
+ const forward = () => base.getCompletionEntryDetails?.(fileName, position, entryName, formatOptions, source, preferences, data);
492
+ if (source !== DEFOLD_COMPLETION_SOURCE || !cache) {
493
+ return forward();
494
+ }
495
+ const program = base.getProgram();
496
+ const table = loadUrlParameterTable();
497
+ if (!program || !table) {
498
+ return forward();
499
+ }
500
+ const slot = resolveClassifiedSlotAtPosition({ program, table, fileName, position });
501
+ if (!slot) {
502
+ return forward();
503
+ }
504
+ const declaredIn = resolveEntryProvenance({ slot, position, cache, fileName, entryName });
505
+ return declaredIn.length === 0 ? forward() : provenancePanel(entryName, declaredIn);
506
+ };
16
507
  proxy.getSemanticDiagnostics = (fileName) => {
17
508
  const prior = base.getSemanticDiagnostics(fileName);
18
509
  const program = base.getProgram();
@@ -22,6 +513,38 @@ function init(modules) {
22
513
  const transpiler = getProgramDiagnostics(program, program.getSourceFile(fileName)).map((diagnostic) => ({ ...diagnostic, category: ts.DiagnosticCategory.Suggestion }));
23
514
  return [...prior, ...transpiler];
24
515
  };
516
+ proxy.getCompletionsAtPosition = (fileName, position, options, formattingSettings) => {
517
+ const prior = base.getCompletionsAtPosition(fileName, position, options, formattingSettings);
518
+ const program = base.getProgram();
519
+ const table = loadUrlParameterTable();
520
+ if (!program || !table) {
521
+ return prior;
522
+ }
523
+ const slot = resolveClassifiedSlotAtPosition({ program, table, fileName, position });
524
+ if (!slot) {
525
+ return prior;
526
+ }
527
+ if (!cache) {
528
+ return prior;
529
+ }
530
+ const baseEntries = prior?.entries ?? [];
531
+ const entries = isAddressClass2(slot.class) ? [
532
+ ...componentEntries(slot, position, cache, baseEntries),
533
+ ...objectPathEntries(slot, position, cache, baseEntries)
534
+ ] : 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
+ if (entries.length === 0) {
536
+ return prior;
537
+ }
538
+ if (!prior) {
539
+ return {
540
+ isGlobalCompletion: false,
541
+ isMemberCompletion: false,
542
+ isNewIdentifierLocation: false,
543
+ entries
544
+ };
545
+ }
546
+ return { ...prior, entries: [...prior.entries, ...entries] };
547
+ };
25
548
  return proxy;
26
549
  }
27
550
  return { create };
@@ -0,0 +1,19 @@
1
+ import type { ClassifiedSlot } from "@defold-typescript/transpiler";
2
+ import type ts from "typescript";
3
+ export declare const CONTRIBUTED_ENTRY_KIND: ts.ScriptElementKind;
4
+ export declare const DEFOLD_COMPLETION_SOURCE = "defold-typescript/scene";
5
+ export declare function buildSceneCompletionEntries(input: {
6
+ slot: ClassifiedSlot;
7
+ ids: ReadonlySet<string>;
8
+ baseEntries: readonly ts.CompletionEntry[];
9
+ }): ts.CompletionEntry[];
10
+ export declare function buildAddressPathCompletionEntries(input: {
11
+ slot: ClassifiedSlot;
12
+ paths: ReadonlySet<string>;
13
+ baseEntries: readonly ts.CompletionEntry[];
14
+ }): ts.CompletionEntry[];
15
+ export declare function buildWholeLiteralCompletionEntries(input: {
16
+ slot: ClassifiedSlot;
17
+ ids: ReadonlySet<string>;
18
+ baseEntries: readonly ts.CompletionEntry[];
19
+ }): ts.CompletionEntry[];
@@ -0,0 +1,18 @@
1
+ export interface SceneReadHost {
2
+ readDirectory?(path: string, extensions?: readonly string[]): string[];
3
+ readFile(path: string): string | undefined;
4
+ }
5
+ export declare const SCENE_EXTENSIONS: string[];
6
+ export declare const GUI_EXTENSIONS: string[];
7
+ export declare const INPUT_BINDING_EXTENSIONS: string[];
8
+ export declare const ANIMATION_ASSET_EXTENSIONS: string[];
9
+ export declare const PROJECT_EXTENSIONS: string[];
10
+ export declare const GAME_PROJECT_DOCUMENT = "game.project";
11
+ export declare function displayPathOf(projectRoot: string, filePath: string): string;
12
+ export declare function isExcludedProjectPath(displayPath: string): boolean;
13
+ export declare function readSceneDocuments(host: SceneReadHost, projectRoot: string, extensions?: readonly string[]): {
14
+ documents: Map<string, string>;
15
+ unreadable: string[];
16
+ paths: string[];
17
+ };
18
+ export declare function listProjectResourcePaths(host: SceneReadHost, projectRoot: string, extensions: readonly string[]): Set<string>;
@@ -0,0 +1,15 @@
1
+ import type * as ts from "typescript";
2
+ import { readSceneDocuments, type SceneReadHost } from "./scene-documents";
3
+ export interface SceneWatchHost extends SceneReadHost {
4
+ watchFile?(path: string, callback: ts.FileWatcherCallback, pollingInterval?: number): ts.FileWatcher;
5
+ watchDirectory?(path: string, callback: ts.DirectoryWatcherCallback, recursive?: boolean): ts.FileWatcher;
6
+ }
7
+ export interface SceneIndexCache {
8
+ readonly host: SceneWatchHost;
9
+ readonly projectRoot: string;
10
+ documents(extensions?: readonly string[]): ReturnType<typeof readSceneDocuments>;
11
+ resourcePaths(extensions: readonly string[]): Set<string>;
12
+ derived<T>(key: string, compute: () => T): T;
13
+ dispose(): void;
14
+ }
15
+ export declare function createSceneIndexCache(host: SceneWatchHost, projectRoot: string): SceneIndexCache;
@@ -0,0 +1,9 @@
1
+ import { type ClassifiedSlot } from "@defold-typescript/transpiler";
2
+ import type { SceneIndexCache } from "./scene-index-cache";
3
+ export declare function resolveEntryProvenance(input: {
4
+ slot: ClassifiedSlot;
5
+ position: number;
6
+ cache: SceneIndexCache;
7
+ fileName: string;
8
+ entryName: string;
9
+ }): readonly string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defold-typescript/tstl-plugin",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "description": "TypeScript language-service plugin surfacing Defold transpiler diagnostics live in the editor.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,7 +35,8 @@
35
35
  "test": "bun test"
36
36
  },
37
37
  "dependencies": {
38
- "@defold-typescript/transpiler": "0.23.0"
38
+ "@defold-typescript/transpiler": "0.25.0",
39
+ "@defold-typescript/types": "0.25.0"
39
40
  },
40
41
  "peerDependencies": {
41
42
  "typescript": ">=5.0.0 <7"
@@ -0,0 +1,24 @@
1
+ import { type BuildConfig, DEFAULT_INCLUDE, parseBuildConfig } from "@defold-typescript/transpiler";
2
+ import type { SceneReadHost } from "./scene-documents";
3
+
4
+ // Alongside output is what the build itself does with no `outDir`, so it is also
5
+ // the honest answer when the config cannot be read: a wrong guess would offer
6
+ // completions for a resource no scene names, and a throw here would take the
7
+ // editor's own completions down with it.
8
+ const ALONGSIDE: BuildConfig = { outDir: undefined, include: DEFAULT_INCLUDE };
9
+
10
+ // The project's `tsconfig.json` as the build reads it, fetched through the
11
+ // editor host rather than the filesystem so the plugin sees the same unsaved
12
+ // state the editor does.
13
+ export function readBuildConfigFromHost(host: SceneReadHost, projectRoot: string): BuildConfig {
14
+ const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
15
+ const text = host.readFile(`${root}/tsconfig.json`);
16
+ if (text === undefined) {
17
+ return ALONGSIDE;
18
+ }
19
+ try {
20
+ return parseBuildConfig(text);
21
+ } catch {
22
+ return ALONGSIDE;
23
+ }
24
+ }