【问题标题】:Transform typescript before typecheck在类型检查之前转换打字稿
【发布时间】:2020-09-18 10:47:26
【问题描述】:

让我们来看看打字稿文件:

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 中构建它:

{
  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 表达式:

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):

/***/ "/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,这在 A 类中不存在,我将不会收到任何错误消息。

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

× 「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.

根据这些事实,我了解到 在实际检查代码后应用了转换器,但转换器包含在 before 部分中,因此我希望 typescript 验证生成的代码而不是原始代码。

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

【问题讨论】:

    标签: typescript abstract-syntax-tree typescript-compiler-api awesome-typescript-loader


    【解决方案1】:

    在较高层次上,TypeScript 编译器被设计为按以下顺序执行以下步骤:

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

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

    例如:

    // `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 的原因之一。需要更新源文件文本和许多内部属性才能正确执行此操作。也就是说,在某些情况下,您也许可以侥幸逃脱...

    我不确定 TS 团队需要付出什么样的努力来更新编译器以在类型检查之前处理转换,我不确定他们会在这方面投入多少精力,但您可能想谈谈向他们询问。在checker.ts 中查看导致getTextOfNodeFromSourceText 的所有调用,以了解这将是一个问题的一堆情况。

    getCustomTransformersbeforeafter 之间的区别

    正如您所注意到的,这两种转换都是在发射时使用的,而不是在发射之前。

    • before - 在编译器进行转换之前评估转换 - 它仍然在 AST 中有 TypeScript 代码。
    • after - 在编译器进行转换后评估的转换——它将被转换为“目标”是什么(例如,打印 AST 将给出 JavaScript 代码)。

    更多详情请见the type declarations

    【讨论】:

    • 太难过了...会尝试使用你的提示。
    猜你喜欢
    • 2019-03-23
    • 2018-11-03
    • 2020-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-29
    • 2018-12-17
    • 2021-01-09
    相关资源
    最近更新 更多