Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using createVariableStatement in a compiler transformer breaks the compiler #22372

Closed
pedro-pedrosa opened this issue Mar 7, 2018 · 4 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@pedro-pedrosa
Copy link

TypeScript Version: 2.8.0-dev.20180307

Search Terms: createVariableStatement const

Code (compiler)

// A *self-contained* demonstration of the problem follows...
import * as ts from 'typescript';

function transformer(program: ts.Program): ts.TransformerFactory<ts.SourceFile> {
    return (context: ts.TransformationContext) => (file: ts.SourceFile) => transformFile(program, context, file);
}

function transformFile(program: ts.Program, context: ts.TransformationContext, file: ts.SourceFile): ts.SourceFile {
    const transformedFile = ts.visitEachChild(file, child => visit(child, context, file), context);
    return transformedFile;
}
function visit(node: ts.Node, context: ts.TransformationContext, file: ts.SourceFile): ts.Node {
    if (ts.isMethodDeclaration(node)) {
        const newNode = ts.createMethod(
            [ts.createToken(ts.SyntaxKind.StaticKeyword)], 
            [], 
            null, 
            node.name, 
            null, 
            [], 
            node.parameters, 
            node.type, 
            ts.createBlock([ 
                ts.createVariableStatement(
                    [ts.createToken(ts.SyntaxKind.ConstKeyword)], 
                    [ts.createVariableDeclaration('myConst', null, ts.createLiteral('value'))]
                ),
                ...(node.body ? node.body.statements : [])
            ])
        );
        console.log(ts.createPrinter().printNode(ts.EmitHint.Unspecified, newNode, file));
        return newNode;
    }
    return ts.visitEachChild(node, child => visit(child, context, file), context);
}

const program = ts.createProgram([
  '../transformer-issue-src/src/A.ts'
], {
    target: ts.ScriptTarget.ES5,
    module: ts.ModuleKind.CommonJS,
    moduleResolution: ts.ModuleResolutionKind.NodeJs,
    importHelpers: true,
    alwaysStrict: true,
    noImplicitAny: true,
    noImplicitThis: true,
    removeComments: true,
    sourceMap: true,
    outDir: "../transformer-issue-src/lib",
    declaration: true,
    declarationDir: "../transformer-issue-src/lib",
    lib: [
      "lib.es2017.d.ts",
    ],
    experimentalDecorators: true,
    noEmitOnError: true,
});

const transformers = {
  before: [
    transformer(program),
  ]
}
const result = program.emit(undefined, undefined, undefined, false, transformers);

Code (test program, ../transformer-issue-src/src/A.ts)

export class A {
    static myMethod() {
        return 'value';
    }
}

Expected behavior:
The .js file should be emitted.
The console.log statement should print the following:

static myMethod() { const myConst = "value"; return 'value'; }

Actual behavior:
The .js file is not emitted due to a compiler exception. Message and stack trace:

TypeError: Cannot read property 'transformFlags' of null
    at aggregateTransformFlagsForNode (d:\solutions\transformer-issue-compiler\node_modules\typescript\lib\typescript.js:54626:18)
    at Object.aggregateTransformFlags (d:\solutions\transformer-issue-compiler\node_modules\typescript\lib\typescript.js:54611:9)
    at visitNode (d:\solutions\transformer-issue-compiler\node_modules\typescript\lib\typescript.js:53754:12)
    at Object.visitEachChild (d:\solutions\transformer-issue-compiler\node_modules\typescript\lib\typescript.js:54053:108)
    at visitVariableDeclaration (d:\solutions\transformer-issue-compiler\node_modules\typescript\lib\typescript.js:61625:30)
    at Object.flatMap (d:\solutions\transformer-issue-compiler\node_modules\typescript\lib\typescript.js:2047:25)
    at visitVariableDeclarationList (d:\solutions\transformer-issue-compiler\node_modules\typescript\lib\typescript.js:61510:39)
    at visitJavaScript (d:\solutions\transformer-issue-compiler\node_modules\typescript\lib\typescript.js:60282:28)
    at visitor (d:\solutions\transformer-issue-compiler\node_modules\typescript\lib\typescript.js:60243:24)
    at visitNode (d:\solutions\transformer-issue-compiler\node_modules\typescript\lib\typescript.js:53755:23)

Even if I remove the ts.createToken(ts.SyntaxKind.ConstKeyword) token, this exception is still thrown.

Also, the console.log statement prints the following (the var keyword is left there):

static myMethod() { const var myConst = "value"; return 'value'; }
@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Mar 7, 2018

80% sure the problem is that you're passing null instead of undefined for some of those parameters. The TS API basically never uses null and will behave badly if given it because it's only ever checking for === undefined

@pedro-pedrosa
Copy link
Author

Replacing the null in createVariableDeclaration with undefined made the exception go away (wasn't needed on createMethod), however it still prints const var.

@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label Mar 7, 2018
@RyanCavanaugh
Copy link
Member

Pass NodeFlags.Const to createVariableDeclarationList rather than trying to push in a const keyword in the array.

There are lots of examples in the TypeScript codebase that show calling this function correctly.

@pedro-pedrosa
Copy link
Author

That worked, I have a correct output now.

Thank you.

@microsoft microsoft locked and limited conversation to collaborators Jul 25, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

2 participants