codemods

code modifications with jscodeshift

Hi!

  • Björn Brauer (ZauberNerd)
  • Hamburg
  • Freelance -> FTE at Costa Digital

What are codemods?

  • code modifications
  • large-scale refactoring / code changes
  • usually automated; can also be manual
  • automates codemods with a toolkit
  • finds files based on extensions/patterns
  • asks for confirmation on diff
codemod -m -d src/ --extensions php,html \
    '<font *color="?(.*?)"?>(.*?)</font>' \
    '<span style="color: \1;">\2</span>'

Why?

Adopt new language features

function() {}
() => {}
Object.assign({}, a, b)
{...a, ...b}
React.createClass({
  render: function() {...}
})
class X extends Component {
  render() {...}
}

Automatically change language constructs throughout the whole code base

Adopt new APIs / migrate breaking changes

var X = React.createClass({
  mixins: [ReactGraphQL.Mixin],
  statics: {
    queries: {
      viewer: () => query`
        viewer { name }
      `,
    },
  },
  render() {...},
})

module.exports = X
var X = React.createClass({
  render() {...}
})

module.exports = Relay.createContainer(X, {
  queries: {
    viewer: () => query`
      viewer { name }
    `,
  },
})

Consistent coding style / patterns

  • Patterns in existing code are copied
  • Part of code base smells -> general health of code base declines

Manual codemods

  • changes get lost / are undocumented
  • parts that need to be updated are missed
  • work in master continues -> merge conflicts

Title Text

var X = React.createClass({
  mixins: [ReactGraphQL.Mixin],
  statics: {
    queries: {
      viewer: () => query`
        viewer { name }
      `,
    },
  },
  render() {...},
})

module.exports = X
var X = React.createClass({
  render() {...}
})

module.exports = Relay.createContainer(X, {
  queries: {
    viewer: () => query`
      viewer { name }
    `,
  },
})

Or switching from ava, tape or mocha to Jest

or upgrading from React.createClass to ES6 classes

(there's codemods for both cases)

Enter jscodeshift

Abstract Syntax Tree (AST)

  • jscodeshift leverages the AST
  • similiar to the DOM (or any other tree)
  • API with "Collection"s (similar to Arrays, jQuery, Backbone)

Built on recast

  • a tool to make transformations on an AST
  • pluggable parser (esprima, babel, flow)
  • pretty print the output
    • infer existing coding style / indentation
    • print only code that has actually changed
    • configurable options

parse -> find -> create -> update -> print

  • parse, print -> recast
  • find, create, update -> jscodeshift, ast-types

find

// simple example
j(file.source)
    .find(j.Identifier)
    .filter(path => /* check something */)

// structural pattern matching example
j(file.source)
    .find(j.CallExpression, {
        callee: {
            object: {
                name: 'console',
            },
            property: {
                name: 'log',
            },
        },
    })

create

// create AST nodes by hand
var identifier = {
    type: 'Identifier',
    name: 'hello',
};

// or use a tool, for example: ast-types

var identifier = j.identifier('hello');

update

// update a property of an AST node:
someAstNodeIdentifier.name = 'Hallo Hamburg!';

// replace an entire AST node / subtree:
j(path).replaceWith(myNewASTNode);

// insert a new AST node:
j(path).insertBefore(myNewASTNode);
j(path).insertAfter(myNewASTNode);

Demo

Questions?

Thank you for your attention!

References / learning material

codemods with jscodeshift

By Björn Brauer

codemods with jscodeshift

Automating large scale changes to big portions of your codebase with jscodeshift

  • 943