From c906bd3edc391cd2b0eeffa260e3611a772a789c Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Sat, 14 Mar 2015 01:00:02 +1100 Subject: [PATCH] start migration of core from nodes to paths --- src/babel/transformation/file.js | 4 +- .../helpers/build-react-transformer.js | 2 +- .../transformation/helpers/replace-supers.js | 2 +- src/babel/transformation/modules/_default.js | 14 +- src/babel/transformation/transformer-pass.js | 3 +- .../transformers/es6/block-scoping-tdz.js | 2 +- .../transformers/es6/block-scoping.js | 4 +- .../transformers/es6/classes.js | 35 ++-- .../transformers/es6/constants.js | 2 +- .../transformation/transformers/es6/for-of.js | 10 +- .../transformers/es6/modules.js | 2 +- .../transformers/es6/parameters.default.js | 27 ++-- .../transformers/es6/parameters.rest.js | 4 +- .../transformation/transformers/es6/spread.js | 4 +- .../transformers/es6/symbols.js | 2 +- .../transformers/es6/tail-call.js | 19 +-- .../transformation/transformers/index.js | 1 + .../transformers/internal/alias-functions.js | 14 +- .../transformers/internal/modules.js | 2 +- .../transformers/optimisation/flow.for-of.js | 11 ++ .../transformation/transformers/other/flow.js | 2 +- .../transformers/other/runtime.js | 8 +- .../playground/object-getter-memoization.js | 5 +- .../utility/dead-code-elimination.js | 5 +- .../utility/inline-expressions.js | 2 +- .../transformers/utility/remove-console.js | 4 +- .../transformers/validation/react.js | 2 +- src/babel/traversal/binding.js | 24 --- src/babel/traversal/index.js | 8 +- .../path/evaluation.js} | 62 +++---- .../traversal/{path.js => path/index.js} | 151 ++++++++++++++---- src/babel/traversal/scope.js | 26 +-- src/babel/types/index.js | 1 - src/babel/types/visitor-keys.json | 2 +- .../array-expression-single/actual.js | 2 +- .../array-expression-single/expected.js | 4 +- .../es7-comprehensions/array-this/actual.js | 2 +- .../es7-comprehensions/array-this/expected.js | 4 +- 38 files changed, 273 insertions(+), 205 deletions(-) create mode 100644 src/babel/transformation/transformers/optimisation/flow.for-of.js rename src/babel/{types/evaluators.js => traversal/path/evaluation.js} (65%) rename src/babel/traversal/{path.js => path/index.js} (68%) diff --git a/src/babel/transformation/file.js b/src/babel/transformation/file.js index 3b8b1abf43..0cd16c2aa5 100644 --- a/src/babel/transformation/file.js +++ b/src/babel/transformation/file.js @@ -464,7 +464,7 @@ export default class File { this.scope = this.path.scope; this.ast = ast; - traverse(ast, { + this.path.traverse({ enter(node, parent, scope) { if (this.isScope()) { for (var key in scope.bindings) { @@ -472,7 +472,7 @@ export default class File { } } } - }, this.scope); + }); } transform(ast) { diff --git a/src/babel/transformation/helpers/build-react-transformer.js b/src/babel/transformation/helpers/build-react-transformer.js index 02b9ba1ffe..ccef9d819b 100644 --- a/src/babel/transformation/helpers/build-react-transformer.js +++ b/src/babel/transformation/helpers/build-react-transformer.js @@ -27,7 +27,7 @@ export default function (exports, opts) { }; exports.JSXNamespacedName = function (node, parent, scope, file) { - throw file.errorWithNode(node, messages.get("JSXNamespacedTags")); + throw this.errorWithNode(messages.get("JSXNamespacedTags")); }; exports.JSXMemberExpression = { diff --git a/src/babel/transformation/helpers/replace-supers.js b/src/babel/transformation/helpers/replace-supers.js index e60cbabcb4..0796a21300 100644 --- a/src/babel/transformation/helpers/replace-supers.js +++ b/src/babel/transformation/helpers/replace-supers.js @@ -219,7 +219,7 @@ export default class ReplaceSupers { var thisReference; if (isIllegalBareSuper(node, parent)) { - throw this.file.errorWithNode(node, messages.get("classesIllegalBareSuper")); + throw this.errorWithNode(messages.get("classesIllegalBareSuper")); } if (t.isCallExpression(node)) { diff --git a/src/babel/transformation/modules/_default.js b/src/babel/transformation/modules/_default.js index a60d3b7507..2158b8525f 100644 --- a/src/babel/transformation/modules/_default.js +++ b/src/babel/transformation/modules/_default.js @@ -45,7 +45,7 @@ var importsVisitor = { ImportDeclaration: { enter(node, parent, scope, formatter) { formatter.hasLocalImports = true; - extend(formatter.localImports, t.getBindingIdentifiers(node)); + extend(formatter.localImports, this.getBindingIdentifiers()); formatter.bumpImportOccurences(node); } } @@ -54,11 +54,11 @@ var importsVisitor = { var exportsVisitor = { ExportDeclaration: { enter(node, parent, scope, formatter) { - var declar = node.declaration; + var declar = this.get("declaration"); formatter.hasLocalImports = true; - if (declar && t.isStatement(declar)) { - extend(formatter.localExports, t.getBindingIdentifiers(declar)); + if (declar.isStatement()) { + extend(formatter.localExports, declar.getBindingIdentifiers()); } if (!node.default) { @@ -105,16 +105,16 @@ export default class DefaultFormatter { } getLocalExports() { - this.file.scope.traverse(this.file.ast, exportsVisitor, this); + this.file.path.traverse(exportsVisitor, this); } getLocalImports() { - this.file.scope.traverse(this.file.ast, importsVisitor, this); + this.file.path.traverse(importsVisitor, this); } remapAssignments() { if (this.hasLocalImports) { - this.file.scope.traverse(this.file.ast, remapVisitor, this); + this.file.path.traverse(remapVisitor, this); } } diff --git a/src/babel/transformation/transformer-pass.js b/src/babel/transformation/transformer-pass.js index 3beac40bc8..dd8fab0431 100644 --- a/src/babel/transformation/transformer-pass.js +++ b/src/babel/transformation/transformer-pass.js @@ -1,4 +1,5 @@ import includes from "lodash/collection/includes"; +import traverse from "../traversal"; /** * This class is responsible for traversing over the provided `File`s @@ -59,7 +60,7 @@ export default class TransformerPass { file.log.debug(`Running transformer ${this.transformer.key}`); - file.scope.traverse(file.ast, this.handlers, file); + traverse(file.ast, this.handlers, file.scope, file); this.ran = true; } diff --git a/src/babel/transformation/transformers/es6/block-scoping-tdz.js b/src/babel/transformation/transformers/es6/block-scoping-tdz.js index d18333a192..0eee6bc324 100644 --- a/src/babel/transformation/transformers/es6/block-scoping-tdz.js +++ b/src/babel/transformation/transformers/es6/block-scoping-tdz.js @@ -32,7 +32,7 @@ export function BlockStatement(node, parent, scope, file) { var letRefs = node._letReferences; if (!letRefs) return; - scope.traverse(node, visitor, { + this.traverse(visitor, { letRefs: letRefs, file: file }); diff --git a/src/babel/transformation/transformers/es6/block-scoping.js b/src/babel/transformation/transformers/es6/block-scoping.js index 79eb0380c7..ee7e87402f 100644 --- a/src/babel/transformation/transformers/es6/block-scoping.js +++ b/src/babel/transformation/transformers/es6/block-scoping.js @@ -111,7 +111,7 @@ function traverseReplace(node, parent, scope, remaps) { var letReferenceBlockVisitor = { enter(node, parent, scope, state) { if (this.isFunction()) { - scope.traverse(node, letReferenceFunctionVisitor, state); + this.traverse(letReferenceFunctionVisitor, state); return this.skip(); } } @@ -173,7 +173,7 @@ var loopVisitor = { if (this.isLoop()) { state.ignoreLabeless = true; - scope.traverse(node, loopVisitor, state); + this.traverse(loopVisitor, state); state.ignoreLabeless = false; } diff --git a/src/babel/transformation/transformers/es6/classes.js b/src/babel/transformation/transformers/es6/classes.js index 862e498d6f..bf66ef32f1 100644 --- a/src/babel/transformation/transformers/es6/classes.js +++ b/src/babel/transformation/transformers/es6/classes.js @@ -15,7 +15,7 @@ export function ClassDeclaration(node, parent, scope, file) { } export function ClassExpression(node, parent, scope, file) { - return new ClassTransformer(node, parent, scope, file).run(); + return new ClassTransformer(this, file).run(); } var verifyConstructorVisitor = traverse.explode({ @@ -37,7 +37,7 @@ var verifyConstructorVisitor = traverse.explode({ state.hasBareSuper = true; if (!state.hasSuper) { - throw state.file.errorWithNode(node, "super call is only allowed in derived constructor"); + throw this.errorWithNode("super call is only allowed in derived constructor"); } } } @@ -46,7 +46,7 @@ var verifyConstructorVisitor = traverse.explode({ ThisExpression: { enter(node, parent, scope, state) { if (state.hasSuper && !state.hasBareSuper) { - throw state.file.errorWithNode(node, "'this' is not allowed before super()"); + throw this.errorWithNode("'this' is not allowed before super()"); } } } @@ -58,10 +58,11 @@ class ClassTransformer { * Description */ - constructor(node: Object, parent: Object, scope: Scope, file: File) { - this.parent = parent; - this.scope = scope; - this.node = node; + constructor(path: TraversalPath, file: File) { + this.parent = path.parent; + this.scope = path.scope; + this.node = path.node; + this.path = path; this.file = file; this.hasInstanceMutators = false; @@ -71,11 +72,11 @@ class ClassTransformer { this.staticMutatorMap = {}; this.hasConstructor = false; - this.className = node.id; - this.classRef = node.id || scope.generateUidIdentifier("class"); + this.className = this.node.id; + this.classRef = this.node.id || this.scope.generateUidIdentifier("class"); - this.superName = node.superClass || t.identifier("Function"); - this.hasSuper = !!node.superClass; + this.superName = this.node.superClass || t.identifier("Function"); + this.hasSuper = !!this.node.superClass; this.isLoose = file.isLoose("es6.classes"); } @@ -174,11 +175,13 @@ class ClassTransformer { var classBody = this.node.body.body; var body = this.body; + var classBodyPaths = this.path.get("body").get("body"); + for (var i = 0; i < classBody.length; i++) { var node = classBody[i]; if (t.isMethodDefinition(node)) { var isConstructor = (!node.computed && t.isIdentifier(node.key, { name: "constructor" })) || t.isLiteral(node.key, { value: "constructor" }); - if (isConstructor) this.verifyConstructor(node); + if (isConstructor) this.verifyConstructor(classBodyPaths[i]); var replaceSupers = new ReplaceSupers({ methodNode: node, @@ -206,7 +209,7 @@ class ClassTransformer { } // we have no constructor, we have a super, and the super doesn't appear to be falsy - if (!this.hasConstructor && this.hasSuper && t.evaluateTruthy(superName, this.scope) !== false) { + if (!this.hasConstructor && this.hasSuper) { // todo: t.evaluateTruthy(superName, this.scope) !== false var helperName = "class-super-constructor-call"; if (this.isLoose) helperName += "-loose"; constructor.body.body.push(util.template(helperName, { @@ -249,17 +252,17 @@ class ClassTransformer { * Description */ - verifyConstructor(node: Object) { + verifyConstructor(path: TraversalPath) { var state = { hasBareSuper: false, hasSuper: this.hasSuper, file: this.file }; - traverse(node, verifyConstructorVisitor, this.scope, state); + path.traverse(verifyConstructorVisitor, state); if (!state.hasBareSuper && this.hasSuper) { - throw this.file.errorWithNode(node, "Derived constructor must call super()"); + throw path.errorWithNode("Derived constructor must call super()"); } } diff --git a/src/babel/transformation/transformers/es6/constants.js b/src/babel/transformation/transformers/es6/constants.js index fc29e8c916..4a4e5c95d2 100644 --- a/src/babel/transformation/transformers/es6/constants.js +++ b/src/babel/transformation/transformers/es6/constants.js @@ -37,7 +37,7 @@ var visitor = { }; export function Scopable(node, parent, scope, file) { - scope.traverse(node, visitor, { + this.traverse(visitor, { constants: scope.getAllBindingsOfKind("const"), file: file }); diff --git a/src/babel/transformation/transformers/es6/for-of.js b/src/babel/transformation/transformers/es6/for-of.js index addd609af5..42c57d5859 100644 --- a/src/babel/transformation/transformers/es6/for-of.js +++ b/src/babel/transformation/transformers/es6/for-of.js @@ -5,12 +5,10 @@ import * as t from "../../../types"; export var check = t.isForOfStatement; export function ForOfStatement(node, parent, scope, file) { - if (this.get("right").isTypeGeneric("Array")) { - return array(node, scope, file); + if (this.get("right").isArrayExpression()) { + return _ForOfStatementArray.call(this, node, scope, file); } - // - var callback = spec; if (file.isLoose("es6.forOf")) callback = loose; @@ -42,7 +40,7 @@ export function ForOfStatement(node, parent, scope, file) { } } -var array = function (node, scope, file) { +export function _ForOfStatementArray(node, scope, file) { var nodes = []; var right = node.right; @@ -78,7 +76,7 @@ var array = function (node, scope, file) { nodes.push(loop); return nodes; -}; +} var loose = function (node, parent, scope, file) { var left = node.left; diff --git a/src/babel/transformation/transformers/es6/modules.js b/src/babel/transformation/transformers/es6/modules.js index 0eab992dfe..cb583622d6 100644 --- a/src/babel/transformation/transformers/es6/modules.js +++ b/src/babel/transformation/transformers/es6/modules.js @@ -26,7 +26,7 @@ export function ImportDeclaration(node, parent, scope, file) { export function ExportDeclaration(node, parent, scope, file) { // flow type - if (t.isTypeAlias(node.declaration)) return; + if (this.get("declaration").isTypeAlias()) return; var nodes = []; var i; diff --git a/src/babel/transformation/transformers/es6/parameters.default.js b/src/babel/transformation/transformers/es6/parameters.default.js index 9e525c87d3..82dd26e20e 100644 --- a/src/babel/transformation/transformers/es6/parameters.default.js +++ b/src/babel/transformation/transformers/es6/parameters.default.js @@ -49,41 +49,42 @@ exports.Function = function (node, parent, scope, file) { body.push(defNode); }; - for (var i = 0; i < node.params.length; i++) { - var param = node.params[i]; + var params = this.get("params"); + for (var i = 0; i < params.length; i++) { + var param = params[i]; - if (!t.isAssignmentPattern(param)) { - if (!t.isRestElement(param)) { + if (!param.isAssignmentPattern()) { + if (!param.isRestElement()) { lastNonDefaultParam = i + 1; } - if (!t.isIdentifier(param)) { - scope.traverse(param, iifeVisitor, state); + if (!param.isIdentifier()) { + param.traverse(iifeVisitor, state); } - if (file.transformers["es6.blockScopingTDZ"].canRun() && t.isIdentifier(param)) { - pushDefNode(param, t.identifier("undefined"), i); + if (file.transformers["es6.blockScopingTDZ"].canRun() && param.isIdentifier()) { + pushDefNode(param.node, t.identifier("undefined"), i); } continue; } - var left = param.left; - var right = param.right; + var left = param.get("left"); + var right = param.get("right"); var placeholder = scope.generateUidIdentifier("x"); placeholder._isDefaultPlaceholder = true; node.params[i] = placeholder; if (!state.iife) { - if (t.isIdentifier(right) && scope.hasOwnBinding(right.name)) { + if (right.isIdentifier() && scope.hasOwnBinding(right.node.name)) { state.iife = true; } else { - scope.traverse(right, iifeVisitor, state); + right.traverse(iifeVisitor, state); } } - pushDefNode(left, right, i); + pushDefNode(left.node, right.node, i); } // we need to cut off all trailing default parameters diff --git a/src/babel/transformation/transformers/es6/parameters.rest.js b/src/babel/transformation/transformers/es6/parameters.rest.js index e79af4418d..d0f967a8f7 100644 --- a/src/babel/transformation/transformers/es6/parameters.rest.js +++ b/src/babel/transformation/transformers/es6/parameters.rest.js @@ -15,7 +15,7 @@ var memberExpressionOptimisationVisitor = { // to the wrong function if (this.isFunctionDeclaration() || this.isFunctionExpression()) { state.noOptimise = true; - scope.traverse(node, memberExpressionOptimisationVisitor, state); + this.traverse(memberExpressionOptimisationVisitor, state); state.noOptimise = false; return this.skip(); } @@ -88,7 +88,7 @@ exports.Function = function (node, parent, scope, file) { name: rest.name }; - scope.traverse(node, memberExpressionOptimisationVisitor, state); + this.traverse(memberExpressionOptimisationVisitor, state); // we only have shorthands and there's no other references if (state.canOptimise && state.candidates.length) { diff --git a/src/babel/transformation/transformers/es6/spread.js b/src/babel/transformation/transformers/es6/spread.js index 4fe2b7d238..693553ded9 100644 --- a/src/babel/transformation/transformers/es6/spread.js +++ b/src/babel/transformation/transformers/es6/spread.js @@ -81,7 +81,7 @@ export function CallExpression(node, parent, scope) { var callee = node.callee; - if (t.isMemberExpression(callee)) { + if (this.get("callee").isMemberExpression()) { var temp = scope.generateTempBasedOnNode(callee.object); if (temp) { callee.object = t.assignmentExpression("=", temp, callee.object); @@ -101,7 +101,7 @@ export function NewExpression(node, parent, scope, file) { var args = node.arguments; if (!hasSpread(args)) return; - var nativeType = t.isIdentifier(node.callee) && includes(t.NATIVE_TYPE_NAMES, node.callee.name); + var nativeType = this.get("callee").isIdentifier() && includes(t.NATIVE_TYPE_NAMES, node.callee.name); var nodes = build(args, scope); diff --git a/src/babel/transformation/transformers/es6/symbols.js b/src/babel/transformation/transformers/es6/symbols.js index fd3fe51b7e..03f7ab491f 100644 --- a/src/babel/transformation/transformers/es6/symbols.js +++ b/src/babel/transformation/transformers/es6/symbols.js @@ -7,7 +7,7 @@ export function UnaryExpression(node, parent, scope, file) { if (node.operator === "typeof") { var call = t.callExpression(file.addHelper("typeof"), [node.argument]); - if (t.isIdentifier(node.argument)) { + if (this.get("argument").isIdentifier()) { var undefLiteral = t.literal("undefined"); return t.conditionalExpression( t.binaryExpression("===", t.unaryExpression("typeof", node.argument), undefLiteral), diff --git a/src/babel/transformation/transformers/es6/tail-call.js b/src/babel/transformation/transformers/es6/tail-call.js index 563ad8bd00..122f456f2a 100644 --- a/src/babel/transformation/transformers/es6/tail-call.js +++ b/src/babel/transformation/transformers/es6/tail-call.js @@ -6,7 +6,7 @@ import map from "lodash/collection/map"; import * as t from "../../../types"; exports.Function = function (node, parent, scope, file) { - var tailCall = new TailCallTransformer(node, scope, file); + var tailCall = new TailCallTransformer(this, scope, file); tailCall.run(); }; @@ -18,11 +18,11 @@ function returnBlock(expr) { var firstPass = { enter(node, parent, scope, state) { if (this.isIfStatement()) { - if (t.isReturnStatement(node.alternate)) { + if (this.get("alternate").isReturnStatement()) { t.ensureBlock(node, "alternate"); } - if (t.isReturnStatement(node.consequent)) { + if (this.get("consequent").isReturnStatement()) { t.ensureBlock(node, "consequent"); } } else if (this.isReturnStatement()) { @@ -85,17 +85,18 @@ var thirdPass = { }; class TailCallTransformer { - constructor(node, scope, file) { + constructor(path, scope, file) { this.hasTailRecursion = false; this.needsArguments = false; this.setsArguments = false; this.needsThis = false; - this.ownerId = node.id; + this.ownerId = path.node.id; this.vars = []; this.scope = scope; + this.path = path; this.file = file; - this.node = node; + this.node = path.node; } getArgumentsId() { @@ -156,7 +157,7 @@ class TailCallTransformer { if (!ownerId) return; // traverse the function and look for tail recursion - scope.traverse(node, firstPass, this); + this.path.traverse(firstPass, this); if (!this.hasTailRecursion) return; @@ -167,10 +168,10 @@ class TailCallTransformer { // - scope.traverse(node, secondPass, this); + this.path.traverse(secondPass, this); if (!this.needsThis || !this.needsArguments) { - scope.traverse(node, thirdPass, this); + this.path.traverse(thirdPass, this); } var body = t.ensureBlock(node).body; diff --git a/src/babel/transformation/transformers/index.js b/src/babel/transformation/transformers/index.js index c1098afeb7..9b008d23c6 100644 --- a/src/babel/transformation/transformers/index.js +++ b/src/babel/transformation/transformers/index.js @@ -44,6 +44,7 @@ export default { // needs to be before `_aliasFunction` due to define property closure "es6.properties.computed": require("./es6/properties.computed"), + "optimisation.es6.forOf": require("./optimisation/flow.for-of"), "es6.forOf": require("./es6/for-of"), "es6.regex.sticky": require("./es6/regex.sticky"), diff --git a/src/babel/transformation/transformers/internal/alias-functions.js b/src/babel/transformation/transformers/internal/alias-functions.js index b76e19ae50..649edd0c38 100644 --- a/src/babel/transformation/transformers/internal/alias-functions.js +++ b/src/babel/transformation/transformers/internal/alias-functions.js @@ -34,13 +34,13 @@ var functionVisitor = { } // traverse all child nodes of this function and find `arguments` and `this` - scope.traverse(node, functionChildrenVisitor, state); + this.traverse(functionChildrenVisitor, state); return this.skip(); } }; -var go = function (getBody, node, scope) { +function aliasFunction(getBody, path, scope) { var argumentsId; var thisId; @@ -56,7 +56,7 @@ var go = function (getBody, node, scope) { // traverse the function and find all alias functions so we can alias // `arguments` and `this` if necessary - scope.traverse(node, functionVisitor, state); + path.traverse(functionVisitor, state); var body; @@ -77,16 +77,16 @@ var go = function (getBody, node, scope) { }; export function Program(node, parent, scope) { - go(function () { + aliasFunction(function () { return node.body; - }, node, scope); + }, this, scope); }; export function FunctionDeclaration(node, parent, scope) { - go(function () { + aliasFunction(function () { t.ensureBlock(node); return node.body.body; - }, node, scope); + }, this, scope); } export { FunctionDeclaration as FunctionExpression }; diff --git a/src/babel/transformation/transformers/internal/modules.js b/src/babel/transformation/transformers/internal/modules.js index 7ef5bc5302..69de42f4d5 100644 --- a/src/babel/transformation/transformers/internal/modules.js +++ b/src/babel/transformation/transformers/internal/modules.js @@ -59,7 +59,7 @@ export function ExportDeclaration(node, parent, scope) { } else if (t.isVariableDeclaration(declar)) { // export var foo = "bar"; var specifiers = []; - var bindings = t.getBindingIdentifiers(declar); + var bindings = this.get("declaration").getBindingIdentifiers(); for (var key in bindings) { var id = bindings[key]; specifiers.push(t.exportSpecifier(id, id)); diff --git a/src/babel/transformation/transformers/optimisation/flow.for-of.js b/src/babel/transformation/transformers/optimisation/flow.for-of.js new file mode 100644 index 0000000000..5e0237d700 --- /dev/null +++ b/src/babel/transformation/transformers/optimisation/flow.for-of.js @@ -0,0 +1,11 @@ +import { _ForOfStatementArray } from "../es6/for-of"; +import * as t from "../../../types"; + +export var check = t.isForOfStatement; +export var optional = true; + +export function ForOfStatement(node, parent, scope, file) { + if (this.get("right").isTypeGeneric("Array")) { + return _ForOfStatementArray.call(this, node, scope, file); + } +} diff --git a/src/babel/transformation/transformers/other/flow.js b/src/babel/transformation/transformers/other/flow.js index 4f8126f9f9..bb6bf4c393 100644 --- a/src/babel/transformation/transformers/other/flow.js +++ b/src/babel/transformation/transformers/other/flow.js @@ -29,5 +29,5 @@ export function ImportDeclaration(node) { } export function ExportDeclaration(node) { - if (t.isTypeAlias(node.declaration)) this.remove(); + if (this.get("declaration").isTypeAlias()) this.remove(); } diff --git a/src/babel/transformation/transformers/other/runtime.js b/src/babel/transformation/transformers/other/runtime.js index a137603e8c..ec2de99541 100644 --- a/src/babel/transformation/transformers/other/runtime.js +++ b/src/babel/transformation/transformers/other/runtime.js @@ -4,7 +4,7 @@ import core from "core-js/library"; import has from "lodash/object/has"; import * as t from "../../../types"; -var isSymboliterator = t.buildMatchMemberExpression("Symbol.iterator"); +var isSymbolIterator = t.buildMatchMemberExpression("Symbol.iterator"); var coreHas = function (node) { return node.name !== "_" && has(core, node.name); @@ -47,7 +47,7 @@ var astVisitor = { if (!callee.computed) return false; prop = callee.property; - if (!isSymboliterator(prop)) return false; + if (!isSymbolIterator(prop)) return false; return util.template("corejs-iterator", { CORE_ID: file.get("coreIdentifier"), @@ -59,7 +59,7 @@ var astVisitor = { if (node.operator !== "in") return; var left = node.left; - if (!isSymboliterator(left)) return; + if (!isSymbolIterator(left)) return; return util.template("corejs-is-iterator", { CORE_ID: file.get("coreIdentifier"), @@ -76,7 +76,7 @@ export function manipulateOptions(opts) { } export function Program(node, parent, scope, file) { - scope.traverse(node, astVisitor, file); + this.traverse(astVisitor, file); } export function pre(file) { diff --git a/src/babel/transformation/transformers/playground/object-getter-memoization.js b/src/babel/transformation/transformers/playground/object-getter-memoization.js index af5ffe8c4c..5cfaf9c61a 100644 --- a/src/babel/transformation/transformers/playground/object-getter-memoization.js +++ b/src/babel/transformation/transformers/playground/object-getter-memoization.js @@ -20,8 +20,7 @@ export function MethodDefinition(node, parent, scope, file) { if (node.kind !== "memo") return; node.kind = "get"; - var value = node.value; - t.ensureBlock(value); + t.ensureBlock(node.value); var key = node.key; @@ -34,7 +33,7 @@ export function MethodDefinition(node, parent, scope, file) { file: file }; - scope.traverse(value, visitor, state); + this.get("value").traverse(visitor, state); return node; } diff --git a/src/babel/transformation/transformers/utility/dead-code-elimination.js b/src/babel/transformation/transformers/utility/dead-code-elimination.js index 3dd7252ae3..bda19f668d 100644 --- a/src/babel/transformation/transformers/utility/dead-code-elimination.js +++ b/src/babel/transformation/transformers/utility/dead-code-elimination.js @@ -20,7 +20,7 @@ function toStatements(node) { export var optional = true; export function ConditionalExpression(node, parent, scope) { - var evaluateTest = t.evaluateTruthy(node.test, scope); + var evaluateTest = this.get("test").evaluateTruthy(); if (evaluateTest === true) { return node.consequent; } else if (evaluateTest === false) { @@ -32,9 +32,8 @@ export var IfStatement = { exit(node, parent, scope) { var consequent = node.consequent; var alternate = node.alternate; - var test = node.test; - var evaluateTest = t.evaluateTruthy(test, scope); + var evaluateTest = this.get("test").evaluateTruthy(); // we can check if a test will be truthy 100% and if so then we can inline // the consequent and completely ignore the alternate diff --git a/src/babel/transformation/transformers/utility/inline-expressions.js b/src/babel/transformation/transformers/utility/inline-expressions.js index 9b77422502..f1be106014 100644 --- a/src/babel/transformation/transformers/utility/inline-expressions.js +++ b/src/babel/transformation/transformers/utility/inline-expressions.js @@ -3,7 +3,7 @@ import * as t from "../../../types"; export var optional = true; export function Expression(node, parent, scope) { - var res = t.evaluate(node, scope); + var res = this.evaluate(); if (res.confident) return t.valueToNode(res.value); } diff --git a/src/babel/transformation/transformers/utility/remove-console.js b/src/babel/transformation/transformers/utility/remove-console.js index 791b17c9f4..9fe1f491a9 100644 --- a/src/babel/transformation/transformers/utility/remove-console.js +++ b/src/babel/transformation/transformers/utility/remove-console.js @@ -1,11 +1,9 @@ import * as t from "../../../types"; -var isConsole = t.buildMatchMemberExpression("console", true); - export var optional = true; export function CallExpression(node, parent) { - if (isConsole(node.callee)) { + if (this.get("callee").matchesPattern("console", true)) { if (t.isExpressionStatement(parent)) { this.parentPath.remove(); } else { diff --git a/src/babel/transformation/transformers/validation/react.js b/src/babel/transformation/transformers/validation/react.js index f8cbc58ffb..536aff1768 100644 --- a/src/babel/transformation/transformers/validation/react.js +++ b/src/babel/transformation/transformers/validation/react.js @@ -14,7 +14,7 @@ function check(source, file) { } export function CallExpression(node, parent, scope, file) { - if (t.isIdentifier(node.callee, { name: "require" }) && node.arguments.length === 1) { + if (this.get("callee").isIdentifier({ name: "require" }) && node.arguments.length === 1) { check(node.arguments[0], file); } } diff --git a/src/babel/traversal/binding.js b/src/babel/traversal/binding.js index 95f45dace7..fe4d29a096 100644 --- a/src/babel/traversal/binding.js +++ b/src/babel/traversal/binding.js @@ -58,30 +58,6 @@ export default class Binding { } } - /** - * Description - */ - - getValueIfImmutable() { - // can't guarantee this value is the same - if (this.reassigned) return; - - var node = this.path.node; - if (t.isVariableDeclarator(node)) { - if (t.isIdentifier(node.id)) { - node = node.init; - } else { - // otherwise it's probably a destructuring like: - // var { foo } = "foo"; - return; - } - } - - if (t.isImmutable(node)) { - return node; - } - } - /** * Description */ diff --git a/src/babel/traversal/index.js b/src/babel/traversal/index.js index b2befe1823..c36162f7df 100644 --- a/src/babel/traversal/index.js +++ b/src/babel/traversal/index.js @@ -1,10 +1,8 @@ -module.exports = traverse; - import TraversalContext from "./context"; import includes from "lodash/collection/includes"; import * as t from "../types"; -function traverse(parent, opts, scope, state) { +export default function traverse(parent, opts, scope, state, parentPath) { if (!parent) return; if (!opts.noScope && !scope) { @@ -20,10 +18,10 @@ function traverse(parent, opts, scope, state) { // array of nodes if (Array.isArray(parent)) { for (var i = 0; i < parent.length; i++) { - traverse.node(parent[i], opts, scope, state); + traverse.node(parent[i], opts, scope, state, parentPath); } } else { - traverse.node(parent, opts, scope, state); + traverse.node(parent, opts, scope, state, parentPath); } } diff --git a/src/babel/types/evaluators.js b/src/babel/traversal/path/evaluation.js similarity index 65% rename from src/babel/types/evaluators.js rename to src/babel/traversal/path/evaluation.js index 163de2581e..2070ab5d2b 100644 --- a/src/babel/types/evaluators.js +++ b/src/babel/traversal/path/evaluation.js @@ -1,5 +1,3 @@ -import * as t from "./index"; - /** * Walk the input `node` and statically evaluate if it's truthy. * @@ -18,15 +16,15 @@ import * as t from "./index"; * */ -export function evaluateTruthy(node: Object, scope: Scope): boolean { - var res = evaluate(node, scope); +export function evaluateTruthy(): boolean { + var res = this.evaluate(); if (res.confident) return !!res.value; } /** * Walk the input `node` and statically evaluate it. * - * Returns an pbject in the form `{ confident, value }`. `confident` indicates + * Returns an object in the form `{ confident, value }`. `confident` indicates * whether or not we had to drop out of evaluating the expression because of * hitting an unknown node that we couldn't confidently find the value of. * @@ -38,24 +36,27 @@ export function evaluateTruthy(node: Object, scope: Scope): boolean { * */ -export function evaluate(node: Object, scope: Scope): { confident: boolean; value: any } { +export function evaluate(): { confident: boolean; value: any } { var confident = true; - var value = evaluate(node); + var value = evaluate(this); if (!confident) value = undefined; return { confident: confident, value: value }; - function evaluate(node) { + function evaluate(path) { if (!confident) return; - if (t.isSequenceExpression(node)) { - return evaluate(node.expressions[node.expressions.length - 1]); + var node = path.node; + + if (path.isSequenceExpression()) { + var exprs = path.get("expressions"); + return evaluate(exprs[exprs.length - 1]); } - if (t.isLiteral(node)) { + if (path.isLiteral()) { if (node.regex && node.value === null) { // we have a regex and we can't represent it natively } else { @@ -63,24 +64,29 @@ export function evaluate(node: Object, scope: Scope): { confident: boolean; valu } } - if (t.isConditionalExpression(node)) { - if (evaluate(node.test)) { - return evaluate(node.consequent); + if (path.isConditionalExpression()) { + if (evaluate(path.get("test"))) { + return evaluate(path.get("consequent")); } else { - return evaluate(node.alternate); + return evaluate(path.get("alternate")); } } - if (t.isIdentifier(node)) { - if (node.name === "undefined") { - return undefined; + if (path.isIdentifier({ name: "undefined" })) { + return undefined; + } + + if (path.isIdentifier() || path.isMemberExpression()) { + path = path.resolve(); + if (path) { + return evaluate(path); } else { - return evaluate(scope.getImmutableBindingValue(node.name)); + return confident = false; } } - if (t.isUnaryExpression(node, { prefix: true })) { - var arg = evaluate(node.argument); + if (path.isUnaryExpression({ prefix: true })) { + var arg = evaluate(path.get("argument")); switch (node.operator) { case "void": return undefined; case "!": return !arg; @@ -89,13 +95,13 @@ export function evaluate(node: Object, scope: Scope): { confident: boolean; valu } } - if (t.isArrayExpression(node) || t.isObjectExpression(node)) { + if (path.isArrayExpression() || path.isObjectExpression()) { // we could evaluate these but it's probably impractical and not very useful } - if (t.isLogicalExpression(node)) { - let left = evaluate(node.left); - let right = evaluate(node.right); + if (path.isLogicalExpression()) { + let left = evaluate(path.get("left")); + let right = evaluate(path.get("right")); switch (node.operator) { case "||": return left || right; @@ -103,9 +109,9 @@ export function evaluate(node: Object, scope: Scope): { confident: boolean; valu } } - if (t.isBinaryExpression(node)) { - let left = evaluate(node.left); - let right = evaluate(node.right); + if (path.isBinaryExpression()) { + let left = evaluate(path.get("left")); + let right = evaluate(path.get("right")); switch (node.operator) { case "-": return left - right; diff --git a/src/babel/traversal/path.js b/src/babel/traversal/path/index.js similarity index 68% rename from src/babel/traversal/path.js rename to src/babel/traversal/path/index.js index 67f527871b..d7fc18a77a 100644 --- a/src/babel/traversal/path.js +++ b/src/babel/traversal/path/index.js @@ -2,10 +2,11 @@ import isBoolean from "lodash/lang/isBoolean"; import isNumber from "lodash/lang/isNumber"; import isRegExp from "lodash/lang/isRegExp"; import isString from "lodash/lang/isString"; -import traverse from "./index"; +import traverse from "../index"; import includes from "lodash/collection/includes"; -import Scope from "./scope"; -import * as t from "../types"; +import assign from "lodash/object/assign"; +import Scope from "../scope"; +import * as t from "../../types"; export default class TraversalPath { constructor(parent, container) { @@ -112,6 +113,13 @@ export default class TraversalPath { this._refresh(node, [node]); } + errorWithNode(msg, Error = SyntaxError) { + var loc = this.node.loc.start; + var err = new Error(`Line ${loc.line}: ${msg}`); + err.loc = loc; + return err; + } + get node() { return this.container[this.key]; } @@ -133,9 +141,6 @@ export default class TraversalPath { // potentially create new scope this.setScope(); - // refresh scope with new/removed bindings - this._refresh(oldNode, replacements); - var file = this.scope && this.scope.file; if (file) { for (var i = 0; i < replacements.length; i++) { @@ -170,12 +175,12 @@ export default class TraversalPath { } } - isBlacklisted() { + isBlacklisted(): boolean { var blacklist = this.opts.blacklist; return blacklist && blacklist.indexOf(this.node.type) > -1; } - visit() { + visit(): boolean { if (this.isBlacklisted()) return false; this.call("enter"); @@ -215,11 +220,22 @@ export default class TraversalPath { } } - has(key) { + has(key): boolean { return !!this.node[key]; } - getTypeAnnotation(): Object { + is(key): boolean { + return this.has(key); + } + + isnt(key): boolean { + return !this.has(key); + } + + getTypeAnnotation(): { + inferred: boolean; + annotation: ?Object; + } { if (this.typeInfo) { return this.typeInfo; } @@ -246,10 +262,15 @@ export default class TraversalPath { resolve(): ?TraversalPath { if (this.isVariableDeclarator()) { - return this.get("init").resolve(); + if (this.get("id").isIdentifier()) { + return this.get("init").resolve(); + } else { + // otherwise it's a request for a destructuring declarator and i'm not + // ready to resolve those just yet + } } else if (this.isIdentifier()) { var binding = this.scope.getBinding(this.node.name); - if (!binding) return; + if (!binding || binding.reassigned) return; if (binding.path === this) { return this; @@ -270,27 +291,44 @@ export default class TraversalPath { if (!prop.isProperty()) continue; var key = prop.get("key"); - if (key.isIdentifier({ name: targetName }) || key.isLiteral({ value: targetName })) { - return prop.get("value"); - } + + // { foo: obj } + var match = prop.isnt("computed") && key.isIdentifier({ name: targetName }); + + // { "foo": "obj" } or { ["foo"]: "obj" } + match ||= key.isLiteral({ value: targetName }); + + if (match) return prop.get("value"); } } else { return this; } } - inferType(path: TraversalPath) { + inferType(path: TraversalPath): ?Object { path = path.resolve(); if (!path) return; - if (path.isRestElement() || path.isArrayExpression()) { + if (path.isRestElement() || path.parentPath.isRestElement() || path.isArrayExpression()) { return t.genericTypeAnnotation(t.identifier("Array")); } + if (path.parentPath.isTypeCastExpression()) { + return path.parentPath.node.typeAnnotation; + } + + if (path.isTypeCastExpression()) { + return path.node.typeAnnotation; + } + if (path.isObjectExpression()) { return t.genericTypeAnnotation(t.identifier("Object")); } + if (path.isFunction()) { + return t.identifier("Function"); + } + if (path.isLiteral()) { var value = path.node.value; if (isString(value)) return t.stringTypeAnnotation(); @@ -304,39 +342,44 @@ export default class TraversalPath { } } - isScope() { + isScope(): boolean { return t.isScope(this.node, this.parent); } - isReferencedIdentifier(opts) { + isReferencedIdentifier(opts): boolean { return t.isReferencedIdentifier(this.node, this.parent, opts); } - isReferenced() { + isReferenced(): boolean { return t.isReferenced(this.node, this.parent); } - isBlockScoped() { + isBlockScoped(): boolean { return t.isBlockScoped(this.node); } - isVar() { + isVar(): boolean { return t.isVar(this.node); } - isScope() { + isScope(): boolean { return t.isScope(this.node, this.parent); } - isTypeGeneric(genericName: string, hasTypeParameters?): boolean { - var type = this.getTypeAnnotation().annotation; + isTypeGeneric(genericName: string, opts = {}): boolean { + var typeInfo = this.getTypeAnnotation(); + var type = typeInfo.annotation; if (!type) return false; + if (type.inferred && opts.inference === false) { + return false; + } + if (!t.isGenericTypeAnnotation(type) || !t.isIdentifier(type.id, { name: genericName })) { return false; } - if (hasTypeParameters && !type.typeParameters) { + if (opts.requireTypeParameters && !type.typeParameters) { return false; } @@ -348,10 +391,64 @@ export default class TraversalPath { } traverse(opts, state) { - traverse(this.node, opts, this.scope, state); + traverse(this.node, opts, this.scope, state, this); + } + + /** + * Match the current node if it matches the provided `pattern`. + * + * For example, given the match `React.createClass` it would match the + * parsed nodes of `React.createClass` and `React["createClass"]`. + */ + + matchesPattern(pattern: string, allowPartial?: boolean): boolean { + var parts = pattern.split("."); + + // not a member expression + if (!this.isMemberExpression()) return false; + + var search = [this.node]; + var i = 0; + + while (search.length) { + var node = search.shift(); + + if (allowPartial && i === parts.length) { + return true; + } + + if (t.isIdentifier(node)) { + // this part doesn't match + if (parts[i] !== node.name) return false; + } else if (t.isLiteral(node)) { + // this part doesn't match + if (parts[i] !== node.value) return false; + } else if (t.isMemberExpression(node)) { + if (node.computed && !t.isLiteral(node.property)) { + // we can't deal with this + return false; + } else { + search.push(node.object); + search.push(node.property); + continue; + } + } else { + // we can't deal with this + return false; + } + + // too many parts + if (++i > parts.length) { + return false; + } + } + + return true; } } +assign(TraversalPath.prototype, require("./evaluation")); + for (var i = 0; i < t.TYPES.length; i++) { let type = t.TYPES[i]; let typeKey = `is${type}`; diff --git a/src/babel/traversal/scope.js b/src/babel/traversal/scope.js index e58da1aabe..24cd0c6b8d 100644 --- a/src/babel/traversal/scope.js +++ b/src/babel/traversal/scope.js @@ -75,7 +75,7 @@ export default class Scope { if (cached) { return cached; } else { - path.setData("scope", this); + //path.setData("scope", this); } this.parent = parent; @@ -245,7 +245,7 @@ export default class Scope { if (t.isReferencedIdentifier(node, parent) && node.name === oldName) { node.name = newName; } else if (t.isDeclaration(node)) { - var ids = t.getBindingIdentifiers(node); + var ids = this.getBindingIdentifiers(); for (var name in ids) { if (name === oldName) ids[name].name = newName; } @@ -272,7 +272,7 @@ export default class Scope { if (t.isIdentifier(node)) { var binding = this.getBinding(node.name); - if (binding && binding.isTypeGeneric("Array")) return node; + if (binding && binding.isTypeGeneric("Array", { inference: false })) return node; } if (t.isArrayExpression(node)) { @@ -613,26 +613,6 @@ export default class Scope { return binding && binding.identifier; } - /** - * Description - */ - - getOwnImmutableBindingValue(name: string) { - return this._immutableBindingInfoToValue(this.getOwnBindingInfo(name)); - } - - /** - * Description - */ - - getImmutableBindingValue(name: string) { - return this._immutableBindingInfoToValue(this.getBinding(name)); - } - - _immutableBindingInfoToValue(binding) { - if (binding) return binding.getValueIfImmutable(); - } - /** * Description */ diff --git a/src/babel/types/index.js b/src/babel/types/index.js index 79293f0694..b82f1b380a 100644 --- a/src/babel/types/index.js +++ b/src/babel/types/index.js @@ -297,7 +297,6 @@ toFastProperties(t); toFastProperties(t.VISITOR_KEYS); exports.__esModule = true; -assign(t, require("./evaluators")); assign(t, require("./retrievers")); assign(t, require("./validators")); assign(t, require("./converters")); diff --git a/src/babel/types/visitor-keys.json b/src/babel/types/visitor-keys.json index e3ad4a629e..b30d9f0106 100644 --- a/src/babel/types/visitor-keys.json +++ b/src/babel/types/visitor-keys.json @@ -93,7 +93,7 @@ "TypeofTypeAnnotation": ["argument"], "TypeAlias": ["id", "typeParameters", "right"], "TypeAnnotation": ["typeAnnotation"], - "TypeCastExpression": ["expression"], + "TypeCastExpression": ["expression", "typeAnnotation"], "TypeParameterDeclaration": ["params"], "TypeParameterInstantiation": ["params"], "ObjectTypeAnnotation": ["key", "value"], diff --git a/test/fixtures/transformation/es7-comprehensions/array-expression-single/actual.js b/test/fixtures/transformation/es7-comprehensions/array-expression-single/actual.js index 2087ae351b..9dda19048b 100644 --- a/test/fixtures/transformation/es7-comprehensions/array-expression-single/actual.js +++ b/test/fixtures/transformation/es7-comprehensions/array-expression-single/actual.js @@ -1 +1 @@ -var arr = [for (i of [1, 2, 3]) i * i]; +var arr = [for (i of nums) i * i]; diff --git a/test/fixtures/transformation/es7-comprehensions/array-expression-single/expected.js b/test/fixtures/transformation/es7-comprehensions/array-expression-single/expected.js index 2ee858e713..10acf74be9 100644 --- a/test/fixtures/transformation/es7-comprehensions/array-expression-single/expected.js +++ b/test/fixtures/transformation/es7-comprehensions/array-expression-single/expected.js @@ -7,7 +7,7 @@ var arr = (function () { var _iteratorError = undefined; try { - for (var _iterator = [1, 2, 3][Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + for (var _iterator = nums[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var i = _step.value; _arr.push(i * i); @@ -28,4 +28,4 @@ var arr = (function () { } return _arr; -})(); \ No newline at end of file +})(); diff --git a/test/fixtures/transformation/es7-comprehensions/array-this/actual.js b/test/fixtures/transformation/es7-comprehensions/array-this/actual.js index 355ed48188..5ae580c2d3 100644 --- a/test/fixtures/transformation/es7-comprehensions/array-this/actual.js +++ b/test/fixtures/transformation/es7-comprehensions/array-this/actual.js @@ -1,5 +1,5 @@ function add() { - return [for (i of [1, 2, 3]) i * this.multiplier]; + return [for (i of nums) i * this.multiplier]; } add.call({ multiplier: 5 }); diff --git a/test/fixtures/transformation/es7-comprehensions/array-this/expected.js b/test/fixtures/transformation/es7-comprehensions/array-this/expected.js index 5f7f52ab97..4ca4eeaee8 100644 --- a/test/fixtures/transformation/es7-comprehensions/array-this/expected.js +++ b/test/fixtures/transformation/es7-comprehensions/array-this/expected.js @@ -10,7 +10,7 @@ function add() { var _iteratorError = undefined; try { - for (var _iterator = [1, 2, 3][Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + for (var _iterator = nums[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var i = _step.value; _ref.push(i * _this.multiplier); @@ -34,4 +34,4 @@ function add() { })(); } -add.call({ multiplier: 5 }); \ No newline at end of file +add.call({ multiplier: 5 });