boperators 0.1.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 +202 -0
- package/dist/cjs/core/BopConfig.js +73 -0
- package/dist/cjs/core/ErrorManager.js +126 -0
- package/dist/cjs/core/OverloadInjector.js +136 -0
- package/dist/cjs/core/OverloadStore.js +622 -0
- package/dist/cjs/core/SourceMap.js +168 -0
- package/dist/cjs/core/helpers/ensureImportedName.js +50 -0
- package/dist/cjs/core/helpers/getImportedNameForSymbol.js +64 -0
- package/dist/cjs/core/helpers/getModuleSpecifier.js +61 -0
- package/dist/cjs/core/helpers/getOperatorStringFromProperty.js +33 -0
- package/dist/cjs/core/helpers/resolveExpressionType.js +30 -0
- package/dist/cjs/core/helpers/unwrapInitializer.js +18 -0
- package/dist/cjs/core/operatorMap.js +95 -0
- package/dist/cjs/core/validateExports.js +87 -0
- package/dist/cjs/index.js +36 -0
- package/dist/cjs/lib/index.js +17 -0
- package/dist/cjs/lib/operatorSymbols.js +37 -0
- package/dist/esm/core/BopConfig.d.ts +34 -0
- package/dist/esm/core/BopConfig.js +65 -0
- package/dist/esm/core/ErrorManager.d.ts +90 -0
- package/dist/esm/core/ErrorManager.js +118 -0
- package/dist/esm/core/OverloadInjector.d.ts +33 -0
- package/dist/esm/core/OverloadInjector.js +129 -0
- package/dist/esm/core/OverloadStore.d.ts +114 -0
- package/dist/esm/core/OverloadStore.js +618 -0
- package/dist/esm/core/SourceMap.d.ts +41 -0
- package/dist/esm/core/SourceMap.js +164 -0
- package/dist/esm/core/helpers/ensureImportedName.d.ts +11 -0
- package/dist/esm/core/helpers/ensureImportedName.js +46 -0
- package/dist/esm/core/helpers/getImportedNameForSymbol.d.ts +8 -0
- package/dist/esm/core/helpers/getImportedNameForSymbol.js +60 -0
- package/dist/esm/core/helpers/getModuleSpecifier.d.ts +2 -0
- package/dist/esm/core/helpers/getModuleSpecifier.js +57 -0
- package/dist/esm/core/helpers/getOperatorStringFromProperty.d.ts +13 -0
- package/dist/esm/core/helpers/getOperatorStringFromProperty.js +30 -0
- package/dist/esm/core/helpers/resolveExpressionType.d.ts +11 -0
- package/dist/esm/core/helpers/resolveExpressionType.js +27 -0
- package/dist/esm/core/helpers/unwrapInitializer.d.ts +9 -0
- package/dist/esm/core/helpers/unwrapInitializer.js +15 -0
- package/dist/esm/core/operatorMap.d.ts +77 -0
- package/dist/esm/core/operatorMap.js +89 -0
- package/dist/esm/core/validateExports.d.ts +30 -0
- package/dist/esm/core/validateExports.js +84 -0
- package/dist/esm/index.d.ts +19 -0
- package/dist/esm/index.js +14 -0
- package/dist/esm/lib/index.d.ts +1 -0
- package/dist/esm/lib/index.js +1 -0
- package/dist/esm/lib/operatorSymbols.d.ts +33 -0
- package/dist/esm/lib/operatorSymbols.js +34 -0
- package/license.txt +8 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
<center>
|
|
2
|
+
|
|
3
|
+
# boperators
|
|
4
|
+
### Operator overloading JavaScript and TypeScript.
|
|
5
|
+
|
|
6
|
+
</center>
|
|
7
|
+
|
|
8
|
+
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.
|
|
9
|
+
|
|
10
|
+
`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.
|
|
11
|
+
|
|
12
|
+
This is the core library and API, and isn't designed to be used directly. Instead, you can use the [Boperators CLI](https://www.npmjs.com/package/@boperators/cli) or our plugins for [compiling with `tsc`](https://www.npmjs.com/package/@boperators/plugin-tsc) or for [running directly with Bun](https://www.npmjs.com/package/@boperators/plugin-bun).
|
|
13
|
+
|
|
14
|
+
We also offer a [TypeScript Language Server plugin](https://www.npmjs.com/package/@boperators/plugin-ts-language-server) for real-time type hinting and intellisense in your IDE, and an [MCP server](https://www.npmjs.com/package/@boperators/mcp-server) to optimize your vibe coding experience.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install -D boperators @boperators/cli @boperators/plugin-ts-language-server
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## Defining Overloads
|
|
25
|
+
|
|
26
|
+
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.
|
|
27
|
+
|
|
28
|
+
### Static Operators
|
|
29
|
+
|
|
30
|
+
Static operators (`+`, `-`, `*`, `/`, `%`, comparisons, logical) are `static readonly` fields with two-parameter functions (LHS and RHS). At least one parameter must match the class type.
|
|
31
|
+
|
|
32
|
+
Arrow functions or function expressions both work for static operators.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
class Vector3 {
|
|
36
|
+
static readonly "+" = [
|
|
37
|
+
(a: Vector3, b: Vector3) => new Vector3(a.x + b.x, a.y + b.y, a.z + b.z),
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
40
|
+
// Multiple overloads for different RHS types
|
|
41
|
+
static readonly "*" = [
|
|
42
|
+
function (a: Vector3, b: Vector3): Vector3 {
|
|
43
|
+
return new Vector3(
|
|
44
|
+
a.y * b.z - a.z * b.y,
|
|
45
|
+
a.z * b.x - a.x * b.z,
|
|
46
|
+
a.x * b.y - a.y * b.x,
|
|
47
|
+
);
|
|
48
|
+
},
|
|
49
|
+
function mutliplyByScalar(a: Vector3, b: number): Vector3 {
|
|
50
|
+
return new Vector3(a.x * b, a.y * b, a.z * b);
|
|
51
|
+
}
|
|
52
|
+
] as const;
|
|
53
|
+
|
|
54
|
+
// Comparison operators must return boolean
|
|
55
|
+
static readonly "==" = [
|
|
56
|
+
(a: Vector3, b: Vector3): boolean => a.length() === b.length(),
|
|
57
|
+
] as const;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Instance Operators
|
|
62
|
+
|
|
63
|
+
Instance operators (`+=`, `-=`, `*=`, `/=`, `%=`, `&&=`, `||=`) are `readonly` instance fields with a single parameter (the RHS). They use `this` to mutate the LHS object and must return `void`.
|
|
64
|
+
|
|
65
|
+
Instance operators **must** use function expressions (not arrow functions), because arrow functions cannot bind `this`.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
class Vector3 {
|
|
69
|
+
readonly "+=" = [
|
|
70
|
+
function (this: Vector3, rhs: Vector3): void {
|
|
71
|
+
this.x += rhs.x;
|
|
72
|
+
this.y += rhs.y;
|
|
73
|
+
this.z += rhs.z;
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
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.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const vec3 = new Vector3(3, 4, 5);
|
|
83
|
+
vec3 += new Vector3(6, 7, 8);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Prefix Unary Operators
|
|
87
|
+
|
|
88
|
+
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.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
class Vector3 {
|
|
92
|
+
static readonly "-" = [
|
|
93
|
+
// two parameters means binary operation: `a - b`
|
|
94
|
+
(a: Vector3, b: Vector3) =>
|
|
95
|
+
new Vector3(a.x - b.x, a.y - b.y, a.z - b.z),
|
|
96
|
+
// single parameter means unary operation, e.g. making a value "negative"
|
|
97
|
+
(a: Vector3) =>
|
|
98
|
+
new Vector3(-a.x, -a.y, -a.z), // unary: -a
|
|
99
|
+
] as const;
|
|
100
|
+
|
|
101
|
+
static readonly "!" = [
|
|
102
|
+
(a: Vector3): boolean =>
|
|
103
|
+
a.x === 0 && a.y === 0 && a.z === 0,
|
|
104
|
+
] as const;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Postfix Unary Operators
|
|
109
|
+
|
|
110
|
+
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.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
class Counter {
|
|
114
|
+
value = 0;
|
|
115
|
+
|
|
116
|
+
readonly "++" = [
|
|
117
|
+
function (this: Counter): void {
|
|
118
|
+
this.value++;
|
|
119
|
+
},
|
|
120
|
+
] as const;
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Using Overloaded Operators Within Definitions
|
|
125
|
+
|
|
126
|
+
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:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
class Expr {
|
|
130
|
+
static readonly "-" = [
|
|
131
|
+
// unary negation
|
|
132
|
+
(inner: Expr): Expr => new Expr.Neg(inner),
|
|
133
|
+
|
|
134
|
+
// binary minus — calls the unary overload and the + overload directly
|
|
135
|
+
(lhs: Expr, rhs: Expr): Expr =>
|
|
136
|
+
lhs + Expr["-"][0](rhs),
|
|
137
|
+
|
|
138
|
+
(lhs: Expr, rhs: number): Expr =>
|
|
139
|
+
lhs + Expr["-"][0](new Expr.Num(rhs)),
|
|
140
|
+
|
|
141
|
+
(lhs: number, rhs: Expr): Expr =>
|
|
142
|
+
new Expr.Num(lhs) + Expr["-"][0](rhs),
|
|
143
|
+
] as const;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
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.
|
|
148
|
+
|
|
149
|
+
## How It Works
|
|
150
|
+
|
|
151
|
+
`boperators` has a two-phase pipeline:
|
|
152
|
+
|
|
153
|
+
1. **Parse**: `OverloadStore` scans all source files for classes with operator-named properties and indexes them by `(operatorKind, lhsType, rhsType)`.
|
|
154
|
+
2. **Transform**: `OverloadInjector` finds binary and unary expressions, looks up matching overloads, and replaces them:
|
|
155
|
+
- **Binary static**: `a + b` becomes `ClassName["+"][0](a, b)`
|
|
156
|
+
- **Binary instance**: `a += b` becomes `a["+="][0].call(a, b)`
|
|
157
|
+
- **Prefix unary**: `-a` becomes `ClassName["-"][1](a)`
|
|
158
|
+
- **Postfix unary**: `x++` becomes `x["++"][0].call(x)`
|
|
159
|
+
|
|
160
|
+
Imports for referenced classes are automatically added where needed.
|
|
161
|
+
|
|
162
|
+
## Supported Operators
|
|
163
|
+
|
|
164
|
+
| Operator | Type | Notes |
|
|
165
|
+
|----------|------|-------|
|
|
166
|
+
| `+` | static | |
|
|
167
|
+
| `-` | static | |
|
|
168
|
+
| `*` | static | |
|
|
169
|
+
| `/` | static | |
|
|
170
|
+
| `%` | static | |
|
|
171
|
+
| `+=` | instance | Must return `void` |
|
|
172
|
+
| `-=` | instance | Must return `void` |
|
|
173
|
+
| `*=` | instance | Must return `void` |
|
|
174
|
+
| `/=` | instance | Must return `void` |
|
|
175
|
+
| `%=` | instance | Must return `void` |
|
|
176
|
+
| `>` | static | Must return `boolean` |
|
|
177
|
+
| `>=` | static | Must return `boolean` |
|
|
178
|
+
| `<` | static | Must return `boolean` |
|
|
179
|
+
| `<=` | static | Must return `boolean` |
|
|
180
|
+
| `==` | static | Must return `boolean` |
|
|
181
|
+
| `===` | static | Must return `boolean` |
|
|
182
|
+
| `!=` | static | Must return `boolean` |
|
|
183
|
+
| `!==` | static | Must return `boolean` |
|
|
184
|
+
| `&&` | static | |
|
|
185
|
+
| `\|\|` | static | |
|
|
186
|
+
| `??` | static | |
|
|
187
|
+
| `&&=` | instance | Must return `void` |
|
|
188
|
+
| `\|\|=` | instance | Must return `void` |
|
|
189
|
+
| `-` (unary) | static | Prefix negation (1 param) |
|
|
190
|
+
| `+` (unary) | static | Prefix plus (1 param) |
|
|
191
|
+
| `!` | static | Prefix logical NOT (1 param) |
|
|
192
|
+
| `~` | static | Prefix bitwise NOT (1 param) |
|
|
193
|
+
| `++` | instance | Postfix increment, must return `void` |
|
|
194
|
+
| `--` | instance | Postfix decrement, must return `void` |
|
|
195
|
+
|
|
196
|
+
## Conflict Detection
|
|
197
|
+
|
|
198
|
+
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).
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ConsoleLogger = void 0;
|
|
7
|
+
exports.loadConfig = loadConfig;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const CONFIG_FILE_NAME = ".bopconf.json";
|
|
11
|
+
// ----- ConsoleLogger -----
|
|
12
|
+
class ConsoleLogger {
|
|
13
|
+
debug(msg) {
|
|
14
|
+
console.debug(`[boperators] ${msg}`);
|
|
15
|
+
}
|
|
16
|
+
info(msg) {
|
|
17
|
+
console.log(`[boperators] ${msg}`);
|
|
18
|
+
}
|
|
19
|
+
warn(msg) {
|
|
20
|
+
console.warn(`[boperators] ${msg}`);
|
|
21
|
+
}
|
|
22
|
+
error(msg) {
|
|
23
|
+
console.error(`[boperators] ${msg}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.ConsoleLogger = ConsoleLogger;
|
|
27
|
+
// ----- Filtered logger -----
|
|
28
|
+
const LOG_LEVELS = ["debug", "info", "warn", "error", "silent"];
|
|
29
|
+
function createFilteredLogger(inner, level) {
|
|
30
|
+
const minIndex = LOG_LEVELS.indexOf(level);
|
|
31
|
+
const noop = () => { };
|
|
32
|
+
return {
|
|
33
|
+
debug: minIndex <= 0 ? inner.debug.bind(inner) : noop,
|
|
34
|
+
info: minIndex <= 1 ? inner.info.bind(inner) : noop,
|
|
35
|
+
warn: minIndex <= 2 ? inner.warn.bind(inner) : noop,
|
|
36
|
+
error: minIndex <= 3 ? inner.error.bind(inner) : noop,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// ----- Config file discovery -----
|
|
40
|
+
function findConfigFile(startDir) {
|
|
41
|
+
let dir = node_path_1.default.resolve(startDir);
|
|
42
|
+
while (true) {
|
|
43
|
+
const candidate = node_path_1.default.join(dir, CONFIG_FILE_NAME);
|
|
44
|
+
try {
|
|
45
|
+
const content = node_fs_1.default.readFileSync(candidate, "utf-8");
|
|
46
|
+
return JSON.parse(content);
|
|
47
|
+
}
|
|
48
|
+
catch (_a) {
|
|
49
|
+
// File not found or invalid JSON — keep walking up
|
|
50
|
+
}
|
|
51
|
+
const parent = node_path_1.default.dirname(dir);
|
|
52
|
+
if (parent === dir)
|
|
53
|
+
break; // filesystem root
|
|
54
|
+
dir = parent;
|
|
55
|
+
}
|
|
56
|
+
return {};
|
|
57
|
+
}
|
|
58
|
+
const DEFAULTS = {
|
|
59
|
+
errorOnWarning: false,
|
|
60
|
+
logLevel: "info",
|
|
61
|
+
};
|
|
62
|
+
function loadConfig(options) {
|
|
63
|
+
var _a, _b;
|
|
64
|
+
const fileConfig = findConfigFile((_a = options === null || options === void 0 ? void 0 : options.searchDir) !== null && _a !== void 0 ? _a : process.cwd());
|
|
65
|
+
const merged = Object.assign(Object.assign(Object.assign({}, DEFAULTS), fileConfig), options === null || options === void 0 ? void 0 : options.overrides);
|
|
66
|
+
const baseLogger = (_b = options === null || options === void 0 ? void 0 : options.logger) !== null && _b !== void 0 ? _b : new ConsoleLogger();
|
|
67
|
+
const logger = createFilteredLogger(baseLogger, merged.logLevel);
|
|
68
|
+
return {
|
|
69
|
+
errorOnWarning: merged.errorOnWarning,
|
|
70
|
+
logLevel: merged.logLevel,
|
|
71
|
+
logger,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ErrorManager = exports.ErrorDescription = void 0;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
/**
|
|
9
|
+
* Stores information about an error or warning for later use.
|
|
10
|
+
*/
|
|
11
|
+
class ErrorDescription {
|
|
12
|
+
/**
|
|
13
|
+
* Create an ErrorDescription.
|
|
14
|
+
*
|
|
15
|
+
* @param errorMessage Custom message text describing the error.
|
|
16
|
+
* @param filePath Error file path.
|
|
17
|
+
* @param lineNumber Error line number in its file.
|
|
18
|
+
* @param codeText Code relevant to the error.q
|
|
19
|
+
*/
|
|
20
|
+
constructor(errorMessage, filePath, lineNumber, codeText) {
|
|
21
|
+
this.errorMessage = errorMessage;
|
|
22
|
+
this.filePath = filePath;
|
|
23
|
+
this.lineNumber = lineNumber;
|
|
24
|
+
this.codeText = codeText;
|
|
25
|
+
this.fileName = node_path_1.default.basename(filePath);
|
|
26
|
+
}
|
|
27
|
+
toString() {
|
|
28
|
+
return `${this.fileName}:${this.lineNumber}: ${this.errorMessage}\n${this.codeText}\n`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.ErrorDescription = ErrorDescription;
|
|
32
|
+
/**
|
|
33
|
+
* Stores errors and warnings in a way that means
|
|
34
|
+
* they can be logged or thrown at a later point.
|
|
35
|
+
*/
|
|
36
|
+
class ErrorManager {
|
|
37
|
+
constructor(config) {
|
|
38
|
+
/**
|
|
39
|
+
* Array of warnings.
|
|
40
|
+
*/
|
|
41
|
+
this._warnings = [];
|
|
42
|
+
/**
|
|
43
|
+
* Array of errors.
|
|
44
|
+
*/
|
|
45
|
+
this._errors = [];
|
|
46
|
+
this._config = config;
|
|
47
|
+
this._errorOnWarning = config.errorOnWarning;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Add an ErrorDescription or a string as a "warning",
|
|
51
|
+
* meaning it will not throw unless `errorOnWarning` is true.
|
|
52
|
+
* @param description Either a string describing the error or a {@link ErrorDescription} instance.
|
|
53
|
+
*/
|
|
54
|
+
addWarning(description) {
|
|
55
|
+
this._warnings.push(description);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Add an ErrorDescription or a string as an "error",
|
|
59
|
+
* meaning it will throw when checked.
|
|
60
|
+
* @param description Either a string describing the error or a {@link ErrorDescription} instance.
|
|
61
|
+
*/
|
|
62
|
+
addError(description) {
|
|
63
|
+
this._errors.push(description);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Gets all warnings as a single string, separated by newlines.
|
|
67
|
+
* @returns String of all warnings.
|
|
68
|
+
*/
|
|
69
|
+
getWarningString() {
|
|
70
|
+
return this._warnings.map((warning) => warning.toString()).join("\n");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Gets all errors as a single string, separated by newlines.
|
|
74
|
+
* @returns String of all errors.
|
|
75
|
+
*/
|
|
76
|
+
getErrorsString() {
|
|
77
|
+
return this._errors.map((error) => error.toString()).join("\n");
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Throws if there are any errors currently registered in the ErrorManager.
|
|
81
|
+
* If `errorOnWarning` is true then it will also throw if there are warnings.
|
|
82
|
+
*/
|
|
83
|
+
throwIfErrors() {
|
|
84
|
+
const shouldThrow = this._errors.length > 0 ||
|
|
85
|
+
(this._errorOnWarning && this._warnings.length > 0);
|
|
86
|
+
if (!shouldThrow)
|
|
87
|
+
return;
|
|
88
|
+
const errorString = this.getErrorsString() +
|
|
89
|
+
(this._errorOnWarning ? this.getWarningString() : "");
|
|
90
|
+
throw new Error(errorString);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Will throw if there are any errors, or if there are warnings and `errorOnWarning` is true.
|
|
94
|
+
* If it does not throw then it will log all wanrnings to the console.
|
|
95
|
+
* @param clearSelf If true, all warnings will be cleared after logging.
|
|
96
|
+
*/
|
|
97
|
+
throwIfErrorsElseLogWarnings(clearSelf = true) {
|
|
98
|
+
this.throwIfErrors();
|
|
99
|
+
if (this._warnings.length > 0) {
|
|
100
|
+
this._config.logger.warn(this.getWarningString());
|
|
101
|
+
}
|
|
102
|
+
if (clearSelf) {
|
|
103
|
+
this.clearWarnings();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Clear out registered warnings to prevent duplicate logging.
|
|
108
|
+
*/
|
|
109
|
+
clearWarnings() {
|
|
110
|
+
this._warnings = [];
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Clear out registered errors.
|
|
114
|
+
*/
|
|
115
|
+
clearErrors() {
|
|
116
|
+
this._errors = [];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Clear all registered errors and warnings.
|
|
120
|
+
*/
|
|
121
|
+
clear() {
|
|
122
|
+
this._errors = [];
|
|
123
|
+
this._warnings = [];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
exports.ErrorManager = ErrorManager;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.OverloadInjector = void 0;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const ts_morph_1 = require("ts-morph");
|
|
9
|
+
const ensureImportedName_1 = require("./helpers/ensureImportedName");
|
|
10
|
+
const getModuleSpecifier_1 = require("./helpers/getModuleSpecifier");
|
|
11
|
+
const resolveExpressionType_1 = require("./helpers/resolveExpressionType");
|
|
12
|
+
const operatorMap_1 = require("./operatorMap");
|
|
13
|
+
const SourceMap_1 = require("./SourceMap");
|
|
14
|
+
class OverloadInjector {
|
|
15
|
+
constructor(
|
|
16
|
+
/**
|
|
17
|
+
* TS Morph project.
|
|
18
|
+
*/
|
|
19
|
+
_project,
|
|
20
|
+
/**
|
|
21
|
+
* Overload store.
|
|
22
|
+
*/
|
|
23
|
+
_overloadStore, logger) {
|
|
24
|
+
this._project = _project;
|
|
25
|
+
this._overloadStore = _overloadStore;
|
|
26
|
+
this._logger = logger;
|
|
27
|
+
}
|
|
28
|
+
overloadFile(file) {
|
|
29
|
+
const sourceFile = file instanceof ts_morph_1.SourceFile
|
|
30
|
+
? file
|
|
31
|
+
: this._project.getSourceFileOrThrow(file);
|
|
32
|
+
const fileName = node_path_1.default.basename(sourceFile.getFilePath());
|
|
33
|
+
const originalText = sourceFile.getFullText();
|
|
34
|
+
let transformCount = 0;
|
|
35
|
+
// Process one innermost binary expression per iteration,
|
|
36
|
+
// re-fetching descendants each time so types resolve correctly
|
|
37
|
+
// after each transformation and AST references stay fresh.
|
|
38
|
+
let changed = true;
|
|
39
|
+
while (changed) {
|
|
40
|
+
changed = false;
|
|
41
|
+
// Reverse DFS pre-order → innermost expressions first
|
|
42
|
+
const binaryExpressions = sourceFile
|
|
43
|
+
.getDescendantsOfKind(ts_morph_1.SyntaxKind.BinaryExpression)
|
|
44
|
+
.reverse();
|
|
45
|
+
for (const expression of binaryExpressions) {
|
|
46
|
+
const operatorKind = expression.getOperatorToken().getKind();
|
|
47
|
+
if (!(0, operatorMap_1.isOperatorSyntaxKind)(operatorKind))
|
|
48
|
+
continue;
|
|
49
|
+
const lhs = expression.getLeft();
|
|
50
|
+
const leftType = (0, resolveExpressionType_1.resolveExpressionType)(lhs);
|
|
51
|
+
const rhs = expression.getRight();
|
|
52
|
+
const rightType = (0, resolveExpressionType_1.resolveExpressionType)(rhs);
|
|
53
|
+
const overloadDesc = this._overloadStore.findOverload(operatorKind, leftType, rightType);
|
|
54
|
+
if (!overloadDesc)
|
|
55
|
+
continue;
|
|
56
|
+
const { className: classNameRaw, classFilePath, operatorString, index, isStatic, } = overloadDesc;
|
|
57
|
+
// Look up the fresh ClassDeclaration from the project
|
|
58
|
+
const classSourceFile = this._project.getSourceFileOrThrow(classFilePath);
|
|
59
|
+
const classDecl = classSourceFile.getClassOrThrow(classNameRaw);
|
|
60
|
+
// Ensure class is imported, get its textual name
|
|
61
|
+
const classModuleSpecifier = (0, getModuleSpecifier_1.getModuleSpecifier)(sourceFile, classSourceFile);
|
|
62
|
+
const className = (0, ensureImportedName_1.ensureImportedName)(sourceFile, classDecl.getSymbol(), classModuleSpecifier);
|
|
63
|
+
// Build the text code to replace the binary operator with the overload call
|
|
64
|
+
const overloadCall = isStatic
|
|
65
|
+
? `${className}["${operatorString}"][${index}](${lhs.getText()}, ${rhs.getText()})`
|
|
66
|
+
: `${lhs.getText()}["${operatorString}"][${index}].call(${lhs.getText()}, ${rhs.getText()})`;
|
|
67
|
+
this._logger.debug(`${fileName}: ${expression.getText()} => ${overloadCall}`);
|
|
68
|
+
expression.replaceWithText(overloadCall);
|
|
69
|
+
transformCount++;
|
|
70
|
+
changed = true;
|
|
71
|
+
break; // re-fetch descendants after each mutation
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Process prefix unary expressions (-x, +x, !x, ~x)
|
|
75
|
+
changed = true;
|
|
76
|
+
while (changed) {
|
|
77
|
+
changed = false;
|
|
78
|
+
const prefixExpressions = sourceFile
|
|
79
|
+
.getDescendantsOfKind(ts_morph_1.SyntaxKind.PrefixUnaryExpression)
|
|
80
|
+
.reverse();
|
|
81
|
+
for (const expression of prefixExpressions) {
|
|
82
|
+
const operatorKind = expression.getOperatorToken();
|
|
83
|
+
if (!(0, operatorMap_1.isPrefixUnaryOperatorSyntaxKind)(operatorKind))
|
|
84
|
+
continue;
|
|
85
|
+
const operand = expression.getOperand();
|
|
86
|
+
const operandType = (0, resolveExpressionType_1.resolveExpressionType)(operand);
|
|
87
|
+
const overloadDesc = this._overloadStore.findPrefixUnaryOverload(operatorKind, operandType);
|
|
88
|
+
if (!overloadDesc)
|
|
89
|
+
continue;
|
|
90
|
+
const { className: classNameRaw, classFilePath, operatorString, index, } = overloadDesc;
|
|
91
|
+
const classSourceFile = this._project.getSourceFileOrThrow(classFilePath);
|
|
92
|
+
const classDecl = classSourceFile.getClassOrThrow(classNameRaw);
|
|
93
|
+
const classModuleSpecifier = (0, getModuleSpecifier_1.getModuleSpecifier)(sourceFile, classSourceFile);
|
|
94
|
+
const className = (0, ensureImportedName_1.ensureImportedName)(sourceFile, classDecl.getSymbol(), classModuleSpecifier);
|
|
95
|
+
const overloadCall = `${className}["${operatorString}"][${index}](${operand.getText()})`;
|
|
96
|
+
this._logger.debug(`${fileName}: ${expression.getText()} => ${overloadCall}`);
|
|
97
|
+
expression.replaceWithText(overloadCall);
|
|
98
|
+
transformCount++;
|
|
99
|
+
changed = true;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Process postfix unary expressions (x++, x--)
|
|
104
|
+
changed = true;
|
|
105
|
+
while (changed) {
|
|
106
|
+
changed = false;
|
|
107
|
+
const postfixExpressions = sourceFile
|
|
108
|
+
.getDescendantsOfKind(ts_morph_1.SyntaxKind.PostfixUnaryExpression)
|
|
109
|
+
.reverse();
|
|
110
|
+
for (const expression of postfixExpressions) {
|
|
111
|
+
const operatorKind = expression.getOperatorToken();
|
|
112
|
+
if (!(0, operatorMap_1.isPostfixUnaryOperatorSyntaxKind)(operatorKind))
|
|
113
|
+
continue;
|
|
114
|
+
const operand = expression.getOperand();
|
|
115
|
+
const operandType = (0, resolveExpressionType_1.resolveExpressionType)(operand);
|
|
116
|
+
const overloadDesc = this._overloadStore.findPostfixUnaryOverload(operatorKind, operandType);
|
|
117
|
+
if (!overloadDesc)
|
|
118
|
+
continue;
|
|
119
|
+
const { operatorString, index } = overloadDesc;
|
|
120
|
+
const overloadCall = `${operand.getText()}["${operatorString}"][${index}].call(${operand.getText()})`;
|
|
121
|
+
this._logger.debug(`${fileName}: ${expression.getText()} => ${overloadCall}`);
|
|
122
|
+
expression.replaceWithText(overloadCall);
|
|
123
|
+
transformCount++;
|
|
124
|
+
changed = true;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (transformCount > 0) {
|
|
129
|
+
this._logger.debug(`${fileName}: ${transformCount} expression${transformCount === 1 ? "" : "s"} transformed`);
|
|
130
|
+
}
|
|
131
|
+
const text = sourceFile.getFullText();
|
|
132
|
+
const sourceMap = new SourceMap_1.SourceMap(originalText, text);
|
|
133
|
+
return { sourceFile, text, sourceMap };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
exports.OverloadInjector = OverloadInjector;
|