@needle-tools/needle-component-compiler 2.4.1-pre → 3.0.0-alpha.1

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/Changelog.md CHANGED
@@ -4,6 +4,11 @@ All notable changes to this package will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [3.0.0-alpha.1] - 2026-03-02
8
+ - Fix: destructured method parameters (e.g. `{xr}`) now emit as `object @obj` instead of invalid `object @{xr}`
9
+ - Fix: `Object3D` type now correctly maps to `UnityEngine.Transform`
10
+ - Add: `RectTransform` type mapping to `UnityEngine.RectTransform`
11
+
7
12
  ## [2.4.1-pre] - 2023-04-03
8
13
  # Blender compiler
9
14
  - Add: typenames
@@ -24,6 +29,46 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
24
29
  ## [2.1.0-pre] - 2022-11-12
25
30
  - Add blender scheme compiler for creating and updating schemes (or removing them when the component gets deleted)
26
31
 
32
+ ## [1.12.2] - 2025-06-06
33
+ - Fix: Private fields decorated with `@serializable()` should be annotated with `UnityEngine.SerializeField`
34
+ - Fix: Comment for `// @serializeField` should not affect subsequent fields
35
+
36
+ ## [1.12.0] - 2025-06-02
37
+ - Add: Support to serialize unknown classes when annotated with `// @type object`
38
+
39
+ ## [1.11.2] - 2023-12-16
40
+ - Fix: property setter emitting invalid C# code
41
+
42
+ ## [1.11.1] - 2023-12-02
43
+ - Fix: Use Unity AnimatorController type
44
+ - Fix: method inline anonymous type declaration (e.g. `myMethod(arg: {x:number})`)
45
+
46
+ ## [1.11.0] - 2023-11-18
47
+ - Add: support to wrap fields with `#if UNITY_EDITOR` if their namespace contains UnityEditor
48
+ - Add: more threejs types to generate Unity Material fields for
49
+
50
+ ## [1.10.3] - 2023-09-08
51
+ - Add: Needle Engine type `RGBAColor` is now automatically emitted as `UnityEngine.Color`
52
+
53
+ ## [1.10.2] - 2023-09-08
54
+ - Fix: `@type` was not properly applied for `new expression` cases (e.g. `new RGBAColor` should produce a `new Color` when decorated with `@type Color`)
55
+
56
+ ## [1.10.1] - 2023-08-02
57
+ - Add: use `@tooltip` to emit a UnityEngine.Tooltip
58
+ - Update Readme
59
+
60
+ ## [1.9.4] - 2023-05-24
61
+ - Change: `Object3D` now emits `GameObject` field instead of `Transform`
62
+ - Fix: ignore `static` methods
63
+ - Fix: ignore `abstract` methods
64
+
65
+ ## [1.9.3] - 2022-12-30
66
+ - Add debug logs for when no file or target directory was passed in. Also wrapping core with try catch
67
+
68
+ ## [1.9.2] - 2022-11-29
69
+ - Fix codegen deleting code added manually outside of codegen sections
70
+ - Fix codegen creating output directory if it does not exist yet
71
+
27
72
  ## [1.9.1] - 2022-11-05
28
73
  - Fix ``new Vector2(1, .5)`` generating invalid C# where number arguments were emitted as double instead of float
29
74
 
