From 4e87809ff904f170009418344d11772e6dec0e1b Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Fri, 1 May 2015 09:31:28 +0100 Subject: [PATCH] make dead code elimination smarter and eliminate non-referenced "pure" nodes --- .../utility/dead-code-elimination.js | 32 +++++++++++++++++++ src/babel/traversal/binding.js | 11 +++++++ src/babel/traversal/path/index.js | 10 ++++++ src/babel/traversal/scope.js | 21 ++++++++++-- src/babel/types/alias-keys.json | 12 +++---- 5 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/babel/transformation/transformers/utility/dead-code-elimination.js b/src/babel/transformation/transformers/utility/dead-code-elimination.js index d1ab736f5c..d5c975f6ac 100644 --- a/src/babel/transformation/transformers/utility/dead-code-elimination.js +++ b/src/babel/transformation/transformers/utility/dead-code-elimination.js @@ -21,6 +21,38 @@ export var metadata = { optional: true }; +export function Identifier(node, parent, scope) { + if (!this.isReferenced()) return; + + var binding = scope.getBinding(node.name); + if (!binding || binding.references > 1 || !binding.constant) return; + + var replacement = binding.path.node; + if (t.isVariableDeclarator(replacement)) { + replacement = replacement.init; + } + t.toExpression(replacement); + + scope.removeBinding(node.name); + + binding.path.remove(); + return replacement; +} + +export function FunctionDeclaration(node, parent, scope) { + var bindingInfo = scope.getBinding(node.id.name); + if (bindingInfo && !bindingInfo.referenced) { + this.remove(); + } +} + +export { FunctionDeclaration as ClassDeclaration }; + +export function VariableDeclarator(node, parent, scope) { + if (!t.isIdentifier(node.id) || !scope.isPure(node.init)) return; + FunctionDeclaration.apply(this, arguments); +} + export function ConditionalExpression(node, parent, scope) { var evaluateTest = this.get("test").evaluateTruthy(); if (evaluateTest === true) { diff --git a/src/babel/traversal/binding.js b/src/babel/traversal/binding.js index ee2cdddf0a..86fe6c5ab6 100644 --- a/src/babel/traversal/binding.js +++ b/src/babel/traversal/binding.js @@ -3,6 +3,8 @@ import * as t from "../types"; export default class Binding { constructor({ identifier, scope, path, kind }) { this.identifier = identifier; + this.references = 0; + this.referenced = false; this.constant = true; this.scope = scope; this.path = path; @@ -58,6 +60,15 @@ export default class Binding { } } + /** + * Description + */ + + reference() { + this.referenced = true; + this.references++; + } + /** * Description */ diff --git a/src/babel/traversal/path/index.js b/src/babel/traversal/path/index.js index 3281a7eda7..197e1fbdf3 100644 --- a/src/babel/traversal/path/index.js +++ b/src/babel/traversal/path/index.js @@ -319,6 +319,12 @@ export default class TraversalPath { var parent = this.parent; if (!parentPath) return; + // we've just removed the last declarator of a variable declaration so there's no point in + // keeping it + if (parentPath.isVariableDeclaration() && parent.declarations.length === 0) { + return parentPath.remove(); + } + // we're the child of an expression statement so we should remove the parent if (parentPath.isExpressionStatement()) { return parentPath.remove(); @@ -650,6 +656,10 @@ export default class TraversalPath { return this.shouldStop; } + /** + * Description + */ + getSibling(key) { return TraversalPath.get(this.parentPath, null, this.parent, this.container, key, this.file); } diff --git a/src/babel/traversal/scope.js b/src/babel/traversal/scope.js index 3f3c8a54a9..1d8c15fd5d 100644 --- a/src/babel/traversal/scope.js +++ b/src/babel/traversal/scope.js @@ -40,8 +40,12 @@ var functionVariableVisitor = { var programReferenceVisitor = { enter(node, parent, scope, state) { - if (t.isReferencedIdentifier(node, parent) && !scope.hasBinding(node.name)) { - state.addGlobal(node); + if (t.isReferencedIdentifier(node, parent)) { + var bindingInfo = scope.getBinding(node.name); + if (bindingInfo) { + state.addGlobal(node); + bindingInfo.reference(); + } } else if (t.isLabeledStatement(node)) { state.addGlobal(node); } else if (t.isAssignmentExpression(node)) { @@ -482,6 +486,19 @@ export default class Scope { this.crawl(); } + /** + * Description + */ + + isPure(node) { + if (t.isIdentifier(node)) { + var bindingInfo = this.getBinding(node.name); + return bindingInfo.constant; + } else { + return t.isPure(node); + } + } + /** * Description */ diff --git a/src/babel/types/alias-keys.json b/src/babel/types/alias-keys.json index 3dcc642134..868edc2ce3 100644 --- a/src/babel/types/alias-keys.json +++ b/src/babel/types/alias-keys.json @@ -26,9 +26,9 @@ "ExportNamedDeclaration": ["Statement", "Declaration", "ModuleDeclaration", "ExportDeclaration"], "ImportDeclaration": ["Statement", "Declaration", "ModuleDeclaration"], - "ArrowFunctionExpression": ["Scopable", "Function", "Expression"], - "FunctionDeclaration": ["Scopable", "Function", "Statement", "Declaration"], - "FunctionExpression": ["Scopable", "Function", "Expression"], + "ArrowFunctionExpression": ["Scopable", "Function", "Expression", "Pure"], + "FunctionDeclaration": ["Scopable", "Function", "Statement", "Pure", "Declaration"], + "FunctionExpression": ["Scopable", "Function", "Expression", "Pure"], "BlockStatement": ["Scopable", "Statement"], "Program": ["Scopable"], @@ -41,8 +41,8 @@ "SpreadProperty": ["UnaryLike"], "SpreadElement": ["UnaryLike"], - "ClassDeclaration": ["Scopable", "Class", "Statement", "Declaration"], - "ClassExpression": ["Scopable", "Class", "Expression"], + "ClassDeclaration": ["Scopable", "Class", "Pure", "Statement", "Declaration"], + "ClassExpression": ["Scopable", "Class", "Pure", "Expression"], "ForOfStatement": ["Scopable", "Statement", "For", "Loop"], "ForInStatement": ["Scopable", "Statement", "For", "Loop"], @@ -62,7 +62,7 @@ "ConditionalExpression": ["Expression"], "DoExpression": ["Expression"], "Identifier": ["Expression"], - "Literal": ["Expression"], + "Literal": ["Expression", "Pure"], "MemberExpression": ["Expression"], "MetaProperty": ["Expression"], "NewExpression": ["Expression"],