ts-code-info
Version:
TypeScript code info extractor
186 lines (155 loc) • 6.46 kB
text/typescript
import * as ts from 'typescript';
import * as types from './types';
export type ParseOptions = {
checker: ts.TypeChecker,
onlyExported?: boolean
}
export function isNodeExported(node: ts.Node): boolean {
return (node.flags & ts.NodeFlags.ExportContext) !== 0 || (node.parent && node.parent.kind === ts.SyntaxKind.SourceFile) || false;
}
function onlyExported(node: ts.Node, opts: ParseOptions): boolean {
return opts.onlyExported ? isNodeExported(node) : true;
}
export function typeOfSymbol(symbol: ts.Symbol, opts: ParseOptions): types.TypeOf {
return typeOfInfo(opts.checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration!), opts);
}
export function typeOfInfo(type: ts.Type, opts: ParseOptions): types.TypeOf {
return {
text: opts.checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseFullyQualifiedType),
tsType: type
}
}
export function symbolInfo(symbol: ts.Symbol|undefined, opts: ParseOptions) {
return {
type: symbol ? typeOfSymbol(symbol, opts) : undefined,
doc: symbol ? ts.displayPartsToString(symbol.getDocumentationComment()) : '',
name: symbol ? symbol.getName() : '',
};
}
export function parseSymbol<NodeDeclT extends ts.Node = ts.Node>(node: ts.Node, opts: ParseOptions, symbol?: ts.Symbol): types.DeclInfo<NodeDeclT> {
const isIdentifier = ts.isIdentifier(node);
const identifier = isIdentifier ? node as ts.Identifier : undefined;
if (identifier) node = identifier.parent!;
if (!symbol)
if (identifier) symbol = opts.checker.getSymbolAtLocation(identifier);
else symbol = opts.checker.getSymbolAtLocation(node);
return {
symbol,
identifier,
modifiers: node.modifiers ? parseModifiers(node.modifiers, opts) : undefined,
node: node as NodeDeclT,
exported: isNodeExported(node) ? true : undefined,
...symbolInfo(symbol, opts)
};
}
export function parseVariable(node: ts.VariableDeclaration, opts: ParseOptions): types.Variable {
return {
...parseSymbol(node.name || node, opts),
};
}
export function parseParameter(node: ts.ParameterDeclaration, opts: ParseOptions): types.Parameter {
return {
...parseSymbol(node.name || node, opts),
};
}
export function parseSignature(signature: ts.Signature, opts: ParseOptions) {
return {
parameters: signature.parameters.map(symb => symbolInfo(symb, opts)),
returnType: typeOfInfo(signature.getReturnType(), opts),
doc: ts.displayPartsToString(signature.getDocumentationComment())
};
}
export function parseFunc(node: ts.FunctionDeclaration, opts: ParseOptions): types.Func {
const symb = parseSymbol<ts.FunctionDeclaration>(node.name || node, opts);
let returnType: types.TypeOf|undefined = undefined;
if (symb.type) {
const callSignatures = symb.type.tsType.getCallSignatures();
if (callSignatures.length > 0) {
const callSignature = callSignatures[0];
const parsedSignature = parseSignature(callSignature, opts);
returnType = parsedSignature.returnType;
}
}
return {
...symb,
args: node.parameters.map(param => parseParameter(param, opts)),
returnType,
bodyNode: node.body!
};
}
export function parseTypeAlias(node: ts.TypeAliasDeclaration, opts: ParseOptions): types.TypeAlias {
return {
...parseSymbol(node.name || node, opts)
};
}
export function parseClass(node: ts.ClassDeclaration, opts: ParseOptions): types.Class {
const methods: types.ClassMethod[] = [];
const members: types.ClassMember[] = [];
ts.forEachChild(node, node => {
if (ts.isMethodDeclaration(node))
methods.push(parseClassMethod(node, opts));
else if (ts.isClassElement(node))
members.push(parseClassMember(node, opts));
});
return {
...parseSymbol(node.name || node, opts),
methods,
members,
decorators: []
}
}
export function parseClassMethod(node: ts.MethodDeclaration, opts: ParseOptions): types.ClassMethod {
const func = parseFunc(node as any as ts.FunctionDeclaration, opts);
return {
...func,
...parseSymbol(node.name || node, opts),
modifiers: []
};
}
export function parseClassMember(node: ts.ClassElement, opts: ParseOptions): types.ClassMember {
return {
...parseSymbol(node.name || node, opts),
modifiers: []
};
}
export function parseModuleBody(parentNode: ts.Node, opts: ParseOptions): types.ModuleBody {
const r: types.ModuleBody = {
functions: [],
variables: [],
classes: [],
types: []
};
ts.forEachChild(parentNode, node => {
if (ts.isFunctionDeclaration(node) && onlyExported(node, opts))
r.functions.push(parseFunc(node as ts.FunctionDeclaration, opts))
else if (ts.isVariableDeclaration(node) && onlyExported(node, opts))
r.variables.push(parseVariable(node as ts.VariableDeclaration, opts))
else if (ts.isClassDeclaration(node) && onlyExported(node, opts))
r.classes.push(parseClass(node as ts.ClassDeclaration, opts))
else if (ts.isTypeAliasDeclaration(node) && onlyExported(node, opts))
r.types.push(parseTypeAlias(node as ts.TypeAliasDeclaration, opts))
});
return r;
}
export function parseModule(node: ts.ModuleDeclaration, opts: ParseOptions): types.Module {
return {
...parseSymbol(node, opts),
...parseModuleBody(node, opts)
};
}
export function parseSourceFile(node: ts.SourceFile, opts: ParseOptions): types.FileInfo {
const modules: types.Module[] = [];
ts.forEachChild(node, node => {
if (ts.isModuleDeclaration(node) && onlyExported(node, opts))
modules.push(parseModule(node as ts.ModuleDeclaration, opts));
})
return {
...parseModuleBody(node, opts),
fileName: node.fileName,
modules,
sourceFile: node
};
}
export function parseModifiers(modifiers: ts.ModifiersArray, opts: ParseOptions): types.Modifier[] {
return modifiers.map(modifier => types.modifierFromSyntax(modifier));
}