@defold-typescript/transpiler 0.21.1 → 0.22.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/editor-script-erasure.d.ts +6 -0
- package/dist/index.js +91 -12
- package/package.json +2 -2
- package/src/editor-script-erasure.ts +102 -0
- package/src/session.ts +2 -0
- package/src/transpile.ts +7 -0
|
@@ -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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
624
|
+
ts9.forEachChild(node, visit);
|
|
551
625
|
};
|
|
552
|
-
|
|
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 =
|
|
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 =
|
|
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 ||
|
|
890
|
+
fileExists: (fileName) => mergedContent(normalizeSlashes(fileName)) !== undefined || ts10.sys.fileExists(fileName),
|
|
812
891
|
getCanonicalFileName: (fileName) => fileName,
|
|
813
892
|
getCurrentDirectory: () => "",
|
|
814
|
-
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 =
|
|
836
|
-
const preEmitDiagnostics =
|
|
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
|
-
...
|
|
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.
|
|
3
|
+
"version": "0.22.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.
|
|
38
|
+
"@defold-typescript/types": "0.22.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 },
|