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);
};
};
};
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.