node.jsabstract-syntax-treejscodeshift

Organizing requires and moving them to document Top


I am organizing code in a app. The require statements are un-organized so I made this codemod to sort them and to add them on top of the page.

The codemod works, almost perfect. I have some doubts:

My Initial code:

var path = require('path');
var stylus = require('stylus');
var express = require('express');
var router = express.Router();
var async = require('async');

let restOfCode = 'foo';

My codemod:

let requires = j(file.source).find(j.CallExpression, {
    "callee": {
        "name": "require"
    }
}).closest(j.VariableDeclarator);

let sortedNames = requires.__paths.map(node => node.node.id.name).sort(sort); // ["async", "express", "path", "stylus"]
let sortedRequires = [];
requires.forEach(r => {
    let index = sortedNames.indexOf(r.node.id.name);
    sortedRequires[index] = j(r).closest(j.VariableDeclaration).__paths[0]; // <- feels like a hack
});

let sourceStart = j(sortedRequires).toSource();
let sourceRest = j(file.source).find(j.CallExpression, {
    "callee": {
        "name": "require"
    }
}).closest(j.VariableDeclaration)
.replaceWith((vD, i) => {
    // return nothing, it will be replaced on top of document
})
.toSource();

return sourceStart.concat(sourceRest).join('\n'); // is there a better way than [].concat(string).join(newLine) ?

And the result I got:

var async = require('async');
var express = require('express');
var path = require('path');
var stylus = require('stylus');
var router = express.Router(); // <- I would expect a empty line before this one

let restOfCode = 'foo';

Solution

  • is this a ok approach, or is there a more correct way to use the API?

    You shouldn't be accessing __paths directly. If you need to access all NodePaths, you can use the .paths() method. If you want to access the AST nodes, use .nodes().

    E.g. the mapping would just be

    let sortedNames = requires.nodes()(node => node.id.name).sort(sort);
    

    how can I keep the empty line between the sourceStart (all the requires) and the rest of the source code?

    There isn't really a good way to do this. See this related recast issue. Hopefully this will become easier one day with CSTs.

    can a similar approach be used in ES6 imports? (ie to sort them with jscodeshift)

    Certainly.


    FWIW, here is my version (based on your first version):

    export default function transformer(file, api) {
        const j = api.jscodeshift;
        const sort = (a, b) => a.declarations[0].id.name.localeCompare(
            b.declarations[0].id.name
        );
    
        const root = j(file.source);
        const requires = root
          .find(j.CallExpression, {"callee": {"name": "require"}})
          .closest(j.VariableDeclaration);
        const sortedRequires = requires.nodes().sort(sort);
    
        requires.remove();
    
        return root
          .find(j.Statement)
          .at(0)
          .insertBefore(sortedRequires)
          .toSource();
        };
    }
    

    https://astexplorer.net/#/i8v3GBENZ7