diff --git a/packages/babel-traverse/src/path/introspection.js b/packages/babel-traverse/src/path/introspection.js index 9b7e7f9813..342bba3e2f 100644 --- a/packages/babel-traverse/src/path/introspection.js +++ b/packages/babel-traverse/src/path/introspection.js @@ -12,54 +12,7 @@ import * as t from "babel-types"; */ export function matchesPattern(pattern: string, allowPartial?: boolean): boolean { - // not a member expression - if (!this.isMemberExpression()) return false; - - const parts = pattern.split("."); - const search = [this.node]; - let i = 0; - - function matches(name) { - const part = parts[i]; - return part === "*" || name === part; - } - - while (search.length) { - const node = search.shift(); - - if (allowPartial && i === parts.length) { - return true; - } - - if (t.isIdentifier(node)) { - // this part doesn't match - if (!matches(node.name)) return false; - } else if (t.isLiteral(node)) { - // this part doesn't match - if (!matches(node.value)) return false; - } else if (t.isMemberExpression(node)) { - if (node.computed && !t.isLiteral(node.property)) { - // we can't deal with this - return false; - } else { - search.unshift(node.property); - search.unshift(node.object); - continue; - } - } else if (t.isThisExpression(node)) { - if (!matches("this")) return false; - } else { - // we can't deal with this - return false; - } - - // too many parts - if (++i > parts.length) { - return false; - } - } - - return i === parts.length; + return t.matchesPattern(this.node, pattern, allowPartial); } /** diff --git a/packages/babel-types/src/index.js b/packages/babel-types/src/index.js index abee71ad06..a07cdf48c5 100644 --- a/packages/babel-types/src/index.js +++ b/packages/babel-types/src/index.js @@ -314,6 +314,51 @@ export function cloneDeep(node: Object): Object { return newNode; } +/** + * Determines whether or not the input node `member` matches the + * input `match`. + * + * For example, given the match `React.createClass` it would match the + * parsed nodes of `React.createClass` and `React["createClass"]`. + */ + +export function matchesPattern( + member: Object, + match: string | Array, + allowPartial?: boolean +): boolean { + // not a member expression + if (!t.isMemberExpression(member)) return false; + + const parts = Array.isArray(match) ? match : match.split("."); + const nodes = []; + + let node; + for (node = member; t.isMemberExpression(node); node = node.object) { + nodes.push(node.property); + } + nodes.push(node); + + if (nodes.length < parts.length) return false; + if (!allowPartial && nodes.length > parts.length) return false; + + for (let i = 0, j = nodes.length - 1; i < parts.length; i++, j--) { + const node = nodes[j]; + let value; + if (t.isIdentifier(node)) { + value = node.name; + } else if (t.isStringLiteral(node)) { + value = node.value; + } else { + return false; + } + + if (parts[i] !== value) return false; + } + + return true; +} + /** * Build a function that when called will return whether or not the * input `node` `MemberExpression` matches the input `match`. @@ -322,50 +367,10 @@ export function cloneDeep(node: Object): Object { * parsed nodes of `React.createClass` and `React["createClass"]`. */ -export function buildMatchMemberExpression(match:string, allowPartial?: boolean): Function { +export function buildMatchMemberExpression(match: string, allowPartial?: boolean): (Object) => boolean { const parts = match.split("."); - return function (member) { - // not a member expression - if (!t.isMemberExpression(member)) return false; - - const search = [member]; - let i = 0; - - while (search.length) { - const node = search.shift(); - - if (allowPartial && i === parts.length) { - return true; - } - - if (t.isIdentifier(node)) { - // this part doesn't match - if (parts[i] !== node.name) return false; - } else if (t.isStringLiteral(node)) { - // this part doesn't match - if (parts[i] !== node.value) return false; - } else if (t.isMemberExpression(node)) { - if (node.computed && !t.isStringLiteral(node.property)) { - // we can't deal with this - return false; - } else { - search.push(node.object); - search.push(node.property); - continue; - } - } else { - // we can't deal with this - return false; - } - - // too many parts - if (++i > parts.length) { - return false; - } - } - - return true; + return matchesPattern(member, parts, allowPartial); }; } diff --git a/packages/babel-types/test/misc.js b/packages/babel-types/test/misc.js new file mode 100644 index 0000000000..d49b675be4 --- /dev/null +++ b/packages/babel-types/test/misc.js @@ -0,0 +1,45 @@ +import * as t from "../lib"; +import { assert } from "chai"; +import { parse } from "babylon"; + +function parseCode(string) { + return parse(string, { + allowReturnOutsideFunction: true, + }).program.body[0]; +} + +describe("misc helpers", function () { + describe("matchesPattern", function () { + it("matches explicitly", function () { + const ast = parseCode("a.b.c.d").expression; + assert(t.matchesPattern(ast, "a.b.c.d")); + assert.isFalse(t.matchesPattern(ast, "a.b.c")); + assert.isFalse(t.matchesPattern(ast, "b.c.d")); + assert.isFalse(t.matchesPattern(ast, "a.b.c.d.e")); + }); + + it("matches partially", function () { + const ast = parseCode("a.b.c.d").expression; + assert(t.matchesPattern(ast, "a.b.c.d", true)); + assert(t.matchesPattern(ast, "a.b.c", true)); + assert.isFalse(t.matchesPattern(ast, "b.c.d", true)); + assert.isFalse(t.matchesPattern(ast, "a.b.c.d.e", true)); + }); + + it("matches string literal expressions", function () { + const ast = parseCode("a['b'].c.d").expression; + assert(t.matchesPattern(ast, "a.b.c.d")); + assert.isFalse(t.matchesPattern(ast, "a.b.c")); + assert.isFalse(t.matchesPattern(ast, "b.c.d")); + assert.isFalse(t.matchesPattern(ast, "a.b.c.d.e")); + }); + + it("matches string literal expressions partially", function () { + const ast = parseCode("a['b'].c.d").expression; + assert(t.matchesPattern(ast, "a.b.c.d", true)); + assert(t.matchesPattern(ast, "a.b.c", true)); + assert.isFalse(t.matchesPattern(ast, "b.c.d", true)); + assert.isFalse(t.matchesPattern(ast, "a.b.c.d.e", true)); + }); + }); +});