A collection of transforms for use with facebook/jscodeshift.
- π© Installation
- πΉ Usage
- βοΈ Contributing
- π API
- β Quick Intro To Making A Codemod
- ππΎββοΈ Getting Help
git clone https://github.com/JamieMason/codemods.git
cd codemods
yarn install
# yarn
yarn name-of-the-transform <path-to-file>
# npm
npm run name-of-the-transform -- <path-to-file>
# jscodeshift
jscodeshift -t ./transforms/name-of-the-transform.js <path-to-file>
Transforms can be created at ./transforms/<transform-name>.js
and tested by
adding example input files at
./test/fixtures/<transform-name>/<scenario-name>.input.js
with the
corresponding expected output alongside it at
./test/fixtures/<transform-name>/<scenario-name>.output.js
.
All fixtures are discovered and tested when running yarn test
.
Convert JSX props which are expressions for a string literal, into just a string literal.
/* INPUT */
const SomeComponent = () => (
<AnotherComponent
foo={'string'}
label={`template with 0 substitutions`}
whatever={`template with ${1} substitution`}
/>
);
/* OUTPUT */
const SomeComponent = () => (
<AnotherComponent
foo="string"
label="template with 0 substitutions"
whatever={`template with ${1} substitution`}
/>
);
Import React if it is missing from a file which uses JSX.
/* INPUT */
export const Component = () => <div />
/* OUTPUT */
import React from "react";
export const Component = () => <div />
Rewrite deep imports to import from a packages' root index.
Set the Environment Variable
IMPORT_FROM_ROOT
to apply this transform only to packages whose name starts with that string:IMPORT_FROM_ROOT=some-package yarn import-from-root <path-to-file>
/* INPUT */
import { foo } from "some-package/foo/bar/baz";
/* OUTPUT */
import { foo } from "some-package";
Remove use of React PropTypes.
/* INPUT */
import React from 'react'
import PropTypes from 'prop-types'
export const Greet = ({ name }) => <span>Hi {name}</span>
Greet.propTypes = { name: PropTypes.string }
/* OUTPUT */
import React from 'react'
export const Greet = ({ name }) => <span>Hi {name}</span>
Remove use of React defaultProps.
/* INPUT */
import React from 'react'
export const Greet = ({ name }) => <span>Hi {name}</span>
Greet.defaultProps = { name: 'Stranger' }
/* OUTPUT */
import React from 'react'
export const Greet = ({ name }) => <span>Hi {name}</span>
Sort props of JSX Components alphabetically.
/* INPUT */
<Music zootWoman={true} rickJames={true} zapp={true} />
/* OUTPUT */
<Music rickJames={true} zapp={true} zootWoman={true} />
Sort members of Object Literals alphabetically.
/* INPUT */
const players = { messi: true, bergkamp: true, ginola: true };
/* OUTPUT */
const players = { bergkamp: true, ginola: true, messi: true };
Naively convert a default export to a named export using the name of the file, which may clash with other variable names in the file. This codemod would need following up on with ESLint and some manual fixes.
/* INPUT */
// ~/Dev/repo/src/apps/health/server.js
export default mount("/health", app);
/* OUTPUT */
// ~/Dev/repo/src/apps/health/server.js
export const server = mount("/health", app);
Naively convert a default import to a named import using the original name, which may not match what the module is actually exporting. This codemod would need following up on with ESLint and some manual fixes.
/* INPUT */
import masthead from "@sky-uk/koa-masthead";
/* OUTPUT */
import { masthead } from "@sky-uk/koa-masthead";
- Open ASTExplorer with the Parser set to
esprima
and Transform set tojscodeshift
. - Paste some Source in the Top-Left Panel which you want to Transform.
- Edit your Codemod in the Bottom-Left Panel.
There will be 4 Panels:
Panel | Purpose |
---|---|
Top-Left | Source you want to transform |
Top-Right | The AST of your Source |
Bottom-Left | Your Codemod Script |
Bottom-Right | The Result of applying your Codemod to your Source |
The docs for jscodeshift aren't
enough and you will need to refer to ast-type definitions to know what is
available. Using VariableDeclaration as an example, you
can find all variable declarations using the PascalCase j.VariableDeclaration
j(file.source).find(j.VariableDeclaration);
and create new variable declarations using the camelCase j.variableDeclarator
.
j.variableDeclaration("const", [
j.variableDeclarator(j.identifier(fileName), exportedValue)
]);
The VariableDeclaration definition shows what fields
it
takes, which are the arguments the j.variableDeclarator
function takes, which
are an Identifier
(a variable with a name but no value), or a
VariableDeclarator
(a variable with a name as well as a value assigned).
If we look up the definition of VariableDeclarator we see
it takes two arguments called id
and init
. The id
is an identifier
j.identifier('varName')
and init
is a value to initialise the variable with,
which should be the AST of whatever value you want to assign. For a simple
literal value, that would be j.literal('hello')
.
Putting that all together you have a Hello World Codemod of:
export default (file, api) => {
const j = api.jscodeshift;
// Have a look in the console at what APIs are available
console.log({
jscodeshiftAPI: j,
fileAPI: j(file.source)
});
return j(file.source)
.find(j.Program)
.forEach(path => {
// Unwrap the AST node from this wrapper
const emptyFile = path.value;
// add a comment
const singleLineComment = j.commentLine(" Hello World");
const varName = j.identifier("hello");
emptyFile.comments = [singleLineComment];
// add a const
const literalString = j.literal("world");
const nameValuePair = j.variableDeclarator(varName, literalString);
const constVarStatement = j.variableDeclaration("const", [nameValuePair]);
emptyFile.body = [constVarStatement];
})
.toSource();
};
Good luck!
- Get help with issues by creating a Bug Report.
- Discuss ideas by opening a Feature Request.