Skip to content

Instantly share code, notes, and snippets.

@donaldpipowitch
Created November 3, 2022 09:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save donaldpipowitch/f6e3aaf419405c2f95b649db1a51aff3 to your computer and use it in GitHub Desktop.
Save donaldpipowitch/f6e3aaf419405c2f95b649db1a51aff3 to your computer and use it in GitHub Desktop.
Codemod to add missing import, if a certain JSX Element is encountered

I have a small helper file called prettiermod.js which I use to run codemods. You need to copy'n'paste the prettiermod.js file and install the dependencies ($ pnpm add globby@^11.0.0 @babel/traverse prettier).

After this you can create new codemods which use prettiermod.js. The following codemod adds an import in case we find a specific component (add import { SuspenseLoader } from 'src/components/spinner-container'; in case <SuspenseLoader/> can be found).

Just call $ node run-codemod.js to execute the codemod.

I use AST explorer for debugging (@babel/parser with flow disabled and typescript enabled and babelv7).

// @ts-check
const globby = require('globby'); // v11
const { readFile, writeFile } = require('fs/promises');
const prettier = require('prettier');
const traverse = require('@babel/traverse');
/**
* @typedef {{
* patterns: string | string[];
* write?: boolean;
* prettierConfig: any;
* visitors: any[];
* }} PrettiermodOptions
*/
module.exports = async function prettiermod(
/** @type {PrettiermodOptions} */ {
patterns,
write = false,
prettierConfig,
visitors,
}
) {
const files = await globby(patterns);
await Promise.all(
files.map(async (file) => {
const code = await readFile(file, 'utf-8');
const result = prettier.format(code, {
...prettierConfig,
parser(code, { 'babel-ts': babel }) {
const ast = babel(code);
visitors.forEach((visitor) =>
traverse.default(ast, visitor({ file, ast }))
);
return ast;
},
});
if (write) {
await writeFile(file, result);
} else {
console.log(result);
}
})
);
};
const prettiermod = require('./prettiermod');
const prettierConfig = require('./prettier.config');
const addMissingImport = ({ ast }) => ({
JSXElement(path) {
// find <SuspenserLoader/>
if (path.node.openingElement.name.name !== 'SuspenseLoader') return;
// check if <SuspenseLoader/> is already imported
if (
ast.program.body.find(
(node) =>
node.type === 'ImportDeclaration' &&
node.specifiers[0]?.imported?.name === 'SuspenseLoader'
)
)
return;
// add missing import
const importNode = {
type: 'ImportDeclaration',
importKind: 'value',
extra: {
rawValue: 'src/components/spinner-container',
raw: "'src/components/spinner-container'",
},
specifiers: [
{
type: 'ImportSpecifier',
imported: {
type: 'Identifier',
name: 'SuspenseLoader',
},
},
],
source: {
type: 'StringLiteral',
value: 'src/components/spinner-container',
extra: {
rawValue: 'src/components/spinner-container',
raw: "'src/components/spinner-container'",
},
},
};
ast.program.body.unshift(importNode);
},
});
prettiermod({
patterns: ['src/app.tsx'], // I usually run previews of the changes on a single file - `write` is set to false
// patterns: ['{src,stories,tests,.storybook}/**/*.{ts,tsx}'],
prettierConfig,
visitors: [addMissingImport],
write: false, // flip to true to persist the change
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment