[譯]使用 TypeScript complier API

原文地址node

免責聲明git

請記住,這仍是一個不完整的 API —— 咱們將其發佈版本定爲 0.5,隨着時間的流逝,狀況會發生改變。畢竟第一次迭代,不免有些不完善的地方。但願社區可以多給予反饋以改進 API。爲了容許用戶在未來的版本之間轉換,咱們會記錄每一個新版本的 API 重大更改。github

設置

首先你須要使用 npm 安裝 TypeScript 且版本高於1.6。typescript

安裝完成以後,你須要在項目路徑下對 TypeScript 進行連接。不然會直接在全局連接。express

npm install -g typescript
npm link typescript
複製代碼

對於某些示例,你還須要 node 定義文件。運行下面命令來獲取定義文件:npm

npm install @types/node
複製代碼

完成這些以後,能夠嘗試一下如下示例。json

compiler API 有一些主要的組件:數組

  • Program 是你整個應用程序的 TypeScript 術語
  • CompilerHost 是用戶系統 API,用於讀取文件、檢查目錄和區分大小寫。
  • SourceFiles 表明應用程序中的源文件,同時包含文本和 TypeScript AST。

一個小型的 complier

這個例子是一個基礎的編輯器,它能獲取 TypeScript 的文件列表,並將它們編譯成相應的 JavaScript。緩存

咱們能夠經過 createProgram 來建立一個 Program —— 這會建立一個默認的 ComplierHost,它使用文件系統來獲取文件。bash

import * as ts from "typescript"

function complier (fileNames: string[], options: ts.CompilerOptions): void {
  let program = ts.createProgram(fileNames, options)
  let emitResult = program.emit()

  let allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics)

  allDiagnostics.forEach(diagnostics => {
    if (diagnostics.file) {
      let { line, character } = diagnostics.file.getLineAndCharacterOfPosition(diagnostics.start!)
      let message = ts.flattenDiagnosticMessageText(diagnostics.messageText, '\n');
      console.log(`${diagnostics.file.fileName} (${line + 1}, ${character + 1}): ${message}`);
    } else {
      console.log(ts.flattenDiagnosticMessageText(diagnostics.messageText, '\n'))
    }
  })

  let exitCode = emitResult.emitSkipped ? 1 : 0;
  console.log(`Process exiting with code ${exitCode}.`);
  process.exit(exitCode)
}

complier(process.argv.slice(2), {
  noEmitOnError: true,
  noImplicitAny: true,
  target: ts.ScriptTarget.ES5,
  module: ts.ModuleKind.CommonJS
})
複製代碼

一個簡單的 transform 函數

建立一個 complier 不須要不少代碼,可是你可能只想在給定 TypeScript 源文件的狀況下獲取相應的 JavaScript 輸出。所以,你可使用 ts.transplieModule 經過兩行代碼獲得一個 string => string 的代碼轉換。

import * as ts from "typescript";

const source = "let x: string = 'string'";

let result = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS } })

console.log(JSON.stringify(result));
複製代碼

從一個 JavaScript 文件中獲得 DTS

只會在 TypeScript 3.7 及以上的版本上運行。這個例子用於說明如何獲取 JavaScript 文件列表,並在終端顯示其生成的 d.ts 文件。

import * as ts from 'typescript'

function compile(fileNames: string[], options: ts.CompilerOptions): void {
  // 用內存中的 emit 建立程序
  const createdFiles = {}
  const host = ts.createCompilerHost(options)
  host.writeFile = (fileName: string, contents: string) => createdFiles[fileName] = contents;

  // 準備並 emit 出 d.ts 文件
  const program = ts.createProgram(fileNames, options, host);
  program.emit()

  // 遍歷全部的輸入文件
  fileNames.forEach(file => {
    console.log('### JavaScript\n');
    console.log(host.readFile(file));

    console.log('### Type Defination\n');
    const dts = file.replace('.js', '.d.ts')
    console.log(createdFiles[dts]);
  })
}

// 運行complier
compile(process.argv.slice(2), {
  allowJs: true,
  declaration: true,
  emitDeclarationOnly: true,
})
複製代碼

從新打印 TypeScript 文件的部分

本示例將會註銷 JavaScript 源文件的 Typescript 子部分,當你但願你的應用程序的代碼成爲真實的來源時,這個模式是頗有用的。例如經過 JSDoc 註釋顯示導出。

import * as ts from 'typescript'

