diff --git a/index.js b/index.js index 30ac154..e50bb7f 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,20 @@ +/** Check if node is a template literal with no expressions (e.g., `foo`) */ +function isConstantTemplateLiteral(t, node) { + return ( + t.isTemplateLiteral(node) && + node.expressions.length === 0 && + node.quasis.length === 1 + ); +} + +/** Create a template literal node with no expressions (e.g., `foo`) */ +function constantTemplateLiteral(t, value) { + return t.templateLiteral( + [t.templateElement({ raw: value, cooked: value }, true)], + [] + ); +} + module.exports = function ({ types: t }, options = {}) { const rename = options.rename || {}; @@ -78,6 +95,39 @@ module.exports = function ({ types: t }, options = {}) { path.skip(); }, }, + BinaryExpression: { + exit(path) { + const node = path.node; + if (node.operator !== "in") { + return; + } + + let oldName; + let isTemplateLiteral = false; + + if (t.isStringLiteral(node.left)) { + oldName = node.left.value; + } else if (isConstantTemplateLiteral(t, node.left)) { + oldName = node.left.quasis[0].value.cooked; + isTemplateLiteral = true; + } else { + return; + } + + const newName = nameMap.get(oldName); + if (newName === undefined) { + return; + } + + const newNode = isTemplateLiteral + ? constantTemplateLiteral(t, newName) + : t.stringLiteral(newName); + + const replacedNode = t.binaryExpression("in", newNode, node.right); + path.replaceWith(replacedNode); + path.skip(); + }, + }, }, }; }; diff --git a/test/index.test.js b/test/index.test.js index ee59b57..caa0d7a 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -194,4 +194,59 @@ describe("babel-plugin-transform-rename-properties", () => { }); }); }); + + describe("for `in` operator", () => { + it("renames string literal property", () => { + compare("'foo' in obj", "'__FOO__' in obj", { + rename: { foo: "__FOO__" }, + }); + }); + it("does not rename non-literal property", () => { + compare("prop in obj", "prop in obj", { + rename: { prop: "__PROP__" }, + }); + + compare("('prop' + '1') in obj", "('prop' + '1') in obj", { + rename: { prop: "__PROP__" }, + }); + }); + it("does not rename numeric literal property", () => { + compare("42 in obj", "42 in obj", { + rename: { 42: "__FORTY_TWO__" }, + }); + }); + it("renames multiple `in` expressions", () => { + compare( + "'foo' in obj && 'bar' in obj", + "'__FOO__' in obj && '__BAR__' in obj", + { + rename: { foo: "__FOO__", bar: "__BAR__" }, + } + ); + }); + it("renames `in` when right-hand side contains property access", () => { + compare("'foo' in obj.bar", "'__FOO__' in obj.__BAR__", { + rename: { foo: "__FOO__", bar: "__BAR__" }, + }); + }); + it("renames constant template literal property", () => { + compare("`foo` in obj", "`__FOO__` in obj", { + rename: { foo: "__FOO__" }, + }); + }); + it("does not rename non-constant template literal property", () => { + compare("`${prop}` in obj", "`${prop}` in obj", { + rename: { prop: "__PROP__" }, + }); + + compare("`prop${prop}` in obj", "`prop${prop}` in obj", { + rename: { prop: "__PROP__" }, + }); + }); + it("renames nested `in` expressions", () => { + compare("'foo' in ('bar' in obj)", "'__FOO__' in ('__BAR__' in obj)", { + rename: { foo: "__FOO__", bar: "__BAR__" }, + }); + }); + }); });