boperators 0.2.0 → 0.3.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/README.md CHANGED
@@ -1,20 +1,20 @@
1
1
  <center>
2
2
 
3
3
  # boperators
4
- ### Operator overloading JavaScript and TypeScript.
4
+ ### Operator overloading for JavaScript and TypeScript.
5
5
 
6
6
  ![Sym.JS logo](https://github.com/DiefBell/boperators/blob/653ea138f4dcd1e6b4dd112133a4942f70e91fb3/logo.png)
7
7
 
8
8
  </center>
9
9
 
10
- Operator overloading is a common programming feature that JavaScript lacks. Just something as simple as adding two vectors, we've got to create a `.Add` method or add elements one-at-a-time.
10
+ Operator overloading is a common programming feature that JavaScript lacks. Just something as simple as adding two vectors requires a `.add()` method or element-by-element assignment.
11
11
 
12
- `boperators` finally brings operator overloading to JavaScript by leveraging TypeScript typings. You define one or more overload functions on a class for whichever operators you want, and with magic we search for anywhere you've used overloaded operators and substitute in your functions.
12
+ `boperators` brings operator overloading to JavaScript by leveraging TypeScript typings. You define overloaded methods on a class for whichever operators you want, and at build time we find every usage of those operators and substitute in your method calls.
13
13
 
14
14
  This is the core library and API, and isn't designed to be used directly. Instead, you can use:
15
15
  - The [Boperators CLI](https://www.npmjs.com/package/@boperators/cli);
16
16
  - The [`tsc`](https://www.npmjs.com/package/@boperators/plugin-tsc) plugin;
17
- - The [webpack loader](https://www.npmjs.com/package/@boperators/webpack-loader);
17
+ - The [NextJS/Webpack loader](https://www.npmjs.com/package/@boperators/webpack-loader);
18
18
  - The [Vite plugin](https://www.npmjs.com/package/@boperators/plugin-vite);
19
19
  - The [ESBuild plugin](https://www.npmjs.com/package/@boperators/plugin-esbuild);
20
20
  - The [Bun plugin](https://www.npmjs.com/package/@boperators/plugin-bun) for running directly with Bun.
@@ -31,142 +31,152 @@ npm install -D boperators @boperators/cli @boperators/plugin-ts-language-server
31
31
 
32
32
  ## Defining Overloads
33
33
 
34
- Define overloads as property arrays on your classes, using the operator string as the property name. Overload fields are readonly arrays (with `as const` at the end) so you can define multiple overloads for different types. As long as you don't have overlapping typings between any functions, we can work out which one to use in a given situation.
34
+ Operator overloads are standard TypeScript methods whose name is the operator string. Both string literal names and computed bracket names are supported they are equivalent:
35
35
 
36
- ### Static Operators
36
+ ```typescript
37
+ class Vec2 {
38
+ // String literal name
39
+ static "+"(a: Vec2, b: Vec2): Vec2 { ... }
40
+
41
+ // Computed bracket name — identical behaviour
42
+ static ["+"](a: Vec2, b: Vec2): Vec2 { ... }
43
+ }
44
+ ```
37
45
 
38
- Static operators (`+`, `-`, `*`, `/`, `%`, comparisons, logical) are `static readonly` fields with two-parameter functions (LHS and RHS). At least one parameter must match the class type.
46
+ Use whichever style you prefer. The examples below use the bracket style.
39
47
 
40
- Arrow functions or function expressions both work for static operators.
48
+
49
+ ### Static Operators
50
+
51
+ Static operators (`+`, `-`, `*`, `/`, `%`, comparisons, logical) are `static` methods with two parameters (LHS and RHS). At least one parameter must match the class type.
41
52
 
42
53
  ```typescript
43
54
  class Vector3 {
44
- static readonly "+" = [
45
- (a: Vector3, b: Vector3) => new Vector3(a.x + b.x, a.y + b.y, a.z + b.z),
46
- ] as const;
47
-
48
- // Multiple overloads for different RHS types
49
- static readonly "*" = [
50
- function (a: Vector3, b: Vector3): Vector3 {
55
+ static ["+"](a: Vector3, b: Vector3): Vector3 {
56
+ return new Vector3(a.x + b.x, a.y + b.y, a.z + b.z);
57
+ }
58
+
59
+ // Multiple overloads for different RHS types — use TypeScript overload signatures,
60
+ // then handle all cases in a single implementation.
61
+ static ["*"](a: Vector3, b: Vector3): Vector3;
62
+ static ["*"](a: Vector3, b: number): Vector3;
63
+ static ["*"](a: Vector3, b: Vector3 | number): Vector3 {
64
+ if (b instanceof Vector3) {
51
65
  return new Vector3(
52
66
  a.y * b.z - a.z * b.y,
53
67
  a.z * b.x - a.x * b.z,
54
68
  a.x * b.y - a.y * b.x,
55
69
  );
56
- },
57
- function mutliplyByScalar(a: Vector3, b: number): Vector3 {
58
- return new Vector3(a.x * b, a.y * b, a.z * b);
59
- }
60
- ] as const;
70
+ }
71
+ return new Vector3(a.x * b, a.y * b, a.z * b);
72
+ }
61
73
 
62
74
  // Comparison operators must return boolean
63
- static readonly "==" = [
64
- (a: Vector3, b: Vector3): boolean => a.length() === b.length(),
65
- ] as const;
75
+ static ["=="](a: Vector3, b: Vector3): boolean {
76
+ return a.length() === b.length();
77
+ }
66
78
  }
67
79
  ```
68
80
 
69
- ### Instance Operators
70
81
 
71
- Instance operators (`+=`, `-=`, `*=`, `/=`, `%=`, `&&=`, `||=`) are `readonly` instance fields with a single parameter (the RHS). They use `this` to mutate the LHS object and must return `void`.
82
+ ### Instance Operators
72
83
 
73
- Instance operators **must** use function expressions (not arrow functions), because arrow functions cannot bind `this`.
84
+ Instance operators (`+=`, `-=`, `*=`, `/=`, `%=`, `&&=`, `||=`) are instance methods with a single parameter (the RHS). They use `this` for the LHS and must return `void`.
74
85
 
75
86
  ```typescript
76
87
  class Vector3 {
77
- readonly "+=" = [
78
- function (this: Vector3, rhs: Vector3): void {
79
- this.x += rhs.x;
80
- this.y += rhs.y;
81
- this.z += rhs.z;
82
- },
83
- ];
88
+ ["+="](rhs: Vector3): void {
89
+ this.x += rhs.x;
90
+ this.y += rhs.y;
91
+ this.z += rhs.z;
92
+ }
84
93
  }
85
94
  ```
86
95
 
87
- Unlike with JavaScript primitives, you can declare a variable as `const` and still use assignment operators with this, as they're only mutating the object.
96
+ Unlike with JavaScript primitives, you can declare a variable as `const` and still use assignment operators with it, since they only mutate the object.
88
97
 
89
98
  ```typescript
90
99
  const vec3 = new Vector3(3, 4, 5);
91
100
  vec3 += new Vector3(6, 7, 8);
92
101
  ```
93
102
 
103
+
94
104
  ### Prefix Unary Operators
95
105
 
96
- Prefix unary operators (`-`, `+`, `!`, `~`) are `static readonly` fields with one-parameter functions. The parameter must match the class type. For operators that also have binary forms (`-`, `+`), both binary and unary overloads can coexist in the same array, distinguished by parameter count.
106
+ Prefix unary operators (`-`, `+`, `!`, `~`) are `static` methods with a single parameter matching the class type.
107
+
108
+ For operators that also have a binary form (`-`, `+`), both can live on the same method — just add overload signatures for each, distinguished by parameter count. The implementation then handles all cases.
97
109
 
98
110
  ```typescript
99
111
  class Vector3 {
100
- static readonly "-" = [
101
- // two parameters means binary operation: `a - b`
102
- (a: Vector3, b: Vector3) =>
103
- new Vector3(a.x - b.x, a.y - b.y, a.z - b.z),
104
- // single parameter means unary operation, e.g. making a value "negative"
105
- (a: Vector3) =>
106
- new Vector3(-a.x, -a.y, -a.z), // unary: -a
107
- ] as const;
108
-
109
- static readonly "!" = [
110
- (a: Vector3): boolean =>
111
- a.x === 0 && a.y === 0 && a.z === 0,
112
- ] as const;
112
+ // Unary-only operator
113
+ static ["!"](a: Vector3): boolean {
114
+ return a.x === 0 && a.y === 0 && a.z === 0;
115
+ }
116
+
117
+ // Combined binary + unary on the same operator
118
+ static ["-"](a: Vector3, b: Vector3): Vector3;
119
+ static ["-"](a: Vector3): Vector3;
120
+ static ["-"](a: Vector3, b?: Vector3): Vector3 {
121
+ if (b) return new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
122
+ return new Vector3(-a.x, -a.y, -a.z);
123
+ }
113
124
  }
114
125
  ```
115
126
 
127
+
116
128
  ### Postfix Unary Operators
117
129
 
118
- Postfix unary operators (`++`, `--`) are `readonly` instance fields with zero-parameter functions (only `this`). They mutate the object and must return `void`. Must use function expressions, not arrow functions.
130
+ Postfix unary operators (`++`, `--`) are instance methods with no parameters. They mutate the object via `this` and must return `void`.
119
131
 
120
132
  ```typescript
121
133
  class Counter {
122
134
  value = 0;
123
135
 
124
- readonly "++" = [
125
- function (this: Counter): void {
126
- this.value++;
127
- },
128
- ] as const;
136
+ ["++"](): void {
137
+ this.value++;
138
+ }
129
139
  }
130
140
  ```
131
141
 
142
+
132
143
  ### Using Overloaded Operators Within Definitions
133
144
 
134
- The transform only applies to **consuming code**, not to the overload definitions themselves. If you need to call an overloaded operator inside an overload body (including on the same class), reference the overload array directly:
145
+ The transform only applies to **consuming code**, not to the overload definitions themselves. If you need to call an overloaded operator inside an overload body, call the method directly:
135
146
 
136
147
  ```typescript
137
148
  class Expr {
138
- static readonly "-" = [
139
- // unary negation
140
- (inner: Expr): Expr => new Expr.Neg(inner),
141
-
142
- // binary minus calls the unary overload and the + overload directly
143
- (lhs: Expr, rhs: Expr): Expr =>
144
- lhs + Expr["-"][0](rhs),
145
-
146
- (lhs: Expr, rhs: number): Expr =>
147
- lhs + Expr["-"][0](new Expr.Num(rhs)),
148
-
149
- (lhs: number, rhs: Expr): Expr =>
150
- new Expr.Num(lhs) + Expr["-"][0](rhs),
151
- ] as const;
149
+ static ["-"](inner: Expr): Expr;
150
+ static ["-"](lhs: Expr, rhs: Expr): Expr;
151
+ static ["-"](lhs: Expr, rhs: number): Expr;
152
+ static ["-"](lhs: number, rhs: Expr): Expr;
153
+ static ["-"](lhs: Expr | number, rhs?: Expr | number): Expr {
154
+ if (rhs === undefined) return new Expr.Neg(lhs as Expr);
155
+
156
+ // Call the overload methods directly — don't use operator syntax here,
157
+ // as the source transform has not yet run on this code.
158
+ const l = typeof lhs === "number" ? new Expr.Num(lhs) : lhs;
159
+ const r = typeof rhs === "number" ? Expr["-"](new Expr.Num(rhs)) : Expr["-"](rhs);
160
+ return Expr["+"](l, r);
161
+ }
152
162
  }
153
163
  ```
154
164
 
155
- Writing `lhs + -rhs` inside the overload body would **not** be transformed, since the source transform has not yet run on this code. Use `ClassName["op"][index](args)` for static overloads and `obj["op"][index].call(obj, args)` for instance overloads.
156
165
 
157
166
  ## How It Works
158
167
 
159
168
  `boperators` has a two-phase pipeline:
160
169
 
161
- 1. **Parse**: `OverloadStore` scans all source files for classes with operator-named properties and indexes them by `(operatorKind, lhsType, rhsType)`.
170
+ 1. **Parse**: `OverloadStore` scans all source files for classes with operator-named methods and indexes them by `(operatorKind, lhsType, rhsType)`.
162
171
  2. **Transform**: `OverloadInjector` finds binary and unary expressions, looks up matching overloads, and replaces them:
163
- - **Binary static**: `a + b` becomes `ClassName["+"][0](a, b)`
164
- - **Binary instance**: `a += b` becomes `a["+="][0].call(a, b)`
165
- - **Prefix unary**: `-a` becomes `ClassName["-"][1](a)`
166
- - **Postfix unary**: `x++` becomes `x["++"][0].call(x)`
172
+ - **Binary static**: `a + b` becomes `Vector3["+"](a, b)`
173
+ - **Instance compound**: `a += b` becomes `a["+="](b)`
174
+ - **Prefix unary**: `-a` becomes `Vector3["-"](a)`
175
+ - **Postfix unary**: `x++` becomes `x["++"]( )`
167
176
 
168
177
  Imports for referenced classes are automatically added where needed.
169
178
 
179
+
170
180
  ## Supported Operators
171
181
 
172
182
  | Operator | Type | Notes |
@@ -201,10 +211,12 @@ Imports for referenced classes are automatically added where needed.
201
211
  | `++` | instance | Postfix increment, must return `void` |
202
212
  | `--` | instance | Postfix decrement, must return `void` |
203
213
 
214
+
204
215
  ## Conflict Detection
205
216
 
206
217
  When parsing overload definitions, if there are duplicate overloads with matching `(operator, lhsType, rhsType)`, a warning is shown (or an error if `--error-on-warning` is set via the CLI).
207
218
 
219
+
208
220
  ## License
209
221
 
210
222
  MIT
@@ -61,7 +61,7 @@ class OverloadInjector {
61
61
  const overloadDesc = this._overloadStore.findOverload(operatorKind, leftType, rightType);
62
62
  if (!overloadDesc)
63
63
  continue;
64
- const { className: classNameRaw, classFilePath, operatorString, index, isStatic, } = overloadDesc;
64
+ const { className: classNameRaw, classFilePath, operatorString, isStatic, } = overloadDesc;
65
65
  // Look up the fresh ClassDeclaration from the project
66
66
  const classSourceFile = this._project.getSourceFileOrThrow(classFilePath);
67
67
  const classDecl = classSourceFile.getClassOrThrow(classNameRaw);
@@ -73,8 +73,8 @@ class OverloadInjector {
73
73
  const className = (0, ensureImportedName_1.ensureImportedName)(sourceFile, classSymbol, classModuleSpecifier);
74
74
  // Build the text code to replace the binary operator with the overload call
75
75
  const overloadCall = isStatic
76
- ? `${className}["${operatorString}"][${index}](${lhs.getText()}, ${rhs.getText()})`
77
- : `${lhs.getText()}["${operatorString}"][${index}].call(${lhs.getText()}, ${rhs.getText()})`;
76
+ ? `${className}["${operatorString}"](${lhs.getText()}, ${rhs.getText()})`
77
+ : `${lhs.getText()}["${operatorString}"](${rhs.getText()})`;
78
78
  this._logger.debug(`${fileName}: ${expression.getText()} => ${overloadCall}`);
79
79
  expression.replaceWithText(overloadCall);
80
80
  transformCount++;
@@ -99,7 +99,7 @@ class OverloadInjector {
99
99
  const overloadDesc = this._overloadStore.findPrefixUnaryOverload(operatorKind, operandType);
100
100
  if (!overloadDesc)
101
101
  continue;
102
- const { className: classNameRaw, classFilePath, operatorString, index, } = overloadDesc;
102
+ const { className: classNameRaw, classFilePath, operatorString, } = overloadDesc;
103
103
  const classSourceFile = this._project.getSourceFileOrThrow(classFilePath);
104
104
  const classDecl = classSourceFile.getClassOrThrow(classNameRaw);
105
105
  const classSymbol = classDecl.getSymbol();
@@ -107,7 +107,7 @@ class OverloadInjector {
107
107
  throw new Error(`No symbol for class "${classNameRaw}"`);
108
108
  const classModuleSpecifier = (0, getModuleSpecifier_1.getModuleSpecifier)(sourceFile, classSourceFile);
109
109
  const className = (0, ensureImportedName_1.ensureImportedName)(sourceFile, classSymbol, classModuleSpecifier);
110
- const overloadCall = `${className}["${operatorString}"][${index}](${operand.getText()})`;
110
+ const overloadCall = `${className}["${operatorString}"](${operand.getText()})`;
111
111
  this._logger.debug(`${fileName}: ${expression.getText()} => ${overloadCall}`);
112
112
  expression.replaceWithText(overloadCall);
113
113
  transformCount++;
@@ -132,8 +132,8 @@ class OverloadInjector {
132
132
  const overloadDesc = this._overloadStore.findPostfixUnaryOverload(operatorKind, operandType);
133
133
  if (!overloadDesc)
134
134
  continue;
135
- const { operatorString, index } = overloadDesc;
136
- const overloadCall = `${operand.getText()}["${operatorString}"][${index}].call(${operand.getText()})`;
135
+ const { operatorString } = overloadDesc;
136
+ const overloadCall = `${operand.getText()}["${operatorString}"]()`;
137
137
  this._logger.debug(`${fileName}: ${expression.getText()} => ${overloadCall}`);
138
138
  expression.replaceWithText(overloadCall);
139
139
  transformCount++;
@@ -21,7 +21,6 @@ export type OverloadDescription = {
21
21
  className: string;
22
22
  classFilePath: string;
23
23
  operatorString: string;
24
- index: number;
25
24
  returnType: string;
26
25
  };
27
26
  /**
@@ -33,7 +32,6 @@ export type OverloadInfo = {
33
32
  className: string;
34
33
  classFilePath: string;
35
34
  operatorString: string;
36
- index: number;
37
35
  isStatic: boolean;
38
36
  /** LHS type name (binary overloads only). */
39
37
  lhsType?: string;
@@ -94,12 +92,6 @@ export declare class OverloadStore extends Map<OperatorSyntaxKind, Map<LhsTypeNa
94
92
  private _addBinaryOverload;
95
93
  private _addPrefixUnaryOverload;
96
94
  private _addPostfixUnaryOverload;
97
- /**
98
- * Extracts overload info from a property's type annotation instead of its
99
- * initializer. This handles `.d.ts` declaration files where the array
100
- * literal (`= [...] as const`) has been replaced by a readonly tuple type.
101
- */
102
- private _addOverloadsFromTypeAnnotation;
103
95
  /**
104
96
  * Returns the type hierarchy chain for a given type name:
105
97
  * [self, parent, grandparent, ...]. Primitives like "number"
@@ -4,9 +4,8 @@ exports.OverloadStore = void 0;
4
4
  const ts_morph_1 = require("ts-morph");
5
5
  const operatorSymbols_1 = require("../lib/operatorSymbols");
6
6
  const ErrorManager_1 = require("./ErrorManager");
7
- const getOperatorStringFromProperty_1 = require("./helpers/getOperatorStringFromProperty");
7
+ const getOperatorStringFromMethod_1 = require("./helpers/getOperatorStringFromMethod");
8
8
  const resolveExpressionType_1 = require("./helpers/resolveExpressionType");
9
- const unwrapInitializer_1 = require("./helpers/unwrapInitializer");
10
9
  const operatorMap_1 = require("./operatorMap");
11
10
  class OverloadStore extends Map {
12
11
  constructor(project, errorManager, logger) {
@@ -159,15 +158,39 @@ class OverloadStore extends Map {
159
158
  this._parsedFiles.add(filePath);
160
159
  const classes = sourceFile.getClasses();
161
160
  classes.forEach((classDecl) => {
161
+ const className = classDecl.getName();
162
+ if (!className)
163
+ return; // skip anonymous classes
162
164
  const classType = (0, resolveExpressionType_1.normalizeTypeName)(classDecl.getType().getText());
163
- classDecl.getProperties().forEach((property) => {
164
- var _a, _b;
165
- if (!ts_morph_1.Node.isPropertyDeclaration(property))
166
- return;
167
- const isStatic = property.isStatic();
168
- const operatorString = (0, getOperatorStringFromProperty_1.getOperatorStringFromProperty)(property);
165
+ // Group method declarations by operator string.
166
+ // For each implementation, we use its overload signatures for per-type discrimination.
167
+ // If there are no overload signatures (simple one-signature methods), we use the
168
+ // implementation itself. In .d.ts files getMethods() returns declaration-only nodes with
169
+ // no body and no overloads, so they are naturally pushed as-is and treated as individual sigs.
170
+ const methodGroups = new Map();
171
+ for (const method of classDecl.getMethods()) {
172
+ const operatorString = (0, getOperatorStringFromMethod_1.getOperatorStringFromMethod)(method);
169
173
  if (!operatorString || !operatorSymbols_1.operatorSymbols.includes(operatorString))
174
+ continue;
175
+ let group = methodGroups.get(operatorString);
176
+ if (!group) {
177
+ group = [];
178
+ methodGroups.set(operatorString, group);
179
+ }
180
+ // Use individual overload signatures for per-type discrimination.
181
+ // Fall back to the implementation itself when there are no overloads.
182
+ const overloadSigs = method.getOverloads();
183
+ group.push(...(overloadSigs.length > 0 ? overloadSigs : [method]));
184
+ }
185
+ methodGroups.forEach((methods, operatorString) => {
186
+ // All entries are either overload signatures (no body) or implementations
187
+ // with no overloads. Use the no-body ones preferentially; if all have bodies
188
+ // (implementation-only methods), use them directly.
189
+ const overloadSigs = methods.filter((m) => !m.hasBody());
190
+ const sigsToProcess = overloadSigs.length > 0 ? overloadSigs : methods;
191
+ if (sigsToProcess.length === 0)
170
192
  return;
193
+ const isStatic = sigsToProcess[0].isStatic();
171
194
  // Look up the operator in all three maps
172
195
  const binarySyntaxKind = operatorMap_1.operatorMap[operatorString];
173
196
  const prefixUnarySyntaxKind = operatorMap_1.prefixUnaryOperatorMap[operatorString];
@@ -176,8 +199,7 @@ class OverloadStore extends Map {
176
199
  !prefixUnarySyntaxKind &&
177
200
  !postfixUnarySyntaxKind)
178
201
  return;
179
- // Property-level static/instance validation.
180
- // Determine what this property should be based on the operator kinds it supports.
202
+ // Validate static/instance context at the method group level
181
203
  const shouldBeStatic = (binarySyntaxKind != null &&
182
204
  !operatorMap_1.instanceOperators.has(binarySyntaxKind)) ||
183
205
  prefixUnarySyntaxKind != null;
@@ -185,127 +207,92 @@ class OverloadStore extends Map {
185
207
  operatorMap_1.instanceOperators.has(binarySyntaxKind)) ||
186
208
  postfixUnarySyntaxKind != null;
187
209
  if ((isStatic && !shouldBeStatic) || (!isStatic && !shouldBeInstance)) {
188
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Expected overload for operator ${operatorString} ` +
189
- `to be ${isStatic ? "a static" : "an instance"} field.`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), property.getText().split("\n")[0]));
210
+ this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Expected overload for operator "${operatorString}" ` +
211
+ `to be ${isStatic ? "a static" : "an instance"} method.`, sigsToProcess[0].getSourceFile().getFilePath(), sigsToProcess[0].getStartLineNumber(), this._minifyString(sigsToProcess[0].getText().split("\n")[0])));
190
212
  return;
191
213
  }
192
- const rawInitializer = property.getInitializer();
193
- // No initializer try type-annotation-based extraction (.d.ts files)
194
- if (!rawInitializer) {
195
- this._addOverloadsFromTypeAnnotation(property, classDecl, classType, filePath, isStatic, operatorString, binarySyntaxKind, prefixUnarySyntaxKind, postfixUnarySyntaxKind);
196
- return;
197
- }
198
- const hasAsConst = ts_morph_1.Node.isAsExpression(rawInitializer) &&
199
- ((_a = rawInitializer.getTypeNode()) === null || _a === void 0 ? void 0 : _a.getText()) === "const";
200
- const initializer = (0, unwrapInitializer_1.unwrapInitializer)(rawInitializer);
201
- if (!initializer || !ts_morph_1.Node.isArrayLiteralExpression(initializer)) {
202
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload field for operator ${operatorString} ` +
203
- "must be an array of overload functions.", property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(property.getName())));
204
- return;
205
- }
206
- if (!hasAsConst) {
207
- this._errorManager.addError(new ErrorManager_1.ErrorDescription(`Overload array for operator ${operatorString} must use "as const". ` +
208
- "Without it, TypeScript widens the array type and loses individual " +
209
- "function signatures, causing type errors in generated code.", property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString((_b = property.getText().split("\n")[0]) !== null && _b !== void 0 ? _b : "")));
210
- return;
211
- }
212
- initializer.getElements().forEach((element, index) => {
213
- if (element.isKind(ts_morph_1.SyntaxKind.ArrowFunction) && !isStatic) {
214
- this._errorManager.addError(new ErrorManager_1.ErrorDescription(`Overload ${index} for operator ${operatorString} must not be an arrow function. ` +
215
- "Use a function expression instead, as arrow functions cannot bind `this` correctly for instance operators.", element.getSourceFile().getFilePath(), element.getStartLineNumber(), this._minifyString(element.getText())));
216
- return;
217
- }
218
- if (!element.isKind(ts_morph_1.SyntaxKind.FunctionExpression) &&
219
- !element.isKind(ts_morph_1.SyntaxKind.FunctionDeclaration) &&
220
- !element.isKind(ts_morph_1.SyntaxKind.ArrowFunction)) {
221
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Expected overload ${index} for operator ${operatorString} to be a function.`, element.getSourceFile().getFilePath(), element.getStartLineNumber(), this._minifyString(element.getText())));
222
- return;
223
- }
224
- // At this point element is guaranteed to be a function-like node
225
- const funcElement = element;
226
- // Exclude `this` pseudo-parameter from count
227
- const parameters = funcElement
214
+ sigsToProcess.forEach((method) => {
215
+ // Exclude the `this` pseudo-parameter if explicitly declared
216
+ const parameters = method
228
217
  .getParameters()
229
218
  .filter((p) => p.getName() !== "this");
230
219
  const paramCount = parameters.length;
231
- // Dispatch by parameter count to determine overload kind
232
220
  if (paramCount === 2 &&
233
221
  isStatic &&
234
222
  binarySyntaxKind &&
235
223
  !operatorMap_1.instanceOperators.has(binarySyntaxKind)) {
236
- this._addBinaryOverload(binarySyntaxKind, classDecl, classType, filePath, property, funcElement, parameters, operatorString, index, true);
224
+ this._addBinaryOverload(binarySyntaxKind, className, classType, filePath, method, parameters, operatorString, true);
237
225
  }
238
226
  else if (paramCount === 1 &&
239
227
  !isStatic &&
240
228
  binarySyntaxKind &&
241
229
  operatorMap_1.instanceOperators.has(binarySyntaxKind)) {
242
- this._addBinaryOverload(binarySyntaxKind, classDecl, classType, filePath, property, funcElement, parameters, operatorString, index, false);
230
+ this._addBinaryOverload(binarySyntaxKind, className, classType, filePath, method, parameters, operatorString, false);
243
231
  }
244
232
  else if (paramCount === 1 && isStatic && prefixUnarySyntaxKind) {
245
- this._addPrefixUnaryOverload(prefixUnarySyntaxKind, classDecl, classType, filePath, property, funcElement, parameters, operatorString, index);
233
+ this._addPrefixUnaryOverload(prefixUnarySyntaxKind, className, classType, filePath, method, parameters, operatorString);
246
234
  }
247
235
  else if (paramCount === 0 && !isStatic && postfixUnarySyntaxKind) {
248
- this._addPostfixUnaryOverload(postfixUnarySyntaxKind, classDecl, classType, filePath, property, funcElement, operatorString, index);
236
+ this._addPostfixUnaryOverload(postfixUnarySyntaxKind, className, classType, filePath, method, operatorString);
249
237
  }
250
238
  else {
251
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload function ${index} for operator ${operatorString} ` +
252
- `has invalid parameter count (${paramCount}) for this operator context.`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(funcElement.getText())));
239
+ this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload signature for operator "${operatorString}" ` +
240
+ `has invalid parameter count (${paramCount}) for this operator context.`, method.getSourceFile().getFilePath(), method.getStartLineNumber(), this._minifyString(method.getText().split("\n")[0])));
253
241
  }
254
242
  });
255
243
  });
256
244
  });
257
245
  }
258
- _addBinaryOverload(syntaxKind, classDecl, classType, filePath, property, element, parameters, operatorString, index, isStatic) {
259
- var _a, _b, _c, _d, _e, _f, _g, _h;
246
+ _addBinaryOverload(syntaxKind, className, classType, filePath, method, parameters, operatorString, isStatic) {
247
+ var _a, _b;
260
248
  let hasWarning = false;
261
- const lhsType = isStatic
262
- ? (0, resolveExpressionType_1.normalizeTypeName)((_b = (_a = parameters[0]) === null || _a === void 0 ? void 0 : _a.getType().getText()) !== null && _b !== void 0 ? _b : "")
263
- : classType;
249
+ // Use the declared type annotation text rather than the resolved type.
250
+ // `parameter.getType().getText()` on an overload signature may return a union
251
+ // of all overload signatures' types (e.g. `Vec2 | undefined`) instead of the
252
+ // type declared in this specific signature (e.g. `Vec2`).
253
+ const getParamTypeName = (p) => {
254
+ var _a, _b, _c;
255
+ return (0, resolveExpressionType_1.normalizeTypeName)((_c = (_b = (_a = p === null || p === void 0 ? void 0 : p.getTypeNode()) === null || _a === void 0 ? void 0 : _a.getText()) !== null && _b !== void 0 ? _b : p === null || p === void 0 ? void 0 : p.getType().getText()) !== null && _c !== void 0 ? _c : "");
256
+ };
257
+ const lhsType = isStatic ? getParamTypeName(parameters[0]) : classType;
264
258
  const rhsType = isStatic
265
- ? (0, resolveExpressionType_1.normalizeTypeName)((_d = (_c = parameters[1]) === null || _c === void 0 ? void 0 : _c.getType().getText()) !== null && _d !== void 0 ? _d : "")
266
- : (0, resolveExpressionType_1.normalizeTypeName)((_f = (_e = parameters[0]) === null || _e === void 0 ? void 0 : _e.getType().getText()) !== null && _f !== void 0 ? _f : "");
259
+ ? getParamTypeName(parameters[1])
260
+ : getParamTypeName(parameters[0]);
267
261
  if (isStatic && lhsType !== classType && rhsType !== classType) {
268
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload for operator ${operatorString} ` +
269
- "must have either LHS or RHS parameter matching its class type.", property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
262
+ this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload for operator "${operatorString}" ` +
263
+ "must have either LHS or RHS parameter matching its class type.", method.getSourceFile().getFilePath(), method.getStartLineNumber(), this._minifyString(method.getText().split("\n")[0])));
270
264
  hasWarning = true;
271
265
  }
272
- const returnType = element.getReturnType().getText();
266
+ const returnType = method.getReturnType().getText();
273
267
  if (operatorMap_1.comparisonOperators.has(syntaxKind) && returnType !== "boolean") {
274
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload function ${index} for comparison operator ${operatorString} ` +
275
- `must have a return type of 'boolean', got '${returnType}'.`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
268
+ this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload for comparison operator "${operatorString}" ` +
269
+ `must have a return type of 'boolean', got '${returnType}'.`, method.getSourceFile().getFilePath(), method.getStartLineNumber(), this._minifyString(method.getText().split("\n")[0])));
276
270
  hasWarning = true;
277
271
  }
278
272
  if (!isStatic && returnType !== "void") {
279
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload function ${index} for instance operator ${operatorString} ` +
280
- `must have a return type of 'void', got '${returnType}'.`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
273
+ this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload for instance operator "${operatorString}" ` +
274
+ `must have a return type of 'void', got '${returnType}'.`, method.getSourceFile().getFilePath(), method.getStartLineNumber(), this._minifyString(method.getText().split("\n")[0])));
281
275
  hasWarning = true;
282
276
  }
283
- const operatorOverloads = (_g = this.get(syntaxKind)) !== null && _g !== void 0 ? _g : new Map();
284
- const lhsMap = (_h = operatorOverloads.get(lhsType)) !== null && _h !== void 0 ? _h : new Map();
277
+ const operatorOverloads = (_a = this.get(syntaxKind)) !== null && _a !== void 0 ? _a : new Map();
278
+ const lhsMap = (_b = operatorOverloads.get(lhsType)) !== null && _b !== void 0 ? _b : new Map();
285
279
  if (lhsMap.has(rhsType)) {
286
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Duplicate overload for operator ${operatorString} with LHS type ${lhsType} and RHS type ${rhsType}`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
280
+ this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Duplicate overload for operator "${operatorString}" with LHS type ${lhsType} and RHS type ${rhsType}`, method.getSourceFile().getFilePath(), method.getStartLineNumber(), this._minifyString(method.getText().split("\n")[0])));
287
281
  hasWarning = true;
288
282
  }
289
283
  if (hasWarning)
290
284
  return;
291
285
  lhsMap.set(rhsType, {
292
286
  isStatic,
293
- className: classDecl.getName(),
287
+ className: className,
294
288
  classFilePath: filePath,
295
289
  operatorString,
296
- index,
297
290
  returnType,
298
291
  });
299
292
  operatorOverloads.set(lhsType, lhsMap);
300
293
  this.set(syntaxKind, operatorOverloads);
301
- const funcName = ts_morph_1.Node.isFunctionExpression(element)
302
- ? element.getName()
303
- : undefined;
304
294
  const sl = this._shortTypeName.bind(this);
305
- const label = funcName
306
- ? `${funcName}(${sl(lhsType)}, ${sl(rhsType)})`
307
- : `(${sl(lhsType)}, ${sl(rhsType)})`;
308
- this._logger.debug(`Loaded ${classDecl.getName()}["${operatorString}"][${index}]: ${label} => ${sl(element.getReturnType().getText())}${isStatic ? " (static)" : " (instance)"}`);
295
+ this._logger.debug(`Loaded ${className}["${operatorString}"]: (${sl(lhsType)}, ${sl(rhsType)}) => ${sl(returnType)}${isStatic ? " (static)" : " (instance)"}`);
309
296
  let fileEntries = this._fileEntries.get(filePath);
310
297
  if (!fileEntries) {
311
298
  fileEntries = [];
@@ -313,40 +300,34 @@ class OverloadStore extends Map {
313
300
  }
314
301
  fileEntries.push({ syntaxKind, lhsType, rhsType });
315
302
  }
316
- _addPrefixUnaryOverload(syntaxKind, classDecl, classType, filePath, property, element, parameters, operatorString, index) {
317
- var _a, _b, _c;
303
+ _addPrefixUnaryOverload(syntaxKind, className, classType, filePath, method, parameters, operatorString) {
304
+ var _a, _b, _c, _d, _e, _f;
318
305
  let hasWarning = false;
319
- const operandType = (0, resolveExpressionType_1.normalizeTypeName)((_b = (_a = parameters[0]) === null || _a === void 0 ? void 0 : _a.getType().getText()) !== null && _b !== void 0 ? _b : "");
306
+ // Use the declared type annotation to avoid union widening from overload groups.
307
+ const operandType = (0, resolveExpressionType_1.normalizeTypeName)((_e = (_c = (_b = (_a = parameters[0]) === null || _a === void 0 ? void 0 : _a.getTypeNode()) === null || _b === void 0 ? void 0 : _b.getText()) !== null && _c !== void 0 ? _c : (_d = parameters[0]) === null || _d === void 0 ? void 0 : _d.getType().getText()) !== null && _e !== void 0 ? _e : "");
320
308
  if (operandType !== classType) {
321
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Prefix unary overload for operator ${operatorString} ` +
322
- "must have its parameter matching its class type.", property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
309
+ this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Prefix unary overload for operator "${operatorString}" ` +
310
+ "must have its parameter matching its class type.", method.getSourceFile().getFilePath(), method.getStartLineNumber(), this._minifyString(method.getText().split("\n")[0])));
323
311
  hasWarning = true;
324
312
  }
325
- const operatorOverloads = (_c = this._prefixUnaryOverloads.get(syntaxKind)) !== null && _c !== void 0 ? _c : new Map();
313
+ const operatorOverloads = (_f = this._prefixUnaryOverloads.get(syntaxKind)) !== null && _f !== void 0 ? _f : new Map();
326
314
  if (operatorOverloads.has(operandType)) {
327
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Duplicate prefix unary overload for operator ${operatorString} with operand type ${operandType}`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
315
+ this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Duplicate prefix unary overload for operator "${operatorString}" with operand type ${operandType}`, method.getSourceFile().getFilePath(), method.getStartLineNumber(), this._minifyString(method.getText().split("\n")[0])));
328
316
  hasWarning = true;
329
317
  }
330
318
  if (hasWarning)
331
319
  return;
332
- const returnType = element.getReturnType().getText();
320
+ const returnType = method.getReturnType().getText();
333
321
  operatorOverloads.set(operandType, {
334
322
  isStatic: true,
335
- className: classDecl.getName(),
323
+ className: className,
336
324
  classFilePath: filePath,
337
325
  operatorString,
338
- index,
339
326
  returnType,
340
327
  });
341
328
  this._prefixUnaryOverloads.set(syntaxKind, operatorOverloads);
342
- const funcName = ts_morph_1.Node.isFunctionExpression(element)
343
- ? element.getName()
344
- : undefined;
345
329
  const sl = this._shortTypeName.bind(this);
346
- const label = funcName
347
- ? `${funcName}(${sl(operandType)})`
348
- : `(${sl(operandType)})`;
349
- this._logger.debug(`Loaded ${classDecl.getName()}["${operatorString}"][${index}]: ${operatorString}${label} => ${sl(returnType)} (prefix unary)`);
330
+ this._logger.debug(`Loaded ${className}["${operatorString}"]: ${operatorString}(${sl(operandType)}) => ${sl(returnType)} (prefix unary)`);
350
331
  let fileEntries = this._prefixUnaryFileEntries.get(filePath);
351
332
  if (!fileEntries) {
352
333
  fileEntries = [];
@@ -354,37 +335,32 @@ class OverloadStore extends Map {
354
335
  }
355
336
  fileEntries.push({ syntaxKind, operandType });
356
337
  }
357
- _addPostfixUnaryOverload(syntaxKind, classDecl, classType, filePath, property, element, operatorString, index) {
338
+ _addPostfixUnaryOverload(syntaxKind, className, classType, filePath, method, operatorString) {
358
339
  var _a;
359
340
  let hasWarning = false;
360
- const returnType = element.getReturnType().getText();
341
+ const returnType = method.getReturnType().getText();
361
342
  if (returnType !== "void") {
362
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload function ${index} for postfix operator ${operatorString} ` +
363
- `must have a return type of 'void', got '${returnType}'.`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
343
+ this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload for postfix operator "${operatorString}" ` +
344
+ `must have a return type of 'void', got '${returnType}'.`, method.getSourceFile().getFilePath(), method.getStartLineNumber(), this._minifyString(method.getText().split("\n")[0])));
364
345
  hasWarning = true;
365
346
  }
366
347
  const operandType = classType;
367
348
  const operatorOverloads = (_a = this._postfixUnaryOverloads.get(syntaxKind)) !== null && _a !== void 0 ? _a : new Map();
368
349
  if (operatorOverloads.has(operandType)) {
369
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Duplicate postfix unary overload for operator ${operatorString} with operand type ${operandType}`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
350
+ this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Duplicate postfix unary overload for operator "${operatorString}" with operand type ${operandType}`, method.getSourceFile().getFilePath(), method.getStartLineNumber(), this._minifyString(method.getText().split("\n")[0])));
370
351
  hasWarning = true;
371
352
  }
372
353
  if (hasWarning)
373
354
  return;
374
355
  operatorOverloads.set(operandType, {
375
356
  isStatic: false,
376
- className: classDecl.getName(),
357
+ className: className,
377
358
  classFilePath: filePath,
378
359
  operatorString,
379
- index,
380
360
  returnType,
381
361
  });
382
362
  this._postfixUnaryOverloads.set(syntaxKind, operatorOverloads);
383
- const funcName = ts_morph_1.Node.isFunctionExpression(element)
384
- ? element.getName()
385
- : undefined;
386
- const label = funcName ? `${funcName}()` : "()";
387
- this._logger.debug(`Loaded ${classDecl.getName()}["${operatorString}"][${index}]: ${this._shortTypeName(operandType)}${operatorString} ${label} (postfix unary)`);
363
+ this._logger.debug(`Loaded ${className}["${operatorString}"]: ${this._shortTypeName(operandType)}${operatorString} () (postfix unary)`);
388
364
  let fileEntries = this._postfixUnaryFileEntries.get(filePath);
389
365
  if (!fileEntries) {
390
366
  fileEntries = [];
@@ -392,175 +368,6 @@ class OverloadStore extends Map {
392
368
  }
393
369
  fileEntries.push({ syntaxKind, operandType });
394
370
  }
395
- /**
396
- * Extracts overload info from a property's type annotation instead of its
397
- * initializer. This handles `.d.ts` declaration files where the array
398
- * literal (`= [...] as const`) has been replaced by a readonly tuple type.
399
- */
400
- _addOverloadsFromTypeAnnotation(property, classDecl, classType, filePath, isStatic, operatorString, binarySyntaxKind, prefixUnarySyntaxKind, postfixUnarySyntaxKind) {
401
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
402
- const propertyType = property.getType();
403
- if (!propertyType.isTuple())
404
- return;
405
- const tupleElements = propertyType.getTupleElements();
406
- const className = classDecl.getName();
407
- if (!className)
408
- return;
409
- const sl = this._shortTypeName.bind(this);
410
- for (let index = 0; index < tupleElements.length; index++) {
411
- const elementType = tupleElements[index];
412
- const callSigs = elementType.getCallSignatures();
413
- if (callSigs.length === 0)
414
- continue;
415
- const sig = callSigs[0];
416
- // Extract parameter types, filtering out the `this` pseudo-parameter
417
- const params = [];
418
- for (const sym of sig.getParameters()) {
419
- const name = sym.getName();
420
- if (name === "this")
421
- continue;
422
- const decl = sym.getValueDeclaration();
423
- if (!decl)
424
- continue;
425
- params.push({
426
- name,
427
- type: (0, resolveExpressionType_1.normalizeTypeName)(decl.getType().getText()),
428
- });
429
- }
430
- const paramCount = params.length;
431
- const returnType = sig.getReturnType().getText();
432
- // --- Binary static ---
433
- if (paramCount === 2 &&
434
- isStatic &&
435
- binarySyntaxKind &&
436
- !operatorMap_1.instanceOperators.has(binarySyntaxKind)) {
437
- const lhsType = params[0].type;
438
- const rhsType = params[1].type;
439
- if (lhsType !== classType && rhsType !== classType) {
440
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload for operator ${operatorString} ` +
441
- "must have either LHS or RHS parameter matching its class type.", filePath, property.getStartLineNumber(), this._minifyString((_a = property.getText().split("\n")[0]) !== null && _a !== void 0 ? _a : "")));
442
- continue;
443
- }
444
- if (operatorMap_1.comparisonOperators.has(binarySyntaxKind) &&
445
- returnType !== "boolean") {
446
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload function ${index} for comparison operator ${operatorString} ` +
447
- `must have a return type of 'boolean', got '${returnType}'.`, filePath, property.getStartLineNumber(), this._minifyString((_b = property.getText().split("\n")[0]) !== null && _b !== void 0 ? _b : "")));
448
- continue;
449
- }
450
- const operatorOverloads = (_c = this.get(binarySyntaxKind)) !== null && _c !== void 0 ? _c : new Map();
451
- const lhsMap = (_d = operatorOverloads.get(lhsType)) !== null && _d !== void 0 ? _d : new Map();
452
- if (lhsMap.has(rhsType))
453
- continue; // duplicate — skip silently for .d.ts
454
- lhsMap.set(rhsType, {
455
- isStatic: true,
456
- className,
457
- classFilePath: filePath,
458
- operatorString,
459
- index,
460
- returnType,
461
- });
462
- operatorOverloads.set(lhsType, lhsMap);
463
- this.set(binarySyntaxKind, operatorOverloads);
464
- this._logger.debug(`Loaded ${className}["${operatorString}"][${index}]: (${sl(lhsType)}, ${sl(rhsType)}) => ${sl(returnType)} (static, from .d.ts)`);
465
- let fileEntries = this._fileEntries.get(filePath);
466
- if (!fileEntries) {
467
- fileEntries = [];
468
- this._fileEntries.set(filePath, fileEntries);
469
- }
470
- fileEntries.push({ syntaxKind: binarySyntaxKind, lhsType, rhsType });
471
- }
472
- // --- Binary instance (compound assignment) ---
473
- else if (paramCount === 1 &&
474
- !isStatic &&
475
- binarySyntaxKind &&
476
- operatorMap_1.instanceOperators.has(binarySyntaxKind)) {
477
- const lhsType = classType;
478
- const rhsType = params[0].type;
479
- if (returnType !== "void") {
480
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload function ${index} for instance operator ${operatorString} ` +
481
- `must have a return type of 'void', got '${returnType}'.`, filePath, property.getStartLineNumber(), this._minifyString((_e = property.getText().split("\n")[0]) !== null && _e !== void 0 ? _e : "")));
482
- continue;
483
- }
484
- const operatorOverloads = (_f = this.get(binarySyntaxKind)) !== null && _f !== void 0 ? _f : new Map();
485
- const lhsMap = (_g = operatorOverloads.get(lhsType)) !== null && _g !== void 0 ? _g : new Map();
486
- if (lhsMap.has(rhsType))
487
- continue;
488
- lhsMap.set(rhsType, {
489
- isStatic: false,
490
- className,
491
- classFilePath: filePath,
492
- operatorString,
493
- index,
494
- returnType,
495
- });
496
- operatorOverloads.set(lhsType, lhsMap);
497
- this.set(binarySyntaxKind, operatorOverloads);
498
- this._logger.debug(`Loaded ${className}["${operatorString}"][${index}]: (${sl(lhsType)}, ${sl(rhsType)}) => void (instance, from .d.ts)`);
499
- let fileEntries = this._fileEntries.get(filePath);
500
- if (!fileEntries) {
501
- fileEntries = [];
502
- this._fileEntries.set(filePath, fileEntries);
503
- }
504
- fileEntries.push({ syntaxKind: binarySyntaxKind, lhsType, rhsType });
505
- }
506
- // --- Prefix unary ---
507
- else if (paramCount === 1 && isStatic && prefixUnarySyntaxKind) {
508
- const operandType = params[0].type;
509
- if (operandType !== classType) {
510
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Prefix unary overload for operator ${operatorString} ` +
511
- "must have its parameter matching its class type.", filePath, property.getStartLineNumber(), this._minifyString((_h = property.getText().split("\n")[0]) !== null && _h !== void 0 ? _h : "")));
512
- continue;
513
- }
514
- const operatorOverloads = (_j = this._prefixUnaryOverloads.get(prefixUnarySyntaxKind)) !== null && _j !== void 0 ? _j : new Map();
515
- if (operatorOverloads.has(operandType))
516
- continue;
517
- operatorOverloads.set(operandType, {
518
- isStatic: true,
519
- className,
520
- classFilePath: filePath,
521
- operatorString,
522
- index,
523
- returnType,
524
- });
525
- this._prefixUnaryOverloads.set(prefixUnarySyntaxKind, operatorOverloads);
526
- this._logger.debug(`Loaded ${className}["${operatorString}"][${index}]: ${operatorString}(${sl(operandType)}) => ${sl(returnType)} (prefix unary, from .d.ts)`);
527
- let fileEntries = this._prefixUnaryFileEntries.get(filePath);
528
- if (!fileEntries) {
529
- fileEntries = [];
530
- this._prefixUnaryFileEntries.set(filePath, fileEntries);
531
- }
532
- fileEntries.push({ syntaxKind: prefixUnarySyntaxKind, operandType });
533
- }
534
- // --- Postfix unary ---
535
- else if (paramCount === 0 && !isStatic && postfixUnarySyntaxKind) {
536
- if (returnType !== "void") {
537
- this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload function ${index} for postfix operator ${operatorString} ` +
538
- `must have a return type of 'void', got '${returnType}'.`, filePath, property.getStartLineNumber(), this._minifyString((_k = property.getText().split("\n")[0]) !== null && _k !== void 0 ? _k : "")));
539
- continue;
540
- }
541
- const operandType = classType;
542
- const operatorOverloads = (_l = this._postfixUnaryOverloads.get(postfixUnarySyntaxKind)) !== null && _l !== void 0 ? _l : new Map();
543
- if (operatorOverloads.has(operandType))
544
- continue;
545
- operatorOverloads.set(operandType, {
546
- isStatic: false,
547
- className,
548
- classFilePath: filePath,
549
- operatorString,
550
- index,
551
- returnType,
552
- });
553
- this._postfixUnaryOverloads.set(postfixUnarySyntaxKind, operatorOverloads);
554
- this._logger.debug(`Loaded ${className}["${operatorString}"][${index}]: ${sl(operandType)}${operatorString} () (postfix unary, from .d.ts)`);
555
- let fileEntries = this._postfixUnaryFileEntries.get(filePath);
556
- if (!fileEntries) {
557
- fileEntries = [];
558
- this._postfixUnaryFileEntries.set(filePath, fileEntries);
559
- }
560
- fileEntries.push({ syntaxKind: postfixUnarySyntaxKind, operandType });
561
- }
562
- }
563
- }
564
371
  /**
565
372
  * Returns the type hierarchy chain for a given type name:
566
373
  * [self, parent, grandparent, ...]. Primitives like "number"
@@ -0,0 +1,13 @@
1
+ import { type MethodDeclaration } from "ts-morph";
2
+ /**
3
+ * Extracts the operator string from a class method declaration, if it
4
+ * represents an operator overload.
5
+ *
6
+ * Handles three method name styles:
7
+ * - `["+"]` — ComputedPropertyName with a StringLiteral expression
8
+ * - `[Operator.PLUS]` — ComputedPropertyName with an enum member expression
9
+ * - `"+"` — StringLiteral method name
10
+ *
11
+ * Returns `undefined` if the method name doesn't resolve to an operator string.
12
+ */
13
+ export declare function getOperatorStringFromMethod(method: MethodDeclaration): string | undefined;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getOperatorStringFromMethod = getOperatorStringFromMethod;
4
+ const ts_morph_1 = require("ts-morph");
5
+ /**
6
+ * Extracts the operator string from a class method declaration, if it
7
+ * represents an operator overload.
8
+ *
9
+ * Handles three method name styles:
10
+ * - `["+"]` — ComputedPropertyName with a StringLiteral expression
11
+ * - `[Operator.PLUS]` — ComputedPropertyName with an enum member expression
12
+ * - `"+"` — StringLiteral method name
13
+ *
14
+ * Returns `undefined` if the method name doesn't resolve to an operator string.
15
+ */
16
+ function getOperatorStringFromMethod(method) {
17
+ const nameNode = method.getNameNode();
18
+ if (nameNode.isKind(ts_morph_1.SyntaxKind.ComputedPropertyName)) {
19
+ const expression = nameNode.getExpression();
20
+ if (expression.isKind(ts_morph_1.SyntaxKind.StringLiteral)) {
21
+ return expression.getLiteralValue();
22
+ }
23
+ // Handle Operator.PLUS style (enum member access)
24
+ const literalValue = expression.getType().getLiteralValue();
25
+ if (typeof literalValue === "string") {
26
+ return literalValue;
27
+ }
28
+ }
29
+ else if (nameNode.isKind(ts_morph_1.SyntaxKind.StringLiteral)) {
30
+ return nameNode.getLiteralValue();
31
+ }
32
+ return undefined;
33
+ }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export type { BopConfFile, BopConfig, BopLogger, LoadConfigOptions, LogLevel, } from "./core/BopConfig";
2
2
  export { ConsoleLogger, loadConfig } from "./core/BopConfig";
3
3
  export { ErrorDescription, ErrorManager } from "./core/ErrorManager";
4
+ export { getOperatorStringFromMethod } from "./core/helpers/getOperatorStringFromMethod";
4
5
  export { getOperatorStringFromProperty } from "./core/helpers/getOperatorStringFromProperty";
5
6
  export { resolveExpressionType } from "./core/helpers/resolveExpressionType";
6
7
  export { unwrapInitializer } from "./core/helpers/unwrapInitializer";
package/dist/index.js CHANGED
@@ -1,13 +1,15 @@
1
1
  "use strict";
2
2
  // Core transformation pipeline
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.SyntaxKind = exports.Project = exports.Node = exports.operatorSymbols = exports.Operator = exports.validateExports = exports.toV3SourceMap = exports.computeEdits = exports.isPrefixUnaryOperatorSyntaxKind = exports.isPostfixUnaryOperatorSyntaxKind = exports.isOperatorSyntaxKind = exports.OverloadStore = exports.OverloadInjector = exports.unwrapInitializer = exports.resolveExpressionType = exports.getOperatorStringFromProperty = exports.ErrorManager = exports.ErrorDescription = exports.loadConfig = exports.ConsoleLogger = void 0;
4
+ exports.SyntaxKind = exports.Project = exports.Node = exports.operatorSymbols = exports.Operator = exports.validateExports = exports.toV3SourceMap = exports.computeEdits = exports.isPrefixUnaryOperatorSyntaxKind = exports.isPostfixUnaryOperatorSyntaxKind = exports.isOperatorSyntaxKind = exports.OverloadStore = exports.OverloadInjector = exports.unwrapInitializer = exports.resolveExpressionType = exports.getOperatorStringFromProperty = exports.getOperatorStringFromMethod = exports.ErrorManager = exports.ErrorDescription = exports.loadConfig = exports.ConsoleLogger = void 0;
5
5
  var BopConfig_1 = require("./core/BopConfig");
6
6
  Object.defineProperty(exports, "ConsoleLogger", { enumerable: true, get: function () { return BopConfig_1.ConsoleLogger; } });
7
7
  Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function () { return BopConfig_1.loadConfig; } });
8
8
  var ErrorManager_1 = require("./core/ErrorManager");
9
9
  Object.defineProperty(exports, "ErrorDescription", { enumerable: true, get: function () { return ErrorManager_1.ErrorDescription; } });
10
10
  Object.defineProperty(exports, "ErrorManager", { enumerable: true, get: function () { return ErrorManager_1.ErrorManager; } });
11
+ var getOperatorStringFromMethod_1 = require("./core/helpers/getOperatorStringFromMethod");
12
+ Object.defineProperty(exports, "getOperatorStringFromMethod", { enumerable: true, get: function () { return getOperatorStringFromMethod_1.getOperatorStringFromMethod; } });
11
13
  var getOperatorStringFromProperty_1 = require("./core/helpers/getOperatorStringFromProperty");
12
14
  Object.defineProperty(exports, "getOperatorStringFromProperty", { enumerable: true, get: function () { return getOperatorStringFromProperty_1.getOperatorStringFromProperty; } });
13
15
  var resolveExpressionType_1 = require("./core/helpers/resolveExpressionType");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "boperators",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "description": "Operator overloading for TypeScript.",
6
6
  "repository": {