boperators 0.1.4 → 0.2.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 (54) hide show
  1. package/README.md +7 -1
  2. package/dist/{esm/core → core}/OverloadInjector.d.ts +3 -3
  3. package/dist/core/OverloadInjector.js +154 -0
  4. package/dist/core/SourceMap.d.ts +20 -0
  5. package/dist/{cjs/core → core}/SourceMap.js +2 -78
  6. package/dist/core/v3SourceMap.d.ts +21 -0
  7. package/dist/core/v3SourceMap.js +121 -0
  8. package/dist/{esm/index.d.ts → index.d.ts} +3 -1
  9. package/dist/{cjs/index.js → index.js} +4 -2
  10. package/package.json +14 -12
  11. package/dist/cjs/core/OverloadInjector.js +0 -136
  12. package/dist/cjs/lib/index.js +0 -17
  13. package/dist/esm/core/BopConfig.js +0 -65
  14. package/dist/esm/core/ErrorManager.js +0 -118
  15. package/dist/esm/core/OverloadInjector.js +0 -129
  16. package/dist/esm/core/OverloadStore.js +0 -624
  17. package/dist/esm/core/SourceMap.d.ts +0 -41
  18. package/dist/esm/core/SourceMap.js +0 -164
  19. package/dist/esm/core/helpers/ensureImportedName.js +0 -46
  20. package/dist/esm/core/helpers/getImportedNameForSymbol.js +0 -60
  21. package/dist/esm/core/helpers/getModuleSpecifier.js +0 -57
  22. package/dist/esm/core/helpers/getOperatorStringFromProperty.js +0 -30
  23. package/dist/esm/core/helpers/resolveExpressionType.js +0 -37
  24. package/dist/esm/core/helpers/unwrapInitializer.js +0 -15
  25. package/dist/esm/core/operatorMap.js +0 -92
  26. package/dist/esm/core/validateExports.js +0 -84
  27. package/dist/esm/index.js +0 -14
  28. package/dist/esm/lib/index.d.ts +0 -1
  29. package/dist/esm/lib/index.js +0 -1
  30. package/dist/esm/lib/operatorSymbols.js +0 -36
  31. /package/dist/{esm/core → core}/BopConfig.d.ts +0 -0
  32. /package/dist/{cjs/core → core}/BopConfig.js +0 -0
  33. /package/dist/{esm/core → core}/ErrorManager.d.ts +0 -0
  34. /package/dist/{cjs/core → core}/ErrorManager.js +0 -0
  35. /package/dist/{esm/core → core}/OverloadStore.d.ts +0 -0
  36. /package/dist/{cjs/core → core}/OverloadStore.js +0 -0
  37. /package/dist/{esm/core → core}/helpers/ensureImportedName.d.ts +0 -0
  38. /package/dist/{cjs/core → core}/helpers/ensureImportedName.js +0 -0
  39. /package/dist/{esm/core → core}/helpers/getImportedNameForSymbol.d.ts +0 -0
  40. /package/dist/{cjs/core → core}/helpers/getImportedNameForSymbol.js +0 -0
  41. /package/dist/{esm/core → core}/helpers/getModuleSpecifier.d.ts +0 -0
  42. /package/dist/{cjs/core → core}/helpers/getModuleSpecifier.js +0 -0
  43. /package/dist/{esm/core → core}/helpers/getOperatorStringFromProperty.d.ts +0 -0
  44. /package/dist/{cjs/core → core}/helpers/getOperatorStringFromProperty.js +0 -0
  45. /package/dist/{esm/core → core}/helpers/resolveExpressionType.d.ts +0 -0
  46. /package/dist/{cjs/core → core}/helpers/resolveExpressionType.js +0 -0
  47. /package/dist/{esm/core → core}/helpers/unwrapInitializer.d.ts +0 -0
  48. /package/dist/{cjs/core → core}/helpers/unwrapInitializer.js +0 -0
  49. /package/dist/{esm/core → core}/operatorMap.d.ts +0 -0
  50. /package/dist/{cjs/core → core}/operatorMap.js +0 -0
  51. /package/dist/{esm/core → core}/validateExports.d.ts +0 -0
  52. /package/dist/{cjs/core → core}/validateExports.js +0 -0
  53. /package/dist/{esm/lib → lib}/operatorSymbols.d.ts +0 -0
  54. /package/dist/{cjs/lib → lib}/operatorSymbols.js +0 -0
