javascriptjqueryabstract-syntax-treejscodeshift

js code shift or transformer from jQuery to native?


So if I would need to change code like this:

var amazed = $(['foo', 'bar']).map(function(i, el){
  return this + '!';
});

into Native like

var amazed = (['foo', 'bar']).map(function(el, i){
  return el + '!';
});

I can do something like (https://astexplorer.net/#/0rIHMowCQf)

  return j(file.source)
    .find(j.Identifier).filter(ident => {
    if (ident.node.name == '$') console.log(ident);
    return ident.node.name == '$';
  }).replaceWith('').toSource();

as a first step and that will delete the jQuery $ sign and just leave a () which can work, but feels like I am cheating since I am just giving a empty Identifier to the CallExpression. I still have to discover how to replace the order of the arguments.

Can js code shift be used for such cases, like transforming jQuery to Native, and eventually just:

var amazed = ['foo', 'bar'].map(function(el, i){
  return el + '!';
});

Solution

  • You can absolute use jscodeshift for this. Just be aware of the limitations:

    However, these might not be issues in your code base. Writing generic codemods is hard(er). Writing one that works for your specific codebase is easier.

    I would to the following:

    Find all CallExpression's whose callee is a MemberExpression and that MemberExpression has map as its property and $(...) as its object. You can also verify that the argument passed to $ is an array literal. That would again have the limitation that it wouldn't consider var foo = []; $(foo);.

    Then you can replace the the "inner" CallExpression with its argument. Replacing the function parameters of the callback is simply to.

    All of that together. All the checks are optional. The less strict the tests are, the more use cases you can cover, but the possibility for getting false positives will also be larger.

      return j(file.source)
        .find(j.CallExpression, {
          callee: {
            property: {
              name: 'map'
            },
            // verify that we call map on $(...)
            object: {
               callee: {
                 name: '$',
               },
               // verify that the argument is an array literal
               arguments: {
                 0: {
                   type: 'ArrayExpression'
                 }
               },
            }
          },
        })
        .forEach(path => {
            const jQueryArgument = path.node.callee.object.arguments[0];
            // replace "inner" CallExpression with argument
            path.node.callee.object = jQueryArgument;
    
            // switch callback arguments
            var callback = path.node.arguments[0];
            callback.params.reverse();
        })
        .toSource();
    

    https://astexplorer.net/#/kQICtMfd89