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

Implemented transform-inline-consecutive-adds plugin. #286

Merged
merged 25 commits into from
Dec 1, 2016

Conversation

shinew
Copy link
Contributor

@shinew shinew commented Nov 22, 2016

This plugin inlines consecutive attribute definitions.

This plugin inlines consecutive attribute definitions.
@shinew shinew changed the title Implemented plugin-transform-consecutive-attribute-defs-plugin. Implemented transform-consecutive-attribute-defs plugin. Nov 22, 2016
@kangax
Copy link
Member

kangax commented Nov 22, 2016

This looks great! Couple things:

  1. Can we utilize computed properties? let x = {}; x[y] = 1;let x = { [y]: 1 }
  2. Should we look into comma operator too? let x = { }; x.a = 1, x.b = 2
  3. Can we make it work with arrays too?

seen = true;
}
},
});
Copy link
Member

Choose a reason for hiding this comment

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

var foo = {};
foo.a = function() {
  var foo = 1;
}

Copy link
Member

@boopathi boopathi Nov 22, 2016

Choose a reason for hiding this comment

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

doing

binding.referencePaths[i].isDescendant(right)

should give you correct result and a cheaper one too I suppose.

@boopathi
Copy link
Member

boopathi commented Nov 22, 2016

we aren't optimizing this, right ?

var a = {};
a.b = {};
a.b.x = 1;
a.b.y = 2;
a.c = 3;

@boopathi boopathi added the Tag: New Feature Pull Request adding a new feature label Nov 22, 2016
@shinew
Copy link
Contributor Author

shinew commented Nov 22, 2016

@boopathi not currently -- fixing a bug right now:

e.g.

var foo = {};
foo.a = 4;
foo.b = bar();
...
function bar() {
  console.log(foo);
  return 0;
}

The bug is that the identifier of the created object (i.e. foo) cannot be referenced before the initialization finishes, while if the properties are created one-by-one, the create object can be referenced by their rvals (i.e. bar()). We had a simple, 1-level lookup before. I added code for recursive lookups below, that should resolve cases like the above example correctly.

@shinew
Copy link
Contributor Author

shinew commented Nov 22, 2016

@kangax:

  1. computed properties should be ok, since I think we can just reference check the computation to make sure there aren't bad side effects like:
var foo = {};
foo.a = 4;
foo[bar()] = 5;
...
function bar() {
  console.log(foo);
  return 0;
}
  1. this is possible as well, if we only take consecutive statements of form:
statement => assignment, assignments
assignment => id1.id2 = expr | none
  1. This can also work for arrays (probably easier since there are no lvals to check in statements like foo.push(bar)).

@kangax
Copy link
Member

kangax commented Nov 23, 2016

Awesome, thanks!

Let's just rename the plugin to "transform-consecutive-property-assignments" since "attributes" usually refer to something else in ES

