typescriptabstract-syntax-treets-loader

How can I properly transform the 'this' keyword in a typescript-transformer function?


I created a typescript transformer that traverses the AST and changes array calls from

myArr.push(el) to Array.prototype.push.call(myArr, args)

This works well for all cases apart the case where the array is using the this key word inside a class:

this.myArr.push(el) transforms to Array.prototype.push.call(this.myArr, args) while the correct transformation should be: this.myArr.push(el) to Array.prototype.push.call(_this.myArr, args) (the _this having been already created by the ts-loader plugin)

How can I achieve this?

This is the code of my transformer:

function getType(type) {
  if (type && type.symbol && type.symbol.name) {
    return type.symbol.name;
  } else if (
    type &&
    type.literalType &&
    type.literalType.symbol &&
    type.literalType.symbol.name
  ) {
    return type.literalType.symbol.name;
  }
  return null;
}
exports.__esModule = true;
const { ClassificationTypeNames } = require("typescript");
var ts = require("typescript");
var transformer = function (typechecker) {
  return function (context) {
    var visitor = function (node) {
      // 1. First check: chained expression
      // if it's array.filter().join().map() then I want to change only the first part
      // meaning 
      // if property access is a call expression - ignore and dont change
      if (
        ts.isCallExpression(node) &&
        ts.isPropertyAccessExpression(node.expression) &&
        ts.isCallExpression(node.expression.expression)

      ) {
        return ts.visitEachChild(node, visitor, context);
        
      }

      if (
        ts.isCallExpression(node) &&
        ts.isPropertyAccessExpression(node.expression)
      ) {
        const type = typechecker.getTypeAtLocation(node.expression.expression);
        const typeNameS = getType(type); 
        if (typeNameS === "Array") {
          const methodName = node.expression.name.getText();
          const callArgs = node.arguments;
          const identifier = node.expression.expression.getText();
          return ts.createCall(
            ts.createPropertyAccess(
              ts.createPropertyAccess(
                ts.createPropertyAccess(
                  ts.createIdentifier("Array"),
                  ts.createIdentifier("prototype")
                ),
                ts.createIdentifier(methodName)
              ),
              ts.createIdentifier("call")
            ),
            undefined,
            [ts.createIdentifier(identifier), ...callArgs]
          );
        }
      }

      return ts.visitEachChild(node, visitor, context);
    };
    return function (node) {
      return ts.visitNode(node, visitor);
    };
  };
};

Solution

  • Found the error.

    I was getting the identifier and creating it manually.

    const identifier = node.expression.expression.getText();
    

    and then

    ts.createIdentifier(identifier)
    

    The correct way to construct the new node is to pass node.expression.expression directly to the construction call.

     return ts.createCall(
            ts.createPropertyAccess(
              ts.createPropertyAccess(
                ts.createPropertyAccess(
                  ts.createIdentifier("Array"),
                  ts.createIdentifier("prototype")
                ),
                ts.createIdentifier(methodName)
              ),
              ts.createIdentifier("call")
            ),
            undefined,
            [node.expression.expression, ...callArgs]
          );
    

    and that way the typescript engine knows how to build the _this and this references correctly.