在类型检查之前转换打字稿

Qwe*_*tiy 6 abstract-syntax-tree typescript typescript-compiler-api awesome-typescript-loader

让我们看一下打字稿文件:

\n
class A {\n    private x? = 0;\n    private y? = 0;\n\n    f() {\n        console.log(this.x, this.y);\n        delete this.x;\n    }\n}\n\nconst a = new A();\na.f();\n
Run Code Online (Sandbox Code Playgroud)\n

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

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

我自己的变压器在哪里deleteTransformer,它用以下方式替换任何delete表达式delete this.y

\n
import * as ts from "typescript";\n\nexport default function getCustomTransformers(program: ts.Program): ts.TransformerFactory<ts.SourceFile> {\n  return (context: ts.TransformationContext) => (file: ts.SourceFile) => visitNodeAndChildren(file, program, context);\n}\n\nfunction visitNodeAndChildren<N extends ts.Node>(node: N, program: ts.Program, context: ts.TransformationContext): N {\n  return ts.visitEachChild(visitNode(node, program), childNode => visitNodeAndChildren(childNode, program, context), context);\n}\n\nfunction visitNode<N extends ts.Node>(node: N, program: ts.Program): N {\n  if (ts.isDeleteExpression(node)) {\n    return ts.factory.createDeleteExpression(ts.factory.createPropertyAccessExpression(\n      ts.factory.createThis(),\n      "y",\n    )) as ts.Node as N;\n  }\n\n  return node;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
/***/ "/7QA":\n/***/ (function(module, exports, __webpack_require__) {\n\n"use strict";\n\nvar A = /** @class */ (function () {\n    function A() {\n        this.x = 0;\n        this.y = 0;\n    }\n    A.prototype.f = function () {\n        console.log(this.x, this.y);\n        delete this.y;\n    };\n    return A;\n}());\nvar a = new A();\na.f();\n\n\n/***/ }),\n
Run Code Online (Sandbox Code Playgroud)\n

但是,如果我将名称更改yz类中不存在的名称A,我将不会收到任何错误消息。

\n

另外,如果我将类更改A为非可选x并保留y在变压器中,我会收到错误

\n
\xc3\x97 \xef\xbd\xa2atl\xef\xbd\xa3: Checking finished with 1 errors\n\nERROR in [at-loader] ./src/index.ts:7:16\n    TS2790: The operand of a \'delete\' operator must be optional.\n
Run Code Online (Sandbox Code Playgroud)\n

根据这些事实,我了解到变压器是在代码实际检查后应用的,但变压器包含在before部分中,所以我希望打字稿验证生成的代码而不是原始代码。

\n

为什么会发生这种情况?beforeobject和afterTransformer有什么区别getCustomTransformers(我都尝试过,没有发现区别)?在检查代码之前如何应用转换?

\n

Dav*_*ret 7

从较高层面来看,TypeScript 编译器被设计为按以下顺序执行以下步骤:

\n
Parse -> Bind -> Type Check -> Emit (transform)\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

例如:

\n
// `declaration` is a variable declaration with type `number`\nconsole.log(typeChecker.typeToString(\n    typeChecker.getTypeAtLocation(declaration) // number\n));\n\ndeclaration = factory.updateVariableDeclaration(\n    declaration,\n    declaration.name,\n    /* exclamation token */ undefined,\n    /* type */ factory.createTypeReferenceNode("Date", undefined),\n    /* initializer */ undefined,\n);\n\n// now type checking won\'t be reliable\nconsole.log(typeChecker.typeToString(\n    typeChecker.getTypeAtLocation(declaration) // still number\n));\nconsole.log(typeChecker.typeToString(\n    typeChecker.getTypeAtLocation(declaration.type!) // any\n));\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

我不确定 TS 团队需要付出什么样的努力来更新编译器以在类型检查之前处理转换,并且我不确定这是他们会投入精力的事情,但您可能会这样做想和他们谈谈并询问此事。在checker.ts中查看getTextOfNodeFromSourceText导致出现问题的一系列情况的所有调用。

\n

before之间和after之间的区别getCustomTransformers

\n

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

\n
    \n
  • before- 在编译器执行转换之前评估的转换\xe2\x80\x94it 仍将在 AST 中包含 TypeScript 代码。
  • \n
  • after- 编译器执行转换后评估的转换\xe2\x80\x94它将被转换为任何“目标”(例如,打印 AST 将给出 JavaScript 代码)。
  • \n
\n

有关更多详细信息,请参阅类型声明。

\n