@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/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
- return bindings.elements.every((element) => FACTORY_NAMES.has((element.propertyName ?? element.name).text));
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 path2 from "node:path";
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 EDITOR_FACTORY_NAME = "defineEditorScript";
542
- var EDITOR_FACTORY_SPECIFIERS = new Set([EDITOR_FACTORY_MODULE]);
543
- function resolvesToEditorFactoryExport(callee, checker) {
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 false;
1000
+ return;
547
1001
  }
548
1002
  if (symbol.flags & ts8.SymbolFlags.Alias) {
549
1003
  symbol = checker.getAliasedSymbol(symbol);
550
1004
  }
551
- if (symbol.getName() !== EDITOR_FACTORY_NAME) {
552
- return false;
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 false;
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 (!resolvesToEditorFactoryExport(expression.expression, context.checker)) {
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 === EDITOR_FACTORY_NAME);
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 path from "node:path";
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 = path.dirname(requireFromHere.resolve("@defold-typescript/types/package.json"));
639
- var TSTL_LANG_EXT_ROOT = path.dirname(requireFromHere.resolve("@typescript-to-lua/language-extensions/package.json"));
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(path.join(TYPES_PKG_ROOT, rel), "utf8");
1103
+ return readFileSync(path2.join(TYPES_PKG_ROOT, rel), "utf8");
642
1104
  }
643
- var LUA_TYPES_ROOT = path.dirname(createRequire(path.join(TYPES_PKG_ROOT, "package.json")).resolve("lua-types/package.json"));
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(path.join(LUA_TYPES_ROOT, rel), "utf8");
1114
+ const source = readFileSync(path2.join(LUA_TYPES_ROOT, rel), "utf8");
653
1115
  files[key] = source;
654
- const dir = path.posix.dirname(rel);
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(path.posix.normalize(path.posix.join(dir, ref)));
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(path.join(TSTL_LANG_EXT_ROOT, "index.d.ts"), "utf8"),
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(path.join(TYPES_PKG_ROOT, "generated"))) {
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(path.join(TYPES_PKG_ROOT, "src"))) {
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 = path2.dirname(requireFromHere2.resolve("typescript"));
865
- filePath = path2.join(typeScriptDir, fileName);
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 = path2.resolve(dtsName);
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
  };