@defold-typescript/transpiler 0.21.1 → 0.23.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.
@@ -0,0 +1,6 @@
1
+ import * as ts from "typescript";
2
+ import { type Plugin } from "typescript-to-lua";
3
+ export declare const EDITOR_FACTORY_MODULE = "@defold-typescript/types";
4
+ export declare const EDITOR_FACTORY_NAME = "defineEditorScript";
5
+ export declare function isEditorFactoryOnlyImport(node: ts.ImportDeclaration): boolean;
6
+ export declare const editorScriptErasurePlugin: Plugin;
package/dist/index.js CHANGED
@@ -529,9 +529,83 @@ function getProgramDiagnostics(program, sourceFile) {
529
529
  import { readFileSync as readFileSync2 } from "node:fs";
530
530
  import { createRequire as createRequire2 } from "node:module";
531
531
  import * as path2 from "node:path";
532
- import * as ts9 from "typescript";
532
+ import * as ts10 from "typescript";
533
533
  import * as tstl3 from "typescript-to-lua";
534
534
 
535
+ // src/editor-script-erasure.ts
536
+ import * as ts8 from "typescript";
537
+ import {
538
+ createReturnStatement as createReturnStatement2
539
+ } from "typescript-to-lua";
540
+ 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) {
544
+ let symbol = checker.getSymbolAtLocation(callee);
545
+ if (symbol === undefined) {
546
+ return false;
547
+ }
548
+ if (symbol.flags & ts8.SymbolFlags.Alias) {
549
+ symbol = checker.getAliasedSymbol(symbol);
550
+ }
551
+ if (symbol.getName() !== EDITOR_FACTORY_NAME) {
552
+ return false;
553
+ }
554
+ const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
555
+ if (declaration === undefined) {
556
+ return false;
557
+ }
558
+ return declaration.getSourceFile().fileName.includes(EDITOR_FACTORY_MODULE);
559
+ }
560
+ function lowerEditorFactoryCall(expression, context) {
561
+ if (!ts8.isCallExpression(expression)) {
562
+ return;
563
+ }
564
+ if (!resolvesToEditorFactoryExport(expression.expression, context.checker)) {
565
+ return;
566
+ }
567
+ const module = expression.arguments[0];
568
+ if (module === undefined) {
569
+ return;
570
+ }
571
+ return [createReturnStatement2([context.transformExpression(module)])];
572
+ }
573
+ function isEditorFactoryOnlyImport(node) {
574
+ if (!ts8.isStringLiteral(node.moduleSpecifier) || !EDITOR_FACTORY_SPECIFIERS.has(node.moduleSpecifier.text)) {
575
+ return false;
576
+ }
577
+ const clause = node.importClause;
578
+ if (clause === undefined || clause.name !== undefined) {
579
+ return false;
580
+ }
581
+ if (clause.isTypeOnly) {
582
+ return false;
583
+ }
584
+ const bindings = clause.namedBindings;
585
+ if (bindings === undefined || !ts8.isNamedImports(bindings)) {
586
+ return false;
587
+ }
588
+ const runtime = bindings.elements.filter((element) => !element.isTypeOnly);
589
+ if (runtime.length === 0) {
590
+ return false;
591
+ }
592
+ return runtime.every((element) => (element.propertyName ?? element.name).text === EDITOR_FACTORY_NAME);
593
+ }
594
+ var editorScriptErasurePlugin = {
595
+ visitors: {
596
+ [ts8.SyntaxKind.ExportAssignment]: (node, context) => {
597
+ if (!node.isExportEquals) {
598
+ const lowered = lowerEditorFactoryCall(node.expression, context);
599
+ if (lowered !== undefined) {
600
+ return lowered;
601
+ }
602
+ }
603
+ return context.superTransformStatements(node);
604
+ },
605
+ [ts8.SyntaxKind.ImportDeclaration]: (node, context) => isEditorFactoryOnlyImport(node) ? [] : context.superTransformStatements(node)
606
+ }
607
+ };
608
+
535
609
  // src/transpile.ts
