UNPKG

babel-core

Version:

Turn ES6 code into readable vanilla ES5 with source maps

834 lines (644 loc) 19.7 kB
"use strict"; var _interopRequireWildcard = function (obj) { return obj && obj.__esModule ? obj : { "default": obj }; }; var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; var includes = _interopRequire(require("lodash/collection/includes")); var traverse = _interopRequire(require("./index")); var defaults = _interopRequire(require("lodash/object/defaults")); var messages = _interopRequireWildcard(require("../messages")); var globals = _interopRequire(require("globals")); var flatten = _interopRequire(require("lodash/array/flatten")); var extend = _interopRequire(require("lodash/object/extend")); var object = _interopRequire(require("../helpers/object")); var each = _interopRequire(require("lodash/collection/each")); var t = _interopRequireWildcard(require("../types")); var functionVariableVisitor = { enter: function enter(node, parent, scope, state) { if (t.isFor(node)) { each(t.FOR_INIT_KEYS, function (key) { var declar = node[key]; if (t.isVar(declar)) state.scope.registerBinding("var", declar); }); } // this block is a function so we'll stop since none of the variables // declared within are accessible if (t.isFunction(node)) return this.skip(); // function identifier doesn't belong to this scope if (state.blockId && node === state.blockId) return; // delegate block scope handling to the `blockVariableVisitor` if (t.isBlockScoped(node)) return; // this will be hit again once we traverse into it after this iteration if (t.isExportDeclaration(node) && t.isDeclaration(node.declaration)) return; // we've ran into a declaration! if (t.isDeclaration(node)) state.scope.registerDeclaration(node); } }; var programReferenceVisitor = { enter: function enter(node, parent, scope, state) { if (t.isReferencedIdentifier(node, parent) && !scope.hasBinding(node.name)) { state.addGlobal(node); } else if (t.isLabeledStatement(node)) { state.addGlobal(node); } else if (t.isAssignmentExpression(node) || t.isUpdateExpression(node) || t.isUnaryExpression(node) && node.operator === "delete") { scope.registerBindingReassignment(node); } } }; var blockVariableVisitor = { enter: function enter(node, parent, scope, state) { if (t.isFunctionDeclaration(node) || t.isBlockScoped(node)) { state.registerDeclaration(node); } else if (t.isScope(node, parent)) { this.skip(); } } }; var Scope = (function () { /** * This searches the current "scope" and collects all references/bindings * within. */ function Scope(block, parentBlock, parent, file) { _classCallCheck(this, Scope); this.parent = parent; this.file = parent ? parent.file : file; this.parentBlock = parentBlock; this.block = block; this.crawl(); } Scope.globals = flatten([globals.builtin, globals.browser, globals.node].map(Object.keys)); Scope.contextVariables = ["this", "arguments", "super"]; /** * Description */ Scope.prototype.traverse = (function (_traverse) { var _traverseWrapper = function traverse(_x, _x2, _x3) { return _traverse.apply(this, arguments); }; _traverseWrapper.toString = function () { return _traverse.toString(); }; return _traverseWrapper; })(function (node, opts, state) { traverse(node, opts, this, state); }); /** * Description */ Scope.prototype.generateTemp = function generateTemp() { var name = arguments[0] === undefined ? "temp" : arguments[0]; var id = this.generateUidIdentifier(name); this.push({ key: id.name, id: id }); return id; }; /** * Description */ Scope.prototype.generateUidIdentifier = function generateUidIdentifier(name) { var id = t.identifier(this.generateUid(name)); this.getFunctionParent().registerBinding("uid", id); return id; }; /** * Description */ Scope.prototype.generateUid = function generateUid(name) { name = t.toIdentifier(name).replace(/^_+/, ""); var uid; var i = 0; do { uid = this._generateUid(name, i); i++; } while (this.hasBinding(uid) || this.hasGlobal(uid)); return uid; }; Scope.prototype._generateUid = function _generateUid(name, i) { var id = name; if (i > 1) id += i; return "_" + id; }; /* * Description */ Scope.prototype.generateUidBasedOnNode = function generateUidBasedOnNode(parent) { var node = parent; if (t.isAssignmentExpression(parent)) { node = parent.left; } else if (t.isVariableDeclarator(parent)) { node = parent.id; } else if (t.isProperty(node)) { node = node.key; } var parts = []; var add = (function (_add) { var _addWrapper = function add(_x) { return _add.apply(this, arguments); }; _addWrapper.toString = function () { return _add.toString(); }; return _addWrapper; })(function (node) { if (t.isMemberExpression(node)) { add(node.object); add(node.property); } else if (t.isIdentifier(node)) { parts.push(node.name); } else if (t.isLiteral(node)) { parts.push(node.value); } else if (t.isCallExpression(node)) { add(node.callee); } }); add(node); var id = parts.join("$"); id = id.replace(/^_/, "") || "ref"; return this.generateUidIdentifier(id); }; /** * Description */ Scope.prototype.generateTempBasedOnNode = function generateTempBasedOnNode(node) { if (t.isIdentifier(node) && this.hasBinding(node.name)) { return null; } var id = this.generateUidBasedOnNode(node); this.push({ key: id.name, id: id }); return id; }; /** * Description */ Scope.prototype.checkBlockScopedCollisions = function checkBlockScopedCollisions(kind, name, id) { var local = this.getOwnBindingInfo(name); if (!local) return; if (kind === "param") return; if (kind === "hoisted" && local.kind === "let") return; if (local.kind === "let" || local.kind === "const" || local.kind === "module") { throw this.file.errorWithNode(id, messages.get("scopeDuplicateDeclaration", name), TypeError); } }; /** * Description */ Scope.prototype.rename = function rename(oldName, newName) { if (!newName) newName = this.generateUidIdentifier(oldName).name; var info = this.getBindingInfo(oldName); if (!info) return; var binding = info.identifier; var scope = info.scope; scope.traverse(scope.block, { enter: function enter(node, parent, scope) { if (t.isReferencedIdentifier(node, parent) && node.name === oldName) { node.name = newName; } else if (t.isDeclaration(node)) { var ids = t.getBindingIdentifiers(node); for (var name in ids) { if (name === oldName) ids[name].name = newName; } } else if (t.isScope(node, parent)) { if (!scope.bindingIdentifierEquals(oldName, binding)) { this.skip(); } } } }); scope.removeOwnBinding(oldName); scope.bindings[newName] = info; binding.name = newName; }; /** * Description */ Scope.prototype.inferType = function inferType(node) { var target; if (t.isVariableDeclarator(node)) { target = node.init; } if (t.isArrayExpression(target)) { return t.genericTypeAnnotation(t.identifier("Array")); } if (t.isObjectExpression(target)) { return; } if (t.isLiteral(target)) { return; } if (t.isCallExpression(target) && t.isIdentifier(target.callee)) { var funcInfo = this.getBindingInfo(target.callee.name); if (funcInfo) { var funcNode = funcInfo.node; return !funcInfo.reassigned && t.isFunction(funcNode) && node.returnType; } } if (t.isIdentifier(target)) { return; } }; /** * Description */ Scope.prototype.isTypeGeneric = function isTypeGeneric(name, genericName) { var info = this.getBindingInfo(name); if (!info) return false; var type = info.typeAnnotation; return t.isGenericTypeAnnotation(type) && t.isIdentifier(type.id, { name: genericName }); }; /** * Description */ Scope.prototype.assignTypeGeneric = function assignTypeGeneric(name, type) { this.assignType(name, t.genericTypeAnnotation(t.identifier(type))); }; /** * Description */ Scope.prototype.assignType = function assignType(name, type) { var info = this.getBindingInfo(name); if (!info) return; info.typeAnnotation = type; }; /** * Description */ Scope.prototype.getTypeAnnotation = function getTypeAnnotation(id, node) { var info = { annotation: null, inferred: false }; var type; if (id.typeAnnotation) { type = id.typeAnnotation; } if (!type) { info.inferred = true; type = this.inferType(node); } if (type) { if (t.isTypeAnnotation(type)) type = type.typeAnnotation; info.annotation = type; } return info; }; /** * Description */ Scope.prototype.toArray = function toArray(node, i) { var file = this.file; if (t.isIdentifier(node) && this.isTypeGeneric(node.name, "Array")) { return node; } if (t.isArrayExpression(node)) { return node; } if (t.isIdentifier(node, { name: "arguments" })) { return t.callExpression(t.memberExpression(file.addHelper("slice"), t.identifier("call")), [node]); } var helperName = "to-array"; var args = [node]; if (i === true) { helperName = "to-consumable-array"; } else if (i) { args.push(t.literal(i)); helperName = "sliced-to-array"; } return t.callExpression(file.addHelper(helperName), args); }; /** * Description */ Scope.prototype.refreshDeclaration = function refreshDeclaration(node) { if (t.isBlockScoped(node)) { this.getBlockParent().registerDeclaration(node); } else if (t.isVariableDeclaration(node, { kind: "var" })) { this.getFunctionParent().registerDeclaration(node); } else if (node === this.block) { this.recrawl(); } }; /** * Description */ Scope.prototype.registerDeclaration = function registerDeclaration(node) { if (t.isFunctionDeclaration(node)) { this.registerBinding("hoisted", node); } else if (t.isVariableDeclaration(node)) { for (var i = 0; i < node.declarations.length; i++) { this.registerBinding(node.kind, node.declarations[i]); } } else if (t.isClassDeclaration(node)) { this.registerBinding("let", node); } else if (t.isImportDeclaration(node) || t.isExportDeclaration(node)) { this.registerBinding("module", node); } else { this.registerBinding("unknown", node); } }; /** * Description */ Scope.prototype.registerBindingReassignment = function registerBindingReassignment(node) { var ids = t.getBindingIdentifiers(node); for (var name in ids) { var info = this.getBindingInfo(name); if (info) { info.reassigned = true; if (info.typeAnnotationInferred) { // destroy the inferred typeAnnotation info.typeAnnotation = null; } } } }; /** * Description */ Scope.prototype.registerBinding = function registerBinding(kind, node) { if (!kind) throw new ReferenceError("no `kind`"); var ids = t.getBindingIdentifiers(node); for (var name in ids) { var id = ids[name]; this.checkBlockScopedCollisions(kind, name, id); var typeInfo = this.getTypeAnnotation(id, node); this.bindings[name] = { typeAnnotationInferred: typeInfo.inferred, typeAnnotation: typeInfo.annotation, reassigned: false, identifier: id, scope: this, node: node, kind: kind }; } }; /** * Description */ Scope.prototype.addGlobal = function addGlobal(node) { this.globals[node.name] = node; }; /** * Description */ Scope.prototype.hasGlobal = function hasGlobal(name) { var scope = this; do { if (scope.globals[name]) return true; } while (scope = scope.parent); return false; }; /** * Description */ Scope.prototype.recrawl = function recrawl() { this.block._scopeInfo = null; this.crawl(); }; /** * Description */ Scope.prototype.crawl = function crawl() { var block = this.block; var i; // var info = block._scopeInfo; if (info) { extend(this, info); return; } info = block._scopeInfo = { bindings: object(), globals: object() }; extend(this, info); // ForStatement - left, init if (t.isLoop(block)) { for (i = 0; i < t.FOR_INIT_KEYS.length; i++) { var node = block[t.FOR_INIT_KEYS[i]]; if (t.isBlockScoped(node)) this.registerBinding("let", node); } if (t.isBlockStatement(block.body)) { block = block.body; } } // FunctionExpression - id if (t.isFunctionExpression(block) && block.id) { if (!t.isProperty(this.parentBlock, { method: true })) { this.registerBinding("var", block.id); } } // Class if (t.isClass(block) && block.id) { this.registerBinding("var", block.id); } // Function - params, rest if (t.isFunction(block)) { for (i = 0; i < block.params.length; i++) { this.registerBinding("param", block.params[i]); } this.traverse(block.body, blockVariableVisitor, this); } // Program, BlockStatement, Function - let variables if (t.isBlockStatement(block) || t.isProgram(block)) { this.traverse(block, blockVariableVisitor, this); } // CatchClause - param if (t.isCatchClause(block)) { this.registerBinding("let", block.param); } // ComprehensionExpression - blocks if (t.isComprehensionExpression(block)) { this.registerBinding("let", block); } // Program, Function - var variables if (t.isProgram(block) || t.isFunction(block)) { this.traverse(block, functionVariableVisitor, { blockId: block.id, scope: this }); } // Program if (t.isProgram(block)) { this.traverse(block, programReferenceVisitor, this); } }; /** * Description */ Scope.prototype.push = function push(opts) { var block = this.block; if (t.isLoop(block) || t.isCatchClause(block) || t.isFunction(block)) { t.ensureBlock(block); block = block.body; } if (!t.isBlockStatement(block) && !t.isProgram(block)) { block = this.getBlockParent().block; } var _block = block; if (!_block._declarations) _block._declarations = {}; block._declarations[opts.key || opts.id.name] = { kind: opts.kind || "var", id: opts.id, init: opts.init }; }; /** * Walk up the scope tree until we hit either a Function or reach the * very top and hit Program. */ Scope.prototype.getFunctionParent = function getFunctionParent() { var scope = this; while (scope.parent && !t.isFunction(scope.block)) { scope = scope.parent; } return scope; }; /** * Walk up the scope tree until we hit either a BlockStatement/Loop or reach the * very top and hit Program. */ Scope.prototype.getBlockParent = function getBlockParent() { var scope = this; while (scope.parent && !t.isFunction(scope.block) && !t.isLoop(scope.block) && !t.isFunction(scope.block)) { scope = scope.parent; } return scope; }; /** * Walks the scope tree and gathers **all** bindings. */ Scope.prototype.getAllBindings = function getAllBindings() { var ids = object(); var scope = this; do { defaults(ids, scope.bindings); scope = scope.parent; } while (scope); return ids; }; /** * Walks the scope tree and gathers all declarations of `kind`. */ Scope.prototype.getAllBindingsOfKind = function getAllBindingsOfKind(kind) { var ids = object(); var scope = this; do { for (var name in scope.bindings) { var binding = scope.bindings[name]; if (binding.kind === kind) ids[name] = binding; } scope = scope.parent; } while (scope); return ids; }; /** * Description */ Scope.prototype.bindingIdentifierEquals = function bindingIdentifierEquals(name, node) { return this.getBindingIdentifier(name) === node; }; /** * Description */ Scope.prototype.getBindingInfo = function getBindingInfo(name) { var scope = this; do { var binding = scope.getOwnBindingInfo(name); if (binding) return binding; } while (scope = scope.parent); }; /** * Description */ Scope.prototype.getOwnBindingInfo = function getOwnBindingInfo(name) { return this.bindings[name]; }; /** * Description */ Scope.prototype.getBindingIdentifier = function getBindingIdentifier(name) { var info = this.getBindingInfo(name); return info && info.identifier; }; /** * Description */ Scope.prototype.getOwnBindingIdentifier = function getOwnBindingIdentifier(name) { var binding = this.bindings[name]; return binding && binding.identifier; }; /** * Description */ Scope.prototype.getOwnImmutableBindingValue = function getOwnImmutableBindingValue(name) { return this._immutableBindingInfoToValue(this.getOwnBindingInfo(name)); }; /** * Description */ Scope.prototype.getImmutableBindingValue = function getImmutableBindingValue(name) { return this._immutableBindingInfoToValue(this.getBindingInfo(name)); }; Scope.prototype._immutableBindingInfoToValue = function _immutableBindingInfoToValue(info) { if (!info) return; // can't guarantee this value is the same if (info.reassigned) return; var node = info.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 */ Scope.prototype.hasOwnBinding = function hasOwnBinding(name) { return !!this.getOwnBindingInfo(name); }; /** * Description */ Scope.prototype.hasBinding = function hasBinding(name) { if (!name) return false; if (this.hasOwnBinding(name)) return true; if (this.parentHasBinding(name)) return true; if (includes(Scope.globals, name)) return true; if (includes(Scope.contextVariables, name)) return true; return false; }; /** * Description */ Scope.prototype.parentHasBinding = function parentHasBinding(name) { return this.parent && this.parent.hasBinding(name); }; /** * Description */ Scope.prototype.removeOwnBinding = function removeOwnBinding(name) { this.bindings[name] = null; }; /** * Description */ Scope.prototype.removeBinding = function removeBinding(name) { var info = this.getBindingInfo(name); if (info) info.scope.removeOwnBinding(name); }; return Scope; })(); module.exports = Scope;