Skip to content

Instantly share code, notes, and snippets.

@rosston
Last active January 9, 2024 20:22
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rosston/9d5319b54ea695826dfc12f13c7e7cb4 to your computer and use it in GitHub Desktop.
Save rosston/9d5319b54ea695826dfc12f13c7e7cb4 to your computer and use it in GitHub Desktop.
jscodeshift "transform" to find uses of any lodash method
'use strict';
//
// Usage:
// jscodeshift -t find-lodash-method.js /path/to/dir/or/file --method-name=<lodash_method> [--min-args=<min_num_args>] [--chain-only=1]
//
// Prints all locations of matching uses of the lodash method (e.g., `map`).
//
// Function copied from https://github.com/jfmengels/lodash-codemods/blob/v1.0.1/lib/method-calls-conditional-name-changes.js#L163-L179
function isLodashChain(node) {
if (node.type === 'CallExpression') {
if (node.callee.type !== 'Identifier') {
return isLodashChain(node.callee);
}
return node.callee.name === '_';
}
if (node.type === 'MemberExpression') {
if (node.object.type !== 'Identifier') {
return isLodashChain(node.object);
}
return node.object.name === '_' &&
node.property.type === 'Identifier' &&
node.property.name === 'chain';
}
return false;
}
// Function copied from https://github.com/jfmengels/lodash-codemods/blob/v1.0.1/lib/method-calls-conditional-name-changes.js#L6-L8
const hasMinArgs = (n, p, isInChain) => {
return p.arguments.length >= n - (isInChain ? 1 : 0);
};
module.exports = function transformer(file, api, options) {
const chainOnly = options['chain-only'];
const methodName = options['method-name'];
const minArgs = options['min-args'];
const j = api.jscodeshift;
const ast = j(file.source);
const selector = {
type: j.CallExpression,
properties: {
callee: {
object: {name: '_'},
property: {name: methodName}
}
}
};
const matchHandler = (p) => {
const position = [
file.path,
p.value.loc.start.line,
p.value.loc.start.column
].join(':');
console.log(`\`_.${methodName}\` found at ${position}`);
};
if (!chainOnly) {
// Find unchained uses, e.g., `_.forEach`
ast.find(selector.type, selector.properties)
.filter((p) => (!minArgs || hasMinArgs(minArgs, p.value, false)))
.forEach(matchHandler);
}
// Find chained uses, e.g., `_(foo).forEach`
ast.find(selector.type, {callee: {property: {name: methodName}}})
.filter((p) => isLodashChain(p.value.callee.object))
.filter((p) => (!minArgs || hasMinArgs(minArgs, p.value, true)))
.forEach(matchHandler);
return file.source;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment