原文地址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"
}
]
}
]
复制代码