diff --git a/packages/babel-traverse/src/path/family.js b/packages/babel-traverse/src/path/family.js index b27538edafb9a..f7a630d0a79c7 100644 --- a/packages/babel-traverse/src/path/family.js +++ b/packages/babel-traverse/src/path/family.js @@ -125,3 +125,64 @@ export function getBindingIdentifiers(duplicates?) { export function getOuterBindingIdentifiers(duplicates?) { return t.getOuterBindingIdentifiers(this.node, duplicates); } + +// original source - https://github.com/babel/babel/blob/master/packages/babel-types/src/retrievers.js +// path.getBindingIdentifiers returns nodes where the following re-implementation +// returns paths +export function getBindingIdentifierPaths(duplicates = false, outerOnly = false) { + let path = this; + let search = [].concat(path); + let ids = Object.create(null); + + while (search.length) { + let id = search.shift(); + if (!id) continue; + if (!id.node) continue; + + let keys = t.getBindingIdentifiers.keys[id.node.type]; + + if (id.isIdentifier()) { + if (duplicates) { + let _ids = ids[id.node.name] = ids[id.node.name] || []; + _ids.push(id); + } else { + ids[id.node.name] = id; + } + continue; + } + + if (id.isExportDeclaration()) { + const declaration = id.get("declaration"); + if (declaration.isDeclaration()) { + search.push(declaration); + } + continue; + } + + if (outerOnly) { + if (id.isFunctionDeclaration()) { + search.push(id.get("id")); + continue; + } + if (id.isFunctionExpression()) { + continue; + } + } + + if (keys) { + for (let i = 0; i < keys.length; i++) { + let key = keys[i]; + let child = id.get(key); + if (Array.isArray(child) || child.node) { + search = search.concat(child); + } + } + } + } + + return ids; +} + +export function getOuterBindingIdentifierPaths(duplicates?) { + return this.getBindingIdentifierPaths(duplicates, true); +} diff --git a/packages/babel-traverse/test/family.js b/packages/babel-traverse/test/family.js new file mode 100644 index 0000000000000..c6fc297f86a69 --- /dev/null +++ b/packages/babel-traverse/test/family.js @@ -0,0 +1,60 @@ +let traverse = require("../lib").default; +let assert = require("assert"); +let parse = require("babylon").parse; + +describe("path/family", function () { + describe("getBindingIdentifiers", function () { + let ast = parse("var a = 1, {b} = c, [d] = e; function f() {}"); + let nodes = {}, paths = {}, outerNodes = {}, outerPaths = {}; + traverse(ast, { + VariableDeclaration(path) { + nodes = path.getBindingIdentifiers(); + paths = path.getBindingIdentifierPaths(); + }, + FunctionDeclaration(path) { + outerNodes = path.getOuterBindingIdentifiers(); + outerPaths = path.getOuterBindingIdentifierPaths(); + } + }); + + it("should contain keys of nodes in paths", function () { + Object.keys(nodes).forEach((id) => { + assert.strictEqual(hop(paths, id), true, "Node's keys exists in paths"); + }); + }); + + it("should contain outer bindings", function () { + Object.keys(outerNodes).forEach((id) => { + assert.strictEqual(hop(outerPaths, id), true, "Has same outer keys"); + }); + }); + + it("should return paths", function () { + Object.keys(paths).forEach((id) => { + assert.strictEqual(!!paths[id].node, true, "Has a property node that's not falsy"); + assert.strictEqual(paths[id].type, paths[id].node.type, "type matches"); + }); + + Object.keys(outerPaths).forEach((id) => { + assert.strictEqual(!!outerPaths[id].node, true, "has property node"); + assert.strictEqual(outerPaths[id].type, outerPaths[id].node.type, "type matches"); + }); + }); + + it("should match paths and nodes returned for the same ast", function () { + Object.keys(nodes).forEach((id) => { + assert.strictEqual(nodes[id], paths[id].node, "Nodes match"); + }); + }); + + it("should match paths and nodes returned for outer Bindings", function () { + Object.keys(outerNodes).forEach((id) => { + assert.strictEqual(outerNodes[id], outerPaths[id].node, "nodes match"); + }); + }); + }); +}); + +function hop(o, key) { + return Object.hasOwnProperty.call(o, key); +}