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.
Files changed (51) hide show
  1. package/README.md +202 -0
  2. package/dist/cjs/core/BopConfig.js +73 -0
  3. package/dist/cjs/core/ErrorManager.js +126 -0
  4. package/dist/cjs/core/OverloadInjector.js +136 -0
  5. package/dist/cjs/core/OverloadStore.js +622 -0
  6. package/dist/cjs/core/SourceMap.js +168 -0
  7. package/dist/cjs/core/helpers/ensureImportedName.js +50 -0
  8. package/dist/cjs/core/helpers/getImportedNameForSymbol.js +64 -0
  9. package/dist/cjs/core/helpers/getModuleSpecifier.js +61 -0
  10. package/dist/cjs/core/helpers/getOperatorStringFromProperty.js +33 -0
  11. package/dist/cjs/core/helpers/resolveExpressionType.js +30 -0
  12. package/dist/cjs/core/helpers/unwrapInitializer.js +18 -0
  13. package/dist/cjs/core/operatorMap.js +95 -0
  14. package/dist/cjs/core/validateExports.js +87 -0
  15. package/dist/cjs/index.js +36 -0
  16. package/dist/cjs/lib/index.js +17 -0
  17. package/dist/cjs/lib/operatorSymbols.js +37 -0
  18. package/dist/esm/core/BopConfig.d.ts +34 -0
  19. package/dist/esm/core/BopConfig.js +65 -0
  20. package/dist/esm/core/ErrorManager.d.ts +90 -0
  21. package/dist/esm/core/ErrorManager.js +118 -0
  22. package/dist/esm/core/OverloadInjector.d.ts +33 -0
  23. package/dist/esm/core/OverloadInjector.js +129 -0
  24. package/dist/esm/core/OverloadStore.d.ts +114 -0
  25. package/dist/esm/core/OverloadStore.js +618 -0
  26. package/dist/esm/core/SourceMap.d.ts +41 -0
  27. package/dist/esm/core/SourceMap.js +164 -0
  28. package/dist/esm/core/helpers/ensureImportedName.d.ts +11 -0
  29. package/dist/esm/core/helpers/ensureImportedName.js +46 -0
  30. package/dist/esm/core/helpers/getImportedNameForSymbol.d.ts +8 -0
  31. package/dist/esm/core/helpers/getImportedNameForSymbol.js +60 -0
  32. package/dist/esm/core/helpers/getModuleSpecifier.d.ts +2 -0
  33. package/dist/esm/core/helpers/getModuleSpecifier.js +57 -0
  34. package/dist/esm/core/helpers/getOperatorStringFromProperty.d.ts +13 -0
  35. package/dist/esm/core/helpers/getOperatorStringFromProperty.js +30 -0
  36. package/dist/esm/core/helpers/resolveExpressionType.d.ts +11 -0
  37. package/dist/esm/core/helpers/resolveExpressionType.js +27 -0
  38. package/dist/esm/core/helpers/unwrapInitializer.d.ts +9 -0
  39. package/dist/esm/core/helpers/unwrapInitializer.js +15 -0
  40. package/dist/esm/core/operatorMap.d.ts +77 -0
  41. package/dist/esm/core/operatorMap.js +89 -0
  42. package/dist/esm/core/validateExports.d.ts +30 -0
  43. package/dist/esm/core/validateExports.js +84 -0
  44. package/dist/esm/index.d.ts +19 -0
  45. package/dist/esm/index.js +14 -0
  46. package/dist/esm/lib/index.d.ts +1 -0
  47. package/dist/esm/lib/index.js +1 -0
  48. package/dist/esm/lib/operatorSymbols.d.ts +33 -0
  49. package/dist/esm/lib/operatorSymbols.js +34 -0
  50. package/license.txt +8 -0
  51. package/package.json +54 -0
