在 运行 Typescript 编译器 plugin/transformer 之后进行类型检查
Typechecking after running Typescript compiler plugin/transformer
我正在关注有关如何编写 Typescript 编译器的博客 (https://dev.doctorevidence.com/how-to-write-a-typescript-transform-plugin-fc5308fdd943) plugin/transformer。
在应用第一个应该引入类型错误的简单转换后(某些 属性 在没有 属性 的对象上访问)我注意到没有类型错误是显示。事实上,编译器会正常进行。
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));
(即使 noEmitOnError: true
)
这是有意为之的行为吗?这是我可以在某处启用的功能吗?
Is this intended behavior?
是的。
Is this something I can enable somewhere?
不,变压器的用途有限。不支持编译器的通用通用 "plugins"。
变形金刚 运行 作为 "emit" 阶段的一部分,该阶段从经过类型检查的 AST 生成 javascript 代码。
This comment 在变形金刚 PR 中说
Transforms, all of them, happen after the checking phase has happened
更新
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.
我不知道。首先要尝试的是让你的转换器像以前一样修改 AST,然后通过调用
手动对修改后的文件进行类型检查
program.getDiagnosticsProducingTypeChecker().getDiagnostics(sourceFile)
(getDiagnostics
有第二个参数 - cancellationToken
- 但似乎省略它是安全的,因为它总是在类型检查代码中被检查为 undefined
。一般来说,您可以查看在其自己的源代码中如何使用各种编译器 API,例如 emit
首先通过调用各种 program.getNNNDiagnostics
进行类型检查,然后 运行s 带有转换的发射器。)
这可能有效也可能无效,因为类型检查器会修改 AST,这取决于 AST 处于正确的状态。
然后,您可能想看看 builder API - it's purpose is to watch for source file modifications and recompile changed files (source code link)。我不知道在 AST 修改后重新编译它会有多难,而且看起来您将无法使用变形金刚中可用的访问者;你必须手动遍历 AST。
此外,还有 ts-simple-ast 库,其声明的目的是 "Provide a simple way to navigate and manipulate TypeScript and JavaScript code"。我自己没有使用过它,也不知道它对您的目标有多大用处。
您只需创建一个 CompilerHost
并将其 getSourceFile
方法设置为指向您的 post-转换源文件。一种方法是使用从文件名到转换后的源文件的映射。之后 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()
我正在关注有关如何编写 Typescript 编译器的博客 (https://dev.doctorevidence.com/how-to-write-a-typescript-transform-plugin-fc5308fdd943) plugin/transformer。
在应用第一个应该引入类型错误的简单转换后(某些 属性 在没有 属性 的对象上访问)我注意到没有类型错误是显示。事实上,编译器会正常进行。
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));
(即使 noEmitOnError: true
)
这是有意为之的行为吗?这是我可以在某处启用的功能吗?
Is this intended behavior?
是的。
Is this something I can enable somewhere?
不,变压器的用途有限。不支持编译器的通用通用 "plugins"。
变形金刚 运行 作为 "emit" 阶段的一部分,该阶段从经过类型检查的 AST 生成 javascript 代码。
This comment 在变形金刚 PR 中说
Transforms, all of them, happen after the checking phase has happened
更新
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.
我不知道。首先要尝试的是让你的转换器像以前一样修改 AST,然后通过调用
手动对修改后的文件进行类型检查program.getDiagnosticsProducingTypeChecker().getDiagnostics(sourceFile)
(getDiagnostics
有第二个参数 - cancellationToken
- 但似乎省略它是安全的,因为它总是在类型检查代码中被检查为 undefined
。一般来说,您可以查看在其自己的源代码中如何使用各种编译器 API,例如 emit
首先通过调用各种 program.getNNNDiagnostics
进行类型检查,然后 运行s 带有转换的发射器。)
这可能有效也可能无效,因为类型检查器会修改 AST,这取决于 AST 处于正确的状态。
然后,您可能想看看 builder API - it's purpose is to watch for source file modifications and recompile changed files (source code link)。我不知道在 AST 修改后重新编译它会有多难,而且看起来您将无法使用变形金刚中可用的访问者;你必须手动遍历 AST。
此外,还有 ts-simple-ast 库,其声明的目的是 "Provide a simple way to navigate and manipulate TypeScript and JavaScript code"。我自己没有使用过它,也不知道它对您的目标有多大用处。
您只需创建一个 CompilerHost
并将其 getSourceFile
方法设置为指向您的 post-转换源文件。一种方法是使用从文件名到转换后的源文件的映射。之后 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()