Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add codemod to replace object-keys #5

Merged
merged 3 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions codemods/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -29,4 +31,5 @@ export const codemods = {
'array.prototype.map': arrayPrototypeMap,
'array.prototype.filter': arrayPrototypeFilter,
'array-includes': arrayIncludes,
'object-keys': objectKeys,
};
85 changes: 85 additions & 0 deletions codemods/object-keys/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
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);

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
.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);
},
};
}
62 changes: 40 additions & 22 deletions codemods/shared.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,47 @@
/**
* 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 =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice 👍

importDeclaration.paths().length > 0
? importDeclaration.get().node.specifiers[0].local.name
: requireDeclaration.paths().length > 0
? requireDeclaration.find(j.Identifier).get().node.name
: null;

const requireDeclaration = root.find(j.VariableDeclarator, {
init: {
callee: {
name: 'require',
},
arguments: [
{
value: name,
},
],
},
});
importDeclaration.remove();
requireDeclaration.remove();

importDeclaration.remove();
requireDeclaration.remove();
return { identifier };
}
7 changes: 7 additions & 0 deletions test/fixtures/object-keys/case-1/after.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
var obj = {
a: true,
b: true,
c: true,
};

Object.keys(obj);
8 changes: 8 additions & 0 deletions test/fixtures/object-keys/case-1/before.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
var keys = require("object-keys");
var obj = {
a: true,
b: true,
c: true,
};

keys(obj);
7 changes: 7 additions & 0 deletions test/fixtures/object-keys/case-1/result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
var obj = {
a: true,
b: true,
c: true,
};

Object.keys(obj);
7 changes: 7 additions & 0 deletions test/fixtures/object-keys/case-2/after.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
var obj = {
a: true,
b: true,
c: true,
};

Object.keys(obj);
8 changes: 8 additions & 0 deletions test/fixtures/object-keys/case-2/before.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import keys from "object-keys";
var obj = {
a: true,
b: true,
c: true,
};

keys(obj);
7 changes: 7 additions & 0 deletions test/fixtures/object-keys/case-2/result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
var obj = {
a: true,
b: true,
c: true,
};

Object.keys(obj);
1 change: 1 addition & 0 deletions test/fixtures/object-keys/case-3/after.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log(Object.keys({ a: 1, b: "2", c: true }));
3 changes: 3 additions & 0 deletions test/fixtures/object-keys/case-3/before.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import banana from "object-keys";

console.log(banana({ a: 1, b: "2", c: true }));
1 change: 1 addition & 0 deletions test/fixtures/object-keys/case-3/result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log(Object.keys({ a: 1, b: "2", c: true }));
1 change: 1 addition & 0 deletions test/fixtures/object-keys/case-4/after.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log(Object.keys({ a: true }));
3 changes: 3 additions & 0 deletions test/fixtures/object-keys/case-4/before.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var keys = Object.keys || require("object-keys");

console.log(keys({ a: true }));
1 change: 1 addition & 0 deletions test/fixtures/object-keys/case-4/result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log(Object.keys({ a: true }));
Loading