Ash*_*vis 8 typechecking transpiler typescript typescript-compiler-api
我正在我的应用程序Data-Forge Notebook 中实现 TypeScript 支持。
我需要编译、类型检查和评估 TypeScript 代码片段。
编译似乎没有问题,我使用transpileModule如下所示将一段 TS 代码转换为可以评估的 JavaScript 代码:
import { transpileModule, TranspileOptions } from "typescript";
const transpileOptions: TranspileOptions = {
compilerOptions: {},
reportDiagnostics: true,
};
const tsCodeSnippet = " /* TS code goes here */ ";
const jsOutput = transpileModule(tsCodeSnippet, transpileOptions);
console.log(JSON.stringify(jsOutput, null, 4));
Run Code Online (Sandbox Code Playgroud)
但是,当我尝试编译有错误的 TS 代码时出现问题。
例如,下面的函数有一个类型错误,但它在没有任何错误诊断的情况下被转换:
function foo(): string {
return 5;
}
Run Code Online (Sandbox Code Playgroud)
转译很棒,但我也希望能够向我的用户显示错误。
所以我的问题是如何做到这一点,同时还要进行类型检查并为语义错误产生错误?
请注意,我不想将 TypeScript 代码保存到文件中。这对我的应用程序来说将是一个不必要的性能负担。我只想编译和键入保存在内存中的代码片段。
Dav*_*ret 10
这不是一项简单的任务,可能需要一段时间才能完成。也许有更简单的方法,但我还没有找到。
ts.CompilerHostwhere 方法,如fileExists、readFile、directoryExists、getDirectories()等。ts.createProgram)并传入您的自定义ts.CompilerHost.ts.getPreEmitDiagnostics(program)以获取诊断信息。不完美的例子
这是一个简短的不完美示例,它没有正确实现内存文件系统并且没有加载 lib 文件(因此会出现全局诊断错误……这些错误可以被忽略,或者您可以调用.program以外的特定方法program.getGlobalDiagnostics()。注意行为在ts.getPreEmitDiagnostics 这里):
import * as ts from "typescript";
console.log(getDiagnosticsForText("const t: number = '';").map(d => d.messageText));
function getDiagnosticsForText(text: string) {
const dummyFilePath = "/file.ts";
const textAst = ts.createSourceFile(dummyFilePath, text, ts.ScriptTarget.Latest);
const options: ts.CompilerOptions = {};
const host: ts.CompilerHost = {
fileExists: filePath => filePath === dummyFilePath,
directoryExists: dirPath => dirPath === "/",
getCurrentDirectory: () => "/",
getDirectories: () => [],
getCanonicalFileName: fileName => fileName,
getNewLine: () => "\n",
getDefaultLibFileName: () => "",
getSourceFile: filePath => filePath === dummyFilePath ? textAst : undefined,
readFile: filePath => filePath === dummyFilePath ? text : undefined,
useCaseSensitiveFileNames: () => true,
writeFile: () => {}
};
const program = ts.createProgram({
options,
rootNames: [dummyFilePath],
host
});
return ts.getPreEmitDiagnostics(program);
}
Run Code Online (Sandbox Code Playgroud)
如果您有权访问文件系统,那么这会容易得多,您可以使用类似于以下的功能:
import * as path from "path";
function getDiagnosticsForText(
rootDir: string,
text: string,
options?: ts.CompilerOptions,
cancellationToken?: ts.CancellationToken
) {
options = options || ts.getDefaultCompilerOptions();
const inMemoryFilePath = path.resolve(path.join(rootDir, "__dummy-file.ts"));
const textAst = ts.createSourceFile(inMemoryFilePath, text, options.target || ts.ScriptTarget.Latest);
const host = ts.createCompilerHost(options, true);
overrideIfInMemoryFile("getSourceFile", textAst);
overrideIfInMemoryFile("readFile", text);
overrideIfInMemoryFile("fileExists", true);
const program = ts.createProgram({
options,
rootNames: [inMemoryFilePath],
host
});
return ts.getPreEmitDiagnostics(program, textAst, cancellationToken);
function overrideIfInMemoryFile(methodName: keyof ts.CompilerHost, inMemoryValue: any) {
const originalMethod = host[methodName] as Function;
host[methodName] = (...args: unknown[]) => {
// resolve the path because typescript will normalize it
// to forward slashes on windows
const filePath = path.resolve(args[0] as string);
if (filePath === inMemoryFilePath)
return inMemoryValue;
return originalMethod.apply(host, args);
};
}
}
// example...
console.log(getDiagnosticsForText(
__dirname,
"import * as ts from 'typescript';\n const t: string = ts.createProgram;"
));
Run Code Online (Sandbox Code Playgroud)
这样做,编译器将搜索提供rootDir的node_modules文件夹并使用其中的类型(它们不需要以其他方式加载到内存中)。
我创建了一个名为@ts-morph/bootstrap的库,它使得使用 Compiler API 进行设置变得更加容易。即使使用内存文件系统,它也会为您加载 TypeScript lib 文件。
import { createProject, ts } from "@ts-morph/bootstrap";
const project = await createProject({ useInMemoryFileSystem: true });
const myClassFile = project.createSourceFile(
"MyClass.ts",
"export class MyClass { prop: string; }",
);
const program = project.createProgram();
ts.getPreEmitDiagnostics(program); // check these
Run Code Online (Sandbox Code Playgroud)
我在 David Sherret 的一些原始帮助和 Fabian Pirklbauer( TypeScript Playground 的创建者)的提示的基础上解决了这个问题。
我创建了一个代理 CompilerHost 来包装真正的 CompilerHost。代理能够返回内存中的 TypeScript 代码进行编译。底层真实的 CompilerHost 能够加载默认的 TypeScript 库。这些库是必需的,否则您会收到大量与内置 TypeScript 数据类型相关的错误。
代码
import * as ts from "typescript";
//
// A snippet of TypeScript code that has a semantic/type error in it.
//
const code
= "function foo(input: number) {\n"
+ " console.log('Hello!');\n"
+ "};\n"
+ "foo('x');"
;
//
// Result of compiling TypeScript code.
//
export interface CompilationResult {
code?: string;
diagnostics: ts.Diagnostic[]
};
//
// Check and compile in-memory TypeScript code for errors.
//
function compileTypeScriptCode(code: string, libs: string[]): CompilationResult {
const options = ts.getDefaultCompilerOptions();
const realHost = ts.createCompilerHost(options, true);
const dummyFilePath = "/in-memory-file.ts";
const dummySourceFile = ts.createSourceFile(dummyFilePath, code, ts.ScriptTarget.Latest);
let outputCode: string | undefined = undefined;
const host: ts.CompilerHost = {
fileExists: filePath => filePath === dummyFilePath || realHost.fileExists(filePath),
directoryExists: realHost.directoryExists && realHost.directoryExists.bind(realHost),
getCurrentDirectory: realHost.getCurrentDirectory.bind(realHost),
getDirectories: realHost.getDirectories.bind(realHost),
getCanonicalFileName: fileName => realHost.getCanonicalFileName(fileName),
getNewLine: realHost.getNewLine.bind(realHost),
getDefaultLibFileName: realHost.getDefaultLibFileName.bind(realHost),
getSourceFile: (fileName, languageVersion, onError, shouldCreateNewSourceFile) => fileName === dummyFilePath
? dummySourceFile
: realHost.getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile),
readFile: filePath => filePath === dummyFilePath
? code
: realHost.readFile(filePath),
useCaseSensitiveFileNames: () => realHost.useCaseSensitiveFileNames(),
writeFile: (fileName, data) => outputCode = data,
};
const rootNames = libs.map(lib => require.resolve(`typescript/lib/lib.${lib}.d.ts`));
const program = ts.createProgram(rootNames.concat([dummyFilePath]), options, host);
const emitResult = program.emit();
const diagnostics = ts.getPreEmitDiagnostics(program);
return {
code: outputCode,
diagnostics: emitResult.diagnostics.concat(diagnostics)
};
}
console.log("==== Evaluating code ====");
console.log(code);
console.log();
const libs = [ 'es2015' ];
const result = compileTypeScriptCode(code, libs);
console.log("==== Output code ====");
console.log(result.code);
console.log();
console.log("==== Diagnostics ====");
for (const diagnostic of result.diagnostics) {
console.log(diagnostic.messageText);
}
console.log();
Run Code Online (Sandbox Code Playgroud)
输出
==== Evaluating code ====
function foo(input: number) {
console.log('Hello!');
};
foo('x');
=========================
Diagnosics:
Argument of type '"x"' is not assignable to parameter of type 'number'.
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1483 次 |
| 最近记录: |