Skip to content

Instantly share code, notes, and snippets.

@donaldpipowitch
Last active November 3, 2022 09:08
Show Gist options
  • Save donaldpipowitch/ba13d370df924fafc172e8a615dccfae to your computer and use it in GitHub Desktop.
Save donaldpipowitch/ba13d370df924fafc172e8a615dccfae to your computer and use it in GitHub Desktop.
Codemod to add an attribute to a JSX Element

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 a property with a default value to a specific component in case it is not there (flow="column" will be added to the <Stack /> component).

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 }))
);
return ast;
},
});
if (write) {
await writeFile(file, result);
} else {
console.log(result);
}
})
);
};
const prettiermod = require('./prettiermod');
const prettierConfig = require('./prettier.config');
const addMissingProp = ({ file }) => ({
JSXElement(path) {
// find "<Stack/>"" elements
if (path.node.openingElement.name.name !== 'Stack') return;
// check if "flow" attribute already exists
const flowAttr = path.node.openingElement.attributes.find(
(attr) => attr.type === 'JSXAttribute' && attr.name.name === 'flow'
);
if (flowAttr) return;
// add missing "flow" attribute
path.node.openingElement.attributes.unshift({
type: 'JSXAttribute',
name: { type: 'JSXIdentifier', name: 'flow' },
value: {
type: 'StringLiteral',
extra: { rawValue: 'column', raw: '"column"' },
value: 'column',
},
});
},
});
prettiermod({
patterns: ['src/components/action-group.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: [addMissingProp],
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