Skip to content

Commit

Permalink
Evaluate some String and Array instance methods at compile time (#505)
Browse files Browse the repository at this point in the history
* Evaluate some String and Array instance methods at compile time

* Fix a few bugs and increase test coverage

* npm run format

* undefined -> void 0

* undefined -> void 0 in tests

* `isUndef(thing)` checks if `thing` is `undefined`

* I can typo!

* Rename `other` handler

* Pacify prettier

* npm run format

* To trigger CircleCI
  • Loading branch information
j-f1 authored and boopathi committed Jun 13, 2017
1 parent cb993ed commit 7e2b315
Show file tree
Hide file tree
Showing 4 changed files with 358 additions and 4 deletions.
37 changes: 34 additions & 3 deletions packages/babel-plugin-minify-constant-folding/README.md
@@ -1,6 +1,6 @@
# babel-plugin-minify-constant-folding

Tries to evaluate expressions and inline the result. For now only deals with numbers and strings.
Tries to evaluate expressions and inline the result.

## Example

Expand All @@ -10,7 +10,22 @@ Tries to evaluate expressions and inline the result. For now only deals with num
"a" + "b"
2 * 3;
4 | 3;
"b" + a + "c" + "d" + g + z + "f" + "h" + "z"
"b" + a + "c" + "d" + g + z + "f" + "h" + "i"

[a, b, c].concat([d, e], f, g, [h]);
["a", "b", "c"].join();
["a", "b", "c"].join('@');
[1, 2, 3].length;
[1, 2, 3][1];
[1, 2, 3].shift();
[1, 2, 3].slice(0, 2);
[a, b, c].pop();
[a, b, c].reverse();
"a,b,c".split(",");
"abc"[0];
"abc".charAt();
"abc".charAt(1);
"abc".length;
```

**Out**
Expand All @@ -19,7 +34,23 @@ Tries to evaluate expressions and inline the result. For now only deals with num
"ab";
6;
7;
"b" + a + "cd" + g + z + "fhz";
"b" + a + "cd" + g + z + "fhi";

[a, b, c, d, e, f, g, h];
"a,b,c";
"a@b@c";
3;
2;
2;
[1, 2];
c;
[c, b, a];
["a", "b", "c"];
"a";
"a";
"a";
"b";
3;
```

## Installation
Expand Down
Expand Up @@ -103,4 +103,142 @@ describe("constant-folding-plugin", () => {
`);
expect(transform(source)).toBe(expected);
});

it("should handle Array methods on array literals", () => {
const source = unpad(
`
[1, 2, 3].concat([4, 5, 6]);
[a, b, c].concat([d, e], f, g, [h]);
[1, 2, 3]["concat"]([4, 5, 6]);
[1, 2, 3].push([4, 5, 6]);
[1, 2, 3].join();
["a", "b", "c"].join();
["a", "b", "c"].join("@");
[1, 2, 3].length;
[1, 2, 3][1];
[1, 2, 3]["1"];
[1, 2, 3][4];
[].shift();
[1, 2, 3].shift();
[1, 2, 3].slice();
[1, 2, 3].slice(1);
[1, 2, 3].slice(0, 2);
[1, 2, 3].slice(0, -1);
[1, 2, 3].pop();
[a, b, c].pop();
[].pop();
[a, b, c].reverse();
[1, 2, 3].reverse();
[1, 2, 3].splice(1);
[1, 2, 3, 4].splice(1, 2);
`
);
const expected = unpad(
`
[1, 2, 3, 4, 5, 6];
[a, b, c, d, e, f, g, h];
[1, 2, 3, 4, 5, 6];
4;
"1,2,3";
"a,b,c";
"a@b@c";
3;
2;
2;
void 0;
void 0;
2;
[1, 2, 3];
[2, 3];
[1, 2];
[1, 2, 3].slice(0, -1);
3;
c;
void 0;
[c, b, a];
[3, 2, 1];
[2, 3];
[2, 3];
`
);
expect(transform(source)).toBe(expected);
});
it("should ignore bad calls to array expression methods", () => {
const source = unpad(
`
[1, 2, 3][concat]([4, 5, 6]);
[a, "b", "c"].join();
["a", "b", "c"].join(a);
[1, 2, 3].splice("a");
`
);
expect(transform(source)).toBe(source);
});
it("should ignore bad calls to string expression methods", () => {
const source = unpad(
`
"abc".something;
"abc"["something"];
`
);
expect(transform(source)).toBe(source);
});
it("should handle String methods on string literals", () => {
const source = unpad(
`
"a,b,c".split(",");
"a,b,c".split("");
"a,b,c".split();
"abc"[0];
"abc"["0"];
"abc"[4];
"abc".charAt();
"abc".charAt(1);
"abc".charCodeAt();
"abc".charCodeAt(1);
"abc".length;
"\u{1f44d}".charCodeAt();
"\u{1f44d}".charCodeAt(1);
"\u{1f44d}".codePointAt();
"\u{1f44d}".codePointAt(1);
`
);

const expected = unpad(
`
["a", "b", "c"];
["a", ",", "b", ",", "c"];
["a,b,c"];
"a";
"a";
void 0;
"a";
"b";
97;
98;
3;
${0xd83d};
${0xdc4d};
${0x1f44d};
${0xdc4d};
`
);
expect(transform(source)).toBe(expected);
});
});
54 changes: 53 additions & 1 deletion packages/babel-plugin-minify-constant-folding/src/index.js
Expand Up @@ -2,8 +2,45 @@

const evaluate = require("babel-helper-evaluate-path");

module.exports = ({ types: t, traverse }) => {
const { FALLBACK_HANDLER } = require("./replacements");

function getName(member) {
if (member.computed) {
switch (member.property.type) {
case "StringLiteral":
case "NumericLiteral":
return member.property.value;
case "TemplateLiteral":
return;
}
} else {
return member.property.name;
}
}

function swap(path, member, handlers, ...args) {
const key = getName(member);
if (key === undefined) return;
let handler = handlers[key];
if (typeof handler !== "function") {
if (typeof handlers[FALLBACK_HANDLER] === "function") {
handler = handlers[FALLBACK_HANDLER].bind(member.object, key);
} else {
return false;
}
}
const replacement = handler.apply(member.object, args);
if (replacement) {
path.replaceWith(replacement);
return true;
}
return false;
}

module.exports = babel => {
const replacements = require("./replacements.js")(babel);
const seen = Symbol("seen");
const { types: t, traverse } = babel;

return {
name: "minify-constant-folding",
Expand Down Expand Up @@ -124,6 +161,21 @@ module.exports = ({ types: t, traverse }) => {
node[seen] = true;
path.replaceWith(node);
}
},
CallExpression(path) {
const { node } = path;
const { callee: member } = node;
if (t.isMemberExpression(member)) {
const helpers = replacements[member.object.type];
if (!helpers || !helpers.calls) return;
swap(path, member, helpers.calls, ...node.arguments);
}
},
MemberExpression(path) {
const { node: member } = path;
const helpers = replacements[member.object.type];
if (!helpers || !helpers.members) return;
swap(path, member, helpers.members);
}
}
};
Expand Down

0 comments on commit 7e2b315

Please sign in to comment.