Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partial application plugin #9474

Merged
merged 16 commits into from
Mar 13, 2019
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,4 @@
],
"directives": []
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -302,4 +302,4 @@
],
"directives": []
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,4 @@
],
"directives": []
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,4 @@
],
"directives": []
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,4 @@
],
"directives": []
}
}
}
3 changes: 3 additions & 0 deletions packages/babel-plugin-proposal-partial-application/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
src
test
*.log
19 changes: 19 additions & 0 deletions packages/babel-plugin-proposal-partial-application/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# @babel/plugin-proposal-partial-application

> Introduces a new ? token in an argument list which allows for partially applying an argument list to a call expression.

See our website [@babel/plugin-proposal-partial-application](https://babeljs.io/docs/en/next/babel-plugin-proposal-partial-application.html) for more information.

## Install

Using npm:

```sh
npm install --save-dev @babel/plugin-proposal-partial-application
```

or using yarn:

```sh
yarn add @babel/plugin-proposal-partial-application --dev
```
25 changes: 25 additions & 0 deletions packages/babel-plugin-proposal-partial-application/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@babel/plugin-proposal-partial-application",
"version": "7.2.0",
"description": "Introduces a new ? token in an argument list which allows for partially applying an argument list to a call expression",
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-partial-application",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"main": "lib/index.js",
"keywords": [
"babel-plugin"
],
"dependencies": {
"@babel/helper-plugin-utils": "^7.0.0",
"@babel/plugin-syntax-partial-application": "^7.2.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"devDependencies": {
"@babel/core": "^7.2.0",
"@babel/helper-plugin-test-runner": "^7.0.0"
}
}
151 changes: 151 additions & 0 deletions packages/babel-plugin-proposal-partial-application/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { declare } from "@babel/helper-plugin-utils";
import syntaxPartialApplication from "@babel/plugin-syntax-partial-application";
import { types as t } from "@babel/core";