@@ -0,0 +1,618 @@
1
+ import { Node, SourceFile, SyntaxKind, } from "ts-morph";
2
+ import { operatorSymbols } from "../lib/operatorSymbols";
3
+ import { ErrorDescription } from "./ErrorManager";
4
+ import { getOperatorStringFromProperty } from "./helpers/getOperatorStringFromProperty";
5
+ import { unwrapInitializer } from "./helpers/unwrapInitializer";
6
+ import { comparisonOperators, instanceOperators, operatorMap, postfixUnaryOperatorMap, prefixUnaryOperatorMap, } from "./operatorMap";
7
+ export class OverloadStore extends Map {
8
+ constructor(project, errorManager, logger) {
9
+ super();
10
+ this._parsedFiles = new Set();
11
+ /** Tracks which map entries came from which file, for targeted invalidation. */
12
+ this._fileEntries = new Map();
13
+ /** Cache for type hierarchy chains: type name → [self, parent, grandparent, ...] */
14
+ this._typeChainCache = new Map();
15
+ /** Storage for prefix unary overloads: operator → operandType → description */
16
+ this._prefixUnaryOverloads = new Map();
17
+ /** Storage for postfix unary overloads: operator → operandType → description */
18
+ this._postfixUnaryOverloads = new Map();
19
+ /** Tracks which prefix unary entries came from which file, for targeted invalidation. */
20
+ this._prefixUnaryFileEntries = new Map();
21
+ /** Tracks which postfix unary entries came from which file, for targeted invalidation. */
22
+ this._postfixUnaryFileEntries = new Map();
23
+ this._project = project;
24
+ this._errorManager = errorManager;
25
+ this._logger = logger;
26
+ }
27
+ /**
28
+ * Looks up an overload description for the given operator kind and
29
+ * LHS/RHS type pair. Walks up the class hierarchy for both sides
30
+ * when an exact match isn't found.
31
+ */
32
+ findOverload(operatorKind, lhsType, rhsType) {
33
+ const operatorOverloads = this.get(operatorKind);
34
+ if (!operatorOverloads)
35
+ return undefined;
36
+ const lhsChain = this._getTypeChain(lhsType);
37
+ const rhsChain = this._getTypeChain(rhsType);
38
+ // Try each combination, most specific types first
39
+ for (const lhs of lhsChain) {
40
+ const lhsMap = operatorOverloads.get(lhs);
41
+ if (!lhsMap)
42
+ continue;
43
+ for (const rhs of rhsChain) {
44
+ const match = lhsMap.get(rhs);
45
+ if (match)
46
+ return match;
47
+ }
48
+ }
49
+ return undefined;
50
+ }
51
+ /**
52
+ * Looks up a prefix unary overload description for the given operator kind
53
+ * and operand type. Walks up the class hierarchy when an exact match isn't found.
54
+ */
55
+ findPrefixUnaryOverload(operatorKind, operandType) {
56
+ const operatorOverloads = this._prefixUnaryOverloads.get(operatorKind);
57
+ if (!operatorOverloads)
58
+ return undefined;
59
+ const typeChain = this._getTypeChain(operandType);
60
+ for (const t of typeChain) {
61
+ const match = operatorOverloads.get(t);
62
+ if (match)
63
+ return match;
64
+ }
65
+ return undefined;
66
+ }
67
+ /**
68
+ * Looks up a postfix unary overload description for the given operator kind
69
+ * and operand type. Walks up the class hierarchy when an exact match isn't found.
70
+ */
71
+ findPostfixUnaryOverload(operatorKind, operandType) {
72
+ const operatorOverloads = this._postfixUnaryOverloads.get(operatorKind);
73
+ if (!operatorOverloads)
74
+ return undefined;
75
+ const typeChain = this._getTypeChain(operandType);
76
+ for (const t of typeChain) {
77
+ const match = operatorOverloads.get(t);
78
+ if (match)
79
+ return match;
80
+ }
81
+ return undefined;
82
+ }
83
+ /**
84
+ * Returns a flat array of all registered overloads (binary, prefix unary,
85
+ * and postfix unary) with clean type names.
86
+ */
87
+ getAllOverloads() {
88
+ const results = [];
89
+ const sl = this._shortTypeName.bind(this);
90
+ for (const [_syntaxKind, lhsMap] of this) {
91
+ for (const [lhsType, rhsMap] of lhsMap) {
92
+ for (const [rhsType, desc] of rhsMap) {
93
+ results.push(Object.assign(Object.assign({ kind: "binary" }, desc), { lhsType: sl(lhsType), rhsType: sl(rhsType) }));
94
+ }
95
+ }
96
+ }
97
+ for (const [_syntaxKind, operandMap] of this._prefixUnaryOverloads) {
98
+ for (const [operandType, desc] of operandMap) {
99
+ results.push(Object.assign(Object.assign({ kind: "prefixUnary" }, desc), { operandType: sl(operandType) }));
100
+ }
101
+ }
102
+ for (const [_syntaxKind, operandMap] of this._postfixUnaryOverloads) {
103
+ for (const [operandType, desc] of operandMap) {
104
+ results.push(Object.assign(Object.assign({ kind: "postfixUnary" }, desc), { operandType: sl(operandType) }));
105
+ }
106
+ }
107
+ return results;
108
+ }
109
+ /**
110
+ * Removes all overload entries that originated from the given file
111
+ * and marks it as unparsed so it will be re-scanned on the next call
112
+ * to `addOverloadsFromFile`.
113
+ *
114
+ * @returns `true` if the file had any overload entries (meaning files
115
+ * that were transformed using those overloads may now be stale).
116
+ */
117
+ invalidateFile(filePath) {
118
+ var _a, _b, _c, _d;
119
+ this._parsedFiles.delete(filePath);
120
+ this._typeChainCache.clear();
121
+ let hadEntries = false;
122
+ const entries = this._fileEntries.get(filePath);
123
+ if (entries) {
124
+ for (const { syntaxKind, lhsType, rhsType } of entries) {
125
+ (_b = (_a = this.get(syntaxKind)) === null || _a === void 0 ? void 0 : _a.get(lhsType)) === null || _b === void 0 ? void 0 : _b.delete(rhsType);
126
+ }
127
+ this._fileEntries.delete(filePath);
128
+ hadEntries = true;
129
+ }
130
+ const prefixEntries = this._prefixUnaryFileEntries.get(filePath);
131
+ if (prefixEntries) {
132
+ for (const { syntaxKind, operandType } of prefixEntries) {
133
+ (_c = this._prefixUnaryOverloads.get(syntaxKind)) === null || _c === void 0 ? void 0 : _c.delete(operandType);
134
+ }
135
+ this._prefixUnaryFileEntries.delete(filePath);
136
+ hadEntries = true;
137
+ }
138
+ const postfixEntries = this._postfixUnaryFileEntries.get(filePath);
139
+ if (postfixEntries) {
140
+ for (const { syntaxKind, operandType } of postfixEntries) {
141
+ (_d = this._postfixUnaryOverloads.get(syntaxKind)) === null || _d === void 0 ? void 0 : _d.delete(operandType);
142
+ }
143
+ this._postfixUnaryFileEntries.delete(filePath);
144
+ hadEntries = true;
145
+ }
146
+ return hadEntries;
147
+ }
148
+ addOverloadsFromFile(file) {
149
+ const sourceFile = file instanceof SourceFile
150
+ ? file
151
+ : this._project.getSourceFileOrThrow(file);
152
+ const filePath = sourceFile.getFilePath();
153
+ if (this._parsedFiles.has(filePath))
154
+ return;
155
+ this._parsedFiles.add(filePath);
156
+ const classes = sourceFile.getClasses();
157
+ classes.forEach((classDecl) => {
158
+ const classType = classDecl.getType().getText();
159
+ classDecl.getProperties().forEach((property) => {
160
+ var _a, _b;
161
+ if (!Node.isPropertyDeclaration(property))
162
+ return;
163
+ const isStatic = property.isStatic();
164
+ const operatorString = getOperatorStringFromProperty(property);
165
+ if (!operatorString || !operatorSymbols.includes(operatorString))
166
+ return;
167
+ // Look up the operator in all three maps
168
+ const binarySyntaxKind = operatorMap[operatorString];
169
+ const prefixUnarySyntaxKind = prefixUnaryOperatorMap[operatorString];
170
+ const postfixUnarySyntaxKind = postfixUnaryOperatorMap[operatorString];
171
+ if (!binarySyntaxKind &&
172
+ !prefixUnarySyntaxKind &&
173
+ !postfixUnarySyntaxKind)
174
+ return;
175
+ // Property-level static/instance validation.
176
+ // Determine what this property should be based on the operator kinds it supports.
177
+ const shouldBeStatic = (binarySyntaxKind != null &&
178
+ !instanceOperators.has(binarySyntaxKind)) ||
179
+ prefixUnarySyntaxKind != null;
180
+ const shouldBeInstance = (binarySyntaxKind != null &&
181
+ instanceOperators.has(binarySyntaxKind)) ||
182
+ postfixUnarySyntaxKind != null;
183
+ if ((isStatic && !shouldBeStatic) || (!isStatic && !shouldBeInstance)) {
184
+ this._errorManager.addWarning(new ErrorDescription(`Expected overload for operator ${operatorString} ` +
185
+ `to be ${isStatic ? "a static" : "an instance"} field.`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), property.getText().split("\n")[0]));
186
+ return;
187
+ }
188
+ const rawInitializer = property.getInitializer();
189
+ // No initializer — try type-annotation-based extraction (.d.ts files)
190
+ if (!rawInitializer) {
191
+ this._addOverloadsFromTypeAnnotation(property, classDecl, classType, filePath, isStatic, operatorString, binarySyntaxKind, prefixUnarySyntaxKind, postfixUnarySyntaxKind);
192
+ return;
193
+ }
194
+ const hasAsConst = Node.isAsExpression(rawInitializer) &&
195
+ ((_a = rawInitializer.getTypeNode()) === null || _a === void 0 ? void 0 : _a.getText()) === "const";
196
+ const initializer = unwrapInitializer(rawInitializer);
197
+ if (!initializer || !Node.isArrayLiteralExpression(initializer)) {
198
+ this._errorManager.addWarning(new ErrorDescription(`Overload field for operator ${operatorString} ` +
199
+ "must be an array of overload functions.", property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(property.getName())));
200
+ return;
201
+ }
202
+ if (!hasAsConst) {
203
+ this._errorManager.addError(new ErrorDescription(`Overload array for operator ${operatorString} must use "as const". ` +
204
+ "Without it, TypeScript widens the array type and loses individual " +
205
+ "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 : "")));
206
+ return;
207
+ }
208
+ initializer.getElements().forEach((element, index) => {
209
+ if (element.isKind(SyntaxKind.ArrowFunction) && !isStatic) {
210
+ this._errorManager.addError(new ErrorDescription(`Overload ${index} for operator ${operatorString} must not be an arrow function. ` +
211
+ "Use a function expression instead, as arrow functions cannot bind `this` correctly for instance operators.", element.getSourceFile().getFilePath(), element.getStartLineNumber(), this._minifyString(element.getText())));
212
+ return;
213
+ }
214
+ if (!element.isKind(SyntaxKind.FunctionExpression) &&
215
+ !element.isKind(SyntaxKind.FunctionDeclaration) &&
216
+ !element.isKind(SyntaxKind.ArrowFunction)) {
217
+ this._errorManager.addWarning(new ErrorDescription(`Expected overload ${index} for operator ${operatorString} to be a function.`, element.getSourceFile().getFilePath(), element.getStartLineNumber(), this._minifyString(element.getText())));
218
+ return;
219
+ }
220
+ // At this point element is guaranteed to be a function-like node
221
+ const funcElement = element;
222
+ // Exclude `this` pseudo-parameter from count
223
+ const parameters = funcElement
224
+ .getParameters()
225
+ .filter((p) => p.getName() !== "this");
226
+ const paramCount = parameters.length;
227
+ // Dispatch by parameter count to determine overload kind
228
+ if (paramCount === 2 &&
229
+ isStatic &&
230
+ binarySyntaxKind &&
231
+ !instanceOperators.has(binarySyntaxKind)) {
232
+ this._addBinaryOverload(binarySyntaxKind, classDecl, classType, filePath, property, funcElement, parameters, operatorString, index, true);
233
+ }
234
+ else if (paramCount === 1 &&
235
+ !isStatic &&
236
+ binarySyntaxKind &&
237
+ instanceOperators.has(binarySyntaxKind)) {
238
+ this._addBinaryOverload(binarySyntaxKind, classDecl, classType, filePath, property, funcElement, parameters, operatorString, index, false);
239
+ }
240
+ else if (paramCount === 1 && isStatic && prefixUnarySyntaxKind) {
241
+ this._addPrefixUnaryOverload(prefixUnarySyntaxKind, classDecl, classType, filePath, property, funcElement, parameters, operatorString, index);
242
+ }
243
+ else if (paramCount === 0 && !isStatic && postfixUnarySyntaxKind) {
244
+ this._addPostfixUnaryOverload(postfixUnarySyntaxKind, classDecl, classType, filePath, property, funcElement, operatorString, index);
245
+ }
246
+ else {
247
+ this._errorManager.addWarning(new ErrorDescription(`Overload function ${index} for operator ${operatorString} ` +
248
+ `has invalid parameter count (${paramCount}) for this operator context.`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(funcElement.getText())));
249
+ }
250
+ });
251
+ });
252
+ });
253
+ }
254
+ _addBinaryOverload(syntaxKind, classDecl, classType, filePath, property, element, parameters, operatorString, index, isStatic) {
255
+ var _a, _b, _c, _d, _e;
256
+ let hasWarning = false;
257
+ const lhsType = isStatic ? (_a = parameters[0]) === null || _a === void 0 ? void 0 : _a.getType().getText() : classType;
258
+ const rhsType = isStatic
259
+ ? (_b = parameters[1]) === null || _b === void 0 ? void 0 : _b.getType().getText()
260
+ : (_c = parameters[0]) === null || _c === void 0 ? void 0 : _c.getType().getText();
261
+ if (isStatic && lhsType !== classType && rhsType !== classType) {
262
+ this._errorManager.addWarning(new ErrorDescription(`Overload for operator ${operatorString} ` +
263
+ "must have either LHS or RHS parameter matching its class type.", property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
264
+ hasWarning = true;
265
+ }
266
+ const returnType = element.getReturnType().getText();
267
+ if (comparisonOperators.has(syntaxKind) && returnType !== "boolean") {
268
+ this._errorManager.addWarning(new ErrorDescription(`Overload function ${index} for comparison operator ${operatorString} ` +
269
+ `must have a return type of 'boolean', got '${returnType}'.`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
270
+ hasWarning = true;
271
+ }
272
+ if (!isStatic && returnType !== "void") {
273
+ this._errorManager.addWarning(new ErrorDescription(`Overload function ${index} for instance operator ${operatorString} ` +
274
+ `must have a return type of 'void', got '${returnType}'.`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
275
+ hasWarning = true;
276
+ }
277
+ const operatorOverloads = (_d = this.get(syntaxKind)) !== null && _d !== void 0 ? _d : new Map();
278
+ const lhsMap = (_e = operatorOverloads.get(lhsType)) !== null && _e !== void 0 ? _e : new Map();
279
+ if (lhsMap.has(rhsType)) {
280
+ this._errorManager.addWarning(new ErrorDescription(`Duplicate overload for operator ${operatorString} with LHS type ${lhsType} and RHS type ${rhsType}`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
281
+ hasWarning = true;
282
+ }
283
+ if (hasWarning)
284
+ return;
285
+ lhsMap.set(rhsType, {
286
+ isStatic,
287
+ className: classDecl.getName(),
288
+ classFilePath: filePath,
289
+ operatorString,
290
+ index,
291
+ returnType,
292
+ });
293
+ operatorOverloads.set(lhsType, lhsMap);
294
+ this.set(syntaxKind, operatorOverloads);
295
+ const funcName = Node.isFunctionExpression(element)
296
+ ? element.getName()
297
+ : undefined;
298
+ const sl = this._shortTypeName.bind(this);
299
+ const label = funcName
300
+ ? `${funcName}(${sl(lhsType)}, ${sl(rhsType)})`
301
+ : `(${sl(lhsType)}, ${sl(rhsType)})`;
302
+ this._logger.debug(`Loaded ${classDecl.getName()}["${operatorString}"][${index}]: ${label} => ${sl(element.getReturnType().getText())}${isStatic ? " (static)" : " (instance)"}`);
303
+ let fileEntries = this._fileEntries.get(filePath);
304
+ if (!fileEntries) {
305
+ fileEntries = [];
306
+ this._fileEntries.set(filePath, fileEntries);
307
+ }
308
+ fileEntries.push({ syntaxKind, lhsType, rhsType });
309
+ }
310
+ _addPrefixUnaryOverload(syntaxKind, classDecl, classType, filePath, property, element, parameters, operatorString, index) {
311
+ var _a, _b;
312
+ let hasWarning = false;
313
+ const operandType = (_a = parameters[0]) === null || _a === void 0 ? void 0 : _a.getType().getText();
314
+ if (operandType !== classType) {
315
+ this._errorManager.addWarning(new ErrorDescription(`Prefix unary overload for operator ${operatorString} ` +
316
+ "must have its parameter matching its class type.", property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
317
+ hasWarning = true;
318
+ }
319
+ const operatorOverloads = (_b = this._prefixUnaryOverloads.get(syntaxKind)) !== null && _b !== void 0 ? _b : new Map();
320
+ if (operatorOverloads.has(operandType)) {
321
+ this._errorManager.addWarning(new ErrorDescription(`Duplicate prefix unary overload for operator ${operatorString} with operand type ${operandType}`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
322
+ hasWarning = true;
323
+ }
324
+ if (hasWarning)
325
+ return;
326
+ const returnType = element.getReturnType().getText();
327
+ operatorOverloads.set(operandType, {
328
+ isStatic: true,
329
+ className: classDecl.getName(),
330
+ classFilePath: filePath,
331
+ operatorString,
332
+ index,
333
+ returnType,
334
+ });
335
+ this._prefixUnaryOverloads.set(syntaxKind, operatorOverloads);
336
+ const funcName = Node.isFunctionExpression(element)
337
+ ? element.getName()
338
+ : undefined;
339
+ const sl = this._shortTypeName.bind(this);
340
+ const label = funcName
341
+ ? `${funcName}(${sl(operandType)})`
342
+ : `(${sl(operandType)})`;
343
+ this._logger.debug(`Loaded ${classDecl.getName()}["${operatorString}"][${index}]: ${operatorString}${label} => ${sl(returnType)} (prefix unary)`);
344
+ let fileEntries = this._prefixUnaryFileEntries.get(filePath);
345
+ if (!fileEntries) {
346
+ fileEntries = [];
347
+ this._prefixUnaryFileEntries.set(filePath, fileEntries);
348
+ }
349
+ fileEntries.push({ syntaxKind, operandType });
350
+ }
351
+ _addPostfixUnaryOverload(syntaxKind, classDecl, classType, filePath, property, element, operatorString, index) {
352
+ var _a;
353
+ let hasWarning = false;
354
+ const returnType = element.getReturnType().getText();
355
+ if (returnType !== "void") {
356
+ this._errorManager.addWarning(new ErrorDescription(`Overload function ${index} for postfix operator ${operatorString} ` +
357
+ `must have a return type of 'void', got '${returnType}'.`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
358
+ hasWarning = true;
359
+ }
360
+ const operandType = classType;
361
+ const operatorOverloads = (_a = this._postfixUnaryOverloads.get(syntaxKind)) !== null && _a !== void 0 ? _a : new Map();
362
+ if (operatorOverloads.has(operandType)) {
363
+ this._errorManager.addWarning(new ErrorDescription(`Duplicate postfix unary overload for operator ${operatorString} with operand type ${operandType}`, property.getSourceFile().getFilePath(), property.getStartLineNumber(), this._minifyString(element.getText())));
364
+ hasWarning = true;
365
+ }
366
+ if (hasWarning)
367
+ return;
368
+ operatorOverloads.set(operandType, {
369
+ isStatic: false,
370
+ className: classDecl.getName(),
371
+ classFilePath: filePath,
372
+ operatorString,
373
+ index,
374
+ returnType,
375
+ });
376
+ this._postfixUnaryOverloads.set(syntaxKind, operatorOverloads);
377
+ const funcName = Node.isFunctionExpression(element)
378
+ ? element.getName()
379
+ : undefined;
380
+ const label = funcName ? `${funcName}()` : "()";
381
+ this._logger.debug(`Loaded ${classDecl.getName()}["${operatorString}"][${index}]: ${this._shortTypeName(operandType)}${operatorString} ${label} (postfix unary)`);
382
+ let fileEntries = this._postfixUnaryFileEntries.get(filePath);
383
+ if (!fileEntries) {
384
+ fileEntries = [];
385
+ this._postfixUnaryFileEntries.set(filePath, fileEntries);
386
+ }
387
+ fileEntries.push({ syntaxKind, operandType });
388
+ }
389
+ /**
390
+ * Extracts overload info from a property's type annotation instead of its
391
+ * initializer. This handles `.d.ts` declaration files where the array
392
+ * literal (`= [...] as const`) has been replaced by a readonly tuple type.
393
+ */
394
+ _addOverloadsFromTypeAnnotation(property, classDecl, classType, filePath, isStatic, operatorString, binarySyntaxKind, prefixUnarySyntaxKind, postfixUnarySyntaxKind) {
395
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
396
+ const propertyType = property.getType();
397
+ if (!propertyType.isTuple())
398
+ return;
399
+ const tupleElements = propertyType.getTupleElements();
400
+ const className = classDecl.getName();
401
+ if (!className)
402
+ return;
403
+ const sl = this._shortTypeName.bind(this);
404
+ for (let index = 0; index < tupleElements.length; index++) {
405
+ const elementType = tupleElements[index];
406
+ const callSigs = elementType.getCallSignatures();
407
+ if (callSigs.length === 0)
408
+ continue;
409
+ const sig = callSigs[0];
410
+ // Extract parameter types, filtering out the `this` pseudo-parameter
411
+ const params = [];
412
+ for (const sym of sig.getParameters()) {
413
+ const name = sym.getName();
414
+ if (name === "this")
415
+ continue;
416
+ const decl = sym.getValueDeclaration();
417
+ if (!decl)
418
+ continue;
419
+ params.push({ name, type: decl.getType().getText() });
420
+ }
421
+ const paramCount = params.length;
422
+ const returnType = sig.getReturnType().getText();
423
+ // --- Binary static ---
424
+ if (paramCount === 2 &&
425
+ isStatic &&
426
+ binarySyntaxKind &&
427
+ !instanceOperators.has(binarySyntaxKind)) {
428
+ const lhsType = params[0].type;
429
+ const rhsType = params[1].type;
430
+ if (lhsType !== classType && rhsType !== classType) {
431
+ this._errorManager.addWarning(new ErrorDescription(`Overload for operator ${operatorString} ` +
432
+ "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 : "")));
433
+ continue;
434
+ }
435
+ if (comparisonOperators.has(binarySyntaxKind) &&
436
+ returnType !== "boolean") {
437
+ this._errorManager.addWarning(new ErrorDescription(`Overload function ${index} for comparison operator ${operatorString} ` +
438
+ `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 : "")));
439
+ continue;
440
+ }
441
+ const operatorOverloads = (_c = this.get(binarySyntaxKind)) !== null && _c !== void 0 ? _c : new Map();
442
+ const lhsMap = (_d = operatorOverloads.get(lhsType)) !== null && _d !== void 0 ? _d : new Map();
443
+ if (lhsMap.has(rhsType))
444
+ continue; // duplicate — skip silently for .d.ts
445
+ lhsMap.set(rhsType, {
446
+ isStatic: true,
447
+ className,
448
+ classFilePath: filePath,
449
+ operatorString,
450
+ index,
451
+ returnType,
452
+ });
453
+ operatorOverloads.set(lhsType, lhsMap);
454
+ this.set(binarySyntaxKind, operatorOverloads);
455
+ this._logger.debug(`Loaded ${className}["${operatorString}"][${index}]: (${sl(lhsType)}, ${sl(rhsType)}) => ${sl(returnType)} (static, from .d.ts)`);
456
+ let fileEntries = this._fileEntries.get(filePath);
457
+ if (!fileEntries) {
458
+ fileEntries = [];
459
+ this._fileEntries.set(filePath, fileEntries);
460
+ }
461
+ fileEntries.push({ syntaxKind: binarySyntaxKind, lhsType, rhsType });
462
+ }
463
+ // --- Binary instance (compound assignment) ---
464
+ else if (paramCount === 1 &&
465
+ !isStatic &&
466
+ binarySyntaxKind &&
467
+ instanceOperators.has(binarySyntaxKind)) {
468
+ const lhsType = classType;
469
+ const rhsType = params[0].type;
470
+ if (returnType !== "void") {
471
+ this._errorManager.addWarning(new ErrorDescription(`Overload function ${index} for instance operator ${operatorString} ` +
472
+ `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 : "")));
473
+ continue;
474
+ }
475
+ const operatorOverloads = (_f = this.get(binarySyntaxKind)) !== null && _f !== void 0 ? _f : new Map();
476
+ const lhsMap = (_g = operatorOverloads.get(lhsType)) !== null && _g !== void 0 ? _g : new Map();
477
+ if (lhsMap.has(rhsType))
478
+ continue;
479
+ lhsMap.set(rhsType, {
480
+ isStatic: false,
481
+ className,
482
+ classFilePath: filePath,
483
+ operatorString,
484
+ index,
485
+ returnType,
486
+ });
487
+ operatorOverloads.set(lhsType, lhsMap);
488
+ this.set(binarySyntaxKind, operatorOverloads);
489
+ this._logger.debug(`Loaded ${className}["${operatorString}"][${index}]: (${sl(lhsType)}, ${sl(rhsType)}) => void (instance, from .d.ts)`);
490
+ let fileEntries = this._fileEntries.get(filePath);
491
+ if (!fileEntries) {
492
+ fileEntries = [];
493
+ this._fileEntries.set(filePath, fileEntries);
494
+ }
495
+ fileEntries.push({ syntaxKind: binarySyntaxKind, lhsType, rhsType });
496
+ }
497
+ // --- Prefix unary ---
498
+ else if (paramCount === 1 && isStatic && prefixUnarySyntaxKind) {
499
+ const operandType = params[0].type;
500
+ if (operandType !== classType) {
501
+ this._errorManager.addWarning(new ErrorDescription(`Prefix unary overload for operator ${operatorString} ` +
502
+ "must have its parameter matching its class type.", filePath, property.getStartLineNumber(), this._minifyString((_h = property.getText().split("\n")[0]) !== null && _h !== void 0 ? _h : "")));
503
+ continue;
504
+ }
505
+ const operatorOverloads = (_j = this._prefixUnaryOverloads.get(prefixUnarySyntaxKind)) !== null && _j !== void 0 ? _j : new Map();
506
+ if (operatorOverloads.has(operandType))
507
+ continue;
508
+ operatorOverloads.set(operandType, {
509
+ isStatic: true,
510
+ className,
511
+ classFilePath: filePath,
512
+ operatorString,
513
+ index,
514
+ returnType,
515
+ });
516
+ this._prefixUnaryOverloads.set(prefixUnarySyntaxKind, operatorOverloads);
517
+ this._logger.debug(`Loaded ${className}["${operatorString}"][${index}]: ${operatorString}(${sl(operandType)}) => ${sl(returnType)} (prefix unary, from .d.ts)`);
518
+ let fileEntries = this._prefixUnaryFileEntries.get(filePath);
519
+ if (!fileEntries) {
520
+ fileEntries = [];
521
+ this._prefixUnaryFileEntries.set(filePath, fileEntries);
522
+ }
523
+ fileEntries.push({ syntaxKind: prefixUnarySyntaxKind, operandType });
524
+ }
525
+ // --- Postfix unary ---
526
+ else if (paramCount === 0 && !isStatic && postfixUnarySyntaxKind) {
527
+ if (returnType !== "void") {
528
+ this._errorManager.addWarning(new ErrorDescription(`Overload function ${index} for postfix operator ${operatorString} ` +
529
+ `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 : "")));
530
+ continue;
531
+ }
532
+ const operandType = classType;
533
+ const operatorOverloads = (_l = this._postfixUnaryOverloads.get(postfixUnarySyntaxKind)) !== null && _l !== void 0 ? _l : new Map();
534
+ if (operatorOverloads.has(operandType))
535
+ continue;
536
+ operatorOverloads.set(operandType, {
537
+ isStatic: false,
538
+ className,
539
+ classFilePath: filePath,
540
+ operatorString,
541
+ index,
542
+ returnType,
543
+ });
544
+ this._postfixUnaryOverloads.set(postfixUnarySyntaxKind, operatorOverloads);
545
+ this._logger.debug(`Loaded ${className}["${operatorString}"][${index}]: ${sl(operandType)}${operatorString} () (postfix unary, from .d.ts)`);
546
+ let fileEntries = this._postfixUnaryFileEntries.get(filePath);
547
+ if (!fileEntries) {
548
+ fileEntries = [];
549
+ this._postfixUnaryFileEntries.set(filePath, fileEntries);
550
+ }
551
+ fileEntries.push({ syntaxKind: postfixUnarySyntaxKind, operandType });
552
+ }
553
+ }
554
+ }
555
+ /**
556
+ * Returns the type hierarchy chain for a given type name:
557
+ * [self, parent, grandparent, ...]. Primitives like "number"
558
+ * return a single-element array.
559
+ */
560
+ _getTypeChain(typeName) {
561
+ var _a, _b;
562
+ const cached = this._typeChainCache.get(typeName);
563
+ if (cached)
564
+ return cached;
565
+ const chain = [typeName];
566
+ // Extract simple class name from fully-qualified type strings
567
+ // e.g. 'import("C:/path/to/Foo").Foo' → 'Foo'
568
+ const simpleName = (_b = (_a = typeName.match(/\.(\w+)$/)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : typeName;
569
+ for (const sourceFile of this._project.getSourceFiles()) {
570
+ const classDecl = sourceFile.getClass(simpleName);
571
+ if (classDecl) {
572
+ let current = classDecl.getBaseClass();
573
+ while (current) {
574
+ chain.push(current.getType().getText());
575
+ current = current.getBaseClass();
576
+ }
577
+ break;
578
+ }
579
+ }
580
+ this._typeChainCache.set(typeName, chain);
581
+ return chain;
582
+ }
583
+ _minifyString(str) {
584
+ return str.replace(/\s+/g, " ").replace("\n", "").trim();
585
+ }
586
+ /** Strips `import("...").` prefixes from fully-qualified type names for readable logs. */
587
+ _shortTypeName(typeName) {
588
+ return typeName.replace(/import\("[^"]*"\)\./g, "");
589
+ }
590
+ toString() {
591
+ let str = "";
592
+ for (const [operatorSyntaxKind, lhsMap] of this) {
593
+ str += `\n\nBinary Operator: ${SyntaxKind[operatorSyntaxKind]}`;
594
+ for (const [lhsType, rhsMap] of lhsMap) {
595
+ str += `\n - LHS Type: ${lhsType}`;
596
+ for (const [rhsType, overload] of rhsMap) {
597
+ str += `\n - RHS Type: ${rhsType}`;
598
+ str += `\n Overload: ${JSON.stringify(overload)}`;
599
+ }
600
+ }
601
+ }
602
+ for (const [syntaxKind, operandMap] of this._prefixUnaryOverloads) {
603
+ str += `\n\nPrefix Unary Operator: ${SyntaxKind[syntaxKind]}`;
604
+ for (const [operandType, overload] of operandMap) {
605
+ str += `\n - Operand Type: ${operandType}`;
606
+ str += `\n Overload: ${JSON.stringify(overload)}`;
607
+ }
608
+ }
609
+ for (const [syntaxKind, operandMap] of this._postfixUnaryOverloads) {
610
+ str += `\n\nPostfix Unary Operator: ${SyntaxKind[syntaxKind]}`;
611
+ for (const [operandType, overload] of operandMap) {
612
+ str += `\n - Operand Type: ${operandType}`;
613
+ str += `\n Overload: ${JSON.stringify(overload)}`;
614
+ }
615
+ }
616
+ return str;
617
+ }
618
+ }
@@ -0,0 +1,41 @@
1
+ export type EditRecord = {
2
+ /** Start position in the original source */
3
+ origStart: number;
4
+ /** End position (exclusive) in the original source */
5
+ origEnd: number;
6
+ /** Start position in the transformed source */
7
+ transStart: number;
8
+ /** End position (exclusive) in the transformed source */
9
+ transEnd: number;
10
+ };
11
+ /**
12
+ * Bidirectional position mapping between original and transformed source text.
13
+ *
14
+ * Computes a list of edit records by diffing the two texts, then provides
15
+ * O(edits) position and span mapping in both directions.
16
+ */
17
+ export declare class SourceMap {
18
+ readonly edits: readonly EditRecord[];
19
+ constructor(original: string, transformed: string);
20
+ /** Returns true if no edits were detected (original === transformed). */
21
+ get isEmpty(): boolean;
22
+ /** Map a position from original source to transformed source. */
23
+ originalToTransformed(pos: number): number;
24
+ /** Map a position from transformed source to original source. */
25
+ transformedToOriginal(pos: number): number;
26
+ /** Map a text span { start, length } from transformed positions to original positions. */
27
+ remapSpan(span: {
28
+ start: number;
29
+ length: number;
30
+ }): {
31
+ start: number;
32
+ length: number;
33
+ };
34
+ /** Check if an original-source position falls inside an edited region. */
35
+ isInsideEdit(originalPos: number): boolean;
36
+ /**
37
+ * For a position inside an edited region (in original coords),
38
+ * return the EditRecord it falls in, or undefined.
39
+ */
40
+ getEditAt(originalPos: number): EditRecord | undefined;
41
+ }