package/Readme.md CHANGED
@@ -1,19 +1,46 @@
1
- ## Typescript to Unity component
2
-
3
- Little helper package to transpile typescript files to Unity C# components.
4
- Please run ``npm install`` first before using.
5
-
6
- ### Usage
7
- ``node <path to>/component-compiler.js <output_directory> <path_to/my_script.ts>``
8
-
9
-
10
- ### Command decorators
11
- - ``@dont-generate-component`` add before class to skip generating a component
12
- - ``@generate-component`` to enforce generating a component (not required)
13
- - ``@serializeField`` field decorator, similar to ``[SerializeField]`` in Unity
14
- - ``@nonSerialized`` field or method decorator to skip generating c# code for a field or a method, similar to ``[NonSerialized]`` in Unity
15
- - ``@type MyNamespace.MyType`` decorator for fields or classes, specifiy C# type of field or class
16
- - ``@ifdef MY_IFDEF`` field decorator only at the moment
17
-
18
- ### Test
19
- - Run single test: `` npm run test:single -- -r ts-node/register "./test/blender/blender.from_typedef.test.ts"``
1
+ ## Needle Engine Component Compiler
2
+
3
+ Compiles TypeScript component definitions into **Unity C#** component stubs and **Blender** Python component schemas for Needle Engine.
4
+
5
+ ```
6
+ npm install @needle-tools/needle-component-compiler
7
+ ```
8
+
9
+ ### Supported TypeScript Features
10
+
11
+ - Classes extending `Behaviour`, `MonoBehaviour`, or implementing `IComponent`
12
+ - Public, protected, and private fields with type annotations
13
+ - Default values (primitives, `new` expressions, literals)
14
+ - Array types: `T[]`, `Array<T>`
15
+ - Union types (nullable types like `Object3D | null` resolve to the concrete type)
16
+ - Enums (with value initialization)
17
+ - Methods with parameters and return types
18
+ - Destructured parameters (compiled as `object`)
19
+
20
+ ### Comment Directives
21
+
22
+ Use `//` comments above classes, fields, or methods to control code generation:
23
+
24
+ | Directive | Target | Description |
25
+ |---|---|---|
26
+ | `@generate-component` | Class | Force generation (not required, classes are generated by default) |
27
+ | `@dont-generate-component` | Class | Skip generating this class entirely |
28
+ | `@type MyNamespace.MyType` | Class / Field | Override the resolved C# type or base class |
29
+ | `@abstract` | Class | Mark the generated class as `abstract` |
30
+ | `@serializeField` | Field | Emit `[UnityEngine.SerializeField]` attribute |
31
+ | `@nonSerialized` | Field / Method | Skip generating C# code for this member |
32
+ | `@tooltip "My text"` | Field | Emit `[UnityEngine.Tooltip("My text")]` |
33
+ | `@contextmenu "Label"` | Method | Emit `[UnityEngine.ContextMenu("Label")]` |
34
+ | `@ifdef MY_DEFINE` | Field | Wrap field in `#if MY_DEFINE` / `#endif` |
35
+
36
+ ### Codegen Fences
37
+
38
+ Generated C# files are wrapped in `// NEEDLE_CODEGEN_START` and `// NEEDLE_CODEGEN_END` markers. This allows you to add custom code outside the fences that will be preserved when the file is regenerated.
39
+
40
+ # Contact ✒️
41
+ <b>[🌵 Needle](https://needle.tools)</b> •
42
+ [Github](https://github.com/needle-tools) •
43
+ [Twitter](https://twitter.com/NeedleTools) •
44
+ [Discord](https://discord.needle.tools) •
45
+ [Forum](https://forum.needle.tools) •
46
+ [Youtube](https://youtube.com/@needle-tools)
@@ -0,0 +1,23 @@
1
+ import { Argument, CodeTextWriter, EnumMember, ISink, IWriter, Visibility } from "./base-compiler";
2
+ import { TypeSourceInformation } from "./register-types";
3
+ export declare abstract class BaseWriter implements IWriter {
4
+ private _sink?;
5
+ protected writer: CodeTextWriter;
6
+ get sink(): ISink;
7
+ constructor(_sink?: ISink);
8
+ /** Enum registry: lowercased enum name → members */
9
+ protected _enumRegistry: Map<string, EnumMember[]>;
10
+ registerEnum(name: string, members: EnumMember[]): void;
11
+ abstract resolveCSharpTypeName(typescriptTypeName: string, baseTypes?: string[]): string | void;
12
+ abstract startNewType(filePath: string, typeName: string, baseType: string[], comments?: string[]): boolean | void;
13
+ abstract endNewType(filePath: string, typeName: string): void;
14
+ abstract writeMember(visibility: Visibility, name: string, isArray: boolean, type: string, initialValue?: string, comments?: string[]): void;
15
+ abstract writeMethod(visibility: Visibility, name: string, returnType: string, args: Argument[], comments: string[]): void;
16
+ abstract writeNewTypeExpression(typeName: string, args?: string[]): void;
17
+ private _currentlyProcessingFiles;
18
+ get outputInfo(): TypeSourceInformation;
19
+ private _createdSchemesPerFile;
20
+ begin(filePath: string | null): void;
21
+ end(filePath: string | null): void;
22
+ protected writeScheme(processingFilePath: string | null, component: string): void;
23
+ }
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseWriter = void 0;
4
+ var fs_1 = require("fs");
5
+ var base_compiler_1 = require("./base-compiler");
6
+ var commands_1 = require("./commands");
7
+ var BaseWriter = /** @class */ (function () {
8
+ function BaseWriter(_sink) {
9
+ this._sink = _sink;
10
+ this.writer = new base_compiler_1.CodeTextWriter();
11
+ /** Enum registry: lowercased enum name → members */
12
+ this._enumRegistry = new Map();
13
+ this._currentlyProcessingFiles = {};
14
+ this._createdSchemesPerFile = {};
15
+ }
16
+ Object.defineProperty(BaseWriter.prototype, "sink", {
17
+ get: function () {
18
+ return this._sink;
19
+ },
20
+ enumerable: false,
21
+ configurable: true
22
+ });
23
+ BaseWriter.prototype.registerEnum = function (name, members) {
24
+ this._enumRegistry.set(name.toLowerCase(), members);
25
+ };
26
+ Object.defineProperty(BaseWriter.prototype, "outputInfo", {
27
+ get: function () {
28
+ return this._createdSchemesPerFile;
29
+ },
30
+ enumerable: false,
31
+ configurable: true
32
+ });
33
+ BaseWriter.prototype.begin = function (filePath) {
34
+ if (!this._currentlyProcessingFiles[filePath]) {
35
+ this._currentlyProcessingFiles[filePath] = [];
36
+ }
37
+ };
38
+ BaseWriter.prototype.end = function (filePath) {
39
+ var results = this._currentlyProcessingFiles[filePath];
40
+ console.log("Writing schemes for", filePath, results);
41
+ if (results) {
42
+ var previousResultsFromThisFile = this._createdSchemesPerFile[filePath];
43
+ if (previousResultsFromThisFile) {
44
+ for (var _i = 0, previousResultsFromThisFile_1 = previousResultsFromThisFile; _i < previousResultsFromThisFile_1.length; _i++) {
45
+ var previouslyCreated = previousResultsFromThisFile_1[_i];
46
+ var foundInAny = false;
47
+ for (var _a = 0, results_1 = results; _a < results_1.length; _a++) {
48
+ var res = results_1[_a];
49
+ if (res.filePath === previouslyCreated.filePath) {
50
+ foundInAny = true;
51
+ break;
52
+ }
53
+ }
54
+ if (!foundInAny) {
55
+ for (var sourcePath in this._createdSchemesPerFile) {
56
+ if (foundInAny)
57
+ break;
58
+ if (sourcePath === filePath)
59
+ continue;
60
+ var otherSourceSchemes = this._createdSchemesPerFile[sourcePath];
61
+ if (otherSourceSchemes.includes(previouslyCreated)) {
62
+ // the file was moved to another source file
63
+ foundInAny = true;
64
+ }
65
+ }
66
+ }
67
+ if (!foundInAny && (0, fs_1.existsSync)(previouslyCreated.filePath)) {
68
+ (0, fs_1.unlinkSync)(previouslyCreated.filePath);
69
+ (0, commands_1.sendFileDeletedCommand)(previouslyCreated.filePath);
70
+ }
71
+ }
72
+ }
73
+ this._createdSchemesPerFile[filePath] = results;
74
+ }
75
+ delete this._currentlyProcessingFiles[filePath];
76
+ };
77
+ BaseWriter.prototype.writeScheme = function (processingFilePath, component) {
78
+ var res = this.sink.flush(component + ".component.json", this.writer.flush());
79
+ // if an output path is returned it means a file has been written to that path
80
+ if (res && (0, fs_1.existsSync)(res)) {
81
+ (0, commands_1.sendFileWrittenCommand)(res);
82
+ // add the scheme to the list of created schemes
83
+ if (processingFilePath && this._currentlyProcessingFiles[processingFilePath]) {
84
+ this._currentlyProcessingFiles[processingFilePath].push({ componentName: component, filePath: res });
85
+ }
86
+ }
87
+ };
88
+ return BaseWriter;
89
+ }());
90
+ exports.BaseWriter = BaseWriter;
@@ -0,0 +1,20 @@
1
+ import { IWriter } from "./base-compiler";
2
+ /** Typescript Walker */
3
+ export declare class Compiler {
4
+ compile(writer: IWriter, code: string, sourceFilePath: string | null): void;
5
+ private run;
6
+ private visitEnumDeclaration;
7
+ private visit;
8
+ private visitClassDeclaration;
9
+ private visitPropertyDeclaration;
10
+ private visitMethodDeclaration;
11
+ private resolveParameters;
12
+ private debugLog;
13
+ private getComments;
14
+ private getVisibility;
15
+ private tryResolveTypeFromExpression;
16
+ private resolveType;
17
+ private resolveTypeFromString;
18
+ private isArrayType;
19
+ private resolveExpression;
20
+ }
@@ -1,189 +1,64 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Compiler = exports.runFromFile = exports.handleDeletedFile = exports.CodeTextWriter = exports.MemorySink = exports.FileSink = exports.Visibility = void 0;
4
- var fs_1 = require("fs");
3
+ exports.Compiler = void 0;
5
4
  var ts = require("typescript");
6
- var fs = require("fs");
7
- var commands_1 = require("./commands");
8
- var Visibility;
9
- (function (Visibility) {
10
- Visibility[Visibility["Public"] = 0] = "Public";
11
- Visibility[Visibility["Protected"] = 1] = "Protected";
12
- Visibility[Visibility["Private"] = 2] = "Private";
13
- })(Visibility = exports.Visibility || (exports.Visibility = {}));
14
- var FileSink = /** @class */ (function () {
15
- function FileSink(directory) {
16
- this.directory = directory;
17
- }
18
- FileSink.prototype.flush = function (id, str) {
19
- if (!fs.existsSync(this.directory)) {
20
- fs.mkdirSync(this.directory, { recursive: true });
21
- }
22
- console.log("Writing " + id + " to " + this.directory);
23
- var fullPath = this.directory + "/" + id;
24
- fs.writeFileSync(fullPath, str);
25
- return fs.realpathSync(fullPath);
26
- };
27
- return FileSink;
28
- }());
29
- exports.FileSink = FileSink;
30
- var MemorySink = /** @class */ (function () {
31
- function MemorySink() {
32
- this.results = [];
33
- }
34
- MemorySink.prototype.clear = function () {
35
- this.results.length = 0;
36
- };
37
- MemorySink.prototype.flush = function (id, str) {
38
- this.results.push(str);
39
- return "";
40
- };
41
- return MemorySink;
42
- }());
43
- exports.MemorySink = MemorySink;
44
- var CodeTextWriter = /** @class */ (function () {
45
- function CodeTextWriter() {
46
- this.indent = 0;
47
- this.lines = [];
48
- }
49
- CodeTextWriter.prototype.beginBlock = function (line) {
50
- this.writeLine(line);
51
- this.indent++;
52
- };
53
- CodeTextWriter.prototype.endBlock = function (line) {
54
- // remove the comma from the last line
55
- if (this.lines.length > 0) {
56
- var prevLine = this.lines[this.lines.length - 1];
57
- if (prevLine.endsWith(",")) {
58
- this.lines[this.lines.length - 1] = prevLine.substring(0, prevLine.length - 1);
59
- }
60
- }
61
- this.indent--;
62
- this.writeLine(line);
63
- };
64
- CodeTextWriter.prototype.writeLine = function (line) {
65
- var indent = "";
66
- for (var i = 0; i < this.indent; i++) {
67
- indent += " ";
68
- }
69
- var lines = line.split(/\r?\n/);
70
- for (var _i = 0, lines_1 = lines; _i < lines_1.length; _i++) {
71
- var line_1 = lines_1[_i];
72
- // remove whitespace so we can ensure we correctly end with a comma
73
- this.lines.push(indent + (line_1 === null || line_1 === void 0 ? void 0 : line_1.trim()));
74
- }
75
- };
76
- CodeTextWriter.prototype.write = function (text) {
77
- var lines = text.split(/\r?\n/);
78
- if (lines.length > 1) {
79
- for (var i = 0; i < lines.length; i++) {
80
- this.writeLine(lines[i]);
81
- }
82
- }
83
- else {
84
- if (this.lines.length == 0) {
85
- this.writeLine(text);
86
- }
87
- else {
88
- var lastLine = this.lines[this.lines.length - 1];
89
- this.lines[this.lines.length - 1] = lastLine + text;
90
- }
91
- }
92
- };
93
- // beginArray() {
94
- // this.writeLine("[");
95
- // this.indent++;
96
- // }
97
- // insertArrayItem(item: string) {
98
- // this.writeLine(item + ",");
99
- // }
100
- // endArray() {
101
- // }
102
- CodeTextWriter.prototype.flush = function () {
103
- var str = this.toString();
104
- this.lines.length = 0;
105
- return str;
106
- // if (this.lines.length === 0) return;
107
- // if (!fs.existsSync(directory)) {
108
- // fs.mkdirSync(directory, { recursive: true });
109
- // }
110
- // console.log("Writing " + filename + " to " + directory);
111
- // const content = this.toString();
112
- // this.lines.length = 0;
113
- // const fullPath = directory + "/" + filename;
114
- // fs.writeFileSync(fullPath, content);
115
- // return fs.realpathSync(fullPath);
116
- };
117
- CodeTextWriter.prototype.clear = function () {
118
- this.lines.length = 0;
119
- };
120
- CodeTextWriter.prototype.toString = function () {
121
- return this.lines.join("\r");
122
- };
123
- return CodeTextWriter;
124
- }());
125
- exports.CodeTextWriter = CodeTextWriter;
126
- function handleDeletedFile(filePath, types) {
127
- if (!fs.existsSync(filePath)) {
128
- var infos = types[filePath];
129
- if (!infos)
130
- return;
131
- for (var i = 0; i < infos.length; i++) {
132
- var info = infos[i];
133
- // check if the source file is in any other file
134
- var found = false;
135
- for (var otherSourceFile in types) {
136
- if (otherSourceFile == filePath)
137
- continue;
138
- for (var _i = 0, _a = types[otherSourceFile]; _i < _a.length; _i++) {
139
- var otherInfo = _a[_i];
140
- if (otherInfo.filePath == info.filePath) {
141
- found = true;
142
- break;
143
- }
144
- }
145
- if (found)
146
- break;
147
- if (fs.existsSync(info.filePath)) {
148
- fs.rmSync(info.filePath);
149
- (0, commands_1.sendFileDeletedCommand)(info.filePath);
150
- infos.splice(i, 1);
151
- i--;
152
- }
153
- }
154
- }
155
- }
156
- }
157
- exports.handleDeletedFile = handleDeletedFile;
158
- function runFromFile(writer, path) {
159
- if (!fs.existsSync(path)) {
160
- console.error("File not found", path);
161
- return;
162
- }
163
- var code = (0, fs_1.readFileSync)(path).toString();
164
- var compiler = new Compiler();
165
- compiler.compile(writer, code, path);
166
- return compiler;
167
- }
168
- exports.runFromFile = runFromFile;
5
+ var base_compiler_1 = require("./base-compiler");
6
+ /** Typescript Walker */
169
7
  var Compiler = /** @class */ (function () {
170
8
  function Compiler() {
171
9
  }
172
10
  Compiler.prototype.compile = function (writer, code, sourceFilePath) {
173
11
  var file = "needle_compiled.ts";
174
- var sourceFile = ts.createSourceFile(file, code, ts.ScriptTarget.ES2015, true, /*setParentNodes */ ts.ScriptKind.TS);
12
+ var sourceFile = ts.createSourceFile(file, code, ts.ScriptTarget.ES2015, true, ts.ScriptKind.TS);
175
13
  var prog = ts.createProgram([file], {});
176
14
  this.run(prog, writer, sourceFile, sourceFilePath);
177
15
  };
178
16
  Compiler.prototype.run = function (prog, writer, sourceFile, filePath) {
179
17
  var _this = this;
180
18
  console.log("Starting compilation of " + filePath);
19
+ // Pre-scan: collect enum declarations so type resolution works for class fields
20
+ ts.forEachChild(sourceFile, function (node) {
21
+ if (ts.isEnumDeclaration(node)) {
22
+ _this.visitEnumDeclaration(node, writer);
23
+ }
24
+ });
181
25
  writer.begin(filePath);
182
26
  ts.forEachChild(sourceFile, function (node) {
183
27
  _this.visit(filePath, node, writer);
184
28
  });
185
29
  writer.end(filePath);
186
30
  };
31
+ Compiler.prototype.visitEnumDeclaration = function (node, writer) {
32
+ var name = node.name.getText();
33
+ var members = [];
34
+ var nextValue = 0;
35
+ for (var _i = 0, _a = node.members; _i < _a.length; _i++) {
36
+ var member = _a[_i];
37
+ var memberName = member.name.getText();
38
+ var value = void 0;
39
+ if (member.initializer) {
40
+ if (ts.isNumericLiteral(member.initializer)) {
41
+ value = parseFloat(member.initializer.getText());
42
+ nextValue = value + 1;
43
+ }
44
+ else if (ts.isPrefixUnaryExpression(member.initializer) &&
45
+ ts.isNumericLiteral(member.initializer.operand)) {
46
+ value = parseFloat(ts.tokenToString(member.initializer.operator) + member.initializer.operand.getText());
47
+ nextValue = value + 1;
48
+ }
49
+ else {
50
+ // String or computed value — store as-is
51
+ value = member.initializer.getText();
52
+ }
53
+ }
54
+ else {
55
+ value = nextValue++;
56
+ }
57
+ members.push({ name: memberName, value: value });
58
+ }
59
+ this.debugLog("[COMPILER] ENUM", name, members);
60
+ writer.registerEnum(name, members);
61
+ };
187
62
  Compiler.prototype.visit = function (filePath, node, writer) {
188
63
  var _this = this;
189
64
  if (ts.isClassDeclaration(node)) {
@@ -218,7 +93,7 @@ var Compiler = /** @class */ (function () {
218
93
  }
219
94
  }
220
95
  }
221
- this.debugLog("CLASS START", name, baseTypes);
96
+ this.debugLog("[COMPILER] CLASS START", name, baseTypes);
222
97
  var res = writer.startNewType(filePath, name, baseTypes, this.getComments(node));
223
98
  if (res === false) {
224
99
  this.debugLog("CLASS SKIPPED", name);
@@ -228,7 +103,7 @@ var Compiler = /** @class */ (function () {
228
103
  _this.visit(filePath, node, writer);
229
104
  });
230
105
  writer.endNewType(filePath, name);
231
- this.debugLog("CLASS END", name, "\n");
106
+ this.debugLog("[COMPILER] CLASS END", name, "\n");
232
107
  };
233
108
  Compiler.prototype.visitPropertyDeclaration = function (node, writer) {
234
109
  var name = node.name.getText();
@@ -239,26 +114,39 @@ var Compiler = /** @class */ (function () {
239
114
  if (!type && node.initializer) {
240
115
  type = this.tryResolveTypeFromExpression(node.initializer, writer);
241
116
  }
242
- this.debugLog("PROPERTY", Visibility[visibility], name, isArray, type, initialValue);
117
+ this.debugLog("[COMPILER] PROPERTY", base_compiler_1.Visibility[visibility], name, isArray, type, initialValue);
118
+ if (!type)
119
+ return; // cannot determine type — skip the field
243
120
  writer.writeMember(visibility, name, isArray, type, initialValue, this.getComments(node));
244
121
  };
245
122
  Compiler.prototype.visitMethodDeclaration = function (node, writer) {
246
123
  var _a;
247
124
  var name = node.name.getText();
248
125
  var visibility = this.getVisibility(node);
126
+ // Skip static and abstract methods — they don't map to Unity MonoBehaviour stubs
127
+ if (node.modifiers) {
128
+ for (var _i = 0, _b = node.modifiers; _i < _b.length; _i++) {
129
+ var modifier = _b[_i];
130
+ if (modifier.kind === ts.SyntaxKind.StaticKeyword ||
131
+ modifier.kind === ts.SyntaxKind.AbstractKeyword) {
132
+ return;
133
+ }
134
+ }
135
+ }
249
136
  var returnType = this.resolveType(node.type, writer);
250
137
  var args;
251
138
  if ((_a = node.parameters) === null || _a === void 0 ? void 0 : _a.length) {
252
139
  args = this.resolveParameters(node.parameters, writer);
253
140
  }
254
- this.debugLog("METHOD", Visibility[visibility], name, returnType, args);
141
+ this.debugLog("[COMPILER] METHOD", base_compiler_1.Visibility[visibility], name, returnType, args);
255
142
  writer.writeMethod(visibility, name, returnType, args, this.getComments(node));
256
143
  };
257
144
  Compiler.prototype.resolveParameters = function (parameters, writer) {
258
145
  var result = [];
259
146
  for (var _i = 0, parameters_1 = parameters; _i < parameters_1.length; _i++) {
260
147
  var parameter = parameters_1[_i];
261
- var name_1 = parameter.name.getText();
148
+ // Destructured parameters (e.g. { xr }) get a generic name
149
+ var name_1 = ts.isObjectBindingPattern(parameter.name) ? "obj" : parameter.name.getText();
262
150
  var type = this.resolveType(parameter.type, writer);
263
151
  var defaultValue = parameter.initializer && this.resolveExpression(parameter.initializer, writer);
264
152
  result.push({ name: name_1, type: type, defaultValue: defaultValue });
@@ -293,17 +181,17 @@ var Compiler = /** @class */ (function () {
293
181
  for (var _i = 0, _a = node.modifiers; _i < _a.length; _i++) {
294
182
  var modifier = _a[_i];
295
183
  if (modifier.kind === ts.SyntaxKind.PublicKeyword) {
296
- return Visibility.Public;
184
+ return base_compiler_1.Visibility.Public;
297
185
  }
298
186
  else if (modifier.kind === ts.SyntaxKind.ProtectedKeyword) {
299
- return Visibility.Protected;
187
+ return base_compiler_1.Visibility.Protected;
300
188
  }
301
189
  else if (modifier.kind === ts.SyntaxKind.PrivateKeyword) {
302
- return Visibility.Private;
190
+ return base_compiler_1.Visibility.Private;
303
191
  }
304
192
  }
305
193
  }
306
- return Visibility.Public;
194
+ return base_compiler_1.Visibility.Public;
307
195
  };
308
196
  Compiler.prototype.tryResolveTypeFromExpression = function (exp, write) {
309
197
  var _a;
@@ -319,15 +207,27 @@ var Compiler = /** @class */ (function () {
319
207
  }
320
208
  }
321
209
  var typeName = exp.expression.getText();
322
- // const typeName = exp.expression.getText();
323
- // console.log("NEW EXPRESSION", typeName);
324
- // const args = exp.arguments?.map(arg => this.resolveExpression(arg, write));
325
- // write.writeNewTypeExpression(typeName, args);
326
- return typeName;
210
+ return this.resolveTypeFromString(typeName, write);
211
+ }
212
+ // Infer type from literal initializers (no explicit type annotation)
213
+ if (ts.isNumericLiteral(exp)) {
214
+ // e.g. speed = 5 or ratio = 5.5 → number → float
215
+ return this.resolveTypeFromString("number", write);
216
+ }
217
+ if (ts.isStringLiteral(exp)) {
218
+ // e.g. label = "hello" → string
219
+ return this.resolveTypeFromString("string", write);
220
+ }
221
+ if (exp.kind === ts.SyntaxKind.TrueKeyword || exp.kind === ts.SyntaxKind.FalseKeyword) {
222
+ // e.g. active = true → boolean → bool
223
+ return this.resolveTypeFromString("boolean", write);
224
+ }
225
+ if (ts.isPrefixUnaryExpression(exp) && ts.isNumericLiteral(exp.operand)) {
226
+ // e.g. speed = -5 or ratio = -1.5 → number → float
227
+ return this.resolveTypeFromString("number", write);
327
228
  }
328
229
  };
329
230
  Compiler.prototype.resolveType = function (typeNode, writer) {
330
- var _a;
331
231
  if (!typeNode)
332
232
  return undefined;
333
233
  // check if its an array
@@ -375,24 +275,26 @@ var Compiler = /** @class */ (function () {
375
275
  // }
376
276
  console.error("!!!!! ----- Unknown array type", typeNode.getText());
377
277
  }
378
- var type = typeNode.getText();
379
- var baseTypes = [];
380
- if (typeNode.kind === ts.SyntaxKind.TypeReference) {
381
- var typeReference = typeNode;
382
- if ((_a = typeReference.typeArguments) === null || _a === void 0 ? void 0 : _a.length) {
383
- for (var _i = 0, _b = typeReference.typeArguments; _i < _b.length; _i++) {
384
- var arg = _b[_i];
385
- var type_1 = this.resolveType(arg, writer);
386
- if (type_1) {
387
- baseTypes.push(type_1);
388
- }
278
+ // Union types: Object3D | null | undefined → resolve the first concrete member
279
+ if (ts.isUnionTypeNode(typeNode)) {
280
+ for (var _i = 0, _a = typeNode.types; _i < _a.length; _i++) {
281
+ var member = _a[_i];
282
+ if (member.kind !== ts.SyntaxKind.NullKeyword &&
283
+ member.kind !== ts.SyntaxKind.UndefinedKeyword) {
284
+ return this.resolveType(member, writer);
389
285
  }
390
286
  }
287
+ return undefined;
288
+ }
289
+ // TypeReference: use base name only (strips generic args like EventList<void> → EventList)
290
+ if (typeNode.kind === ts.SyntaxKind.TypeReference) {
291
+ var typeReference = typeNode;
292
+ return this.resolveTypeFromString(typeReference.typeName.getText(), writer);
391
293
  }
392
- return this.resolveTypeFromString(type, writer);
294
+ return this.resolveTypeFromString(typeNode.getText(), writer);
393
295
  };
394
296
  Compiler.prototype.resolveTypeFromString = function (type, writer) {
395
- var resolved = writer.resolveTypeName(type);
297
+ var resolved = writer.resolveCSharpTypeName(type);
396
298
  return resolved || type;
397
299
  };
398
300
  Compiler.prototype.isArrayType = function (typeNode) {
@@ -436,6 +338,9 @@ var Compiler = /** @class */ (function () {
436
338
  else if (ts.isIdentifier(node)) {
437
339
  return this.resolveTypeFromString(node.text, writer);
438
340
  }
341
+ else if (node.kind === ts.SyntaxKind.NullKeyword) {
342
+ return this.resolveTypeFromString(node.getText(), writer);
343
+ }
439
344
  // is true or false
440
345
  else if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) {
441
346
  return this.resolveTypeFromString(node.getText(), writer);
@@ -463,7 +368,7 @@ var Compiler = /** @class */ (function () {
463
368
  else if (ts.isPrefixUnaryExpression(node)) {
464
369
  var operand = this.resolveExpression(node.operand, writer);
465
370
  if (operand) {
466
- return node.operator + operand;
371
+ return ts.tokenToString(node.operator) + operand;
467
372
  }
468
373
  }
469
374
  else if (ts.isPostfixUnaryExpression(node)) {
@@ -494,6 +399,7 @@ var Compiler = /** @class */ (function () {
494
399
  else if (ts.isNewExpression(node)) {
495
400
  var typeName = this.resolveTypeFromString(node.expression.getText(), writer);
496
401
  if (typeName) {
402
+ console.log("TODO: new expression");
497
403
  var args = node.arguments.map(function (a) { return _this.resolveExpression(a, writer); }).filter(function (a) { return a; });
498
404
  // writer.writeNewTypeExpression(typeName, args);
499
405
  // return;