@shinew shinew changed the title Implemented transform-consecutive-attribute-defs plugin. Implemented transform-inline-consecutive-adds plugin. Nov 23, 2016
it("should collapse normal computed properties", () => {
const source = unpad(`
var foo = {};
foo["a"] = 0;
Copy link
Member

Choose a reason for hiding this comment

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

These are not computed properties though. It's just a regular bracket-based property resolution. What I wanted us to do is transform to computed property, when there's an identifier var foo = {}; foo[x] = 1 to var foo = { [x]: 1 };.

However, on a second thought, this would mean we're turning potentially ES5 code to ES6 so it's a special kind of transformation. Let's ignore that for now and revisit later.

Copy link
Member

Choose a reason for hiding this comment

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

I faced this in #242 as well. We could let the user decide based on options - outputLanguage: es2015|es2016|es2017|...:

foo.add(3);
`);
const expected = unpad(`
var foo = new Set([1, 2, 3]);
Copy link
Member

Choose a reason for hiding this comment

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

This is awesome!

it("should collapse statements for arrays", () => {
const source = unpad(`
var foo = [1, 2];
foo.push(3, 4), foo.push(5);
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should bother with property-based assignments:

var foo = [];
foo[1] = 'blah';
foo[2] = 'blah';

transforming to:

var foo=[,"blah","blah"];

Closure Compiler does this, btw — https://closure-compiler.appspot.com/home#code%3Dvar%2520foo%2520%253D%2520%255B%255D%253B%250Afoo%255B1%255D%2520%253D%2520'blah'%253B%250Afoo%255B2%255D%2520%253D%2520'blah'%253B

They also use smart heuristic (based on final length?) and transform this:

var foo = [];
foo[3] = 'blah';
foo[5] = 'blah';
foo[7] = 'blah';

to:

var foo=[,,,"blah",,"blah",,"blah"];

but not:

var foo = [];
foo[10] = 'blah';

{
"name": "babel-plugin-transform-inline-consecutive-adds",
"version": "0.0.1",
"description": "This plugin inlines consecutive property definitions, array pushes, etc.",
Copy link
Member

Choose a reason for hiding this comment

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

*property assignments

if (statements.length > 0) {
const addons = exprs.map((e) => collapser.extractAddon(e));
addons.forEach((addon) => collapser.addAddon(t, addon, init));
statements.forEach((s) => s.remove());
Copy link
Member

Choose a reason for hiding this comment

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

I really like the overall architecture here, where we have a nice modular way of defining extractable behavior for various data types — arrays, objects, sets, whatever else comes in the future.

But I did find some names a little confusing while reading it and trying to figure it out.

I would rename addons to assignments — extractAssignment, addAssignment — since addons is a little ambiguous and feels out of place.

I also found makeCheckExpression a little confusing. How about makeExpressionValidator (alternatively getExpressionValidator or make/getExpressionChecker) and checkExpressionType --> isExpressionTypeValid

Copy link
Member

@kangax kangax left a comment

Choose a reason for hiding this comment

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

Inline

@kangax
Copy link
Member

kangax commented Nov 27, 2016

@boopathi mind taking a look at the implementation details?

@boopathi
Copy link
Member

Sure ... I'll take a look tomorrow.

@boopathi
Copy link
Member

This should be added to preset. But I guess, this was forked before preset options was merged to master. So we can merge this and I can add it to preset separately or @shinew, mind adding it to preset with some option name ?

@kangax
Copy link
Member

kangax commented Nov 29, 2016

@shinew Sorry, I accidentally commented directly on your last commit — 5c85e78 Can you see the comments?

visitor: {
VariableDeclaration(varDecl) {
const topLevel = validateTopLevel(varDecl);
if (topLevel === undefined) {
Copy link
Member

Choose a reason for hiding this comment

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

if (!toplevel)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

"./array-property-collapser",
"./set-collapser"
].map((x) => {
const Collapser = require(x);
Copy link
Member

Choose a reason for hiding this comment

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

use require("./object-collapser"). Do not use dynamic requires - for bundling purposes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -1,26 +1,27 @@
jest.autoMockOff();

const mocks = [
"babel-plugin-inline-consecutive-adds",
Copy link
Member

Choose a reason for hiding this comment

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

typo - missed -transform-

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

}

function getIdAndFunctionReferences(name, parent) {
const binding = parent.scope.getBinding(name);
Copy link
Member

Choose a reason for hiding this comment

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

this is currently failing when used with other plugins

Copy link
Member

Choose a reason for hiding this comment

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

plugins that create a new binding - like for of transformation in es2015 block-scoping, etc...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So what's the todo here? When new scopes are created, they should make sure the bindings are resolved properly (leaves AST in consistent state).

Copy link
Member

Choose a reason for hiding this comment

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

This is currently an issue with mangler and deadcode. Running it as a separate plugin and path.scope.crawl to recalculate all the bindings and references ensures it's partly fixing most of the issues.

I'm not yet sure if this is problem here. If there is an unregistered binding, bailing out as early as possible without breaking the code - without applying the optimisation is actually fine.

Just pointing it out so it is verified.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see -- added check for when binding == false.

.reduce((a, b) => a + b, 0); // sum

return (numCommaAdded + sizeOfRvals < statementsLength);
}
Copy link
Member

@boopathi boopathi Nov 29, 2016

Choose a reason for hiding this comment

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

rvals created on the fly (by other plugins in the same transform cycle) won't have a start and end ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Aside: I don't like the fact that we need to check for the existence of .start and .end every time -- those should be guaranteed by the babel framework (as in the plugin handbook). When the AST is modified, it should update metadata like this (and also scoping).

if (!obj.isIdentifier() || obj.node.name !== objName) {
return false;
}
if (!prop.isIdentifier() && checkReference(prop)) {
Copy link
Member

Choose a reason for hiding this comment

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

you should also check if this is a computed property ...

function foo() {
  var a = {};
  a[global] = bar; // shouldn't transform to { global: bar }
}

Copy link
Member

@boopathi boopathi Nov 29, 2016

Choose a reason for hiding this comment

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

Ah. I should've added this comment in the place where you add it to the list of properties

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How would we know if it's a computed property?

Copy link
Member

Choose a reason for hiding this comment

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

node.computed = true

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

var foo = {};
foo[global] = 0;
`);
expect(transform(source)).toBe(source);
Copy link
Member

Choose a reason for hiding this comment

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

why not transform to

var foo = {
  [global]: 0
};

Copy link
Member

Choose a reason for hiding this comment

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

But this might require an option now .. Can we add some meaningful option for now ?

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Let's leave this for later. Wouldn't want to cram everything into this PR.

@kangax
Copy link
Member

kangax commented Nov 30, 2016

@shinew could you check if our size benchmark reports any positive size differences with this transform?

@kangax
Copy link
Member

kangax commented Nov 30, 2016

@shinew could you please also fix the tests and we should be ready to go

@shinew
Copy link
Contributor Author

shinew commented Nov 30, 2016

@kangax I tried running the benchmark (./scripts/benchmark.js react@0.14.3 react/dist/react.js), but I got an error (below). This was on master.

/Users/shinew/dev/babili/node_modules/babel-core/lib/transformation/file/index.js:600
      throw err;
      ^

TypeError: unknown: Property right of AssignmentExpression expected node to be of a type ["Expression"] but instead got undefined
    at Object.validate (/Users/shinew/dev/babili/node_modules/babel-types/lib/definitions/index.js:109:13)
    at validate (/Users/shinew/dev/babili/node_modules/babel-types/lib/index.js:511:9)
    at Object.builder (/Users/shinew/dev/babili/node_modules/babel-types/lib/index.js:472:7)
    **at liftDeclaration (/Users/shinew/dev/babili/packages/babel-plugin-transform-merge-sibling-variables/lib/index.js:31:29)**
    at PluginPass.ForStatement (/Users/shinew/dev/babili/packages/babel-plugin-transform-merge-sibling-variables/lib/index.js:44:9)
    at newFn (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/visitors.js:276:21)
    at NodePath._call (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/path/context.js:76:18)
    at NodePath.call (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/path/context.js:48:17)
    at NodePath.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/path/context.js:105:12)
    at TraversalContext.visitQueue (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:150:16)
    at TraversalContext.visitMultiple (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:103:17)
    at TraversalContext.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:190:19)
    at Function.traverse.node (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/index.js:114:17)
    at NodePath.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/path/context.js:115:19)
    at TraversalContext.visitQueue (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:150:16)
    at TraversalContext.visitSingle (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:108:19)
    at TraversalContext.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:192:19)
    at Function.traverse.node (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/index.js:114:17)
    at NodePath.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/path/context.js:115:19)
    at TraversalContext.visitQueue (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:150:16)
    at TraversalContext.visitMultiple (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:103:17)
    at TraversalContext.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:190:19)
    at Function.traverse.node (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/index.js:114:17)
    at NodePath.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/path/context.js:115:19)
    at TraversalContext.visitQueue (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:150:16)
    at TraversalContext.visitSingle (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:108:19)
    at TraversalContext.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:192:19)
    at Function.traverse.node (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/index.js:114:17)
    at NodePath.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/path/context.js:115:19)
    at TraversalContext.visitQueue (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:150:16)
    at TraversalContext.visitMultiple (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:103:17)
    at TraversalContext.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:190:19)
    at Function.traverse.node (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/index.js:114:17)
    at NodePath.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/path/context.js:115:19)
    at TraversalContext.visitQueue (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:150:16)
    at TraversalContext.visitSingle (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:108:19)
    at TraversalContext.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:192:19)
    at Function.traverse.node (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/index.js:114:17)
    at NodePath.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/path/context.js:115:19)
    at TraversalContext.visitQueue (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:150:16)
    at TraversalContext.visitMultiple (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:103:17)
    at TraversalContext.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:190:19)
    at Function.traverse.node (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/index.js:114:17)
    at NodePath.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/path/context.js:115:19)
    at TraversalContext.visitQueue (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:150:16)
    at TraversalContext.visitMultiple (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:103:17)
    at TraversalContext.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:190:19)
    at Function.traverse.node (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/index.js:114:17)
    at NodePath.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/path/context.js:115:19)
    at TraversalContext.visitQueue (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:150:16)
    at TraversalContext.visitSingle (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:108:19)
    at TraversalContext.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:192:19)
    at Function.traverse.node (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/index.js:114:17)
    at NodePath.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/path/context.js:115:19)
    at TraversalContext.visitQueue (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:150:16)
    at TraversalContext.visitSingle (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:108:19)
    at TraversalContext.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:192:19)
    at Function.traverse.node (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/index.js:114:17)
    at NodePath.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/path/context.js:115:19)
    at TraversalContext.visitQueue (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:150:16)
    at TraversalContext.visitMultiple (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:103:17)
    at TraversalContext.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/context.js:190:19)
    at Function.traverse.node (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/index.js:114:17)
    at NodePath.visit (/Users/shinew/dev/babili/node_modules/babel-traverse/lib/path/context.js:115:19)

Will update the test suite for this plugin.

@kangax kangax merged commit 2e3dea8 into babel:master Dec 1, 2016
@kangax
Copy link
Member

kangax commented Dec 1, 2016

Ok, based on the benchmark error, looks like we have an issue with recently implemented var lifting behavior /cc @vigneshshanmugam

@kangax
Copy link
Member

kangax commented Dec 1, 2016

Damn it, forgot to squash before merging. @hzoo @boopathi is there a way to squash now that it's merged?

@kangax
Copy link
Member

kangax commented Dec 1, 2016

Published to NPM

@boopathi
Copy link
Member

boopathi commented Dec 1, 2016

I squashed. Take a pull git pull --rebase

Squash after merge:

git log --oneline
# xxxxxx - Merge Commit
# ensure log shows Merge commit as the first commit
git rebase -i HEAD~2
# since it is a merge commit, git will show all the commits that were merged
# usual rebase stuff (pick -> squash except first commit)
# save and
git push -f origin master

# I usually verify like this -
# Before starting rebase -
# ensure latest changes
git pull --rebase 
# fork a branch from master
git checkout -b squash-1 
# go back to master and do the squash
git checkout master && git rebase -i HEAD~2
# check if anything changed
git diff squash-1

# Important - check if the previous commits are NOT affected
# That they EXACTLY have the SAME commit hashes
# vim is pretty good, you can use any diff tool.
vim -d <(git log --oneline | head -n 30) <(git log --oneline squash-1 | head -n 30)
# ensure head -n XX is set accordingly
# branch-name in the second git log command is set correctly.

@boopathi boopathi mentioned this pull request Dec 1, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Tag: New Feature Pull Request adding a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants