├── .babelrc ├── .gitignore ├── README.md ├── package.json ├── src └── index.js └── test ├── fixtures └── pipe-operator │ ├── expected.js │ └── input.js └── spec └── fixtureSpec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /index.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-operator-overload 2 | 3 | > Overload operators in local scope. 4 | 5 | This is a babel plugin allowing you to "overload" operators without affecting the whole program's scope. 6 | 7 | For example, let's say you wanted the pipe (`|`) operator do just that, pipe. So basically what we want is that `left | right` becomes `right(left)`. 8 | 9 | This can be done using the `defineBinaryOperator` macro provided by the plugin: 10 | 11 | ```javascript 12 | defineBinaryOperator("|", (left, right) => right(left)); 13 | ``` 14 | 15 | Easy, no? Now we can use this operator in a program: 16 | 17 | ```javascript 18 | defineBinaryOperator("|", (left, right) => right(left)); 19 | 20 | const map = transformer => list => list.map(item => transformer(item)); 21 | const filter = condition => list => list.filter(item => condition(item)); 22 | const mul = a => b => a * b; 23 | const gt = a => b => b > a; 24 | 25 | const numbers = [ 26 | 1, 27 | 2, 28 | 3, 29 | 4, 30 | 5, 31 | 6, 32 | 7, 33 | 8, 34 | ] 35 | | filter(gt(3)) 36 | | map(mul(3)) 37 | 38 | console.log(numbers); // [ 12, 15, 18, 21, 24 ] 39 | ``` 40 | 41 | Et voilà! 42 | 43 | The overloads are completely bound to the scope they're declared in, so for example this works: 44 | 45 | ```javascript 46 | function pointAdd ([leftX, leftY], [rightX, rightY]) { 47 | return [ 48 | leftX + rightX, 49 | leftY + rightY, 50 | ]; 51 | } 52 | 53 | function createPoint () { 54 | defineBinaryOperator("+", pointAdd) 55 | return [1, 2] + [3, 5]; 56 | } 57 | 58 | 1 + 2 // 3 59 | createPoint() // [ 4, 7 ] 60 | ``` 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-operator-overload", 3 | "version": "2.0.0", 4 | "description": "Babel plugin for overloading operators", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/jussi-kalliokoski/babel-plugin-operator-overload.git" 8 | }, 9 | "author": "Jussi Kalliokoski (http://juss.in)", 10 | "license": "ISC", 11 | "bugs": { 12 | "url": "https://github.com/jussi-kalliokoski/babel-plugin-operator-overload/issues" 13 | }, 14 | "homepage": "https://github.com/jussi-kalliokoski/babel-plugin-operator-overload", 15 | "keywords": [ 16 | "babel-plugin", 17 | "babel", 18 | "plugin" 19 | ], 20 | "main": "index.js", 21 | "files": [ 22 | "index.js" 23 | ], 24 | "scripts": { 25 | "prepublish": "babel -o index.js src/index.js", 26 | "test": "mocha test/spec" 27 | }, 28 | "peerDependencies": { 29 | "babel-core": "^6.5.1" 30 | }, 31 | "devDependencies": { 32 | "babel-cli": "^6.5.1", 33 | "babel-preset-es2015": "^6.5.0", 34 | "expect.js": "^0.3.1", 35 | "mocha": "^2.4.5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const OPERATOR_TYPES = { 2 | "+": "op1", 3 | "-": "op2", 4 | "/": "op3", 5 | "*": "op4", 6 | "%": "op4", 7 | "&": "op5", 8 | "&&": "op6", 9 | "|": "op7", 10 | "||": "op8", 11 | "<": "op9", 12 | "<<": "op10", 13 | "<<<": "op11", 14 | ">": "op12", 15 | ">>": "op13", 16 | }; 17 | 18 | const BINARY_OPERATOR_DEFINITION = "defineBinaryOperator"; 19 | 20 | function isOperatorDefinition (t, node) { 21 | return t.isCallExpression(node.expression) && 22 | t.isIdentifier(node.expression.callee) && 23 | node.expression.callee.name === BINARY_OPERATOR_DEFINITION && 24 | node.expression.arguments[0] != null && 25 | t.isLiteral(node.expression.arguments[0]) && 26 | node.expression.arguments[0].value in OPERATOR_TYPES; 27 | } 28 | 29 | function findOverload (scope, operator) { 30 | const type = OPERATOR_TYPES[operator]; 31 | 32 | while ( scope ) { 33 | if ( scope[type] ) { return scope[type]; } 34 | scope = scope.parent; 35 | } 36 | } 37 | 38 | module.exports = ({ types: t }) => ({ 39 | visitor: { 40 | ExpressionStatement (path) { 41 | const { node, scope } = path; 42 | 43 | if ( !isOperatorDefinition(t, node) ) { return; } 44 | 45 | const operator = node.expression.arguments[0].value; 46 | const operatorType = OPERATOR_TYPES[operator]; 47 | const id = scope.generateUidIdentifier(operatorType); 48 | 49 | scope[operatorType] = id; 50 | 51 | path.replaceWith(t.VariableDeclaration("const", [ 52 | t.VariableDeclarator(id, node.expression.arguments[1]), 53 | ])); 54 | }, 55 | 56 | BinaryExpression (path) { 57 | const { node, scope } = path; 58 | const overload = findOverload(scope, node.operator); 59 | 60 | if ( !overload ) { return; } 61 | 62 | path.replaceWith(t.CallExpression(overload, [node.left, node.right])); 63 | }, 64 | }, 65 | }); 66 | -------------------------------------------------------------------------------- /test/fixtures/pipe-operator/expected.js: -------------------------------------------------------------------------------- 1 | const _op = (left, right) => right(left); 2 | 3 | const x = _op(a(), b()); 4 | -------------------------------------------------------------------------------- /test/fixtures/pipe-operator/input.js: -------------------------------------------------------------------------------- 1 | defineBinaryOperator("|", (left, right) => right(left)); 2 | const x = a() | b(); 3 | -------------------------------------------------------------------------------- /test/spec/fixtureSpec.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const expect = require("expect.js"); 3 | const babel = require("babel-core"); 4 | const OperatorOverload = require("../.."); 5 | 6 | const BABEL_OPTIONS = { 7 | plugins: [OperatorOverload], 8 | }; 9 | 10 | const TEST_CASES = [ 11 | "pipe-operator", 12 | ]; 13 | 14 | TEST_CASES.forEach((testCase) => { 15 | describe(testCase, () => { 16 | it("should produce expected results", () => { 17 | const input = fs.readFileSync(require.resolve("../fixtures/" + testCase + "/input"), "utf8").trim(); 18 | const expected = fs.readFileSync(require.resolve("../fixtures/" + testCase + "/expected"), "utf8").trim(); 19 | const actual = babel.transform(input, BABEL_OPTIONS).code.trim(); 20 | 21 | expect(actual).to.be(expected); 22 | }); 23 | }); 24 | }); 25 | --------------------------------------------------------------------------------