From f785b67677c526cdd461f0c72e758394d1c6c9a5 Mon Sep 17 00:00:00 2001 From: Cameron McHenry Date: Wed, 17 Jul 2024 23:20:13 -0400 Subject: [PATCH 1/3] Add codemod to replace object-keys --- codemods/index.js | 3 ++ codemods/object-keys/index.js | 39 ++++++++++++++ codemods/shared.js | 60 ++++++++++++++-------- test/fixtures/object-keys/case-1/after.js | 7 +++ test/fixtures/object-keys/case-1/before.js | 8 +++ test/fixtures/object-keys/case-1/result.js | 7 +++ test/fixtures/object-keys/case-2/after.js | 7 +++ test/fixtures/object-keys/case-2/before.js | 8 +++ test/fixtures/object-keys/case-2/result.js | 7 +++ test/fixtures/object-keys/case-3/after.js | 1 + test/fixtures/object-keys/case-3/before.js | 3 ++ test/fixtures/object-keys/case-3/result.js | 1 + 12 files changed, 129 insertions(+), 22 deletions(-) create mode 100644 codemods/object-keys/index.js create mode 100644 test/fixtures/object-keys/case-1/after.js create mode 100644 test/fixtures/object-keys/case-1/before.js create mode 100644 test/fixtures/object-keys/case-1/result.js create mode 100644 test/fixtures/object-keys/case-2/after.js create mode 100644 test/fixtures/object-keys/case-2/before.js create mode 100644 test/fixtures/object-keys/case-2/result.js create mode 100644 test/fixtures/object-keys/case-3/after.js create mode 100644 test/fixtures/object-keys/case-3/before.js create mode 100644 test/fixtures/object-keys/case-3/result.js diff --git a/codemods/index.js b/codemods/index.js index 36cbdc7..46a3e7c 100644 --- a/codemods/index.js +++ b/codemods/index.js @@ -18,6 +18,8 @@ import arrayPrototypeFilter from './array.prototype.filter/index.js'; import arrayIncludes from './array-includes/index.js'; +import objectKeys from './object-keys/index.js'; + export const codemods = { 'is-whitespace': isWhitespace, 'is-array-buffer': isArrayBuffer, @@ -29,4 +31,5 @@ export const codemods = { 'array.prototype.map': arrayPrototypeMap, 'array.prototype.filter': arrayPrototypeFilter, 'array-includes': arrayIncludes, + 'object-keys': objectKeys, }; diff --git a/codemods/object-keys/index.js b/codemods/object-keys/index.js new file mode 100644 index 0000000..430d987 --- /dev/null +++ b/codemods/object-keys/index.js @@ -0,0 +1,39 @@ +import jscodeshift from "jscodeshift"; +import { removeImport } from "../shared.js"; + +/** + * @typedef {import('../../types.js').Codemod} Codemod + * @typedef {import('../../types.js').CodemodOptions} CodemodOptions + */ + +/** + * @param {CodemodOptions} [options] + * @returns {Codemod} + */ +export default function (options) { + return { + name: "object-keys", + transform: ({ file }) => { + const j = jscodeshift; + const root = j(file.source); + + const { identifier } = removeImport("object-keys", root, j); + + // Replace `$identifier(obj)` with `Object.keys(obj)` + root + .find(j.CallExpression, { + callee: { + name: identifier, + }, + }) + .replaceWith(({ node }) => { + return j.callExpression( + j.memberExpression(j.identifier("Object"), j.identifier("keys")), + node.arguments + ); + }); + + return root.toSource(options); + }, + }; +} diff --git a/codemods/shared.js b/codemods/shared.js index 2ca148d..0bcb63e 100644 --- a/codemods/shared.js +++ b/codemods/shared.js @@ -1,29 +1,45 @@ +/** + * type definition for return type object + * @typedef {Object} RemoveImport + * @property {string} identifier - the name of the module as it was imported or required. for example, `keys` in `import keys from 'object-keys'` + */ + /** * @param {string} name - package name to remove import/require calls for - * @param {any} root - package name to remove import/require calls for - * @param {any} j - jscodeshift instance + * @param {import("jscodeshift").Collection} root - package name to remove import/require calls for + * @param {import("jscodeshift").JSCodeshift} j - jscodeshift instance + * @returns {RemoveImport} */ export function removeImport(name, root, j) { - // Find the import or require statement for 'is-boolean-object' - const importDeclaration = root.find(j.ImportDeclaration, { - source: { - value: name, - }, - }); + // Find the import or require statement for 'is-boolean-object' + const importDeclaration = root.find(j.ImportDeclaration, { + source: { + value: name, + }, + }); + + const requireDeclaration = root.find(j.VariableDeclarator, { + init: { + callee: { + name: "require", + }, + arguments: [ + { + value: name, + }, + ], + }, + }); + + // Return the identifier name, e.g. 'fn' in `import { fn } from 'is-boolean-object'` + // or `var fn = require('is-boolean-object')` + const identifier = + importDeclaration.paths().length > 0 + ? importDeclaration.get().node.specifiers[0].local.name + : requireDeclaration.find(j.Identifier).get().node.name; - const requireDeclaration = root.find(j.VariableDeclarator, { - init: { - callee: { - name: 'require', - }, - arguments: [ - { - value: name, - }, - ], - }, - }); + importDeclaration.remove(); + requireDeclaration.remove(); - importDeclaration.remove(); - requireDeclaration.remove(); + return { identifier }; } diff --git a/test/fixtures/object-keys/case-1/after.js b/test/fixtures/object-keys/case-1/after.js new file mode 100644 index 0000000..4567cd5 --- /dev/null +++ b/test/fixtures/object-keys/case-1/after.js @@ -0,0 +1,7 @@ +var obj = { + a: true, + b: true, + c: true, +}; + +Object.keys(obj); diff --git a/test/fixtures/object-keys/case-1/before.js b/test/fixtures/object-keys/case-1/before.js new file mode 100644 index 0000000..a93173f --- /dev/null +++ b/test/fixtures/object-keys/case-1/before.js @@ -0,0 +1,8 @@ +var keys = require("object-keys"); +var obj = { + a: true, + b: true, + c: true, +}; + +keys(obj); diff --git a/test/fixtures/object-keys/case-1/result.js b/test/fixtures/object-keys/case-1/result.js new file mode 100644 index 0000000..4567cd5 --- /dev/null +++ b/test/fixtures/object-keys/case-1/result.js @@ -0,0 +1,7 @@ +var obj = { + a: true, + b: true, + c: true, +}; + +Object.keys(obj); diff --git a/test/fixtures/object-keys/case-2/after.js b/test/fixtures/object-keys/case-2/after.js new file mode 100644 index 0000000..4567cd5 --- /dev/null +++ b/test/fixtures/object-keys/case-2/after.js @@ -0,0 +1,7 @@ +var obj = { + a: true, + b: true, + c: true, +}; + +Object.keys(obj); diff --git a/test/fixtures/object-keys/case-2/before.js b/test/fixtures/object-keys/case-2/before.js new file mode 100644 index 0000000..84ce921 --- /dev/null +++ b/test/fixtures/object-keys/case-2/before.js @@ -0,0 +1,8 @@ +import keys from "object-keys"; +var obj = { + a: true, + b: true, + c: true, +}; + +keys(obj); diff --git a/test/fixtures/object-keys/case-2/result.js b/test/fixtures/object-keys/case-2/result.js new file mode 100644 index 0000000..4567cd5 --- /dev/null +++ b/test/fixtures/object-keys/case-2/result.js @@ -0,0 +1,7 @@ +var obj = { + a: true, + b: true, + c: true, +}; + +Object.keys(obj); diff --git a/test/fixtures/object-keys/case-3/after.js b/test/fixtures/object-keys/case-3/after.js new file mode 100644 index 0000000..3d3f171 --- /dev/null +++ b/test/fixtures/object-keys/case-3/after.js @@ -0,0 +1 @@ +console.log(Object.keys({ a: 1, b: "2", c: true })); diff --git a/test/fixtures/object-keys/case-3/before.js b/test/fixtures/object-keys/case-3/before.js new file mode 100644 index 0000000..1c02c57 --- /dev/null +++ b/test/fixtures/object-keys/case-3/before.js @@ -0,0 +1,3 @@ +import banana from "object-keys"; + +console.log(banana({ a: 1, b: "2", c: true })); diff --git a/test/fixtures/object-keys/case-3/result.js b/test/fixtures/object-keys/case-3/result.js new file mode 100644 index 0000000..3d3f171 --- /dev/null +++ b/test/fixtures/object-keys/case-3/result.js @@ -0,0 +1 @@ +console.log(Object.keys({ a: 1, b: "2", c: true })); From 236a6592e6122810a0bfb6c406c44c79fed36e9b Mon Sep 17 00:00:00 2001 From: Cameron McHenry Date: Wed, 17 Jul 2024 23:44:49 -0400 Subject: [PATCH 2/3] Handle recommended usage --- codemods/object-keys/index.js | 49 +++++++++++++++++++++- codemods/shared.js | 4 +- test/fixtures/object-keys/case-4/after.js | 1 + test/fixtures/object-keys/case-4/before.js | 3 ++ test/fixtures/object-keys/case-4/result.js | 1 + 5 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/object-keys/case-4/after.js create mode 100644 test/fixtures/object-keys/case-4/before.js create mode 100644 test/fixtures/object-keys/case-4/result.js diff --git a/codemods/object-keys/index.js b/codemods/object-keys/index.js index 430d987..78d6c69 100644 --- a/codemods/object-keys/index.js +++ b/codemods/object-keys/index.js @@ -1,5 +1,6 @@ import jscodeshift from "jscodeshift"; import { removeImport } from "../shared.js"; +import { log } from "console"; /** * @typedef {import('../../types.js').Codemod} Codemod @@ -17,7 +18,53 @@ export default function (options) { const j = jscodeshift; const root = j(file.source); - const { identifier } = removeImport("object-keys", root, j); + let { identifier } = removeImport("object-keys", root, j); + + // Replace `$identifier(obj)` with `Object.keys(obj)` + root + .find(j.CallExpression, { + callee: { + name: identifier, + }, + }) + .replaceWith(({ node }) => { + return j.callExpression( + j.memberExpression(j.identifier("Object"), j.identifier("keys")), + node.arguments + ); + }); + + // Remove recommended usage of `var keys = Object.keys || require("object-keys")` + const logicalExpressionRequire = root.find(j.VariableDeclarator, { + init: { + type: "LogicalExpression", + left: { + object: { + name: "Object", + }, + property: { + name: "keys", + }, + }, + right: { + callee: { + name: "require", + }, + arguments: [ + { + value: "object-keys", + }, + ], + }, + }, + }); + + identifier = + logicalExpressionRequire.paths().length > 0 + ? logicalExpressionRequire.get().node.id.name + : null; + + logicalExpressionRequire.remove(); // Replace `$identifier(obj)` with `Object.keys(obj)` root diff --git a/codemods/shared.js b/codemods/shared.js index 0bcb63e..8cf8af0 100644 --- a/codemods/shared.js +++ b/codemods/shared.js @@ -36,7 +36,9 @@ export function removeImport(name, root, j) { const identifier = importDeclaration.paths().length > 0 ? importDeclaration.get().node.specifiers[0].local.name - : requireDeclaration.find(j.Identifier).get().node.name; + : requireDeclaration.paths().length > 0 + ? requireDeclaration.find(j.Identifier).get().node.name + : null; importDeclaration.remove(); requireDeclaration.remove(); diff --git a/test/fixtures/object-keys/case-4/after.js b/test/fixtures/object-keys/case-4/after.js new file mode 100644 index 0000000..186697a --- /dev/null +++ b/test/fixtures/object-keys/case-4/after.js @@ -0,0 +1 @@ +console.log(Object.keys({ a: true })); diff --git a/test/fixtures/object-keys/case-4/before.js b/test/fixtures/object-keys/case-4/before.js new file mode 100644 index 0000000..05d30b9 --- /dev/null +++ b/test/fixtures/object-keys/case-4/before.js @@ -0,0 +1,3 @@ +var keys = Object.keys || require("object-keys"); + +console.log(keys({ a: true })); diff --git a/test/fixtures/object-keys/case-4/result.js b/test/fixtures/object-keys/case-4/result.js new file mode 100644 index 0000000..186697a --- /dev/null +++ b/test/fixtures/object-keys/case-4/result.js @@ -0,0 +1 @@ +console.log(Object.keys({ a: true })); From fc3375ac69e8f72d0377dc9f0d7355dcf272541d Mon Sep 17 00:00:00 2001 From: Pascal Schilp Date: Thu, 18 Jul 2024 08:36:12 +0200 Subject: [PATCH 3/3] Update codemods/object-keys/index.js --- codemods/object-keys/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/codemods/object-keys/index.js b/codemods/object-keys/index.js index 78d6c69..73869dd 100644 --- a/codemods/object-keys/index.js +++ b/codemods/object-keys/index.js @@ -1,6 +1,5 @@ import jscodeshift from "jscodeshift"; import { removeImport } from "../shared.js"; -import { log } from "console"; /** * @typedef {import('../../types.js').Codemod} Codemod