export default declare(api => {
api.assertVersion(7);

/**
* a function to figure out if a call expression has
* ArgumentPlaceholder as one of its arguments
* @param node a callExpression node
* @returns boolean
*/
function hasArgumentPlaceholder(node) {
return node.arguments.some(arg => t.isArgumentPlaceholder(arg));
}

function unwrapArguments(node, scope) {
const init = [];
for (let i = 0; i < node.arguments.length; i++) {
if (
!t.isArgumentPlaceholder(node.arguments[i]) &&
!t.isImmutable(node.arguments[i])
) {
const id = scope.generateUidIdentifierBasedOnNode(
node.arguments[i],
"param",
);
scope.push({ id });
if (t.isSpreadElement(node.arguments[i])) {
init.push(
t.assignmentExpression(
"=",
t.cloneNode(id),
t.arrayExpression([t.spreadElement(node.arguments[i].argument)]),
),
);
node.arguments[i].argument = t.cloneNode(id);
} else {
init.push(
t.assignmentExpression("=", t.cloneNode(id), node.arguments[i]),
);
node.arguments[i] = t.cloneNode(id);
}
}
}
return init;
}

function replacePlaceholders(node, scope) {
const placeholders = [];
const args = [];

node.arguments.forEach(arg => {
if (t.isArgumentPlaceholder(arg)) {
const id = scope.generateUid("_argPlaceholder");
placeholders.push(t.identifier(id));
args.push(t.identifier(id));
} else {
args.push(arg);
}
});
return [placeholders, args];
}

return {
name: "proposal-partial-application",
inherits: syntaxPartialApplication,

visitor: {
CallExpression(path) {
if (!hasArgumentPlaceholder(path.node)) {
return;
}
const { node, scope } = path;
const functionLVal = path.scope.generateUidIdentifierBasedOnNode(
node.callee,
);

const argsInitializers = unwrapArguments(node, scope);
const [placeholdersParams, args] = replacePlaceholders(node, scope);

scope.push({ id: functionLVal });

if (node.callee.type === "MemberExpression") {
const receiverLVal = path.scope.generateUidIdentifierBasedOnNode(
node.callee.object,
);
scope.push({ id: receiverLVal });
const finalExpression = t.sequenceExpression([
t.assignmentExpression(
"=",
t.cloneNode(receiverLVal),
node.callee.object,
),
t.assignmentExpression(
"=",
t.cloneNode(functionLVal),
t.memberExpression(
receiverLVal,
node.callee.property,
false,
false,
),
),
...argsInitializers,
t.functionExpression(
node.callee.property,
placeholdersParams,
t.blockStatement(
[
t.returnStatement(
t.callExpression(
t.memberExpression(
functionLVal,
t.identifier("call"),
false,
false,
),
[receiverLVal, ...args],
),
),
],
[],
),
false,
false,
),
]);
path.replaceWith(finalExpression);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not big deal by any means, but since both of these end with replacing the path with a sequenceExpression, we could do:

const sequenceParts = [];

if (node.callee.type === "MemberExpression") {
  sequenceParts.push(
    /* stuff */
  );
} else {
  sequenceParts.push(
    /* stuff */
  );
}

path.replaceWith(t.sequenceExpression(sequenceParts));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea! made the necessary changes.

} else {
const finalExpression = t.sequenceExpression([
t.assignmentExpression("=", t.cloneNode(functionLVal), node.callee),
...argsInitializers,
t.functionExpression(
node.callee,
placeholdersParams,
t.blockStatement(
[t.returnStatement(t.callExpression(functionLVal, args))],
[],
),
false,
false,
),
]);
path.replaceWith(finalExpression);
}
},
},
};
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class Partial {
constructor() {
this.compare = this.compare(?, ?);
}
compare(a, b) {
if(a > b){
return a;
}
}
}

const foo = new Partial;

expect(foo.compare(3,1)).toEqual(3);
expect(foo.compare.length).toEqual(2);
expect(foo.compare.name).toEqual("compare");
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class Collator {
constructor() {
this.compare = this.compare(?, ?);
}
compare(a, b) {
if (a > b) {
return a;
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, but this semi seems unnecessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, unnecessary, I'll remove it.

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class Collator {
constructor() {
var _this$compare, _this;

this.compare = (_this = this, _this$compare = _this.compare, function compare(_argPlaceholder, _argPlaceholder2) {
return _this$compare.call(_this, _argPlaceholder, _argPlaceholder2);
});
}

compare(a, b) {
if (a > b) {
return a;
}

;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"use strict";

function add(x, y) { return x + y; }
const addOne = add(1, ?);

expect(addOne(2)).toEqual(3);
expect(addOne.length).toEqual(1);
expect(addOne.name).toEqual("add");
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"use strict";

const foo = bar(?, 1);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use strict";

var _bar;

const foo = (_bar = bar, function bar(_argPlaceholder) {
return _bar(_argPlaceholder, 1);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"use strict";

function add(x, y) { return x + y; }
const addTen = add(?, 10);

expect(addTen(2)).toEqual(12);
expect(addTen.length).toEqual(1);
expect(addTen.name).toEqual("add");
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"use strict";

const foo = bar(1, ?);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use strict";

var _bar;

const foo = (_bar = bar, function bar(_argPlaceholder) {
return _bar(1, _argPlaceholder);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use strict";
const obj = { add: (x, y) => x + y };
const addOne = obj.add(1, ?);

expect(addOne(5)).toEqual(6);
expect(addOne.length).toEqual(1);
expect(addOne.name).toEqual("add");
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use strict";

const g = o.f(?, x, 1);

const h = p.b(1, y, x, 2, ?);

const j = a.b.c.d.e.foo(?, 1);
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use strict";

var _x, _o$f, _o, _y, _x2, _p$b, _p, _a$b$c$d$e$foo, _a$b$c$d$e;

const g = (_o = o, _o$f = _o.f, _x = x, function f(_argPlaceholder) {
return _o$f.call(_o, _argPlaceholder, _x, 1);
});
const h = (_p = p, _p$b = _p.b, _y = y, _x2 = x, function b(_argPlaceholder2) {
return _p$b.call(_p, 1, _y, _x2, 2, _argPlaceholder2);
});
const j = (_a$b$c$d$e = a.b.c.d.e, _a$b$c$d$e$foo = _a$b$c$d$e.foo, function foo(_argPlaceholder3) {
return _a$b$c$d$e$foo.call(_a$b$c$d$e, _argPlaceholder3, 1);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
function add(a, b) {
return a + b;
}

function square(x){
return x * x;
}

const foo = add(?, 1);
const bar = square(?);

expect(bar(foo(2))).toEqual(9);
expect(foo(2)).toEqual(3);
expect(bar(4)).toEqual(16);
expect(foo.length).toEqual(1);
expect(foo.name).toEqual("add");
expect(bar.length).toEqual(1);
expect(bar.name).toEqual("square");
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"use strict";

const baz = ber(2, 4, ?, ?, 1);
const foo = bar(1, ?, x, 3, ?);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"use strict";

var _ber, _x, _bar;

const baz = (_ber = ber, function ber(_argPlaceholder, _argPlaceholder2) {
return _ber(2, 4, _argPlaceholder, _argPlaceholder2, 1);
});
const foo = (_bar = bar, _x = x, function bar(_argPlaceholder3, _argPlaceholder4) {
return _bar(1, _argPlaceholder3, _x, 3, _argPlaceholder4);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use strict";

function add(x, y) { return x + y; }
const addOne = add(1, ?);
const addTen = add(?, 10);

expect(addOne(addTen(1))).toEqual(12);