/** * 從源文件中打印出特定的節點 * * @param file a path to a file * @param identifiers top level identifiers available */
function extract(file: string, identifiers: string[]): void {
  // 建立一個表明項目的 Program
  // 而後取出它的源文件來解析它的 AST
  let program = ts.createProgram([file], { allowJs: true });
  const sourceFile = program.getSourceFile(file)

  // 爲了打印 AST,咱們將使用 TypeScript 的 printer
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });

  // 要給出有建設性的錯誤消息,請跟蹤找到和未找到的標識符
  const unfoundNodes = [], foundNodes = [];

  // 遍歷源文件的 AST 的根節點,可能有個頂級的標識符
  // 你可使用 https://ts-ast-viewer.com/ 展開這個列表,查看文件的 AST
  // 而後使用下面的相同模式
  ts.forEachChild(sourceFile, node => {
    let name = ''

    // 這是一組不完成的 AST 節點
    if (ts.isFunctionDeclaration(node)) {
      name = node.name.text;
      // 打印的時候隱藏方法主體
      node.body = undefined;
    } else if (ts.isVariableStatement(node)) {
      name = node.declarationList.declarations[0].name.getText(sourceFile);
    } else if (ts.isInterfaceDeclaration(node)) {
      name = node.name.text;
    }

    const container = identifiers.includes(name) ? foundNodes : unfoundNodes;
    container.push([name, node])
  });

  // 要麼打印找到的節點,要麼提供找到的標識符的列表
  if (!foundNodes.length) {
    console.log(`Could not found any of ${identifiers.join(',')} in ${file}, found: ${unfoundNodes.filter(f => f[0].map(f=>f[0]).join(''))}`);
    process.exitCode = 1;
  } else {
    foundNodes.map(f => {
      const [name , node] = f;
      console.log('###' + name + '\n');
      console.log(printer.printNode(ts.EmitHint.Unspecified, node, sourceFile)) + '\n';
    });
  }
}

// 使用腳本的參數來運行 extract 函數
複製代碼

使用小的檢查器來 Traverse AST

Node接口是 TypeScript AST 的根接口。一般咱們使用 forEachChild函數來遞歸遍歷樹。這包含了訪問者模式,而且一般會提供更多的靈活性。

做爲如何遍歷文件 AST 的例子,請考慮執行如下操做的最小的的檢查器:

  • 檢查全部循環語句是否被花括號括起來。
  • 檢查全部 if/else 語句是否被花括號括起來。
  • 強等符號(===/!==)是否用來替換了鬆的那種(== / !=)。
import { readFileSync } from 'fs'
import * as ts from 'typescript'

export function delint (sourceFile: ts.SourceFile) {
  delintNode(sourceFile)

  function delintNode (node: ts.Node) {
    switch (node.kind) {
      case ts.SyntaxKind.ForStatement:
      case ts.SyntaxKind.ForInStatement:
      case ts.SyntaxKind.WhileStatement:
      case ts.SyntaxKind.DoStatement:
        if ((node as ts.IterationStatement).statement.kind !== ts.SyntaxKind.Block) {
          report(
            node,
            'A looping statement\'s contents should be wrapped in a block body.'
          )
        }
        break;
      case ts.SyntaxKind.IfStatement:
        const ifStatement = node as ts.IfStatement;
        if (ifStatement.thenStatement.kind !== ts.SyntaxKind.Block) {
          report(ifStatement.thenStatement, 'An if statement\'s contents should be wrapped in a block body.');
        }
        if (
          ifStatement.elseStatement &&
          ifStatement.elseStatement.kind !== ts.SyntaxKind.Block &&
          ifStatement.elseStatement.kind !== ts.SyntaxKind.IfStatement
        ) {
          report(
            ifStatement.elseStatement,
            'An else statement\'s contents should be wrapped in a block body.'
          );
        }
        break;
      case ts.SyntaxKind.BinaryExpression:
        const op = (node as ts.BinaryExpression).operatorToken.kind;
        if (op === ts.SyntaxKind.EqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsToken) {
          report(node, 'Use \'==\' and \'!==\'.');
        }
        break;
    }
    ts.forEachChild(node, delintNode)
  }

  function report (node: ts.Node, message: string) {
    const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
    console.log(`${sourceFile.fileName} (${line + 1}, ${character + 1}): ${message}`);
  }
}

