@defold-typescript/transpiler 0.24.0 → 0.26.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/config-key-index.d.ts +1 -0
- package/dist/editor-script-erasure.d.ts +2 -1
- package/dist/gui-node-index.d.ts +5 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +915 -27
- package/dist/input-action-index.d.ts +1 -0
- package/dist/output-paths.d.ts +11 -0
- package/dist/project-resources.d.ts +2 -0
- package/dist/scene-component-index.d.ts +5 -0
- package/dist/scene-object-path-index.d.ts +6 -0
- package/dist/scene-text-format.d.ts +9 -0
- package/dist/sprite-animation-index.d.ts +10 -0
- package/dist/url-address-slots.d.ts +19 -0
- package/dist/url-fragment-reachability.d.ts +23 -0
- package/package.json +2 -2
- package/src/config-key-index.ts +34 -0
- package/src/editor-script-erasure.ts +44 -13
- package/src/gui-node-index.ts +71 -0
- package/src/index.ts +33 -0
- package/src/input-action-index.ts +41 -0
- package/src/output-paths.ts +69 -0
- package/src/project-resources.ts +20 -0
- package/src/scene-component-index.ts +80 -0
- package/src/scene-object-path-index.ts +148 -0
- package/src/scene-text-format.ts +171 -0
- package/src/sprite-animation-index.ts +241 -0
- package/src/transpile.ts +5 -0
- package/src/url-address-slots.ts +290 -0
- package/src/url-fragment-reachability.ts +175 -0
package/dist/index.js
CHANGED
|
@@ -1,3 +1,257 @@
|
|
|
1
|
+
// src/config-key-index.ts
|
|
2
|
+
function sectionNameOf(line) {
|
|
3
|
+
const match = line.trim().match(/^\[(.+)\]$/);
|
|
4
|
+
return match?.[1];
|
|
5
|
+
}
|
|
6
|
+
function buildConfigKeyIndex(gameProjectText) {
|
|
7
|
+
const keys = new Set;
|
|
8
|
+
let section;
|
|
9
|
+
for (const line of gameProjectText.split(`
|
|
10
|
+
`)) {
|
|
11
|
+
const header = sectionNameOf(line);
|
|
12
|
+
if (header !== undefined) {
|
|
13
|
+
section = header;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (section === undefined)
|
|
17
|
+
continue;
|
|
18
|
+
const separator = line.indexOf("=");
|
|
19
|
+
if (separator === -1)
|
|
20
|
+
continue;
|
|
21
|
+
const key = line.slice(0, separator).trim();
|
|
22
|
+
if (key === "")
|
|
23
|
+
continue;
|
|
24
|
+
keys.add(`${section}.${key}`);
|
|
25
|
+
}
|
|
26
|
+
return keys;
|
|
27
|
+
}
|
|
28
|
+
// src/scene-text-format.ts
|
|
29
|
+
class SceneTextFormatError extends Error {
|
|
30
|
+
line;
|
|
31
|
+
constructor(message, line) {
|
|
32
|
+
super(`${message} (line ${line})`);
|
|
33
|
+
this.name = "SceneTextFormatError";
|
|
34
|
+
this.line = line;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
var IDENTIFIER_PART = /[A-Za-z0-9_]/;
|
|
38
|
+
function append(target, key, value) {
|
|
39
|
+
const existing = target.get(key);
|
|
40
|
+
if (existing === undefined) {
|
|
41
|
+
target.set(key, [value]);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
existing.push(value);
|
|
45
|
+
}
|
|
46
|
+
function parseSceneTextFormat(text) {
|
|
47
|
+
let pos = 0;
|
|
48
|
+
let line = 1;
|
|
49
|
+
function fail(message, at) {
|
|
50
|
+
throw new SceneTextFormatError(message, at);
|
|
51
|
+
}
|
|
52
|
+
function advance() {
|
|
53
|
+
const ch = text.charAt(pos);
|
|
54
|
+
pos += 1;
|
|
55
|
+
if (ch === `
|
|
56
|
+
`)
|
|
57
|
+
line += 1;
|
|
58
|
+
return ch;
|
|
59
|
+
}
|
|
60
|
+
function skipWhitespace() {
|
|
61
|
+
while (pos < text.length) {
|
|
62
|
+
const ch = text.charAt(pos);
|
|
63
|
+
if (ch !== " " && ch !== "\t" && ch !== "\r" && ch !== `
|
|
64
|
+
`)
|
|
65
|
+
return;
|
|
66
|
+
advance();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function skipInlineWhitespace() {
|
|
70
|
+
while (pos < text.length) {
|
|
71
|
+
const ch = text.charAt(pos);
|
|
72
|
+
if (ch !== " " && ch !== "\t")
|
|
73
|
+
return;
|
|
74
|
+
pos += 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function readIdentifier(at) {
|
|
78
|
+
const start = pos;
|
|
79
|
+
while (pos < text.length && IDENTIFIER_PART.test(text.charAt(pos))) {
|
|
80
|
+
pos += 1;
|
|
81
|
+
}
|
|
82
|
+
if (pos === start) {
|
|
83
|
+
fail(`unexpected \`${text.charAt(start)}\``, at);
|
|
84
|
+
}
|
|
85
|
+
return text.slice(start, pos);
|
|
86
|
+
}
|
|
87
|
+
function readQuotedScalar() {
|
|
88
|
+
let value = "";
|
|
89
|
+
for (;; ) {
|
|
90
|
+
const openedAt = line;
|
|
91
|
+
advance();
|
|
92
|
+
let closed = false;
|
|
93
|
+
while (pos < text.length) {
|
|
94
|
+
const ch = advance();
|
|
95
|
+
if (ch === "\\") {
|
|
96
|
+
if (pos >= text.length)
|
|
97
|
+
break;
|
|
98
|
+
const escaped = advance();
|
|
99
|
+
if (escaped === '"')
|
|
100
|
+
value += '"';
|
|
101
|
+
else if (escaped === "\\")
|
|
102
|
+
value += "\\";
|
|
103
|
+
else if (escaped === "n")
|
|
104
|
+
value += `
|
|
105
|
+
`;
|
|
106
|
+
else if (escaped === "r")
|
|
107
|
+
value += "\r";
|
|
108
|
+
else if (escaped === "t")
|
|
109
|
+
value += "\t";
|
|
110
|
+
else
|
|
111
|
+
value += `\\${escaped}`;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (ch === '"') {
|
|
115
|
+
closed = true;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
if (ch === `
|
|
119
|
+
`)
|
|
120
|
+
break;
|
|
121
|
+
value += ch;
|
|
122
|
+
}
|
|
123
|
+
if (!closed) {
|
|
124
|
+
fail("unterminated string literal", openedAt);
|
|
125
|
+
}
|
|
126
|
+
const resumePos = pos;
|
|
127
|
+
const resumeLine = line;
|
|
128
|
+
skipWhitespace();
|
|
129
|
+
if (text.charAt(pos) === '"') {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
pos = resumePos;
|
|
133
|
+
line = resumeLine;
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function readUnquotedScalar() {
|
|
138
|
+
const newline = text.indexOf(`
|
|
139
|
+
`, pos);
|
|
140
|
+
const end = newline === -1 ? text.length : newline;
|
|
141
|
+
const raw = text.slice(pos, end);
|
|
142
|
+
pos = end;
|
|
143
|
+
return raw.trim();
|
|
144
|
+
}
|
|
145
|
+
function parseBody(nested, openedAt) {
|
|
146
|
+
const fields = new Map;
|
|
147
|
+
const messages = new Map;
|
|
148
|
+
for (;; ) {
|
|
149
|
+
skipWhitespace();
|
|
150
|
+
if (pos >= text.length) {
|
|
151
|
+
if (nested) {
|
|
152
|
+
fail("unclosed `{`", openedAt);
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
if (text.charAt(pos) === "}") {
|
|
157
|
+
if (!nested) {
|
|
158
|
+
fail("unexpected `}`", line);
|
|
159
|
+
}
|
|
160
|
+
advance();
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
const keyLine = line;
|
|
164
|
+
const key = readIdentifier(keyLine);
|
|
165
|
+
skipInlineWhitespace();
|
|
166
|
+
const delimiter = text.charAt(pos);
|
|
167
|
+
if (delimiter === "{") {
|
|
168
|
+
const braceLine = line;
|
|
169
|
+
advance();
|
|
170
|
+
append(messages, key, parseBody(true, braceLine));
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (delimiter === ":") {
|
|
174
|
+
advance();
|
|
175
|
+
skipInlineWhitespace();
|
|
176
|
+
append(fields, key, text.charAt(pos) === '"' ? readQuotedScalar() : readUnquotedScalar());
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
fail(`expected \`:\` or \`{\` after \`${key}\``, keyLine);
|
|
180
|
+
}
|
|
181
|
+
return { fields, messages };
|
|
182
|
+
}
|
|
183
|
+
return parseBody(false, 1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/gui-node-index.ts
|
|
187
|
+
var SCRIPT_SUFFIX = ".gui_script";
|
|
188
|
+
function scriptKeyOf(script) {
|
|
189
|
+
if (!script.endsWith(SCRIPT_SUFFIX))
|
|
190
|
+
return;
|
|
191
|
+
const key = script.startsWith("/") ? script.slice(1) : script;
|
|
192
|
+
return key === SCRIPT_SUFFIX ? undefined : key;
|
|
193
|
+
}
|
|
194
|
+
function buildGuiNodeIndex(documents) {
|
|
195
|
+
const byScriptResource = new Map;
|
|
196
|
+
const claimedBy = new Map;
|
|
197
|
+
const unresolved = [];
|
|
198
|
+
for (const [displayPath, text] of documents) {
|
|
199
|
+
let document;
|
|
200
|
+
try {
|
|
201
|
+
document = parseSceneTextFormat(text);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (!(error instanceof SceneTextFormatError))
|
|
204
|
+
throw error;
|
|
205
|
+
unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const [script] = document.fields.get("script") ?? [];
|
|
209
|
+
if (script === undefined)
|
|
210
|
+
continue;
|
|
211
|
+
const key = scriptKeyOf(script);
|
|
212
|
+
if (key === undefined)
|
|
213
|
+
continue;
|
|
214
|
+
const owner = claimedBy.get(key);
|
|
215
|
+
if (owner !== undefined) {
|
|
216
|
+
byScriptResource.delete(key);
|
|
217
|
+
unresolved.push(`${key}: claimed by both ${owner} and ${displayPath}, so its node ids are ambiguous`);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
claimedBy.set(key, displayPath);
|
|
221
|
+
const ids = new Set;
|
|
222
|
+
for (const message of document.messages.get("nodes") ?? []) {
|
|
223
|
+
for (const id of message.fields.get("id") ?? []) {
|
|
224
|
+
if (id !== "")
|
|
225
|
+
ids.add(id);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
byScriptResource.set(key, ids);
|
|
229
|
+
}
|
|
230
|
+
return { byScriptResource, unresolved };
|
|
231
|
+
}
|
|
232
|
+
// src/input-action-index.ts
|
|
233
|
+
function buildInputActionIndex(documents) {
|
|
234
|
+
const actions = new Set;
|
|
235
|
+
for (const text of documents.values()) {
|
|
236
|
+
let document;
|
|
237
|
+
try {
|
|
238
|
+
document = parseSceneTextFormat(text);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (!(error instanceof SceneTextFormatError))
|
|
241
|
+
throw error;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
for (const triggers of document.messages.values()) {
|
|
245
|
+
for (const trigger of triggers) {
|
|
246
|
+
for (const action of trigger.fields.get("action") ?? []) {
|
|
247
|
+
if (action !== "")
|
|
248
|
+
actions.add(action);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return actions;
|
|
254
|
+
}
|
|
1
255
|
// src/main-entry-factory-imports.ts
|
|
2
256
|
import * as ts3 from "typescript";
|
|
3
257
|
|
|
@@ -361,6 +615,46 @@ function findMainEntryFactoryImports(fileName, source) {
|
|
|
361
615
|
}
|
|
362
616
|
return factories;
|
|
363
617
|
}
|
|
618
|
+
// src/output-paths.ts
|
|
619
|
+
import * as path from "node:path";
|
|
620
|
+
var DEFAULT_INCLUDE = ["src/**/*.ts"];
|
|
621
|
+
var SCRIPT_SUFFIX_BY_KIND = {
|
|
622
|
+
script: ".ts.script",
|
|
623
|
+
"gui-script": ".ts.gui_script",
|
|
624
|
+
"render-script": ".ts.render_script",
|
|
625
|
+
"editor-script": ".ts.editor_script"
|
|
626
|
+
};
|
|
627
|
+
function parseBuildConfig(raw) {
|
|
628
|
+
const tsconfig = JSON.parse(raw);
|
|
629
|
+
const outDir = tsconfig.compilerOptions?.outDir;
|
|
630
|
+
const include = tsconfig.include?.length ? tsconfig.include : DEFAULT_INCLUDE;
|
|
631
|
+
return { outDir, include };
|
|
632
|
+
}
|
|
633
|
+
function stripIncludeBase(pattern) {
|
|
634
|
+
const firstWildcard = pattern.search(/[*?[]/);
|
|
635
|
+
if (firstWildcard === -1) {
|
|
636
|
+
return pattern.endsWith("/") ? pattern : `${path.posix.dirname(pattern)}/`;
|
|
637
|
+
}
|
|
638
|
+
const upToWildcard = pattern.slice(0, firstWildcard);
|
|
639
|
+
const lastSlash = upToWildcard.lastIndexOf("/");
|
|
640
|
+
return lastSlash === -1 ? "" : upToWildcard.slice(0, lastSlash + 1);
|
|
641
|
+
}
|
|
642
|
+
function relUnderOutDir(rel, config) {
|
|
643
|
+
const { outDir, include } = config;
|
|
644
|
+
if (outDir === undefined || outDir === "" || outDir === ".") {
|
|
645
|
+
return rel;
|
|
646
|
+
}
|
|
647
|
+
const includeBase = include.map(stripIncludeBase).filter((base) => rel.startsWith(base)).sort((a, b) => b.length - a.length)[0] ?? "";
|
|
648
|
+
const relUnderBase = rel.slice(includeBase.length);
|
|
649
|
+
return path.posix.join(outDir, relUnderBase);
|
|
650
|
+
}
|
|
651
|
+
function computeOutputRel(rel, config, kind) {
|
|
652
|
+
const baseRel = relUnderOutDir(rel, config);
|
|
653
|
+
if (kind === "module") {
|
|
654
|
+
return baseRel.replace(/\.ts$/, ".lua");
|
|
655
|
+
}
|
|
656
|
+
return baseRel.replace(/\.ts$/, SCRIPT_SUFFIX_BY_KIND[kind]);
|
|
657
|
+
}
|
|
364
658
|
// src/program-diagnostics.ts
|
|
365
659
|
import * as ts7 from "typescript";
|
|
366
660
|
import * as tstl from "typescript-to-lua";
|
|
@@ -532,10 +826,155 @@ function getProgramDiagnostics(program, sourceFile) {
|
|
|
532
826
|
});
|
|
533
827
|
return diagnostics.filter((d) => d.file !== undefined && !isAmbient(d.file.fileName));
|
|
534
828
|
}
|
|
829
|
+
// src/project-resources.ts
|
|
830
|
+
var SCAFFOLDED_DEFIGNORE_LINES = ["/node_modules", "/.defold-types", "/.vscode"];
|
|
831
|
+
function isDefignoredPath(displayPath) {
|
|
832
|
+
const rooted = `/${displayPath.replace(/^\/+/, "")}`;
|
|
833
|
+
return SCAFFOLDED_DEFIGNORE_LINES.some((line) => rooted === line || rooted.startsWith(`${line}/`));
|
|
834
|
+
}
|
|
835
|
+
// src/scene-component-index.ts
|
|
836
|
+
var COMPONENT_BLOCKS = new Set(["components", "embedded_components"]);
|
|
837
|
+
var EMBEDDING_BLOCKS = new Set(["embedded_instances", "embedded_components"]);
|
|
838
|
+
function collect(message, blockName, depth, displayPath, ids, incomplete) {
|
|
839
|
+
if (COMPONENT_BLOCKS.has(blockName)) {
|
|
840
|
+
for (const id of message.fields.get("id") ?? []) {
|
|
841
|
+
ids.add(id);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
if (EMBEDDING_BLOCKS.has(blockName)) {
|
|
845
|
+
for (const payload of message.fields.get("data") ?? []) {
|
|
846
|
+
try {
|
|
847
|
+
collect(parseSceneTextFormat(payload), "", depth + 1, displayPath, ids, incomplete);
|
|
848
|
+
} catch (error) {
|
|
849
|
+
if (!(error instanceof SceneTextFormatError))
|
|
850
|
+
throw error;
|
|
851
|
+
incomplete.push(`${displayPath}: embedded payload at depth ${depth + 1} could not be parsed (${error.message})`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
for (const [name, nested] of message.messages) {
|
|
856
|
+
for (const child of nested) {
|
|
857
|
+
collect(child, name, depth, displayPath, ids, incomplete);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
function buildSceneComponentIndex(documents) {
|
|
862
|
+
const ids = new Set;
|
|
863
|
+
const incomplete = [];
|
|
864
|
+
if (documents.size === 0) {
|
|
865
|
+
incomplete.push("no scene sources were read, so no component id can be proven absent");
|
|
866
|
+
return { ids, incomplete };
|
|
867
|
+
}
|
|
868
|
+
for (const [displayPath, text] of documents) {
|
|
869
|
+
try {
|
|
870
|
+
collect(parseSceneTextFormat(text), "", 0, displayPath, ids, incomplete);
|
|
871
|
+
} catch (error) {
|
|
872
|
+
if (!(error instanceof SceneTextFormatError))
|
|
873
|
+
throw error;
|
|
874
|
+
incomplete.push(`${displayPath}: could not be parsed (${error.message})`);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return { ids, incomplete };
|
|
878
|
+
}
|
|
879
|
+
// src/scene-object-path-index.ts
|
|
880
|
+
var LEAF_BLOCKS = ["instances", "embedded_instances"];
|
|
881
|
+
function resourceKey(resource) {
|
|
882
|
+
return resource.startsWith("/") ? resource.slice(1) : resource;
|
|
883
|
+
}
|
|
884
|
+
function firstField(message, name) {
|
|
885
|
+
return (message.fields.get(name) ?? [])[0];
|
|
886
|
+
}
|
|
887
|
+
function childrenOf(message, name) {
|
|
888
|
+
return message.messages.get(name) ?? [];
|
|
889
|
+
}
|
|
890
|
+
function buildSceneObjectPathIndex(documents) {
|
|
891
|
+
const incomplete = [];
|
|
892
|
+
if (documents.size === 0) {
|
|
893
|
+
incomplete.push("no scene sources were read, so no game-object path can be proven absent");
|
|
894
|
+
return { paths: new Set, declaredIn: new Map, incomplete };
|
|
895
|
+
}
|
|
896
|
+
const parsed = new Map;
|
|
897
|
+
for (const [displayPath, text] of documents) {
|
|
898
|
+
try {
|
|
899
|
+
parsed.set(displayPath, parseSceneTextFormat(text));
|
|
900
|
+
} catch (error) {
|
|
901
|
+
if (!(error instanceof SceneTextFormatError))
|
|
902
|
+
throw error;
|
|
903
|
+
incomplete.push(`${displayPath}: could not be parsed (${error.message})`);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
const instanced = new Set;
|
|
907
|
+
for (const document of parsed.values()) {
|
|
908
|
+
for (const block of childrenOf(document, "collection_instances")) {
|
|
909
|
+
const collection = firstField(block, "collection");
|
|
910
|
+
if (collection !== undefined)
|
|
911
|
+
instanced.add(resourceKey(collection));
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
const composed = new Map;
|
|
915
|
+
const inProgress = new Set;
|
|
916
|
+
function pathsOf(displayPath) {
|
|
917
|
+
const done = composed.get(displayPath);
|
|
918
|
+
if (done !== undefined)
|
|
919
|
+
return done;
|
|
920
|
+
if (inProgress.has(displayPath)) {
|
|
921
|
+
incomplete.push(`${displayPath}: is instanced inside itself through a cycle of collection references, so its paths cannot be composed`);
|
|
922
|
+
return new Map;
|
|
923
|
+
}
|
|
924
|
+
const document = parsed.get(displayPath);
|
|
925
|
+
if (document === undefined)
|
|
926
|
+
return new Map;
|
|
927
|
+
inProgress.add(displayPath);
|
|
928
|
+
const paths2 = new Map;
|
|
929
|
+
for (const blockName of LEAF_BLOCKS) {
|
|
930
|
+
for (const block of childrenOf(document, blockName)) {
|
|
931
|
+
const id = firstField(block, "id");
|
|
932
|
+
if (id !== undefined && id !== "")
|
|
933
|
+
paths2.set(`/${id}`, displayPath);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
for (const block of childrenOf(document, "collection_instances")) {
|
|
937
|
+
const id = firstField(block, "id");
|
|
938
|
+
const collection = firstField(block, "collection");
|
|
939
|
+
if (id === undefined || id === "" || collection === undefined)
|
|
940
|
+
continue;
|
|
941
|
+
const key = resourceKey(collection);
|
|
942
|
+
if (!parsed.has(key)) {
|
|
943
|
+
incomplete.push(`${displayPath}: the collection instance "${id}" names ${collection}, which is not among the project's readable scene documents`);
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
for (const [nested, declarer] of pathsOf(key)) {
|
|
947
|
+
paths2.set(`/${id}${nested}`, declarer);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
inProgress.delete(displayPath);
|
|
951
|
+
composed.set(displayPath, paths2);
|
|
952
|
+
return paths2;
|
|
953
|
+
}
|
|
954
|
+
const paths = new Set;
|
|
955
|
+
const declaredIn = new Map;
|
|
956
|
+
for (const displayPath of parsed.keys()) {
|
|
957
|
+
const own = pathsOf(displayPath);
|
|
958
|
+
if (instanced.has(displayPath))
|
|
959
|
+
continue;
|
|
960
|
+
for (const [path2, declarer] of own) {
|
|
961
|
+
paths.add(path2);
|
|
962
|
+
const declarers = declaredIn.get(path2);
|
|
963
|
+
if (declarers === undefined) {
|
|
964
|
+
declaredIn.set(path2, [declarer]);
|
|
965
|
+
} else if (!declarers.includes(declarer)) {
|
|
966
|
+
declarers.push(declarer);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
for (const declarers of declaredIn.values())
|
|
971
|
+
declarers.sort();
|
|
972
|
+
return { paths, declaredIn, incomplete };
|
|
973
|
+
}
|
|
535
974
|
// src/session.ts
|
|
536
975
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
537
976
|
import { createRequire as createRequire2 } from "node:module";
|
|
538
|
-
import * as
|
|
977
|
+
import * as path3 from "node:path";
|
|
539
978
|
import * as ts10 from "typescript";
|
|
540
979
|
import * as tstl3 from "typescript-to-lua";
|
|
541
980
|
|
|
@@ -545,30 +984,39 @@ import {
|
|
|
545
984
|
createReturnStatement as createReturnStatement2
|
|
546
985
|
} from "typescript-to-lua";
|
|
547
986
|
var EDITOR_FACTORY_MODULE = "@defold-typescript/types";
|
|
548
|
-
var
|
|
549
|
-
var
|
|
550
|
-
|
|
987
|
+
var EDITOR_SCRIPT_FACTORY_NAME = "defineEditorScript";
|
|
988
|
+
var EDITOR_COMMAND_FACTORY_NAME = "defineEditorCommand";
|
|
989
|
+
var EDITOR_FACTORY_NAMES = new Set([
|
|
990
|
+
EDITOR_SCRIPT_FACTORY_NAME,
|
|
991
|
+
EDITOR_COMMAND_FACTORY_NAME
|
|
992
|
+
]);
|
|
993
|
+
var EDITOR_FACTORY_SPECIFIERS = new Set([
|
|
994
|
+
EDITOR_FACTORY_MODULE,
|
|
995
|
+
`${EDITOR_FACTORY_MODULE}/editor-script`
|
|
996
|
+
]);
|
|
997
|
+
function resolveEditorFactoryExport(callee, checker) {
|
|
551
998
|
let symbol = checker.getSymbolAtLocation(callee);
|
|
552
999
|
if (symbol === undefined) {
|
|
553
|
-
return
|
|
1000
|
+
return;
|
|
554
1001
|
}
|
|
555
1002
|
if (symbol.flags & ts8.SymbolFlags.Alias) {
|
|
556
1003
|
symbol = checker.getAliasedSymbol(symbol);
|
|
557
1004
|
}
|
|
558
|
-
|
|
559
|
-
|
|
1005
|
+
const name = symbol.getName();
|
|
1006
|
+
if (!EDITOR_FACTORY_NAMES.has(name)) {
|
|
1007
|
+
return;
|
|
560
1008
|
}
|
|
561
1009
|
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
562
1010
|
if (declaration === undefined) {
|
|
563
|
-
return
|
|
1011
|
+
return;
|
|
564
1012
|
}
|
|
565
|
-
return declaration.getSourceFile().fileName.includes(EDITOR_FACTORY_MODULE);
|
|
1013
|
+
return declaration.getSourceFile().fileName.includes(EDITOR_FACTORY_MODULE) ? name : undefined;
|
|
566
1014
|
}
|
|
567
1015
|
function lowerEditorFactoryCall(expression, context) {
|
|
568
1016
|
if (!ts8.isCallExpression(expression)) {
|
|
569
1017
|
return;
|
|
570
1018
|
}
|
|
571
|
-
if (
|
|
1019
|
+
if (resolveEditorFactoryExport(expression.expression, context.checker) !== EDITOR_SCRIPT_FACTORY_NAME) {
|
|
572
1020
|
return;
|
|
573
1021
|
}
|
|
574
1022
|
const module = expression.arguments[0];
|
|
@@ -596,7 +1044,7 @@ function isEditorFactoryOnlyImport(node) {
|
|
|
596
1044
|
if (runtime.length === 0) {
|
|
597
1045
|
return false;
|
|
598
1046
|
}
|
|
599
|
-
return runtime.every((element) => (element.propertyName ?? element.name).text
|
|
1047
|
+
return runtime.every((element) => EDITOR_FACTORY_NAMES.has((element.propertyName ?? element.name).text));
|
|
600
1048
|
}
|
|
601
1049
|
var editorScriptErasurePlugin = {
|
|
602
1050
|
visitors: {
|
|
@@ -609,6 +1057,13 @@ var editorScriptErasurePlugin = {
|
|
|
609
1057
|
}
|
|
610
1058
|
return context.superTransformStatements(node);
|
|
611
1059
|
},
|
|
1060
|
+
[ts8.SyntaxKind.CallExpression]: (node, context) => {
|
|
1061
|
+
const command = node.arguments[0];
|
|
1062
|
+
if (command !== undefined && resolveEditorFactoryExport(node.expression, context.checker) === EDITOR_COMMAND_FACTORY_NAME) {
|
|
1063
|
+
return context.transformExpression(command);
|
|
1064
|
+
}
|
|
1065
|
+
return context.superTransformExpression(node);
|
|
1066
|
+
},
|
|
612
1067
|
[ts8.SyntaxKind.ImportDeclaration]: (node, context) => isEditorFactoryOnlyImport(node) ? [] : context.superTransformStatements(node)
|
|
613
1068
|
}
|
|
614
1069
|
};
|
|
@@ -616,7 +1071,7 @@ var editorScriptErasurePlugin = {
|
|
|
616
1071
|
// src/transpile.ts
|
|
617
1072
|
import { readdirSync, readFileSync } from "node:fs";
|
|
618
1073
|
import { createRequire } from "node:module";
|
|
619
|
-
import * as
|
|
1074
|
+
import * as path2 from "node:path";
|
|
620
1075
|
import * as tstl2 from "typescript-to-lua";
|
|
621
1076
|
|
|
622
1077
|
// src/go-property-direct-call.ts
|
|
@@ -642,12 +1097,12 @@ function flattenDiagnosticMessage(text) {
|
|
|
642
1097
|
return flattenDiagnosticMessage(text.messageText);
|
|
643
1098
|
}
|
|
644
1099
|
var requireFromHere = createRequire(import.meta.url);
|
|
645
|
-
var TYPES_PKG_ROOT =
|
|
646
|
-
var TSTL_LANG_EXT_ROOT =
|
|
1100
|
+
var TYPES_PKG_ROOT = path2.dirname(requireFromHere.resolve("@defold-typescript/types/package.json"));
|
|
1101
|
+
var TSTL_LANG_EXT_ROOT = path2.dirname(requireFromHere.resolve("@typescript-to-lua/language-extensions/package.json"));
|
|
647
1102
|
function readAmbient(rel) {
|
|
648
|
-
return readFileSync(
|
|
1103
|
+
return readFileSync(path2.join(TYPES_PKG_ROOT, rel), "utf8");
|
|
649
1104
|
}
|
|
650
|
-
var LUA_TYPES_ROOT =
|
|
1105
|
+
var LUA_TYPES_ROOT = path2.dirname(createRequire(path2.join(TYPES_PKG_ROOT, "package.json")).resolve("lua-types/package.json"));
|
|
651
1106
|
var LUA_TYPES_ENTRY_FILES = ["5.1.d.ts", "special/jit-only.d.ts"];
|
|
652
1107
|
var REFERENCE_PATH_RE = /\/\/\/\s*<reference\s+path="([^"]+)"\s*\/>/g;
|
|
653
1108
|
function collectLuaTypesClosure() {
|
|
@@ -656,14 +1111,14 @@ function collectLuaTypesClosure() {
|
|
|
656
1111
|
const key = `node_modules/lua-types/${rel}`;
|
|
657
1112
|
if (key in files)
|
|
658
1113
|
return;
|
|
659
|
-
const source = readFileSync(
|
|
1114
|
+
const source = readFileSync(path2.join(LUA_TYPES_ROOT, rel), "utf8");
|
|
660
1115
|
files[key] = source;
|
|
661
|
-
const dir =
|
|
1116
|
+
const dir = path2.posix.dirname(rel);
|
|
662
1117
|
for (const match of source.matchAll(REFERENCE_PATH_RE)) {
|
|
663
1118
|
const ref = match[1];
|
|
664
1119
|
if (!ref)
|
|
665
1120
|
continue;
|
|
666
|
-
visit(
|
|
1121
|
+
visit(path2.posix.normalize(path2.posix.join(dir, ref)));
|
|
667
1122
|
}
|
|
668
1123
|
};
|
|
669
1124
|
for (const entry of LUA_TYPES_ENTRY_FILES)
|
|
@@ -672,7 +1127,7 @@ function collectLuaTypesClosure() {
|
|
|
672
1127
|
}
|
|
673
1128
|
function buildAmbientFiles() {
|
|
674
1129
|
const files = {
|
|
675
|
-
"node_modules/@typescript-to-lua/language-extensions/index.d.ts": readFileSync(
|
|
1130
|
+
"node_modules/@typescript-to-lua/language-extensions/index.d.ts": readFileSync(path2.join(TSTL_LANG_EXT_ROOT, "index.d.ts"), "utf8"),
|
|
676
1131
|
"node_modules/@defold-typescript/types/src/core-types.ts": readAmbient("src/core-types.ts"),
|
|
677
1132
|
"node_modules/@defold-typescript/types/src/engine-globals.d.ts": readAmbient("src/engine-globals.d.ts"),
|
|
678
1133
|
"node_modules/@defold-typescript/types/src/go-overloads.d.ts": readAmbient("src/go-overloads.d.ts"),
|
|
@@ -700,15 +1155,21 @@ export type { ScriptProperties, ScriptProperty } from "./src/lifecycle.js";
|
|
|
700
1155
|
`,
|
|
701
1156
|
"node_modules/@defold-typescript/types/render-script.d.ts": `export { defineRenderScript } from "./src/lifecycle.js";
|
|
702
1157
|
export type { ScriptProperties, ScriptProperty } from "./src/lifecycle.js";
|
|
703
|
-
|
|
1158
|
+
`,
|
|
1159
|
+
"node_modules/@defold-typescript/types/editor-script.d.ts": [
|
|
1160
|
+
'export { defineEditorCommand, defineEditorScript } from "./src/editor.js";',
|
|
1161
|
+
'export type { EditorCommandQuery, EditorNode } from "./src/editor.js";',
|
|
1162
|
+
""
|
|
1163
|
+
].join(`
|
|
1164
|
+
`)
|
|
704
1165
|
};
|
|
705
1166
|
Object.assign(files, collectLuaTypesClosure());
|
|
706
|
-
for (const entry of readdirSync(
|
|
1167
|
+
for (const entry of readdirSync(path2.join(TYPES_PKG_ROOT, "generated"))) {
|
|
707
1168
|
if (entry.endsWith(".d.ts")) {
|
|
708
1169
|
files[`node_modules/@defold-typescript/types/generated/${entry}`] = readAmbient(`generated/${entry}`);
|
|
709
1170
|
}
|
|
710
1171
|
}
|
|
711
|
-
for (const entry of readdirSync(
|
|
1172
|
+
for (const entry of readdirSync(path2.join(TYPES_PKG_ROOT, "src"))) {
|
|
712
1173
|
if (entry.endsWith(".d.ts")) {
|
|
713
1174
|
files[`node_modules/@defold-typescript/types/src/${entry}`] = readAmbient(`src/${entry}`);
|
|
714
1175
|
}
|
|
@@ -868,12 +1329,12 @@ function createTranspileSession() {
|
|
|
868
1329
|
}
|
|
869
1330
|
let filePath;
|
|
870
1331
|
if (fileName.startsWith("lib.")) {
|
|
871
|
-
const typeScriptDir =
|
|
872
|
-
filePath =
|
|
1332
|
+
const typeScriptDir = path3.dirname(requireFromHere2.resolve("typescript"));
|
|
1333
|
+
filePath = path3.join(typeScriptDir, fileName);
|
|
873
1334
|
}
|
|
874
1335
|
if (fileName.includes("language-extensions")) {
|
|
875
1336
|
const dtsName = fileName.replace(/(\.d)?(\.ts)$/, ".d.ts");
|
|
876
|
-
filePath =
|
|
1337
|
+
filePath = path3.resolve(dtsName);
|
|
877
1338
|
}
|
|
878
1339
|
if (filePath !== undefined) {
|
|
879
1340
|
const cached = libCache.get(fileName);
|
|
@@ -935,10 +1396,437 @@ function createTranspileSession() {
|
|
|
935
1396
|
getProgram: () => program
|
|
936
1397
|
};
|
|
937
1398
|
}
|
|
1399
|
+
// src/sprite-animation-index.ts
|
|
1400
|
+
function resourceKey2(resource) {
|
|
1401
|
+
return resource.startsWith("/") ? resource.slice(1) : resource;
|
|
1402
|
+
}
|
|
1403
|
+
function normalizeDocumentKey(path4) {
|
|
1404
|
+
return path4.replaceAll("\\", "/");
|
|
1405
|
+
}
|
|
1406
|
+
var SCRIPT_SUFFIX2 = ".script";
|
|
1407
|
+
var SPRITE_SUFFIX = ".sprite";
|
|
1408
|
+
function firstField2(message, name) {
|
|
1409
|
+
return (message.fields.get(name) ?? [])[0];
|
|
1410
|
+
}
|
|
1411
|
+
function childrenOf2(message, name) {
|
|
1412
|
+
return message.messages.get(name) ?? [];
|
|
1413
|
+
}
|
|
1414
|
+
function declaredAnimations(document) {
|
|
1415
|
+
const ids = new Set;
|
|
1416
|
+
for (const animation of childrenOf2(document, "animations")) {
|
|
1417
|
+
for (const id of animation.fields.get("id") ?? []) {
|
|
1418
|
+
if (id !== "")
|
|
1419
|
+
ids.add(id);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
return ids;
|
|
1423
|
+
}
|
|
1424
|
+
function readAssets(assets, unresolved) {
|
|
1425
|
+
const animationsByTileSet = new Map;
|
|
1426
|
+
const tileSetBySprite = new Map;
|
|
1427
|
+
for (const [path4, text] of assets) {
|
|
1428
|
+
const displayPath = normalizeDocumentKey(path4);
|
|
1429
|
+
let document;
|
|
1430
|
+
try {
|
|
1431
|
+
document = parseSceneTextFormat(text);
|
|
1432
|
+
} catch (error) {
|
|
1433
|
+
if (!(error instanceof SceneTextFormatError))
|
|
1434
|
+
throw error;
|
|
1435
|
+
unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
if (displayPath.endsWith(SPRITE_SUFFIX)) {
|
|
1439
|
+
const tileSet = firstField2(document, "tile_set");
|
|
1440
|
+
if (tileSet !== undefined)
|
|
1441
|
+
tileSetBySprite.set(displayPath, resourceKey2(tileSet));
|
|
1442
|
+
continue;
|
|
1443
|
+
}
|
|
1444
|
+
animationsByTileSet.set(displayPath, declaredAnimations(document));
|
|
1445
|
+
}
|
|
1446
|
+
return { animationsByTileSet, tileSetBySprite };
|
|
1447
|
+
}
|
|
1448
|
+
function spriteTileSets(object, displayPath, assets, unresolved) {
|
|
1449
|
+
const tileSets = new Map;
|
|
1450
|
+
for (const embedded of childrenOf2(object, "embedded_components")) {
|
|
1451
|
+
if (firstField2(embedded, "type") !== "sprite")
|
|
1452
|
+
continue;
|
|
1453
|
+
const id = firstField2(embedded, "id");
|
|
1454
|
+
const payload = firstField2(embedded, "data");
|
|
1455
|
+
if (id === undefined || payload === undefined)
|
|
1456
|
+
continue;
|
|
1457
|
+
try {
|
|
1458
|
+
const tileSet = firstField2(parseSceneTextFormat(payload), "tile_set");
|
|
1459
|
+
if (tileSet !== undefined)
|
|
1460
|
+
tileSets.set(id, resourceKey2(tileSet));
|
|
1461
|
+
} catch (error) {
|
|
1462
|
+
if (!(error instanceof SceneTextFormatError))
|
|
1463
|
+
throw error;
|
|
1464
|
+
unresolved.push(`${displayPath}: the embedded sprite "${id}" could not be read (${error.message})`);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
for (const referenced of childrenOf2(object, "components")) {
|
|
1468
|
+
const component = firstField2(referenced, "component");
|
|
1469
|
+
const id = firstField2(referenced, "id");
|
|
1470
|
+
if (id === undefined || component === undefined || !component.endsWith(SPRITE_SUFFIX))
|
|
1471
|
+
continue;
|
|
1472
|
+
const tileSet = assets.tileSetBySprite.get(resourceKey2(component));
|
|
1473
|
+
if (tileSet === undefined) {
|
|
1474
|
+
unresolved.push(`${displayPath}: the sprite component "${id}" names ${component}, which is not among the project's sprite documents`);
|
|
1475
|
+
continue;
|
|
1476
|
+
}
|
|
1477
|
+
tileSets.set(id, tileSet);
|
|
1478
|
+
}
|
|
1479
|
+
return tileSets;
|
|
1480
|
+
}
|
|
1481
|
+
function scriptResourcesOf(object) {
|
|
1482
|
+
const resources = [];
|
|
1483
|
+
for (const component of childrenOf2(object, "components")) {
|
|
1484
|
+
const resource = firstField2(component, "component");
|
|
1485
|
+
if (resource === undefined || !resource.endsWith(SCRIPT_SUFFIX2))
|
|
1486
|
+
continue;
|
|
1487
|
+
const key = resourceKey2(resource);
|
|
1488
|
+
if (key !== SCRIPT_SUFFIX2.slice(1))
|
|
1489
|
+
resources.push(key);
|
|
1490
|
+
}
|
|
1491
|
+
return resources;
|
|
1492
|
+
}
|
|
1493
|
+
function isGameObject(message) {
|
|
1494
|
+
return message.messages.has("components") || message.messages.has("embedded_components");
|
|
1495
|
+
}
|
|
1496
|
+
function buildSpriteAnimationIndex(input) {
|
|
1497
|
+
const unresolved = [];
|
|
1498
|
+
const assets = readAssets(input.assets, unresolved);
|
|
1499
|
+
const byScriptResource = new Map;
|
|
1500
|
+
const tileSetByScriptResource = new Map;
|
|
1501
|
+
const claimedBy = new Map;
|
|
1502
|
+
function walk(message, blockName, displayPath) {
|
|
1503
|
+
if (isGameObject(message)) {
|
|
1504
|
+
claim(message, displayPath);
|
|
1505
|
+
}
|
|
1506
|
+
if (blockName === "embedded_instances") {
|
|
1507
|
+
for (const payload of message.fields.get("data") ?? []) {
|
|
1508
|
+
try {
|
|
1509
|
+
walk(parseSceneTextFormat(payload), "", displayPath);
|
|
1510
|
+
} catch (error) {
|
|
1511
|
+
if (!(error instanceof SceneTextFormatError))
|
|
1512
|
+
throw error;
|
|
1513
|
+
unresolved.push(`${displayPath}: an embedded game object could not be read (${error.message})`);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
for (const [name, nested] of message.messages) {
|
|
1518
|
+
for (const child of nested) {
|
|
1519
|
+
walk(child, name, displayPath);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
function claim(object, displayPath) {
|
|
1524
|
+
const scripts = scriptResourcesOf(object);
|
|
1525
|
+
if (scripts.length === 0)
|
|
1526
|
+
return;
|
|
1527
|
+
const tileSets = spriteTileSets(object, displayPath, assets, unresolved);
|
|
1528
|
+
const animations = new Map;
|
|
1529
|
+
const declaringTileSets = new Map;
|
|
1530
|
+
for (const [id, tileSet] of tileSets) {
|
|
1531
|
+
const declared = assets.animationsByTileSet.get(tileSet);
|
|
1532
|
+
if (declared === undefined) {
|
|
1533
|
+
unresolved.push(`${displayPath}: the sprite component "${id}" names the tile source /${tileSet}, which is not among the project's asset documents`);
|
|
1534
|
+
continue;
|
|
1535
|
+
}
|
|
1536
|
+
animations.set(id, declared);
|
|
1537
|
+
declaringTileSets.set(id, tileSet);
|
|
1538
|
+
}
|
|
1539
|
+
for (const key of scripts) {
|
|
1540
|
+
const owner = claimedBy.get(key);
|
|
1541
|
+
if (owner !== undefined) {
|
|
1542
|
+
byScriptResource.delete(key);
|
|
1543
|
+
tileSetByScriptResource.delete(key);
|
|
1544
|
+
unresolved.push(`${key}: claimed by both ${owner} and ${displayPath}, so its sprite animations are ambiguous`);
|
|
1545
|
+
continue;
|
|
1546
|
+
}
|
|
1547
|
+
claimedBy.set(key, displayPath);
|
|
1548
|
+
byScriptResource.set(key, animations);
|
|
1549
|
+
tileSetByScriptResource.set(key, declaringTileSets);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
for (const [path4, text] of input.scenes) {
|
|
1553
|
+
const displayPath = normalizeDocumentKey(path4);
|
|
1554
|
+
try {
|
|
1555
|
+
walk(parseSceneTextFormat(text), "", displayPath);
|
|
1556
|
+
} catch (error) {
|
|
1557
|
+
if (!(error instanceof SceneTextFormatError))
|
|
1558
|
+
throw error;
|
|
1559
|
+
unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
return { byScriptResource, tileSetByScriptResource, unresolved };
|
|
1563
|
+
}
|
|
1564
|
+
function componentIdOfSameObjectAddress(address) {
|
|
1565
|
+
if (!address.startsWith("#"))
|
|
1566
|
+
return;
|
|
1567
|
+
const id = address.slice(1);
|
|
1568
|
+
if (id === "" || id.includes("/") || id.includes("#"))
|
|
1569
|
+
return;
|
|
1570
|
+
return id;
|
|
1571
|
+
}
|
|
1572
|
+
// src/url-address-slots.ts
|
|
1573
|
+
import * as ts11 from "typescript";
|
|
1574
|
+
var ADDRESS_CLASSES = {
|
|
1575
|
+
"game-object": true,
|
|
1576
|
+
component: true,
|
|
1577
|
+
either: true,
|
|
1578
|
+
"gui-node": false,
|
|
1579
|
+
animation: false,
|
|
1580
|
+
"resource-path": false,
|
|
1581
|
+
"config-key": false,
|
|
1582
|
+
"action-id": false,
|
|
1583
|
+
none: false
|
|
1584
|
+
};
|
|
1585
|
+
function isAddressClass(parameterClass) {
|
|
1586
|
+
return ADDRESS_CLASSES[parameterClass];
|
|
1587
|
+
}
|
|
1588
|
+
function isFragmentCaret(slot, position) {
|
|
1589
|
+
return slot.fragmentStart !== -1 && position >= slot.fragmentStart;
|
|
1590
|
+
}
|
|
1591
|
+
function tableKey(checker, callee) {
|
|
1592
|
+
const symbol = checker.getSymbolAtLocation(ts11.isIdentifier(callee) ? callee : callee.name);
|
|
1593
|
+
if (!symbol)
|
|
1594
|
+
return;
|
|
1595
|
+
const fqn = checker.getFullyQualifiedName(symbol);
|
|
1596
|
+
return fqn.startsWith("global.") ? fqn.slice("global.".length) : undefined;
|
|
1597
|
+
}
|
|
1598
|
+
function parameterName(checker, call, index) {
|
|
1599
|
+
const declaration = checker.getResolvedSignature(call)?.declaration;
|
|
1600
|
+
if (!declaration || !ts11.isFunctionLike(declaration))
|
|
1601
|
+
return;
|
|
1602
|
+
const parameter = declaration.parameters[index];
|
|
1603
|
+
if (!parameter || parameter.dotDotDotToken || !ts11.isIdentifier(parameter.name))
|
|
1604
|
+
return;
|
|
1605
|
+
return parameter.name.text;
|
|
1606
|
+
}
|
|
1607
|
+
var EQUALITY_OPERATORS = new Set([
|
|
1608
|
+
ts11.SyntaxKind.EqualsEqualsEqualsToken,
|
|
1609
|
+
ts11.SyntaxKind.ExclamationEqualsEqualsToken,
|
|
1610
|
+
ts11.SyntaxKind.EqualsEqualsToken,
|
|
1611
|
+
ts11.SyntaxKind.ExclamationEqualsToken
|
|
1612
|
+
]);
|
|
1613
|
+
function comparisonSatisfied(checker, entry, call) {
|
|
1614
|
+
if (entry.comparedParameter === undefined)
|
|
1615
|
+
return true;
|
|
1616
|
+
let node = call;
|
|
1617
|
+
while (ts11.isParenthesizedExpression(node.parent))
|
|
1618
|
+
node = node.parent;
|
|
1619
|
+
const comparison = node.parent;
|
|
1620
|
+
if (!ts11.isBinaryExpression(comparison))
|
|
1621
|
+
return false;
|
|
1622
|
+
if (!EQUALITY_OPERATORS.has(comparison.operatorToken.kind))
|
|
1623
|
+
return false;
|
|
1624
|
+
const other = comparison.left === node ? comparison.right : comparison.left;
|
|
1625
|
+
const symbol = checker.getSymbolAtLocation(other);
|
|
1626
|
+
return (symbol?.declarations ?? []).some((declaration) => ts11.isParameter(declaration) && ts11.isIdentifier(declaration.name) && declaration.name.text === entry.comparedParameter);
|
|
1627
|
+
}
|
|
1628
|
+
function classifiedEntryOfArgument(checker, table, argument) {
|
|
1629
|
+
const call = argument.parent;
|
|
1630
|
+
if (!ts11.isCallExpression(call))
|
|
1631
|
+
return;
|
|
1632
|
+
const argumentIndex = call.arguments.indexOf(argument);
|
|
1633
|
+
if (argumentIndex === -1)
|
|
1634
|
+
return;
|
|
1635
|
+
const callee = call.expression;
|
|
1636
|
+
if (!ts11.isPropertyAccessExpression(callee) && !ts11.isIdentifier(callee))
|
|
1637
|
+
return;
|
|
1638
|
+
const fqn = tableKey(checker, callee);
|
|
1639
|
+
if (fqn === undefined)
|
|
1640
|
+
return;
|
|
1641
|
+
const parameter = parameterName(checker, call, argumentIndex);
|
|
1642
|
+
if (parameter === undefined)
|
|
1643
|
+
return;
|
|
1644
|
+
for (const entry of table) {
|
|
1645
|
+
if (entry.fqn !== fqn || entry.parameter !== parameter)
|
|
1646
|
+
continue;
|
|
1647
|
+
return comparisonSatisfied(checker, entry, call) ? { entry, call } : undefined;
|
|
1648
|
+
}
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
function slotThroughHashedCall(checker, table, literal) {
|
|
1652
|
+
const call = literal.parent;
|
|
1653
|
+
if (!ts11.isCallExpression(call))
|
|
1654
|
+
return;
|
|
1655
|
+
const callee = call.expression;
|
|
1656
|
+
if (!ts11.isPropertyAccessExpression(callee) && !ts11.isIdentifier(callee))
|
|
1657
|
+
return;
|
|
1658
|
+
if (tableKey(checker, callee) !== "hash")
|
|
1659
|
+
return;
|
|
1660
|
+
const ascended = classifiedEntryOfArgument(checker, table, call);
|
|
1661
|
+
return ascended?.entry.fqn === "hash" ? undefined : ascended;
|
|
1662
|
+
}
|
|
1663
|
+
function addressClassOfArgument(checker, table, argument) {
|
|
1664
|
+
return classifiedEntryOfArgument(checker, table, argument)?.entry.class ?? "none";
|
|
1665
|
+
}
|
|
1666
|
+
function addressTextOf(checker, entry, call) {
|
|
1667
|
+
if (entry.addressParameter === undefined)
|
|
1668
|
+
return;
|
|
1669
|
+
const declaration = checker.getResolvedSignature(call)?.declaration;
|
|
1670
|
+
if (!declaration || !ts11.isFunctionLike(declaration))
|
|
1671
|
+
return;
|
|
1672
|
+
const index = declaration.parameters.findIndex((parameter) => !parameter.dotDotDotToken && ts11.isIdentifier(parameter.name) && parameter.name.text === entry.addressParameter);
|
|
1673
|
+
if (index === -1)
|
|
1674
|
+
return;
|
|
1675
|
+
const argument = call.arguments[index];
|
|
1676
|
+
return argument && ts11.isStringLiteralLike(argument) ? argument.text : undefined;
|
|
1677
|
+
}
|
|
1678
|
+
function innermostLiteralAt(sourceFile, position) {
|
|
1679
|
+
let found;
|
|
1680
|
+
const visit = (node) => {
|
|
1681
|
+
if (position < node.getStart(sourceFile) || position > node.getEnd())
|
|
1682
|
+
return;
|
|
1683
|
+
if (ts11.isStringLiteralLike(node))
|
|
1684
|
+
found = node;
|
|
1685
|
+
ts11.forEachChild(node, visit);
|
|
1686
|
+
};
|
|
1687
|
+
ts11.forEachChild(sourceFile, visit);
|
|
1688
|
+
return found;
|
|
1689
|
+
}
|
|
1690
|
+
function resolveClassifiedSlotAtPosition(input) {
|
|
1691
|
+
const { program, table, fileName, position } = input;
|
|
1692
|
+
const sourceFile = program.getSourceFile(fileName);
|
|
1693
|
+
if (!sourceFile)
|
|
1694
|
+
return;
|
|
1695
|
+
const literal = innermostLiteralAt(sourceFile, position);
|
|
1696
|
+
if (!literal)
|
|
1697
|
+
return;
|
|
1698
|
+
if (literal.getWidth(sourceFile) !== literal.text.length + 2)
|
|
1699
|
+
return;
|
|
1700
|
+
const textStart = literal.getStart(sourceFile) + 1;
|
|
1701
|
+
if (position < textStart || position > textStart + literal.text.length)
|
|
1702
|
+
return;
|
|
1703
|
+
const checker = program.getTypeChecker();
|
|
1704
|
+
const own = classifiedEntryOfArgument(checker, table, literal);
|
|
1705
|
+
const classified = own && own.entry.class !== "none" ? own : slotThroughHashedCall(checker, table, literal);
|
|
1706
|
+
if (!classified || classified.entry.class === "none")
|
|
1707
|
+
return;
|
|
1708
|
+
const hash = literal.text.lastIndexOf("#");
|
|
1709
|
+
const addressText = addressTextOf(checker, classified.entry, classified.call);
|
|
1710
|
+
return {
|
|
1711
|
+
class: classified.entry.class,
|
|
1712
|
+
text: literal.text,
|
|
1713
|
+
textStart,
|
|
1714
|
+
fragmentStart: hash === -1 ? -1 : textStart + hash + 1,
|
|
1715
|
+
...addressText === undefined ? {} : { addressText },
|
|
1716
|
+
...classified.entry.resourceExtensions === undefined ? {} : { resourceExtensions: classified.entry.resourceExtensions }
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
// src/url-fragment-reachability.ts
|
|
1720
|
+
import * as ts12 from "typescript";
|
|
1721
|
+
function isAmbient2(fileName) {
|
|
1722
|
+
return /[\\/]node_modules[\\/]/.test(fileName) || /(^|[\\/])lib\.[^\\/]*\.d\.ts$/.test(fileName);
|
|
1723
|
+
}
|
|
1724
|
+
function referenceTargetSymbol(type) {
|
|
1725
|
+
if ((type.flags & ts12.TypeFlags.Object) === 0)
|
|
1726
|
+
return;
|
|
1727
|
+
if ((type.objectFlags & ts12.ObjectFlags.Reference) === 0)
|
|
1728
|
+
return;
|
|
1729
|
+
return type.target.symbol;
|
|
1730
|
+
}
|
|
1731
|
+
function ambientHashAliasTargets(checker, anchor) {
|
|
1732
|
+
const targets = new Set;
|
|
1733
|
+
for (const symbol of checker.getSymbolsInScope(anchor, ts12.SymbolFlags.Type)) {
|
|
1734
|
+
if (symbol.name !== "Hash")
|
|
1735
|
+
continue;
|
|
1736
|
+
const target = referenceTargetSymbol(checker.getDeclaredTypeOfSymbol(symbol));
|
|
1737
|
+
if (target)
|
|
1738
|
+
targets.add(target);
|
|
1739
|
+
}
|
|
1740
|
+
return targets;
|
|
1741
|
+
}
|
|
1742
|
+
function canonicalHashSymbols(checker, program) {
|
|
1743
|
+
const files = program.getSourceFiles();
|
|
1744
|
+
const anchor = files.find((file) => isAmbient2(file.fileName)) ?? files[0];
|
|
1745
|
+
const canonical = new Set;
|
|
1746
|
+
if (!anchor)
|
|
1747
|
+
return canonical;
|
|
1748
|
+
const aliased = ambientHashAliasTargets(checker, anchor);
|
|
1749
|
+
for (const symbol of checker.getSymbolsInScope(anchor, ts12.SymbolFlags.Function)) {
|
|
1750
|
+
if (symbol.name !== "hash")
|
|
1751
|
+
continue;
|
|
1752
|
+
const declaration = symbol.declarations?.[0];
|
|
1753
|
+
if (!declaration)
|
|
1754
|
+
continue;
|
|
1755
|
+
for (const signature of checker.getTypeOfSymbolAtLocation(symbol, declaration).getCallSignatures()) {
|
|
1756
|
+
const target = referenceTargetSymbol(signature.getReturnType());
|
|
1757
|
+
if (target && aliased.has(target))
|
|
1758
|
+
canonical.add(target);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
return canonical;
|
|
1762
|
+
}
|
|
1763
|
+
function hashedSourceOfType(checker, type, canonical) {
|
|
1764
|
+
const target = referenceTargetSymbol(type);
|
|
1765
|
+
if (!target || !canonical.has(target))
|
|
1766
|
+
return;
|
|
1767
|
+
const [source] = checker.getTypeArguments(type);
|
|
1768
|
+
return source?.isStringLiteral() ? source.value : undefined;
|
|
1769
|
+
}
|
|
1770
|
+
function checkUrlFragmentReachability(input) {
|
|
1771
|
+
const { program, table, index, sourceFiles } = input;
|
|
1772
|
+
if (index.incomplete.length > 0) {
|
|
1773
|
+
return { kind: "suppressed", reasons: index.incomplete };
|
|
1774
|
+
}
|
|
1775
|
+
const checker = program.getTypeChecker();
|
|
1776
|
+
const canonical = canonicalHashSymbols(checker, program);
|
|
1777
|
+
const findings = [];
|
|
1778
|
+
const targets = sourceFiles ?? program.getSourceFiles();
|
|
1779
|
+
for (const sourceFile of targets) {
|
|
1780
|
+
if (isAmbient2(sourceFile.fileName))
|
|
1781
|
+
continue;
|
|
1782
|
+
const visit = (node) => {
|
|
1783
|
+
if (ts12.isExpression(node) && isAddressClass(addressClassOfArgument(checker, table, node))) {
|
|
1784
|
+
const text = ts12.isStringLiteralLike(node) ? node.text : hashedSourceOfType(checker, checker.getTypeAtLocation(node), canonical);
|
|
1785
|
+
if (text !== undefined) {
|
|
1786
|
+
const hash = text.indexOf("#");
|
|
1787
|
+
const fragment = hash === -1 ? "" : text.slice(hash + 1);
|
|
1788
|
+
if (fragment !== "" && !index.ids.has(fragment)) {
|
|
1789
|
+
findings.push({
|
|
1790
|
+
fileName: sourceFile.fileName,
|
|
1791
|
+
start: node.getStart(sourceFile),
|
|
1792
|
+
length: node.getWidth(sourceFile),
|
|
1793
|
+
fragment,
|
|
1794
|
+
message: `no \`.go\` or \`.collection\` in this project declares a component with the id ` + `"${fragment}", so this address cannot resolve at runtime`
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
ts12.forEachChild(node, visit);
|
|
1800
|
+
};
|
|
1801
|
+
ts12.forEachChild(sourceFile, visit);
|
|
1802
|
+
}
|
|
1803
|
+
return { kind: "checked", findings };
|
|
1804
|
+
}
|
|
938
1805
|
export {
|
|
939
1806
|
transpileProject,
|
|
940
1807
|
transpile,
|
|
1808
|
+
stripIncludeBase,
|
|
1809
|
+
resolveClassifiedSlotAtPosition,
|
|
1810
|
+
parseSceneTextFormat,
|
|
1811
|
+
parseBuildConfig,
|
|
1812
|
+
isFragmentCaret,
|
|
1813
|
+
isDefignoredPath,
|
|
1814
|
+
isAddressClass,
|
|
941
1815
|
getProgramDiagnostics,
|
|
942
1816
|
findMainEntryFactoryImports,
|
|
943
|
-
createTranspileSession
|
|
1817
|
+
createTranspileSession,
|
|
1818
|
+
computeOutputRel,
|
|
1819
|
+
componentIdOfSameObjectAddress,
|
|
1820
|
+
checkUrlFragmentReachability,
|
|
1821
|
+
buildSpriteAnimationIndex,
|
|
1822
|
+
buildSceneObjectPathIndex,
|
|
1823
|
+
buildSceneComponentIndex,
|
|
1824
|
+
buildInputActionIndex,
|
|
1825
|
+
buildGuiNodeIndex,
|
|
1826
|
+
buildConfigKeyIndex,
|
|
1827
|
+
addressClassOfArgument,
|
|
1828
|
+
SceneTextFormatError,
|
|
1829
|
+
SCRIPT_SUFFIX_BY_KIND,
|
|
1830
|
+
SCAFFOLDED_DEFIGNORE_LINES,
|
|
1831
|
+
DEFAULT_INCLUDE
|
|
944
1832
|
};
|