@defold-typescript/transpiler 0.23.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +923 -28
- 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/lifecycle-erasure.ts +12 -3
- 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
|
|
|
@@ -307,11 +561,18 @@ function isFactoryOnlyImport(node) {
|
|
|
307
561
|
if (clause === undefined || clause.name !== undefined) {
|
|
308
562
|
return false;
|
|
309
563
|
}
|
|
564
|
+
if (clause.isTypeOnly) {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
310
567
|
const bindings = clause.namedBindings;
|
|
311
568
|
if (bindings === undefined || !ts2.isNamedImports(bindings)) {
|
|
312
569
|
return false;
|
|
313
570
|
}
|
|
314
|
-
|
|
571
|
+
const runtime = bindings.elements.filter((element) => !element.isTypeOnly);
|
|
572
|
+
if (runtime.length === 0) {
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
return runtime.every((element) => FACTORY_NAMES.has((element.propertyName ?? element.name).text));
|
|
315
576
|
}
|
|
316
577
|
var lifecycleErasurePlugin = {
|
|
317
578
|
visitors: {
|
|
@@ -354,6 +615,46 @@ function findMainEntryFactoryImports(fileName, source) {
|
|
|
354
615
|
}
|
|
355
616
|
return factories;
|
|
356
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
|
+
}
|
|
357
658
|
// src/program-diagnostics.ts
|
|
358
659
|
import * as ts7 from "typescript";
|
|
359
660
|
import * as tstl from "typescript-to-lua";
|
|
@@ -525,10 +826,155 @@ function getProgramDiagnostics(program, sourceFile) {
|
|
|
525
826
|
});
|
|
526
827
|
return diagnostics.filter((d) => d.file !== undefined && !isAmbient(d.file.fileName));
|
|
527
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
|
+
}
|
|
528
974
|
// src/session.ts
|
|
529
975
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
530
976
|
import { createRequire as createRequire2 } from "node:module";
|
|
531
|
-
import * as
|
|
977
|
+
import * as path3 from "node:path";
|
|
532
978
|
import * as ts10 from "typescript";
|
|
533
979
|
import * as tstl3 from "typescript-to-lua";
|
|
534
980
|
|
|
@@ -538,30 +984,39 @@ import {
|
|
|
538
984
|
createReturnStatement as createReturnStatement2
|
|
539
985
|
} from "typescript-to-lua";
|
|
540
986
|
var EDITOR_FACTORY_MODULE = "@defold-typescript/types";
|
|
541
|
-
var
|
|
542
|
-
var
|
|
543
|
-
|
|
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) {
|
|
544
998
|
let symbol = checker.getSymbolAtLocation(callee);
|
|
545
999
|
if (symbol === undefined) {
|
|
546
|
-
return
|
|
1000
|
+
return;
|
|
547
1001
|
}
|
|
548
1002
|
if (symbol.flags & ts8.SymbolFlags.Alias) {
|
|
549
1003
|
symbol = checker.getAliasedSymbol(symbol);
|
|
550
1004
|
}
|
|
551
|
-
|
|
552
|
-
|
|
1005
|
+
const name = symbol.getName();
|
|
1006
|
+
if (!EDITOR_FACTORY_NAMES.has(name)) {
|
|
1007
|
+
return;
|
|
553
1008
|
}
|
|
554
1009
|
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
555
1010
|
if (declaration === undefined) {
|
|
556
|
-
return
|
|
1011
|
+
return;
|
|
557
1012
|
}
|
|
558
|
-
return declaration.getSourceFile().fileName.includes(EDITOR_FACTORY_MODULE);
|
|
1013
|
+
return declaration.getSourceFile().fileName.includes(EDITOR_FACTORY_MODULE) ? name : undefined;
|
|
559
1014
|
}
|
|
560
1015
|
function lowerEditorFactoryCall(expression, context) {
|
|
561
1016
|
if (!ts8.isCallExpression(expression)) {
|
|
562
1017
|
return;
|
|
563
1018
|
}
|
|
564
|
-
if (
|
|
1019
|
+
if (resolveEditorFactoryExport(expression.expression, context.checker) !== EDITOR_SCRIPT_FACTORY_NAME) {
|
|
565
1020
|
return;
|
|
566
1021
|
}
|
|
567
1022
|
const module = expression.arguments[0];
|
|
@@ -589,7 +1044,7 @@ function isEditorFactoryOnlyImport(node) {
|
|
|
589
1044
|
if (runtime.length === 0) {
|
|
590
1045
|
return false;
|
|
591
1046
|
}
|
|
592
|
-
return runtime.every((element) => (element.propertyName ?? element.name).text
|
|
1047
|
+
return runtime.every((element) => EDITOR_FACTORY_NAMES.has((element.propertyName ?? element.name).text));
|
|
593
1048
|
}
|
|
594
1049
|
var editorScriptErasurePlugin = {
|
|
595
1050
|
visitors: {
|
|
@@ -602,6 +1057,13 @@ var editorScriptErasurePlugin = {
|
|
|
602
1057
|
}
|
|
603
1058
|
return context.superTransformStatements(node);
|
|
604
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
|
+
},
|
|
605
1067
|
[ts8.SyntaxKind.ImportDeclaration]: (node, context) => isEditorFactoryOnlyImport(node) ? [] : context.superTransformStatements(node)
|
|
606
1068
|
}
|
|
607
1069
|
};
|
|
@@ -609,7 +1071,7 @@ var editorScriptErasurePlugin = {
|
|
|
609
1071
|
// src/transpile.ts
|
|
610
1072
|
import { readdirSync, readFileSync } from "node:fs";
|
|
611
1073
|
import { createRequire } from "node:module";
|
|
612
|
-
import * as
|
|
1074
|
+
import * as path2 from "node:path";
|
|
613
1075
|
import * as tstl2 from "typescript-to-lua";
|
|
614
1076
|
|
|
615
1077
|
// src/go-property-direct-call.ts
|
|
@@ -635,12 +1097,12 @@ function flattenDiagnosticMessage(text) {
|
|
|
635
1097
|
return flattenDiagnosticMessage(text.messageText);
|
|
636
1098
|
}
|
|
637
1099
|
var requireFromHere = createRequire(import.meta.url);
|
|
638
|
-
var TYPES_PKG_ROOT =
|
|
639
|
-
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"));
|
|
640
1102
|
function readAmbient(rel) {
|
|
641
|
-
return readFileSync(
|
|
1103
|
+
return readFileSync(path2.join(TYPES_PKG_ROOT, rel), "utf8");
|
|
642
1104
|
}
|
|
643
|
-
var LUA_TYPES_ROOT =
|
|
1105
|
+
var LUA_TYPES_ROOT = path2.dirname(createRequire(path2.join(TYPES_PKG_ROOT, "package.json")).resolve("lua-types/package.json"));
|
|
644
1106
|
var LUA_TYPES_ENTRY_FILES = ["5.1.d.ts", "special/jit-only.d.ts"];
|
|
645
1107
|
var REFERENCE_PATH_RE = /\/\/\/\s*<reference\s+path="([^"]+)"\s*\/>/g;
|
|
646
1108
|
function collectLuaTypesClosure() {
|
|
@@ -649,14 +1111,14 @@ function collectLuaTypesClosure() {
|
|
|
649
1111
|
const key = `node_modules/lua-types/${rel}`;
|
|
650
1112
|
if (key in files)
|
|
651
1113
|
return;
|
|
652
|
-
const source = readFileSync(
|
|
1114
|
+
const source = readFileSync(path2.join(LUA_TYPES_ROOT, rel), "utf8");
|
|
653
1115
|
files[key] = source;
|
|
654
|
-
const dir =
|
|
1116
|
+
const dir = path2.posix.dirname(rel);
|
|
655
1117
|
for (const match of source.matchAll(REFERENCE_PATH_RE)) {
|
|
656
1118
|
const ref = match[1];
|
|
657
1119
|
if (!ref)
|
|
658
1120
|
continue;
|
|
659
|
-
visit(
|
|
1121
|
+
visit(path2.posix.normalize(path2.posix.join(dir, ref)));
|
|
660
1122
|
}
|
|
661
1123
|
};
|
|
662
1124
|
for (const entry of LUA_TYPES_ENTRY_FILES)
|
|
@@ -665,7 +1127,7 @@ function collectLuaTypesClosure() {
|
|
|
665
1127
|
}
|
|
666
1128
|
function buildAmbientFiles() {
|
|
667
1129
|
const files = {
|
|
668
|
-
"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"),
|
|
669
1131
|
"node_modules/@defold-typescript/types/src/core-types.ts": readAmbient("src/core-types.ts"),
|
|
670
1132
|
"node_modules/@defold-typescript/types/src/engine-globals.d.ts": readAmbient("src/engine-globals.d.ts"),
|
|
671
1133
|
"node_modules/@defold-typescript/types/src/go-overloads.d.ts": readAmbient("src/go-overloads.d.ts"),
|
|
@@ -693,15 +1155,21 @@ export type { ScriptProperties, ScriptProperty } from "./src/lifecycle.js";
|
|
|
693
1155
|
`,
|
|
694
1156
|
"node_modules/@defold-typescript/types/render-script.d.ts": `export { defineRenderScript } from "./src/lifecycle.js";
|
|
695
1157
|
export type { ScriptProperties, ScriptProperty } from "./src/lifecycle.js";
|
|
696
|
-
|
|
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
|
+
`)
|
|
697
1165
|
};
|
|
698
1166
|
Object.assign(files, collectLuaTypesClosure());
|
|
699
|
-
for (const entry of readdirSync(
|
|
1167
|
+
for (const entry of readdirSync(path2.join(TYPES_PKG_ROOT, "generated"))) {
|
|
700
1168
|
if (entry.endsWith(".d.ts")) {
|
|
701
1169
|
files[`node_modules/@defold-typescript/types/generated/${entry}`] = readAmbient(`generated/${entry}`);
|
|
702
1170
|
}
|
|
703
1171
|
}
|
|
704
|
-
for (const entry of readdirSync(
|
|
1172
|
+
for (const entry of readdirSync(path2.join(TYPES_PKG_ROOT, "src"))) {
|
|
705
1173
|
if (entry.endsWith(".d.ts")) {
|
|
706
1174
|
files[`node_modules/@defold-typescript/types/src/${entry}`] = readAmbient(`src/${entry}`);
|
|
707
1175
|
}
|
|
@@ -861,12 +1329,12 @@ function createTranspileSession() {
|
|
|
861
1329
|
}
|
|
862
1330
|
let filePath;
|
|
863
1331
|
if (fileName.startsWith("lib.")) {
|
|
864
|
-
const typeScriptDir =
|
|
865
|
-
filePath =
|
|
1332
|
+
const typeScriptDir = path3.dirname(requireFromHere2.resolve("typescript"));
|
|
1333
|
+
filePath = path3.join(typeScriptDir, fileName);
|
|
866
1334
|
}
|
|
867
1335
|
if (fileName.includes("language-extensions")) {
|
|
868
1336
|
const dtsName = fileName.replace(/(\.d)?(\.ts)$/, ".d.ts");
|
|
869
|
-
filePath =
|
|
1337
|
+
filePath = path3.resolve(dtsName);
|
|
870
1338
|
}
|
|
871
1339
|
if (filePath !== undefined) {
|
|
872
1340
|
const cached = libCache.get(fileName);
|
|
@@ -928,10 +1396,437 @@ function createTranspileSession() {
|
|
|
928
1396
|
getProgram: () => program
|
|
929
1397
|
};
|
|
930
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
|
+
}
|
|
931
1805
|
export {
|
|
932
1806
|
transpileProject,
|
|
933
1807
|
transpile,
|
|
1808
|
+
stripIncludeBase,
|
|
1809
|
+
resolveClassifiedSlotAtPosition,
|
|
1810
|
+
parseSceneTextFormat,
|
|
1811
|
+
parseBuildConfig,
|
|
1812
|
+
isFragmentCaret,
|
|
1813
|
+
isDefignoredPath,
|
|
1814
|
+
isAddressClass,
|
|
934
1815
|
getProgramDiagnostics,
|
|
935
1816
|
findMainEntryFactoryImports,
|
|
936
|
-
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
|
|
937
1832
|
};
|