我正在关注一个博客(https://dev.doctorevidence.com/how-to-write-a-typescript-transform-plugin-fc5308fdd943),该博客有关如何编写Typescript编译器插件/变压器。
在应用了第一个简单的转换之后,该转换会引入类型错误(在没有该属性的对象上访问的某些属性),我注意到没有显示类型错误。实际上,编译器可以正常进行。
services:
audit.persister.base:
class: MyBundle\Security\Audit\Persister\ChainedEntityTrailPersister
calls:
- ['addPersister', ['@audit.persister_elasticsearch']]
应用于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);
};
};
:
index.ts
收益declare function someCall(...args: any[]): string;
console.log(someCall(1, 2, true));
:
index.js
(即使使用console.log(someCall.nonExisting(1, 2, true));
)
这是预期的行为吗?我可以在某处启用此功能吗?
答案 0 :(得分:2)
这是预期的行为吗?
是的
这可以在某处启用吗?
不,变压器的用途有限。不支持编译器的通用通用“插件”。
变压器作为“发射”阶段的一部分运行,该阶段从经过类型检查的AST生成JavaScript代码。
This comment在变压器PR中说
所有这些转换都是在检查阶段发生之后发生的
更新
是否有某种方法可以编译两次:一次转换文件,然后一次对整个项目进行类型检查?我不在乎是否需要对转换后的文件进行单独检查。
我不知道。尝试做的第一件事是让您的转换器像以前一样修改AST,然后通过调用
来手动检查修改过的文件program.getDiagnosticsProducingTypeChecker().getDiagnostics(sourceFile)
({getDiagnostics
具有第二个参数-cancellationToken
-但似乎可以忽略它,因为在类型检查器代码中始终会对其进行检查以防undefined
。通常,您可以看看如何在自己的源代码中使用各种编译器API,例如emit
首先通过调用各种program.getNNNDiagnostics
进行类型检查,然后运行带有转换的发射器。)
这可能行不通,因为类型检查器会修改AST,这取决于AST处于正确的状态。
然后,您可能需要查看builder API-目的是观察源文件的修改并重新编译更改的文件(source code link)。我不知道要在AST修改上对其进行重新编译将有多么困难,而且看来您将无法使用变形金刚中的可用访问者;您将不得不手动遍历AST。
还有ts-simple-ast库,其声明的目的是“提供一种导航和操作TypeScript和JavaScript代码的简单方法”。我自己没有使用过它,也不知道它对您的目标有多有用。
答案 1 :(得分:0)
您只需要创建一个CompilerHost
并将其getSourceFile
方法设置为指向转换后的源文件即可。一种方法是通过从文件名到转换后的源文件的映射。之后,CompilerHost
的创建将类似于:
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);
};
然后,您只需要将此CompilerHost
作为第三个参数传递给ts.createProgram()