从程序创建的AST中打印类型定义

时间:2018-05-11 23:20:28

标签: typescript typescript-compiler-api

我正在尝试以编程方式为第三方库创建的定义创建一个typescript AST,并使用该AST打印我们在语法中声明的类型定义集(.d.ts文件)。库。

依靠documentation for the compiler API,并挖掘打字稿编译器的源文件,我相信我有一个大多数正确的AST。但是,我还没有找到一种方法来输出此AST的类型定义(对应于.d.ts文件),只打印AST作为打字稿程序。

我的问题实际上是两部分:

  1. 有没有办法从AST打印类型定义(输出.d.ts文件)?
  2. 如果是的话,有没有这方面的例子?

1 个答案:

答案 0 :(得分:0)

复制和更改TypeScript源的ts.transpileModule函数中所做的操作以仅输出声明文件可能是最简单的。

例如:

export function transpileModuleDeclaration(sourceFile: ts.SourceFile, transpileOptions: ts.TranspileOptions) {
    const diagnostics: ts.Diagnostic[] = [];
    const options = transpileOptions.compilerOptions != null ? { ...transpileOptions.compilerOptions } : ts.getDefaultCompilerOptions();

    options.isolatedModules = true;

    // in this case we only want to emit the declaration file
    options.emitDeclarationOnly = true;
    options.declaration = true;

    // this does not write anything to disk so there is no need to verify that there are no conflicts between input and output paths.
    options.suppressOutputPathCheck = true;

    // Filename can be non-ts file.
    options.allowNonTsExtensions = true;

    // We are not returning a sourceFile for lib file when asked by the program,
    // so pass --noLib to avoid reporting a file not found error.
    options.noLib = true;

    // Clear out other settings that would not be used in transpiling this module
    options.lib = undefined;
    options.types = undefined;
    options.noEmit = undefined;
    options.noEmitOnError = undefined;
    options.paths = undefined;
    options.rootDirs = undefined;
    options.composite = undefined;
    options.declarationDir = undefined;
    options.out = undefined;
    options.outFile = undefined;

    // We are not doing a full typecheck, we are not resolving the whole context,
    // so pass --noResolve to avoid reporting missing file errors.
    options.noResolve = true;

    // if jsx is specified then treat file as .tsx
    const inputFileName = transpileOptions.fileName || (options.jsx ? "module.tsx" : "module.ts");
    if (transpileOptions.moduleName) {
        sourceFile.moduleName = transpileOptions.moduleName;
    }

    // Output
    let outputText: string | undefined;

    // Create a compilerHost object to allow the compiler to read and write files
    const compilerHost: ts.CompilerHost = {
        getSourceFile: (fileName) => fileName === inputFileName.replace(/\\/g, "/") ? sourceFile : undefined,
        writeFile: (name, text) => {
            outputText = text;
        },
        getDefaultLibFileName: () => "lib.d.ts",
        useCaseSensitiveFileNames: () => false,
        getCanonicalFileName: fileName => fileName,
        getCurrentDirectory: () => "",
        getNewLine: () => options.newLine == null || options.newLine === ts.NewLineKind.LineFeed ? "\n" : "\r\n",
        fileExists: fileName => fileName === inputFileName,
        readFile: () => "",
        directoryExists: () => true,
        getDirectories: () => []
    };

    const program = ts.createProgram([inputFileName], options, compilerHost);

    if (transpileOptions.reportDiagnostics) {
        diagnostics.push(...program.getSyntacticDiagnostics(sourceFile));
        diagnostics.push(...program.getOptionsDiagnostics());
    }
    // Emit
    program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, transpileOptions.transformers);

    if (outputText === undefined)
        throw new Error("Output generation failed.");

    return { outputText, diagnostics };
}

然后使用它:

const file = ts.createSourceFile("test.ts", `function myFunc(a: number, b: number) {
    return a + b;
}`, ts.ScriptTarget.Latest, true);

const result = transpileModuleDeclaration(file, {});
console.log(result.outputText);

输出:

declare function myFunc(a: number, b: number): number;