Skip to content

Commit

Permalink
Implemented transform-void-to-nothing plugin.
Browse files Browse the repository at this point in the history
Removes the 'void 0' rval for let-/var-assignments.
  • Loading branch information
Shine Wang committed Oct 19, 2016
1 parent a7a890a commit 2e62acf
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/babel-plugin-remove-undefined-if-possible/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
src
__tests__
node_modules
*.log
59 changes: 59 additions & 0 deletions packages/babel-plugin-remove-undefined-if-possible/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# babel-plugin-remove-undefined-if-possible

For variable assignments, this removes rvals that evaluate to `undefined`
(`var`s in functions only).
For functions, this removes return arguments that evaluate to `undefined`.

## Example

**In**

```javascript
let a = void 0;
function foo() {
var b = undefined;
return undefined;
}
```

**Out**

```javascript
let a;
function foo() {
var b;
return;
}
```

## Installation

```sh
$ npm install babel-plugin-remove-undefined-if-possible
```

## Usage

### Via `.babelrc` (Recommended)

**.babelrc**

```json
{
"plugins": ["babel-plugin-remove-undefined-if-possible"]
}
```

### Via CLI

```sh
$ babel --plugins babel-plugin-remove-undefined-if-possible script.js
```

### Via Node API

```javascript
require("babel-core").transform("code", {
plugins: ["babel-plugin-remove-undefined-if-possible"]
});
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
jest.autoMockOff();

const babel = require("babel-core");
const plugin = require("../src/index");

function transform(code) {
return babel.transform(code, {
plugins: [plugin],
}).code;
}

describe("remove-undefined-if-possible-plugin", () => {
it("should remove multiple undefined assignments", () => {
const source = "let a = undefined, b = 3, c = undefined, d;";
const expected = "let a,\n b = 3,\n c,\n d;";
expect(transform(source)).toBe(expected);
});

it("should remove let-assignments to undefined", () => {
const source = "let a = undefined;";
const expected = "let a;";
expect(transform(source)).toBe(expected);
});

it("should remove let-assignments to void 0", () => {
const source = "let a = void 0;";
const expected = "let a;";
expect(transform(source)).toBe(expected);
});

it("should remove undefined return value", () => {
const source = `
function foo() {
const a = undefined;
return undefined;
}`;
const expected = `
function foo() {
const a = undefined;
return;
}`;
expect(transform(source)).toBe(expected);
});

it("should remove var declarations in functions", () => {
const source = `
function foo() {
var a = undefined;
}`;
const expected = `
function foo() {
var a;
}`;
expect(transform(source)).toBe(expected);
});

it("should not remove const-assignments to undefined", () => {
const source = "const a = undefined;";
expect(transform(source)).toBe(source);
});

it("should not remove var-assignments in loops", () => {
const source = `
for (var a = undefined;;) {
var b = undefined;
}`;
expect(transform(source)).toBe(source);
});

it("should not remove var-assignments if referenced before", () => {
const source = `
function foo() {
a = 3;
var a = undefined;
}`;
expect(transform(source)).toBe(source);
});
});
96 changes: 96 additions & 0 deletions packages/babel-plugin-remove-undefined-if-possible/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"use strict";

function isInLocalLoop(path, t) {
if (path === null) {
return false;
} else if (t.isLoop(path)) {
return true;
} else if (t.isFunction(path)) {
return false;
} else {
return isInLocalLoop(path.parentPath, t);
}
}

function removeRvalIfUndefined(declaratorPath) {
const rval = declaratorPath.get("init")
.evaluate();
if (rval.confident === true && rval.value === undefined) {
declaratorPath.node.init = null;
}
}

function isAnyLvalReferencedBefore(declaratorPath, seenIds, t) {
const id = declaratorPath.get("id");
if (t.isIdentifier(id)) {
return seenIds.has(id.node.name);
}
let hasReference = false;
id.traverse({
Identifier(path) {
if (seenIds.has(path.node.name)) {
hasReference = true;
}
}
});
return hasReference;
}

function removeUndefinedAssignments(functionPath, t) {
const seenIds = new Set();
functionPath.traverse({
Function(path) {
path.skip();
},
Identifier(path) {
// potential optimization: only store ids that are lvals of assignments.
seenIds.add(path.node.name);
},
VariableDeclaration(path) {
switch (path.node.kind) {
case "const":
break;
case "let":
path.node.declarations.forEach((declarator, index) => {
const declaratorPath = path.get("declarations")[index];
removeRvalIfUndefined(declaratorPath);
});
break;
case "var":
if (!t.isFunction(functionPath)) {
break;
}
if (isInLocalLoop(path.parentPath, t)) {
break;
}
path.node.declarations.forEach((declarator, index) => {
const declaratorPath = path.get("declarations")[index];
if (!isAnyLvalReferencedBefore(declaratorPath, seenIds, t)) {
removeRvalIfUndefined(declaratorPath);
}
});
break;
}
},
});
}

module.exports = function({ types: t }) {
return {
name: "remove-undefined-if-possible",
visitor: {
FunctionParent(path) {
removeUndefinedAssignments(path, t);
},
ReturnStatement(path) {
if (path.node.argument !== null) {
const rval = path.get("argument")
.evaluate();
if (rval.confident === true && rval.value === undefined) {
path.node.argument = null;
}
}
},
},
};
};

0 comments on commit 2e62acf

Please sign in to comment.