原文地址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。這個例子是一個基礎的編輯器,它能獲取 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
})
複製代碼
建立一個 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));
複製代碼
只會在 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,
})
複製代碼
本示例將會註銷 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 函數
複製代碼
Node
接口是 TypeScript AST 的根接口。一般咱們使用 forEachChild
函數來遞歸遍歷樹。這包含了訪問者模式,而且一般會提供更多的靈活性。
做爲如何遍歷文件 AST 的例子,請考慮執行如下操做的最小的的檢查器:
===
/!==
)是否用來替換了鬆的那種(==
/ !=
)。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
對象來實現這一點。與上一個示例中的程序相似。咱們須要一個 LanguageServiceHost
。LanguageServiceHost
經過 version
、isOpen
標誌和 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
的數組,該數組的每一個元素都存儲如下任一內容:
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 有工廠函數和能夠結合使用的打印 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"
}
]
}
]
複製代碼