microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.08k stars 12.37k forks source link

Crash when transformer mutates decorators on classes with initialized static fields #49794

Open rezonant opened 2 years ago

rezonant commented 2 years ago

Bug Report

🔎 Search Terms

🕗 Version & Regression Information

⏯ Playground Link

I'm not sure how to set up a Bug Workbench for transformers, but I do have a very terse isolated reproduction at https://github.com/typescript-rtti/ts-transform-bug

💻 Code

Code being transformed:

class A {
    static stuff = 'things';
}

Transformer:

import * as ts from 'typescript';

const transformer: (program: ts.Program) => ts.TransformerFactory<ts.SourceFile> = (program: ts.Program) => {
    const transform: ts.TransformerFactory<ts.SourceFile> = (context: ts.TransformationContext) => {
        return sourceFile => {
            console.log(`Transforming source file '${sourceFile.fileName}'...`);
            function visitor(node: ts.Node) {
                if (ts.isClassDeclaration(node)) {
                    return ts.factory.updateClassDeclaration(
                        node,
                        ts.factory.createNodeArray(node.decorators),
                        node.modifiers,
                        node.name,
                        node.typeParameters,
                        node.heritageClauses,
                        node.members
                    )
                }
                return ts.visitEachChild(node, visitor, context);
            }

            return ts.visitNode(sourceFile, visitor);
        }
    };

    return transform;
};

export default transformer;

🙁 Actual behavior

Error: Debug Failure. Invalid cast. The supplied value [object Object] did not pass the test 'isCallExpression'.
    at Object.cast (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:1847:25)
    at visitTypeScriptClassWrapper (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:101166:27)
    at visitCallExpression (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:101086:24)
    at visitorWorker (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:98718:28)
    at visitor (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:98639:44)
    at visitNode (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:89314:23)
    at Object.visitEachChild (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:89826:236)
    at visitVariableDeclaration (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:100136:30)
    at visitorWorker (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:98680:28)
    at visitor (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:98639:44)

🙂 Expected behavior

The transformer should be able to transform the class node without causing the above debug failure crash.

rezonant commented 2 years ago

On typescript@next (ie 4.8.0 prereleases), there have been some changes to decorators (ie they are with modifiers now)-- it no longer crashes simply by creating a new array with the existing decorators, but it does crash with a trivial change, again only when there is a static field with an initializer. Below I assume that there will be a global in the execution environment called aGlobalDecorator that can be called (equivalent to @aGlobalDecorator):

import * as ts from 'typescript';

const transformer: (program: ts.Program) => ts.TransformerFactory<ts.SourceFile> = (program: ts.Program) => {
     const transform: ts.TransformerFactory<ts.SourceFile> = (context: ts.TransformationContext) => {
        return sourceFile => {
            console.log(`Transforming source file '${sourceFile.fileName}'...`);
            function visitor(node: ts.Node) {
                if (ts.isClassDeclaration(node)) {
                    return ts.factory.updateClassDeclaration(
                        node,
                        [
                            <ts.ModifierLike>ts.factory.createDecorator(
                                ts.factory.createIdentifier('aGlobalDecorator')
                            )
                        ].concat(node.modifiers || []),
                        node.name,
                        node.typeParameters,
                        node.heritageClauses,
                        node.members
                    )
                }
                return ts.visitEachChild(node, visitor, context);
            }

            return ts.visitNode(sourceFile, visitor);
        }
     };

     return transform;
 };

 export default transformer;

Crash with typescript@4.8.0-dev.20220705:

D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:118860
                throw e;
                ^

Error: Debug Failure. Invalid cast. The supplied value [object Object] did not pass the test 'isCallExpression'.
    at Object.cast (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:1892:25)
    at visitTypeScriptClassWrapper (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:102419:27)
    at visitCallExpression (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:102339:24)
    at visitorWorker (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:99975:28)
    at visitor (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:99896:44)
    at visitNode (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:90295:23)
    at Object.visitEachChild (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:90833:236)
    at visitVariableDeclaration (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:101389:30)
    at visitorWorker (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:99937:28)
    at visitor (D:\Dev\typescript-rtti\ts-transform-bug\node_modules\typescript\lib\typescript.js:99896:44)