Skip to content

Instantly share code, notes, and snippets.

@davidofwatkins
Last active February 21, 2023 17:23
Show Gist options
  • Save davidofwatkins/774be958d7f1e5b20bf8dbf46b4a8ba3 to your computer and use it in GitHub Desktop.
Save davidofwatkins/774be958d7f1e5b20bf8dbf46b4a8ba3 to your computer and use it in GitHub Desktop.
jscodeshift: convert `String.fmt()` to template literal

This is a JS codemod written in jscodeshift to transform custom String.fmt() calls to template literals. For example:

const firstAnimalType = 'fox';
const secondAnimalType = 'dog';

'The big red %s jumps over the lazy brown %s'.fmt(firstAnamalType, secondAnimalType);

...is converetd to:

const firstAnimalType = 'fox';
const secondAnimalType = 'dog';

`The big red ${firstAnamalType} jumps over the lazy brown ${secondAnimalType}`;

To apply this transform on a JS file, first install jscodeshift:

npm i -g jscodeshift

Then, save transform.js below and run it with jscodeshift for the desired file:

npx jscodeshift -t transform.js path/to/file/to/jsfile.js -p

Bulk Changes with Ripgrep

One way to run this for only files that contain String.fmt() is to first search for all files that have it and run the script only for thos files. In the below example rg is used to search for fmt():

rg "\.fmt\(" -t js --files-with-matches | xargs npx jscodeshift -t transform.js

Formatting

If your codebase uses a formatter like Prettier, remember to run it after applying changes!

// Command to run:
// npx jscodeshift -t transform.js path/to/file/to/jsfile.js [--print] [--dry]
//
// Or to re-run on save:
// echo "jscodeshift-transform.js" | entr bash -c 'npx jscodeshift -t transform.js path/to/file/to/transform.js -d -p'
export const parser = "babel";
const getTemplateElementsFromStringLiteral = (j, stringLiteral) =>
stringLiteral
.split("%s")
.map((part) =>
j.templateElement({ cooked: part.toString(), raw: part.toString() }, true)
)
.filter(Boolean);
const getExpressionsFromArgs = (j, args) =>
args.map((arg) => j.identifier(j(arg).toSource()));
export default (file, api) => {
const j = api.jscodeshift;
const replaceCallExpression = (callExpression) => {
const args = callExpression.value.arguments;
const stringLiteralValue = callExpression.value.callee.object.value;
const templateElements = getTemplateElementsFromStringLiteral(
j,
stringLiteralValue
);
const expressions = getExpressionsFromArgs(j, args);
j(callExpression).replaceWith(
j.templateLiteral(templateElements, expressions)
);
};
const callExpressions = j(file.source).find(j.CallExpression, {
callee: {
type: "MemberExpression",
object: {
type: "Literal",
},
property: {
type: "Identifier",
name: "fmt",
},
},
});
return callExpressions.forEach(replaceCallExpression).toSource();
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment