在类型检查之前转换打字稿 [英] Transform typescript before typecheck

查看:32
本文介绍了在类型检查之前转换打字稿的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我们以打字稿文件为例:

Let's take typescript file:

class A {
    private x? = 0;
    private y? = 0;

    f() {
        console.log(this.x, this.y);
        delete this.x;
    }
}

const a = new A();
a.f();

我正在使用 awesome-typescript-loader 在 webpack 中构建它:

I'm building it in webpack using awesome-typescript-loader:

{
  test: /\.tsx?$/,
  include: path.resolve("./src"),
  exclude: path.resolve("./node_modules/"),
  use: {
    loader: 'awesome-typescript-loader',
    options: {
      getCustomTransformers: program => ({ 
        before: [deleteTransformer(program)]
      })
    }
  }
},

其中 deleteTransformer 是我自己的转换器,它用 delete this.y 替换任何 delete 表达式:

Where deleteTransformer is my own transformer which replaces any delete expression by delete this.y:

import * as ts from "typescript";

export default function getCustomTransformers(program: ts.Program): ts.TransformerFactory<ts.SourceFile> {
  return (context: ts.TransformationContext) => (file: ts.SourceFile) => visitNodeAndChildren(file, program, context);
}

function visitNodeAndChildren<N extends ts.Node>(node: N, program: ts.Program, context: ts.TransformationContext): N {
  return ts.visitEachChild(visitNode(node, program), childNode => visitNodeAndChildren(childNode, program, context), context);
}

function visitNode<N extends ts.Node>(node: N, program: ts.Program): N {
  if (ts.isDeleteExpression(node)) {
    return ts.factory.createDeleteExpression(ts.factory.createPropertyAccessExpression(
      ts.factory.createThis(),
      "y",
    )) as ts.Node as N;
  }

  return node;
}

如果我运行编译,我将得到我期望的代码(删除 y,而不是 x):

If I run compilation I will get the code I expect (deletes y, not x):

/***/ "/7QA":
/***/ (function(module, exports, __webpack_require__) {

"use strict";

var A = /** @class */ (function () {
    function A() {
        this.x = 0;
        this.y = 0;
    }
    A.prototype.f = function () {
        console.log(this.x, this.y);
        delete this.y;
    };
    return A;
}());
var a = new A();
a.f();


/***/ }),

但是,如果我将名称 y 更改为 z,而 z 在类 A 中不存在,我将不会收到任何错误消息.

But if I change the name y to z which does not exist on class A I won't get any error message.

此外,如果我将 A 类更改为具有非可选的 x 并将 y 保留在转换器中,我会收到错误

Also if I change class A to have nonoptional x and keep y in transformer, I'll get an error

× 「atl」: Checking finished with 1 errors

ERROR in [at-loader] ./src/index.ts:7:16
    TS2790: The operand of a 'delete' operator must be optional.

根据这些事实我知道transformer是在实际检查代码之后应用的,但是transformer包含在before部分,所以我希望打字稿能够验证生成的代码而不是原创.

According to these facts I understand that transformer is applied after the code is actually checked, but transformer is included into before section, so I expect typescript to validate generated code instead of original.

为什么会这样?getCustomTransformers 对象中的 beforeafter 转换器有什么区别(我都试过,没有发现区别)?以及如何在检查代码之前应用转换?

Why does it happen? What are the differences of before and after transformers in getCustomTransformers object (I tried both and found no difference)? And how can I apply transformations before code is checked?

推荐答案

在高层次上,TypeScript 编译器旨在按以下顺序执行以下步骤:

At a high level, the TypeScript compiler was designed to do the following steps in the following order:

Parse -> Bind -> Type Check -> Emit (transform)

由于这种设计,类型检查器代码通常假设解析时创建的 AST 与源文件文本匹配并且没有改变.

Due to this design, the type checker code often assumes the AST created in parsing matches the source file text and hasn't changed.

例如:

// `declaration` is a variable declaration with type `number`
console.log(typeChecker.typeToString(
    typeChecker.getTypeAtLocation(declaration) // number
));

declaration = factory.updateVariableDeclaration(
    declaration,
    declaration.name,
    /* exclamation token */ undefined,
    /* type */ factory.createTypeReferenceNode("Date", undefined),
    /* initializer */ undefined,
);

// now type checking won't be reliable
console.log(typeChecker.typeToString(
    typeChecker.getTypeAtLocation(declaration) // still number
));
console.log(typeChecker.typeToString(
    typeChecker.getTypeAtLocation(declaration.type!) // any
));

因此,您不能可靠地仅转换 AST,然后使用现有的 TypeScript Compiler API 代码进行类型检查.这就是 ts-morph 实际上修改文本而不是 AST 然后重建的原因之一AST.需要更新源文件文本和许多内部属性才能正确执行此操作.也就是说,在某些情况下,您或许可以侥幸逃脱...

So, you cannot reliably only transform an AST then type check using the existing TypeScript Compiler API code. That's one of reasons why ts-morph actually makes modifications to the text instead of the AST then rebuilds the AST. The source file text and a lot of internal properties need to be updated to do this properly. That said, you might be able to get away with it in certain scenarios...

我不确定 TS 团队在类型检查之前更新编译器以处理转换需要付出什么样的努力,我不确定他们是否会投入精力,但您可能想谈谈向他们询问.在 checker.ts 中查看所有调用到 getTextOfNodeFromSourceText 以解决一系列问题.

I'm not sure what kind of effort it would take for the TS team to update the compiler to handle transforms before type checking and I'm not sure it's something they'd invest effort in, but you may want to talk to them and ask about that. See in checker.ts all the calls that lead to getTextOfNodeFromSourceText for a bunch of cases where this would be a problem.

getCustomTransformers

如您所见,这两种变换都在发射时使用,而不是在发射之前使用.

As you noticed, both of these transforms are used while emitting and not before.

  • before - 在编译器进行转换之前要评估的转换——它仍然会在 AST 中包含 TypeScript 代码.
  • after - 在编译器进行转换后要评估的转换——它将被转换为任何目标"是(例如,打印 AST 将提供 JavaScript 代码).
  • before - Transformations to evaluate before the compiler does its transformations—it will still have TypeScript code in the AST.
  • after - Transformations to evaluate after the compiler does its transformations—it will be transformed to whatever the "target" is (ex. printing the AST will give JavaScript code).

请参阅类型声明更多详情.

这篇关于在类型检查之前转换打字稿的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