Typechecking after running Typescript compiler plugin/transformer

亡梦爱人 提交于 2021-02-10 13:16:12

问题


I'm following a blog (https://dev.doctorevidence.com/how-to-write-a-typescript-transform-plugin-fc5308fdd943) on how to write a Typescript compiler plugin/transformer.

After applying a first simple transformation which should introduce a type-error (some property accessed on an object that doesn't have that property) I noticed that the no type-error is shown. In fact, the compiler proceeds as normal.

import * as ts from "typescript";

export const transformerFactory = (
  program: ts.Program
): ts.TransformerFactory<ts.SourceFile> => {
  return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
    const visitor: ts.Visitor = (node: ts.Node): ts.VisitResult<ts.Node> => {
      if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
        if (node.expression.escapedText === "someCall") {
          return ts.createCall(
            ts.createPropertyAccess(node.expression, "nonExisting"),
            node.typeArguments,
            node.arguments
          );
        }
      }
      return ts.visitEachChild(node, visitor, context);
    };

    return (sf: ts.SourceFile) => ts.visitNode(sf, visitor);
  };
};

Applied to index.ts:

declare function someCall(...args: any[]): string;

console.log(someCall(1, 2, true));

Yields index.js:

console.log(someCall.nonExisting(1, 2, true));

(even with noEmitOnError: true)

Is this intended behavior? Is this something I can enable somewhere?


回答1:


Is this intended behavior?

Yes.

Is this something I can enable somewhere?

No, transformers have limited purpose. General all-purpose "plugins" for the compiler are not supported.

Transformers are run as part of the "emit" phase which generates javascript code from type-checked AST.

This comment in the transformers PR says

Transforms, all of them, happen after the checking phase has happened

UPDATE

is there some way to compile twice: once to transform the file and once to type-check the whole thing? I don't mind if I have to run a separate check for the transformed files.

I don't know. The first thing to try is to have your transformers modify AST as before, then type-check modified files manually by calling

program.getDiagnosticsProducingTypeChecker().getDiagnostics(sourceFile)

(getDiagnostics has second parameter - cancellationToken - but it seems that it's safe to omit it because it's always checked against being undefined in the type checker code. In general, you can look how various compiler APIs are used in its own source code, for example emit does type-checking first by calling various program.getNNNDiagnostics, then runs emitter with transforms.)

This might or might not work, because type checker modifies AST, and it depends on AST being in the correct state.

Then, you might want to look at the builder API - it's purpose is to watch for source file modifications and recompile changed files (source code link). I don't know how hard it would be to make it recompile on AST modifications, also it looks like you would not be able to use visitors available in tranformers; you'll have to traverse AST manually.

Also, there's ts-simple-ast library, whose stated purpose is to "Provide a simple way to navigate and manipulate TypeScript and JavaScript code". I haven't used it myself and have no idea how useful it is for your goal.




回答2:


You just need to create a CompilerHost and set its getSourceFile method to point to your post-transform source files. One way to do this is by having a Map from file names to transformed source files. Afterwards the CompilerHost creation will look something like:

const compilerHost = ts.createCompilerHost(compilerOptions);
    const defaultLibFileName = ts.getDefaultLibFileName(compilerOptions);
    compilerHost.getSourceFile = (sourceName) => {
      let sourcePath = sourceName;
      if (sourceName === defaultLibFileName) {
        sourcePath = ts.getDefaultLibFilePath(compilerOptions);
      } else if (this.sourceFileMap.has(sourceName)) {
        return this.sourceFileMap.get(sourceName);
      }
      if (!fs.existsSync(sourcePath)) {
        return undefined;
      }
      const contents = fs.readFileSync(sourcePath, 'utf-8');
      return ts.createSourceFile(sourceName, contents, COMPILER_OPTIONS.target);
    };

Then you just need to pass this CompilerHost as the third argument to ts.createProgram()



来源:https://stackoverflow.com/questions/53448691/typechecking-after-running-typescript-compiler-plugin-transformer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!