ts-morph icon indicating copy to clipboard operation
ts-morph copied to clipboard

CallExpression.addTypeArgument crashes if the CallExpression's expression is a PropertyAccessExpression

Open zivarah opened this issue 3 years ago • 2 comments

Describe the bug

Version: 13.0.2

addTypeArgument() only seems to work for simple call expressions of the form foo(), but not forms like this.foo().

To Reproduce

Consider this source file:

function foo<T>(_param: T): void {
}

export class Test {
    public foo<T>(_param: T): void {
    }

    public bar(): void {
        this.foo({});
        new Test().foo({});
        foo({});
    }
}

And this program:

import * as AST from "ts-morph";

const project = new AST.Project();
const sourceFile = project.addSourceFileAtPath("./CallExprAddTypeArgument.ts");
sourceFile.getDescendantsOfKind(AST.SyntaxKind.CallExpression).forEach(callExpr => {
    callExpr.addTypeArgument("object");
});

We see the following crash for this.foo({}) and new Test().foo({}), while it works as expected for foo({})

.../node_modules/@ts-morph/common/dist/ts-morph-common.js:473
            throw new InvalidOperationError(typeof errorMessage === "string" ? errorMessage : errorMessage());
            ^

Error: A child of the kind Identifier was expected.
    at Object.throwIfNullOrUndefined (.../node_modules/@ts-morph/common/dist/ts-morph-common.js:473:19)
    at CallExpression.getFirstChildByKindOrThrow (.../node_modules/ts-morph/dist/ts-morph.js:3754:30)
    at CallExpression.insertTypeArguments (.../node_modules/ts-morph/dist/ts-morph.js:10006:41)
    at CallExpression.addTypeArguments (.../node_modules/ts-morph/dist/ts-morph.js:9995:25)
    at CallExpression.addTypeArgument (.../node_modules/ts-morph/dist/ts-morph.js:9992:25)
    ...

It appears as though insertTypeArguments assumes it will have an identifier as a direct child. In reality, it could be an expressioned node and thus the identifier is not a direct child.

Expected behavior

The type argument should be added without crashing for all three of the example call expressions.

zivarah avatar Nov 24 '21 04:11 zivarah

From some quick testing, I believe something like this in TypeArgumentedNode.ts addresses the issue, though I'm unsure if there are any more complicating factors.

    insertTypeArguments(index: number, argumentTexts: ReadonlyArray<string>) {
    ...
      if (typeArguments.length === 0) {
        const expression = Node.hasExpression(this) ? this.getExpression() : undefined;
        const nodeWithIdentifier = Node.isPropertyAccessExpression(expression) ? expression : this;
        const identifier = nodeWithIdentifier.getFirstChildByKindOrThrow(SyntaxKind.Identifier);
        insertIntoParentTextRange({
        ...

zivarah avatar Nov 24 '21 05:11 zivarah

I experience the same issue. Have you come around this?

johannesfritsch avatar Jan 24 '24 19:01 johannesfritsch