const fileNames = process.argv.slice(2);
fileNames.forEach(fileName => {
  // Parse a file
  const sourceFile = ts.createSourceFile(
    fileName,
    readFileSync(fileName).toString(),
    ts.ScriptTarget.ES2015,
    /* setParentNodes */ true
  );

  // delint it
  delint(sourceFile)
});
複製代碼

在本例子中,咱們不須要建立類型檢查器,由於咱們只想 traverse 每一個源文件。

全部的 ts.SyntaxKind 可以在枚舉類型中找到。

編寫增量程序監視器

TypeScript 2.7 引入了兩個新的 API:一個用於建立 「watcher」 程序,並提供一組觸發重構的 API;另外一個 「builder」 API,watcher 能夠利用這個 API。BuilderPrograms 是一種程序實例,若是模塊或其依賴項沒有以級聯方式更新,那麼它就能緩存錯誤並在以前編譯的模塊上發出錯誤。「watcher」程序能夠利用生成器程序實例在編譯中只更新受影響文件的結果(例如錯誤併發出)。這能夠加快有許多文件的大型項目的速度。

該 API 在 compiler 內部使用,用於實現 --watch 模式,但也能夠被其餘工具使用,例如:

import ts = require("typescript")

const formatHost: ts.FormatDiagnosticsHost = {
  getCanonicalFileName: path => path,
  getCurrentDirectory: ts.sys.getCurrentDirectory,
  getNewLine: () => ts.sys.newLine
};

function watchMain () {
  const configPath = ts.findConfigFile(
    /*searchPath*/ "./",
    ts.sys.fileExists,
    "tsconfig.json"
  );
  if (!configPath) {
    throw new Error("Could not find a valid 'tsconfig.json'.");
  }

  // TypeScript 可使用幾種不一樣的程序建立「策略」:
  // * ts.createEmitAndSemanticDiagnosticsBuilderProgram
  // * ts.createSemanticDiagnosticsBuilderProgram
  // * ts.createAbstractBuilder

  // 前兩個 API 產生「生成器程序」。
  // 它們使用增量策略僅從新檢查和發出文件,這些文件內容可能已經更改
  // 或者其依賴項可能發生改變,這些更改可能會影響先前的類型檢查和發出結果的更改
  // 最後一個 API 會在每一個更改後都會執行完整的類型檢查。
  // 在 `createEmitAndSemanticDiagnosticsBuilderProgram` 和 `createSemanticDiagnosticsBuilderProgram` 惟一的區別是不會 emit
  // 對於純類型檢查場景,或者當另外一個工具/進程處理髮出時,使用 `createSemanticDiagnosticsBuilderProgram` 獲取會更可取
  const createProgram = ts.createSemanticDiagnosticsBuilderProgram;

  // 注意,`createWatchCompilerHost` 還有一個重載,它須要一組根文件。
  const host = ts.createWatchCompilerHost(
    configPath,
    {},
    ts.sys,
    createProgram,
    reportDiagnostic,
    reportWatchStatusChanged
  );

  // 從技術上講,你能夠覆蓋主機上的任何給定鉤子函數,儘管你可能不須要這樣作。
  // 注意,咱們假設 `origCreateProgram` 和 `origPostProgramCreate` 根本不使用 `this`。
  const origCreateProgram = host.createProgram
  host.createProgram = (rootNames: ReadonlyArray<string>, options, host, oldProgram) => {
    console.log("** We're about to create the program! **");
    return origCreateProgram(rootNames, options, host, oldProgram);
  };

  const origPostProgramCreate = host.afterProgramCreate;
  host.afterProgramCreate = program => {
    console.log("** We finished making the program! **");
    origPostProgramCreate!(program);
  };

  // `createWatchProgram` 建立一個初始程序、監視文件,並隨着時間的推移更新更新程序。
  ts.createWatchProgram(host);
}

function reportDiagnostic(diagnostic: ts.Diagnostic) {
  console.error("Error", diagnostic.code, ":", ts.flattenDiagnosticMessageText(diagnostic.messageText, formatHost.getNewLine()));
}

/** * 每次監視狀態更改時,都會打印出診斷信息 * 這主要用於例如 「開始編譯」 或 「編譯完成」 之類的消息。 */
function reportWatchStatusChanged (diagnostic: ts.Diagnostic) {
  console.info(ts.formatDiagnostic(diagnostic, formatHost));
}

watchMain();
複製代碼

使用語言服務的增量構建支持

能夠參考 Using the Language Service API 獲得更多詳細信息。