package/README.md CHANGED
@@ -11,7 +11,13 @@ Operator overloading is a common programming feature that JavaScript lacks. Just
11
11
 
12
12
  `boperators` finally brings operator overloading to JavaScript by leveraging TypeScript typings. You define one or more overload functions on a class for whichever operators you want, and with magic we search for anywhere you've used overloaded operators and substitute in your functions.
13
13
 
14
- 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).
14
+ This is the core library and API, and isn't designed to be used directly. Instead, you can use:
15
+ - The [Boperators CLI](https://www.npmjs.com/package/@boperators/cli);
16
+ - The [`tsc`](https://www.npmjs.com/package/@boperators/plugin-tsc) plugin;
17
+ - The [webpack loader](https://www.npmjs.com/package/@boperators/webpack-loader);
18
+ - The [Vite plugin](https://www.npmjs.com/package/@boperators/plugin-vite);
19
+ - The [ESBuild plugin](https://www.npmjs.com/package/@boperators/plugin-esbuild);
20
+ - The [Bun plugin](https://www.npmjs.com/package/@boperators/plugin-bun) for running directly with Bun.
15
21
 
16
22
  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.
17
23
 
@@ -1,14 +1,14 @@
1
1
  import { SourceFile, type Project as TsMorphProject } from "ts-morph";
2
2
  import type { BopLogger } from "./BopConfig";
3
3
  import type { OverloadStore } from "./OverloadStore";
4
- import { SourceMap } from "./SourceMap";
4
+ import { type EditRecord } from "./SourceMap";
5
5
  export type TransformResult = {
6
6
  /** The mutated ts-morph SourceFile (same reference as input). */
7
7
  sourceFile: SourceFile;
8
8
  /** The full text after transformation. */
9
9
  text: string;
10
- /** Bidirectional source map between original and transformed text. */
11
- sourceMap: SourceMap;
10
+ /** Edit records mapping positions between original and transformed text. */
11
+ edits: readonly EditRecord[];
12
12
  };
13
13
  export declare class OverloadInjector {
14
14
  /**
@@ -0,0 +1,154 @@
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
+ // Outer loop: re-run all three passes until the file is fully stable.
36
+ // This is necessary because transforming a unary expression can unblock
37
+ // a binary expression (e.g. in `-v1 + v2`, the unary `-` must be
38
+ // replaced first so TypeScript can resolve its return type; the binary
39
+ // `+` overload then becomes matchable on the next outer iteration).
40
+ let outerChanged = true;
41
+ while (outerChanged) {
42
+ outerChanged = false;
43
+ // ── Binary expressions ────────────────────────────────────────────
44
+ // Process one innermost expression per inner iteration, re-fetching
45
+ // descendants each time so types resolve correctly after each
46
+ // transformation and AST references stay fresh.
47
+ let changed = true;
48
+ while (changed) {
49
+ changed = false;
50
+ const binaryExpressions = sourceFile
51
+ .getDescendantsOfKind(ts_morph_1.SyntaxKind.BinaryExpression)
52
+ .reverse();
53
+ for (const expression of binaryExpressions) {
54
+ const operatorKind = expression.getOperatorToken().getKind();
55
+ if (!(0, operatorMap_1.isOperatorSyntaxKind)(operatorKind))
56
+ continue;
57
+ const lhs = expression.getLeft();
58
+ const leftType = (0, resolveExpressionType_1.resolveExpressionType)(lhs);
59
+ const rhs = expression.getRight();
60
+ const rightType = (0, resolveExpressionType_1.resolveExpressionType)(rhs);
61
+ const overloadDesc = this._overloadStore.findOverload(operatorKind, leftType, rightType);
62
+ if (!overloadDesc)
63
+ continue;
64
+ const { className: classNameRaw, classFilePath, operatorString, index, isStatic, } = overloadDesc;
65
+ // Look up the fresh ClassDeclaration from the project
66
+ const classSourceFile = this._project.getSourceFileOrThrow(classFilePath);
67
+ const classDecl = classSourceFile.getClassOrThrow(classNameRaw);
68
+ // Ensure class is imported, get its textual name
69
+ const classSymbol = classDecl.getSymbol();
70
+ if (!classSymbol)
71
+ throw new Error(`No symbol for class "${classNameRaw}"`);
72
+ const classModuleSpecifier = (0, getModuleSpecifier_1.getModuleSpecifier)(sourceFile, classSourceFile);
73
+ const className = (0, ensureImportedName_1.ensureImportedName)(sourceFile, classSymbol, classModuleSpecifier);
74
+ // Build the text code to replace the binary operator with the overload call
75
+ const overloadCall = isStatic
76
+ ? `${className}["${operatorString}"][${index}](${lhs.getText()}, ${rhs.getText()})`
77
+ : `${lhs.getText()}["${operatorString}"][${index}].call(${lhs.getText()}, ${rhs.getText()})`;
78
+ this._logger.debug(`${fileName}: ${expression.getText()} => ${overloadCall}`);
79
+ expression.replaceWithText(overloadCall);
80
+ transformCount++;
81
+ changed = true;
82
+ outerChanged = true;
83
+ break; // re-fetch descendants after each mutation
84
+ }
85
+ }
86
+ // ── Prefix unary expressions (-x, +x, !x, ~x) ───────────────────
87
+ changed = true;
88
+ while (changed) {
89
+ changed = false;
90
+ const prefixExpressions = sourceFile
91
+ .getDescendantsOfKind(ts_morph_1.SyntaxKind.PrefixUnaryExpression)
92
+ .reverse();
93
+ for (const expression of prefixExpressions) {
94
+ const operatorKind = expression.getOperatorToken();
95
+ if (!(0, operatorMap_1.isPrefixUnaryOperatorSyntaxKind)(operatorKind))
96
+ continue;
97
+ const operand = expression.getOperand();
98
+ const operandType = (0, resolveExpressionType_1.resolveExpressionType)(operand);
99
+ const overloadDesc = this._overloadStore.findPrefixUnaryOverload(operatorKind, operandType);
100
+ if (!overloadDesc)
101
+ continue;
102
+ const { className: classNameRaw, classFilePath, operatorString, index, } = overloadDesc;
103
+ const classSourceFile = this._project.getSourceFileOrThrow(classFilePath);
104
+ const classDecl = classSourceFile.getClassOrThrow(classNameRaw);
105
+ const classSymbol = classDecl.getSymbol();
106
+ if (!classSymbol)
107
+ throw new Error(`No symbol for class "${classNameRaw}"`);
108
+ const classModuleSpecifier = (0, getModuleSpecifier_1.getModuleSpecifier)(sourceFile, classSourceFile);
109
+ const className = (0, ensureImportedName_1.ensureImportedName)(sourceFile, classSymbol, classModuleSpecifier);
110
+ const overloadCall = `${className}["${operatorString}"][${index}](${operand.getText()})`;
111
+ this._logger.debug(`${fileName}: ${expression.getText()} => ${overloadCall}`);
112
+ expression.replaceWithText(overloadCall);
113
+ transformCount++;
114
+ changed = true;
115
+ outerChanged = true;
116
+ break;
117
+ }
118
+ }
119
+ // ── Postfix unary expressions (x++, x--) ─────────────────────────
120
+ changed = true;
121
+ while (changed) {
122
+ changed = false;
123
+ const postfixExpressions = sourceFile
124
+ .getDescendantsOfKind(ts_morph_1.SyntaxKind.PostfixUnaryExpression)
125
+ .reverse();
126
+ for (const expression of postfixExpressions) {
127
+ const operatorKind = expression.getOperatorToken();
128
+ if (!(0, operatorMap_1.isPostfixUnaryOperatorSyntaxKind)(operatorKind))
129
+ continue;
130
+ const operand = expression.getOperand();
131
+ const operandType = (0, resolveExpressionType_1.resolveExpressionType)(operand);
132
+ const overloadDesc = this._overloadStore.findPostfixUnaryOverload(operatorKind, operandType);
133
+ if (!overloadDesc)
134
+ continue;
135
+ const { operatorString, index } = overloadDesc;
136
+ const overloadCall = `${operand.getText()}["${operatorString}"][${index}].call(${operand.getText()})`;
137
+ this._logger.debug(`${fileName}: ${expression.getText()} => ${overloadCall}`);
138
+ expression.replaceWithText(overloadCall);
139
+ transformCount++;
140
+ changed = true;
141
+ outerChanged = true;
142
+ break;
143
+ }
144
+ }
145
+ }
146
+ if (transformCount > 0) {
147
+ this._logger.debug(`${fileName}: ${transformCount} expression${transformCount === 1 ? "" : "s"} transformed`);
148
+ }
149
+ const text = sourceFile.getFullText();
150
+ const edits = (0, SourceMap_1.computeEdits)(originalText, text);
151
+ return { sourceFile, text, edits };
152
+ }
153
+ }
154
+ exports.OverloadInjector = OverloadInjector;
@@ -0,0 +1,20 @@
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
+ * Compute edit records by scanning both texts for mismatches.
13
+ * @public
14
+ *
15
+ * Uses character-level comparison with anchor-based convergence:
16
+ * identical characters are skipped, mismatches start an edit region,
17
+ * and convergence is found by searching for a matching anchor substring
18
+ * in the remaining text.
19
+ */
20
+ export declare function computeEdits(original: string, transformed: string): EditRecord[];
@@ -1,85 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SourceMap = void 0;
4
- /**
5
- * Bidirectional position mapping between original and transformed source text.
6
- *
7
- * Computes a list of edit records by diffing the two texts, then provides
8
- * O(edits) position and span mapping in both directions.
9
- */
10
- class SourceMap {
11
- constructor(original, transformed) {
12
- this.edits = computeEdits(original, transformed);
13
- }
14
- /** Returns true if no edits were detected (original === transformed). */
15
- get isEmpty() {
16
- return this.edits.length === 0;
17
- }
18
- /** Map a position from original source to transformed source. */
19
- originalToTransformed(pos) {
20
- let delta = 0;
21
- for (const edit of this.edits) {
22
- if (pos < edit.origStart) {
23
- // Before this edit — just apply accumulated delta
24
- break;
25
- }
26
- if (pos < edit.origEnd) {
27
- // Inside an edited region — map to start of the transformed replacement
28
- return edit.transStart;
29
- }
30
- // Past this edit — accumulate the delta
31
- delta = edit.transEnd - edit.origEnd;
32
- }
33
- return pos + delta;
34
- }
35
- /** Map a position from transformed source to original source. */
36
- transformedToOriginal(pos) {
37
- let delta = 0;
38
- for (const edit of this.edits) {
39
- if (pos < edit.transStart) {
40
- break;
41
- }
42
- if (pos < edit.transEnd) {
43
- // Inside a transformed region — map to start of the original span
44
- return edit.origStart;
45
- }
46
- delta = edit.origEnd - edit.transEnd;
47
- }
48
- return pos + delta;
49
- }
50
- /** Map a text span { start, length } from transformed positions to original positions. */
51
- remapSpan(span) {
52
- const origStart = this.transformedToOriginal(span.start);
53
- const origEnd = this.transformedToOriginal(span.start + span.length);
54
- return { start: origStart, length: origEnd - origStart };
55
- }
56
- /** Check if an original-source position falls inside an edited region. */
57
- isInsideEdit(originalPos) {
58
- for (const edit of this.edits) {
59
- if (originalPos < edit.origStart)
60
- return false;
61
- if (originalPos < edit.origEnd)
62
- return true;
63
- }
64
- return false;
65
- }
66
- /**
67
- * For a position inside an edited region (in original coords),
68
- * return the EditRecord it falls in, or undefined.
69
- */
70
- getEditAt(originalPos) {
71
- for (const edit of this.edits) {
72
- if (originalPos < edit.origStart)
73
- return undefined;
74
- if (originalPos < edit.origEnd)
75
- return edit;
76
- }
77
- return undefined;
78
- }
79
- }
80
- exports.SourceMap = SourceMap;
3
+ exports.computeEdits = computeEdits;
81
4
  /**
82
5
  * Compute edit records by scanning both texts for mismatches.
6
+ * @public
83
7
  *
84
8
  * Uses character-level comparison with anchor-based convergence:
85
9
  * identical characters are skipped, mismatches start an edit region,
@@ -0,0 +1,21 @@
1
+ import type { EditRecord } from "./SourceMap";
2
+ /**
3
+ * Standard V3 source map object.
4
+ * @see https://sourcemaps.info/spec.html
5
+ */
6
+ export interface V3SourceMap {
7
+ version: 3;
8
+ sources: string[];
9
+ names: string[];
10
+ sourceRoot?: string;
11
+ sourcesContent?: string[];
12
+ mappings: string;
13
+ file: string;
14
+ }
15
+ /**
16
+ * Convert boperators EditRecord[] into a V3 source map.
17
+ *
18
+ * Generates line-level mappings: unchanged regions map 1:1, and edit
19
+ * boundaries map back to the start of the original edit region.
20
+ */
21
+ export declare function toV3SourceMap(edits: readonly EditRecord[], originalText: string, transformedText: string, fileName: string): V3SourceMap;
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toV3SourceMap = toV3SourceMap;
4
+ /**
5
+ * Convert boperators EditRecord[] into a V3 source map.
6
+ *
7
+ * Generates line-level mappings: unchanged regions map 1:1, and edit
8
+ * boundaries map back to the start of the original edit region.
9
+ */
10
+ function toV3SourceMap(edits, originalText, transformedText, fileName) {
11
+ const origLineStarts = buildLineStarts(originalText);
12
+ const transLineStarts = buildLineStarts(transformedText);
13
+ // Each segment is [transCol, sourceIndex, origLine, origCol]
14
+ // We only have one source (index 0), so sourceIndex is always 0.
15
+ const mappingLines = [];
16
+ const transLineCount = transLineStarts.length;
17
+ // Initialise empty lines
18
+ for (let i = 0; i < transLineCount; i++) {
19
+ mappingLines.push([]);
20
+ }
21
+ // For positions outside any edit, the mapping is identity + accumulated delta.
22
+ // For positions inside an edit's transformed region, map to the original edit start.
23
+ // We generate one segment per transformed line start.
24
+ for (let transLine = 0; transLine < transLineCount; transLine++) {
25
+ const transOffset = transLineStarts[transLine];
26
+ const origOffset = transformedToOriginal(edits, transOffset);
27
+ const { line: origLine, col: origCol } = offsetToLineCol(origLineStarts, origOffset);
28
+ mappingLines[transLine].push([0, 0, origLine, origCol]);
29
+ }
30
+ return {
31
+ version: 3,
32
+ file: fileName,
33
+ sources: [fileName],
34
+ sourcesContent: [originalText],
35
+ names: [],
36
+ mappings: encodeMappings(mappingLines),
37
+ };
38
+ }
39
+ /** Map a transformed-source offset back to the original source. */
40
+ function transformedToOriginal(edits, pos) {
41
+ let delta = 0;
42
+ for (const edit of edits) {
43
+ if (pos < edit.transStart) {
44
+ break;
45
+ }
46
+ if (pos < edit.transEnd) {
47
+ return edit.origStart;
48
+ }
49
+ delta = edit.origEnd - edit.transEnd;
50
+ }
51
+ return pos + delta;
52
+ }
53
+ /** Build an array where index i is the character offset of line i. */
54
+ function buildLineStarts(text) {
55
+ const starts = [0];
56
+ for (let i = 0; i < text.length; i++) {
57
+ if (text[i] === "\n") {
58
+ starts.push(i + 1);
59
+ }
60
+ }
61
+ return starts;
62
+ }
63
+ /** Convert a character offset to 0-based line and column. */
64
+ function offsetToLineCol(lineStarts, offset) {
65
+ // Binary search for the line containing this offset
66
+ let lo = 0;
67
+ let hi = lineStarts.length - 1;
68
+ while (lo < hi) {
69
+ const mid = (lo + hi + 1) >>> 1;
70
+ if (lineStarts[mid] <= offset) {
71
+ lo = mid;
72
+ }
73
+ else {
74
+ hi = mid - 1;
75
+ }
76
+ }
77
+ return { line: lo, col: offset - lineStarts[lo] };
78
+ }
79
+ /** Encode segment arrays into a VLQ mappings string. */
80
+ function encodeMappings(lines) {
81
+ let prevTransCol = 0;
82
+ let prevOrigLine = 0;
83
+ let prevOrigCol = 0;
84
+ let prevSourceIdx = 0;
85
+ const lineStrings = [];
86
+ for (const segments of lines) {
87
+ prevTransCol = 0; // reset column tracking per line
88
+ const segStrings = [];
89
+ for (const seg of segments) {
90
+ const [transCol, sourceIdx, origLine, origCol] = seg;
91
+ segStrings.push(encodeVLQ(transCol - prevTransCol) +
92
+ encodeVLQ(sourceIdx - prevSourceIdx) +
93
+ encodeVLQ(origLine - prevOrigLine) +
94
+ encodeVLQ(origCol - prevOrigCol));
95
+ prevTransCol = transCol;
96
+ prevSourceIdx = sourceIdx;
97
+ prevOrigLine = origLine;
98
+ prevOrigCol = origCol;
99
+ }
100
+ lineStrings.push(segStrings.join(","));
101
+ }
102
+ return lineStrings.join(";");
103
+ }
104
+ const VLQ_BASE = 32;
105
+ const VLQ_BASE_MASK = VLQ_BASE - 1;
106
+ const VLQ_CONTINUATION = VLQ_BASE;
107
+ const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
108
+ /** Encode a single integer as a VLQ base64 string. */
109
+ function encodeVLQ(value) {
110
+ let vlq = value < 0 ? (-value << 1) | 1 : value << 1;
111
+ let result = "";
112
+ do {
113
+ let digit = vlq & VLQ_BASE_MASK;
114
+ vlq >>>= 5;
115
+ if (vlq > 0) {
116
+ digit |= VLQ_CONTINUATION;
117
+ }
118
+ result += BASE64_CHARS[digit];
119
+ } while (vlq > 0);
120
+ return result;
121
+ }
@@ -11,7 +11,9 @@ export { OverloadStore } from "./core/OverloadStore";
11
11
  export type { OperatorSyntaxKind, PostfixUnaryOperatorSyntaxKind, PrefixUnaryOperatorSyntaxKind, } from "./core/operatorMap";
12
12
  export { isOperatorSyntaxKind, isPostfixUnaryOperatorSyntaxKind, isPrefixUnaryOperatorSyntaxKind, } from "./core/operatorMap";
13
13
  export type { EditRecord } from "./core/SourceMap";
14
- export { SourceMap } from "./core/SourceMap";
14
+ export { computeEdits } from "./core/SourceMap";
15
+ export type { V3SourceMap } from "./core/v3SourceMap";
16
+ export { toV3SourceMap } from "./core/v3SourceMap";
15
17
  export type { ExportViolation, ExportViolationReason, ValidateExportsResult, } from "./core/validateExports";
16
18
  export { validateExports } from "./core/validateExports";
17
19
  export { Operator, operatorSymbols } from "./lib/operatorSymbols";
@@ -1,7 +1,7 @@
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.SourceMap = 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.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; } });
@@ -23,7 +23,9 @@ Object.defineProperty(exports, "isOperatorSyntaxKind", { enumerable: true, get:
23
23
  Object.defineProperty(exports, "isPostfixUnaryOperatorSyntaxKind", { enumerable: true, get: function () { return operatorMap_1.isPostfixUnaryOperatorSyntaxKind; } });
24
24
  Object.defineProperty(exports, "isPrefixUnaryOperatorSyntaxKind", { enumerable: true, get: function () { return operatorMap_1.isPrefixUnaryOperatorSyntaxKind; } });
25
25
  var SourceMap_1 = require("./core/SourceMap");
26
- Object.defineProperty(exports, "SourceMap", { enumerable: true, get: function () { return SourceMap_1.SourceMap; } });
26
+ Object.defineProperty(exports, "computeEdits", { enumerable: true, get: function () { return SourceMap_1.computeEdits; } });
27
+ var v3SourceMap_1 = require("./core/v3SourceMap");
28
+ Object.defineProperty(exports, "toV3SourceMap", { enumerable: true, get: function () { return v3SourceMap_1.toV3SourceMap; } });
27
29
  var validateExports_1 = require("./core/validateExports");
28
30
  Object.defineProperty(exports, "validateExports", { enumerable: true, get: function () { return validateExports_1.validateExports; } });
29
31
  // Operator definitions
package/package.json CHANGED
@@ -1,22 +1,23 @@
1
1
  {
2
2
  "name": "boperators",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "description": "Operator overloading for TypeScript.",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "https://github.com/DiefBell/boperators",
8
+ "url": "git+https://github.com/DiefBell/boperators.git",
9
9
  "directory": "package"
10
10
  },
11
+ "bugs": {
12
+ "url": "https://github.com/DiefBell/boperators/issues"
13
+ },
11
14
  "homepage": "https://github.com/DiefBell/boperators/tree/main/package",
12
- "main": "./dist/cjs/index.js",
13
- "module": "./dist/esm/index.js",
14
- "types": "./dist/esm/index.d.ts",
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
15
17
  "exports": {
16
18
  ".": {
17
- "types": "./dist/esm/index.d.ts",
18
- "require": "./dist/cjs/index.js",
19
- "import": "./dist/esm/index.js"
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
20
21
  }
21
22
  },
22
23
  "keywords": [
@@ -30,8 +31,10 @@
30
31
  "transform"
31
32
  ],
32
33
  "scripts": {
33
- "build": "tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json",
34
- "watch": "concurrently \"tsc -p tsconfig.cjs.json -w\" \"tsc -p tsconfig.esm.json -w\"",
34
+ "build": "tsc",
35
+ "watch": "tsc -w",
36
+ "test": "bun test",
37
+ "test:watch": "bun test --watch",
35
38
  "prepare": "bun run build",
36
39
  "prepublish": "bun run build"
37
40
  },
@@ -49,7 +52,6 @@
49
52
  "logo.png"
50
53
  ],
51
54
  "devDependencies": {
52
- "@types/node": "^22.0.0",
53
- "concurrently": "^9.2.1"
55
+ "@types/node": "^22.0.0"
54
56
  }
55
57
  }
@@ -1,136 +0,0 @@
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;