Babel: Determine the variable value in the current scope

Viewed 1378

Let's say I have the following code I want to transform using a custom Babel plugin:

let c;
c = document;
console.log(c.readyState);

The goal would be to replace all occurrences of document.readyState with a custom function, e.g. window.getDocumentReadyState(). So the output should look something like this:

let c;
c = document;
console.log(window.getDocumentReadyState());

The difficulty here is to determine which value the object c actually has when the MemberExpression Visitor is called, as I just want to replace MemberExpressions for document. That's why I need to find out if the current value of c is document.

Here's an implementation which just replaces every <obj>.readyState MemberExpression:

      /**
       * Replace document.readyState with
       * window.getDocumentReadyState();
       */
      MemberExpression(path) {
        const { node, parent } = path;
        const objName = node.object.name;
        const propName = node.property.name;

        if (t.isAssignmentExpression(parent)) {
          return;
        }

        if (t.isCallExpression(parent)) {
          const isCallee = parent.callee === node;
          if (isCallee) return;
        }

        if (propName === 'readyState') {
          const customReadyStateFn = t.callExpression(
            t.memberExpression(
              t.identifier('window'),
              t.identifier('getDocumentReadyState'),
            ),
            [t.identifier(objName)],
          );
          path.replaceWith(customReadyStateFn);
        }
      },

Using this implementation, I perform a runtime check to determine if the object is of type document (HTMLDocument) inside window.getDocumentReadyState, because I wasn't able to do the same using Babel. But there must be a way to reliably tell if this variable has the value document using static analysis, right?

Essentially, I need to find the last AssignmentExpression of this variable in the current scope.

I already tried looking the variable up in the scope using path.scope.getBinding(<variableName>), but the problem is that the last AssignmentExpression doesn't show up in binding.references. If the value was assigned during declaration (let c = document), it would be no problem, because that reference can be accessed using the binding.

I also tried traversing through the scope, but the AssignmentExpression visitor wasn't invoked.

I am fairly new to Babel and ASTs and reached a point where I don't know what to do next, and I would really like to get rid of that runtime check.

How would you solve such a problem?

1 Answers

Maybe a bit late, but I think it can be done.

You need to traverse through all left-side Identifiers and check if their right-side value is document. While doing so you keep an array with all the variable names that value is equal to document, and delete them from the array if their value changes and it is not document anymore.

Then when you traverse the MemberExpression check if the object name is in the array.

Related