type2docfx
Version:
A tool to convert json format output from TypeDoc to universal reference model for DocFx to consume.
506 lines (453 loc) • 17.7 kB
text/typescript
import { YamlModel, YamlParameter, Exception, Type, UnionType } from './interfaces/YamlModel';
import { Node, Tag, Parameter, Comment, ParameterType } from './interfaces/TypeDocModel';
import { UidMapping } from './interfaces/UidMapping';
import { RepoConfig } from './interfaces/RepoConfig';
import { convertLinkToGfm, getTextAndLink } from './helpers/linkConvertHelper';
import { typeToString } from './idResolver';
import { flags } from './common/flags';
import * as _ from 'lodash';
export function traverse(node: Node, parentUid: string, parentContainer: YamlModel[], moduleName: string, uidMapping: UidMapping, repoConfig: RepoConfig): void {
if (node.flags.isPrivate || node.flags.isProtected) {
return;
}
if (parentUid.length && !node.flags.isExported) {
return;
}
if (node.name && node.name[0] === '_') {
return;
}
let uid = parentUid;
if (node.kind === 0) {
uid = node.name;
}
if (node.kindString === 'Module') {
if (!moduleName) {
moduleName = node.name.replace(/"/g, '');
} else {
moduleName = `${moduleName}.${node.name.replace(/"/g, '')}`;
}
uid += `.${moduleName.replace(/\//g, '.')}`;
console.log(`${node.kindString}: ${uid}`);
}
let myself: YamlModel = null;
if ((node.kindString === 'Class' || node.kindString === 'Interface' || node.kindString === 'Enumeration' || node.kindString === 'Type alias') && node.name) {
uid += `.${node.name}`;
console.log(`${node.kindString}: ${uid}`);
let customModuleName = findModuleInfoInComment(node.comment);
if (customModuleName) {
if (moduleName) {
moduleName += `.${customModuleName}`;
} else {
moduleName = customModuleName;
}
}
myself = {
uid: uid,
name: node.name,
fullName: node.name + getGenericType(node.typeParameter),
children: [],
langs: ['typeScript'],
type: node.kindString.toLowerCase(),
summary: node.comment ? findDescriptionInComment(node.comment) : ''
};
if (myself.type === 'enumeration') {
myself.type = 'enum';
}
if (myself.type === 'type alias') {
myself.type = 'class';
myself.summary += `\n${generateTypeAliasInformation(node)}`;
}
if (node.extendedTypes && node.extendedTypes.length) {
myself.extends = {
name : extractType(node.extendedTypes[0])[0]
};
}
if (repoConfig && node.sources && node.sources.length) {
myself.source = {
path: node.sources[0].fileName,
// shift one line up as systematic off for TypeDoc
startLine: node.sources[0].line,
remote: {
path: `${repoConfig.basePath}\\${node.sources[0].fileName}`,
repo: repoConfig.repo,
branch: repoConfig.branch
}
};
}
let tokens = parentUid.split('.');
myself.package = tokens[0];
}
if ((node.kindString === 'Method' || node.kindString === 'Function' || node.kindString === 'Constructor') && node.name) {
if (!node.signatures || node.inheritedFrom) {
return;
}
uid += '.' + node.name;
console.log(` - ${node.kindString}: ${uid}`);
myself = {
uid: uid,
name: node.name,
children: [],
type: '',
langs: ['typeScript'],
summary: '',
syntax: {
content: ''
}
};
extractInformationFromSignature(myself, node, 0);
}
if (node.kindString === 'Enumeration member' && node.name) {
uid += '.' + node.name;
console.log(` - ${node.kindString}: ${uid}`);
myself = {
uid: uid,
name: node.name,
children: [],
langs: ['typeScript'],
summary: node.comment ? findDescriptionInComment(node.comment) : '',
type: 'field'
};
if (node.defaultValue) {
myself.numericValue = parseInt(node.defaultValue, 10);
}
}
if (node.kindString === 'Property' && node.name) {
if (node.inheritedFrom) {
return;
}
uid += '.' + node.name;
console.log(` - ${node.kindString}: ${uid}`);
let isPublic = node.flags && node.flags.isPublic ? 'public ' : '';
let isStatic = node.flags && node.flags.isStatic ? 'static ' : '';
let isOptional = node.flags && node.flags.isOptional ? '?' : '';
let defaultValue = node.defaultValue ? ` = ${_.trim(node.defaultValue)}` : '';
myself = {
uid: uid,
name: node.name,
fullName: node.name,
children: [],
langs: ['typeScript'],
type: node.kindString.toLowerCase(),
summary: node.comment ? findDescriptionInComment(node.comment) : '',
optional: node.flags && node.flags.isOptional,
syntax: {
content: `${isPublic}${isStatic}${node.name}${isOptional}: ${typeToString(extractType(node.type)[0])}${defaultValue}`,
return: {
type: extractType(node.type)
}
}
};
}
if (node.kindString === 'Accessor' && node.name) {
if (node.inheritedFrom) {
return;
}
uid += '.' + node.name;
console.log(` - ${node.kindString}: ${uid}`);
let signatureType;
if (node.getSignature) {
if (Array.isArray(node.getSignature)) {
signatureType = node.getSignature[0].type;
} else {
signatureType = node.getSignature.type;
}
} else if (node.setSignature) {
if (Array.isArray(node.setSignature)) {
signatureType = node.setSignature[0].type;
} else {
signatureType = node.setSignature.type;
}
}
myself = {
uid: uid,
name: node.name,
fullName: node.name,
children: [],
langs: ['typeScript'],
type: 'property',
summary: node.comment ? findDescriptionInComment(node.comment) : '',
syntax: {
content: `${node.flags && node.flags.isStatic ? 'static ' : ''}${typeToString(extractType(signatureType)[0])} ${node.name}`,
return: {
type: extractType(signatureType)
}
}
};
}
if (myself) {
myself.summary = convertLinkToGfm(myself.summary);
uidMapping[node.id] = myself.uid;
parentContainer.push(myself);
if (flags.hasModule && moduleName) {
myself.module = moduleName;
}
if (node.comment || node.signatures && node.signatures.length && node.signatures[0].comment) {
let comment = node.comment ? node.comment : node.signatures[0].comment;
let deprecated = findDeprecatedInfoInComment(comment);
if (deprecated != null) {
myself.deprecated = {
content: convertLinkToGfm(deprecated)
};
}
let inherits = findInheritsInfoInComment(comment);
if (inherits != null) {
let tokens = getTextAndLink(inherits);
if (tokens.length === 2) {
myself.extends = {
name: tokens[0],
href: tokens[1]
};
}
}
let isPreview = findPreviewInfoInComment(comment);
if (isPreview != null) {
myself.isPreview = true;
}
let remarks = findRemarkInfoInComment(comment);
if (remarks != null) {
myself.remarks = convertLinkToGfm(remarks);
}
}
if (node.signatures && node.signatures.length > 1) {
for (let index = 1; index < node.signatures.length; index++) {
let newMethod = _.cloneDeep(myself);
newMethod.uid = `${newMethod.uid}_${index}`;
extractInformationFromSignature(newMethod, node, index);
parentContainer.push(newMethod);
}
}
}
if (node.children && node.children.length > 0) {
node.children.forEach(subNode => {
if (myself) {
traverse(subNode, uid, myself.children as YamlModel[], moduleName, uidMapping, repoConfig);
} else {
traverse(subNode, uid, parentContainer, moduleName, uidMapping, repoConfig);
}
});
}
}
function extractInformationFromSignature(method: YamlModel, node: Node, signatureIndex: number) {
if (node.signatures[signatureIndex].comment) {
method.summary = findDescriptionInComment(node.signatures[signatureIndex].comment);
}
method.syntax.parameters = fillParameters(node.signatures[signatureIndex].parameters);
if (node.signatures[signatureIndex].type && node.kindString !== 'Constructor' && node.signatures[signatureIndex].type.name !== 'void') {
method.syntax.return = {
type: extractType(node.signatures[signatureIndex].type)
};
}
let exceptions;
if (node.signatures[signatureIndex].comment && node.signatures[signatureIndex].comment.tags) {
exceptions = node.signatures[signatureIndex].comment.tags.filter(tag => tag.tag === 'throws');
}
if (exceptions && exceptions.length) {
method.exceptions = exceptions.map(e => extractException(e));
}
if (node.kindString === 'Method' || node.kindString === 'Function') {
method.name = node.name;
let functionBody = generateCallFunction(method.name, method.syntax.parameters, node.signatures[signatureIndex].typeParameter);
method.syntax.content = `${node.flags && node.flags.isStatic ? 'static ' : ''}function ${functionBody}`;
method.type = node.kindString.toLowerCase();
} else {
method.name = method.uid.split('.').reverse()[1];
let functionBody = generateCallFunction(method.name, method.syntax.parameters);
method.syntax.content = `new ${functionBody}`;
method.type = 'constructor';
}
}
function hasCommonPrefix(types: ParameterType[]): boolean {
if (types && types.length > 1 && types[0].name) {
if (types[0].name.indexOf('.') < 0) {
return false;
}
let prefix = types[0].name.split('.')[0];
types.forEach(t => {
if (!t.name || t.name.split('.')[0] !== prefix) {
return false;
}
});
return true;
}
return false;
}
function extractType(type: ParameterType): Type[] {
let result: Type[] = [];
if (type === undefined) {
return result;
}
if (type.type === 'union' && type.types && type.types.length) {
if (hasCommonPrefix(type.types)) {
result.push({
typeName: type.types[0].name.split('.')[0]
});
} else {
result.push({
unionType: {
types: type.types.map(t => extractType(t)[0])
}
});
}
} else if (type.type === 'array') {
let newType = extractType(type.elementType);
result.push({
arrayType: newType[0]
});
} else if (type.type === 'intersection' && type.types.length) {
result.push({
intersectionType: {
types: type.types.map(t => extractType(t)[0])
}
});
} else if (type.type === 'reflection' && type.declaration) {
if (type.declaration.indexSignature && type.declaration.indexSignature.length) {
result.push({
reflectedType: {
key: {
typeName: type.declaration.indexSignature[0].parameters[0].type.name,
typeId: type.declaration.indexSignature[0].parameters[0].type.id
},
value: {
typeName: type.declaration.indexSignature[0].type.name,
typeId: type.declaration.indexSignature[0].type.id
}
}
});
} else if (type.declaration.signatures && type.declaration.signatures.length) {
result.push({
typeName: `${generateCallFunction('', fillParameters(type.declaration.signatures[0].parameters))} => ${typeToString(extractType(type.declaration.signatures[0].type)[0])}`
});
} else {
result.push({
typeName: 'function'
});
}
} else if (type.typeArguments && type.typeArguments.length) {
result.push({
genericType: {
outter: {
typeName: type.name,
typeId: type.id
},
inner: type.typeArguments.map(t => extractType(t)[0])
}
});
} else if (type.name) {
result.push({
typeName: type.name,
typeId: type.id
});
} else if (type.value) {
result.push({
typeName: `"${type.value}"`
});
} else {
result.push({
typeName: 'function'
});
}
return result;
}
function extractException(exception: Tag): Exception {
let tokens = exception.text.match(/{(.*)} +((.|\s)+)/);
if (tokens.length >= 3) {
return {
type: tokens[1],
description: tokens[2]
};
}
return null;
}
function findModuleInfoInComment(comment: Comment): string {
return findInfoInComment('module', comment);
}
function findInheritsInfoInComment(comment: Comment): string {
return findInfoInComment('inherits', comment);
}
function findDeprecatedInfoInComment(comment: Comment): string {
return findInfoInComment('deprecated', comment);
}
function findPreviewInfoInComment(comment: Comment): string {
return findInfoInComment('beta', comment);
}
function findRemarkInfoInComment(comment: Comment): string {
return findInfoInComment('remarks', comment);
}
function findInfoInComment(infoName: string, comment: Comment): string {
if (comment && comment.tags) {
let text: string = null;
comment.tags.forEach(tag => {
if (tag.tag === infoName) {
text = tag.text;
return;
}
});
if (text) {
return text.trim();
}
}
return null;
}
function findDescriptionInComment(comment: Comment): string {
if (!comment) {
return '';
}
if (comment.tags) {
let text: string = null;
comment.tags.forEach(tag => {
if (tag.tag === 'classdesc' || tag.tag === 'description' || tag.tag === 'exemptedapi') {
text = tag.text;
return;
}
});
if (text) {
return text.trim();
}
}
if (comment.shortText && comment.text) {
return `${comment.shortText}\n${comment.text}`;
}
if (comment.text) {
return comment.text.trim();
}
if (comment.shortText) {
return comment.shortText.trim();
}
return '';
}
function fillParameters(parameters: Parameter[]): YamlParameter[] {
if (parameters) {
return parameters.map<YamlParameter>(p => {
let description = '';
if (p.comment) {
description = (p.comment.shortText && p.comment.shortText !== '') ? p.comment.shortText : p.comment.text;
}
return <YamlParameter> {
id: p.name,
type: extractType(p.type),
description: convertLinkToGfm(description),
optional: p.flags && p.flags.isOptional
};
});
}
return [];
}
function generateCallFunction(prefix: string, parameters: YamlParameter[], typeParameters?: ParameterType[]): string {
if (parameters) {
return `${prefix}${getGenericType(typeParameters)}(${parameters.map(p => `${p.id}${p.optional ? '?' : ''}: ${(typeToString(p.type[0]))}`).join(', ')})`;
}
return '';
}
function getGenericType(typeParameters: ParameterType[]): string {
if (typeParameters && typeParameters.length) {
return `<${typeParameters[0].name}>`;
}
return '';
}
function generateTypeAliasInformation(node: Node) {
if (!node || !node.type || !node.type.types || node.type.type.length < 2) {
return '';
}
let typeAliases = node.type.types.map(t => typeToString(extractType(t)[0]));
return `"${node.name}" is a type alias. It refers to ${_.take(typeAliases, typeAliases.length - 1).join(', ')} and ${_.last(typeAliases)}.`;
}