boperators 0.2.1 → 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 +86 -74
- package/dist/core/OverloadInjector.js +7 -7
- package/dist/core/OverloadStore.d.ts +0 -8
- package/dist/core/OverloadStore.js +86 -279
- package/dist/core/helpers/getOperatorStringFromMethod.d.ts +13 -0
- package/dist/core/helpers/getOperatorStringFromMethod.js +33 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
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
|

|
|
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
|
|
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`
|
|
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);
|
|
@@ -31,142 +31,152 @@ npm install -D boperators @boperators/cli @boperators/plugin-ts-language-server
|
|
|
31
31
|
|
|
32
32
|
## Defining Overloads
|
|
33
33
|
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
+
Use whichever style you prefer. The examples below use the bracket style.
|
|
39
47
|
|
|
40
|
-
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// Multiple overloads for different RHS types
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
82
|
+
### Instance Operators
|
|
72
83
|
|
|
73
|
-
Instance operators
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
]
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
(a
|
|
111
|
-
|
|
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
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
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
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
(
|
|
150
|
-
|
|
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
|
|
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 `
|
|
164
|
-
- **
|
|
165
|
-
- **Prefix unary**: `-a` becomes `
|
|
166
|
-
- **Postfix unary**: `x++` becomes `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,
|
|
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}"]
|
|
77
|
-
: `${lhs.getText()}["${operatorString}"]
|
|
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,
|
|
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}"]
|
|
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
|
|
136
|
-
const overloadCall = `${operand.getText()}["${operatorString}"]
|
|
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
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
//
|
|
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"}
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
233
|
+
this._addPrefixUnaryOverload(prefixUnarySyntaxKind, className, classType, filePath, method, parameters, operatorString);
|
|
246
234
|
}
|
|
247
235
|
else if (paramCount === 0 && !isStatic && postfixUnarySyntaxKind) {
|
|
248
|
-
this._addPostfixUnaryOverload(postfixUnarySyntaxKind,
|
|
236
|
+
this._addPostfixUnaryOverload(postfixUnarySyntaxKind, className, classType, filePath, method, operatorString);
|
|
249
237
|
}
|
|
250
238
|
else {
|
|
251
|
-
this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload
|
|
252
|
-
`has invalid parameter count (${paramCount}) for this operator context.`,
|
|
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,
|
|
259
|
-
var _a, _b
|
|
246
|
+
_addBinaryOverload(syntaxKind, className, classType, filePath, method, parameters, operatorString, isStatic) {
|
|
247
|
+
var _a, _b;
|
|
260
248
|
let hasWarning = false;
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
? (
|
|
266
|
-
: (
|
|
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.",
|
|
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 =
|
|
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
|
|
275
|
-
`must have a return type of 'boolean', got '${returnType}'.`,
|
|
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
|
|
280
|
-
`must have a return type of 'void', got '${returnType}'.`,
|
|
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 = (
|
|
284
|
-
const lhsMap = (
|
|
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}`,
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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.",
|
|
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 = (
|
|
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}`,
|
|
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 =
|
|
320
|
+
const returnType = method.getReturnType().getText();
|
|
333
321
|
operatorOverloads.set(operandType, {
|
|
334
322
|
isStatic: true,
|
|
335
|
-
className:
|
|
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
|
-
|
|
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,
|
|
338
|
+
_addPostfixUnaryOverload(syntaxKind, className, classType, filePath, method, operatorString) {
|
|
358
339
|
var _a;
|
|
359
340
|
let hasWarning = false;
|
|
360
|
-
const returnType =
|
|
341
|
+
const returnType = method.getReturnType().getText();
|
|
361
342
|
if (returnType !== "void") {
|
|
362
|
-
this._errorManager.addWarning(new ErrorManager_1.ErrorDescription(`Overload
|
|
363
|
-
`must have a return type of 'void', got '${returnType}'.`,
|
|
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}`,
|
|
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:
|
|
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
|
-
|
|
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");
|