@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.
- package/dist/build-config.d.ts +1 -2
- package/dist/completion-harness.d.ts +49 -0
- package/dist/index.cjs +6 -0
- package/dist/index.js +249 -103
- package/dist/scene-diagnostics.d.ts +29 -0
- package/dist/scene-index-cache.d.ts +3 -1
- package/dist/scene-provenance.d.ts +20 -1
- package/dist/scene-relative-addresses.d.ts +55 -0
- package/package.json +8 -6
- package/src/build-config.ts +6 -2
- package/src/index.ts +103 -49
- package/src/require-entry.cjs +6 -0
- package/src/scene-diagnostics.ts +92 -0
- package/src/scene-index-cache.ts +50 -2
- package/src/scene-provenance.ts +87 -21
- package/src/scene-relative-addresses.ts +145 -0
- package/dist/scene-documents.d.ts +0 -18
- package/src/scene-documents.ts +0 -126
package/dist/build-config.d.ts
CHANGED
|
@@ -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
|
-
|
|
9
|
-
buildSceneObjectPathIndex as buildSceneObjectPathIndex2,
|
|
10
|
-
buildSpriteAnimationIndex as buildSpriteAnimationIndex2,
|
|
11
|
+
buildSceneObjectPathIndex as buildSceneObjectPathIndex3,
|
|
11
12
|
componentIdOfSameObjectAddress as componentIdOfSameObjectAddress2,
|
|
12
|
-
computeOutputRel as
|
|
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 {
|
|
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-
|
|
82
|
-
import {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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:
|
|
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) =>
|
|
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(
|
|
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 =
|
|
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, () =>
|
|
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, () =>
|
|
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 =
|
|
337
|
-
const
|
|
338
|
-
|
|
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(
|
|
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) ? [
|
|
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:
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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(
|
|
543
|
+
const index = cache.derived("gui-nodes", () => buildGuiNodeIndex2(cache.documents(GUI_EXTENSIONS3).documents));
|
|
415
544
|
const config = readBuildConfigFromHost(cache.host, projectRoot);
|
|
416
|
-
const resource =
|
|
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
|
|
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(
|
|
567
|
+
assets: cache.documents(ANIMATION_ASSET_EXTENSIONS2).documents
|
|
429
568
|
}));
|
|
430
569
|
const config = readBuildConfigFromHost(cache.host, projectRoot);
|
|
431
|
-
const resource =
|
|
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(
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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
|
|
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;
|