[Javascript AST] 0. Introduction: Write a simple BabelJS plugin

To write a simple Babel plugin, we can use http://astexplorer.net/ to help us.

The plugin we want to write is:

var foo = 'o'
var bar = 'o'
foo === bar
function foo(foo, bar) {
 foo === bar;
}

We want to trasnform the code which highlighted in foo() function to:

_foo === _bar

Notice that, we also have 'foo === bar' which is not inside foo() function. But we don't want to touch that.

To get started, we have code below:

export default function(babel) {
  const { types: t } = babel;

  return {
    name: "add_underscore",
    visitor: {
      // your code here
    }
  };
}

All the code is under 'visitor' prop. 

By hightlight the code we can see, it is a 'BinaryExpression':

So we focus on 'BinaryExpression':

export default function(babel) {
  const { types: t } = babel;

  return {
    name: "add_underscore",
    visitor: {
      BinaryExpression(path) {

      }
    }
  };
}

'path' param contains all the information we need.

If add a console.log() inside BinarayExpression() function, we can see two logs, one is from global scope, another one is from 'foo()' scope. We only want to get one from foo() function scope:

The way to do is check 'path.scope.block.type', which is current code' block scope.

'foo === bar' exisits on global belongs to 'Program':

the one exists in foo() belongs to 'BlockStatement':

BinaryExpression(path) {
// remove global one
if (path.scope.block.type === "Program") { return; } }

And the opreator we want to check is '===':

      BinaryExpression(path) {
        if (path.scope.block.type === "Program") {
          return;
        }

        if (path.node.operator !== "===") {
          return;
        }
}

Now we have located the one 'foo === bar' we want, we can start to replace the name:

export default function(babel) {
  const { types: t } = babel;

  return {
    name: "add_underscore",
    visitor: {
      BinaryExpression(path) {
        if (path.scope.block.type === "Program") {
          return;
        }

        if (path.node.operator !== "===") {
          return;
        }

        // locate the 'foo' and 'bar'
        // as left and right Identifier
        const leftIdentifier = path.node.left;
        const rightIndentifier = path.node.right;

        // generate a new identifier
        const newLeftIdentifier = path.scope.generateUidIdentifier(leftIdentifier.name);
        const newRightIdentifier = path.scope.generateUidIdentifier(
          rightIndentifier.name
        );

        // replace the old with new one
        path.node.left = t.identifier(newLeftIdentifier.name);
        path.node.right = t.identifier(newRightIdentifier.name);
      }
    }
  };
}

Now the generate code looks like:

var foo = 'o'
var bar = 'o'
foo === bar
function foo(foo, bar) {
 _foo === _bar;
}

The code have successfully transform to '_foo === _bar'.

But clearly the code won't work, because _foo and _bar is undefined.

We need to update the params in the function as well.

        // update params in the function
        const [fooParam, barParam] = path.scope.block.params;
        fooParam.name = t.identifier(newLeftIdentifier.name).name;
        barParam.name = t.identifier(newRightIdentifier.name).name;

All Code:

export default function(babel) {
  const { types: t } = babel;

  return {
    name: "add_underscore",
    visitor: {
      BinaryExpression(path) {
        if (path.scope.block.type === "Program") {
          return;
        }

        if (path.node.operator !== "===") {
          return;
        }

        // locate the 'foo' and 'bar'
        // as left and right Identifier
        const leftIdentifier = path.node.left;
        const rightIndentifier = path.node.right;

        // generate a new identifier
        const newLeftIdentifier = path.scope.generateUidIdentifier(leftIdentifier.name);
        const newRightIdentifier = path.scope.generateUidIdentifier(
          rightIndentifier.name
        );

        // replace the old with new one
        path.node.left = t.identifier(newLeftIdentifier.name);
        path.node.right = t.identifier(newRightIdentifier.name);

        // update params in the function
        const [fooParam, barParam] = path.scope.block.params;
        fooParam.name = t.identifier(newLeftIdentifier.name).name;
        barParam.name = t.identifier(newRightIdentifier.name).name;
      }
    }
  };
}

[Notice]: this is a just learning note for myself. The approache might not be optimal. 

原文地址:https://www.cnblogs.com/Answer1215/p/7588047.html