import * as _ from 'lodash'; import { DocsInfo } from '../docs_info'; import { CustomType, CustomTypeChild, DocAgnosticFormat, DocSection, IndexSignature, KindString, Parameter, Property, SectionsMap, Type, TypeDocNode, TypeDocType, TypeDocTypes, TypeParameter, TypescriptFunction, TypescriptMethod, } from '../types'; import { utils } from '../utils/utils'; export const typeDocUtils = { isType(entity: TypeDocNode): boolean { return ( entity.kindString === KindString.Interface || entity.kindString === KindString.Function || entity.kindString === KindString.TypeAlias || entity.kindString === KindString.Variable || entity.kindString === KindString.Enumeration ); }, isMethod(entity: TypeDocNode): boolean { return entity.kindString === KindString.Method; }, isConstructor(entity: TypeDocNode): boolean { return entity.kindString === KindString.Constructor; }, isProperty(entity: TypeDocNode): boolean { return entity.kindString === KindString.Property; }, isPrivateOrProtectedProperty(propertyName: string): boolean { return _.startsWith(propertyName, '_'); }, getModuleDefinitionsBySectionName(versionDocObj: TypeDocNode, configModulePaths: string[]): TypeDocNode[] { const moduleDefinitions: TypeDocNode[] = []; const jsonModules = versionDocObj.children; _.each(jsonModules, jsonMod => { _.each(configModulePaths, configModulePath => { if (_.includes(configModulePath, jsonMod.name)) { moduleDefinitions.push(jsonMod); } }); }); return moduleDefinitions; }, convertToDocAgnosticFormat(typeDocJson: TypeDocNode, docsInfo: DocsInfo): DocAgnosticFormat { const subMenus = _.values(docsInfo.getMenu()); const orderedSectionNames = _.flatten(subMenus); const docAgnosticFormat: DocAgnosticFormat = {}; _.each(orderedSectionNames, sectionName => { const modulePathsIfExists = docsInfo.getModulePathsIfExists(sectionName); if (_.isUndefined(modulePathsIfExists)) { return; // no-op } const packageDefinitions = typeDocUtils.getModuleDefinitionsBySectionName(typeDocJson, modulePathsIfExists); let packageDefinitionWithMergedChildren; if (_.isEmpty(packageDefinitions)) { return; // no-op } else if (packageDefinitions.length === 1) { packageDefinitionWithMergedChildren = packageDefinitions[0]; } else { // HACK: For now, if there are two modules to display in a single section, // we simply concat the children. This works for our limited use-case where // we want to display types stored in two files under a single section packageDefinitionWithMergedChildren = packageDefinitions[0]; for (let i = 1; i < packageDefinitions.length; i++) { packageDefinitionWithMergedChildren.children = [ ...packageDefinitionWithMergedChildren.children, ...packageDefinitions[i].children, ]; } } let entities; let packageComment = ''; // HACK: We assume 1 exported class per file const classChildren = _.filter(packageDefinitionWithMergedChildren.children, (child: TypeDocNode) => { return child.kindString === KindString.Class; }); if (classChildren.length > 1 && sectionName !== 'types') { throw new Error('`react-docs` only supports projects with 1 exported class per file'); } const isClassExport = packageDefinitionWithMergedChildren.children[0].kindString === KindString.Class; if (isClassExport) { entities = packageDefinitionWithMergedChildren.children[0].children; const commentObj = packageDefinitionWithMergedChildren.children[0].comment; packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment; } else { entities = packageDefinitionWithMergedChildren.children; } const docSection = typeDocUtils._convertEntitiesToDocSection(entities, docsInfo, sectionName); docSection.comment = packageComment; docAgnosticFormat[sectionName] = docSection; }); return docAgnosticFormat; }, _convertEntitiesToDocSection(entities: TypeDocNode[], docsInfo: DocsInfo, sectionName: string) { const docSection: DocSection = { comment: '', constructors: [], methods: [], functions: [], properties: [], types: [], }; let isConstructor; _.each(entities, entity => { switch (entity.kindString) { case KindString.Constructor: isConstructor = true; const constructor = typeDocUtils._convertMethod( entity, isConstructor, docsInfo.sections, sectionName, docsInfo.id, ); docSection.constructors.push(constructor); break; case KindString.Function: if (entity.flags.isExported) { const func = typeDocUtils._convertFunction(entity, docsInfo.sections, sectionName, docsInfo.id); docSection.functions.push(func); } break; case KindString.Method: if (entity.flags.isPublic) { isConstructor = false; const method = typeDocUtils._convertMethod( entity, isConstructor, docsInfo.sections, sectionName, docsInfo.id, ); docSection.methods.push(method); } break; case KindString.Property: if (!typeDocUtils.isPrivateOrProtectedProperty(entity.name)) { const property = typeDocUtils._convertProperty( entity, docsInfo.sections, sectionName, docsInfo.id, ); docSection.properties.push(property); } break; case KindString.Interface: case KindString.Variable: case KindString.Enumeration: case KindString.TypeAlias: if (docsInfo.isPublicType(entity.name)) { const customType = typeDocUtils._convertCustomType( entity, docsInfo.sections, sectionName, docsInfo.id, ); const seenTypeNames = _.map(docSection.types, t => t.name); const isUnseen = !_.includes(seenTypeNames, customType.name); if (isUnseen) { docSection.types.push(customType); } } break; case KindString.Class: // We currently do not support more then a single class per file // except for the types section, where we ignore classes since we // only want to render type definitions. break; default: throw utils.spawnSwitchErr('kindString', entity.kindString); } }); return docSection; }, _convertCustomType(entity: TypeDocNode, sections: SectionsMap, sectionName: string, docId: string): CustomType { const typeIfExists = !_.isUndefined(entity.type) ? typeDocUtils._convertType(entity.type, sections, sectionName, docId) : undefined; const isConstructor = false; const methodIfExists = !_.isUndefined(entity.declaration) ? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, docId) : undefined; const doesIndexSignatureExist = !_.isUndefined(entity.indexSignature); const isIndexSignatureArray = _.isArray(entity.indexSignature); // HACK: TypeDoc Versions <0.9.0 indexSignature is of type TypeDocNode[] // Versions >0.9.0 have it as type TypeDocNode const indexSignature = doesIndexSignatureExist && isIndexSignatureArray ? (entity.indexSignature as TypeDocNode[])[0] : (entity.indexSignature as TypeDocNode); const indexSignatureIfExists = doesIndexSignatureExist ? typeDocUtils._convertIndexSignature(indexSignature, sections, sectionName, docId) : undefined; const commentIfExists = !_.isUndefined(entity.comment) && !_.isUndefined(entity.comment.shortText) ? entity.comment.shortText : undefined; const childrenIfExist = !_.isUndefined(entity.children) ? _.map(entity.children, (child: TypeDocNode) => { let childTypeIfExists = !_.isUndefined(child.type) ? typeDocUtils._convertType(child.type, sections, sectionName, docId) : undefined; if (child.kindString === KindString.Method) { childTypeIfExists = { name: child.name, typeDocType: TypeDocTypes.Reflection, method: this._convertMethod(child, isConstructor, sections, sectionName, docId), }; } const c: CustomTypeChild = { name: child.name, type: childTypeIfExists, defaultValue: child.defaultValue, }; return c; }) : undefined; const customType = { name: entity.name, kindString: entity.kindString, type: typeIfExists, method: methodIfExists, indexSignature: indexSignatureIfExists, defaultValue: entity.defaultValue, comment: commentIfExists, children: childrenIfExist, }; return customType; }, _convertIndexSignature( entity: TypeDocNode, sections: SectionsMap, sectionName: string, docId: string, ): IndexSignature { const key = entity.parameters[0]; const indexSignature = { keyName: key.name, keyType: typeDocUtils._convertType(key.type, sections, sectionName, docId), valueName: entity.type.name, }; return indexSignature; }, _convertProperty(entity: TypeDocNode, sections: SectionsMap, sectionName: string, docId: string): Property { const source = entity.sources[0]; const commentIfExists = !_.isUndefined(entity.comment) ? entity.comment.shortText : undefined; const property = { name: entity.name, type: typeDocUtils._convertType(entity.type, sections, sectionName, docId), source: { fileName: source.fileName, line: source.line, }, comment: commentIfExists, }; return property; }, _convertMethod( entity: TypeDocNode, isConstructor: boolean, sections: SectionsMap, sectionName: string, docId: string, ): TypescriptMethod { const signature = entity.signatures[0]; const source = entity.sources[0]; const hasComment = !_.isUndefined(signature.comment); const isStatic = _.isUndefined(entity.flags.isStatic) ? false : entity.flags.isStatic; // HACK: we use the fact that the sectionName is the same as the property name at the top-level // of the public interface. In the future, we shouldn't use this hack but rather get it from the JSON. let callPath; if (isConstructor || entity.name === '__type') { callPath = ''; // TODO: Get rid of this 0x-specific logic } else if (docId === 'ZERO_EX_JS') { const topLevelInterface = isStatic ? 'ZeroEx.' : 'zeroEx.'; callPath = !_.isUndefined(sections.zeroEx) && sectionName !== sections.zeroEx ? `${topLevelInterface}${sectionName}.` : topLevelInterface; } else { callPath = `${sectionName}.`; } const parameters = _.map(signature.parameters, param => { return typeDocUtils._convertParameter(param, sections, sectionName, docId); }); const returnType = typeDocUtils._convertType(signature.type, sections, sectionName, docId); const typeParameter = _.isUndefined(signature.typeParameter) ? undefined : typeDocUtils._convertTypeParameter(signature.typeParameter[0], sections, sectionName, docId); const method = { isConstructor, isStatic, name: signature.name, comment: hasComment ? signature.comment.shortText : undefined, returnComment: hasComment && signature.comment.returns ? signature.comment.returns : undefined, source: { fileName: source.fileName, line: source.line, }, callPath, parameters, returnType, typeParameter, }; return method; }, _convertFunction( entity: TypeDocNode, sections: SectionsMap, sectionName: string, docId: string, ): TypescriptFunction { const signature = entity.signatures[0]; const source = entity.sources[0]; const hasComment = !_.isUndefined(signature.comment); const parameters = _.map(signature.parameters, param => { return typeDocUtils._convertParameter(param, sections, sectionName, docId); }); const returnType = typeDocUtils._convertType(signature.type, sections, sectionName, docId); const typeParameter = _.isUndefined(signature.typeParameter) ? undefined : typeDocUtils._convertTypeParameter(signature.typeParameter[0], sections, sectionName, docId); const func = { name: signature.name, comment: hasComment ? signature.comment.shortText : undefined, returnComment: hasComment && signature.comment.returns ? signature.comment.returns : undefined, source: { fileName: source.fileName, line: source.line, }, parameters, returnType, typeParameter, }; return func; }, _convertTypeParameter( entity: TypeDocNode, sections: SectionsMap, sectionName: string, docId: string, ): TypeParameter { const type = typeDocUtils._convertType(entity.type, sections, sectionName, docId); const parameter = { name: entity.name, type, }; return parameter; }, _convertParameter(entity: TypeDocNode, sections: SectionsMap, sectionName: string, docId: string): Parameter { let comment = ''; if (entity.comment && entity.comment.shortText) { comment = entity.comment.shortText; } else if (entity.comment && entity.comment.text) { comment = entity.comment.text; } const isOptional = !_.isUndefined(entity.flags.isOptional) ? entity.flags.isOptional : false; const type = typeDocUtils._convertType(entity.type, sections, sectionName, docId); const parameter = { name: entity.name, comment, isOptional, defaultValue: entity.defaultValue, type, }; return parameter; }, _convertType(entity: TypeDocType, sections: SectionsMap, sectionName: string, docId: string): Type { const typeArguments = _.map(entity.typeArguments, typeArgument => { return typeDocUtils._convertType(typeArgument, sections, sectionName, docId); }); const types = _.map(entity.types, t => { return typeDocUtils._convertType(t, sections, sectionName, docId); }); const isConstructor = false; const methodIfExists = !_.isUndefined(entity.declaration) ? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, docId) : undefined; const elementTypeIfExists = !_.isUndefined(entity.elementType) ? { name: entity.elementType.name, typeDocType: entity.elementType.type, } : undefined; const type = { name: entity.name, value: entity.value, typeDocType: entity.type, typeArguments, elementType: elementTypeIfExists, types, method: methodIfExists, }; return type; }, };