├── .gitignore ├── README.md ├── .npmignore ├── src ├── hub.js ├── cache.js ├── path │ ├── conversion.js │ ├── removal.js │ ├── comments.js │ ├── lib │ │ ├── removal-hooks.js │ │ ├── virtual-types.js │ │ └── hoister.js │ ├── inference │ │ ├── index.js │ │ ├── inferers.js │ │ └── inferer-reference.js │ ├── family.js │ ├── index.js │ ├── context.js │ ├── modification.js │ ├── ancestry.js │ └── replacement.js ├── scope │ ├── binding.js │ └── lib │ │ └── renamer.js ├── index.js ├── context.js └── visitors.js ├── lib ├── hub.js ├── cache.js ├── path │ ├── comments.js │ ├── conversion.js │ ├── lib │ │ ├── removal-hooks.js │ │ ├── virtual-types.js │ │ └── hoister.js │ ├── removal.js │ ├── inference │ │ ├── index.js │ │ ├── inferers.js │ │ └── inferer-reference.js │ ├── family.js │ ├── ancestry.js │ ├── context.js │ ├── index.js │ ├── replacement.js │ ├── modification.js │ └── evaluation.js ├── scope │ ├── binding.js │ └── lib │ │ └── renamer.js ├── context.js ├── index.js └── visitors.js ├── package.json └── test ├── scope.js ├── inference.js ├── evaluation.js └── traverse.js /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # babel-traverse 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | test 3 | node_modules 4 | -------------------------------------------------------------------------------- /src/hub.js: -------------------------------------------------------------------------------- 1 | export default class Hub { 2 | constructor(file, options) { 3 | this.file = file; 4 | this.options = options; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/cache.js: -------------------------------------------------------------------------------- 1 | export let path = new WeakMap(); 2 | export let scope = new WeakMap(); 3 | 4 | export function clear() { 5 | path = new WeakMap(); 6 | scope = new WeakMap(); 7 | } 8 | -------------------------------------------------------------------------------- /lib/hub.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); 6 | 7 | var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); 8 | 9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 10 | 11 | var Hub = function Hub(file, options) { 12 | (0, _classCallCheck3.default)(this, Hub); 13 | 14 | this.file = file; 15 | this.options = options; 16 | }; 17 | 18 | exports.default = Hub; 19 | module.exports = exports["default"]; -------------------------------------------------------------------------------- /lib/cache.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | exports.scope = exports.path = undefined; 5 | 6 | var _weakMap = require("babel-runtime/core-js/weak-map"); 7 | 8 | var _weakMap2 = _interopRequireDefault(_weakMap); 9 | 10 | exports.clear = clear; 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 13 | 14 | var path = exports.path = new _weakMap2.default(); 15 | var scope = exports.scope = new _weakMap2.default(); 16 | 17 | function clear() { 18 | exports.path = path = new _weakMap2.default(); 19 | exports.scope = scope = new _weakMap2.default(); 20 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-traverse", 3 | "version": "6.13.0", 4 | "description": "", 5 | "author": "Sebastian McKenzie ", 6 | "homepage": "https://babeljs.io/", 7 | "license": "MIT", 8 | "repository": "https://github.com/babel/babel/tree/master/packages/babel-traverse", 9 | "main": "lib/index.js", 10 | "dependencies": { 11 | "babel-code-frame": "^6.8.0", 12 | "babel-messages": "^6.8.0", 13 | "babel-runtime": "^6.9.0", 14 | "babel-types": "^6.13.0", 15 | "babylon": "^6.9.0", 16 | "debug": "^2.2.0", 17 | "globals": "^8.3.0", 18 | "invariant": "^2.2.0", 19 | "lodash": "^4.2.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/path/conversion.js: -------------------------------------------------------------------------------- 1 | // This file contains methods that convert the path node into another node or some other type of data. 2 | 3 | import * as t from "babel-types"; 4 | 5 | export function toComputedKey(): Object { 6 | let node = this.node; 7 | 8 | let key; 9 | if (this.isMemberExpression()) { 10 | key = node.property; 11 | } else if (this.isProperty() || this.isMethod()) { 12 | key = node.key; 13 | } else { 14 | throw new ReferenceError("todo"); 15 | } 16 | 17 | if (!node.computed) { 18 | if (t.isIdentifier(key)) key = t.stringLiteral(key.name); 19 | } 20 | 21 | return key; 22 | } 23 | 24 | export function ensureBlock() { 25 | return t.ensureBlock(this.node); 26 | } 27 | 28 | export function arrowFunctionToShadowed() { 29 | // todo: maybe error 30 | if (!this.isArrowFunctionExpression()) return; 31 | 32 | this.ensureBlock(); 33 | 34 | let { node } = this; 35 | node.expression = false; 36 | node.type = "FunctionExpression"; 37 | node.shadow = node.shadow || true; 38 | } 39 | -------------------------------------------------------------------------------- /src/path/removal.js: -------------------------------------------------------------------------------- 1 | // This file contains methods responsible for removing a node. 2 | 3 | import { hooks } from "./lib/removal-hooks"; 4 | 5 | export function remove() { 6 | this._assertUnremoved(); 7 | 8 | this.resync(); 9 | 10 | if (this._callRemovalHooks()) { 11 | this._markRemoved(); 12 | return; 13 | } 14 | 15 | this.shareCommentsWithSiblings(); 16 | this._remove(); 17 | this._markRemoved(); 18 | } 19 | 20 | export function _callRemovalHooks() { 21 | for (let fn of (hooks: Array)) { 22 | if (fn(this, this.parentPath)) return true; 23 | } 24 | } 25 | 26 | export function _remove() { 27 | if (Array.isArray(this.container)) { 28 | this.container.splice(this.key, 1); 29 | this.updateSiblingKeys(this.key, -1); 30 | } else { 31 | this._replaceWith(null); 32 | } 33 | } 34 | 35 | export function _markRemoved() { 36 | this.shouldSkip = true; 37 | this.removed = true; 38 | this.node = null; 39 | } 40 | 41 | export function _assertUnremoved() { 42 | if (this.removed) { 43 | throw this.buildCodeFrameError("NodePath has been removed so is read-only."); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/path/comments.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | exports.shareCommentsWithSiblings = shareCommentsWithSiblings; 5 | exports.addComment = addComment; 6 | exports.addComments = addComments; 7 | function shareCommentsWithSiblings() { 8 | var node = this.node; 9 | if (!node) return; 10 | 11 | var trailing = node.trailingComments; 12 | var leading = node.leadingComments; 13 | if (!trailing && !leading) return; 14 | 15 | var prev = this.getSibling(this.key - 1); 16 | var next = this.getSibling(this.key + 1); 17 | 18 | if (!prev.node) prev = next; 19 | if (!next.node) next = prev; 20 | 21 | prev.addComments("trailing", leading); 22 | next.addComments("leading", trailing); 23 | } 24 | 25 | function addComment(type, content, line) { 26 | this.addComments(type, [{ 27 | type: line ? "CommentLine" : "CommentBlock", 28 | value: content 29 | }]); 30 | } 31 | 32 | function addComments(type, comments) { 33 | if (!comments) return; 34 | 35 | var node = this.node; 36 | if (!node) return; 37 | 38 | var key = type + "Comments"; 39 | 40 | if (node[key]) { 41 | node[key] = node[key].concat(comments); 42 | } else { 43 | node[key] = comments; 44 | } 45 | } -------------------------------------------------------------------------------- /src/path/comments.js: -------------------------------------------------------------------------------- 1 | // This file contains methods responsible for dealing with comments. 2 | 3 | /** 4 | * Share comments amongst siblings. 5 | */ 6 | 7 | export function shareCommentsWithSiblings() { 8 | let node = this.node; 9 | if (!node) return; 10 | 11 | let trailing = node.trailingComments; 12 | let leading = node.leadingComments; 13 | if (!trailing && !leading) return; 14 | 15 | let prev = this.getSibling(this.key - 1); 16 | let next = this.getSibling(this.key + 1); 17 | 18 | if (!prev.node) prev = next; 19 | if (!next.node) next = prev; 20 | 21 | prev.addComments("trailing", leading); 22 | next.addComments("leading", trailing); 23 | } 24 | 25 | export function addComment(type, content, line?) { 26 | this.addComments(type, [{ 27 | type: line ? "CommentLine" : "CommentBlock", 28 | value: content 29 | }]); 30 | } 31 | 32 | /** 33 | * Give node `comments` of the specified `type`. 34 | */ 35 | 36 | export function addComments(type: string, comments: Array) { 37 | if (!comments) return; 38 | 39 | let node = this.node; 40 | if (!node) return; 41 | 42 | let key = `${type}Comments`; 43 | 44 | if (node[key]) { 45 | node[key] = node[key].concat(comments); 46 | } else { 47 | node[key] = comments; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/path/conversion.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | exports.toComputedKey = toComputedKey; 5 | exports.ensureBlock = ensureBlock; 6 | exports.arrowFunctionToShadowed = arrowFunctionToShadowed; 7 | 8 | var _babelTypes = require("babel-types"); 9 | 10 | var t = _interopRequireWildcard(_babelTypes); 11 | 12 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 13 | 14 | function toComputedKey() { 15 | var node = this.node; 16 | 17 | var key = void 0; 18 | if (this.isMemberExpression()) { 19 | key = node.property; 20 | } else if (this.isProperty() || this.isMethod()) { 21 | key = node.key; 22 | } else { 23 | throw new ReferenceError("todo"); 24 | } 25 | 26 | if (!node.computed) { 27 | if (t.isIdentifier(key)) key = t.stringLiteral(key.name); 28 | } 29 | 30 | return key; 31 | } 32 | 33 | function ensureBlock() { 34 | return t.ensureBlock(this.node); 35 | } 36 | 37 | function arrowFunctionToShadowed() { 38 | if (!this.isArrowFunctionExpression()) return; 39 | 40 | this.ensureBlock(); 41 | 42 | var node = this.node; 43 | 44 | node.expression = false; 45 | node.type = "FunctionExpression"; 46 | node.shadow = node.shadow || true; 47 | } -------------------------------------------------------------------------------- /test/scope.js: -------------------------------------------------------------------------------- 1 | var traverse = require("../lib").default; 2 | var assert = require("assert"); 3 | var parse = require("babylon").parse; 4 | 5 | function getPath(code) { 6 | var ast = parse(code); 7 | var path; 8 | traverse(ast, { 9 | Program: function (_path) { 10 | path = _path; 11 | _path.stop(); 12 | } 13 | }); 14 | return path; 15 | } 16 | 17 | suite("scope", function () { 18 | suite("binding paths", function () { 19 | test("function declaration id", function () { 20 | assert.ok(getPath("function foo() {}").scope.getBinding("foo").path.type === "FunctionDeclaration"); 21 | }); 22 | 23 | test("function expression id", function () { 24 | assert.ok(getPath("(function foo() {})").get("body")[0].get("expression").scope.getBinding("foo").path.type === "FunctionExpression"); 25 | }); 26 | 27 | test("function param", function () { 28 | assert.ok(getPath("(function (foo) {})").get("body")[0].get("expression").scope.getBinding("foo").path.type === "Identifier"); 29 | }); 30 | 31 | test("variable declaration", function () { 32 | assert.ok(getPath("var foo = null;").scope.getBinding("foo").path.type === "VariableDeclarator"); 33 | assert.ok(getPath("var { foo } = null;").scope.getBinding("foo").path.type === "VariableDeclarator"); 34 | assert.ok(getPath("var [ foo ] = null;").scope.getBinding("foo").path.type === "VariableDeclarator"); 35 | assert.ok(getPath("var { bar: [ foo ] } = null;").scope.getBinding("foo").path.type === "VariableDeclarator"); 36 | }); 37 | 38 | test("purity", function () { 39 | assert.ok(getPath("({ x: 1 })").get("body")[0].get("expression").isPure()); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /lib/path/lib/removal-hooks.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | var hooks = exports.hooks = [function (self, parent) { 5 | if (self.key === "body" && parent.isArrowFunctionExpression()) { 6 | self.replaceWith(self.scope.buildUndefinedNode()); 7 | return true; 8 | } 9 | }, function (self, parent) { 10 | var removeParent = false; 11 | 12 | removeParent = removeParent || self.key === "test" && (parent.isWhile() || parent.isSwitchCase()); 13 | 14 | removeParent = removeParent || self.key === "declaration" && parent.isExportDeclaration(); 15 | 16 | removeParent = removeParent || self.key === "body" && parent.isLabeledStatement(); 17 | 18 | removeParent = removeParent || self.listKey === "declarations" && parent.isVariableDeclaration() && parent.node.declarations.length === 1; 19 | 20 | removeParent = removeParent || self.key === "expression" && parent.isExpressionStatement(); 21 | 22 | if (removeParent) { 23 | parent.remove(); 24 | return true; 25 | } 26 | }, function (self, parent) { 27 | if (parent.isSequenceExpression() && parent.node.expressions.length === 1) { 28 | parent.replaceWith(parent.node.expressions[0]); 29 | return true; 30 | } 31 | }, function (self, parent) { 32 | if (parent.isBinary()) { 33 | if (self.key === "left") { 34 | parent.replaceWith(parent.node.right); 35 | } else { 36 | parent.replaceWith(parent.node.left); 37 | } 38 | return true; 39 | } 40 | }, function (self, parent) { 41 | if (parent.isIfStatement() && (self.key === 'consequent' || self.key === 'alternate') || parent.isLoop() && self.key === 'body') { 42 | self.replaceWith({ 43 | type: 'BlockStatement', 44 | body: [] 45 | }); 46 | return true; 47 | } 48 | }]; -------------------------------------------------------------------------------- /lib/path/removal.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _getIterator2 = require("babel-runtime/core-js/get-iterator"); 6 | 7 | var _getIterator3 = _interopRequireDefault(_getIterator2); 8 | 9 | exports.remove = remove; 10 | exports._callRemovalHooks = _callRemovalHooks; 11 | exports._remove = _remove; 12 | exports._markRemoved = _markRemoved; 13 | exports._assertUnremoved = _assertUnremoved; 14 | 15 | var _removalHooks = require("./lib/removal-hooks"); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | function remove() { 20 | this._assertUnremoved(); 21 | 22 | this.resync(); 23 | 24 | if (this._callRemovalHooks()) { 25 | this._markRemoved(); 26 | return; 27 | } 28 | 29 | this.shareCommentsWithSiblings(); 30 | this._remove(); 31 | this._markRemoved(); 32 | } 33 | 34 | function _callRemovalHooks() { 35 | for (var _iterator = _removalHooks.hooks, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3.default)(_iterator);;) { 36 | var _ref; 37 | 38 | if (_isArray) { 39 | if (_i >= _iterator.length) break; 40 | _ref = _iterator[_i++]; 41 | } else { 42 | _i = _iterator.next(); 43 | if (_i.done) break; 44 | _ref = _i.value; 45 | } 46 | 47 | var fn = _ref; 48 | 49 | if (fn(this, this.parentPath)) return true; 50 | } 51 | } 52 | 53 | function _remove() { 54 | if (Array.isArray(this.container)) { 55 | this.container.splice(this.key, 1); 56 | this.updateSiblingKeys(this.key, -1); 57 | } else { 58 | this._replaceWith(null); 59 | } 60 | } 61 | 62 | function _markRemoved() { 63 | this.shouldSkip = true; 64 | this.removed = true; 65 | this.node = null; 66 | } 67 | 68 | function _assertUnremoved() { 69 | if (this.removed) { 70 | throw this.buildCodeFrameError("NodePath has been removed so is read-only."); 71 | } 72 | } -------------------------------------------------------------------------------- /test/inference.js: -------------------------------------------------------------------------------- 1 | var traverse = require("../lib").default; 2 | var assert = require("assert"); 3 | var parse = require("babylon").parse; 4 | 5 | function getPath(code) { 6 | var ast = parse(code); 7 | var path; 8 | traverse(ast, { 9 | Program: function (_path) { 10 | path = _path; 11 | _path.stop(); 12 | } 13 | }); 14 | return path; 15 | } 16 | 17 | suite("inference", function () { 18 | suite("baseTypeStrictlyMatches", function () { 19 | test("it should work with null", function () { 20 | var path = getPath("var x = null; x === null").get("body")[1].get("expression"); 21 | var left = path.get("left"); 22 | var right = path.get("right"); 23 | var strictMatch = left.baseTypeStrictlyMatches(right); 24 | 25 | assert.ok(strictMatch, "null should be equal to null"); 26 | }); 27 | 28 | test("it should work with numbers", function () { 29 | var path = getPath("var x = 1; x === 2").get("body")[1].get("expression"); 30 | var left = path.get("left"); 31 | var right = path.get("right"); 32 | var strictMatch = left.baseTypeStrictlyMatches(right); 33 | 34 | assert.ok(strictMatch, "null should be equal to null"); 35 | }); 36 | 37 | test("it should bail when type changes", function () { 38 | var path = getPath("var x = 1; if (foo) x = null;else x = 3; x === 2").get("body")[2].get("expression"); 39 | var left = path.get("left"); 40 | var right = path.get("right"); 41 | 42 | var strictMatch = left.baseTypeStrictlyMatches(right); 43 | 44 | assert.ok(!strictMatch, "type might change in if statement"); 45 | }); 46 | 47 | test("it should differentiate between null and undefined", function () { 48 | var path = getPath("var x; x === null").get("body")[1].get("expression"); 49 | var left = path.get("left"); 50 | var right = path.get("right"); 51 | var strictMatch = left.baseTypeStrictlyMatches(right); 52 | 53 | assert.ok(!strictMatch, "null should not match undefined"); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /lib/scope/binding.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); 6 | 7 | var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); 8 | 9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 10 | 11 | var Binding = function () { 12 | function Binding(_ref) { 13 | var existing = _ref.existing; 14 | var identifier = _ref.identifier; 15 | var scope = _ref.scope; 16 | var path = _ref.path; 17 | var kind = _ref.kind; 18 | (0, _classCallCheck3.default)(this, Binding); 19 | 20 | this.identifier = identifier; 21 | this.scope = scope; 22 | this.path = path; 23 | this.kind = kind; 24 | 25 | this.constantViolations = []; 26 | this.constant = true; 27 | 28 | this.referencePaths = []; 29 | this.referenced = false; 30 | this.references = 0; 31 | 32 | this.clearValue(); 33 | 34 | if (existing) { 35 | this.constantViolations = [].concat(existing.path, existing.constantViolations, this.constantViolations); 36 | } 37 | } 38 | 39 | Binding.prototype.deoptValue = function deoptValue() { 40 | this.clearValue(); 41 | this.hasDeoptedValue = true; 42 | }; 43 | 44 | Binding.prototype.setValue = function setValue(value) { 45 | if (this.hasDeoptedValue) return; 46 | this.hasValue = true; 47 | this.value = value; 48 | }; 49 | 50 | Binding.prototype.clearValue = function clearValue() { 51 | this.hasDeoptedValue = false; 52 | this.hasValue = false; 53 | this.value = null; 54 | }; 55 | 56 | Binding.prototype.reassign = function reassign(path) { 57 | this.constant = false; 58 | if (this.constantViolations.indexOf(path) !== -1) { 59 | return; 60 | } 61 | this.constantViolations.push(path); 62 | }; 63 | 64 | Binding.prototype.reference = function reference(path) { 65 | if (this.referencePaths.indexOf(path) !== -1) { 66 | return; 67 | } 68 | this.referenced = true; 69 | this.references++; 70 | this.referencePaths.push(path); 71 | }; 72 | 73 | Binding.prototype.dereference = function dereference() { 74 | this.references--; 75 | this.referenced = !!this.references; 76 | }; 77 | 78 | return Binding; 79 | }(); 80 | 81 | exports.default = Binding; 82 | module.exports = exports["default"]; -------------------------------------------------------------------------------- /src/scope/binding.js: -------------------------------------------------------------------------------- 1 | import type NodePath from "../path"; 2 | 3 | /** 4 | * This class is responsible for a binding inside of a scope. 5 | * 6 | * It tracks the following: 7 | * 8 | * * Node path. 9 | * * Amount of times referenced by other nodes. 10 | * * Paths to nodes that reassign or modify this binding. 11 | * * The kind of binding. (Is it a parameter, declaration etc) 12 | */ 13 | 14 | export default class Binding { 15 | constructor({ existing, identifier, scope, path, kind }) { 16 | this.identifier = identifier; 17 | this.scope = scope; 18 | this.path = path; 19 | this.kind = kind; 20 | 21 | this.constantViolations = []; 22 | this.constant = true; 23 | 24 | this.referencePaths = []; 25 | this.referenced = false; 26 | this.references = 0; 27 | 28 | this.clearValue(); 29 | 30 | if (existing) { 31 | this.constantViolations = [].concat( 32 | existing.path, 33 | existing.constantViolations, 34 | this.constantViolations 35 | ); 36 | } 37 | } 38 | 39 | 40 | constantViolations: Array; 41 | constant: boolean; 42 | 43 | referencePaths: Array; 44 | referenced: boolean; 45 | references: number; 46 | 47 | hasDeoptedValue: boolean; 48 | hasValue: boolean; 49 | value: any; 50 | 51 | deoptValue() { 52 | this.clearValue(); 53 | this.hasDeoptedValue = true; 54 | } 55 | 56 | setValue(value: any) { 57 | if (this.hasDeoptedValue) return; 58 | this.hasValue = true; 59 | this.value = value; 60 | } 61 | 62 | clearValue() { 63 | this.hasDeoptedValue = false; 64 | this.hasValue = false; 65 | this.value = null; 66 | } 67 | 68 | /** 69 | * Register a constant violation with the provided `path`. 70 | */ 71 | 72 | reassign(path: Object) { 73 | this.constant = false; 74 | if (this.constantViolations.indexOf(path) !== -1) { 75 | return; 76 | } 77 | this.constantViolations.push(path); 78 | } 79 | 80 | /** 81 | * Increment the amount of references to this binding. 82 | */ 83 | 84 | reference(path: NodePath) { 85 | if (this.referencePaths.indexOf(path) !== -1) { 86 | return; 87 | } 88 | this.referenced = true; 89 | this.references++; 90 | this.referencePaths.push(path); 91 | } 92 | 93 | /** 94 | * Decrement the amount of references to this binding. 95 | */ 96 | 97 | dereference() { 98 | this.references--; 99 | this.referenced = !!this.references; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test/evaluation.js: -------------------------------------------------------------------------------- 1 | var traverse = require("../lib").default; 2 | var assert = require("assert"); 3 | var parse = require("babylon").parse; 4 | 5 | function getPath(code) { 6 | var ast = parse(code); 7 | var path; 8 | traverse(ast, { 9 | Program: function (_path) { 10 | path = _path; 11 | _path.stop(); 12 | } 13 | }); 14 | return path; 15 | } 16 | 17 | suite("evaluation", function () { 18 | suite("evaluateTruthy", function () { 19 | test("it should work with null", function () { 20 | assert.strictEqual( 21 | getPath("false || a.length === 0;").get("body")[0].evaluateTruthy(), 22 | undefined 23 | ); 24 | }); 25 | 26 | test("it should not mistake lack of confidence for falsy", function () { 27 | assert.strictEqual( 28 | getPath("foo || 'bar'").get("body")[0].evaluate().value, 29 | undefined 30 | ); 31 | }); 32 | }); 33 | 34 | test("should bail out on recursive evaluation", function () { 35 | assert.strictEqual( 36 | getPath("function fn(a) { var g = a ? 1 : 2, a = g * this.foo; }").get("body.0.body.body.0.declarations.1.init").evaluate().confident, 37 | false 38 | ); 39 | }); 40 | 41 | test("should work with repeated, indeterminate identifiers", function () { 42 | assert.strictEqual( 43 | getPath("var num = foo(); (num > 0 && num < 100);").get("body")[1].evaluateTruthy(), 44 | undefined 45 | ); 46 | }); 47 | 48 | test("should work with repeated, determinate identifiers", function () { 49 | assert.strictEqual( 50 | getPath("var num = 5; (num > 0 && num < 100);").get("body")[1].evaluateTruthy(), 51 | true 52 | ); 53 | }); 54 | 55 | test("should deopt when var is redeclared in the same scope", function () { 56 | assert.strictEqual( 57 | getPath("var x = 2; var y = x + 2; { var x = 3 }").get("body.1.declarations.0.init").evaluate().confident, 58 | false 59 | ); 60 | }); 61 | 62 | test("it should not deopt vars in different scope", function () { 63 | const input = "var a = 5; function x() { var a = 5; var b = a + 1; } var b = a + 2"; 64 | assert.strictEqual( 65 | getPath(input).get("body.1.body.body.1.declarations.0.init").evaluate().value, 66 | 6 67 | ); 68 | assert.strictEqual( 69 | getPath(input).get("body.2.declarations.0.init").evaluate().value, 70 | 7 71 | ); 72 | }); 73 | 74 | test("it should not deopt let/const inside blocks", function () { 75 | assert.strictEqual( 76 | getPath("let x = 5; { let x = 1; } let y = x + 5").get("body.2.declarations.0.init").evaluate().value, 77 | 10 78 | ); 79 | const constExample = "const d = true; if (d && true || false) { const d = false; d && 5; }"; 80 | assert.strictEqual( 81 | getPath(constExample).get("body.1.test").evaluate().value, 82 | true 83 | ); 84 | assert.strictEqual( 85 | getPath(constExample).get("body.1.consequent.body.1").evaluate().value, 86 | false 87 | ); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /src/path/lib/removal-hooks.js: -------------------------------------------------------------------------------- 1 | // this file contains hooks that handle ancestry cleanup of parent nodes when removing children 2 | 3 | /** 4 | * Pre hooks should be used for either rejecting removal or delegating removal 5 | */ 6 | 7 | export let hooks = [ 8 | function (self, parent) { 9 | if (self.key === "body" && parent.isArrowFunctionExpression()) { 10 | self.replaceWith(self.scope.buildUndefinedNode()); 11 | return true; 12 | } 13 | }, 14 | 15 | function (self, parent) { 16 | let removeParent = false; 17 | 18 | // while (NODE); 19 | // removing the test of a while/switch, we can either just remove it entirely *or* turn the `test` into `true` 20 | // unlikely that the latter will ever be what's wanted so we just remove the loop to avoid infinite recursion 21 | removeParent = removeParent || (self.key === "test" && (parent.isWhile() || parent.isSwitchCase())); 22 | 23 | // export NODE; 24 | // just remove a declaration for an export as this is no longer valid 25 | removeParent = removeParent || (self.key === "declaration" && parent.isExportDeclaration()); 26 | 27 | // label: NODE 28 | // stray labeled statement with no body 29 | removeParent = removeParent || (self.key === "body" && parent.isLabeledStatement()); 30 | 31 | // let NODE; 32 | // remove an entire declaration if there are no declarators left 33 | removeParent = removeParent || (self.listKey === "declarations" && parent.isVariableDeclaration() && parent.node.declarations.length === 1); 34 | 35 | // NODE; 36 | // remove the entire expression statement if there's no expression 37 | removeParent = removeParent || (self.key === "expression" && parent.isExpressionStatement()); 38 | 39 | if (removeParent) { 40 | parent.remove(); 41 | return true; 42 | } 43 | }, 44 | 45 | function (self, parent) { 46 | if (parent.isSequenceExpression() && parent.node.expressions.length === 1) { 47 | // (node, NODE); 48 | // we've just removed the second element of a sequence expression so let's turn that sequence 49 | // expression into a regular expression 50 | parent.replaceWith(parent.node.expressions[0]); 51 | return true; 52 | } 53 | }, 54 | 55 | function (self, parent) { 56 | if (parent.isBinary()) { 57 | // left + NODE; 58 | // NODE + right; 59 | // we're in a binary expression, better remove it and replace it with the last expression 60 | if (self.key === "left") { 61 | parent.replaceWith(parent.node.right); 62 | } else { // key === "right" 63 | parent.replaceWith(parent.node.left); 64 | } 65 | return true; 66 | } 67 | }, 68 | 69 | function (self, parent) { 70 | if ( 71 | (parent.isIfStatement() && (self.key === 'consequent' || self.key === 'alternate')) || 72 | (parent.isLoop() && self.key === 'body') 73 | ) { 74 | self.replaceWith({ 75 | type: 'BlockStatement', 76 | body: [] 77 | }); 78 | return true; 79 | } 80 | } 81 | ]; 82 | -------------------------------------------------------------------------------- /src/path/lib/virtual-types.js: -------------------------------------------------------------------------------- 1 | import type NodePath from "../index"; 2 | import { react } from "babel-types"; 3 | import * as t from "babel-types"; 4 | 5 | export let ReferencedIdentifier = { 6 | types: ["Identifier", "JSXIdentifier"], 7 | checkPath({ node, parent }: NodePath, opts?: Object): boolean { 8 | if (!t.isIdentifier(node, opts)) { 9 | if (t.isJSXIdentifier(node, opts)) { 10 | if (react.isCompatTag(node.name)) return false; 11 | } else { 12 | // not a JSXIdentifier or an Identifier 13 | return false; 14 | } 15 | } 16 | 17 | // check if node is referenced 18 | return t.isReferenced(node, parent); 19 | } 20 | }; 21 | 22 | export let ReferencedMemberExpression = { 23 | types: ["MemberExpression"], 24 | checkPath({ node, parent }) { 25 | return t.isMemberExpression(node) && t.isReferenced(node, parent); 26 | } 27 | }; 28 | 29 | export let BindingIdentifier = { 30 | types: ["Identifier"], 31 | checkPath({ node, parent }: NodePath): boolean { 32 | return t.isIdentifier(node) && t.isBinding(node, parent); 33 | } 34 | }; 35 | 36 | export let Statement = { 37 | types: ["Statement"], 38 | checkPath({ node, parent }: NodePath): boolean { 39 | if (t.isStatement(node)) { 40 | if (t.isVariableDeclaration(node)) { 41 | if (t.isForXStatement(parent, { left: node })) return false; 42 | if (t.isForStatement(parent, { init: node })) return false; 43 | } 44 | 45 | return true; 46 | } else { 47 | return false; 48 | } 49 | } 50 | }; 51 | 52 | export let Expression = { 53 | types: ["Expression"], 54 | checkPath(path: NodePath): boolean { 55 | if (path.isIdentifier()) { 56 | return path.isReferencedIdentifier(); 57 | } else { 58 | return t.isExpression(path.node); 59 | } 60 | } 61 | }; 62 | 63 | export let Scope = { 64 | types: ["Scopable"], 65 | checkPath(path) { 66 | return t.isScope(path.node, path.parent); 67 | } 68 | }; 69 | 70 | export let Referenced = { 71 | checkPath(path: NodePath): boolean { 72 | return t.isReferenced(path.node, path.parent); 73 | } 74 | }; 75 | 76 | export let BlockScoped = { 77 | checkPath(path: NodePath): boolean { 78 | return t.isBlockScoped(path.node); 79 | } 80 | }; 81 | 82 | export let Var = { 83 | types: ["VariableDeclaration"], 84 | checkPath(path: NodePath): boolean { 85 | return t.isVar(path.node); 86 | } 87 | }; 88 | 89 | export let User = { 90 | checkPath(path: NodePath): boolean { 91 | return path.node && !!path.node.loc; 92 | } 93 | }; 94 | 95 | export let Generated = { 96 | checkPath(path: NodePath): boolean { 97 | return !path.isUser(); 98 | } 99 | }; 100 | 101 | export let Pure = { 102 | checkPath(path: NodePath, opts?): boolean { 103 | return path.scope.isPure(path.node, opts); 104 | } 105 | }; 106 | 107 | export let Flow = { 108 | types: ["Flow", "ImportDeclaration", "ExportDeclaration"], 109 | checkPath({ node }: NodePath): boolean { 110 | if (t.isFlow(node)) { 111 | return true; 112 | } else if (t.isImportDeclaration(node)) { 113 | return node.importKind === "type" || node.importKind === "typeof"; 114 | } else if (t.isExportDeclaration(node)) { 115 | return node.exportKind === "type"; 116 | } else { 117 | return false; 118 | } 119 | } 120 | }; 121 | -------------------------------------------------------------------------------- /src/path/inference/index.js: -------------------------------------------------------------------------------- 1 | import type NodePath from "./index"; 2 | import * as inferers from "./inferers"; 3 | import * as t from "babel-types"; 4 | 5 | /** 6 | * Infer the type of the current `NodePath`. 7 | */ 8 | 9 | export function getTypeAnnotation(): Object { 10 | if (this.typeAnnotation) return this.typeAnnotation; 11 | 12 | let type = this._getTypeAnnotation() || t.anyTypeAnnotation(); 13 | if (t.isTypeAnnotation(type)) type = type.typeAnnotation; 14 | return this.typeAnnotation = type; 15 | } 16 | 17 | /** 18 | * todo: split up this method 19 | */ 20 | 21 | export function _getTypeAnnotation(): ?Object { 22 | let node = this.node; 23 | 24 | if (!node) { 25 | // handle initializerless variables, add in checks for loop initializers too 26 | if (this.key === "init" && this.parentPath.isVariableDeclarator()) { 27 | let declar = this.parentPath.parentPath; 28 | let declarParent = declar.parentPath; 29 | 30 | // for (let NODE in bar) {} 31 | if (declar.key === "left" && declarParent.isForInStatement()) { 32 | return t.stringTypeAnnotation(); 33 | } 34 | 35 | // for (let NODE of bar) {} 36 | if (declar.key === "left" && declarParent.isForOfStatement()) { 37 | return t.anyTypeAnnotation(); 38 | } 39 | 40 | return t.voidTypeAnnotation(); 41 | } else { 42 | return; 43 | } 44 | } 45 | 46 | if (node.typeAnnotation) { 47 | return node.typeAnnotation; 48 | } 49 | 50 | let inferer = inferers[node.type]; 51 | if (inferer) { 52 | return inferer.call(this, node); 53 | } 54 | 55 | inferer = inferers[this.parentPath.type]; 56 | if (inferer && inferer.validParent) { 57 | return this.parentPath.getTypeAnnotation(); 58 | } 59 | } 60 | 61 | export function isBaseType(baseName: string, soft?: boolean): boolean { 62 | return _isBaseType(baseName, this.getTypeAnnotation(), soft); 63 | } 64 | 65 | function _isBaseType(baseName: string, type?, soft?): boolean { 66 | if (baseName === "string") { 67 | return t.isStringTypeAnnotation(type); 68 | } else if (baseName === "number") { 69 | return t.isNumberTypeAnnotation(type); 70 | } else if (baseName === "boolean") { 71 | return t.isBooleanTypeAnnotation(type); 72 | } else if (baseName === "any") { 73 | return t.isAnyTypeAnnotation(type); 74 | } else if (baseName === "mixed") { 75 | return t.isMixedTypeAnnotation(type); 76 | } else if (baseName === "void") { 77 | return t.isVoidTypeAnnotation(type); 78 | } else { 79 | if (soft) { 80 | return false; 81 | } else { 82 | throw new Error(`Unknown base type ${baseName}`); 83 | } 84 | } 85 | } 86 | 87 | export function couldBeBaseType(name: string): boolean { 88 | let type = this.getTypeAnnotation(); 89 | if (t.isAnyTypeAnnotation(type)) return true; 90 | 91 | if (t.isUnionTypeAnnotation(type)) { 92 | for (let type2 of (type.types: Array)) { 93 | if (t.isAnyTypeAnnotation(type2) || _isBaseType(name, type2, true)) { 94 | return true; 95 | } 96 | } 97 | return false; 98 | } else { 99 | return _isBaseType(name, type, true); 100 | } 101 | } 102 | 103 | export function baseTypeStrictlyMatches(right: NodePath) { 104 | let left = this.getTypeAnnotation(); 105 | right = right.getTypeAnnotation(); 106 | 107 | if (!t.isAnyTypeAnnotation(left) && t.isFlowBaseAnnotation(left)) { 108 | return right.type === left.type; 109 | } 110 | } 111 | 112 | export function isGenericType(genericName: string): boolean { 113 | let type = this.getTypeAnnotation(); 114 | return t.isGenericTypeAnnotation(type) && t.isIdentifier(type.id, { name: genericName }); 115 | } 116 | -------------------------------------------------------------------------------- /src/path/family.js: -------------------------------------------------------------------------------- 1 | // This file contains methods responsible for dealing with/retrieving children or siblings. 2 | 3 | import type TraversalContext from "../index"; 4 | import NodePath from "./index"; 5 | import * as t from "babel-types"; 6 | 7 | export function getStatementParent(): ?NodePath { 8 | let path = this; 9 | 10 | do { 11 | if (!path.parentPath || (Array.isArray(path.container) && path.isStatement())) { 12 | break; 13 | } else { 14 | path = path.parentPath; 15 | } 16 | } while (path); 17 | 18 | if (path && (path.isProgram() || path.isFile())) { 19 | throw new Error("File/Program node, we can't possibly find a statement parent to this"); 20 | } 21 | 22 | return path; 23 | } 24 | 25 | export function getOpposite() { 26 | if (this.key === "left") { 27 | return this.getSibling("right"); 28 | } else if (this.key === "right") { 29 | return this.getSibling("left"); 30 | } 31 | } 32 | 33 | export function getCompletionRecords(): Array { 34 | let paths = []; 35 | 36 | let add = function (path) { 37 | if (path) paths = paths.concat(path.getCompletionRecords()); 38 | }; 39 | 40 | if (this.isIfStatement()) { 41 | add(this.get("consequent")); 42 | add(this.get("alternate")); 43 | } else if (this.isDoExpression() || this.isFor() || this.isWhile()) { 44 | add(this.get("body")); 45 | } else if (this.isProgram() || this.isBlockStatement()) { 46 | add(this.get("body").pop()); 47 | } else if (this.isFunction()) { 48 | return this.get("body").getCompletionRecords(); 49 | } else if (this.isTryStatement()) { 50 | add(this.get("block")); 51 | add(this.get("handler")); 52 | add(this.get("finalizer")); 53 | } else { 54 | paths.push(this); 55 | } 56 | 57 | return paths; 58 | } 59 | 60 | export function getSibling(key) { 61 | return NodePath.get({ 62 | parentPath: this.parentPath, 63 | parent: this.parent, 64 | container: this.container, 65 | listKey: this.listKey, 66 | key: key 67 | }); 68 | } 69 | 70 | export function get(key: string, context?: boolean | TraversalContext): NodePath { 71 | if (context === true) context = this.context; 72 | let parts = key.split("."); 73 | if (parts.length === 1) { // "foo" 74 | return this._getKey(key, context); 75 | } else { // "foo.bar" 76 | return this._getPattern(parts, context); 77 | } 78 | } 79 | 80 | export function _getKey(key, context?) { 81 | let node = this.node; 82 | let container = node[key]; 83 | 84 | if (Array.isArray(container)) { 85 | // requested a container so give them all the paths 86 | return container.map((_, i) => { 87 | return NodePath.get({ 88 | listKey: key, 89 | parentPath: this, 90 | parent: node, 91 | container: container, 92 | key: i 93 | }).setContext(context); 94 | }); 95 | } else { 96 | return NodePath.get({ 97 | parentPath: this, 98 | parent: node, 99 | container: node, 100 | key: key 101 | }).setContext(context); 102 | } 103 | } 104 | 105 | export function _getPattern(parts, context) { 106 | let path = this; 107 | for (let part of (parts: Array)) { 108 | if (part === ".") { 109 | path = path.parentPath; 110 | } else { 111 | if (Array.isArray(path)) { 112 | path = path[part]; 113 | } else { 114 | path = path.get(part, context); 115 | } 116 | } 117 | } 118 | return path; 119 | } 120 | 121 | export function getBindingIdentifiers(duplicates?) { 122 | return t.getBindingIdentifiers(this.node, duplicates); 123 | } 124 | 125 | export function getOuterBindingIdentifiers(duplicates?) { 126 | return t.getOuterBindingIdentifiers(this.node, duplicates); 127 | } 128 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* eslint max-len: 0 */ 2 | 3 | import TraversalContext from "./context"; 4 | import * as visitors from "./visitors"; 5 | import * as messages from "babel-messages"; 6 | import includes from "lodash/includes"; 7 | import * as t from "babel-types"; 8 | import * as cache from "./cache"; 9 | 10 | export { default as NodePath } from "./path"; 11 | export { default as Scope } from "./scope"; 12 | export { default as Hub } from "./hub"; 13 | export { visitors }; 14 | 15 | export default function traverse( 16 | parent: Object | Array, 17 | opts?: Object, 18 | scope?: Object, 19 | state: Object, 20 | parentPath: Object, 21 | ) { 22 | if (!parent) return; 23 | if (!opts) opts = {}; 24 | 25 | if (!opts.noScope && !scope) { 26 | if (parent.type !== "Program" && parent.type !== "File") { 27 | throw new Error(messages.get("traverseNeedsParent", parent.type)); 28 | } 29 | } 30 | 31 | visitors.explode(opts); 32 | 33 | traverse.node(parent, opts, scope, state, parentPath); 34 | } 35 | 36 | traverse.visitors = visitors; 37 | traverse.verify = visitors.verify; 38 | traverse.explode = visitors.explode; 39 | 40 | traverse.NodePath = require("./path"); 41 | traverse.Scope = require("./scope"); 42 | traverse.Hub = require("./hub"); 43 | 44 | traverse.cheap = function (node, enter) { 45 | if (!node) return; 46 | 47 | let keys = t.VISITOR_KEYS[node.type]; 48 | if (!keys) return; 49 | 50 | enter(node); 51 | 52 | for (let key of keys) { 53 | let subNode = node[key]; 54 | 55 | if (Array.isArray(subNode)) { 56 | for (let node of subNode) { 57 | traverse.cheap(node, enter); 58 | } 59 | } else { 60 | traverse.cheap(subNode, enter); 61 | } 62 | } 63 | }; 64 | 65 | traverse.node = function (node: Object, opts: Object, scope: Object, state: Object, parentPath: Object, skipKeys?) { 66 | let keys: Array = t.VISITOR_KEYS[node.type]; 67 | if (!keys) return; 68 | 69 | let context = new TraversalContext(scope, opts, state, parentPath); 70 | for (let key of keys) { 71 | if (skipKeys && skipKeys[key]) continue; 72 | if (context.visit(node, key)) return; 73 | } 74 | }; 75 | 76 | const CLEAR_KEYS: Array = t.COMMENT_KEYS.concat([ 77 | "tokens", "comments", 78 | "start", "end", "loc", 79 | "raw", "rawValue" 80 | ]); 81 | 82 | traverse.clearNode = function (node) { 83 | for (let key of CLEAR_KEYS) { 84 | if (node[key] != null) node[key] = undefined; 85 | } 86 | 87 | for (let key in node) { 88 | if (key[0] === "_" && node[key] != null) node[key] = undefined; 89 | } 90 | 91 | cache.path.delete(node); 92 | 93 | let syms: Array = Object.getOwnPropertySymbols(node); 94 | for (let sym of syms) { 95 | node[sym] = null; 96 | } 97 | }; 98 | 99 | traverse.removeProperties = function (tree) { 100 | traverse.cheap(tree, traverse.clearNode); 101 | return tree; 102 | }; 103 | 104 | function hasBlacklistedType(path, state) { 105 | if (path.node.type === state.type) { 106 | state.has = true; 107 | path.stop(); 108 | } 109 | } 110 | 111 | traverse.hasType = function (tree: Object, scope: Object, type: Object, blacklistTypes: Array): boolean { 112 | // the node we're searching in is blacklisted 113 | if (includes(blacklistTypes, tree.type)) return false; 114 | 115 | // the type we're looking for is the same as the passed node 116 | if (tree.type === type) return true; 117 | 118 | let state = { 119 | has: false, 120 | type: type 121 | }; 122 | 123 | traverse(tree, { 124 | blacklist: blacklistTypes, 125 | enter: hasBlacklistedType 126 | }, scope, state); 127 | 128 | return state.has; 129 | }; 130 | 131 | traverse.clearCache = function() { 132 | cache.clear(); 133 | }; 134 | 135 | traverse.copyCache = function(source, destination) { 136 | if (cache.path.has(source)) { 137 | cache.path.set(destination, cache.path.get(source)); 138 | } 139 | }; 140 | -------------------------------------------------------------------------------- /test/traverse.js: -------------------------------------------------------------------------------- 1 | var traverse = require("../lib").default; 2 | var assert = require("assert"); 3 | var _ = require("lodash"); 4 | 5 | suite("traverse", function () { 6 | var ast = { 7 | type: "Program", 8 | body: [ 9 | { 10 | "type": "VariableDeclaration", 11 | "declarations": [ 12 | { 13 | "type": "VariableDeclarator", 14 | "id": { 15 | "type": "Identifier", 16 | "name": "foo", 17 | }, 18 | "init": { 19 | "type": "StringLiteral", 20 | "value": "bar", 21 | "raw": "\'bar\'" 22 | } 23 | } 24 | ], 25 | "kind": "var" 26 | }, 27 | { 28 | "type": "ExpressionStatement", 29 | "expression": { 30 | "type": "AssignmentExpression", 31 | "operator": "=", 32 | "left": { 33 | "type": "MemberExpression", 34 | "computed": false, 35 | "object": { 36 | "type": "ThisExpression" 37 | }, 38 | "property": { 39 | "type": "Identifier", 40 | "name": "test" 41 | } 42 | }, 43 | "right": { 44 | "type": "StringLiteral", 45 | "value": "wow", 46 | "raw": "\'wow\'" 47 | } 48 | } 49 | } 50 | ] 51 | }; 52 | 53 | var body = ast.body; 54 | 55 | test("traverse replace", function () { 56 | var replacement = { 57 | type: "StringLiteral", 58 | value: "foo" 59 | }; 60 | var ast2 = _.cloneDeep(ast); 61 | 62 | traverse(ast2, { 63 | enter: function (path) { 64 | if (path.node.type === "ThisExpression") path.replaceWith(replacement); 65 | } 66 | }); 67 | 68 | assert.equal(ast2.body[1].expression.left.object, replacement); 69 | }); 70 | 71 | test("traverse", function () { 72 | var expect = [ 73 | body[0], body[0].declarations[0], body[0].declarations[0].id, body[0].declarations[0].init, 74 | body[1], body[1].expression, body[1].expression.left, body[1].expression.left.object, body[1].expression.left.property, body[1].expression.right 75 | ]; 76 | 77 | var actual = []; 78 | 79 | traverse(ast, { 80 | enter: function (path) { 81 | actual.push(path.node); 82 | } 83 | }); 84 | 85 | assert.deepEqual(actual, expect); 86 | }); 87 | 88 | test("traverse falsy parent", function () { 89 | traverse(null, { 90 | enter: function () { 91 | throw new Error("should not be ran"); 92 | } 93 | }); 94 | }); 95 | 96 | test("traverse blacklistTypes", function () { 97 | var expect = [ 98 | body[0], body[0].declarations[0], body[0].declarations[0].id, body[0].declarations[0].init, 99 | body[1], body[1].expression, body[1].expression.right 100 | ]; 101 | 102 | var actual = []; 103 | 104 | traverse(ast, { 105 | blacklist: ["MemberExpression"], 106 | enter: function (path) { 107 | actual.push(path.node); 108 | } 109 | }); 110 | 111 | assert.deepEqual(actual, expect); 112 | }); 113 | 114 | test("hasType", function () { 115 | assert.ok(traverse.hasType(ast, null, "ThisExpression")); 116 | assert.ok(!traverse.hasType(ast, null, "ThisExpression", ["AssignmentExpression"])); 117 | 118 | assert.ok(traverse.hasType(ast, null, "ThisExpression")); 119 | assert.ok(traverse.hasType(ast, null, "Program")); 120 | 121 | assert.ok(!traverse.hasType(ast, null, "ThisExpression", ["MemberExpression"])); 122 | assert.ok(!traverse.hasType(ast, null, "ThisExpression", ["Program"])); 123 | 124 | assert.ok(!traverse.hasType(ast, null, "ArrowFunctionExpression")); 125 | }); 126 | 127 | test("clearCache", function () { 128 | var paths = []; 129 | traverse(ast, { 130 | enter: function (path) { 131 | paths.push(path); 132 | } 133 | }); 134 | 135 | traverse.clearCache(); 136 | 137 | var paths2 = []; 138 | traverse(ast, { 139 | enter: function (path) { 140 | paths2.push(path); 141 | } 142 | }); 143 | 144 | paths2.forEach(function (p, i) { 145 | assert.notStrictEqual(p, paths[i]); 146 | }); 147 | }); 148 | }); 149 | -------------------------------------------------------------------------------- /lib/path/lib/virtual-types.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | exports.Flow = exports.Pure = exports.Generated = exports.User = exports.Var = exports.BlockScoped = exports.Referenced = exports.Scope = exports.Expression = exports.Statement = exports.BindingIdentifier = exports.ReferencedMemberExpression = exports.ReferencedIdentifier = undefined; 5 | 6 | var _babelTypes = require("babel-types"); 7 | 8 | var t = _interopRequireWildcard(_babelTypes); 9 | 10 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 11 | 12 | var ReferencedIdentifier = exports.ReferencedIdentifier = { 13 | types: ["Identifier", "JSXIdentifier"], 14 | checkPath: function checkPath(_ref, opts) { 15 | var node = _ref.node; 16 | var parent = _ref.parent; 17 | 18 | if (!t.isIdentifier(node, opts)) { 19 | if (t.isJSXIdentifier(node, opts)) { 20 | if (_babelTypes.react.isCompatTag(node.name)) return false; 21 | } else { 22 | return false; 23 | } 24 | } 25 | 26 | return t.isReferenced(node, parent); 27 | } 28 | }; 29 | 30 | var ReferencedMemberExpression = exports.ReferencedMemberExpression = { 31 | types: ["MemberExpression"], 32 | checkPath: function checkPath(_ref2) { 33 | var node = _ref2.node; 34 | var parent = _ref2.parent; 35 | 36 | return t.isMemberExpression(node) && t.isReferenced(node, parent); 37 | } 38 | }; 39 | 40 | var BindingIdentifier = exports.BindingIdentifier = { 41 | types: ["Identifier"], 42 | checkPath: function checkPath(_ref3) { 43 | var node = _ref3.node; 44 | var parent = _ref3.parent; 45 | 46 | return t.isIdentifier(node) && t.isBinding(node, parent); 47 | } 48 | }; 49 | 50 | var Statement = exports.Statement = { 51 | types: ["Statement"], 52 | checkPath: function checkPath(_ref4) { 53 | var node = _ref4.node; 54 | var parent = _ref4.parent; 55 | 56 | if (t.isStatement(node)) { 57 | if (t.isVariableDeclaration(node)) { 58 | if (t.isForXStatement(parent, { left: node })) return false; 59 | if (t.isForStatement(parent, { init: node })) return false; 60 | } 61 | 62 | return true; 63 | } else { 64 | return false; 65 | } 66 | } 67 | }; 68 | 69 | var Expression = exports.Expression = { 70 | types: ["Expression"], 71 | checkPath: function checkPath(path) { 72 | if (path.isIdentifier()) { 73 | return path.isReferencedIdentifier(); 74 | } else { 75 | return t.isExpression(path.node); 76 | } 77 | } 78 | }; 79 | 80 | var Scope = exports.Scope = { 81 | types: ["Scopable"], 82 | checkPath: function checkPath(path) { 83 | return t.isScope(path.node, path.parent); 84 | } 85 | }; 86 | 87 | var Referenced = exports.Referenced = { 88 | checkPath: function checkPath(path) { 89 | return t.isReferenced(path.node, path.parent); 90 | } 91 | }; 92 | 93 | var BlockScoped = exports.BlockScoped = { 94 | checkPath: function checkPath(path) { 95 | return t.isBlockScoped(path.node); 96 | } 97 | }; 98 | 99 | var Var = exports.Var = { 100 | types: ["VariableDeclaration"], 101 | checkPath: function checkPath(path) { 102 | return t.isVar(path.node); 103 | } 104 | }; 105 | 106 | var User = exports.User = { 107 | checkPath: function checkPath(path) { 108 | return path.node && !!path.node.loc; 109 | } 110 | }; 111 | 112 | var Generated = exports.Generated = { 113 | checkPath: function checkPath(path) { 114 | return !path.isUser(); 115 | } 116 | }; 117 | 118 | var Pure = exports.Pure = { 119 | checkPath: function checkPath(path, opts) { 120 | return path.scope.isPure(path.node, opts); 121 | } 122 | }; 123 | 124 | var Flow = exports.Flow = { 125 | types: ["Flow", "ImportDeclaration", "ExportDeclaration"], 126 | checkPath: function checkPath(_ref5) { 127 | var node = _ref5.node; 128 | 129 | if (t.isFlow(node)) { 130 | return true; 131 | } else if (t.isImportDeclaration(node)) { 132 | return node.importKind === "type" || node.importKind === "typeof"; 133 | } else if (t.isExportDeclaration(node)) { 134 | return node.exportKind === "type"; 135 | } else { 136 | return false; 137 | } 138 | } 139 | }; -------------------------------------------------------------------------------- /src/context.js: -------------------------------------------------------------------------------- 1 | import NodePath from "./path"; 2 | import * as t from "babel-types"; 3 | 4 | let testing = process.env.NODE_ENV === "test"; 5 | 6 | export default class TraversalContext { 7 | constructor(scope, opts, state, parentPath) { 8 | this.parentPath = parentPath; 9 | this.scope = scope; 10 | this.state = state; 11 | this.opts = opts; 12 | } 13 | 14 | parentPath: NodePath; 15 | scope; 16 | state; 17 | opts; 18 | queue: ?Array = null; 19 | 20 | /** 21 | * This method does a simple check to determine whether or not we really need to attempt 22 | * visit a node. This will prevent us from constructing a NodePath. 23 | */ 24 | 25 | shouldVisit(node): boolean { 26 | let opts = this.opts; 27 | if (opts.enter || opts.exit) return true; 28 | 29 | // check if we have a visitor for this node 30 | if (opts[node.type]) return true; 31 | 32 | // check if we're going to traverse into this node 33 | let keys: ?Array = t.VISITOR_KEYS[node.type]; 34 | if (!keys || !keys.length) return false; 35 | 36 | // we need to traverse into this node so ensure that it has children to traverse into! 37 | for (let key of keys) { 38 | if (node[key]) return true; 39 | } 40 | 41 | return false; 42 | } 43 | 44 | create(node, obj, key, listKey): NodePath { 45 | return NodePath.get({ 46 | parentPath: this.parentPath, 47 | parent: node, 48 | container: obj, 49 | key: key, 50 | listKey 51 | }); 52 | } 53 | 54 | maybeQueue(path, notPriority?: boolean) { 55 | if (this.trap) { 56 | throw new Error("Infinite cycle detected"); 57 | } 58 | 59 | if (this.queue) { 60 | if (notPriority) { 61 | this.queue.push(path); 62 | } else { 63 | this.priorityQueue.push(path); 64 | } 65 | } 66 | } 67 | 68 | visitMultiple(container, parent, listKey) { 69 | // nothing to traverse! 70 | if (container.length === 0) return false; 71 | 72 | let queue = []; 73 | 74 | // build up initial queue 75 | for (let key = 0; key < container.length; key++) { 76 | let node = container[key]; 77 | if (node && this.shouldVisit(node)) { 78 | queue.push(this.create(parent, container, key, listKey)); 79 | } 80 | } 81 | 82 | return this.visitQueue(queue); 83 | } 84 | 85 | visitSingle(node, key): boolean { 86 | if (this.shouldVisit(node[key])) { 87 | return this.visitQueue([ 88 | this.create(node, node, key) 89 | ]); 90 | } else { 91 | return false; 92 | } 93 | } 94 | 95 | visitQueue(queue: Array) { 96 | // set queue 97 | this.queue = queue; 98 | this.priorityQueue = []; 99 | 100 | let visited = []; 101 | let stop = false; 102 | 103 | // visit the queue 104 | for (let path of queue) { 105 | path.resync(); 106 | 107 | if (path.contexts.length === 0 || path.contexts[path.contexts.length - 1] !== this) { 108 | // The context might already have been pushed when this path was inserted and queued. 109 | // If we always re-pushed here, we could get duplicates and risk leaving contexts 110 | // on the stack after the traversal has completed, which could break things. 111 | path.pushContext(this); 112 | } 113 | 114 | // this path no longer belongs to the tree 115 | if (path.key === null) continue; 116 | 117 | if (testing && queue.length >= 1000) { 118 | this.trap = true; 119 | } 120 | 121 | // ensure we don't visit the same node twice 122 | if (visited.indexOf(path.node) >= 0) continue; 123 | visited.push(path.node); 124 | 125 | if (path.visit()) { 126 | stop = true; 127 | break; 128 | } 129 | 130 | if (this.priorityQueue.length) { 131 | stop = this.visitQueue(this.priorityQueue); 132 | this.priorityQueue = []; 133 | this.queue = queue; 134 | if (stop) break; 135 | } 136 | } 137 | 138 | // clear queue 139 | for (let path of queue) { 140 | path.popContext(); 141 | } 142 | 143 | // clear queue 144 | this.queue = null; 145 | 146 | return stop; 147 | } 148 | 149 | visit(node, key) { 150 | let nodes = node[key]; 151 | if (!nodes) return false; 152 | 153 | if (Array.isArray(nodes)) { 154 | return this.visitMultiple(nodes, node, key); 155 | } else { 156 | return this.visitSingle(node, key); 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/scope/lib/renamer.js: -------------------------------------------------------------------------------- 1 | import Binding from "../binding"; 2 | import * as t from "babel-types"; 3 | 4 | let renameVisitor = { 5 | ReferencedIdentifier({ node }, state) { 6 | if (node.name === state.oldName) { 7 | node.name = state.newName; 8 | } 9 | }, 10 | 11 | Scope(path, state) { 12 | if (!path.scope.bindingIdentifierEquals(state.oldName, state.binding.identifier)) { 13 | path.skip(); 14 | } 15 | }, 16 | 17 | "AssignmentExpression|Declaration"(path, state) { 18 | let ids = path.getOuterBindingIdentifiers(); 19 | 20 | for (let name in ids) { 21 | if (name === state.oldName) ids[name].name = state.newName; 22 | } 23 | } 24 | }; 25 | 26 | export default class Renamer { 27 | constructor(binding: Binding, oldName: string, newName: string) { 28 | this.newName = newName; 29 | this.oldName = oldName; 30 | this.binding = binding; 31 | } 32 | 33 | oldName: string; 34 | newName: string; 35 | binding: Binding; 36 | 37 | maybeConvertFromExportDeclaration(parentDeclar) { 38 | let exportDeclar = parentDeclar.parentPath.isExportDeclaration() && parentDeclar.parentPath; 39 | if (!exportDeclar) return; 40 | 41 | // build specifiers that point back to this export declaration 42 | let isDefault = exportDeclar.isExportDefaultDeclaration(); 43 | 44 | if (isDefault && (parentDeclar.isFunctionDeclaration() || 45 | parentDeclar.isClassDeclaration())&& !parentDeclar.node.id) { 46 | // Ensure that default class and function exports have a name so they have a identifier to 47 | // reference from the export specifier list. 48 | parentDeclar.node.id = parentDeclar.scope.generateUidIdentifier("default"); 49 | } 50 | 51 | let bindingIdentifiers = parentDeclar.getOuterBindingIdentifiers(); 52 | let specifiers = []; 53 | 54 | for (let name in bindingIdentifiers) { 55 | let localName = name === this.oldName ? this.newName : name; 56 | let exportedName = isDefault ? "default" : name; 57 | specifiers.push(t.exportSpecifier(t.identifier(localName), t.identifier(exportedName))); 58 | } 59 | 60 | let aliasDeclar = t.exportNamedDeclaration(null, specifiers); 61 | 62 | // hoist to the top if it's a function 63 | if (parentDeclar.isFunctionDeclaration()) { 64 | aliasDeclar._blockHoist = 3; 65 | } 66 | 67 | exportDeclar.insertAfter(aliasDeclar); 68 | exportDeclar.replaceWith(parentDeclar.node); 69 | } 70 | 71 | maybeConvertFromClassFunctionDeclaration(path) { 72 | return; // TODO 73 | 74 | // retain the `name` of a class/function declaration 75 | 76 | if (!path.isFunctionDeclaration() && !path.isClassDeclaration()) return; 77 | if (this.binding.kind !== "hoisted") return; 78 | 79 | path.node.id = t.identifier(this.oldName); 80 | path.node._blockHoist = 3; 81 | 82 | path.replaceWith(t.variableDeclaration("let", [ 83 | t.variableDeclarator(t.identifier(this.newName), t.toExpression(path.node)) 84 | ])); 85 | } 86 | 87 | maybeConvertFromClassFunctionExpression(path) { 88 | return; // TODO 89 | 90 | // retain the `name` of a class/function expression 91 | 92 | if (!path.isFunctionExpression() && !path.isClassExpression()) return; 93 | if (this.binding.kind !== "local") return; 94 | 95 | path.node.id = t.identifier(this.oldName); 96 | 97 | this.binding.scope.parent.push({ 98 | id: t.identifier(this.newName) 99 | }); 100 | 101 | path.replaceWith(t.assignmentExpression("=", t.identifier(this.newName), path.node)); 102 | } 103 | 104 | rename(block?) { 105 | let { binding, oldName, newName } = this; 106 | let { scope, path } = binding; 107 | 108 | let parentDeclar = path.find((path) => path.isDeclaration() || path.isFunctionExpression()); 109 | if (parentDeclar) { 110 | this.maybeConvertFromExportDeclaration(parentDeclar); 111 | } 112 | 113 | scope.traverse(block || scope.block, renameVisitor, this); 114 | 115 | if (!block) { 116 | scope.removeOwnBinding(oldName); 117 | scope.bindings[newName] = binding; 118 | this.binding.identifier.name = newName; 119 | } 120 | 121 | if (binding.type === "hoisted") { 122 | // https://github.com/babel/babel/issues/2435 123 | // todo: hoist and convert function to a let 124 | } 125 | 126 | if (parentDeclar) { 127 | this.maybeConvertFromClassFunctionDeclaration(parentDeclar); 128 | this.maybeConvertFromClassFunctionExpression(parentDeclar); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lib/path/inference/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _getIterator2 = require("babel-runtime/core-js/get-iterator"); 6 | 7 | var _getIterator3 = _interopRequireDefault(_getIterator2); 8 | 9 | exports.getTypeAnnotation = getTypeAnnotation; 10 | exports._getTypeAnnotation = _getTypeAnnotation; 11 | exports.isBaseType = isBaseType; 12 | exports.couldBeBaseType = couldBeBaseType; 13 | exports.baseTypeStrictlyMatches = baseTypeStrictlyMatches; 14 | exports.isGenericType = isGenericType; 15 | 16 | var _inferers = require("./inferers"); 17 | 18 | var inferers = _interopRequireWildcard(_inferers); 19 | 20 | var _babelTypes = require("babel-types"); 21 | 22 | var t = _interopRequireWildcard(_babelTypes); 23 | 24 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 25 | 26 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 27 | 28 | function getTypeAnnotation() { 29 | if (this.typeAnnotation) return this.typeAnnotation; 30 | 31 | var type = this._getTypeAnnotation() || t.anyTypeAnnotation(); 32 | if (t.isTypeAnnotation(type)) type = type.typeAnnotation; 33 | return this.typeAnnotation = type; 34 | } 35 | 36 | function _getTypeAnnotation() { 37 | var node = this.node; 38 | 39 | if (!node) { 40 | if (this.key === "init" && this.parentPath.isVariableDeclarator()) { 41 | var declar = this.parentPath.parentPath; 42 | var declarParent = declar.parentPath; 43 | 44 | if (declar.key === "left" && declarParent.isForInStatement()) { 45 | return t.stringTypeAnnotation(); 46 | } 47 | 48 | if (declar.key === "left" && declarParent.isForOfStatement()) { 49 | return t.anyTypeAnnotation(); 50 | } 51 | 52 | return t.voidTypeAnnotation(); 53 | } else { 54 | return; 55 | } 56 | } 57 | 58 | if (node.typeAnnotation) { 59 | return node.typeAnnotation; 60 | } 61 | 62 | var inferer = inferers[node.type]; 63 | if (inferer) { 64 | return inferer.call(this, node); 65 | } 66 | 67 | inferer = inferers[this.parentPath.type]; 68 | if (inferer && inferer.validParent) { 69 | return this.parentPath.getTypeAnnotation(); 70 | } 71 | } 72 | 73 | function isBaseType(baseName, soft) { 74 | return _isBaseType(baseName, this.getTypeAnnotation(), soft); 75 | } 76 | 77 | function _isBaseType(baseName, type, soft) { 78 | if (baseName === "string") { 79 | return t.isStringTypeAnnotation(type); 80 | } else if (baseName === "number") { 81 | return t.isNumberTypeAnnotation(type); 82 | } else if (baseName === "boolean") { 83 | return t.isBooleanTypeAnnotation(type); 84 | } else if (baseName === "any") { 85 | return t.isAnyTypeAnnotation(type); 86 | } else if (baseName === "mixed") { 87 | return t.isMixedTypeAnnotation(type); 88 | } else if (baseName === "void") { 89 | return t.isVoidTypeAnnotation(type); 90 | } else { 91 | if (soft) { 92 | return false; 93 | } else { 94 | throw new Error("Unknown base type " + baseName); 95 | } 96 | } 97 | } 98 | 99 | function couldBeBaseType(name) { 100 | var type = this.getTypeAnnotation(); 101 | if (t.isAnyTypeAnnotation(type)) return true; 102 | 103 | if (t.isUnionTypeAnnotation(type)) { 104 | for (var _iterator = type.types, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3.default)(_iterator);;) { 105 | var _ref; 106 | 107 | if (_isArray) { 108 | if (_i >= _iterator.length) break; 109 | _ref = _iterator[_i++]; 110 | } else { 111 | _i = _iterator.next(); 112 | if (_i.done) break; 113 | _ref = _i.value; 114 | } 115 | 116 | var type2 = _ref; 117 | 118 | if (t.isAnyTypeAnnotation(type2) || _isBaseType(name, type2, true)) { 119 | return true; 120 | } 121 | } 122 | return false; 123 | } else { 124 | return _isBaseType(name, type, true); 125 | } 126 | } 127 | 128 | function baseTypeStrictlyMatches(right) { 129 | var left = this.getTypeAnnotation(); 130 | right = right.getTypeAnnotation(); 131 | 132 | if (!t.isAnyTypeAnnotation(left) && t.isFlowBaseAnnotation(left)) { 133 | return right.type === left.type; 134 | } 135 | } 136 | 137 | function isGenericType(genericName) { 138 | var type = this.getTypeAnnotation(); 139 | return t.isGenericTypeAnnotation(type) && t.isIdentifier(type.id, { name: genericName }); 140 | } -------------------------------------------------------------------------------- /src/path/inference/inferers.js: -------------------------------------------------------------------------------- 1 | import * as t from "babel-types"; 2 | 3 | export { default as Identifier } from "./inferer-reference"; 4 | 5 | export function VariableDeclarator() { 6 | let id = this.get("id"); 7 | 8 | if (id.isIdentifier()) { 9 | return this.get("init").getTypeAnnotation(); 10 | } else { 11 | return; 12 | } 13 | } 14 | 15 | export function TypeCastExpression(node) { 16 | return node.typeAnnotation; 17 | } 18 | 19 | TypeCastExpression.validParent = true; 20 | 21 | export function NewExpression(node) { 22 | if (this.get("callee").isIdentifier()) { 23 | // only resolve identifier callee 24 | return t.genericTypeAnnotation(node.callee); 25 | } 26 | } 27 | 28 | export function TemplateLiteral() { 29 | return t.stringTypeAnnotation(); 30 | } 31 | 32 | export function UnaryExpression(node) { 33 | let operator = node.operator; 34 | 35 | if (operator === "void") { 36 | return t.voidTypeAnnotation(); 37 | } else if (t.NUMBER_UNARY_OPERATORS.indexOf(operator) >= 0) { 38 | return t.numberTypeAnnotation(); 39 | } else if (t.STRING_UNARY_OPERATORS.indexOf(operator) >= 0) { 40 | return t.stringTypeAnnotation(); 41 | } else if (t.BOOLEAN_UNARY_OPERATORS.indexOf(operator) >= 0) { 42 | return t.booleanTypeAnnotation(); 43 | } 44 | } 45 | 46 | export function BinaryExpression(node) { 47 | let operator = node.operator; 48 | 49 | if (t.NUMBER_BINARY_OPERATORS.indexOf(operator) >= 0) { 50 | return t.numberTypeAnnotation(); 51 | } else if (t.BOOLEAN_BINARY_OPERATORS.indexOf(operator) >= 0) { 52 | return t.booleanTypeAnnotation(); 53 | } else if (operator === "+") { 54 | let right = this.get("right"); 55 | let left = this.get("left"); 56 | 57 | if (left.isBaseType("number") && right.isBaseType("number")) { 58 | // both numbers so this will be a number 59 | return t.numberTypeAnnotation(); 60 | } else if (left.isBaseType("string") || right.isBaseType("string")) { 61 | // one is a string so the result will be a string 62 | return t.stringTypeAnnotation(); 63 | } 64 | 65 | // unsure if left and right are strings or numbers so stay on the safe side 66 | return t.unionTypeAnnotation([ 67 | t.stringTypeAnnotation(), 68 | t.numberTypeAnnotation() 69 | ]); 70 | } 71 | } 72 | 73 | export function LogicalExpression() { 74 | return t.createUnionTypeAnnotation([ 75 | this.get("left").getTypeAnnotation(), 76 | this.get("right").getTypeAnnotation() 77 | ]); 78 | } 79 | 80 | export function ConditionalExpression() { 81 | return t.createUnionTypeAnnotation([ 82 | this.get("consequent").getTypeAnnotation(), 83 | this.get("alternate").getTypeAnnotation() 84 | ]); 85 | } 86 | 87 | export function SequenceExpression() { 88 | return this.get("expressions").pop().getTypeAnnotation(); 89 | } 90 | 91 | export function AssignmentExpression() { 92 | return this.get("right").getTypeAnnotation(); 93 | } 94 | 95 | export function UpdateExpression(node) { 96 | let operator = node.operator; 97 | if (operator === "++" || operator === "--") { 98 | return t.numberTypeAnnotation(); 99 | } 100 | } 101 | 102 | export function StringLiteral() { 103 | return t.stringTypeAnnotation(); 104 | } 105 | 106 | export function NumericLiteral() { 107 | return t.numberTypeAnnotation(); 108 | } 109 | 110 | export function BooleanLiteral() { 111 | return t.booleanTypeAnnotation(); 112 | } 113 | 114 | export function NullLiteral() { 115 | return t.nullLiteralTypeAnnotation(); 116 | } 117 | 118 | export function RegExpLiteral() { 119 | return t.genericTypeAnnotation(t.identifier("RegExp")); 120 | } 121 | 122 | export function ObjectExpression() { 123 | return t.genericTypeAnnotation(t.identifier("Object")); 124 | } 125 | 126 | export function ArrayExpression() { 127 | return t.genericTypeAnnotation(t.identifier("Array")); 128 | } 129 | 130 | export function RestElement() { 131 | return ArrayExpression(); 132 | } 133 | 134 | RestElement.validParent = true; 135 | 136 | function Func() { 137 | return t.genericTypeAnnotation(t.identifier("Function")); 138 | } 139 | 140 | export { Func as Function, Func as Class }; 141 | 142 | export function CallExpression() { 143 | return resolveCall(this.get("callee")); 144 | } 145 | 146 | export function TaggedTemplateExpression() { 147 | return resolveCall(this.get("tag")); 148 | } 149 | 150 | function resolveCall(callee) { 151 | callee = callee.resolve(); 152 | 153 | if (callee.isFunction()) { 154 | if (callee.is("async")) { 155 | if (callee.is("generator")) { 156 | return t.genericTypeAnnotation(t.identifier("AsyncIterator")); 157 | } else { 158 | return t.genericTypeAnnotation(t.identifier("Promise")); 159 | } 160 | } else { 161 | if (callee.node.returnType) { 162 | return callee.node.returnType; 163 | } else { 164 | // todo: get union type of all return arguments 165 | } 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /lib/path/family.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _getIterator2 = require("babel-runtime/core-js/get-iterator"); 6 | 7 | var _getIterator3 = _interopRequireDefault(_getIterator2); 8 | 9 | exports.getStatementParent = getStatementParent; 10 | exports.getOpposite = getOpposite; 11 | exports.getCompletionRecords = getCompletionRecords; 12 | exports.getSibling = getSibling; 13 | exports.get = get; 14 | exports._getKey = _getKey; 15 | exports._getPattern = _getPattern; 16 | exports.getBindingIdentifiers = getBindingIdentifiers; 17 | exports.getOuterBindingIdentifiers = getOuterBindingIdentifiers; 18 | 19 | var _index = require("./index"); 20 | 21 | var _index2 = _interopRequireDefault(_index); 22 | 23 | var _babelTypes = require("babel-types"); 24 | 25 | var t = _interopRequireWildcard(_babelTypes); 26 | 27 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 28 | 29 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 30 | 31 | function getStatementParent() { 32 | var path = this; 33 | 34 | do { 35 | if (!path.parentPath || Array.isArray(path.container) && path.isStatement()) { 36 | break; 37 | } else { 38 | path = path.parentPath; 39 | } 40 | } while (path); 41 | 42 | if (path && (path.isProgram() || path.isFile())) { 43 | throw new Error("File/Program node, we can't possibly find a statement parent to this"); 44 | } 45 | 46 | return path; 47 | } 48 | 49 | function getOpposite() { 50 | if (this.key === "left") { 51 | return this.getSibling("right"); 52 | } else if (this.key === "right") { 53 | return this.getSibling("left"); 54 | } 55 | } 56 | 57 | function getCompletionRecords() { 58 | var paths = []; 59 | 60 | var add = function add(path) { 61 | if (path) paths = paths.concat(path.getCompletionRecords()); 62 | }; 63 | 64 | if (this.isIfStatement()) { 65 | add(this.get("consequent")); 66 | add(this.get("alternate")); 67 | } else if (this.isDoExpression() || this.isFor() || this.isWhile()) { 68 | add(this.get("body")); 69 | } else if (this.isProgram() || this.isBlockStatement()) { 70 | add(this.get("body").pop()); 71 | } else if (this.isFunction()) { 72 | return this.get("body").getCompletionRecords(); 73 | } else if (this.isTryStatement()) { 74 | add(this.get("block")); 75 | add(this.get("handler")); 76 | add(this.get("finalizer")); 77 | } else { 78 | paths.push(this); 79 | } 80 | 81 | return paths; 82 | } 83 | 84 | function getSibling(key) { 85 | return _index2.default.get({ 86 | parentPath: this.parentPath, 87 | parent: this.parent, 88 | container: this.container, 89 | listKey: this.listKey, 90 | key: key 91 | }); 92 | } 93 | 94 | function get(key, context) { 95 | if (context === true) context = this.context; 96 | var parts = key.split("."); 97 | if (parts.length === 1) { 98 | return this._getKey(key, context); 99 | } else { 100 | return this._getPattern(parts, context); 101 | } 102 | } 103 | 104 | function _getKey(key, context) { 105 | var _this = this; 106 | 107 | var node = this.node; 108 | var container = node[key]; 109 | 110 | if (Array.isArray(container)) { 111 | return container.map(function (_, i) { 112 | return _index2.default.get({ 113 | listKey: key, 114 | parentPath: _this, 115 | parent: node, 116 | container: container, 117 | key: i 118 | }).setContext(context); 119 | }); 120 | } else { 121 | return _index2.default.get({ 122 | parentPath: this, 123 | parent: node, 124 | container: node, 125 | key: key 126 | }).setContext(context); 127 | } 128 | } 129 | 130 | function _getPattern(parts, context) { 131 | var path = this; 132 | for (var _iterator = parts, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3.default)(_iterator);;) { 133 | var _ref; 134 | 135 | if (_isArray) { 136 | if (_i >= _iterator.length) break; 137 | _ref = _iterator[_i++]; 138 | } else { 139 | _i = _iterator.next(); 140 | if (_i.done) break; 141 | _ref = _i.value; 142 | } 143 | 144 | var part = _ref; 145 | 146 | if (part === ".") { 147 | path = path.parentPath; 148 | } else { 149 | if (Array.isArray(path)) { 150 | path = path[part]; 151 | } else { 152 | path = path.get(part, context); 153 | } 154 | } 155 | } 156 | return path; 157 | } 158 | 159 | function getBindingIdentifiers(duplicates) { 160 | return t.getBindingIdentifiers(this.node, duplicates); 161 | } 162 | 163 | function getOuterBindingIdentifiers(duplicates) { 164 | return t.getOuterBindingIdentifiers(this.node, duplicates); 165 | } -------------------------------------------------------------------------------- /lib/scope/lib/renamer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); 6 | 7 | var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); 8 | 9 | var _binding = require("../binding"); 10 | 11 | var _binding2 = _interopRequireDefault(_binding); 12 | 13 | var _babelTypes = require("babel-types"); 14 | 15 | var t = _interopRequireWildcard(_babelTypes); 16 | 17 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | var renameVisitor = { 22 | ReferencedIdentifier: function ReferencedIdentifier(_ref, state) { 23 | var node = _ref.node; 24 | 25 | if (node.name === state.oldName) { 26 | node.name = state.newName; 27 | } 28 | }, 29 | Scope: function Scope(path, state) { 30 | if (!path.scope.bindingIdentifierEquals(state.oldName, state.binding.identifier)) { 31 | path.skip(); 32 | } 33 | }, 34 | "AssignmentExpression|Declaration": function AssignmentExpressionDeclaration(path, state) { 35 | var ids = path.getOuterBindingIdentifiers(); 36 | 37 | for (var name in ids) { 38 | if (name === state.oldName) ids[name].name = state.newName; 39 | } 40 | } 41 | }; 42 | 43 | var Renamer = function () { 44 | function Renamer(binding, oldName, newName) { 45 | (0, _classCallCheck3.default)(this, Renamer); 46 | 47 | this.newName = newName; 48 | this.oldName = oldName; 49 | this.binding = binding; 50 | } 51 | 52 | Renamer.prototype.maybeConvertFromExportDeclaration = function maybeConvertFromExportDeclaration(parentDeclar) { 53 | var exportDeclar = parentDeclar.parentPath.isExportDeclaration() && parentDeclar.parentPath; 54 | if (!exportDeclar) return; 55 | 56 | var isDefault = exportDeclar.isExportDefaultDeclaration(); 57 | 58 | if (isDefault && (parentDeclar.isFunctionDeclaration() || parentDeclar.isClassDeclaration()) && !parentDeclar.node.id) { 59 | parentDeclar.node.id = parentDeclar.scope.generateUidIdentifier("default"); 60 | } 61 | 62 | var bindingIdentifiers = parentDeclar.getOuterBindingIdentifiers(); 63 | var specifiers = []; 64 | 65 | for (var name in bindingIdentifiers) { 66 | var localName = name === this.oldName ? this.newName : name; 67 | var exportedName = isDefault ? "default" : name; 68 | specifiers.push(t.exportSpecifier(t.identifier(localName), t.identifier(exportedName))); 69 | } 70 | 71 | var aliasDeclar = t.exportNamedDeclaration(null, specifiers); 72 | 73 | if (parentDeclar.isFunctionDeclaration()) { 74 | aliasDeclar._blockHoist = 3; 75 | } 76 | 77 | exportDeclar.insertAfter(aliasDeclar); 78 | exportDeclar.replaceWith(parentDeclar.node); 79 | }; 80 | 81 | Renamer.prototype.maybeConvertFromClassFunctionDeclaration = function maybeConvertFromClassFunctionDeclaration(path) { 82 | return; 83 | 84 | if (!path.isFunctionDeclaration() && !path.isClassDeclaration()) return; 85 | if (this.binding.kind !== "hoisted") return; 86 | 87 | path.node.id = t.identifier(this.oldName); 88 | path.node._blockHoist = 3; 89 | 90 | path.replaceWith(t.variableDeclaration("let", [t.variableDeclarator(t.identifier(this.newName), t.toExpression(path.node))])); 91 | }; 92 | 93 | Renamer.prototype.maybeConvertFromClassFunctionExpression = function maybeConvertFromClassFunctionExpression(path) { 94 | return; 95 | 96 | if (!path.isFunctionExpression() && !path.isClassExpression()) return; 97 | if (this.binding.kind !== "local") return; 98 | 99 | path.node.id = t.identifier(this.oldName); 100 | 101 | this.binding.scope.parent.push({ 102 | id: t.identifier(this.newName) 103 | }); 104 | 105 | path.replaceWith(t.assignmentExpression("=", t.identifier(this.newName), path.node)); 106 | }; 107 | 108 | Renamer.prototype.rename = function rename(block) { 109 | var binding = this.binding; 110 | var oldName = this.oldName; 111 | var newName = this.newName; 112 | var scope = binding.scope; 113 | var path = binding.path; 114 | 115 | 116 | var parentDeclar = path.find(function (path) { 117 | return path.isDeclaration() || path.isFunctionExpression(); 118 | }); 119 | if (parentDeclar) { 120 | this.maybeConvertFromExportDeclaration(parentDeclar); 121 | } 122 | 123 | scope.traverse(block || scope.block, renameVisitor, this); 124 | 125 | if (!block) { 126 | scope.removeOwnBinding(oldName); 127 | scope.bindings[newName] = binding; 128 | this.binding.identifier.name = newName; 129 | } 130 | 131 | if (binding.type === "hoisted") {} 132 | 133 | if (parentDeclar) { 134 | this.maybeConvertFromClassFunctionDeclaration(parentDeclar); 135 | this.maybeConvertFromClassFunctionExpression(parentDeclar); 136 | } 137 | }; 138 | 139 | return Renamer; 140 | }(); 141 | 142 | exports.default = Renamer; 143 | module.exports = exports["default"]; -------------------------------------------------------------------------------- /src/path/lib/hoister.js: -------------------------------------------------------------------------------- 1 | import { react } from "babel-types"; 2 | import * as t from "babel-types"; 3 | 4 | let referenceVisitor = { 5 | ReferencedIdentifier(path, state) { 6 | if (path.isJSXIdentifier() && react.isCompatTag(path.node.name)) { 7 | return; 8 | } 9 | 10 | // direct references that we need to track to hoist this to the highest scope we can 11 | let binding = path.scope.getBinding(path.node.name); 12 | if (!binding) return; 13 | 14 | // this binding isn't accessible from the parent scope so we can safely ignore it 15 | // eg. it's in a closure etc 16 | if (binding !== state.scope.getBinding(path.node.name)) return; 17 | 18 | if (binding.constant) { 19 | state.bindings[path.node.name] = binding; 20 | } else { 21 | for (let violationPath of (binding.constantViolations: Array)) { 22 | state.breakOnScopePaths = state.breakOnScopePaths.concat(violationPath.getAncestry()); 23 | } 24 | } 25 | } 26 | }; 27 | 28 | export default class PathHoister { 29 | constructor(path, scope) { 30 | this.breakOnScopePaths = []; 31 | this.bindings = {}; 32 | this.scopes = []; 33 | this.scope = scope; 34 | this.path = path; 35 | } 36 | 37 | isCompatibleScope(scope) { 38 | for (let key in this.bindings) { 39 | let binding = this.bindings[key]; 40 | if (!scope.bindingIdentifierEquals(key, binding.identifier)) { 41 | return false; 42 | } 43 | } 44 | 45 | return true; 46 | } 47 | 48 | getCompatibleScopes() { 49 | let scope = this.path.scope; 50 | do { 51 | if (this.isCompatibleScope(scope)) { 52 | this.scopes.push(scope); 53 | } else { 54 | break; 55 | } 56 | 57 | if (this.breakOnScopePaths.indexOf(scope.path) >= 0) { 58 | break; 59 | } 60 | } while (scope = scope.parent); 61 | } 62 | 63 | getAttachmentPath() { 64 | let path = this._getAttachmentPath(); 65 | if (!path) return; 66 | 67 | let targetScope = path.scope; 68 | 69 | // don't allow paths that have their own lexical environments to pollute 70 | if (targetScope.path === path) { 71 | targetScope = path.scope.parent; 72 | } 73 | 74 | // avoid hoisting to a scope that contains bindings that are executed after our attachment path 75 | if (targetScope.path.isProgram() || targetScope.path.isFunction()) { 76 | for (let name in this.bindings) { 77 | // check binding is a direct child of this paths scope 78 | if (!targetScope.hasOwnBinding(name)) continue; 79 | 80 | let binding = this.bindings[name]; 81 | 82 | // allow parameter references 83 | if (binding.kind === "param") continue; 84 | 85 | // if this binding appears after our attachment point then don't hoist it 86 | if (binding.path.getStatementParent().key > path.key) return; 87 | } 88 | } 89 | 90 | return path; 91 | } 92 | 93 | _getAttachmentPath() { 94 | let scopes = this.scopes; 95 | 96 | let scope = scopes.pop(); 97 | if (!scope) return; 98 | 99 | if (scope.path.isFunction()) { 100 | if (this.hasOwnParamBindings(scope)) { 101 | // should ignore this scope since it's ourselves 102 | if (this.scope === scope) return; 103 | 104 | // needs to be attached to the body 105 | return scope.path.get("body").get("body")[0]; 106 | } else { 107 | // doesn't need to be be attached to this scope 108 | return this.getNextScopeStatementParent(); 109 | } 110 | } else if (scope.path.isProgram()) { 111 | return this.getNextScopeStatementParent(); 112 | } 113 | } 114 | 115 | getNextScopeStatementParent() { 116 | let scope = this.scopes.pop(); 117 | if (scope) return scope.path.getStatementParent(); 118 | } 119 | 120 | hasOwnParamBindings(scope) { 121 | for (let name in this.bindings) { 122 | if (!scope.hasOwnBinding(name)) continue; 123 | 124 | let binding = this.bindings[name]; 125 | if (binding.kind === "param") return true; 126 | } 127 | return false; 128 | } 129 | 130 | run() { 131 | let node = this.path.node; 132 | if (node._hoisted) return; 133 | node._hoisted = true; 134 | 135 | this.path.traverse(referenceVisitor, this); 136 | 137 | this.getCompatibleScopes(); 138 | 139 | let attachTo = this.getAttachmentPath(); 140 | if (!attachTo) return; 141 | 142 | // don't bother hoisting to the same function as this will cause multiple branches to be evaluated more than once leading to a bad optimisation 143 | if (attachTo.getFunctionParent() === this.path.getFunctionParent()) return; 144 | 145 | // generate declaration and insert it to our point 146 | let uid = attachTo.scope.generateUidIdentifier("ref"); 147 | attachTo.insertBefore([ 148 | t.variableDeclaration("var", [ 149 | t.variableDeclarator(uid, this.path.node) 150 | ]) 151 | ]); 152 | 153 | let parent = this.path.parentPath; 154 | if (parent.isJSXElement() && this.path.container === parent.node.children) { 155 | // turning the `span` in `
` to an expression so we need to wrap it with 156 | // an expression container 157 | uid = t.JSXExpressionContainer(uid); 158 | } 159 | 160 | this.path.replaceWith(uid); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/path/index.js: -------------------------------------------------------------------------------- 1 | /* eslint max-len: 0 */ 2 | 3 | import type Hub from "../hub"; 4 | import type TraversalContext from "../context"; 5 | import * as virtualTypes from "./lib/virtual-types"; 6 | import buildDebug from "debug"; 7 | import invariant from "invariant"; 8 | import traverse from "../index"; 9 | import assign from "lodash/assign"; 10 | import Scope from "../scope"; 11 | import * as t from "babel-types"; 12 | import { path as pathCache } from "../cache"; 13 | 14 | let debug = buildDebug("babel"); 15 | 16 | export default class NodePath { 17 | constructor(hub: Hub, parent: Object) { 18 | this.parent = parent; 19 | this.hub = hub; 20 | this.contexts = []; 21 | this.data = {}; 22 | this.shouldSkip = false; 23 | this.shouldStop = false; 24 | this.removed = false; 25 | this.state = null; 26 | this.opts = null; 27 | this.skipKeys = null; 28 | this.parentPath = null; 29 | this.context = null; 30 | this.container = null; 31 | this.listKey = null; 32 | this.inList = false; 33 | this.parentKey = null; 34 | this.key = null; 35 | this.node = null; 36 | this.scope = null; 37 | this.type = null; 38 | this.typeAnnotation = null; 39 | } 40 | 41 | parent: Object; 42 | hub: Hub; 43 | contexts: Array; 44 | data: Object; 45 | shouldSkip: boolean; 46 | shouldStop: boolean; 47 | removed: boolean; 48 | state: any; 49 | opts: ?Object; 50 | skipKeys: ?Object; 51 | parentPath: ?NodePath; 52 | context: TraversalContext; 53 | container: ?Object | Array; 54 | listKey: ?string; 55 | inList: boolean; 56 | parentKey: ?string; 57 | key: ?string; 58 | node: ?Object; 59 | scope: Scope; 60 | type: ?string; 61 | typeAnnotation: ?Object; 62 | 63 | static get({ hub, parentPath, parent, container, listKey, key }): NodePath { 64 | if (!hub && parentPath) { 65 | hub = parentPath.hub; 66 | } 67 | 68 | invariant(parent, "To get a node path the parent needs to exist"); 69 | 70 | let targetNode = container[key]; 71 | 72 | let paths = pathCache.get(parent) || []; 73 | if (!pathCache.has(parent)) { 74 | pathCache.set(parent, paths); 75 | } 76 | 77 | let path; 78 | 79 | for (let i = 0; i < paths.length; i++) { 80 | let pathCheck = paths[i]; 81 | if (pathCheck.node === targetNode) { 82 | path = pathCheck; 83 | break; 84 | } 85 | } 86 | 87 | if (!path) { 88 | path = new NodePath(hub, parent); 89 | paths.push(path); 90 | } 91 | 92 | path.setup(parentPath, container, listKey, key); 93 | 94 | return path; 95 | } 96 | 97 | getScope(scope: Scope) { 98 | let ourScope = scope; 99 | 100 | // we're entering a new scope so let's construct it! 101 | if (this.isScope()) { 102 | ourScope = new Scope(this, scope); 103 | } 104 | 105 | return ourScope; 106 | } 107 | 108 | setData(key: string, val: any): any { 109 | return this.data[key] = val; 110 | } 111 | 112 | getData(key: string, def?: any): any { 113 | let val = this.data[key]; 114 | if (!val && def) val = this.data[key] = def; 115 | return val; 116 | } 117 | 118 | buildCodeFrameError(msg: string, Error: typeof Error = SyntaxError): Error { 119 | return this.hub.file.buildCodeFrameError(this.node, msg, Error); 120 | } 121 | 122 | traverse(visitor: Object, state?: any) { 123 | traverse(this.node, visitor, this.scope, state, this); 124 | } 125 | 126 | mark(type: string, message: string) { 127 | this.hub.file.metadata.marked.push({ 128 | type, 129 | message, 130 | loc: this.node.loc 131 | }); 132 | } 133 | 134 | set(key: string, node: Object) { 135 | t.validate(this.node, key, node); 136 | this.node[key] = node; 137 | } 138 | 139 | getPathLocation(): string { 140 | let parts = []; 141 | let path = this; 142 | do { 143 | let key = path.key; 144 | if (path.inList) key = `${path.listKey}[${key}]`; 145 | parts.unshift(key); 146 | } while (path = path.parentPath); 147 | return parts.join("."); 148 | } 149 | 150 | debug(buildMessage: Function) { 151 | if (!debug.enabled) return; 152 | debug(`${this.getPathLocation()} ${this.type}: ${buildMessage()}`); 153 | } 154 | } 155 | 156 | assign(NodePath.prototype, require("./ancestry")); 157 | assign(NodePath.prototype, require("./inference")); 158 | assign(NodePath.prototype, require("./replacement")); 159 | assign(NodePath.prototype, require("./evaluation")); 160 | assign(NodePath.prototype, require("./conversion")); 161 | assign(NodePath.prototype, require("./introspection")); 162 | assign(NodePath.prototype, require("./context")); 163 | assign(NodePath.prototype, require("./removal")); 164 | assign(NodePath.prototype, require("./modification")); 165 | assign(NodePath.prototype, require("./family")); 166 | assign(NodePath.prototype, require("./comments")); 167 | 168 | for (let type of (t.TYPES: Array)) { 169 | let typeKey = `is${type}`; 170 | NodePath.prototype[typeKey] = function (opts) { 171 | return t[typeKey](this.node, opts); 172 | }; 173 | 174 | NodePath.prototype[`assert${type}`] = function (opts) { 175 | if (!this[typeKey](opts)) { 176 | throw new TypeError(`Expected node path of type ${type}`); 177 | } 178 | }; 179 | } 180 | 181 | for (let type in virtualTypes) { 182 | if (type[0] === "_") continue; 183 | if (t.TYPES.indexOf(type) < 0) t.TYPES.push(type); 184 | 185 | let virtualType = virtualTypes[type]; 186 | 187 | NodePath.prototype[`is${type}`] = function (opts) { 188 | return virtualType.checkPath(this, opts); 189 | }; 190 | } 191 | -------------------------------------------------------------------------------- /lib/path/lib/hoister.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); 6 | 7 | var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); 8 | 9 | var _getIterator2 = require("babel-runtime/core-js/get-iterator"); 10 | 11 | var _getIterator3 = _interopRequireDefault(_getIterator2); 12 | 13 | var _babelTypes = require("babel-types"); 14 | 15 | var t = _interopRequireWildcard(_babelTypes); 16 | 17 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | var referenceVisitor = { 22 | ReferencedIdentifier: function ReferencedIdentifier(path, state) { 23 | if (path.isJSXIdentifier() && _babelTypes.react.isCompatTag(path.node.name)) { 24 | return; 25 | } 26 | 27 | var binding = path.scope.getBinding(path.node.name); 28 | if (!binding) return; 29 | 30 | if (binding !== state.scope.getBinding(path.node.name)) return; 31 | 32 | if (binding.constant) { 33 | state.bindings[path.node.name] = binding; 34 | } else { 35 | for (var _iterator = binding.constantViolations, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3.default)(_iterator);;) { 36 | var _ref; 37 | 38 | if (_isArray) { 39 | if (_i >= _iterator.length) break; 40 | _ref = _iterator[_i++]; 41 | } else { 42 | _i = _iterator.next(); 43 | if (_i.done) break; 44 | _ref = _i.value; 45 | } 46 | 47 | var violationPath = _ref; 48 | 49 | state.breakOnScopePaths = state.breakOnScopePaths.concat(violationPath.getAncestry()); 50 | } 51 | } 52 | } 53 | }; 54 | 55 | var PathHoister = function () { 56 | function PathHoister(path, scope) { 57 | (0, _classCallCheck3.default)(this, PathHoister); 58 | 59 | this.breakOnScopePaths = []; 60 | this.bindings = {}; 61 | this.scopes = []; 62 | this.scope = scope; 63 | this.path = path; 64 | } 65 | 66 | PathHoister.prototype.isCompatibleScope = function isCompatibleScope(scope) { 67 | for (var key in this.bindings) { 68 | var binding = this.bindings[key]; 69 | if (!scope.bindingIdentifierEquals(key, binding.identifier)) { 70 | return false; 71 | } 72 | } 73 | 74 | return true; 75 | }; 76 | 77 | PathHoister.prototype.getCompatibleScopes = function getCompatibleScopes() { 78 | var scope = this.path.scope; 79 | do { 80 | if (this.isCompatibleScope(scope)) { 81 | this.scopes.push(scope); 82 | } else { 83 | break; 84 | } 85 | 86 | if (this.breakOnScopePaths.indexOf(scope.path) >= 0) { 87 | break; 88 | } 89 | } while (scope = scope.parent); 90 | }; 91 | 92 | PathHoister.prototype.getAttachmentPath = function getAttachmentPath() { 93 | var path = this._getAttachmentPath(); 94 | if (!path) return; 95 | 96 | var targetScope = path.scope; 97 | 98 | if (targetScope.path === path) { 99 | targetScope = path.scope.parent; 100 | } 101 | 102 | if (targetScope.path.isProgram() || targetScope.path.isFunction()) { 103 | for (var name in this.bindings) { 104 | if (!targetScope.hasOwnBinding(name)) continue; 105 | 106 | var binding = this.bindings[name]; 107 | 108 | if (binding.kind === "param") continue; 109 | 110 | if (binding.path.getStatementParent().key > path.key) return; 111 | } 112 | } 113 | 114 | return path; 115 | }; 116 | 117 | PathHoister.prototype._getAttachmentPath = function _getAttachmentPath() { 118 | var scopes = this.scopes; 119 | 120 | var scope = scopes.pop(); 121 | if (!scope) return; 122 | 123 | if (scope.path.isFunction()) { 124 | if (this.hasOwnParamBindings(scope)) { 125 | if (this.scope === scope) return; 126 | 127 | return scope.path.get("body").get("body")[0]; 128 | } else { 129 | return this.getNextScopeStatementParent(); 130 | } 131 | } else if (scope.path.isProgram()) { 132 | return this.getNextScopeStatementParent(); 133 | } 134 | }; 135 | 136 | PathHoister.prototype.getNextScopeStatementParent = function getNextScopeStatementParent() { 137 | var scope = this.scopes.pop(); 138 | if (scope) return scope.path.getStatementParent(); 139 | }; 140 | 141 | PathHoister.prototype.hasOwnParamBindings = function hasOwnParamBindings(scope) { 142 | for (var name in this.bindings) { 143 | if (!scope.hasOwnBinding(name)) continue; 144 | 145 | var binding = this.bindings[name]; 146 | if (binding.kind === "param") return true; 147 | } 148 | return false; 149 | }; 150 | 151 | PathHoister.prototype.run = function run() { 152 | var node = this.path.node; 153 | if (node._hoisted) return; 154 | node._hoisted = true; 155 | 156 | this.path.traverse(referenceVisitor, this); 157 | 158 | this.getCompatibleScopes(); 159 | 160 | var attachTo = this.getAttachmentPath(); 161 | if (!attachTo) return; 162 | 163 | if (attachTo.getFunctionParent() === this.path.getFunctionParent()) return; 164 | 165 | var uid = attachTo.scope.generateUidIdentifier("ref"); 166 | attachTo.insertBefore([t.variableDeclaration("var", [t.variableDeclarator(uid, this.path.node)])]); 167 | 168 | var parent = this.path.parentPath; 169 | if (parent.isJSXElement() && this.path.container === parent.node.children) { 170 | uid = t.JSXExpressionContainer(uid); 171 | } 172 | 173 | this.path.replaceWith(uid); 174 | }; 175 | 176 | return PathHoister; 177 | }(); 178 | 179 | exports.default = PathHoister; 180 | module.exports = exports["default"]; -------------------------------------------------------------------------------- /lib/context.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _getIterator2 = require("babel-runtime/core-js/get-iterator"); 6 | 7 | var _getIterator3 = _interopRequireDefault(_getIterator2); 8 | 9 | var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); 10 | 11 | var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); 12 | 13 | var _path2 = require("./path"); 14 | 15 | var _path3 = _interopRequireDefault(_path2); 16 | 17 | var _babelTypes = require("babel-types"); 18 | 19 | var t = _interopRequireWildcard(_babelTypes); 20 | 21 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 22 | 23 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 24 | 25 | var testing = process.env.NODE_ENV === "test"; 26 | 27 | var TraversalContext = function () { 28 | function TraversalContext(scope, opts, state, parentPath) { 29 | (0, _classCallCheck3.default)(this, TraversalContext); 30 | this.queue = null; 31 | 32 | this.parentPath = parentPath; 33 | this.scope = scope; 34 | this.state = state; 35 | this.opts = opts; 36 | } 37 | 38 | TraversalContext.prototype.shouldVisit = function shouldVisit(node) { 39 | var opts = this.opts; 40 | if (opts.enter || opts.exit) return true; 41 | 42 | if (opts[node.type]) return true; 43 | 44 | var keys = t.VISITOR_KEYS[node.type]; 45 | if (!keys || !keys.length) return false; 46 | 47 | for (var _iterator = keys, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3.default)(_iterator);;) { 48 | var _ref; 49 | 50 | if (_isArray) { 51 | if (_i >= _iterator.length) break; 52 | _ref = _iterator[_i++]; 53 | } else { 54 | _i = _iterator.next(); 55 | if (_i.done) break; 56 | _ref = _i.value; 57 | } 58 | 59 | var key = _ref; 60 | 61 | if (node[key]) return true; 62 | } 63 | 64 | return false; 65 | }; 66 | 67 | TraversalContext.prototype.create = function create(node, obj, key, listKey) { 68 | return _path3.default.get({ 69 | parentPath: this.parentPath, 70 | parent: node, 71 | container: obj, 72 | key: key, 73 | listKey: listKey 74 | }); 75 | }; 76 | 77 | TraversalContext.prototype.maybeQueue = function maybeQueue(path, notPriority) { 78 | if (this.trap) { 79 | throw new Error("Infinite cycle detected"); 80 | } 81 | 82 | if (this.queue) { 83 | if (notPriority) { 84 | this.queue.push(path); 85 | } else { 86 | this.priorityQueue.push(path); 87 | } 88 | } 89 | }; 90 | 91 | TraversalContext.prototype.visitMultiple = function visitMultiple(container, parent, listKey) { 92 | if (container.length === 0) return false; 93 | 94 | var queue = []; 95 | 96 | for (var key = 0; key < container.length; key++) { 97 | var node = container[key]; 98 | if (node && this.shouldVisit(node)) { 99 | queue.push(this.create(parent, container, key, listKey)); 100 | } 101 | } 102 | 103 | return this.visitQueue(queue); 104 | }; 105 | 106 | TraversalContext.prototype.visitSingle = function visitSingle(node, key) { 107 | if (this.shouldVisit(node[key])) { 108 | return this.visitQueue([this.create(node, node, key)]); 109 | } else { 110 | return false; 111 | } 112 | }; 113 | 114 | TraversalContext.prototype.visitQueue = function visitQueue(queue) { 115 | this.queue = queue; 116 | this.priorityQueue = []; 117 | 118 | var visited = []; 119 | var stop = false; 120 | 121 | for (var _iterator2 = queue, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : (0, _getIterator3.default)(_iterator2);;) { 122 | var _ref2; 123 | 124 | if (_isArray2) { 125 | if (_i2 >= _iterator2.length) break; 126 | _ref2 = _iterator2[_i2++]; 127 | } else { 128 | _i2 = _iterator2.next(); 129 | if (_i2.done) break; 130 | _ref2 = _i2.value; 131 | } 132 | 133 | var path = _ref2; 134 | 135 | path.resync(); 136 | 137 | if (path.contexts.length === 0 || path.contexts[path.contexts.length - 1] !== this) { 138 | path.pushContext(this); 139 | } 140 | 141 | if (path.key === null) continue; 142 | 143 | if (testing && queue.length >= 1000) { 144 | this.trap = true; 145 | } 146 | 147 | if (visited.indexOf(path.node) >= 0) continue; 148 | visited.push(path.node); 149 | 150 | if (path.visit()) { 151 | stop = true; 152 | break; 153 | } 154 | 155 | if (this.priorityQueue.length) { 156 | stop = this.visitQueue(this.priorityQueue); 157 | this.priorityQueue = []; 158 | this.queue = queue; 159 | if (stop) break; 160 | } 161 | } 162 | 163 | for (var _iterator3 = queue, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : (0, _getIterator3.default)(_iterator3);;) { 164 | var _ref3; 165 | 166 | if (_isArray3) { 167 | if (_i3 >= _iterator3.length) break; 168 | _ref3 = _iterator3[_i3++]; 169 | } else { 170 | _i3 = _iterator3.next(); 171 | if (_i3.done) break; 172 | _ref3 = _i3.value; 173 | } 174 | 175 | var _path = _ref3; 176 | 177 | _path.popContext(); 178 | } 179 | 180 | this.queue = null; 181 | 182 | return stop; 183 | }; 184 | 185 | TraversalContext.prototype.visit = function visit(node, key) { 186 | var nodes = node[key]; 187 | if (!nodes) return false; 188 | 189 | if (Array.isArray(nodes)) { 190 | return this.visitMultiple(nodes, node, key); 191 | } else { 192 | return this.visitSingle(node, key); 193 | } 194 | }; 195 | 196 | return TraversalContext; 197 | }(); 198 | 199 | exports.default = TraversalContext; 200 | module.exports = exports["default"]; -------------------------------------------------------------------------------- /lib/path/inference/inferers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | exports.Class = exports.Function = exports.Identifier = undefined; 5 | 6 | var _infererReference = require("./inferer-reference"); 7 | 8 | Object.defineProperty(exports, "Identifier", { 9 | enumerable: true, 10 | get: function get() { 11 | return _interopRequireDefault(_infererReference).default; 12 | } 13 | }); 14 | exports.VariableDeclarator = VariableDeclarator; 15 | exports.TypeCastExpression = TypeCastExpression; 16 | exports.NewExpression = NewExpression; 17 | exports.TemplateLiteral = TemplateLiteral; 18 | exports.UnaryExpression = UnaryExpression; 19 | exports.BinaryExpression = BinaryExpression; 20 | exports.LogicalExpression = LogicalExpression; 21 | exports.ConditionalExpression = ConditionalExpression; 22 | exports.SequenceExpression = SequenceExpression; 23 | exports.AssignmentExpression = AssignmentExpression; 24 | exports.UpdateExpression = UpdateExpression; 25 | exports.StringLiteral = StringLiteral; 26 | exports.NumericLiteral = NumericLiteral; 27 | exports.BooleanLiteral = BooleanLiteral; 28 | exports.NullLiteral = NullLiteral; 29 | exports.RegExpLiteral = RegExpLiteral; 30 | exports.ObjectExpression = ObjectExpression; 31 | exports.ArrayExpression = ArrayExpression; 32 | exports.RestElement = RestElement; 33 | exports.CallExpression = CallExpression; 34 | exports.TaggedTemplateExpression = TaggedTemplateExpression; 35 | 36 | var _babelTypes = require("babel-types"); 37 | 38 | var t = _interopRequireWildcard(_babelTypes); 39 | 40 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 41 | 42 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 43 | 44 | function VariableDeclarator() { 45 | var id = this.get("id"); 46 | 47 | if (id.isIdentifier()) { 48 | return this.get("init").getTypeAnnotation(); 49 | } else { 50 | return; 51 | } 52 | } 53 | 54 | function TypeCastExpression(node) { 55 | return node.typeAnnotation; 56 | } 57 | 58 | TypeCastExpression.validParent = true; 59 | 60 | function NewExpression(node) { 61 | if (this.get("callee").isIdentifier()) { 62 | return t.genericTypeAnnotation(node.callee); 63 | } 64 | } 65 | 66 | function TemplateLiteral() { 67 | return t.stringTypeAnnotation(); 68 | } 69 | 70 | function UnaryExpression(node) { 71 | var operator = node.operator; 72 | 73 | if (operator === "void") { 74 | return t.voidTypeAnnotation(); 75 | } else if (t.NUMBER_UNARY_OPERATORS.indexOf(operator) >= 0) { 76 | return t.numberTypeAnnotation(); 77 | } else if (t.STRING_UNARY_OPERATORS.indexOf(operator) >= 0) { 78 | return t.stringTypeAnnotation(); 79 | } else if (t.BOOLEAN_UNARY_OPERATORS.indexOf(operator) >= 0) { 80 | return t.booleanTypeAnnotation(); 81 | } 82 | } 83 | 84 | function BinaryExpression(node) { 85 | var operator = node.operator; 86 | 87 | if (t.NUMBER_BINARY_OPERATORS.indexOf(operator) >= 0) { 88 | return t.numberTypeAnnotation(); 89 | } else if (t.BOOLEAN_BINARY_OPERATORS.indexOf(operator) >= 0) { 90 | return t.booleanTypeAnnotation(); 91 | } else if (operator === "+") { 92 | var right = this.get("right"); 93 | var left = this.get("left"); 94 | 95 | if (left.isBaseType("number") && right.isBaseType("number")) { 96 | return t.numberTypeAnnotation(); 97 | } else if (left.isBaseType("string") || right.isBaseType("string")) { 98 | return t.stringTypeAnnotation(); 99 | } 100 | 101 | return t.unionTypeAnnotation([t.stringTypeAnnotation(), t.numberTypeAnnotation()]); 102 | } 103 | } 104 | 105 | function LogicalExpression() { 106 | return t.createUnionTypeAnnotation([this.get("left").getTypeAnnotation(), this.get("right").getTypeAnnotation()]); 107 | } 108 | 109 | function ConditionalExpression() { 110 | return t.createUnionTypeAnnotation([this.get("consequent").getTypeAnnotation(), this.get("alternate").getTypeAnnotation()]); 111 | } 112 | 113 | function SequenceExpression() { 114 | return this.get("expressions").pop().getTypeAnnotation(); 115 | } 116 | 117 | function AssignmentExpression() { 118 | return this.get("right").getTypeAnnotation(); 119 | } 120 | 121 | function UpdateExpression(node) { 122 | var operator = node.operator; 123 | if (operator === "++" || operator === "--") { 124 | return t.numberTypeAnnotation(); 125 | } 126 | } 127 | 128 | function StringLiteral() { 129 | return t.stringTypeAnnotation(); 130 | } 131 | 132 | function NumericLiteral() { 133 | return t.numberTypeAnnotation(); 134 | } 135 | 136 | function BooleanLiteral() { 137 | return t.booleanTypeAnnotation(); 138 | } 139 | 140 | function NullLiteral() { 141 | return t.nullLiteralTypeAnnotation(); 142 | } 143 | 144 | function RegExpLiteral() { 145 | return t.genericTypeAnnotation(t.identifier("RegExp")); 146 | } 147 | 148 | function ObjectExpression() { 149 | return t.genericTypeAnnotation(t.identifier("Object")); 150 | } 151 | 152 | function ArrayExpression() { 153 | return t.genericTypeAnnotation(t.identifier("Array")); 154 | } 155 | 156 | function RestElement() { 157 | return ArrayExpression(); 158 | } 159 | 160 | RestElement.validParent = true; 161 | 162 | function Func() { 163 | return t.genericTypeAnnotation(t.identifier("Function")); 164 | } 165 | 166 | exports.Function = Func; 167 | exports.Class = Func; 168 | function CallExpression() { 169 | return resolveCall(this.get("callee")); 170 | } 171 | 172 | function TaggedTemplateExpression() { 173 | return resolveCall(this.get("tag")); 174 | } 175 | 176 | function resolveCall(callee) { 177 | callee = callee.resolve(); 178 | 179 | if (callee.isFunction()) { 180 | if (callee.is("async")) { 181 | if (callee.is("generator")) { 182 | return t.genericTypeAnnotation(t.identifier("AsyncIterator")); 183 | } else { 184 | return t.genericTypeAnnotation(t.identifier("Promise")); 185 | } 186 | } else { 187 | if (callee.node.returnType) { 188 | return callee.node.returnType; 189 | } else {} 190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /lib/path/inference/inferer-reference.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _getIterator2 = require("babel-runtime/core-js/get-iterator"); 6 | 7 | var _getIterator3 = _interopRequireDefault(_getIterator2); 8 | 9 | exports.default = function (node) { 10 | if (!this.isReferenced()) return; 11 | 12 | var binding = this.scope.getBinding(node.name); 13 | if (binding) { 14 | if (binding.identifier.typeAnnotation) { 15 | return binding.identifier.typeAnnotation; 16 | } else { 17 | return getTypeAnnotationBindingConstantViolations(this, node.name); 18 | } 19 | } 20 | 21 | if (node.name === "undefined") { 22 | return t.voidTypeAnnotation(); 23 | } else if (node.name === "NaN" || node.name === "Infinity") { 24 | return t.numberTypeAnnotation(); 25 | } else if (node.name === "arguments") {} 26 | }; 27 | 28 | var _babelTypes = require("babel-types"); 29 | 30 | var t = _interopRequireWildcard(_babelTypes); 31 | 32 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 33 | 34 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 35 | 36 | function getTypeAnnotationBindingConstantViolations(path, name) { 37 | var binding = path.scope.getBinding(name); 38 | 39 | var types = []; 40 | path.typeAnnotation = t.unionTypeAnnotation(types); 41 | 42 | var functionConstantViolations = []; 43 | var constantViolations = getConstantViolationsBefore(binding, path, functionConstantViolations); 44 | 45 | var testType = getConditionalAnnotation(path, name); 46 | if (testType) { 47 | (function () { 48 | var testConstantViolations = getConstantViolationsBefore(binding, testType.ifStatement); 49 | 50 | constantViolations = constantViolations.filter(function (path) { 51 | return testConstantViolations.indexOf(path) < 0; 52 | }); 53 | 54 | types.push(testType.typeAnnotation); 55 | })(); 56 | } 57 | 58 | if (constantViolations.length) { 59 | constantViolations = constantViolations.concat(functionConstantViolations); 60 | 61 | for (var _iterator = constantViolations, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3.default)(_iterator);;) { 62 | var _ref; 63 | 64 | if (_isArray) { 65 | if (_i >= _iterator.length) break; 66 | _ref = _iterator[_i++]; 67 | } else { 68 | _i = _iterator.next(); 69 | if (_i.done) break; 70 | _ref = _i.value; 71 | } 72 | 73 | var violation = _ref; 74 | 75 | types.push(violation.getTypeAnnotation()); 76 | } 77 | } 78 | 79 | if (types.length) { 80 | return t.createUnionTypeAnnotation(types); 81 | } 82 | } 83 | 84 | function getConstantViolationsBefore(binding, path, functions) { 85 | var violations = binding.constantViolations.slice(); 86 | violations.unshift(binding.path); 87 | return violations.filter(function (violation) { 88 | violation = violation.resolve(); 89 | var status = violation._guessExecutionStatusRelativeTo(path); 90 | if (functions && status === "function") functions.push(violation); 91 | return status === "before"; 92 | }); 93 | } 94 | 95 | function inferAnnotationFromBinaryExpression(name, path) { 96 | var operator = path.node.operator; 97 | 98 | var right = path.get("right").resolve(); 99 | var left = path.get("left").resolve(); 100 | 101 | var target = void 0; 102 | if (left.isIdentifier({ name: name })) { 103 | target = right; 104 | } else if (right.isIdentifier({ name: name })) { 105 | target = left; 106 | } 107 | if (target) { 108 | if (operator === "===") { 109 | return target.getTypeAnnotation(); 110 | } else if (t.BOOLEAN_NUMBER_BINARY_OPERATORS.indexOf(operator) >= 0) { 111 | return t.numberTypeAnnotation(); 112 | } else { 113 | return; 114 | } 115 | } else { 116 | if (operator !== "===") return; 117 | } 118 | 119 | var typeofPath = void 0; 120 | var typePath = void 0; 121 | if (left.isUnaryExpression({ operator: "typeof" })) { 122 | typeofPath = left; 123 | typePath = right; 124 | } else if (right.isUnaryExpression({ operator: "typeof" })) { 125 | typeofPath = right; 126 | typePath = left; 127 | } 128 | if (!typePath && !typeofPath) return; 129 | 130 | typePath = typePath.resolve(); 131 | if (!typePath.isLiteral()) return; 132 | 133 | var typeValue = typePath.node.value; 134 | if (typeof typeValue !== "string") return; 135 | 136 | if (!typeofPath.get("argument").isIdentifier({ name: name })) return; 137 | 138 | return t.createTypeAnnotationBasedOnTypeof(typePath.node.value); 139 | } 140 | 141 | function getParentConditionalPath(path) { 142 | var parentPath = void 0; 143 | while (parentPath = path.parentPath) { 144 | if (parentPath.isIfStatement() || parentPath.isConditionalExpression()) { 145 | if (path.key === "test") { 146 | return; 147 | } else { 148 | return parentPath; 149 | } 150 | } else { 151 | path = parentPath; 152 | } 153 | } 154 | } 155 | 156 | function getConditionalAnnotation(path, name) { 157 | var ifStatement = getParentConditionalPath(path); 158 | if (!ifStatement) return; 159 | 160 | var test = ifStatement.get("test"); 161 | var paths = [test]; 162 | var types = []; 163 | 164 | do { 165 | var _path = paths.shift().resolve(); 166 | 167 | if (_path.isLogicalExpression()) { 168 | paths.push(_path.get("left")); 169 | paths.push(_path.get("right")); 170 | } 171 | 172 | if (_path.isBinaryExpression()) { 173 | var type = inferAnnotationFromBinaryExpression(name, _path); 174 | if (type) types.push(type); 175 | } 176 | } while (paths.length); 177 | 178 | if (types.length) { 179 | return { 180 | typeAnnotation: t.createUnionTypeAnnotation(types), 181 | ifStatement: ifStatement 182 | }; 183 | } else { 184 | return getConditionalAnnotation(ifStatement, name); 185 | } 186 | } 187 | module.exports = exports["default"]; -------------------------------------------------------------------------------- /src/path/context.js: -------------------------------------------------------------------------------- 1 | // This file contains methods responsible for maintaining a TraversalContext. 2 | 3 | import traverse from "../index"; 4 | 5 | export function call(key): boolean { 6 | let opts = this.opts; 7 | 8 | this.debug(() => key); 9 | 10 | if (this.node) { 11 | if (this._call(opts[key])) return true; 12 | } 13 | 14 | if (this.node) { 15 | return this._call(opts[this.node.type] && opts[this.node.type][key]); 16 | } 17 | 18 | return false; 19 | } 20 | 21 | export function _call(fns?: Array): boolean { 22 | if (!fns) return false; 23 | 24 | for (let fn of fns) { 25 | if (!fn) continue; 26 | 27 | let node = this.node; 28 | if (!node) return true; 29 | 30 | let ret = fn.call(this.state, this, this.state); 31 | if (ret) return true; 32 | 33 | // node has been replaced, it will have been requeued 34 | if (this.node !== node) return true; 35 | 36 | if (this.shouldStop || this.shouldSkip || this.removed) return true; 37 | } 38 | 39 | return false; 40 | } 41 | 42 | export function isBlacklisted(): boolean { 43 | let blacklist = this.opts.blacklist; 44 | return blacklist && blacklist.indexOf(this.node.type) > -1; 45 | } 46 | 47 | export function visit(): boolean { 48 | if (!this.node) { 49 | return false; 50 | } 51 | 52 | if (this.isBlacklisted()) { 53 | return false; 54 | } 55 | 56 | if (this.opts.shouldSkip && this.opts.shouldSkip(this)) { 57 | return false; 58 | } 59 | 60 | if (this.call("enter") || this.shouldSkip) { 61 | this.debug(() => "Skip..."); 62 | return this.shouldStop; 63 | } 64 | 65 | this.debug(() => "Recursing into..."); 66 | traverse.node(this.node, this.opts, this.scope, this.state, this, this.skipKeys); 67 | 68 | this.call("exit"); 69 | 70 | return this.shouldStop; 71 | } 72 | 73 | export function skip() { 74 | this.shouldSkip = true; 75 | } 76 | 77 | export function skipKey(key) { 78 | this.skipKeys[key] = true; 79 | } 80 | 81 | export function stop() { 82 | this.shouldStop = true; 83 | this.shouldSkip = true; 84 | } 85 | 86 | export function setScope() { 87 | if (this.opts && this.opts.noScope) return; 88 | 89 | let target = this.context && this.context.scope; 90 | 91 | if (!target) { 92 | let path = this.parentPath; 93 | while (path && !target) { 94 | if (path.opts && path.opts.noScope) return; 95 | 96 | target = path.scope; 97 | path = path.parentPath; 98 | } 99 | } 100 | 101 | this.scope = this.getScope(target); 102 | if (this.scope) this.scope.init(); 103 | } 104 | 105 | export function setContext(context) { 106 | this.shouldSkip = false; 107 | this.shouldStop = false; 108 | this.removed = false; 109 | this.skipKeys = {}; 110 | 111 | if (context) { 112 | this.context = context; 113 | this.state = context.state; 114 | this.opts = context.opts; 115 | } 116 | 117 | this.setScope(); 118 | 119 | return this; 120 | } 121 | 122 | /** 123 | * Here we resync the node paths `key` and `container`. If they've changed according 124 | * to what we have stored internally then we attempt to resync by crawling and looking 125 | * for the new values. 126 | */ 127 | 128 | export function resync() { 129 | if (this.removed) return; 130 | 131 | this._resyncParent(); 132 | this._resyncList(); 133 | this._resyncKey(); 134 | //this._resyncRemoved(); 135 | } 136 | 137 | export function _resyncParent() { 138 | if (this.parentPath) { 139 | this.parent = this.parentPath.node; 140 | } 141 | } 142 | 143 | export function _resyncKey() { 144 | if (!this.container) return; 145 | 146 | if (this.node === this.container[this.key]) return; 147 | 148 | // grrr, path key is out of sync. this is likely due to a modification to the AST 149 | // not done through our path APIs 150 | 151 | if (Array.isArray(this.container)) { 152 | for (let i = 0; i < this.container.length; i++) { 153 | if (this.container[i] === this.node) { 154 | return this.setKey(i); 155 | } 156 | } 157 | } else { 158 | for (let key in this.container) { 159 | if (this.container[key] === this.node) { 160 | return this.setKey(key); 161 | } 162 | } 163 | } 164 | 165 | // ¯\_(ツ)_/¯ who knows where it's gone lol 166 | this.key = null; 167 | } 168 | 169 | export function _resyncList() { 170 | if (!this.parent || !this.inList) return; 171 | 172 | let newContainer = this.parent[this.listKey]; 173 | if (this.container === newContainer) return; 174 | 175 | // container is out of sync. this is likely the result of it being reassigned 176 | this.container = newContainer || null; 177 | } 178 | 179 | export function _resyncRemoved() { 180 | if (this.key == null || !this.container || this.container[this.key] !== this.node) { 181 | this._markRemoved(); 182 | } 183 | } 184 | 185 | export function popContext() { 186 | this.contexts.pop(); 187 | this.setContext(this.contexts[this.contexts.length - 1]); 188 | } 189 | 190 | export function pushContext(context) { 191 | this.contexts.push(context); 192 | this.setContext(context); 193 | } 194 | 195 | export function setup(parentPath, container, listKey, key) { 196 | this.inList = !!listKey; 197 | this.listKey = listKey; 198 | this.parentKey = listKey || key; 199 | this.container = container; 200 | 201 | this.parentPath = parentPath || this.parentPath; 202 | this.setKey(key); 203 | } 204 | 205 | export function setKey(key) { 206 | this.key = key; 207 | this.node = this.container[this.key]; 208 | this.type = this.node && this.node.type; 209 | } 210 | 211 | export function requeue(pathToQueue = this) { 212 | if (pathToQueue.removed) return; 213 | 214 | // TODO(loganfsmyth): This should be switched back to queue in parent contexts 215 | // automatically once T2892 and T7160 have been resolved. See T7166. 216 | // let contexts = this._getQueueContexts(); 217 | let contexts = this.contexts; 218 | 219 | for (let context of contexts) { 220 | context.maybeQueue(pathToQueue); 221 | } 222 | } 223 | 224 | export function _getQueueContexts() { 225 | let path = this; 226 | let contexts = this.contexts; 227 | while (!contexts.length) { 228 | path = path.parentPath; 229 | contexts = path.contexts; 230 | } 231 | return contexts; 232 | } 233 | -------------------------------------------------------------------------------- /lib/path/ancestry.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _getIterator2 = require("babel-runtime/core-js/get-iterator"); 6 | 7 | var _getIterator3 = _interopRequireDefault(_getIterator2); 8 | 9 | exports.findParent = findParent; 10 | exports.find = find; 11 | exports.getFunctionParent = getFunctionParent; 12 | exports.getStatementParent = getStatementParent; 13 | exports.getEarliestCommonAncestorFrom = getEarliestCommonAncestorFrom; 14 | exports.getDeepestCommonAncestorFrom = getDeepestCommonAncestorFrom; 15 | exports.getAncestry = getAncestry; 16 | exports.inType = inType; 17 | exports.inShadow = inShadow; 18 | 19 | var _babelTypes = require("babel-types"); 20 | 21 | var t = _interopRequireWildcard(_babelTypes); 22 | 23 | var _index = require("./index"); 24 | 25 | var _index2 = _interopRequireDefault(_index); 26 | 27 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 28 | 29 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 30 | 31 | function findParent(callback) { 32 | var path = this; 33 | while (path = path.parentPath) { 34 | if (callback(path)) return path; 35 | } 36 | return null; 37 | } 38 | 39 | function find(callback) { 40 | var path = this; 41 | do { 42 | if (callback(path)) return path; 43 | } while (path = path.parentPath); 44 | return null; 45 | } 46 | 47 | function getFunctionParent() { 48 | return this.findParent(function (path) { 49 | return path.isFunction() || path.isProgram(); 50 | }); 51 | } 52 | 53 | function getStatementParent() { 54 | var path = this; 55 | do { 56 | if (Array.isArray(path.container)) { 57 | return path; 58 | } 59 | } while (path = path.parentPath); 60 | } 61 | 62 | function getEarliestCommonAncestorFrom(paths) { 63 | return this.getDeepestCommonAncestorFrom(paths, function (deepest, i, ancestries) { 64 | var earliest = void 0; 65 | var keys = t.VISITOR_KEYS[deepest.type]; 66 | 67 | for (var _iterator = ancestries, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3.default)(_iterator);;) { 68 | var _ref; 69 | 70 | if (_isArray) { 71 | if (_i >= _iterator.length) break; 72 | _ref = _iterator[_i++]; 73 | } else { 74 | _i = _iterator.next(); 75 | if (_i.done) break; 76 | _ref = _i.value; 77 | } 78 | 79 | var ancestry = _ref; 80 | 81 | var path = ancestry[i + 1]; 82 | 83 | if (!earliest) { 84 | earliest = path; 85 | continue; 86 | } 87 | 88 | if (path.listKey && earliest.listKey === path.listKey) { 89 | if (path.key < earliest.key) { 90 | earliest = path; 91 | continue; 92 | } 93 | } 94 | 95 | var earliestKeyIndex = keys.indexOf(earliest.parentKey); 96 | var currentKeyIndex = keys.indexOf(path.parentKey); 97 | if (earliestKeyIndex > currentKeyIndex) { 98 | earliest = path; 99 | } 100 | } 101 | 102 | return earliest; 103 | }); 104 | } 105 | 106 | function getDeepestCommonAncestorFrom(paths, filter) { 107 | var _this = this; 108 | 109 | if (!paths.length) { 110 | return this; 111 | } 112 | 113 | if (paths.length === 1) { 114 | return paths[0]; 115 | } 116 | 117 | var minDepth = Infinity; 118 | 119 | var lastCommonIndex = void 0, 120 | lastCommon = void 0; 121 | 122 | var ancestries = paths.map(function (path) { 123 | var ancestry = []; 124 | 125 | do { 126 | ancestry.unshift(path); 127 | } while ((path = path.parentPath) && path !== _this); 128 | 129 | if (ancestry.length < minDepth) { 130 | minDepth = ancestry.length; 131 | } 132 | 133 | return ancestry; 134 | }); 135 | 136 | var first = ancestries[0]; 137 | 138 | depthLoop: for (var i = 0; i < minDepth; i++) { 139 | var shouldMatch = first[i]; 140 | 141 | for (var _iterator2 = ancestries, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : (0, _getIterator3.default)(_iterator2);;) { 142 | var _ref2; 143 | 144 | if (_isArray2) { 145 | if (_i2 >= _iterator2.length) break; 146 | _ref2 = _iterator2[_i2++]; 147 | } else { 148 | _i2 = _iterator2.next(); 149 | if (_i2.done) break; 150 | _ref2 = _i2.value; 151 | } 152 | 153 | var ancestry = _ref2; 154 | 155 | if (ancestry[i] !== shouldMatch) { 156 | break depthLoop; 157 | } 158 | } 159 | 160 | lastCommonIndex = i; 161 | lastCommon = shouldMatch; 162 | } 163 | 164 | if (lastCommon) { 165 | if (filter) { 166 | return filter(lastCommon, lastCommonIndex, ancestries); 167 | } else { 168 | return lastCommon; 169 | } 170 | } else { 171 | throw new Error("Couldn't find intersection"); 172 | } 173 | } 174 | 175 | function getAncestry() { 176 | var path = this; 177 | var paths = []; 178 | do { 179 | paths.push(path); 180 | } while (path = path.parentPath); 181 | return paths; 182 | } 183 | 184 | function inType() { 185 | var path = this; 186 | while (path) { 187 | for (var _iterator3 = arguments, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : (0, _getIterator3.default)(_iterator3);;) { 188 | var _ref3; 189 | 190 | if (_isArray3) { 191 | if (_i3 >= _iterator3.length) break; 192 | _ref3 = _iterator3[_i3++]; 193 | } else { 194 | _i3 = _iterator3.next(); 195 | if (_i3.done) break; 196 | _ref3 = _i3.value; 197 | } 198 | 199 | var type = _ref3; 200 | 201 | if (path.node.type === type) return true; 202 | } 203 | path = path.parentPath; 204 | } 205 | 206 | return false; 207 | } 208 | 209 | function inShadow(key) { 210 | var parentFn = this.isFunction() ? this : this.findParent(function (p) { 211 | return p.isFunction(); 212 | }); 213 | if (!parentFn) return; 214 | 215 | if (parentFn.isFunctionExpression() || parentFn.isFunctionDeclaration()) { 216 | var shadow = parentFn.node.shadow; 217 | 218 | if (shadow && (!key || shadow[key] !== false)) { 219 | return parentFn; 220 | } 221 | } else if (parentFn.isArrowFunctionExpression()) { 222 | return parentFn; 223 | } 224 | 225 | return null; 226 | } -------------------------------------------------------------------------------- /src/path/inference/inferer-reference.js: -------------------------------------------------------------------------------- 1 | import type NodePath from "../index"; 2 | import * as t from "babel-types"; 3 | 4 | export default function (node: Object) { 5 | if (!this.isReferenced()) return; 6 | 7 | // check if a binding exists of this value and if so then return a union type of all 8 | // possible types that the binding could be 9 | let binding = this.scope.getBinding(node.name); 10 | if (binding) { 11 | if (binding.identifier.typeAnnotation) { 12 | return binding.identifier.typeAnnotation; 13 | } else { 14 | return getTypeAnnotationBindingConstantViolations(this, node.name); 15 | } 16 | } 17 | 18 | // built-in values 19 | if (node.name === "undefined") { 20 | return t.voidTypeAnnotation(); 21 | } else if (node.name === "NaN" || node.name === "Infinity") { 22 | return t.numberTypeAnnotation(); 23 | } else if (node.name === "arguments") { 24 | // todo 25 | } 26 | } 27 | 28 | function getTypeAnnotationBindingConstantViolations(path, name) { 29 | let binding = path.scope.getBinding(name); 30 | 31 | let types = []; 32 | path.typeAnnotation = t.unionTypeAnnotation(types); 33 | 34 | let functionConstantViolations = []; 35 | let constantViolations = getConstantViolationsBefore(binding, path, functionConstantViolations); 36 | 37 | let testType = getConditionalAnnotation(path, name); 38 | if (testType) { 39 | let testConstantViolations = getConstantViolationsBefore(binding, testType.ifStatement); 40 | 41 | // remove constant violations observed before the IfStatement 42 | constantViolations = constantViolations.filter((path) => testConstantViolations.indexOf(path) < 0); 43 | 44 | // clear current types and add in observed test type 45 | types.push(testType.typeAnnotation); 46 | } 47 | 48 | if (constantViolations.length) { 49 | // pick one constant from each scope which will represent the last possible 50 | // control flow path that it could've taken/been 51 | /* This code is broken for the following problems: 52 | * It thinks that assignments can only happen in scopes. 53 | * What about conditionals, if statements without block, 54 | * or guarded assignments. 55 | * It also checks to see if one of the assignments is in the 56 | * same scope and uses that as the only "violation". However, 57 | * the binding is returned by `getConstantViolationsBefore` so we for 58 | * sure always going to return that as the only "violation". 59 | let rawConstantViolations = constantViolations.reverse(); 60 | let visitedScopes = []; 61 | constantViolations = []; 62 | for (let violation of (rawConstantViolations: Array)) { 63 | let violationScope = violation.scope; 64 | if (visitedScopes.indexOf(violationScope) >= 0) continue; 65 | 66 | visitedScopes.push(violationScope); 67 | constantViolations.push(violation); 68 | 69 | if (violationScope === path.scope) { 70 | constantViolations = [violation]; 71 | break; 72 | } 73 | }*/ 74 | 75 | // add back on function constant violations since we can't track calls 76 | constantViolations = constantViolations.concat(functionConstantViolations); 77 | 78 | // push on inferred types of violated paths 79 | for (let violation of (constantViolations: Array)) { 80 | types.push(violation.getTypeAnnotation()); 81 | } 82 | } 83 | 84 | if (types.length) { 85 | return t.createUnionTypeAnnotation(types); 86 | } 87 | } 88 | 89 | function getConstantViolationsBefore(binding, path, functions) { 90 | let violations = binding.constantViolations.slice(); 91 | violations.unshift(binding.path); 92 | return violations.filter((violation) => { 93 | violation = violation.resolve(); 94 | let status = violation._guessExecutionStatusRelativeTo(path); 95 | if (functions && status === "function") functions.push(violation); 96 | return status === "before"; 97 | }); 98 | } 99 | 100 | function inferAnnotationFromBinaryExpression(name, path) { 101 | let operator = path.node.operator; 102 | 103 | let right = path.get("right").resolve(); 104 | let left = path.get("left").resolve(); 105 | 106 | let target; 107 | if (left.isIdentifier({ name })) { 108 | target = right; 109 | } else if (right.isIdentifier({ name })) { 110 | target = left; 111 | } 112 | if (target) { 113 | if (operator === "===") { 114 | return target.getTypeAnnotation(); 115 | } else if (t.BOOLEAN_NUMBER_BINARY_OPERATORS.indexOf(operator) >= 0) { 116 | return t.numberTypeAnnotation(); 117 | } else { 118 | return; 119 | } 120 | } else { 121 | if (operator !== "===") return; 122 | } 123 | 124 | // 125 | let typeofPath; 126 | let typePath; 127 | if (left.isUnaryExpression({ operator: "typeof" })) { 128 | typeofPath = left; 129 | typePath = right; 130 | } else if (right.isUnaryExpression({ operator: "typeof" })) { 131 | typeofPath = right; 132 | typePath = left; 133 | } 134 | if (!typePath && !typeofPath) return; 135 | 136 | // ensure that the type path is a Literal 137 | typePath = typePath.resolve(); 138 | if (!typePath.isLiteral()) return; 139 | 140 | // and that it's a string so we can infer it 141 | let typeValue = typePath.node.value; 142 | if (typeof typeValue !== "string") return; 143 | 144 | // and that the argument of the typeof path references us! 145 | if (!typeofPath.get("argument").isIdentifier({ name })) return; 146 | 147 | // turn type value into a type annotation 148 | return t.createTypeAnnotationBasedOnTypeof(typePath.node.value); 149 | } 150 | 151 | function getParentConditionalPath(path) { 152 | let parentPath; 153 | while (parentPath = path.parentPath) { 154 | if (parentPath.isIfStatement() || parentPath.isConditionalExpression()) { 155 | if (path.key === "test") { 156 | return; 157 | } else { 158 | return parentPath; 159 | } 160 | } else { 161 | path = parentPath; 162 | } 163 | } 164 | } 165 | 166 | function getConditionalAnnotation(path, name) { 167 | let ifStatement = getParentConditionalPath(path); 168 | if (!ifStatement) return; 169 | 170 | let test = ifStatement.get("test"); 171 | let paths = [test]; 172 | let types = []; 173 | 174 | do { 175 | let path = paths.shift().resolve(); 176 | 177 | if (path.isLogicalExpression()) { 178 | paths.push(path.get("left")); 179 | paths.push(path.get("right")); 180 | } 181 | 182 | if (path.isBinaryExpression()) { 183 | let type = inferAnnotationFromBinaryExpression(name, path); 184 | if (type) types.push(type); 185 | } 186 | } while (paths.length); 187 | 188 | if (types.length) { 189 | return { 190 | typeAnnotation: t.createUnionTypeAnnotation(types), 191 | ifStatement 192 | }; 193 | } else { 194 | return getConditionalAnnotation(ifStatement, name); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /lib/path/context.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _getIterator2 = require("babel-runtime/core-js/get-iterator"); 6 | 7 | var _getIterator3 = _interopRequireDefault(_getIterator2); 8 | 9 | exports.call = call; 10 | exports._call = _call; 11 | exports.isBlacklisted = isBlacklisted; 12 | exports.visit = visit; 13 | exports.skip = skip; 14 | exports.skipKey = skipKey; 15 | exports.stop = stop; 16 | exports.setScope = setScope; 17 | exports.setContext = setContext; 18 | exports.resync = resync; 19 | exports._resyncParent = _resyncParent; 20 | exports._resyncKey = _resyncKey; 21 | exports._resyncList = _resyncList; 22 | exports._resyncRemoved = _resyncRemoved; 23 | exports.popContext = popContext; 24 | exports.pushContext = pushContext; 25 | exports.setup = setup; 26 | exports.setKey = setKey; 27 | exports.requeue = requeue; 28 | exports._getQueueContexts = _getQueueContexts; 29 | 30 | var _index = require("../index"); 31 | 32 | var _index2 = _interopRequireDefault(_index); 33 | 34 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 35 | 36 | function call(key) { 37 | var opts = this.opts; 38 | 39 | this.debug(function () { 40 | return key; 41 | }); 42 | 43 | if (this.node) { 44 | if (this._call(opts[key])) return true; 45 | } 46 | 47 | if (this.node) { 48 | return this._call(opts[this.node.type] && opts[this.node.type][key]); 49 | } 50 | 51 | return false; 52 | } 53 | 54 | function _call(fns) { 55 | if (!fns) return false; 56 | 57 | for (var _iterator = fns, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3.default)(_iterator);;) { 58 | var _ref; 59 | 60 | if (_isArray) { 61 | if (_i >= _iterator.length) break; 62 | _ref = _iterator[_i++]; 63 | } else { 64 | _i = _iterator.next(); 65 | if (_i.done) break; 66 | _ref = _i.value; 67 | } 68 | 69 | var fn = _ref; 70 | 71 | if (!fn) continue; 72 | 73 | var node = this.node; 74 | if (!node) return true; 75 | 76 | var ret = fn.call(this.state, this, this.state); 77 | if (ret) return true; 78 | 79 | if (this.node !== node) return true; 80 | 81 | if (this.shouldStop || this.shouldSkip || this.removed) return true; 82 | } 83 | 84 | return false; 85 | } 86 | 87 | function isBlacklisted() { 88 | var blacklist = this.opts.blacklist; 89 | return blacklist && blacklist.indexOf(this.node.type) > -1; 90 | } 91 | 92 | function visit() { 93 | if (!this.node) { 94 | return false; 95 | } 96 | 97 | if (this.isBlacklisted()) { 98 | return false; 99 | } 100 | 101 | if (this.opts.shouldSkip && this.opts.shouldSkip(this)) { 102 | return false; 103 | } 104 | 105 | if (this.call("enter") || this.shouldSkip) { 106 | this.debug(function () { 107 | return "Skip..."; 108 | }); 109 | return this.shouldStop; 110 | } 111 | 112 | this.debug(function () { 113 | return "Recursing into..."; 114 | }); 115 | _index2.default.node(this.node, this.opts, this.scope, this.state, this, this.skipKeys); 116 | 117 | this.call("exit"); 118 | 119 | return this.shouldStop; 120 | } 121 | 122 | function skip() { 123 | this.shouldSkip = true; 124 | } 125 | 126 | function skipKey(key) { 127 | this.skipKeys[key] = true; 128 | } 129 | 130 | function stop() { 131 | this.shouldStop = true; 132 | this.shouldSkip = true; 133 | } 134 | 135 | function setScope() { 136 | if (this.opts && this.opts.noScope) return; 137 | 138 | var target = this.context && this.context.scope; 139 | 140 | if (!target) { 141 | var path = this.parentPath; 142 | while (path && !target) { 143 | if (path.opts && path.opts.noScope) return; 144 | 145 | target = path.scope; 146 | path = path.parentPath; 147 | } 148 | } 149 | 150 | this.scope = this.getScope(target); 151 | if (this.scope) this.scope.init(); 152 | } 153 | 154 | function setContext(context) { 155 | this.shouldSkip = false; 156 | this.shouldStop = false; 157 | this.removed = false; 158 | this.skipKeys = {}; 159 | 160 | if (context) { 161 | this.context = context; 162 | this.state = context.state; 163 | this.opts = context.opts; 164 | } 165 | 166 | this.setScope(); 167 | 168 | return this; 169 | } 170 | 171 | function resync() { 172 | if (this.removed) return; 173 | 174 | this._resyncParent(); 175 | this._resyncList(); 176 | this._resyncKey(); 177 | } 178 | 179 | function _resyncParent() { 180 | if (this.parentPath) { 181 | this.parent = this.parentPath.node; 182 | } 183 | } 184 | 185 | function _resyncKey() { 186 | if (!this.container) return; 187 | 188 | if (this.node === this.container[this.key]) return; 189 | 190 | if (Array.isArray(this.container)) { 191 | for (var i = 0; i < this.container.length; i++) { 192 | if (this.container[i] === this.node) { 193 | return this.setKey(i); 194 | } 195 | } 196 | } else { 197 | for (var key in this.container) { 198 | if (this.container[key] === this.node) { 199 | return this.setKey(key); 200 | } 201 | } 202 | } 203 | 204 | this.key = null; 205 | } 206 | 207 | function _resyncList() { 208 | if (!this.parent || !this.inList) return; 209 | 210 | var newContainer = this.parent[this.listKey]; 211 | if (this.container === newContainer) return; 212 | 213 | this.container = newContainer || null; 214 | } 215 | 216 | function _resyncRemoved() { 217 | if (this.key == null || !this.container || this.container[this.key] !== this.node) { 218 | this._markRemoved(); 219 | } 220 | } 221 | 222 | function popContext() { 223 | this.contexts.pop(); 224 | this.setContext(this.contexts[this.contexts.length - 1]); 225 | } 226 | 227 | function pushContext(context) { 228 | this.contexts.push(context); 229 | this.setContext(context); 230 | } 231 | 232 | function setup(parentPath, container, listKey, key) { 233 | this.inList = !!listKey; 234 | this.listKey = listKey; 235 | this.parentKey = listKey || key; 236 | this.container = container; 237 | 238 | this.parentPath = parentPath || this.parentPath; 239 | this.setKey(key); 240 | } 241 | 242 | function setKey(key) { 243 | this.key = key; 244 | this.node = this.container[this.key]; 245 | this.type = this.node && this.node.type; 246 | } 247 | 248 | function requeue() { 249 | var pathToQueue = arguments.length <= 0 || arguments[0] === undefined ? this : arguments[0]; 250 | 251 | if (pathToQueue.removed) return; 252 | 253 | var contexts = this.contexts; 254 | 255 | for (var _iterator2 = contexts, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : (0, _getIterator3.default)(_iterator2);;) { 256 | var _ref2; 257 | 258 | if (_isArray2) { 259 | if (_i2 >= _iterator2.length) break; 260 | _ref2 = _iterator2[_i2++]; 261 | } else { 262 | _i2 = _iterator2.next(); 263 | if (_i2.done) break; 264 | _ref2 = _i2.value; 265 | } 266 | 267 | var context = _ref2; 268 | 269 | context.maybeQueue(pathToQueue); 270 | } 271 | } 272 | 273 | function _getQueueContexts() { 274 | var path = this; 275 | var contexts = this.contexts; 276 | while (!contexts.length) { 277 | path = path.parentPath; 278 | contexts = path.contexts; 279 | } 280 | return contexts; 281 | } -------------------------------------------------------------------------------- /src/path/modification.js: -------------------------------------------------------------------------------- 1 | /* eslint max-len: 0 */ 2 | // This file contains methods that modify the path/node in some ways. 3 | 4 | import { path as pathCache } from "../cache"; 5 | import PathHoister from "./lib/hoister"; 6 | import NodePath from "./index"; 7 | import * as t from "babel-types"; 8 | 9 | /** 10 | * Insert the provided nodes before the current one. 11 | */ 12 | 13 | export function insertBefore(nodes) { 14 | this._assertUnremoved(); 15 | 16 | nodes = this._verifyNodeList(nodes); 17 | 18 | if (this.parentPath.isExpressionStatement() || this.parentPath.isLabeledStatement()) { 19 | return this.parentPath.insertBefore(nodes); 20 | } else if (this.isNodeType("Expression") || (this.parentPath.isForStatement() && this.key === "init")) { 21 | if (this.node) nodes.push(this.node); 22 | this.replaceExpressionWithStatements(nodes); 23 | } else { 24 | this._maybePopFromStatements(nodes); 25 | if (Array.isArray(this.container)) { 26 | return this._containerInsertBefore(nodes); 27 | } else if (this.isStatementOrBlock()) { 28 | if (this.node) nodes.push(this.node); 29 | this._replaceWith(t.blockStatement(nodes)); 30 | } else { 31 | throw new Error("We don't know what to do with this node type. We were previously a Statement but we can't fit in here?"); 32 | } 33 | } 34 | 35 | return [this]; 36 | } 37 | 38 | export function _containerInsert(from, nodes) { 39 | this.updateSiblingKeys(from, nodes.length); 40 | 41 | let paths = []; 42 | 43 | for (let i = 0; i < nodes.length; i++) { 44 | let to = from + i; 45 | let node = nodes[i]; 46 | this.container.splice(to, 0, node); 47 | 48 | if (this.context) { 49 | let path = this.context.create(this.parent, this.container, to, this.listKey); 50 | 51 | // While this path may have a context, there is currently no guarantee that the context 52 | // will be the active context, because `popContext` may leave a final context in place. 53 | // We should remove this `if` and always push once T7171 has been resolved. 54 | if (this.context.queue) path.pushContext(this.context); 55 | paths.push(path); 56 | } else { 57 | paths.push(NodePath.get({ 58 | parentPath: this.parentPath, 59 | parent: this.parent, 60 | container: this.container, 61 | listKey: this.listKey, 62 | key: to 63 | })); 64 | } 65 | } 66 | 67 | let contexts = this._getQueueContexts(); 68 | 69 | for (let path of paths) { 70 | path.setScope(); 71 | path.debug(() => "Inserted."); 72 | 73 | for (let context of contexts) { 74 | context.maybeQueue(path, true); 75 | } 76 | } 77 | 78 | return paths; 79 | } 80 | 81 | export function _containerInsertBefore(nodes) { 82 | return this._containerInsert(this.key, nodes); 83 | } 84 | 85 | export function _containerInsertAfter(nodes) { 86 | return this._containerInsert(this.key + 1, nodes); 87 | } 88 | 89 | export function _maybePopFromStatements(nodes) { 90 | let last = nodes[nodes.length - 1]; 91 | let isIdentifier = t.isIdentifier(last) || (t.isExpressionStatement(last) && t.isIdentifier(last.expression)); 92 | 93 | if (isIdentifier && !this.isCompletionRecord()) { 94 | nodes.pop(); 95 | } 96 | } 97 | 98 | /** 99 | * Insert the provided nodes after the current one. When inserting nodes after an 100 | * expression, ensure that the completion record is correct by pushing the current node. 101 | */ 102 | 103 | export function insertAfter(nodes) { 104 | this._assertUnremoved(); 105 | 106 | nodes = this._verifyNodeList(nodes); 107 | 108 | if (this.parentPath.isExpressionStatement() || this.parentPath.isLabeledStatement()) { 109 | return this.parentPath.insertAfter(nodes); 110 | } else if (this.isNodeType("Expression") || (this.parentPath.isForStatement() && this.key === "init")) { 111 | if (this.node) { 112 | let temp = this.scope.generateDeclaredUidIdentifier(); 113 | nodes.unshift(t.expressionStatement(t.assignmentExpression("=", temp, this.node))); 114 | nodes.push(t.expressionStatement(temp)); 115 | } 116 | this.replaceExpressionWithStatements(nodes); 117 | } else { 118 | this._maybePopFromStatements(nodes); 119 | if (Array.isArray(this.container)) { 120 | return this._containerInsertAfter(nodes); 121 | } else if (this.isStatementOrBlock()) { 122 | if (this.node) nodes.unshift(this.node); 123 | this._replaceWith(t.blockStatement(nodes)); 124 | } else { 125 | throw new Error("We don't know what to do with this node type. We were previously a Statement but we can't fit in here?"); 126 | } 127 | } 128 | 129 | return [this]; 130 | } 131 | 132 | /** 133 | * Update all sibling node paths after `fromIndex` by `incrementBy`. 134 | */ 135 | 136 | export function updateSiblingKeys(fromIndex, incrementBy) { 137 | if (!this.parent) return; 138 | 139 | let paths = pathCache.get(this.parent); 140 | for (let i = 0; i < paths.length; i++) { 141 | let path = paths[i]; 142 | if (path.key >= fromIndex) { 143 | path.key += incrementBy; 144 | } 145 | } 146 | } 147 | 148 | export function _verifyNodeList(nodes) { 149 | if (!nodes) { 150 | return []; 151 | } 152 | 153 | if (nodes.constructor !== Array) { 154 | nodes = [nodes]; 155 | } 156 | 157 | for (let i = 0; i < nodes.length; i++) { 158 | let node = nodes[i]; 159 | let msg; 160 | 161 | if (!node) { 162 | msg = "has falsy node"; 163 | } else if (typeof node !== "object") { 164 | msg = "contains a non-object node"; 165 | } else if (!node.type) { 166 | msg = "without a type"; 167 | } else if (node instanceof NodePath) { 168 | msg = "has a NodePath when it expected a raw object"; 169 | } 170 | 171 | if (msg) { 172 | let type = Array.isArray(node) ? "array" : typeof node; 173 | throw new Error(`Node list ${msg} with the index of ${i} and type of ${type}`); 174 | } 175 | } 176 | 177 | return nodes; 178 | } 179 | 180 | export function unshiftContainer(listKey, nodes) { 181 | this._assertUnremoved(); 182 | 183 | nodes = this._verifyNodeList(nodes); 184 | 185 | // get the first path and insert our nodes before it, if it doesn't exist then it 186 | // doesn't matter, our nodes will be inserted anyway 187 | let path = NodePath.get({ 188 | parentPath: this, 189 | parent: this.node, 190 | container: this.node[listKey], 191 | listKey, 192 | key: 0 193 | }); 194 | 195 | return path.insertBefore(nodes); 196 | } 197 | 198 | export function pushContainer(listKey, nodes) { 199 | this._assertUnremoved(); 200 | 201 | nodes = this._verifyNodeList(nodes); 202 | 203 | // get an invisible path that represents the last node + 1 and replace it with our 204 | // nodes, effectively inlining it 205 | 206 | let container = this.node[listKey]; 207 | let path = NodePath.get({ 208 | parentPath: this, 209 | parent: this.node, 210 | container: container, 211 | listKey, 212 | key: container.length 213 | }); 214 | 215 | return path.replaceWithMultiple(nodes); 216 | } 217 | 218 | /** 219 | * Hoist the current node to the highest scope possible and return a UID 220 | * referencing it. 221 | */ 222 | 223 | export function hoist(scope = this.scope) { 224 | let hoister = new PathHoister(this, scope); 225 | return hoister.run(); 226 | } 227 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | exports.visitors = exports.Hub = exports.Scope = exports.NodePath = undefined; 5 | 6 | var _getOwnPropertySymbols = require("babel-runtime/core-js/object/get-own-property-symbols"); 7 | 8 | var _getOwnPropertySymbols2 = _interopRequireDefault(_getOwnPropertySymbols); 9 | 10 | var _getIterator2 = require("babel-runtime/core-js/get-iterator"); 11 | 12 | var _getIterator3 = _interopRequireDefault(_getIterator2); 13 | 14 | var _path = require("./path"); 15 | 16 | Object.defineProperty(exports, "NodePath", { 17 | enumerable: true, 18 | get: function get() { 19 | return _interopRequireDefault(_path).default; 20 | } 21 | }); 22 | 23 | var _scope = require("./scope"); 24 | 25 | Object.defineProperty(exports, "Scope", { 26 | enumerable: true, 27 | get: function get() { 28 | return _interopRequireDefault(_scope).default; 29 | } 30 | }); 31 | 32 | var _hub = require("./hub"); 33 | 34 | Object.defineProperty(exports, "Hub", { 35 | enumerable: true, 36 | get: function get() { 37 | return _interopRequireDefault(_hub).default; 38 | } 39 | }); 40 | exports.default = traverse; 41 | 42 | var _context = require("./context"); 43 | 44 | var _context2 = _interopRequireDefault(_context); 45 | 46 | var _visitors = require("./visitors"); 47 | 48 | var visitors = _interopRequireWildcard(_visitors); 49 | 50 | var _babelMessages = require("babel-messages"); 51 | 52 | var messages = _interopRequireWildcard(_babelMessages); 53 | 54 | var _includes = require("lodash/includes"); 55 | 56 | var _includes2 = _interopRequireDefault(_includes); 57 | 58 | var _babelTypes = require("babel-types"); 59 | 60 | var t = _interopRequireWildcard(_babelTypes); 61 | 62 | var _cache = require("./cache"); 63 | 64 | var cache = _interopRequireWildcard(_cache); 65 | 66 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 67 | 68 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 69 | 70 | exports.visitors = visitors; 71 | function traverse(parent, opts, scope, state, parentPath) { 72 | if (!parent) return; 73 | if (!opts) opts = {}; 74 | 75 | if (!opts.noScope && !scope) { 76 | if (parent.type !== "Program" && parent.type !== "File") { 77 | throw new Error(messages.get("traverseNeedsParent", parent.type)); 78 | } 79 | } 80 | 81 | visitors.explode(opts); 82 | 83 | traverse.node(parent, opts, scope, state, parentPath); 84 | } 85 | 86 | traverse.visitors = visitors; 87 | traverse.verify = visitors.verify; 88 | traverse.explode = visitors.explode; 89 | 90 | traverse.NodePath = require("./path"); 91 | traverse.Scope = require("./scope"); 92 | traverse.Hub = require("./hub"); 93 | 94 | traverse.cheap = function (node, enter) { 95 | if (!node) return; 96 | 97 | var keys = t.VISITOR_KEYS[node.type]; 98 | if (!keys) return; 99 | 100 | enter(node); 101 | 102 | for (var _iterator = keys, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3.default)(_iterator);;) { 103 | var _ref; 104 | 105 | if (_isArray) { 106 | if (_i >= _iterator.length) break; 107 | _ref = _iterator[_i++]; 108 | } else { 109 | _i = _iterator.next(); 110 | if (_i.done) break; 111 | _ref = _i.value; 112 | } 113 | 114 | var key = _ref; 115 | 116 | var subNode = node[key]; 117 | 118 | if (Array.isArray(subNode)) { 119 | for (var _iterator2 = subNode, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : (0, _getIterator3.default)(_iterator2);;) { 120 | var _ref2; 121 | 122 | if (_isArray2) { 123 | if (_i2 >= _iterator2.length) break; 124 | _ref2 = _iterator2[_i2++]; 125 | } else { 126 | _i2 = _iterator2.next(); 127 | if (_i2.done) break; 128 | _ref2 = _i2.value; 129 | } 130 | 131 | var _node = _ref2; 132 | 133 | traverse.cheap(_node, enter); 134 | } 135 | } else { 136 | traverse.cheap(subNode, enter); 137 | } 138 | } 139 | }; 140 | 141 | traverse.node = function (node, opts, scope, state, parentPath, skipKeys) { 142 | var keys = t.VISITOR_KEYS[node.type]; 143 | if (!keys) return; 144 | 145 | var context = new _context2.default(scope, opts, state, parentPath); 146 | for (var _iterator3 = keys, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : (0, _getIterator3.default)(_iterator3);;) { 147 | var _ref3; 148 | 149 | if (_isArray3) { 150 | if (_i3 >= _iterator3.length) break; 151 | _ref3 = _iterator3[_i3++]; 152 | } else { 153 | _i3 = _iterator3.next(); 154 | if (_i3.done) break; 155 | _ref3 = _i3.value; 156 | } 157 | 158 | var key = _ref3; 159 | 160 | if (skipKeys && skipKeys[key]) continue; 161 | if (context.visit(node, key)) return; 162 | } 163 | }; 164 | 165 | var CLEAR_KEYS = t.COMMENT_KEYS.concat(["tokens", "comments", "start", "end", "loc", "raw", "rawValue"]); 166 | 167 | traverse.clearNode = function (node) { 168 | for (var _iterator4 = CLEAR_KEYS, _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : (0, _getIterator3.default)(_iterator4);;) { 169 | var _ref4; 170 | 171 | if (_isArray4) { 172 | if (_i4 >= _iterator4.length) break; 173 | _ref4 = _iterator4[_i4++]; 174 | } else { 175 | _i4 = _iterator4.next(); 176 | if (_i4.done) break; 177 | _ref4 = _i4.value; 178 | } 179 | 180 | var _key = _ref4; 181 | 182 | if (node[_key] != null) node[_key] = undefined; 183 | } 184 | 185 | for (var key in node) { 186 | if (key[0] === "_" && node[key] != null) node[key] = undefined; 187 | } 188 | 189 | cache.path.delete(node); 190 | 191 | var syms = (0, _getOwnPropertySymbols2.default)(node); 192 | for (var _iterator5 = syms, _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : (0, _getIterator3.default)(_iterator5);;) { 193 | var _ref5; 194 | 195 | if (_isArray5) { 196 | if (_i5 >= _iterator5.length) break; 197 | _ref5 = _iterator5[_i5++]; 198 | } else { 199 | _i5 = _iterator5.next(); 200 | if (_i5.done) break; 201 | _ref5 = _i5.value; 202 | } 203 | 204 | var sym = _ref5; 205 | 206 | node[sym] = null; 207 | } 208 | }; 209 | 210 | traverse.removeProperties = function (tree) { 211 | traverse.cheap(tree, traverse.clearNode); 212 | return tree; 213 | }; 214 | 215 | function hasBlacklistedType(path, state) { 216 | if (path.node.type === state.type) { 217 | state.has = true; 218 | path.stop(); 219 | } 220 | } 221 | 222 | traverse.hasType = function (tree, scope, type, blacklistTypes) { 223 | if ((0, _includes2.default)(blacklistTypes, tree.type)) return false; 224 | 225 | if (tree.type === type) return true; 226 | 227 | var state = { 228 | has: false, 229 | type: type 230 | }; 231 | 232 | traverse(tree, { 233 | blacklist: blacklistTypes, 234 | enter: hasBlacklistedType 235 | }, scope, state); 236 | 237 | return state.has; 238 | }; 239 | 240 | traverse.clearCache = function () { 241 | cache.clear(); 242 | }; 243 | 244 | traverse.copyCache = function (source, destination) { 245 | if (cache.path.has(source)) { 246 | cache.path.set(destination, cache.path.get(source)); 247 | } 248 | }; -------------------------------------------------------------------------------- /lib/path/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _getIterator2 = require("babel-runtime/core-js/get-iterator"); 6 | 7 | var _getIterator3 = _interopRequireDefault(_getIterator2); 8 | 9 | var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); 10 | 11 | var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); 12 | 13 | var _virtualTypes = require("./lib/virtual-types"); 14 | 15 | var virtualTypes = _interopRequireWildcard(_virtualTypes); 16 | 17 | var _debug2 = require("debug"); 18 | 19 | var _debug3 = _interopRequireDefault(_debug2); 20 | 21 | var _invariant = require("invariant"); 22 | 23 | var _invariant2 = _interopRequireDefault(_invariant); 24 | 25 | var _index = require("../index"); 26 | 27 | var _index2 = _interopRequireDefault(_index); 28 | 29 | var _assign = require("lodash/assign"); 30 | 31 | var _assign2 = _interopRequireDefault(_assign); 32 | 33 | var _scope = require("../scope"); 34 | 35 | var _scope2 = _interopRequireDefault(_scope); 36 | 37 | var _babelTypes = require("babel-types"); 38 | 39 | var t = _interopRequireWildcard(_babelTypes); 40 | 41 | var _cache = require("../cache"); 42 | 43 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 44 | 45 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 46 | 47 | var _debug = (0, _debug3.default)("babel"); 48 | 49 | var NodePath = function () { 50 | function NodePath(hub, parent) { 51 | (0, _classCallCheck3.default)(this, NodePath); 52 | 53 | this.parent = parent; 54 | this.hub = hub; 55 | this.contexts = []; 56 | this.data = {}; 57 | this.shouldSkip = false; 58 | this.shouldStop = false; 59 | this.removed = false; 60 | this.state = null; 61 | this.opts = null; 62 | this.skipKeys = null; 63 | this.parentPath = null; 64 | this.context = null; 65 | this.container = null; 66 | this.listKey = null; 67 | this.inList = false; 68 | this.parentKey = null; 69 | this.key = null; 70 | this.node = null; 71 | this.scope = null; 72 | this.type = null; 73 | this.typeAnnotation = null; 74 | } 75 | 76 | NodePath.get = function get(_ref) { 77 | var hub = _ref.hub; 78 | var parentPath = _ref.parentPath; 79 | var parent = _ref.parent; 80 | var container = _ref.container; 81 | var listKey = _ref.listKey; 82 | var key = _ref.key; 83 | 84 | if (!hub && parentPath) { 85 | hub = parentPath.hub; 86 | } 87 | 88 | (0, _invariant2.default)(parent, "To get a node path the parent needs to exist"); 89 | 90 | var targetNode = container[key]; 91 | 92 | var paths = _cache.path.get(parent) || []; 93 | if (!_cache.path.has(parent)) { 94 | _cache.path.set(parent, paths); 95 | } 96 | 97 | var path = void 0; 98 | 99 | for (var i = 0; i < paths.length; i++) { 100 | var pathCheck = paths[i]; 101 | if (pathCheck.node === targetNode) { 102 | path = pathCheck; 103 | break; 104 | } 105 | } 106 | 107 | if (!path) { 108 | path = new NodePath(hub, parent); 109 | paths.push(path); 110 | } 111 | 112 | path.setup(parentPath, container, listKey, key); 113 | 114 | return path; 115 | }; 116 | 117 | NodePath.prototype.getScope = function getScope(scope) { 118 | var ourScope = scope; 119 | 120 | if (this.isScope()) { 121 | ourScope = new _scope2.default(this, scope); 122 | } 123 | 124 | return ourScope; 125 | }; 126 | 127 | NodePath.prototype.setData = function setData(key, val) { 128 | return this.data[key] = val; 129 | }; 130 | 131 | NodePath.prototype.getData = function getData(key, def) { 132 | var val = this.data[key]; 133 | if (!val && def) val = this.data[key] = def; 134 | return val; 135 | }; 136 | 137 | NodePath.prototype.buildCodeFrameError = function buildCodeFrameError(msg) { 138 | var Error = arguments.length <= 1 || arguments[1] === undefined ? SyntaxError : arguments[1]; 139 | 140 | return this.hub.file.buildCodeFrameError(this.node, msg, Error); 141 | }; 142 | 143 | NodePath.prototype.traverse = function traverse(visitor, state) { 144 | (0, _index2.default)(this.node, visitor, this.scope, state, this); 145 | }; 146 | 147 | NodePath.prototype.mark = function mark(type, message) { 148 | this.hub.file.metadata.marked.push({ 149 | type: type, 150 | message: message, 151 | loc: this.node.loc 152 | }); 153 | }; 154 | 155 | NodePath.prototype.set = function set(key, node) { 156 | t.validate(this.node, key, node); 157 | this.node[key] = node; 158 | }; 159 | 160 | NodePath.prototype.getPathLocation = function getPathLocation() { 161 | var parts = []; 162 | var path = this; 163 | do { 164 | var key = path.key; 165 | if (path.inList) key = path.listKey + "[" + key + "]"; 166 | parts.unshift(key); 167 | } while (path = path.parentPath); 168 | return parts.join("."); 169 | }; 170 | 171 | NodePath.prototype.debug = function debug(buildMessage) { 172 | if (!_debug.enabled) return; 173 | _debug(this.getPathLocation() + " " + this.type + ": " + buildMessage()); 174 | }; 175 | 176 | return NodePath; 177 | }(); 178 | 179 | exports.default = NodePath; 180 | 181 | 182 | (0, _assign2.default)(NodePath.prototype, require("./ancestry")); 183 | (0, _assign2.default)(NodePath.prototype, require("./inference")); 184 | (0, _assign2.default)(NodePath.prototype, require("./replacement")); 185 | (0, _assign2.default)(NodePath.prototype, require("./evaluation")); 186 | (0, _assign2.default)(NodePath.prototype, require("./conversion")); 187 | (0, _assign2.default)(NodePath.prototype, require("./introspection")); 188 | (0, _assign2.default)(NodePath.prototype, require("./context")); 189 | (0, _assign2.default)(NodePath.prototype, require("./removal")); 190 | (0, _assign2.default)(NodePath.prototype, require("./modification")); 191 | (0, _assign2.default)(NodePath.prototype, require("./family")); 192 | (0, _assign2.default)(NodePath.prototype, require("./comments")); 193 | 194 | var _loop2 = function _loop2() { 195 | if (_isArray) { 196 | if (_i >= _iterator.length) return "break"; 197 | _ref2 = _iterator[_i++]; 198 | } else { 199 | _i = _iterator.next(); 200 | if (_i.done) return "break"; 201 | _ref2 = _i.value; 202 | } 203 | 204 | var type = _ref2; 205 | 206 | var typeKey = "is" + type; 207 | NodePath.prototype[typeKey] = function (opts) { 208 | return t[typeKey](this.node, opts); 209 | }; 210 | 211 | NodePath.prototype["assert" + type] = function (opts) { 212 | if (!this[typeKey](opts)) { 213 | throw new TypeError("Expected node path of type " + type); 214 | } 215 | }; 216 | }; 217 | 218 | for (var _iterator = t.TYPES, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3.default)(_iterator);;) { 219 | var _ref2; 220 | 221 | var _ret2 = _loop2(); 222 | 223 | if (_ret2 === "break") break; 224 | } 225 | 226 | var _loop = function _loop(type) { 227 | if (type[0] === "_") return "continue"; 228 | if (t.TYPES.indexOf(type) < 0) t.TYPES.push(type); 229 | 230 | var virtualType = virtualTypes[type]; 231 | 232 | NodePath.prototype["is" + type] = function (opts) { 233 | return virtualType.checkPath(this, opts); 234 | }; 235 | }; 236 | 237 | for (var type in virtualTypes) { 238 | var _ret = _loop(type); 239 | 240 | if (_ret === "continue") continue; 241 | } 242 | module.exports = exports["default"]; -------------------------------------------------------------------------------- /src/path/ancestry.js: -------------------------------------------------------------------------------- 1 | // This file contains that retrieve or validate anything related to the current paths ancestry. 2 | 3 | import * as t from "babel-types"; 4 | import NodePath from "./index"; 5 | 6 | /** 7 | * Call the provided `callback` with the `NodePath`s of all the parents. 8 | * When the `callback` returns a truthy value, we return that node path. 9 | */ 10 | 11 | export function findParent(callback) { 12 | let path = this; 13 | while (path = path.parentPath) { 14 | if (callback(path)) return path; 15 | } 16 | return null; 17 | } 18 | 19 | /** 20 | * Description 21 | */ 22 | 23 | export function find(callback) { 24 | let path = this; 25 | do { 26 | if (callback(path)) return path; 27 | } while (path = path.parentPath); 28 | return null; 29 | } 30 | 31 | /** 32 | * Get the parent function of the current path. 33 | */ 34 | 35 | export function getFunctionParent() { 36 | return this.findParent((path) => path.isFunction() || path.isProgram()); 37 | } 38 | 39 | /** 40 | * Walk up the tree until we hit a parent node path in a list. 41 | */ 42 | 43 | export function getStatementParent() { 44 | let path = this; 45 | do { 46 | if (Array.isArray(path.container)) { 47 | return path; 48 | } 49 | } while (path = path.parentPath); 50 | } 51 | 52 | /** 53 | * Get the deepest common ancestor and then from it, get the earliest relationship path 54 | * to that ancestor. 55 | * 56 | * Earliest is defined as being "before" all the other nodes in terms of list container 57 | * position and visiting key. 58 | */ 59 | 60 | export function getEarliestCommonAncestorFrom(paths: Array): NodePath { 61 | return this.getDeepestCommonAncestorFrom(paths, function (deepest, i, ancestries) { 62 | let earliest; 63 | let keys = t.VISITOR_KEYS[deepest.type]; 64 | 65 | for (let ancestry of (ancestries: Array)) { 66 | let path = ancestry[i + 1]; 67 | 68 | // first path 69 | if (!earliest) { 70 | earliest = path; 71 | continue; 72 | } 73 | 74 | // handle containers 75 | if (path.listKey && earliest.listKey === path.listKey) { 76 | // we're in the same container so check if we're earlier 77 | if (path.key < earliest.key) { 78 | earliest = path; 79 | continue; 80 | } 81 | } 82 | 83 | // handle keys 84 | let earliestKeyIndex = keys.indexOf(earliest.parentKey); 85 | let currentKeyIndex = keys.indexOf(path.parentKey); 86 | if (earliestKeyIndex > currentKeyIndex) { 87 | // key appears before so it's earlier 88 | earliest = path; 89 | } 90 | } 91 | 92 | return earliest; 93 | }); 94 | } 95 | 96 | /** 97 | * Get the earliest path in the tree where the provided `paths` intersect. 98 | * 99 | * TODO: Possible optimisation target. 100 | */ 101 | 102 | export function getDeepestCommonAncestorFrom(paths: Array, filter?: Function): NodePath { 103 | if (!paths.length) { 104 | return this; 105 | } 106 | 107 | if (paths.length === 1) { 108 | return paths[0]; 109 | } 110 | 111 | // minimum depth of the tree so we know the highest node 112 | let minDepth = Infinity; 113 | 114 | // last common ancestor 115 | let lastCommonIndex, lastCommon; 116 | 117 | // get the ancestors of the path, breaking when the parent exceeds ourselves 118 | let ancestries = paths.map((path) => { 119 | let ancestry = []; 120 | 121 | do { 122 | ancestry.unshift(path); 123 | } while ((path = path.parentPath) && path !== this); 124 | 125 | // save min depth to avoid going too far in 126 | if (ancestry.length < minDepth) { 127 | minDepth = ancestry.length; 128 | } 129 | 130 | return ancestry; 131 | }); 132 | 133 | // get the first ancestry so we have a seed to assess all other ancestries with 134 | let first = ancestries[0]; 135 | 136 | // check ancestor equality 137 | depthLoop: for (let i = 0; i < minDepth; i++) { 138 | let shouldMatch = first[i]; 139 | 140 | for (let ancestry of (ancestries: Array)) { 141 | if (ancestry[i] !== shouldMatch) { 142 | // we've hit a snag 143 | break depthLoop; 144 | } 145 | } 146 | 147 | // next iteration may break so store these so they can be returned 148 | lastCommonIndex = i; 149 | lastCommon = shouldMatch; 150 | } 151 | 152 | if (lastCommon) { 153 | if (filter) { 154 | return filter(lastCommon, lastCommonIndex, ancestries); 155 | } else { 156 | return lastCommon; 157 | } 158 | } else { 159 | throw new Error("Couldn't find intersection"); 160 | } 161 | } 162 | 163 | /** 164 | * Build an array of node paths containing the entire ancestry of the current node path. 165 | * 166 | * NOTE: The current node path is included in this. 167 | */ 168 | 169 | export function getAncestry() { 170 | let path = this; 171 | let paths = []; 172 | do { 173 | paths.push(path); 174 | } while (path = path.parentPath); 175 | return paths; 176 | } 177 | 178 | export function inType() { 179 | let path = this; 180 | while (path) { 181 | for (let type of (arguments: Array)) { 182 | if (path.node.type === type) return true; 183 | } 184 | path = path.parentPath; 185 | } 186 | 187 | return false; 188 | } 189 | 190 | /** 191 | * Checks whether the binding for 'key' is a local binding in its current function context. 192 | * 193 | * Checks if the current path either is, or has a direct parent function that is, inside 194 | * of a function that is marked for shadowing of a binding matching 'key'. Also returns 195 | * the parent path if the parent path is an arrow, since arrow functions pass through 196 | * binding values to their parent, meaning they have no local bindings. 197 | * 198 | * Shadowing means that when the given binding is transformed, it will read the binding 199 | * value from the container containing the shadow function, rather than from inside the 200 | * shadow function. 201 | * 202 | * Function shadowing is acheieved by adding a "shadow" property on "FunctionExpression" 203 | * and "FunctionDeclaration" node types. 204 | * 205 | * Node's "shadow" props have the following behavior: 206 | * 207 | * - Boolean true will cause the function to shadow both "this" and "arguments". 208 | * - {this: false} Shadows "arguments" but not "this". 209 | * - {arguments: false} Shadows "this" but not "arguments". 210 | * 211 | * Separately, individual identifiers can be flagged with two flags: 212 | * 213 | * - _forceShadow - If truthy, this specific identifier will be bound in the closest 214 | * Function that is not flagged "shadow", or the Program. 215 | * - _shadowedFunctionLiteral - When set to a NodePath, this specific identifier will be bound 216 | * to this NodePath/Node or the Program. If this path is not found relative to the 217 | * starting location path, the closest function will be used. 218 | * 219 | * Please Note, these flags are for private internal use only and should be avoided. 220 | * Only "shadow" is a public property that other transforms may manipulate. 221 | */ 222 | 223 | export function inShadow(key?) { 224 | let parentFn = this.isFunction() ? this : this.findParent((p) => p.isFunction()); 225 | if (!parentFn) return; 226 | 227 | if (parentFn.isFunctionExpression() || parentFn.isFunctionDeclaration()) { 228 | let shadow = parentFn.node.shadow; 229 | 230 | // this is because sometimes we may have a `shadow` value of: 231 | // 232 | // { this: false } 233 | // 234 | // we need to catch this case if `inShadow` has been passed a `key` 235 | if (shadow && (!key || shadow[key] !== false)) { 236 | return parentFn; 237 | } 238 | } else if (parentFn.isArrowFunctionExpression()) { 239 | return parentFn; 240 | } 241 | 242 | // normal function, we've found our function context 243 | return null; 244 | } 245 | -------------------------------------------------------------------------------- /src/path/replacement.js: -------------------------------------------------------------------------------- 1 | /* eslint max-len: 0 */ 2 | // This file contains methods responsible for replacing a node with another. 3 | 4 | import codeFrame from "babel-code-frame"; 5 | import traverse from "../index"; 6 | import NodePath from "./index"; 7 | import { parse } from "babylon"; 8 | import * as t from "babel-types"; 9 | 10 | let hoistVariablesVisitor = { 11 | Function(path) { 12 | path.skip(); 13 | }, 14 | 15 | VariableDeclaration(path) { 16 | if (path.node.kind !== "var") return; 17 | 18 | let bindings = path.getBindingIdentifiers(); 19 | for (let key in bindings) { 20 | path.scope.push({ id: bindings[key] }); 21 | } 22 | 23 | let exprs = []; 24 | 25 | for (let declar of (path.node.declarations: Array)) { 26 | if (declar.init) { 27 | exprs.push(t.expressionStatement( 28 | t.assignmentExpression("=", declar.id, declar.init) 29 | )); 30 | } 31 | } 32 | 33 | path.replaceWithMultiple(exprs); 34 | } 35 | }; 36 | 37 | /** 38 | * Replace a node with an array of multiple. This method performs the following steps: 39 | * 40 | * - Inherit the comments of first provided node with that of the current node. 41 | * - Insert the provided nodes after the current node. 42 | * - Remove the current node. 43 | */ 44 | 45 | export function replaceWithMultiple(nodes: Array) { 46 | this.resync(); 47 | 48 | nodes = this._verifyNodeList(nodes); 49 | t.inheritLeadingComments(nodes[0], this.node); 50 | t.inheritTrailingComments(nodes[nodes.length - 1], this.node); 51 | this.node = this.container[this.key] = null; 52 | this.insertAfter(nodes); 53 | 54 | if (this.node) { 55 | this.requeue(); 56 | } else { 57 | this.remove(); 58 | } 59 | } 60 | 61 | /** 62 | * Parse a string as an expression and replace the current node with the result. 63 | * 64 | * NOTE: This is typically not a good idea to use. Building source strings when 65 | * transforming ASTs is an antipattern and SHOULD NOT be encouraged. Even if it's 66 | * easier to use, your transforms will be extremely brittle. 67 | */ 68 | 69 | export function replaceWithSourceString(replacement) { 70 | this.resync(); 71 | 72 | try { 73 | replacement = `(${replacement})`; 74 | replacement = parse(replacement); 75 | } catch (err) { 76 | let loc = err.loc; 77 | if (loc) { 78 | err.message += " - make sure this is an expression."; 79 | err.message += "\n" + codeFrame(replacement, loc.line, loc.column + 1); 80 | } 81 | throw err; 82 | } 83 | 84 | replacement = replacement.program.body[0].expression; 85 | traverse.removeProperties(replacement); 86 | return this.replaceWith(replacement); 87 | } 88 | 89 | /** 90 | * Replace the current node with another. 91 | */ 92 | 93 | export function replaceWith(replacement) { 94 | this.resync(); 95 | 96 | if (this.removed) { 97 | throw new Error("You can't replace this node, we've already removed it"); 98 | } 99 | 100 | if (replacement instanceof NodePath) { 101 | replacement = replacement.node; 102 | } 103 | 104 | if (!replacement) { 105 | throw new Error("You passed `path.replaceWith()` a falsy node, use `path.remove()` instead"); 106 | } 107 | 108 | if (this.node === replacement) { 109 | return; 110 | } 111 | 112 | if (this.isProgram() && !t.isProgram(replacement)) { 113 | throw new Error("You can only replace a Program root node with another Program node"); 114 | } 115 | 116 | if (Array.isArray(replacement)) { 117 | throw new Error("Don't use `path.replaceWith()` with an array of nodes, use `path.replaceWithMultiple()`"); 118 | } 119 | 120 | if (typeof replacement === "string") { 121 | throw new Error("Don't use `path.replaceWith()` with a source string, use `path.replaceWithSourceString()`"); 122 | } 123 | 124 | if (this.isNodeType("Statement") && t.isExpression(replacement)) { 125 | if (!this.canHaveVariableDeclarationOrExpression() && !this.canSwapBetweenExpressionAndStatement(replacement)) { 126 | // replacing a statement with an expression so wrap it in an expression statement 127 | replacement = t.expressionStatement(replacement); 128 | } 129 | } 130 | 131 | if (this.isNodeType("Expression") && t.isStatement(replacement)) { 132 | if (!this.canHaveVariableDeclarationOrExpression() && !this.canSwapBetweenExpressionAndStatement(replacement)) { 133 | // replacing an expression with a statement so let's explode it 134 | return this.replaceExpressionWithStatements([replacement]); 135 | } 136 | } 137 | 138 | let oldNode = this.node; 139 | if (oldNode) { 140 | t.inheritsComments(replacement, oldNode); 141 | t.removeComments(oldNode); 142 | } 143 | 144 | // replace the node 145 | this._replaceWith(replacement); 146 | this.type = replacement.type; 147 | 148 | // potentially create new scope 149 | this.setScope(); 150 | 151 | // requeue for visiting 152 | this.requeue(); 153 | } 154 | 155 | /** 156 | * Description 157 | */ 158 | 159 | export function _replaceWith(node) { 160 | if (!this.container) { 161 | throw new ReferenceError("Container is falsy"); 162 | } 163 | 164 | if (this.inList) { 165 | t.validate(this.parent, this.key, [node]); 166 | } else { 167 | t.validate(this.parent, this.key, node); 168 | } 169 | 170 | this.debug(() => `Replace with ${node && node.type}`); 171 | 172 | this.node = this.container[this.key] = node; 173 | } 174 | 175 | /** 176 | * This method takes an array of statements nodes and then explodes it 177 | * into expressions. This method retains completion records which is 178 | * extremely important to retain original semantics. 179 | */ 180 | 181 | export function replaceExpressionWithStatements(nodes: Array) { 182 | this.resync(); 183 | 184 | let toSequenceExpression = t.toSequenceExpression(nodes, this.scope); 185 | 186 | if (t.isSequenceExpression(toSequenceExpression)) { 187 | let exprs = toSequenceExpression.expressions; 188 | 189 | if (exprs.length >= 2 && this.parentPath.isExpressionStatement()) { 190 | this._maybePopFromStatements(exprs); 191 | } 192 | 193 | // could be just one element due to the previous maybe popping 194 | if (exprs.length === 1) { 195 | this.replaceWith(exprs[0]); 196 | } else { 197 | this.replaceWith(toSequenceExpression); 198 | } 199 | } else if (toSequenceExpression) { 200 | this.replaceWith(toSequenceExpression); 201 | } else { 202 | let container = t.functionExpression(null, [], t.blockStatement(nodes)); 203 | container.shadow = true; 204 | 205 | this.replaceWith(t.callExpression(container, [])); 206 | this.traverse(hoistVariablesVisitor); 207 | 208 | // add implicit returns to all ending expression statements 209 | let completionRecords: Array = this.get("callee").getCompletionRecords(); 210 | for (let path of completionRecords) { 211 | if (!path.isExpressionStatement()) continue; 212 | 213 | let loop = path.findParent((path) => path.isLoop()); 214 | if (loop) { 215 | let callee = this.get("callee"); 216 | 217 | let uid = callee.scope.generateDeclaredUidIdentifier("ret"); 218 | callee.get("body").pushContainer("body", t.returnStatement(uid)); 219 | 220 | path.get("expression").replaceWith( 221 | t.assignmentExpression("=", uid, path.node.expression) 222 | ); 223 | } else { 224 | path.replaceWith(t.returnStatement(path.node.expression)); 225 | } 226 | } 227 | 228 | return this.node; 229 | } 230 | } 231 | 232 | export function replaceInline(nodes: Object | Array) { 233 | this.resync(); 234 | 235 | if (Array.isArray(nodes)) { 236 | if (Array.isArray(this.container)) { 237 | nodes = this._verifyNodeList(nodes); 238 | this._containerInsertAfter(nodes); 239 | return this.remove(); 240 | } else { 241 | return this.replaceWithMultiple(nodes); 242 | } 243 | } else { 244 | return this.replaceWith(nodes); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/visitors.js: -------------------------------------------------------------------------------- 1 | import * as virtualTypes from "./path/lib/virtual-types"; 2 | import * as messages from "babel-messages"; 3 | import * as t from "babel-types"; 4 | import clone from "lodash/clone"; 5 | 6 | /** 7 | * explode() will take a visitor object with all of the various shorthands 8 | * that we support, and validates & normalizes it into a common format, ready 9 | * to be used in traversal 10 | * 11 | * The various shorthands are: 12 | * * `Identifier() { ... }` -> `Identifier: { enter() { ... } }` 13 | * * `"Identifier|NumericLiteral": { ... }` -> `Identifier: { ... }, NumericLiteral: { ... }` 14 | * * Aliases in `babel-types`: e.g. `Property: { ... }` -> `ObjectProperty: { ... }, ClassProperty: { ... }` 15 | * 16 | * Other normalizations are: 17 | * * Visitors of virtual types are wrapped, so that they are only visited when 18 | * their dynamic check passes 19 | * * `enter` and `exit` functions are wrapped in arrays, to ease merging of 20 | * visitors 21 | */ 22 | export function explode(visitor) { 23 | if (visitor._exploded) return visitor; 24 | visitor._exploded = true; 25 | 26 | // normalise pipes 27 | for (let nodeType in visitor) { 28 | if (shouldIgnoreKey(nodeType)) continue; 29 | 30 | let parts: Array = nodeType.split("|"); 31 | if (parts.length === 1) continue; 32 | 33 | let fns = visitor[nodeType]; 34 | delete visitor[nodeType]; 35 | 36 | for (let part of parts) { 37 | visitor[part] = fns; 38 | } 39 | } 40 | 41 | // verify data structure 42 | verify(visitor); 43 | 44 | // make sure there's no __esModule type since this is because we're using loose mode 45 | // and it sets __esModule to be enumerable on all modules :( 46 | delete visitor.__esModule; 47 | 48 | // ensure visitors are objects 49 | ensureEntranceObjects(visitor); 50 | 51 | // ensure enter/exit callbacks are arrays 52 | ensureCallbackArrays(visitor); 53 | 54 | // add type wrappers 55 | for (let nodeType of (Object.keys(visitor): Array)) { 56 | if (shouldIgnoreKey(nodeType)) continue; 57 | 58 | let wrapper = virtualTypes[nodeType]; 59 | if (!wrapper) continue; 60 | 61 | // wrap all the functions 62 | let fns = visitor[nodeType]; 63 | for (let type in fns) { 64 | fns[type] = wrapCheck(wrapper, fns[type]); 65 | } 66 | 67 | // clear it from the visitor 68 | delete visitor[nodeType]; 69 | 70 | if (wrapper.types) { 71 | for (let type of (wrapper.types: Array)) { 72 | // merge the visitor if necessary or just put it back in 73 | if (visitor[type]) { 74 | mergePair(visitor[type], fns); 75 | } else { 76 | visitor[type] = fns; 77 | } 78 | } 79 | } else { 80 | mergePair(visitor, fns); 81 | } 82 | } 83 | 84 | // add aliases 85 | for (let nodeType in visitor) { 86 | if (shouldIgnoreKey(nodeType)) continue; 87 | 88 | let fns = visitor[nodeType]; 89 | 90 | let aliases: ?Array = t.FLIPPED_ALIAS_KEYS[nodeType]; 91 | 92 | let deprecratedKey = t.DEPRECATED_KEYS[nodeType]; 93 | if (deprecratedKey) { 94 | console.trace(`Visitor defined for ${nodeType} but it has been renamed to ${deprecratedKey}`); 95 | aliases = [deprecratedKey]; 96 | } 97 | 98 | if (!aliases) continue; 99 | 100 | // clear it from the visitor 101 | delete visitor[nodeType]; 102 | 103 | for (let alias of aliases) { 104 | let existing = visitor[alias]; 105 | if (existing) { 106 | mergePair(existing, fns); 107 | } else { 108 | visitor[alias] = clone(fns); 109 | } 110 | } 111 | } 112 | 113 | for (let nodeType in visitor) { 114 | if (shouldIgnoreKey(nodeType)) continue; 115 | 116 | ensureCallbackArrays(visitor[nodeType]); 117 | } 118 | 119 | return visitor; 120 | } 121 | 122 | export function verify(visitor) { 123 | if (visitor._verified) return; 124 | 125 | if (typeof visitor === "function") { 126 | throw new Error(messages.get("traverseVerifyRootFunction")); 127 | } 128 | 129 | for (let nodeType in visitor) { 130 | if (nodeType === "enter" || nodeType === "exit") { 131 | validateVisitorMethods(nodeType, visitor[nodeType]); 132 | } 133 | 134 | if (shouldIgnoreKey(nodeType)) continue; 135 | 136 | if (t.TYPES.indexOf(nodeType) < 0) { 137 | throw new Error(messages.get("traverseVerifyNodeType", nodeType)); 138 | } 139 | 140 | let visitors = visitor[nodeType]; 141 | if (typeof visitors === "object") { 142 | for (let visitorKey in visitors) { 143 | if (visitorKey === "enter" || visitorKey === "exit") { 144 | // verify that it just contains functions 145 | validateVisitorMethods(`${nodeType}.${visitorKey}`, visitors[visitorKey]); 146 | } else { 147 | throw new Error(messages.get("traverseVerifyVisitorProperty", nodeType, visitorKey)); 148 | } 149 | } 150 | } 151 | } 152 | 153 | visitor._verified = true; 154 | } 155 | 156 | function validateVisitorMethods(path, val) { 157 | let fns = [].concat(val); 158 | for (let fn of fns) { 159 | if (typeof fn !== "function") { 160 | throw new TypeError(`Non-function found defined in ${path} with type ${typeof fn}`); 161 | } 162 | } 163 | } 164 | 165 | export function merge(visitors: Array, states: Array = [], wrapper?: ?Function) { 166 | let rootVisitor = {}; 167 | 168 | for (let i = 0; i < visitors.length; i++) { 169 | let visitor = visitors[i]; 170 | let state = states[i]; 171 | 172 | explode(visitor); 173 | 174 | for (let type in visitor) { 175 | let visitorType = visitor[type]; 176 | 177 | // if we have state or wrapper then overload the callbacks to take it 178 | if (state || wrapper) { 179 | visitorType = wrapWithStateOrWrapper(visitorType, state, wrapper); 180 | } 181 | 182 | let nodeVisitor = rootVisitor[type] = rootVisitor[type] || {}; 183 | mergePair(nodeVisitor, visitorType); 184 | } 185 | } 186 | 187 | return rootVisitor; 188 | } 189 | 190 | function wrapWithStateOrWrapper(oldVisitor, state, wrapper: ?Function) { 191 | let newVisitor = {}; 192 | 193 | for (let key in oldVisitor) { 194 | let fns = oldVisitor[key]; 195 | 196 | // not an enter/exit array of callbacks 197 | if (!Array.isArray(fns)) continue; 198 | 199 | fns = fns.map(function (fn) { 200 | let newFn = fn; 201 | 202 | if (state) { 203 | newFn = function (path) { 204 | return fn.call(state, path, state); 205 | }; 206 | } 207 | 208 | if (wrapper) { 209 | newFn = wrapper(state.key, key, newFn); 210 | } 211 | 212 | return newFn; 213 | }); 214 | 215 | newVisitor[key] = fns; 216 | } 217 | 218 | return newVisitor; 219 | } 220 | 221 | function ensureEntranceObjects(obj) { 222 | for (let key in obj) { 223 | if (shouldIgnoreKey(key)) continue; 224 | 225 | let fns = obj[key]; 226 | if (typeof fns === "function") { 227 | obj[key] = { enter: fns }; 228 | } 229 | } 230 | } 231 | 232 | function ensureCallbackArrays(obj) { 233 | if (obj.enter && !Array.isArray(obj.enter)) obj.enter = [obj.enter]; 234 | if (obj.exit && !Array.isArray(obj.exit)) obj.exit = [obj.exit]; 235 | } 236 | 237 | function wrapCheck(wrapper, fn) { 238 | let newFn = function (path) { 239 | if (wrapper.checkPath(path)) { 240 | return fn.apply(this, arguments); 241 | } 242 | }; 243 | newFn.toString = () => fn.toString(); 244 | return newFn; 245 | } 246 | 247 | function shouldIgnoreKey(key) { 248 | // internal/hidden key 249 | if (key[0] === "_") return true; 250 | 251 | // ignore function keys 252 | if (key === "enter" || key === "exit" || key === "shouldSkip") return true; 253 | 254 | // ignore other options 255 | if (key === "blacklist" || key === "noScope" || key === "skipKeys") return true; 256 | 257 | return false; 258 | } 259 | 260 | function mergePair(dest, src) { 261 | for (let key in src) { 262 | dest[key] = [].concat(dest[key] || [], src[key]); 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /lib/path/replacement.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _getIterator2 = require("babel-runtime/core-js/get-iterator"); 6 | 7 | var _getIterator3 = _interopRequireDefault(_getIterator2); 8 | 9 | exports.replaceWithMultiple = replaceWithMultiple; 10 | exports.replaceWithSourceString = replaceWithSourceString; 11 | exports.replaceWith = replaceWith; 12 | exports._replaceWith = _replaceWith; 13 | exports.replaceExpressionWithStatements = replaceExpressionWithStatements; 14 | exports.replaceInline = replaceInline; 15 | 16 | var _babelCodeFrame = require("babel-code-frame"); 17 | 18 | var _babelCodeFrame2 = _interopRequireDefault(_babelCodeFrame); 19 | 20 | var _index = require("../index"); 21 | 22 | var _index2 = _interopRequireDefault(_index); 23 | 24 | var _index3 = require("./index"); 25 | 26 | var _index4 = _interopRequireDefault(_index3); 27 | 28 | var _babylon = require("babylon"); 29 | 30 | var _babelTypes = require("babel-types"); 31 | 32 | var t = _interopRequireWildcard(_babelTypes); 33 | 34 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 35 | 36 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 37 | 38 | var hoistVariablesVisitor = { 39 | Function: function Function(path) { 40 | path.skip(); 41 | }, 42 | VariableDeclaration: function VariableDeclaration(path) { 43 | if (path.node.kind !== "var") return; 44 | 45 | var bindings = path.getBindingIdentifiers(); 46 | for (var key in bindings) { 47 | path.scope.push({ id: bindings[key] }); 48 | } 49 | 50 | var exprs = []; 51 | 52 | for (var _iterator = path.node.declarations, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3.default)(_iterator);;) { 53 | var _ref; 54 | 55 | if (_isArray) { 56 | if (_i >= _iterator.length) break; 57 | _ref = _iterator[_i++]; 58 | } else { 59 | _i = _iterator.next(); 60 | if (_i.done) break; 61 | _ref = _i.value; 62 | } 63 | 64 | var declar = _ref; 65 | 66 | if (declar.init) { 67 | exprs.push(t.expressionStatement(t.assignmentExpression("=", declar.id, declar.init))); 68 | } 69 | } 70 | 71 | path.replaceWithMultiple(exprs); 72 | } 73 | }; 74 | 75 | function replaceWithMultiple(nodes) { 76 | this.resync(); 77 | 78 | nodes = this._verifyNodeList(nodes); 79 | t.inheritLeadingComments(nodes[0], this.node); 80 | t.inheritTrailingComments(nodes[nodes.length - 1], this.node); 81 | this.node = this.container[this.key] = null; 82 | this.insertAfter(nodes); 83 | 84 | if (this.node) { 85 | this.requeue(); 86 | } else { 87 | this.remove(); 88 | } 89 | } 90 | 91 | function replaceWithSourceString(replacement) { 92 | this.resync(); 93 | 94 | try { 95 | replacement = "(" + replacement + ")"; 96 | replacement = (0, _babylon.parse)(replacement); 97 | } catch (err) { 98 | var loc = err.loc; 99 | if (loc) { 100 | err.message += " - make sure this is an expression."; 101 | err.message += "\n" + (0, _babelCodeFrame2.default)(replacement, loc.line, loc.column + 1); 102 | } 103 | throw err; 104 | } 105 | 106 | replacement = replacement.program.body[0].expression; 107 | _index2.default.removeProperties(replacement); 108 | return this.replaceWith(replacement); 109 | } 110 | 111 | function replaceWith(replacement) { 112 | this.resync(); 113 | 114 | if (this.removed) { 115 | throw new Error("You can't replace this node, we've already removed it"); 116 | } 117 | 118 | if (replacement instanceof _index4.default) { 119 | replacement = replacement.node; 120 | } 121 | 122 | if (!replacement) { 123 | throw new Error("You passed `path.replaceWith()` a falsy node, use `path.remove()` instead"); 124 | } 125 | 126 | if (this.node === replacement) { 127 | return; 128 | } 129 | 130 | if (this.isProgram() && !t.isProgram(replacement)) { 131 | throw new Error("You can only replace a Program root node with another Program node"); 132 | } 133 | 134 | if (Array.isArray(replacement)) { 135 | throw new Error("Don't use `path.replaceWith()` with an array of nodes, use `path.replaceWithMultiple()`"); 136 | } 137 | 138 | if (typeof replacement === "string") { 139 | throw new Error("Don't use `path.replaceWith()` with a source string, use `path.replaceWithSourceString()`"); 140 | } 141 | 142 | if (this.isNodeType("Statement") && t.isExpression(replacement)) { 143 | if (!this.canHaveVariableDeclarationOrExpression() && !this.canSwapBetweenExpressionAndStatement(replacement)) { 144 | replacement = t.expressionStatement(replacement); 145 | } 146 | } 147 | 148 | if (this.isNodeType("Expression") && t.isStatement(replacement)) { 149 | if (!this.canHaveVariableDeclarationOrExpression() && !this.canSwapBetweenExpressionAndStatement(replacement)) { 150 | return this.replaceExpressionWithStatements([replacement]); 151 | } 152 | } 153 | 154 | var oldNode = this.node; 155 | if (oldNode) { 156 | t.inheritsComments(replacement, oldNode); 157 | t.removeComments(oldNode); 158 | } 159 | 160 | this._replaceWith(replacement); 161 | this.type = replacement.type; 162 | 163 | this.setScope(); 164 | 165 | this.requeue(); 166 | } 167 | 168 | function _replaceWith(node) { 169 | if (!this.container) { 170 | throw new ReferenceError("Container is falsy"); 171 | } 172 | 173 | if (this.inList) { 174 | t.validate(this.parent, this.key, [node]); 175 | } else { 176 | t.validate(this.parent, this.key, node); 177 | } 178 | 179 | this.debug(function () { 180 | return "Replace with " + (node && node.type); 181 | }); 182 | 183 | this.node = this.container[this.key] = node; 184 | } 185 | 186 | function replaceExpressionWithStatements(nodes) { 187 | this.resync(); 188 | 189 | var toSequenceExpression = t.toSequenceExpression(nodes, this.scope); 190 | 191 | if (t.isSequenceExpression(toSequenceExpression)) { 192 | var exprs = toSequenceExpression.expressions; 193 | 194 | if (exprs.length >= 2 && this.parentPath.isExpressionStatement()) { 195 | this._maybePopFromStatements(exprs); 196 | } 197 | 198 | if (exprs.length === 1) { 199 | this.replaceWith(exprs[0]); 200 | } else { 201 | this.replaceWith(toSequenceExpression); 202 | } 203 | } else if (toSequenceExpression) { 204 | this.replaceWith(toSequenceExpression); 205 | } else { 206 | var container = t.functionExpression(null, [], t.blockStatement(nodes)); 207 | container.shadow = true; 208 | 209 | this.replaceWith(t.callExpression(container, [])); 210 | this.traverse(hoistVariablesVisitor); 211 | 212 | var completionRecords = this.get("callee").getCompletionRecords(); 213 | for (var _iterator2 = completionRecords, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : (0, _getIterator3.default)(_iterator2);;) { 214 | var _ref2; 215 | 216 | if (_isArray2) { 217 | if (_i2 >= _iterator2.length) break; 218 | _ref2 = _iterator2[_i2++]; 219 | } else { 220 | _i2 = _iterator2.next(); 221 | if (_i2.done) break; 222 | _ref2 = _i2.value; 223 | } 224 | 225 | var path = _ref2; 226 | 227 | if (!path.isExpressionStatement()) continue; 228 | 229 | var loop = path.findParent(function (path) { 230 | return path.isLoop(); 231 | }); 232 | if (loop) { 233 | var callee = this.get("callee"); 234 | 235 | var uid = callee.scope.generateDeclaredUidIdentifier("ret"); 236 | callee.get("body").pushContainer("body", t.returnStatement(uid)); 237 | 238 | path.get("expression").replaceWith(t.assignmentExpression("=", uid, path.node.expression)); 239 | } else { 240 | path.replaceWith(t.returnStatement(path.node.expression)); 241 | } 242 | } 243 | 244 | return this.node; 245 | } 246 | } 247 | 248 | function replaceInline(nodes) { 249 | this.resync(); 250 | 251 | if (Array.isArray(nodes)) { 252 | if (Array.isArray(this.container)) { 253 | nodes = this._verifyNodeList(nodes); 254 | this._containerInsertAfter(nodes); 255 | return this.remove(); 256 | } else { 257 | return this.replaceWithMultiple(nodes); 258 | } 259 | } else { 260 | return this.replaceWith(nodes); 261 | } 262 | } -------------------------------------------------------------------------------- /lib/path/modification.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _typeof2 = require("babel-runtime/helpers/typeof"); 6 | 7 | var _typeof3 = _interopRequireDefault(_typeof2); 8 | 9 | var _getIterator2 = require("babel-runtime/core-js/get-iterator"); 10 | 11 | var _getIterator3 = _interopRequireDefault(_getIterator2); 12 | 13 | exports.insertBefore = insertBefore; 14 | exports._containerInsert = _containerInsert; 15 | exports._containerInsertBefore = _containerInsertBefore; 16 | exports._containerInsertAfter = _containerInsertAfter; 17 | exports._maybePopFromStatements = _maybePopFromStatements; 18 | exports.insertAfter = insertAfter; 19 | exports.updateSiblingKeys = updateSiblingKeys; 20 | exports._verifyNodeList = _verifyNodeList; 21 | exports.unshiftContainer = unshiftContainer; 22 | exports.pushContainer = pushContainer; 23 | exports.hoist = hoist; 24 | 25 | var _cache = require("../cache"); 26 | 27 | var _hoister = require("./lib/hoister"); 28 | 29 | var _hoister2 = _interopRequireDefault(_hoister); 30 | 31 | var _index = require("./index"); 32 | 33 | var _index2 = _interopRequireDefault(_index); 34 | 35 | var _babelTypes = require("babel-types"); 36 | 37 | var t = _interopRequireWildcard(_babelTypes); 38 | 39 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 40 | 41 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 42 | 43 | function insertBefore(nodes) { 44 | this._assertUnremoved(); 45 | 46 | nodes = this._verifyNodeList(nodes); 47 | 48 | if (this.parentPath.isExpressionStatement() || this.parentPath.isLabeledStatement()) { 49 | return this.parentPath.insertBefore(nodes); 50 | } else if (this.isNodeType("Expression") || this.parentPath.isForStatement() && this.key === "init") { 51 | if (this.node) nodes.push(this.node); 52 | this.replaceExpressionWithStatements(nodes); 53 | } else { 54 | this._maybePopFromStatements(nodes); 55 | if (Array.isArray(this.container)) { 56 | return this._containerInsertBefore(nodes); 57 | } else if (this.isStatementOrBlock()) { 58 | if (this.node) nodes.push(this.node); 59 | this._replaceWith(t.blockStatement(nodes)); 60 | } else { 61 | throw new Error("We don't know what to do with this node type. We were previously a Statement but we can't fit in here?"); 62 | } 63 | } 64 | 65 | return [this]; 66 | } 67 | 68 | function _containerInsert(from, nodes) { 69 | this.updateSiblingKeys(from, nodes.length); 70 | 71 | var paths = []; 72 | 73 | for (var i = 0; i < nodes.length; i++) { 74 | var to = from + i; 75 | var node = nodes[i]; 76 | this.container.splice(to, 0, node); 77 | 78 | if (this.context) { 79 | var path = this.context.create(this.parent, this.container, to, this.listKey); 80 | 81 | if (this.context.queue) path.pushContext(this.context); 82 | paths.push(path); 83 | } else { 84 | paths.push(_index2.default.get({ 85 | parentPath: this.parentPath, 86 | parent: this.parent, 87 | container: this.container, 88 | listKey: this.listKey, 89 | key: to 90 | })); 91 | } 92 | } 93 | 94 | var contexts = this._getQueueContexts(); 95 | 96 | for (var _iterator = paths, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3.default)(_iterator);;) { 97 | var _ref; 98 | 99 | if (_isArray) { 100 | if (_i >= _iterator.length) break; 101 | _ref = _iterator[_i++]; 102 | } else { 103 | _i = _iterator.next(); 104 | if (_i.done) break; 105 | _ref = _i.value; 106 | } 107 | 108 | var _path = _ref; 109 | 110 | _path.setScope(); 111 | _path.debug(function () { 112 | return "Inserted."; 113 | }); 114 | 115 | for (var _iterator2 = contexts, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : (0, _getIterator3.default)(_iterator2);;) { 116 | var _ref2; 117 | 118 | if (_isArray2) { 119 | if (_i2 >= _iterator2.length) break; 120 | _ref2 = _iterator2[_i2++]; 121 | } else { 122 | _i2 = _iterator2.next(); 123 | if (_i2.done) break; 124 | _ref2 = _i2.value; 125 | } 126 | 127 | var context = _ref2; 128 | 129 | context.maybeQueue(_path, true); 130 | } 131 | } 132 | 133 | return paths; 134 | } 135 | 136 | function _containerInsertBefore(nodes) { 137 | return this._containerInsert(this.key, nodes); 138 | } 139 | 140 | function _containerInsertAfter(nodes) { 141 | return this._containerInsert(this.key + 1, nodes); 142 | } 143 | 144 | function _maybePopFromStatements(nodes) { 145 | var last = nodes[nodes.length - 1]; 146 | var isIdentifier = t.isIdentifier(last) || t.isExpressionStatement(last) && t.isIdentifier(last.expression); 147 | 148 | if (isIdentifier && !this.isCompletionRecord()) { 149 | nodes.pop(); 150 | } 151 | } 152 | 153 | function insertAfter(nodes) { 154 | this._assertUnremoved(); 155 | 156 | nodes = this._verifyNodeList(nodes); 157 | 158 | if (this.parentPath.isExpressionStatement() || this.parentPath.isLabeledStatement()) { 159 | return this.parentPath.insertAfter(nodes); 160 | } else if (this.isNodeType("Expression") || this.parentPath.isForStatement() && this.key === "init") { 161 | if (this.node) { 162 | var temp = this.scope.generateDeclaredUidIdentifier(); 163 | nodes.unshift(t.expressionStatement(t.assignmentExpression("=", temp, this.node))); 164 | nodes.push(t.expressionStatement(temp)); 165 | } 166 | this.replaceExpressionWithStatements(nodes); 167 | } else { 168 | this._maybePopFromStatements(nodes); 169 | if (Array.isArray(this.container)) { 170 | return this._containerInsertAfter(nodes); 171 | } else if (this.isStatementOrBlock()) { 172 | if (this.node) nodes.unshift(this.node); 173 | this._replaceWith(t.blockStatement(nodes)); 174 | } else { 175 | throw new Error("We don't know what to do with this node type. We were previously a Statement but we can't fit in here?"); 176 | } 177 | } 178 | 179 | return [this]; 180 | } 181 | 182 | function updateSiblingKeys(fromIndex, incrementBy) { 183 | if (!this.parent) return; 184 | 185 | var paths = _cache.path.get(this.parent); 186 | for (var i = 0; i < paths.length; i++) { 187 | var path = paths[i]; 188 | if (path.key >= fromIndex) { 189 | path.key += incrementBy; 190 | } 191 | } 192 | } 193 | 194 | function _verifyNodeList(nodes) { 195 | if (!nodes) { 196 | return []; 197 | } 198 | 199 | if (nodes.constructor !== Array) { 200 | nodes = [nodes]; 201 | } 202 | 203 | for (var i = 0; i < nodes.length; i++) { 204 | var node = nodes[i]; 205 | var msg = void 0; 206 | 207 | if (!node) { 208 | msg = "has falsy node"; 209 | } else if ((typeof node === "undefined" ? "undefined" : (0, _typeof3.default)(node)) !== "object") { 210 | msg = "contains a non-object node"; 211 | } else if (!node.type) { 212 | msg = "without a type"; 213 | } else if (node instanceof _index2.default) { 214 | msg = "has a NodePath when it expected a raw object"; 215 | } 216 | 217 | if (msg) { 218 | var type = Array.isArray(node) ? "array" : typeof node === "undefined" ? "undefined" : (0, _typeof3.default)(node); 219 | throw new Error("Node list " + msg + " with the index of " + i + " and type of " + type); 220 | } 221 | } 222 | 223 | return nodes; 224 | } 225 | 226 | function unshiftContainer(listKey, nodes) { 227 | this._assertUnremoved(); 228 | 229 | nodes = this._verifyNodeList(nodes); 230 | 231 | var path = _index2.default.get({ 232 | parentPath: this, 233 | parent: this.node, 234 | container: this.node[listKey], 235 | listKey: listKey, 236 | key: 0 237 | }); 238 | 239 | return path.insertBefore(nodes); 240 | } 241 | 242 | function pushContainer(listKey, nodes) { 243 | this._assertUnremoved(); 244 | 245 | nodes = this._verifyNodeList(nodes); 246 | 247 | var container = this.node[listKey]; 248 | var path = _index2.default.get({ 249 | parentPath: this, 250 | parent: this.node, 251 | container: container, 252 | listKey: listKey, 253 | key: container.length 254 | }); 255 | 256 | return path.replaceWithMultiple(nodes); 257 | } 258 | 259 | function hoist() { 260 | var scope = arguments.length <= 0 || arguments[0] === undefined ? this.scope : arguments[0]; 261 | 262 | var hoister = new _hoister2.default(this, scope); 263 | return hoister.run(); 264 | } -------------------------------------------------------------------------------- /lib/visitors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _typeof2 = require("babel-runtime/helpers/typeof"); 6 | 7 | var _typeof3 = _interopRequireDefault(_typeof2); 8 | 9 | var _keys = require("babel-runtime/core-js/object/keys"); 10 | 11 | var _keys2 = _interopRequireDefault(_keys); 12 | 13 | var _getIterator2 = require("babel-runtime/core-js/get-iterator"); 14 | 15 | var _getIterator3 = _interopRequireDefault(_getIterator2); 16 | 17 | exports.explode = explode; 18 | exports.verify = verify; 19 | exports.merge = merge; 20 | 21 | var _virtualTypes = require("./path/lib/virtual-types"); 22 | 23 | var virtualTypes = _interopRequireWildcard(_virtualTypes); 24 | 25 | var _babelMessages = require("babel-messages"); 26 | 27 | var messages = _interopRequireWildcard(_babelMessages); 28 | 29 | var _babelTypes = require("babel-types"); 30 | 31 | var t = _interopRequireWildcard(_babelTypes); 32 | 33 | var _clone = require("lodash/clone"); 34 | 35 | var _clone2 = _interopRequireDefault(_clone); 36 | 37 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 38 | 39 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 40 | 41 | function explode(visitor) { 42 | if (visitor._exploded) return visitor; 43 | visitor._exploded = true; 44 | 45 | for (var nodeType in visitor) { 46 | if (shouldIgnoreKey(nodeType)) continue; 47 | 48 | var parts = nodeType.split("|"); 49 | if (parts.length === 1) continue; 50 | 51 | var fns = visitor[nodeType]; 52 | delete visitor[nodeType]; 53 | 54 | for (var _iterator = parts, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3.default)(_iterator);;) { 55 | var _ref; 56 | 57 | if (_isArray) { 58 | if (_i >= _iterator.length) break; 59 | _ref = _iterator[_i++]; 60 | } else { 61 | _i = _iterator.next(); 62 | if (_i.done) break; 63 | _ref = _i.value; 64 | } 65 | 66 | var part = _ref; 67 | 68 | visitor[part] = fns; 69 | } 70 | } 71 | 72 | verify(visitor); 73 | 74 | delete visitor.__esModule; 75 | 76 | ensureEntranceObjects(visitor); 77 | 78 | ensureCallbackArrays(visitor); 79 | 80 | for (var _iterator2 = (0, _keys2.default)(visitor), _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : (0, _getIterator3.default)(_iterator2);;) { 81 | var _ref2; 82 | 83 | if (_isArray2) { 84 | if (_i2 >= _iterator2.length) break; 85 | _ref2 = _iterator2[_i2++]; 86 | } else { 87 | _i2 = _iterator2.next(); 88 | if (_i2.done) break; 89 | _ref2 = _i2.value; 90 | } 91 | 92 | var _nodeType3 = _ref2; 93 | 94 | if (shouldIgnoreKey(_nodeType3)) continue; 95 | 96 | var wrapper = virtualTypes[_nodeType3]; 97 | if (!wrapper) continue; 98 | 99 | var _fns2 = visitor[_nodeType3]; 100 | for (var type in _fns2) { 101 | _fns2[type] = wrapCheck(wrapper, _fns2[type]); 102 | } 103 | 104 | delete visitor[_nodeType3]; 105 | 106 | if (wrapper.types) { 107 | for (var _iterator4 = wrapper.types, _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : (0, _getIterator3.default)(_iterator4);;) { 108 | var _ref4; 109 | 110 | if (_isArray4) { 111 | if (_i4 >= _iterator4.length) break; 112 | _ref4 = _iterator4[_i4++]; 113 | } else { 114 | _i4 = _iterator4.next(); 115 | if (_i4.done) break; 116 | _ref4 = _i4.value; 117 | } 118 | 119 | var _type = _ref4; 120 | 121 | if (visitor[_type]) { 122 | mergePair(visitor[_type], _fns2); 123 | } else { 124 | visitor[_type] = _fns2; 125 | } 126 | } 127 | } else { 128 | mergePair(visitor, _fns2); 129 | } 130 | } 131 | 132 | for (var _nodeType in visitor) { 133 | if (shouldIgnoreKey(_nodeType)) continue; 134 | 135 | var _fns = visitor[_nodeType]; 136 | 137 | var aliases = t.FLIPPED_ALIAS_KEYS[_nodeType]; 138 | 139 | var deprecratedKey = t.DEPRECATED_KEYS[_nodeType]; 140 | if (deprecratedKey) { 141 | console.trace("Visitor defined for " + _nodeType + " but it has been renamed to " + deprecratedKey); 142 | aliases = [deprecratedKey]; 143 | } 144 | 145 | if (!aliases) continue; 146 | 147 | delete visitor[_nodeType]; 148 | 149 | for (var _iterator3 = aliases, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : (0, _getIterator3.default)(_iterator3);;) { 150 | var _ref3; 151 | 152 | if (_isArray3) { 153 | if (_i3 >= _iterator3.length) break; 154 | _ref3 = _iterator3[_i3++]; 155 | } else { 156 | _i3 = _iterator3.next(); 157 | if (_i3.done) break; 158 | _ref3 = _i3.value; 159 | } 160 | 161 | var alias = _ref3; 162 | 163 | var existing = visitor[alias]; 164 | if (existing) { 165 | mergePair(existing, _fns); 166 | } else { 167 | visitor[alias] = (0, _clone2.default)(_fns); 168 | } 169 | } 170 | } 171 | 172 | for (var _nodeType2 in visitor) { 173 | if (shouldIgnoreKey(_nodeType2)) continue; 174 | 175 | ensureCallbackArrays(visitor[_nodeType2]); 176 | } 177 | 178 | return visitor; 179 | } 180 | 181 | function verify(visitor) { 182 | if (visitor._verified) return; 183 | 184 | if (typeof visitor === "function") { 185 | throw new Error(messages.get("traverseVerifyRootFunction")); 186 | } 187 | 188 | for (var nodeType in visitor) { 189 | if (nodeType === "enter" || nodeType === "exit") { 190 | validateVisitorMethods(nodeType, visitor[nodeType]); 191 | } 192 | 193 | if (shouldIgnoreKey(nodeType)) continue; 194 | 195 | if (t.TYPES.indexOf(nodeType) < 0) { 196 | throw new Error(messages.get("traverseVerifyNodeType", nodeType)); 197 | } 198 | 199 | var visitors = visitor[nodeType]; 200 | if ((typeof visitors === "undefined" ? "undefined" : (0, _typeof3.default)(visitors)) === "object") { 201 | for (var visitorKey in visitors) { 202 | if (visitorKey === "enter" || visitorKey === "exit") { 203 | validateVisitorMethods(nodeType + "." + visitorKey, visitors[visitorKey]); 204 | } else { 205 | throw new Error(messages.get("traverseVerifyVisitorProperty", nodeType, visitorKey)); 206 | } 207 | } 208 | } 209 | } 210 | 211 | visitor._verified = true; 212 | } 213 | 214 | function validateVisitorMethods(path, val) { 215 | var fns = [].concat(val); 216 | for (var _iterator5 = fns, _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : (0, _getIterator3.default)(_iterator5);;) { 217 | var _ref5; 218 | 219 | if (_isArray5) { 220 | if (_i5 >= _iterator5.length) break; 221 | _ref5 = _iterator5[_i5++]; 222 | } else { 223 | _i5 = _iterator5.next(); 224 | if (_i5.done) break; 225 | _ref5 = _i5.value; 226 | } 227 | 228 | var fn = _ref5; 229 | 230 | if (typeof fn !== "function") { 231 | throw new TypeError("Non-function found defined in " + path + " with type " + (typeof fn === "undefined" ? "undefined" : (0, _typeof3.default)(fn))); 232 | } 233 | } 234 | } 235 | 236 | function merge(visitors) { 237 | var states = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; 238 | var wrapper = arguments[2]; 239 | 240 | var rootVisitor = {}; 241 | 242 | for (var i = 0; i < visitors.length; i++) { 243 | var visitor = visitors[i]; 244 | var state = states[i]; 245 | 246 | explode(visitor); 247 | 248 | for (var type in visitor) { 249 | var visitorType = visitor[type]; 250 | 251 | if (state || wrapper) { 252 | visitorType = wrapWithStateOrWrapper(visitorType, state, wrapper); 253 | } 254 | 255 | var nodeVisitor = rootVisitor[type] = rootVisitor[type] || {}; 256 | mergePair(nodeVisitor, visitorType); 257 | } 258 | } 259 | 260 | return rootVisitor; 261 | } 262 | 263 | function wrapWithStateOrWrapper(oldVisitor, state, wrapper) { 264 | var newVisitor = {}; 265 | 266 | var _loop = function _loop(key) { 267 | var fns = oldVisitor[key]; 268 | 269 | if (!Array.isArray(fns)) return "continue"; 270 | 271 | fns = fns.map(function (fn) { 272 | var newFn = fn; 273 | 274 | if (state) { 275 | newFn = function newFn(path) { 276 | return fn.call(state, path, state); 277 | }; 278 | } 279 | 280 | if (wrapper) { 281 | newFn = wrapper(state.key, key, newFn); 282 | } 283 | 284 | return newFn; 285 | }); 286 | 287 | newVisitor[key] = fns; 288 | }; 289 | 290 | for (var key in oldVisitor) { 291 | var _ret = _loop(key); 292 | 293 | if (_ret === "continue") continue; 294 | } 295 | 296 | return newVisitor; 297 | } 298 | 299 | function ensureEntranceObjects(obj) { 300 | for (var key in obj) { 301 | if (shouldIgnoreKey(key)) continue; 302 | 303 | var fns = obj[key]; 304 | if (typeof fns === "function") { 305 | obj[key] = { enter: fns }; 306 | } 307 | } 308 | } 309 | 310 | function ensureCallbackArrays(obj) { 311 | if (obj.enter && !Array.isArray(obj.enter)) obj.enter = [obj.enter]; 312 | if (obj.exit && !Array.isArray(obj.exit)) obj.exit = [obj.exit]; 313 | } 314 | 315 | function wrapCheck(wrapper, fn) { 316 | var newFn = function newFn(path) { 317 | if (wrapper.checkPath(path)) { 318 | return fn.apply(this, arguments); 319 | } 320 | }; 321 | newFn.toString = function () { 322 | return fn.toString(); 323 | }; 324 | return newFn; 325 | } 326 | 327 | function shouldIgnoreKey(key) { 328 | if (key[0] === "_") return true; 329 | 330 | if (key === "enter" || key === "exit" || key === "shouldSkip") return true; 331 | 332 | if (key === "blacklist" || key === "noScope" || key === "skipKeys") return true; 333 | 334 | return false; 335 | } 336 | 337 | function mergePair(dest, src) { 338 | for (var key in src) { 339 | dest[key] = [].concat(dest[key] || [], src[key]); 340 | } 341 | } -------------------------------------------------------------------------------- /lib/path/evaluation.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _typeof2 = require("babel-runtime/helpers/typeof"); 6 | 7 | var _typeof3 = _interopRequireDefault(_typeof2); 8 | 9 | var _getIterator2 = require("babel-runtime/core-js/get-iterator"); 10 | 11 | var _getIterator3 = _interopRequireDefault(_getIterator2); 12 | 13 | var _map = require("babel-runtime/core-js/map"); 14 | 15 | var _map2 = _interopRequireDefault(_map); 16 | 17 | exports.evaluateTruthy = evaluateTruthy; 18 | exports.evaluate = evaluate; 19 | 20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 21 | 22 | var VALID_CALLEES = ["String", "Number", "Math"]; 23 | var INVALID_METHODS = ["random"]; 24 | 25 | function evaluateTruthy() { 26 | var res = this.evaluate(); 27 | if (res.confident) return !!res.value; 28 | } 29 | 30 | function evaluate() { 31 | var confident = true; 32 | var deoptPath = void 0; 33 | var seen = new _map2.default(); 34 | 35 | function deopt(path) { 36 | if (!confident) return; 37 | deoptPath = path; 38 | confident = false; 39 | } 40 | 41 | var value = evaluate(this); 42 | if (!confident) value = undefined; 43 | return { 44 | confident: confident, 45 | deopt: deoptPath, 46 | value: value 47 | }; 48 | 49 | function evaluate(path) { 50 | var node = path.node; 51 | 52 | 53 | if (seen.has(node)) { 54 | var existing = seen.get(node); 55 | if (existing.resolved) { 56 | return existing.value; 57 | } else { 58 | deopt(path); 59 | return; 60 | } 61 | } else { 62 | var item = { resolved: false }; 63 | seen.set(node, item); 64 | 65 | var val = _evaluate(path); 66 | if (confident) { 67 | item.resolved = true; 68 | item.value = val; 69 | } 70 | return val; 71 | } 72 | } 73 | 74 | function _evaluate(path) { 75 | if (!confident) return; 76 | 77 | var node = path.node; 78 | 79 | 80 | if (path.isSequenceExpression()) { 81 | var exprs = path.get("expressions"); 82 | return evaluate(exprs[exprs.length - 1]); 83 | } 84 | 85 | if (path.isStringLiteral() || path.isNumericLiteral() || path.isBooleanLiteral()) { 86 | return node.value; 87 | } 88 | 89 | if (path.isNullLiteral()) { 90 | return null; 91 | } 92 | 93 | if (path.isTemplateLiteral()) { 94 | var str = ""; 95 | 96 | var i = 0; 97 | var _exprs = path.get("expressions"); 98 | 99 | for (var _iterator = node.quasis, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3.default)(_iterator);;) { 100 | var _ref; 101 | 102 | if (_isArray) { 103 | if (_i >= _iterator.length) break; 104 | _ref = _iterator[_i++]; 105 | } else { 106 | _i = _iterator.next(); 107 | if (_i.done) break; 108 | _ref = _i.value; 109 | } 110 | 111 | var elem = _ref; 112 | 113 | if (!confident) break; 114 | 115 | str += elem.value.cooked; 116 | 117 | var expr = _exprs[i++]; 118 | if (expr) str += String(evaluate(expr)); 119 | } 120 | 121 | if (!confident) return; 122 | return str; 123 | } 124 | 125 | if (path.isConditionalExpression()) { 126 | var testResult = evaluate(path.get("test")); 127 | if (!confident) return; 128 | if (testResult) { 129 | return evaluate(path.get("consequent")); 130 | } else { 131 | return evaluate(path.get("alternate")); 132 | } 133 | } 134 | 135 | if (path.isExpressionWrapper()) { 136 | return evaluate(path.get("expression")); 137 | } 138 | 139 | if (path.isMemberExpression() && !path.parentPath.isCallExpression({ callee: node })) { 140 | var property = path.get("property"); 141 | var object = path.get("object"); 142 | 143 | if (object.isLiteral() && property.isIdentifier()) { 144 | var _value = object.node.value; 145 | var type = typeof _value === "undefined" ? "undefined" : (0, _typeof3.default)(_value); 146 | if (type === "number" || type === "string") { 147 | return _value[property.node.name]; 148 | } 149 | } 150 | } 151 | 152 | if (path.isReferencedIdentifier()) { 153 | var binding = path.scope.getBinding(node.name); 154 | 155 | if (binding && binding.constantViolations.length > 0) { 156 | return deopt(binding.path); 157 | } 158 | 159 | if (binding && binding.hasValue) { 160 | return binding.value; 161 | } else { 162 | if (node.name === "undefined") { 163 | return undefined; 164 | } else if (node.name === "Infinity") { 165 | return Infinity; 166 | } else if (node.name === "NaN") { 167 | return NaN; 168 | } 169 | 170 | var resolved = path.resolve(); 171 | if (resolved === path) { 172 | return deopt(path); 173 | } else { 174 | return evaluate(resolved); 175 | } 176 | } 177 | } 178 | 179 | if (path.isUnaryExpression({ prefix: true })) { 180 | if (node.operator === "void") { 181 | return undefined; 182 | } 183 | 184 | var argument = path.get("argument"); 185 | if (node.operator === "typeof" && (argument.isFunction() || argument.isClass())) { 186 | return "function"; 187 | } 188 | 189 | var arg = evaluate(argument); 190 | if (!confident) return; 191 | switch (node.operator) { 192 | case "!": 193 | return !arg; 194 | case "+": 195 | return +arg; 196 | case "-": 197 | return -arg; 198 | case "~": 199 | return ~arg; 200 | case "typeof": 201 | return typeof arg === "undefined" ? "undefined" : (0, _typeof3.default)(arg); 202 | } 203 | } 204 | 205 | if (path.isArrayExpression()) { 206 | var arr = []; 207 | var elems = path.get("elements"); 208 | for (var _iterator2 = elems, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : (0, _getIterator3.default)(_iterator2);;) { 209 | var _ref2; 210 | 211 | if (_isArray2) { 212 | if (_i2 >= _iterator2.length) break; 213 | _ref2 = _iterator2[_i2++]; 214 | } else { 215 | _i2 = _iterator2.next(); 216 | if (_i2.done) break; 217 | _ref2 = _i2.value; 218 | } 219 | 220 | var _elem = _ref2; 221 | 222 | _elem = _elem.evaluate(); 223 | 224 | if (_elem.confident) { 225 | arr.push(_elem.value); 226 | } else { 227 | return deopt(_elem); 228 | } 229 | } 230 | return arr; 231 | } 232 | 233 | if (path.isObjectExpression()) {} 234 | 235 | if (path.isLogicalExpression()) { 236 | var wasConfident = confident; 237 | var left = evaluate(path.get("left")); 238 | var leftConfident = confident; 239 | confident = wasConfident; 240 | var right = evaluate(path.get("right")); 241 | var rightConfident = confident; 242 | confident = leftConfident && rightConfident; 243 | 244 | switch (node.operator) { 245 | case "||": 246 | if (left && leftConfident) { 247 | confident = true; 248 | return left; 249 | } 250 | 251 | if (!confident) return; 252 | 253 | return left || right; 254 | case "&&": 255 | if (!left && leftConfident || !right && rightConfident) { 256 | confident = true; 257 | } 258 | 259 | if (!confident) return; 260 | 261 | return left && right; 262 | } 263 | } 264 | 265 | if (path.isBinaryExpression()) { 266 | var _left = evaluate(path.get("left")); 267 | if (!confident) return; 268 | var _right = evaluate(path.get("right")); 269 | if (!confident) return; 270 | 271 | switch (node.operator) { 272 | case "-": 273 | return _left - _right; 274 | case "+": 275 | return _left + _right; 276 | case "/": 277 | return _left / _right; 278 | case "*": 279 | return _left * _right; 280 | case "%": 281 | return _left % _right; 282 | case "**": 283 | return Math.pow(_left, _right); 284 | case "<": 285 | return _left < _right; 286 | case ">": 287 | return _left > _right; 288 | case "<=": 289 | return _left <= _right; 290 | case ">=": 291 | return _left >= _right; 292 | case "==": 293 | return _left == _right; 294 | case "!=": 295 | return _left != _right; 296 | case "===": 297 | return _left === _right; 298 | case "!==": 299 | return _left !== _right; 300 | case "|": 301 | return _left | _right; 302 | case "&": 303 | return _left & _right; 304 | case "^": 305 | return _left ^ _right; 306 | case "<<": 307 | return _left << _right; 308 | case ">>": 309 | return _left >> _right; 310 | case ">>>": 311 | return _left >>> _right; 312 | } 313 | } 314 | 315 | if (path.isCallExpression()) { 316 | var callee = path.get("callee"); 317 | var context = void 0; 318 | var func = void 0; 319 | 320 | if (callee.isIdentifier() && !path.scope.getBinding(callee.node.name, true) && VALID_CALLEES.indexOf(callee.node.name) >= 0) { 321 | func = global[node.callee.name]; 322 | } 323 | 324 | if (callee.isMemberExpression()) { 325 | var _object = callee.get("object"); 326 | var _property = callee.get("property"); 327 | 328 | if (_object.isIdentifier() && _property.isIdentifier() && VALID_CALLEES.indexOf(_object.node.name) >= 0 && INVALID_METHODS.indexOf(_property.node.name) < 0) { 329 | context = global[_object.node.name]; 330 | func = context[_property.node.name]; 331 | } 332 | 333 | if (_object.isLiteral() && _property.isIdentifier()) { 334 | var _type = (0, _typeof3.default)(_object.node.value); 335 | if (_type === "string" || _type === "number") { 336 | context = _object.node.value; 337 | func = context[_property.node.name]; 338 | } 339 | } 340 | } 341 | 342 | if (func) { 343 | var args = path.get("arguments").map(evaluate); 344 | if (!confident) return; 345 | 346 | return func.apply(context, args); 347 | } 348 | } 349 | 350 | deopt(path); 351 | } 352 | } --------------------------------------------------------------------------------