536
610
  import { readdirSync, readFileSync } from "node:fs";
537
611
  import { createRequire } from "node:module";
@@ -539,17 +613,17 @@ import * as path from "node:path";
539
613
  import * as tstl2 from "typescript-to-lua";
540
614
 
541
615
  // src/go-property-direct-call.ts
542
- import * as ts8 from "typescript";
616
+ import * as ts9 from "typescript";
543
617
  var GO_PROPERTY_DIRECT_CALL_MESSAGE = "`go.property` called directly is deprecated; declare the property in `defineScript({ properties })` so it types onto `self` (the transpiler emits the registration for you).";
544
618
  function findDirectGoPropertyCalls(sourceFile) {
545
619
  const calls = [];
546
620
  const visit = (node) => {
547
- if (ts8.isCallExpression(node) && ts8.isPropertyAccessExpression(node.expression) && node.expression.name.text === "property" && ts8.isIdentifier(node.expression.expression) && node.expression.expression.text === "go") {
621
+ if (ts9.isCallExpression(node) && ts9.isPropertyAccessExpression(node.expression) && node.expression.name.text === "property" && ts9.isIdentifier(node.expression.expression) && node.expression.expression.text === "go") {
548
622
  calls.push(node);
549
623
  }
550
- ts8.forEachChild(node, visit);
624
+ ts9.forEachChild(node, visit);
551
625
  };
552
- ts8.forEachChild(sourceFile, visit);
626
+ ts9.forEachChild(sourceFile, visit);
553
627
  return calls;
554
628
  }
555
629
 
@@ -600,10 +674,13 @@ function buildAmbientFiles() {
600
674
  "node_modules/@defold-typescript/types/src/window-event-guard.d.ts": readAmbient("src/window-event-guard.d.ts"),
601
675
  "node_modules/@defold-typescript/types/src/message-dispatch.d.ts": readAmbient("src/message-dispatch.d.ts"),
602
676
  "node_modules/@defold-typescript/types/src/lifecycle.ts": readAmbient("src/lifecycle.ts"),
677
+ "node_modules/@defold-typescript/types/src/editor.ts": readAmbient("src/editor.ts"),
603
678
  "node_modules/@defold-typescript/types/src/timers.d.ts": readAmbient("src/timers.d.ts"),
604
679
  "node_modules/@defold-typescript/types/index.ts": [
605
680
  'export { defineGuiScript, defineRenderScript, defineScript } from "./src/lifecycle";',
606
681
  'export type { GuiScriptHooks, InputAction, InputTouch, RenderScriptHooks, ScriptHooks, ScriptProperties, ScriptProperty } from "./src/lifecycle";',
682
+ 'export { defineEditorScript } from "./src/editor";',
683
+ 'export type { EditorCommand, EditorScriptModule } from "./src/editor";',
607
684
  'export type { Hash, Matrix4, Quaternion, Url, Vector, Vector3, Vector4 } from "./src/core-types";',
608
685
  ""
609
686
  ].join(`
@@ -705,6 +782,7 @@ function transpileProject(input) {
705
782
  noImplicitSelf: true,
706
783
  luaPlugins: [
707
784
  { plugin: lifecycleErasurePlugin },
785
+ { plugin: editorScriptErasurePlugin },
708
786
  { plugin: messageGuardLoweringPlugin },
709
787
  { plugin: windowEventGuardLoweringPlugin },
710
788
  { plugin: messageDispatchLoweringPlugin },
@@ -732,6 +810,7 @@ var COMPILER_OPTIONS = {
732
810
  noImplicitSelf: true,
733
811
  luaPlugins: [
734
812
  { plugin: lifecycleErasurePlugin },
813
+ { plugin: editorScriptErasurePlugin },
735
814
  { plugin: messageGuardLoweringPlugin },
736
815
  { plugin: windowEventGuardLoweringPlugin },
737
816
  { plugin: messageDispatchLoweringPlugin },
@@ -776,7 +855,7 @@ function createTranspileSession() {
776
855
  if (cached) {
777
856
  return cached;
778
857
  }
779
- const created = ts9.createSourceFile(fileName, content, ts9.ScriptTarget.Latest, false);
858
+ const created = ts10.createSourceFile(fileName, content, ts10.ScriptTarget.Latest, false);
780
859
  sourceFileCache.set(normalized, created);
781
860
  return created;
782
861
  }
@@ -795,7 +874,7 @@ function createTranspileSession() {
795
874
  return cached;
796
875
  }
797
876
  const fileContent = readFileSync2(filePath, "utf8");
798
- const created = ts9.createSourceFile(filePath, fileContent, ts9.ScriptTarget.Latest, false);
877
+ const created = ts10.createSourceFile(filePath, fileContent, ts10.ScriptTarget.Latest, false);
799
878
  libCache.set(fileName, created);
800
879
  return created;
801
880
  }
@@ -808,10 +887,10 @@ function createTranspileSession() {
808
887
  return AMBIENT_FILES[normalized];
809
888
  }
810
889
  const host = {
811
- fileExists: (fileName) => mergedContent(normalizeSlashes(fileName)) !== undefined || ts9.sys.fileExists(fileName),
890
+ fileExists: (fileName) => mergedContent(normalizeSlashes(fileName)) !== undefined || ts10.sys.fileExists(fileName),
812
891
  getCanonicalFileName: (fileName) => fileName,
813
892
  getCurrentDirectory: () => "",
814
- getDefaultLibFileName: ts9.getDefaultLibFileName,
893
+ getDefaultLibFileName: ts10.getDefaultLibFileName,
815
894
  readFile: () => "",
816
895
  getNewLine: () => `
817
896
  `,
@@ -832,15 +911,15 @@ function createTranspileSession() {
832
911
  }
833
912
  const userKeys = new Set(userFiles.keys());
834
913
  const rootNames = [...Object.keys(AMBIENT_FILES).map(normalizeSlashes), ...userKeys];
835
- program = ts9.createProgram(rootNames, COMPILER_OPTIONS, host, program);
836
- const preEmitDiagnostics = ts9.getPreEmitDiagnostics(program);
914
+ program = ts10.createProgram(rootNames, COMPILER_OPTIONS, host, program);
915
+ const preEmitDiagnostics = ts10.getPreEmitDiagnostics(program);
837
916
  const collector = createOutputCollector();
838
917
  const { diagnostics: transpileDiagnostics } = new tstl3.Transpiler().emit({
839
918
  program,
840
919
  writeFile: collector.writeFile
841
920
  });
842
921
  const diagnostics = [
843
- ...ts9.sortAndDeduplicateDiagnostics([...preEmitDiagnostics, ...transpileDiagnostics])
922
+ ...ts10.sortAndDeduplicateDiagnostics([...preEmitDiagnostics, ...transpileDiagnostics])
844
923
  ];
845
924
  return collectOutputs(collector.files, diagnostics, userKeys);
846
925
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defold-typescript/transpiler",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "TypeScript-to-Lua build pipeline tuned for Defold's runtime.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,7 +35,7 @@
35
35
  "test": "bun test"
36
36
  },
37
37
  "dependencies": {
38
- "@defold-typescript/types": "0.21.1",
38
+ "@defold-typescript/types": "0.23.0",
39
39
  "@typescript-to-lua/language-extensions": "1.19.0",
40
40
  "typescript-to-lua": "^1.36.0"
41
41
  }
@@ -0,0 +1,102 @@
1
+ import * as ts from "typescript";
2
+ import {
3
+ createReturnStatement,
4
+ type Plugin,
5
+ type Statement,
6
+ type TransformationContext,
7
+ } from "typescript-to-lua";
8
+
9
+ export const EDITOR_FACTORY_MODULE = "@defold-typescript/types";
10
+ export const EDITOR_FACTORY_NAME = "defineEditorScript";
11
+ // Only the bare main entry for now; the walled `@defold-typescript/types/editor-script`
12
+ // subpath import arrives with the per-kind editor wall (a deferred slice).
13
+ const EDITOR_FACTORY_SPECIFIERS = new Set([EDITOR_FACTORY_MODULE]);
14
+
15
+ function resolvesToEditorFactoryExport(callee: ts.Expression, checker: ts.TypeChecker): boolean {
16
+ let symbol = checker.getSymbolAtLocation(callee);
17
+ if (symbol === undefined) {
18
+ return false;
19
+ }
20
+ if (symbol.flags & ts.SymbolFlags.Alias) {
21
+ symbol = checker.getAliasedSymbol(symbol);
22
+ }
23
+ if (symbol.getName() !== EDITOR_FACTORY_NAME) {
24
+ return false;
25
+ }
26
+ const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
27
+ if (declaration === undefined) {
28
+ return false;
29
+ }
30
+ return declaration.getSourceFile().fileName.includes(EDITOR_FACTORY_MODULE);
31
+ }
32
+
33
+ // Unlike the runtime kinds (lowered to flat top-level lifecycle globals), the
34
+ // editor loads the chunk and uses its return value, so the hooks table must be
35
+ // the chunk's return. Unwrap `defineEditorScript(OBJ)` and emit `return OBJ`
36
+ // directly — not `export default`, whose TSTL lowering returns `{ default = OBJ }`.
37
+ function lowerEditorFactoryCall(
38
+ expression: ts.Expression,
39
+ context: TransformationContext,
40
+ ): Statement[] | undefined {
41
+ if (!ts.isCallExpression(expression)) {
42
+ return undefined;
43
+ }
44
+ if (!resolvesToEditorFactoryExport(expression.expression, context.checker)) {
45
+ return undefined;
46
+ }
47
+ const module = expression.arguments[0];
48
+ if (module === undefined) {
49
+ return undefined;
50
+ }
51
+ return [createReturnStatement([context.transformExpression(module)])];
52
+ }
53
+
54
+ export function isEditorFactoryOnlyImport(node: ts.ImportDeclaration): boolean {
55
+ if (
56
+ !ts.isStringLiteral(node.moduleSpecifier) ||
57
+ !EDITOR_FACTORY_SPECIFIERS.has(node.moduleSpecifier.text)
58
+ ) {
59
+ return false;
60
+ }
61
+ const clause = node.importClause;
62
+ if (clause === undefined || clause.name !== undefined) {
63
+ return false;
64
+ }
65
+ // A whole-clause `import type { ... }` binds nothing at runtime; leave it for
66
+ // the normal transform, which elides it.
67
+ if (clause.isTypeOnly) {
68
+ return false;
69
+ }
70
+ const bindings = clause.namedBindings;
71
+ if (bindings === undefined || !ts.isNamedImports(bindings)) {
72
+ return false;
73
+ }
74
+ // Type-only specifiers ride along and drop with the erased statement; erase
75
+ // only when every runtime specifier is the editor factory itself.
76
+ const runtime = bindings.elements.filter((element) => !element.isTypeOnly);
77
+ if (runtime.length === 0) {
78
+ return false;
79
+ }
80
+ return runtime.every(
81
+ (element) => (element.propertyName ?? element.name).text === EDITOR_FACTORY_NAME,
82
+ );
83
+ }
84
+
85
+ // The editor-factory recognition is disjoint from the runtime factories, so this
86
+ // plugin composes with `lifecycleErasurePlugin` in either order: each visitor
87
+ // lowers its own kind and delegates the rest via `superTransformStatements`.
88
+ export const editorScriptErasurePlugin: Plugin = {
89
+ visitors: {
90
+ [ts.SyntaxKind.ExportAssignment]: (node, context) => {
91
+ if (!node.isExportEquals) {
92
+ const lowered = lowerEditorFactoryCall(node.expression, context);
93
+ if (lowered !== undefined) {
94
+ return lowered;
95
+ }
96
+ }
97
+ return context.superTransformStatements(node);
98
+ },
99
+ [ts.SyntaxKind.ImportDeclaration]: (node, context) =>
100
+ isEditorFactoryOnlyImport(node) ? [] : context.superTransformStatements(node),
101
+ },
102
+ };
package/src/session.ts CHANGED
@@ -3,6 +3,7 @@ import { createRequire } from "node:module";
3
3
  import * as path from "node:path";
4
4
  import * as ts from "typescript";
5
5
  import * as tstl from "typescript-to-lua";
6
+ import { editorScriptErasurePlugin } from "./editor-script-erasure";
6
7
  import { lifecycleErasurePlugin } from "./lifecycle-erasure";
7
8
  import { messageDispatchLoweringPlugin } from "./message-dispatch-lowering";
8
9
  import { messageGuardLoweringPlugin } from "./message-guard-lowering";
@@ -32,6 +33,7 @@ const COMPILER_OPTIONS: tstl.CompilerOptions = {
32
33
  noImplicitSelf: true,
33
34
  luaPlugins: [
34
35
  { plugin: lifecycleErasurePlugin },
36
+ { plugin: editorScriptErasurePlugin },
35
37
  { plugin: messageGuardLoweringPlugin },
36
38
  { plugin: windowEventGuardLoweringPlugin },
37
39
  { plugin: messageDispatchLoweringPlugin },
package/src/transpile.ts CHANGED
@@ -3,6 +3,7 @@ import { createRequire } from "node:module";
3
3
  import * as path from "node:path";
4
4
  import type * as ts from "typescript";
5
5
  import * as tstl from "typescript-to-lua";
6
+ import { editorScriptErasurePlugin } from "./editor-script-erasure";
6
7
  import {
7
8
  findDirectGoPropertyCalls,
8
9
  GO_PROPERTY_DIRECT_CALL_MESSAGE,
@@ -128,6 +129,9 @@ function buildAmbientFiles(): Record<string, string> {
128
129
  "src/message-dispatch.d.ts",
129
130
  ),
130
131
  "node_modules/@defold-typescript/types/src/lifecycle.ts": readAmbient("src/lifecycle.ts"),
132
+ // The editor-script factory + hook types. A `.ts` (not `.d.ts`), so it needs
133
+ // explicit seeding like lifecycle.ts — the `src/*.d.ts` loop below skips it.
134
+ "node_modules/@defold-typescript/types/src/editor.ts": readAmbient("src/editor.ts"),
131
135
  // Ambient `declare module "@defold-typescript/types/timers"` so user code
132
136
  // type-resolves the polyfill import in the virtual program; the lowering
133
137
  // rewrites the specifier to a flat require at emit.
@@ -137,6 +141,8 @@ function buildAmbientFiles(): Record<string, string> {
137
141
  "node_modules/@defold-typescript/types/index.ts": [
138
142
  'export { defineGuiScript, defineRenderScript, defineScript } from "./src/lifecycle";',
139
143
  'export type { GuiScriptHooks, InputAction, InputTouch, RenderScriptHooks, ScriptHooks, ScriptProperties, ScriptProperty } from "./src/lifecycle";',
144
+ 'export { defineEditorScript } from "./src/editor";',
145
+ 'export type { EditorCommand, EditorScriptModule } from "./src/editor";',
140
146
  'export type { Hash, Matrix4, Quaternion, Url, Vector, Vector3, Vector4 } from "./src/core-types";',
141
147
  "",
142
148
  ].join("\n"),
@@ -282,6 +288,7 @@ export function transpileProject(input: TranspileProjectInput): TranspileProject
282
288
  noImplicitSelf: true,
283
289
  luaPlugins: [
284
290
  { plugin: lifecycleErasurePlugin },
291
+ { plugin: editorScriptErasurePlugin },
285
292
  { plugin: messageGuardLoweringPlugin },
286
293
  { plugin: windowEventGuardLoweringPlugin },
287
294
  { plugin: messageDispatchLoweringPlugin },