服務層提供了一層附加的實用程序,能夠幫助簡化一些複雜的場景。在下面的代碼段中,咱們將嘗試構建一個增量構建服務器,該服務器會監視一組文件並僅更新已更改的文件的輸出。咱們會建立一個叫作 LanguageService 對象來實現這一點。與上一個示例中的程序相似。咱們須要一個 LanguageServiceHostLanguageServiceHost 經過 versionisOpen 標誌和 ScriptSnapshot 來實現這一點。該 version 容許語言服務跟蹤文件的更改。isOpen 告訴語言服務在使用文件時將 AST 保存在內存中。ScriptSnapshot 是一種對文本的抽象,它容許語言服務查詢更改。

若是你只是想實現監視樣式的功能,能夠去研究一下上面的監視器程序 API。

import * as fs from 'fs'
import * as ts from 'typescript'

function watch (rootFileNames: string[], options: ts.CompilerOptions) {
  const files: ts.MapLike<{ version: number }> = {};

  // 初始化文件列表
  rootFileNames.forEach(fileName => {
    files[fileName] = { version: 0 };
  });

  // 建立語言服務主機以容許 LS 與主機進行通訊
  const serviceHost: ts.LanguageServiceHost = {
    getScriptFileNames: () => rootFileNames,
    getScriptVersion: fileName => files[fileName] && files[fileName].version.toString(),
    getScriptSnapshot: fileName => {
      if (!fs.existsSync(fileName)) {
        return undefined
      }
      return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString())
    },
    getCurrentDirectory: () => process.cwd(),
    getCompilationSettings: () => options,
    getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
    fileExists: ts.sys.fileExists,
    readFile: ts.sys.readFile,
    readDirectory: ts.sys.readDirectory
  };

  // 建立語言服務文件
  const services = ts.createLanguageService(serviceHost, ts.createDocumentRegistry());

  // 開始監聽程序
  rootFileNames.forEach(fileName => {
    // 首先,發出全部的文件
    emitFile(fileName);

    // 增長一個腳本在上面來監聽下一次改變
    fs.watchFile(fileName, { persistent: true, interval: 250 }, (curr, prev) => {
      // 檢查時間戳
      if (+curr.mtime <= +prev.mtime) {
        return;
      }

      // 更新 version 代表文件已經修改
      files[fileName].version ++

      // 把更改寫入磁盤
      emitFile(fileName)
    })
  })

  function emitFile(fileName: string) {
    let output = services.getEmitOutput(fileName);

    if (!output.emitSkipped) {
      console.log(`Emitting ${fileName}`);
    } else {
      console.log(`Emitting ${fileName} failed`);
      logErrors(fileName)
    }

    output.outputFiles.forEach(o => {
      fs.writeFileSync(o.name, o.text, "utf8")
    })
  }

  function logErrors(fileName: string) {
    let allDiagnostics = services
      .getCompilerOptionsDiagnostics()
      .concat(services.getSyntacticDiagnostics(fileName))
      .concat(services.getSemanticDiagnostics(fileName));
    
    allDiagnostics.forEach(diagnostic => {
      let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
      if (diagnostic.file) {
        let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(
          diagnostic.start!
        );
        console.log(` Error ${diagnostic.file.fileName} (${line + 1}): ${message}`);
      } else {
        console.log(` Error: ${message} `);
      }
    })
  }
}

// 初始化組成程序的文件,使之成爲當前目錄中的全部 .ts 文件
const currentDirectoryFiles = fs.readdirSync(process.cwd()).filter(fileName => fileName.length >= 3 && fileName.substr(fileName.length - 3, 3) === '.ts');

// 開始監聽
watch(currentDirectoryFiles, { module: ts.ModuleKind.CommonJS })
複製代碼

自定義模塊分辨率

經過實現可選方法,能夠重寫編譯器解析模塊的標準方式:CompilerHost.resolvedModuleNames:

CompilerHost.resolveModuleNames(moduleNames: string[], containingFile: string): string[]

該方法在一個文件中給出一個模塊名列表,並指望返回一個大小爲 moduleNames.length 的數組,該數組的每一個元素都存儲如下任一內容:

  • 具備非空屬性 resolveFileName 的 ResolveModule 實例 moduleNames 數組中對應名稱的解析
  • 若是模塊名稱不能被解析就返回 undefined

你能夠經過調用 resolveModuleName 來調用標準模塊解析過程:

