UNPKG

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
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)}.`; }