From cafd7f8e390c6765751f18f0031cc7cafaa8d90b Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Tue, 2 Jun 2015 16:18:08 +0100 Subject: [PATCH] add Infinity/NaN, string/number member expressions/calls and Math calls static evaluation --- .../transformation/transformers/index.js | 4 +- src/babel/traversal/path/evaluation.js | 64 ++++++++++++++++++- src/babel/traversal/scope/index.js | 55 ++++++++++++---- src/babel/types/alias-keys.json | 12 ++-- 4 files changed, 113 insertions(+), 22 deletions(-) diff --git a/src/babel/transformation/transformers/index.js b/src/babel/transformation/transformers/index.js index f96442ce7d..30f8317d93 100644 --- a/src/babel/transformation/transformers/index.js +++ b/src/babel/transformation/transformers/index.js @@ -7,14 +7,14 @@ export default { "minification.removeDebugger": require("./minification/remove-debugger"), "minification.removeConsole": require("./minification/remove-console"), "utility.inlineEnvironmentVariables": require("./utility/inline-environment-variables"), - "minification.inlineExpressions": require("./minification/inline-expressions"), - "minification.deadCodeElimination": require("./minification/dead-code-elimination"), + "minification.constantFolding": require("./minification/constant-folding"), _modules: require("./internal/modules"), "spec.functionName": require("./spec/function-name"), //- builtin-basic // this is where the bulk of the ES6 transformations take place, none of them require traversal state // so they can all be concatenated together for performance + "minification.deadCodeElimination": require("./minification/dead-code-elimination"), "es7.classProperties": require("./es7/class-properties"), "es7.trailingFunctionCommas": require("./es7/trailing-function-commas"), "es7.asyncFunctions": require("./es7/async-functions"), diff --git a/src/babel/traversal/path/evaluation.js b/src/babel/traversal/path/evaluation.js index fbdc2935c6..3c7d8f9215 100644 --- a/src/babel/traversal/path/evaluation.js +++ b/src/babel/traversal/path/evaluation.js @@ -1,5 +1,7 @@ /* eslint eqeqeq: 0 */ +const VALID_CALLEES = ["String", "Number", "Math"]; + /** * Walk the input `node` and statically evaluate if it's truthy. * @@ -74,8 +76,28 @@ export function evaluate(): { confident: boolean; value: any } { } } - if (path.isIdentifier({ name: "undefined" })) { - return undefined; + if (path.isIdentifier() && !path.scope.hasBinding(node.name, true)) { + if (node.name === "undefined") { + return undefined; + } else if (node.name === "Infinity") { + return Infinity; + } else if (node.name === "NaN") { + return NaN; + } + } + + // "foo".length + if (path.isMemberExpression() && !path.parentPath.isCallExpression({ callee: node })) { + let property = path.get("property"); + let object = path.get("object"); + + if (object.isLiteral() && property.isIdentifier()) { + let value = object.node.value; + let type = typeof value; + if (type === "number" || type === "string") { + return value[property.node.name]; + } + } } if ((path.isIdentifier() || path.isMemberExpression()) && path.isReferenced()) { @@ -134,6 +156,44 @@ export function evaluate(): { confident: boolean; value: any } { } } + if (path.isCallExpression()) { + var callee = path.get("callee"); + var context; + var func; + + // Number(1); + if (callee.isIdentifier() && !path.scope.getBinding(callee.node.name, true) && VALID_CALLEES.indexOf(callee.node.name) >= 0) { + func = global[node.callee.name]; + } + + if (callee.isMemberExpression()) { + let object = callee.get("object"); + var property = callee.get("property"); + + // Math.min(1, 2) + if (object.isIdentifier() && property.isIdentifier() && VALID_CALLEES.indexOf(object.node.name) >= 0) { + context = global[object.node.name]; + func = context[property.node.name]; + } + + // "abc".charCodeAt(4) + if (object.isLiteral() && property.isIdentifier()) { + let type = typeof object.node.value; + if (type === "string" || type === "number") { + context = object.node.value; + func = context[property.node.name]; + } + } + } + + if (func) { + var args = path.get("arguments").map(evaluate); + if (!confident) return; + + return func.apply(context, args); + } + } + confident = false; } } diff --git a/src/babel/traversal/scope/index.js b/src/babel/traversal/scope/index.js index 1425e524af..f1bec899c8 100644 --- a/src/babel/traversal/scope/index.js +++ b/src/babel/traversal/scope/index.js @@ -77,9 +77,19 @@ var collectorVisitor = { }, BlockScoped(node, parent, scope) { + if (this.isFunctionDeclaration()) return; if (scope.path === this) scope = scope.parent; scope.getBlockParent().registerDeclaration(this); - } + }, + + Block(node, parent, scope) { + var paths = this.get("body"); + for (var path of (paths: Array)) { + if (path.isFunctionDeclaration()) { + scope.getBlockParent().registerDeclaration(path); + } + } + }, }; var renameVisitor = { @@ -132,7 +142,13 @@ export default class Scope { } static globals = flatten([globals.builtin, globals.browser, globals.node].map(Object.keys)); - static contextVariables = ["this", "arguments", "super", "undefined"]; + + static contextVariables = [ + "arguments", + "undefined", + "Infinity", + "NaN" + ]; /** * Description @@ -539,14 +555,29 @@ export default class Scope { * Description */ - isPure(node, constantsOnly) { + isPure(node, constantsOnly?: boolean) { if (t.isIdentifier(node)) { - var bindingInfo = this.getBinding(node.name); - return !!bindingInfo && (!constantsOnly || (constantsOnly && bindingInfo.constant)); + var binding = this.getBinding(node.name); + if (!binding) return false; + if (constantsOnly) return binding.constant; + return true; } else if (t.isClass(node)) { - return !node.superClass; + return !node.superClass || this.isPure(node.superClass, constantsOnly); } else if (t.isBinary(node)) { return this.isPure(node.left, constantsOnly) && this.isPure(node.right, constantsOnly); + } else if (t.isArrayExpression(node)) { + for (var elem of (node.elements: Array)) { + if (!this.isPure(elem, constantsOnly)) return false; + } + return true; + } else if (t.isObjectExpression(node)) { + for (var prop of (node.properties: Array)) { + if (!this.isPure(prop, constantsOnly)) return false; + } + return true; + } else if (t.isProperty(node)) { + if (node.computed && !t.isPure(node.key, constantsOnly)) return false; + return t.isPure(node.value, constantsOnly); } else { return t.isPure(node); } @@ -821,13 +852,13 @@ export default class Scope { * Description */ - hasBinding(name: string) { + hasBinding(name: string, noGlobals?) { if (!name) return false; if (this.hasOwnBinding(name)) return true; - if (this.parentHasBinding(name)) return true; + if (this.parentHasBinding(name, noGlobals)) return true; if (this.hasUid(name)) return true; - if (includes(Scope.globals, name)) return true; - if (includes(Scope.contextVariables, name)) return true; + if (!noGlobals && includes(Scope.globals, name)) return true; + if (!noGlobals && includes(Scope.contextVariables, name)) return true; return false; } @@ -835,8 +866,8 @@ export default class Scope { * Description */ - parentHasBinding(name: string) { - return this.parent && this.parent.hasBinding(name); + parentHasBinding(name: string, noGlobals?) { + return this.parent && this.parent.hasBinding(name, noGlobals); } /** diff --git a/src/babel/types/alias-keys.json b/src/babel/types/alias-keys.json index 530327ddf7..901df4ae94 100644 --- a/src/babel/types/alias-keys.json +++ b/src/babel/types/alias-keys.json @@ -8,10 +8,10 @@ "LabeledStatement": ["Statement"], "VariableDeclaration": ["Statement", "Declaration"], - "BreakStatement": ["Statement", "Terminatorless"], - "ContinueStatement": ["Statement", "Terminatorless"], - "ReturnStatement": ["Statement", "Terminatorless"], - "ThrowStatement": ["Statement", "Terminatorless"], + "BreakStatement": ["Statement", "Terminatorless", "CompletionStatement"], + "ContinueStatement": ["Statement", "Terminatorless", "CompletionStatement"], + "ReturnStatement": ["Statement", "Terminatorless", "CompletionStatement"], + "ThrowStatement": ["Statement", "Terminatorless", "CompletionStatement"], "DoWhileStatement": ["Statement", "BlockParent", "Loop", "While", "Scopable"], "WhileStatement": ["Statement", "BlockParent", "Loop", "While", "Scopable"], @@ -32,8 +32,8 @@ "FunctionDeclaration": ["Scopable", "Function", "Func", "BlockParent", "FunctionParent", "Statement", "Pure", "Declaration"], "FunctionExpression": ["Scopable", "Function", "Func", "BlockParent", "FunctionParent", "Expression", "Pure"], - "BlockStatement": ["Scopable", "BlockParent", "Statement"], - "Program": ["Scopable", "BlockParent", "FunctionParent"], + "BlockStatement": ["Scopable", "BlockParent", "Block", "Statement"], + "Program": ["Scopable", "BlockParent", "Block", "FunctionParent"], "CatchClause": ["Scopable"], "LogicalExpression": ["Binary", "Expression"],