resolveModuleName(moduleName: string, containingFile: string, options: CompilerOptions, moduleResolutionHost: ModuleResolutionHost): ResolvedModuleNameWithFallbackLocations.

這個函數返回一個對象,該對象存儲模塊解析的結果 (resolvedModule 屬性的值)以及在作出當前決策以前被認爲是候選的文件名列表。

import * as ts from "typescript";
import * as path from "path";

function createCompilerHost(options: ts.CompilerOptions, moduleSearchLocations: string[]): ts.CompilerHost {
  return {
    getSourceFile,
    getDefaultLibFileName: () => "lib.d.ts",
    writeFile: (fileName, content) => ts.sys.writeFile(fileName, content),
    getCurrentDirectory: () => ts.sys.getCurrentDirectory(),
    getDirectories: path => ts.sys.getDirectories(path),
    getCanonicalFileName: fileName =>
      ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(),
    getNewLine: () => ts.sys.newLine,
    useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
    fileExists,
    readFile,
    resolveModuleNames
  };

  function fileExists(fileName: string): boolean {
    return ts.sys.fileExists(fileName);
  }

  function readFile(fileName: string): string | undefined {
    return ts.sys.readFile(fileName);
  }

  function getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) {
    const sourceText = ts.sys.readFile(fileName);
    return sourceText !== undefined
      ? ts.createSourceFile(fileName, sourceText, languageVersion)
      : undefined;
  }

  function resolveModuleNames(
    moduleNames: string[],
    containingFile: string
  ): ts.ResolvedModule[] {
    const resolvedModules: ts.ResolvedModule[] = [];
    for (const moduleName of moduleNames) {
      // try to use standard resolution
      let result = ts.resolveModuleName(moduleName, containingFile, options, {
        fileExists,
        readFile
      });
      if (result.resolvedModule) {
        resolvedModules.push(result.resolvedModule);
      } else {
        // check fallback locations, for simplicity assume that module at location
        // should be represented by '.d.ts' file
        for (const location of moduleSearchLocations) {
          const modulePath = path.join(location, moduleName + ".d.ts");
          if (fileExists(modulePath)) {
            resolvedModules.push({ resolvedFileName: modulePath });
          }
        }
      }
    }
    return resolvedModules;
  }
}

function compile(sourceFiles: string[], moduleSearchLocations: string[]): void {
  const options: ts.CompilerOptions = {
    module: ts.ModuleKind.AMD,
    target: ts.ScriptTarget.ES5
  };
  const host = createCompilerHost(options, moduleSearchLocations);
  const program = ts.createProgram(sourceFiles, options, host);

  /// do something with program...
}
複製代碼

建立和打印 TypeScript AST

TypeScript 有工廠函數和能夠結合使用的打印 API。

  • 工廠函數容許你以 TypeScript 的 AST 格式生成新的樹節點。
  • 打印 API 能夠獲取現有的樹結構 (由 createSourceFile 或工廠函數生成),並生成輸出字符串。

下面是一個使用這兩個方法生成階乘函數的示例:

import ts = require("typescript");

function makeFactorialFunction() {
  const functionName = ts.createIdentifier("factorial");
  const paramName = ts.createIdentifier("n");
  const parameter = ts.createParameter(
    /*decorators*/ undefined,
    /*modifiers*/ undefined,
    /*dotDotDotToken*/ undefined,
    paramName
  );

  const condition = ts.createBinary(paramName, ts.SyntaxKind.LessThanEqualsToken, ts.createLiteral(1));
  const ifBody = ts.createBlock([ts.createReturn(ts.createLiteral(1))], /*multiline*/ true);

  const decrementedArg = ts.createBinary(paramName, ts.SyntaxKind.MinusToken, ts.createLiteral(1));
  const recurse = ts.createBinary(paramName, ts.SyntaxKind.AsteriskToken, ts.createCall(functionName, /*typeArgs*/ undefined, [decrementedArg]));
  const statements = [ts.createIf(condition, ifBody), ts.createReturn(recurse)];

  return ts.createFunctionDeclaration(
    /*decorators*/ undefined,
    /*modifiers*/ [ts.createToken(ts.SyntaxKind.ExportKeyword)],
    /*asteriskToken*/ undefined,
    functionName,
    /*typeParameters*/ undefined,
    [parameter],
    /*returnType*/ ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
    ts.createBlock(statements, /*multiline*/ true)
  );
}

