Skip to content

JavaScript codemods using jscodeshift

Notifications You must be signed in to change notification settings

ticklepoke/codemods

Repository files navigation

codemods CI

JavaScript codemods using jscodeshift

Usage

The npm package is namespaced to my username (@ticklepoke) for now as it is intended to be a personal project. If this package gains enough traction, I will consider finding a more elegant package name.

via git

git clone https://github.com/facebook/jscodeshift.git

Install the codeshift runner globally. There is a known bug where the runner doesnt work with yarn: 424.

npm install -g jscodeshift

Alternatively, one can format jscodeshift's binaries on every install using a tool such as dos2unix:

dos2unix node_modules/jscodeshift/bin/*

Install dependencies and build transforms

yarn install && yarn build

List available transforms

yarn transform:ls

Execute a codemod:

jscodeshift -t dist/[TRANSFORM FILENAME.js] [YOUR INPUT FILE.js]

via npm

# npm
npm install @ticklepoke/codemods

# yarn
yarn add @ticklepoke/codemods

Install jscodeshift and format with dos2unix if we want to use jscodeshift within a package.json script

yarn add jscodeshift &&
dos2unix node_modules/jscodeshift/bin/*

Add a transform to your package.json

{
  "scripts": {
    "start": "jscodeshift -d -p -t node_modules/@ticklepoke/codemods/[DESIRED TRANSFORM].js [YOUR INPUT FILE].js",
    "list:transform": "node node_modules/@ticklepoke/codemods/index.js",
    "fix:deps": "dos2unix node_modules/jscodeshift/bin/*"
  },
}

Development

Creating a new transform

To create a new transform, run

yarn plop

This will generate the transform file and relevant test and fixture files.

Testing

Fill in the fixture files with the input and output code. Then, run

yarn test

Branch Naming

Branches follow this naming convention:

  • For new transforms feat/transfrom-[transform_name]

  • For bug fixes: fix/[bug_description]

  • For other chores chore/[chore_description]

Transforms

remove-debugger

Removes all debugger statements

// Input code
1+1;
debugger;

// Output code
1+1;

remove-console

Removes all console statements, include destructured console statements

// Input code
console.log();

const { log } = console;

log();

const copiedLog = log; // Also handles this stupid case

copiedLog();

// Output code
// empty

convert-template-literal

Converts template literals without any template elements to string literals

// Input code
const a = `abcde`;
const b = `1234${true}`;


// Output code
const a = 'abcde';
const b = `1234${true}`;

convert-object-destructure

Converts variable declarations to object destructure. Also combines object destructures of the same member expression

// Input code
const a = obj.a;
const b = obj.b;
const d = obj.c;

const c = newObj.c;

function bar() {
  const a = obj.a;
  const c = obj.c;
  const d = obj.c;
  const e = otherObj.a;
}

// Output code
const {
    a,
    b,
    c: d
} = obj;

const {
  c
} = newObj;

function bar() {
    const {
        a,
        c,
        c: d
    } = obj;
    const {
      a: e
    } = otherObj;
}

convert-object-shorthand

Convert object expressions and object patterns that have the same key and value to shorthand

// Input code
const { a: a } = obj;

bar({a: a});


// Output code
const { a } = obj;

bar({ a });

convert-concat-strings

Converts string concatenations to template literals. Unable to handle escaped characters

// Input code
let vars = "b"
"a" + vars + "c"

// Output code
let vars = "b"
`a${vars}c`

convert-let-const

Converts variable declarations that are not reassigned from let to const.

  • Supports shadowed variables

  • Supports UpdateExpressions (a++)

  • Supports object patterns const { a, b } = .... Entire object is changed to const only if all properties are not reassigned.

  • Supports array patterns let [a, b] = .... Entire array pattern is changed to const only if all properties are not reassigned.

// Input code
let a = 1;
let b = 1;
b = 2;
{
  let a = 2;
  let b = 3;
  a = 3;
}

// Output code
const a = 1;
let b = 1;
b = 2;
{
  let a = 2;
  const b = 3;
  a = 3;
}

convert-function-expression-arrow

Transfrom function expressions to arrow functions without violating lexical this. Only converts if this is not used in the function body:

  • Preserves async function signatures

  • Does not transform generator functions (arrow functions cannot be generators)

// Input code
const a = function() {};
const b = function() { this };

// Output code
const a = () => {};
const b = function() { this };

convert-bind-arrow-function

Transform function expression with .bind(this) to arrow functions.

  • Preserves async function signatures

  • Does not transform generator functions (arrow functions cannot be generators)

// Input code
const a = function() {}.bind(this);

// Output code
const a = () => {};

convert-then-async

Convert .then() promises to async / await with support for catch and finally blocks

  • Supports .then() chaining

  • Only supports promises in a return statement

  • Only supports single param callbacks .then(a => ...) or .then(({a, b}) => ...). Multi params .then((a, b) => ...) and .then(({a, b}, c) => ...) are not allowed

  • Supports .then().catch().finally(), then().finally()

Improvements: Support for .then().finally().then()

// Input code
function bar() {
  return myPromise.then((a) => a).catch(e => e).finally()
}

// Output code
async function bar() {
  try {
    const a = await myPromise;
  } catch (e) {
    return e
  } finally {

  }
}

convert-spread-assign

Converts object spread to Object.assign, preserving order of properties being written

// Input code
const a = {
  a: 1,
  b: 2,
  ...otherObj
}

const b = {
  a: 1,
  ...otherObj,
  b:2
}

// Output code
const a = Object.assign({}, {a:1, b:2}, otherObj)

const b = Object.assign({}, {a:1}, otherObj, {b:2})

convert-chained-declarations

Converts chained variable declarations to individual declarations

// Input code
let a = b = [];
let c = d = 1;

// Output code
let b = [], a = b;

let d = 1, c = d;

convert-react-pure

Converts a react class component with only a render() method into a React.memo functional component

  • Considers both MethodDefinitions render() and ClassProperties render = () => {}
  • Does not modify render's function body before the return statement. This body could potentially have side effect which defeats the purpose of React.memo, but most idiomatic react code does not have side effects in render's body
  • Supports props detructuring: const { bar } = this.props becomes const { bar } = props
  • Removes this expression destructuring: const { props } = this
// Input code
class Foo extends React.Component {
  render() {
    return (
      <div>
        {this.props.bar}
      </div>
    )
  }
}

// Output code
const Foo = React.memo(function (props) {
  return (
    <div>
      {props.bar}
    </div>
  )
})

Road Map

Future transforms in the works. Feel free to open an issue if you would like to suggest another transform.

  • no-params-reassignment: Convert function params reassignment to scoped variables
// from this
bar(baz) {
    baz = 1;
}

// to this
bar(baz) {
    _baz = 1;
}
  • convert-await-loop-promise-all: Converts await statements in a loop into Promise.all()
// from this
let data = []
for (let i ....) {
    data.push(await fn())
}
return data

// to this
let promises = [];
for (let i ...) {
    promises.push(fn())
}
return Promise.all(promises)

React Codemods

  • destructure-props: Destructures react props:
// from this:
this.props.bar;

// to this
const { bar } = this.props;
bar;