@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/src/scene-provenance.ts
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ANIMATION_ASSET_EXTENSIONS,
|
|
3
|
+
buildComponentAnimationIndex,
|
|
2
4
|
buildConfigKeyIndex,
|
|
5
|
+
buildGuiFlipbookIndex,
|
|
3
6
|
buildGuiNodeIndex,
|
|
4
7
|
buildInputActionIndex,
|
|
5
8
|
buildSceneComponentIndex,
|
|
6
9
|
buildSceneObjectPathIndex,
|
|
7
|
-
buildSpriteAnimationIndex,
|
|
8
10
|
type ClassifiedSlot,
|
|
9
11
|
componentIdOfSameObjectAddress,
|
|
10
12
|
computeOutputRel,
|
|
11
|
-
isAddressClass,
|
|
12
|
-
isFragmentCaret,
|
|
13
|
-
} from "@defold-typescript/transpiler";
|
|
14
|
-
import { readBuildConfigFromHost } from "./build-config";
|
|
15
|
-
import {
|
|
16
|
-
ANIMATION_ASSET_EXTENSIONS,
|
|
17
13
|
displayPathOf,
|
|
18
14
|
GAME_PROJECT_DOCUMENT,
|
|
19
15
|
GUI_EXTENSIONS,
|
|
20
16
|
INPUT_BINDING_EXTENSIONS,
|
|
17
|
+
isAddressClass,
|
|
18
|
+
isFragmentCaret,
|
|
21
19
|
PROJECT_EXTENSIONS,
|
|
22
|
-
} from "
|
|
23
|
-
import
|
|
20
|
+
} from "@defold-typescript/transpiler";
|
|
21
|
+
import { readBuildConfigFromHost } from "./build-config";
|
|
22
|
+
import { type SceneIndexCache, sceneCollectionRolesOf } from "./scene-index-cache";
|
|
23
|
+
import { relativeUniverseFor } from "./scene-relative-addresses";
|
|
24
24
|
|
|
25
25
|
// Keys of their own under the same cache the completion indexes use, so a
|
|
26
26
|
// provenance map is thrown away with every index it was derived beside — and
|
|
@@ -29,7 +29,8 @@ const COMPONENT_PROVENANCE = "provenance:component-ids";
|
|
|
29
29
|
const ACTION_PROVENANCE = "provenance:input-actions";
|
|
30
30
|
const GUI_PROVENANCE = "provenance:gui-nodes";
|
|
31
31
|
const PATH_PROVENANCE = "provenance:object-paths";
|
|
32
|
-
const ANIMATION_PROVENANCE = "provenance:
|
|
32
|
+
const ANIMATION_PROVENANCE = "provenance:component-animations";
|
|
33
|
+
const GUI_ANIMATION_PROVENANCE = "provenance:gui-flipbook";
|
|
33
34
|
|
|
34
35
|
type Provenance = ReadonlyMap<string, readonly string[]>;
|
|
35
36
|
|
|
@@ -125,16 +126,48 @@ function nodeProvenance(cache: SceneIndexCache, fileName: string): Provenance {
|
|
|
125
126
|
function pathProvenance(cache: SceneIndexCache): Provenance {
|
|
126
127
|
return cache.derived(
|
|
127
128
|
PATH_PROVENANCE,
|
|
128
|
-
() =>
|
|
129
|
+
() =>
|
|
130
|
+
buildSceneObjectPathIndex(cache.documents().documents, sceneCollectionRolesOf(cache))
|
|
131
|
+
.declaredIn,
|
|
129
132
|
);
|
|
130
133
|
}
|
|
131
134
|
|
|
132
|
-
// Scoped exactly the way the
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
|
|
135
|
+
// Scoped exactly the way the gui flipbook completion is: to the `.gui` scene
|
|
136
|
+
// naming this file's generated gui script. The index already records which
|
|
137
|
+
// named texture declared each id, so the answer is read straight off it — and
|
|
138
|
+
// an id no named texture carries is answered with silence rather than the
|
|
139
|
+
// scene that failed to declare it.
|
|
140
|
+
function guiAnimationProvenance(input: {
|
|
141
|
+
cache: SceneIndexCache;
|
|
142
|
+
fileName: string;
|
|
143
|
+
entryName: string;
|
|
144
|
+
}): readonly string[] {
|
|
145
|
+
const { cache, fileName, entryName } = input;
|
|
146
|
+
const index = cache.derived(GUI_ANIMATION_PROVENANCE, () =>
|
|
147
|
+
buildGuiFlipbookIndex({
|
|
148
|
+
scenes: cache.documents(GUI_EXTENSIONS).documents,
|
|
149
|
+
assets: cache.documents(ANIMATION_ASSET_EXTENSIONS).documents,
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
const config = readBuildConfigFromHost(cache.host, cache.projectRoot);
|
|
153
|
+
const resource = computeOutputRel(
|
|
154
|
+
displayPathOf(cache.projectRoot, fileName),
|
|
155
|
+
config,
|
|
156
|
+
"gui-script",
|
|
157
|
+
);
|
|
158
|
+
const declarers = index.byScriptResource.get(resource)?.get(entryName);
|
|
159
|
+
return declarers === undefined ? [] : [...declarers].sort();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Scoped exactly the way the animation completion is: to the sprite or model
|
|
163
|
+
// component the slot's sibling literal addresses on the one game object owning
|
|
164
|
+
// this script, resolved through the same helper so the two cannot disagree
|
|
165
|
+
// about which component is addressed. The declaring document — an atlas for a
|
|
166
|
+
// sprite, and for a model the animation set that lists the entry producing the
|
|
167
|
+
// id — is reported only for an id it really declares: the per-id map's key set
|
|
168
|
+
// *is* the component's id set, so a miss there already means this component
|
|
169
|
+
// does not carry the name, and a panel naming a file that does not carry it is
|
|
170
|
+
// the fabricated answer this surface exists to avoid.
|
|
138
171
|
function animationProvenance(input: {
|
|
139
172
|
slot: ClassifiedSlot;
|
|
140
173
|
cache: SceneIndexCache;
|
|
@@ -142,19 +175,21 @@ function animationProvenance(input: {
|
|
|
142
175
|
entryName: string;
|
|
143
176
|
}): readonly string[] {
|
|
144
177
|
const { slot, cache, fileName, entryName } = input;
|
|
178
|
+
if (slot.nodeParameter !== undefined) {
|
|
179
|
+
return guiAnimationProvenance({ cache, fileName, entryName });
|
|
180
|
+
}
|
|
145
181
|
const component = componentIdOfSameObjectAddress(slot.addressText ?? "");
|
|
146
182
|
if (component === undefined) return [];
|
|
147
183
|
const index = cache.derived(ANIMATION_PROVENANCE, () =>
|
|
148
|
-
|
|
184
|
+
buildComponentAnimationIndex({
|
|
149
185
|
scenes: cache.documents().documents,
|
|
150
186
|
assets: cache.documents(ANIMATION_ASSET_EXTENSIONS).documents,
|
|
151
187
|
}),
|
|
152
188
|
);
|
|
153
189
|
const config = readBuildConfigFromHost(cache.host, cache.projectRoot);
|
|
154
190
|
const resource = computeOutputRel(displayPathOf(cache.projectRoot, fileName), config, "script");
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
return index.byScriptResource.get(resource)?.get(component)?.has(entryName) ? [tileSet] : [];
|
|
191
|
+
const source = index.sourceByScriptResource.get(resource)?.get(component)?.get(entryName);
|
|
192
|
+
return source === undefined ? [] : [source];
|
|
158
193
|
}
|
|
159
194
|
|
|
160
195
|
// A config key is declared by the one file that answers a reader at runtime, and
|
|
@@ -177,6 +212,37 @@ function constantProvenance(
|
|
|
177
212
|
return cache.resourcePaths(extensions).has(entryName) ? [entryName.replace(/^\//, "")] : [];
|
|
178
213
|
}
|
|
179
214
|
|
|
215
|
+
/**
|
|
216
|
+
* The sentence a relative entry's panel carries, or `undefined` where the entry
|
|
217
|
+
* is not one — a fragment caret, a slot that names no address, or a name this
|
|
218
|
+
* file's relative universe does not offer.
|
|
219
|
+
*
|
|
220
|
+
* A relative address is valid from the object hosting the script and nowhere
|
|
221
|
+
* else, so the objects it resolves from *are* its provenance: naming a declaring
|
|
222
|
+
* file instead would answer a question the author is not asking. Where the entry
|
|
223
|
+
* resolves from only some of the contexts this script runs in, the others are
|
|
224
|
+
* named too — a sometimes-valid entry that read as an always-valid one is the
|
|
225
|
+
* wrong-suggestion class this panel exists to close.
|
|
226
|
+
*/
|
|
227
|
+
export function resolveRelativeEntryProvenance(input: {
|
|
228
|
+
slot: ClassifiedSlot;
|
|
229
|
+
position: number;
|
|
230
|
+
cache: SceneIndexCache;
|
|
231
|
+
fileName: string;
|
|
232
|
+
entryName: string;
|
|
233
|
+
}): string | undefined {
|
|
234
|
+
const { slot, position, cache, fileName, entryName } = input;
|
|
235
|
+
if (!isAddressClass(slot.class) || isFragmentCaret(slot, position)) return undefined;
|
|
236
|
+
const universe = relativeUniverseFor(cache, fileName);
|
|
237
|
+
const resolvesFrom = universe.contextsByEntry.get(entryName);
|
|
238
|
+
if (resolvesFrom === undefined) return undefined;
|
|
239
|
+
const others = universe.contexts
|
|
240
|
+
.map((context) => context.object)
|
|
241
|
+
.filter((object) => !resolvesFrom.includes(object));
|
|
242
|
+
const relative = `Relative to ${resolvesFrom.join(", ")}`;
|
|
243
|
+
return others.length === 0 ? relative : `${relative}; not from ${others.join(", ")}`;
|
|
244
|
+
}
|
|
245
|
+
|
|
180
246
|
// The display paths of the project files that declare `entryName` in the
|
|
181
247
|
// universe this slot draws from, sorted, empty when the project does not name
|
|
182
248
|
// one exactly. Every kind the completion path offers is answered here; silence
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildSceneObjectPathIndex,
|
|
3
|
+
buildScriptNamingContexts,
|
|
4
|
+
computeOutputRel,
|
|
5
|
+
displayPathOf,
|
|
6
|
+
GUI_EXTENSIONS,
|
|
7
|
+
guiScriptResourcesOf,
|
|
8
|
+
type NamingContext,
|
|
9
|
+
relativeAddressesFrom,
|
|
10
|
+
type SceneObjectPathIndex,
|
|
11
|
+
} from "@defold-typescript/transpiler";
|
|
12
|
+
import { readBuildConfigFromHost } from "./build-config";
|
|
13
|
+
import { type SceneIndexCache, sceneCollectionRolesOf } from "./scene-index-cache";
|
|
14
|
+
|
|
15
|
+
// The relative forms the file being edited may write, and the objects each one
|
|
16
|
+
// resolves from. Unlike every other completion universe this is per-file by
|
|
17
|
+
// construction: a relative address continues the collection path of the object
|
|
18
|
+
// hosting the script, so it means one thing from there and nothing anywhere
|
|
19
|
+
// else.
|
|
20
|
+
export interface RelativeUniverse {
|
|
21
|
+
/** Every object hosting this file's generated script, sorted by object path. */
|
|
22
|
+
readonly contexts: readonly NamingContext[];
|
|
23
|
+
/** Relative game-object paths, valid from at least one context. */
|
|
24
|
+
readonly paths: ReadonlySet<string>;
|
|
25
|
+
/** Those paths joined to the components their object owns. */
|
|
26
|
+
readonly addresses: ReadonlySet<string>;
|
|
27
|
+
/** Entry name -> the sorted object paths it resolves from. */
|
|
28
|
+
readonly contextsByEntry: ReadonlyMap<string, readonly string[]>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const CONTEXTS = "relative:naming-contexts";
|
|
32
|
+
|
|
33
|
+
interface ProjectContexts {
|
|
34
|
+
readonly index: SceneObjectPathIndex;
|
|
35
|
+
readonly byScriptResource: ReadonlyMap<string, readonly NamingContext[]>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function projectContexts(cache: SceneIndexCache): ProjectContexts {
|
|
39
|
+
return cache.derived(CONTEXTS, () => {
|
|
40
|
+
const index = buildSceneObjectPathIndex(
|
|
41
|
+
cache.documents().documents,
|
|
42
|
+
sceneCollectionRolesOf(cache),
|
|
43
|
+
);
|
|
44
|
+
// A `.gui` names the gui script it drives, and that indirection is the only
|
|
45
|
+
// way a gui script reaches an object: the edge is read through the same
|
|
46
|
+
// shared builder the CLI's walk uses rather than a second reader.
|
|
47
|
+
const guiScripts = guiScriptResourcesOf(cache.documents(GUI_EXTENSIONS).documents);
|
|
48
|
+
return { index, byScriptResource: buildScriptNamingContexts(index, guiScripts) };
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Every game-object path the project declares and the components each owns —
|
|
54
|
+
* the index a check scopes an absolute `#fragment` address against.
|
|
55
|
+
*
|
|
56
|
+
* Read off the walk `projectContexts` already caches rather than built again:
|
|
57
|
+
* the naming contexts are derived *from* this index, so the reachability path
|
|
58
|
+
* costs no second `.go`/`.collection` parse.
|
|
59
|
+
*/
|
|
60
|
+
export function projectObjectIndex(cache: SceneIndexCache): SceneObjectPathIndex {
|
|
61
|
+
return projectContexts(cache).index;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The relative universe the file at `fileName` may write from, keyed per file
|
|
66
|
+
* under the same cache the absolute indexes live in.
|
|
67
|
+
*
|
|
68
|
+
* The file is mapped *forward* to the script resource a scene would name — the
|
|
69
|
+
* same direction `nodeEntries` takes, because an output path cannot say which
|
|
70
|
+
* include base produced it. Both script kinds are resolved and their contexts
|
|
71
|
+
* unioned: a `.ts` is named as a `.script` by a game object and as a
|
|
72
|
+
* `.gui_script` by a `.gui`, and nothing in the file itself says which.
|
|
73
|
+
*/
|
|
74
|
+
export function relativeUniverseFor(cache: SceneIndexCache, fileName: string): RelativeUniverse {
|
|
75
|
+
return cache.derived(`relative-universe:${fileName}`, () => {
|
|
76
|
+
const { index, byScriptResource } = projectContexts(cache);
|
|
77
|
+
const config = readBuildConfigFromHost(cache.host, cache.projectRoot);
|
|
78
|
+
const rel = displayPathOf(cache.projectRoot, fileName);
|
|
79
|
+
|
|
80
|
+
const byObject = new Map<string, NamingContext>();
|
|
81
|
+
for (const kind of ["script", "gui-script"] as const) {
|
|
82
|
+
for (const context of byScriptResource.get(computeOutputRel(rel, config, kind)) ?? []) {
|
|
83
|
+
byObject.set(context.object, context);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const contexts = [...byObject.values()].sort((a, b) => a.object.localeCompare(b.object));
|
|
87
|
+
|
|
88
|
+
const paths = new Set<string>();
|
|
89
|
+
const addresses = new Set<string>();
|
|
90
|
+
const contextsByEntry = new Map<string, string[]>();
|
|
91
|
+
for (const context of contexts) {
|
|
92
|
+
const relative = relativeAddressesFrom(index, context);
|
|
93
|
+
for (const entry of relative.paths) {
|
|
94
|
+
paths.add(entry);
|
|
95
|
+
contextsByEntry.set(entry, [...(contextsByEntry.get(entry) ?? []), context.object]);
|
|
96
|
+
}
|
|
97
|
+
for (const entry of relative.addresses) {
|
|
98
|
+
addresses.add(entry);
|
|
99
|
+
contextsByEntry.set(entry, [...(contextsByEntry.get(entry) ?? []), context.object]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
for (const objects of contextsByEntry.values()) objects.sort();
|
|
103
|
+
|
|
104
|
+
return { contexts, paths, addresses, contextsByEntry };
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The worlds the file's script runs in, as `checkCrossWorldAddresses` asks for
|
|
110
|
+
* them: the de-duplicated sockets of every naming context hosting it, with the
|
|
111
|
+
* bootstrap world carried as `undefined`.
|
|
112
|
+
*
|
|
113
|
+
* The same answer `scriptWorldsForBuild` gives the CLI, reached through the
|
|
114
|
+
* chain that resolver was modelled on — so an address the build calls foreign is
|
|
115
|
+
* foreign in the editor too. An empty answer is the honest unknown: a script no
|
|
116
|
+
* scene hosts runs in no world this project can prove, and the check reports
|
|
117
|
+
* nothing for it.
|
|
118
|
+
*/
|
|
119
|
+
export function worldsFor(
|
|
120
|
+
cache: SceneIndexCache,
|
|
121
|
+
fileName: string,
|
|
122
|
+
): readonly (string | undefined)[] {
|
|
123
|
+
const sockets = new Set<string | undefined>();
|
|
124
|
+
for (const context of relativeUniverseFor(cache, fileName).contexts) {
|
|
125
|
+
sockets.add(context.socket);
|
|
126
|
+
}
|
|
127
|
+
return [...sockets];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Whether a bare `/path` can resolve from where this file runs. A bare address
|
|
132
|
+
* continues the caller's own world, so one offered to a script running behind a
|
|
133
|
+
* proxy names an object that world cannot reach.
|
|
134
|
+
*
|
|
135
|
+
* A file with no naming context offers them: nothing is known about where it
|
|
136
|
+
* runs, and withholding every bare path would delete the completions the
|
|
137
|
+
* absolute universe has always provided. Socket-qualified keys are never
|
|
138
|
+
* touched — whether a slot accepts a foreign socket is a different question.
|
|
139
|
+
*/
|
|
140
|
+
export function offersBareWorld(universe: RelativeUniverse): boolean {
|
|
141
|
+
return (
|
|
142
|
+
universe.contexts.length === 0 ||
|
|
143
|
+
universe.contexts.some((context) => context.socket === undefined)
|
|
144
|
+
);
|
|
145
|
+
}
|
|
@@ -1,18 +0,0 @@
|
|
|
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>;
|
package/src/scene-documents.ts
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { isDefignoredPath } from "@defold-typescript/transpiler";
|
|
2
|
-
|
|
3
|
-
// The narrow slice of `ts.server.ServerHost` this needs: the real host
|
|
4
|
-
// satisfies it structurally, and a test fake is two methods rather than a whole
|
|
5
|
-
// server. `readDirectory` is optional because a host may not provide it, and a
|
|
6
|
-
// plugin that throws takes the editor's completions down with it.
|
|
7
|
-
export interface SceneReadHost {
|
|
8
|
-
readDirectory?(path: string, extensions?: readonly string[]): string[];
|
|
9
|
-
readFile(path: string): string | undefined;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const SCENE_EXTENSIONS = [".go", ".collection"];
|
|
13
|
-
|
|
14
|
-
// The extension sets more than one reader asks the host for. They live here
|
|
15
|
-
// rather than beside either caller because the index cache serves exactly the
|
|
16
|
-
// sets it is handed: a second definition that drifted would walk — and watch —
|
|
17
|
-
// a universe the other reader never invalidates.
|
|
18
|
-
export const GUI_EXTENSIONS = [".gui"];
|
|
19
|
-
export const INPUT_BINDING_EXTENSIONS = [".input_binding"];
|
|
20
|
-
|
|
21
|
-
// The documents an animation id is declared by: an atlas or tile source names
|
|
22
|
-
// the ids, and a `.sprite` is the hop a referenced component takes to reach one.
|
|
23
|
-
export const ANIMATION_ASSET_EXTENSIONS = [".atlas", ".tilesource", ".sprite"];
|
|
24
|
-
|
|
25
|
-
// The walk returns every `.project` the project holds, so the lookup is by
|
|
26
|
-
// display path — a vendored `*.project` declares keys this project's readers
|
|
27
|
-
// cannot resolve.
|
|
28
|
-
export const PROJECT_EXTENSIONS = [".project"];
|
|
29
|
-
export const GAME_PROJECT_DOCUMENT = "game.project";
|
|
30
|
-
|
|
31
|
-
// The project-relative name a file is keyed by — the same name the editor
|
|
32
|
-
// reports for a program file, so a scene's claim on a script can be looked up
|
|
33
|
-
// against the file being edited.
|
|
34
|
-
export function displayPathOf(projectRoot: string, filePath: string): string {
|
|
35
|
-
const path = filePath.replace(/\\/g, "/");
|
|
36
|
-
const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
37
|
-
return path.startsWith(`${root}/`) ? path.slice(root.length + 1) : path;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// What both walks exclude is what Defold does not load as a project resource:
|
|
41
|
-
// `build/` output, plus the directories the scaffolded `.defignore` names
|
|
42
|
-
// (`isDefignoredPath`, root-anchored because a `.defignore` line is a
|
|
43
|
-
// root-relative path).
|
|
44
|
-
//
|
|
45
|
-
// Bob writes `_generated_*.go` copies of the project's scenes under `build/`;
|
|
46
|
-
// reading them would report every id twice, against files no author edits. The
|
|
47
|
-
// segment test runs on the project-relative path, so a project that itself
|
|
48
|
-
// lives under a directory named `build` is not excluded wholesale — a different
|
|
49
|
-
// rule from `.defignore`'s, because bob writes those copies below whatever
|
|
50
|
-
// depth it is pointed at.
|
|
51
|
-
function isBuildOutput(displayPath: string): boolean {
|
|
52
|
-
return displayPath.split("/").includes("build");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// The single rule, exported because the index cache filters watch events with
|
|
56
|
-
// it: a copy in the watcher could drift from what the walks exclude, and an
|
|
57
|
-
// event the walk would have ignored is exactly the event that must not throw the
|
|
58
|
-
// cache away — bob writes `_generated_*.go` throughout a build.
|
|
59
|
-
export function isExcludedProjectPath(displayPath: string): boolean {
|
|
60
|
-
return isBuildOutput(displayPath) || isDefignoredPath(displayPath);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Read the project's own scene sources through the editor host — by default the
|
|
64
|
-
// `.go`/`.collection` set the component-id universe is built from, or whatever
|
|
65
|
-
// `extensions` names instead. The two universes stay disjoint: a `.gui` declares
|
|
66
|
-
// node ids, not component ids, so folding it into the default would feed gui
|
|
67
|
-
// text to `buildSceneComponentIndex`. Keys are project-relative display paths,
|
|
68
|
-
// matching what `buildSceneComponentIndex` reports in `incomplete`, and every
|
|
69
|
-
// file that could not be read is named in `unreadable` — a universe with a
|
|
70
|
-
// silent hole cannot be reasoned about. `paths` names the host paths actually
|
|
71
|
-
// read — what a watcher must be registered on, which cannot be reconstructed
|
|
72
|
-
// from a display path for a file outside the project root.
|
|
73
|
-
export function readSceneDocuments(
|
|
74
|
-
host: SceneReadHost,
|
|
75
|
-
projectRoot: string,
|
|
76
|
-
extensions: readonly string[] = SCENE_EXTENSIONS,
|
|
77
|
-
): { documents: Map<string, string>; unreadable: string[]; paths: string[] } {
|
|
78
|
-
const documents = new Map<string, string>();
|
|
79
|
-
const unreadable: string[] = [];
|
|
80
|
-
const paths: string[] = [];
|
|
81
|
-
|
|
82
|
-
if (!host.readDirectory) {
|
|
83
|
-
unreadable.push("the editor host cannot enumerate project files, so no scene source was read");
|
|
84
|
-
return { documents, unreadable, paths };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
for (const filePath of host.readDirectory(projectRoot, extensions)) {
|
|
88
|
-
const displayPath = displayPathOf(projectRoot, filePath);
|
|
89
|
-
if (isExcludedProjectPath(displayPath)) continue;
|
|
90
|
-
const text = host.readFile(filePath);
|
|
91
|
-
if (text === undefined) {
|
|
92
|
-
unreadable.push(`${displayPath}: could not be read`);
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
documents.set(displayPath, text);
|
|
96
|
-
paths.push(filePath);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return { documents, unreadable, paths };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// The project's own files of the given kinds, as the `/`-prefixed
|
|
103
|
-
// project-relative paths a Defold resource property names. Nothing is read: a
|
|
104
|
-
// resource path is the whole suggestion, so the walk is the entire universe.
|
|
105
|
-
//
|
|
106
|
-
// The extension filter is re-applied rather than trusted. `readSceneDocuments`
|
|
107
|
-
// can take the host at its word because a wrong document merely contributes no
|
|
108
|
-
// ids, but here a wrong file *is* the suggestion — a host that ignored its
|
|
109
|
-
// `extensions` argument would turn a `.font` slot into a project-wide file dump.
|
|
110
|
-
export function listProjectResourcePaths(
|
|
111
|
-
host: SceneReadHost,
|
|
112
|
-
projectRoot: string,
|
|
113
|
-
extensions: readonly string[],
|
|
114
|
-
): Set<string> {
|
|
115
|
-
if (!host.readDirectory) return new Set();
|
|
116
|
-
|
|
117
|
-
const paths: string[] = [];
|
|
118
|
-
for (const filePath of host.readDirectory(projectRoot, extensions)) {
|
|
119
|
-
const displayPath = displayPathOf(projectRoot, filePath);
|
|
120
|
-
if (isExcludedProjectPath(displayPath)) continue;
|
|
121
|
-
if (!extensions.some((extension) => displayPath.endsWith(extension))) continue;
|
|
122
|
-
paths.push(`/${displayPath}`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return new Set(paths.sort());
|
|
126
|
-
}
|