const resultFile = ts.createSourceFile("someFileName.ts", "", ts.ScriptTarget.Latest, /*setParentNodes*/ false, ts.ScriptKind.TS);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });

const result = printer.printNode(ts.EmitHint.Unspecified, makeFactorialFunction(), resultFile);
console.log(result);
複製代碼

使用類型檢查器

在本例中,咱們將遍歷 AST 並使用檢查器來序列化類信息。咱們將使用類型檢查器來獲取符號和類型信息,同時爲導出的類、它們的構造函數和各自的構造函數參數獲取 JSDoc 註釋。

import * as ts from "typescript";
import * as fs from "fs";

interface DocEntry {
  name?: string;
  fileName?: string;
  documentation?: string;
  type?: string;
  constructors?: DocEntry[];
  parameters?: DocEntry[];
  returnType?: string;
}

/** Generate documentation for all classes in a set of .ts files */
function generateDocumentation( fileNames: string[], options: ts.CompilerOptions ): void {
  // Build a program using the set of root file names in fileNames
  let program = ts.createProgram(fileNames, options);

  // Get the checker, we will use it to find more about classes
  let checker = program.getTypeChecker();
  let output: DocEntry[] = [];

  // Visit every sourceFile in the program
  for (const sourceFile of program.getSourceFiles()) {
    if (!sourceFile.isDeclarationFile) {
      // Walk the tree to search for classes
      ts.forEachChild(sourceFile, visit);
    }
  }

  // print out the doc
  fs.writeFileSync("classes.json", JSON.stringify(output, undefined, 4));

  return;

  /** visit nodes finding exported classes */
  function visit(node: ts.Node) {
    // Only consider exported nodes
    if (!isNodeExported(node)) {
      return;
    }

    if (ts.isClassDeclaration(node) && node.name) {
      // This is a top level class, get its symbol
      let symbol = checker.getSymbolAtLocation(node.name);
      if (symbol) {
        output.push(serializeClass(symbol));
      }
      // No need to walk any further, class expressions/inner declarations
      // cannot be exported
    } else if (ts.isModuleDeclaration(node)) {
      // This is a namespace, visit its children
      ts.forEachChild(node, visit);
    }
  }

  /** Serialize a symbol into a json object */
  function serializeSymbol(symbol: ts.Symbol): DocEntry {
    return {
      name: symbol.getName(),
      documentation: ts.displayPartsToString(symbol.getDocumentationComment(checker)),
      type: checker.typeToString(
        checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration!)
      )
    };
  }

  /** Serialize a class symbol information */
  function serializeClass(symbol: ts.Symbol) {
    let details = serializeSymbol(symbol);

    // Get the construct signatures
    let constructorType = checker.getTypeOfSymbolAtLocation(
      symbol,
      symbol.valueDeclaration!
    );
    details.constructors = constructorType
      .getConstructSignatures()
      .map(serializeSignature);
    return details;
  }

  /** Serialize a signature (call or construct) */
  function serializeSignature(signature: ts.Signature) {
    return {
      parameters: signature.parameters.map(serializeSymbol),
      returnType: checker.typeToString(signature.getReturnType()),
      documentation: ts.displayPartsToString(signature.getDocumentationComment(checker))
    };
  }

  /** True if this is visible outside this file, false otherwise */
  function isNodeExported(node: ts.Node): boolean {
    return (
      (ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Export) !== 0 ||
      (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile)
    );
  }
}

generateDocumentation(process.argv.slice(2), {
  target: ts.ScriptTarget.ES5,
  module: ts.ModuleKind.CommonJS
});
複製代碼

使用這個腳本:

tsc docGenerator.ts --m commonjs
node docGenerator.js test.ts
複製代碼

輸入如下例子:

/** * Documentation for C */
class C {
    /** * constructor documentation * @param a my parameter documentation * @param b another parameter documentation */
    constructor(a: string, b: C) { }
}
複製代碼

咱們能拿到這樣的輸出:

[
    {
        "name": "C",
        "documentation": "Documentation for C ",
        "type": "typeof C",
        "constructors": [
            {
                "parameters": [
                    {
                        "name": "a",
                        "documentation": "my parameter documentation",
                        "type": "string"
                    },
                    {
                        "name": "b",
                        "documentation": "another parameter documentation",
                        "type": "C"
                    }
                ],
                "returnType": "C",
                "documentation": "constructor documentation"
            }
        ]
    }
]
複製代碼
相關文章
相關標籤/搜索