diff --git a/packages/babel-plugin-syntax-pipeline-operator/README.md b/packages/babel-plugin-syntax-pipeline-operator/README.md new file mode 100644 index 0000000000000..1d06577938785 --- /dev/null +++ b/packages/babel-plugin-syntax-pipeline-operator/README.md @@ -0,0 +1,35 @@ +# babel-plugin-syntax-pipeline-operator + +Allow parsing of do expressions. + +## Installation + +```sh +$ npm install babel-plugin-syntax-pipeline-operator +``` + +## Usage + +### Via `.babelrc` (Recommended) + +**.babelrc** + +```json +{ + "plugins": ["syntax-pipeline-operator"] +} +``` + +### Via CLI + +```sh +$ babel --plugins syntax-pipeline-operator script.js +``` + +### Via Node API + +```javascript +require("babel-core").transform("code", { + plugins: ["syntax-pipeline-operator"] +}); +``` diff --git a/packages/babel-plugin-syntax-pipeline-operator/package.json b/packages/babel-plugin-syntax-pipeline-operator/package.json new file mode 100644 index 0000000000000..09ec94901fa1e --- /dev/null +++ b/packages/babel-plugin-syntax-pipeline-operator/package.json @@ -0,0 +1,17 @@ +{ + "name": "babel-plugin-syntax-pipeline-operator", + "version": "6.3.13", + "description": "Allow parsing of the pipeline operator", + "repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-pipeline-operator", + "license": "MIT", + "main": "lib/index.js", + "keywords": [ + "babel-plugin" + ], + "dependencies": { + "babel-runtime": "^6.0.0" + }, + "devDependencies": { + "babel-helper-plugin-test-runner": "^6.3.13" + } +} diff --git a/packages/babel-plugin-syntax-pipeline-operator/src/index.js b/packages/babel-plugin-syntax-pipeline-operator/src/index.js new file mode 100644 index 0000000000000..d98f6686f348c --- /dev/null +++ b/packages/babel-plugin-syntax-pipeline-operator/src/index.js @@ -0,0 +1,7 @@ +export default function () { + return { + manipulateOptions(opts, parserOpts) { + parserOpts.plugins.push("pipelineOperator"); + } + }; +} diff --git a/packages/babel-plugin-transform-pipeline-operator/README.md b/packages/babel-plugin-transform-pipeline-operator/README.md new file mode 100644 index 0000000000000..f6b50e0e47d7b --- /dev/null +++ b/packages/babel-plugin-transform-pipeline-operator/README.md @@ -0,0 +1,35 @@ +# babel-plugin-transform-pipeline-operator + +Compile pipeline operator `|>` usage to ES5. See [the ES.next proposal](https://github.com/mindeavor/es-pipeline-operator) for details. + +## Installation + +```sh +$ npm install babel-plugin-transform-pipeline-operator +``` + +## Usage + +### Via `.babelrc` (Recommended) + +**.babelrc** + +```json +{ + "plugins": ["transform-pipeline-operator"] +} +``` + +### Via CLI + +```sh +$ babel --plugins transform-pipeline-operator script.js +``` + +### Via Node API + +```javascript +require("babel-core").transform("code", { + plugins: ["transform-pipeline-operator"] +}); +``` diff --git a/packages/babel-plugin-transform-pipeline-operator/package.json b/packages/babel-plugin-transform-pipeline-operator/package.json new file mode 100644 index 0000000000000..df1e0ddef1bfb --- /dev/null +++ b/packages/babel-plugin-transform-pipeline-operator/package.json @@ -0,0 +1,18 @@ +{ + "name": "babel-plugin-transform-pipeline-operator", + "version": "6.3.13", + "description": "Compile do expressions to ES5", + "repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-pipeline-operator", + "license": "MIT", + "main": "lib/index.js", + "keywords": [ + "babel-plugin" + ], + "dependencies": { + "babel-plugin-syntax-pipeline-operator": "^6.3.13", + "babel-runtime": "^6.0.0" + }, + "devDependencies": { + "babel-helper-plugin-test-runner": "^6.3.13" + } +} diff --git a/packages/babel-plugin-transform-pipeline-operator/src/index.js b/packages/babel-plugin-transform-pipeline-operator/src/index.js new file mode 100644 index 0000000000000..ae9fce7a04a64 --- /dev/null +++ b/packages/babel-plugin-transform-pipeline-operator/src/index.js @@ -0,0 +1,71 @@ +export default function ({ types: t }) { + return { + inherits: require("babel-plugin-syntax-pipeline-operator"), + + visitor: { + + BinaryExpression(path) { + if (path.node.operator === "|>") { + let right = path.node.right; + + if ( + right.type === "ArrowFunctionExpression" && + right.params.length === 1 && + t.isIdentifier(right.params[0]) && + t.isExpression(right.body) + ) { + // + // Optimize away arrow function! + // + // This step converts: + // let result = arg |> x => x + x; + // To: + // let _x = arg; + // let result = arg |> x => x + x; + // + let paramName = right.params[0].name; + let placeholder = path.scope.generateUidIdentifier(paramName); + path.parentPath.insertBefore(t.variableDeclarator(placeholder, path.node.left)) + + // + // This step converts: + // let _x = arg; + // let result = arg |> x => x + x; + // To: + // let _x = arg; + // let result = arg |> _x => _x + _x; + // + let rightPath = path.node.right._paths[0] + rightPath.scope.rename(paramName, placeholder.name) + + // + // This step converts: + // let _x = arg; + // let result = arg |> _x => _x + _x; + // To: + // let _x = arg; + // let result = _x + _x; + // + path.replaceWith(right.body); + } + else { + // + // Simple invocation. + // + // Converts: + // x |> obj.f; + // To: + // obj.f(x); + // + path.replaceWith({ + type: "CallExpression", + callee: path.node.right, + arguments: [ path.node.left ] + }); + } + } + } + + } + }; +} diff --git a/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/arrow-functions.js b/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/arrow-functions.js new file mode 100644 index 0000000000000..98b1ab27dd808 --- /dev/null +++ b/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/arrow-functions.js @@ -0,0 +1,14 @@ +var result = [5,10] + |> _ => _.map(x => x * 2) + |> _ => _.reduce( (a,b) => a + b ) + |> sum => sum + 1 + +assert.equal(result, 31) + + +var inc = (x) => x + 1; +var double = (x) => x * 2; + +var result2 = [4, 9].map( x => x |> inc |> double ) + +assert.deepEqual(result2, [10, 20]) diff --git a/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/basic.js b/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/basic.js new file mode 100644 index 0000000000000..3d1a636f3c06b --- /dev/null +++ b/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/basic.js @@ -0,0 +1,3 @@ +var inc = (x) => x + 1 + +assert.equal(10 |> inc, 11); diff --git a/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/chaining.js b/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/chaining.js new file mode 100644 index 0000000000000..3b7a74eb7595b --- /dev/null +++ b/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/chaining.js @@ -0,0 +1,4 @@ +var inc = (x) => x + 1; +var double = (x) => x * 2; + +assert.equal(10 |> inc |> double, 22); diff --git a/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/currying.js b/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/currying.js new file mode 100644 index 0000000000000..4279d4fec8012 --- /dev/null +++ b/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/currying.js @@ -0,0 +1,6 @@ + +var map = (fn) => (array) => array.map(fn); + +var result = [10,20] |> map(x => x * 20); + +assert.deepEqual(result, [200, 400]) diff --git a/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/multiple-argument-use.js b/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/multiple-argument-use.js new file mode 100644 index 0000000000000..b83dc3f21b8a4 --- /dev/null +++ b/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/multiple-argument-use.js @@ -0,0 +1,5 @@ +var array = [10,20,30]; + +var last = array |> a => a[a.length-1]; + +assert.equal(last, 30); diff --git a/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/options.json b/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/options.json new file mode 100644 index 0000000000000..cd9de337d5f9a --- /dev/null +++ b/packages/babel-plugin-transform-pipeline-operator/test/fixtures/pipeline-operator/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["transform-pipeline-operator"] +} diff --git a/packages/babel-plugin-transform-pipeline-operator/test/index.js b/packages/babel-plugin-transform-pipeline-operator/test/index.js new file mode 100644 index 0000000000000..1f6634aabde02 --- /dev/null +++ b/packages/babel-plugin-transform-pipeline-operator/test/index.js @@ -0,0 +1 @@ +require("babel-helper-plugin-test-runner")(__dirname); diff --git a/packages/babylon/src/parser/expression.js b/packages/babylon/src/parser/expression.js index 64b7f1c18d59f..cfca5506267b6 100644 --- a/packages/babylon/src/parser/expression.js +++ b/packages/babylon/src/parser/expression.js @@ -189,6 +189,12 @@ pp.parseExprOp = function(left, leftStartPos, leftStartLoc, minPrec, noIn) { let startPos = this.state.start; let startLoc = this.state.startLoc; + + if (node.operator === "|>") { + // Support syntax such as 10 |> x => x + 1 + this.state.potentialArrowAt = startPos; + } + node.right = this.parseExprOp(this.parseMaybeUnary(), startPos, startLoc, op.rightAssociative ? prec - 1 : prec, noIn); this.finishNode(node, (op === tt.logicalOR || op === tt.logicalAND) ? "LogicalExpression" : "BinaryExpression"); diff --git a/packages/babylon/src/tokenizer/index.js b/packages/babylon/src/tokenizer/index.js index 8e610f77ddfb3..9f0d7e1467bab 100644 --- a/packages/babylon/src/tokenizer/index.js +++ b/packages/babylon/src/tokenizer/index.js @@ -316,8 +316,12 @@ export default class Tokenizer { return this.finishOp(type, width); } - readToken_pipe_amp(code) { // '|&' + readToken_pipe_amp(code) { // '|&', '|>' let next = this.input.charCodeAt(this.state.pos + 1); + + if (code === 124 && next === 62 && this.hasPlugin("pipelineOperator")) { + return this.finishOp(tt.pipeline, 2); + } if (next === code) return this.finishOp(code === 124 ? tt.logicalOR : tt.logicalAND, 2); if (next === 61) return this.finishOp(tt.assign, 2); return this.finishOp(code === 124 ? tt.bitwiseOR : tt.bitwiseAND, 1); @@ -444,7 +448,7 @@ export default class Tokenizer { case 37: case 42: // '%*' return this.readToken_mult_modulo(code); - case 124: case 38: // '|&' + case 124: case 38: // '|&', '|>' return this.readToken_pipe_amp(code); case 94: // '^' diff --git a/packages/babylon/src/tokenizer/types.js b/packages/babylon/src/tokenizer/types.js index c97e829287781..b055563d9486a 100644 --- a/packages/babylon/src/tokenizer/types.js +++ b/packages/babylon/src/tokenizer/types.js @@ -27,7 +27,7 @@ export class TokenType { this.isAssign = !!conf.isAssign; this.prefix = !!conf.prefix; this.postfix = !!conf.postfix; - this.binop = conf.binop || null; + this.binop = (conf.binop === 0) ? 0 : (conf.binop || null); this.updateContext = null; } } @@ -82,6 +82,7 @@ export const types = { assign: new TokenType("_=", {beforeExpr: true, isAssign: true}), incDec: new TokenType("++/--", {prefix: true, postfix: true, startsExpr: true}), prefix: new TokenType("prefix", {beforeExpr: true, prefix: true, startsExpr: true}), + pipeline: binop("|>", 0), logicalOR: binop("||", 1), logicalAND: binop("&&", 2), bitwiseOR: binop("|", 3),