import {DocCollection, Document, Processor} from 'dgeni'; import {ConstExportDoc} from 'dgeni-packages/typescript/api-doc-types/ConstExportDoc'; import {FunctionExportDoc} from 'dgeni-packages/typescript/api-doc-types/FunctionExportDoc'; import {InterfaceExportDoc} from 'dgeni-packages/typescript/api-doc-types/InterfaceExportDoc'; import {TypeAliasExportDoc} from 'dgeni-packages/typescript/api-doc-types/TypeAliasExportDoc'; import * as path from 'path'; import {computeApiDocumentUrl} from '../common/compute-api-url'; import {isDeprecatedDoc, isPrimaryModuleDoc} from '../common/decorators'; import {CategorizedClassDoc} from '../common/dgeni-definitions'; export interface ModuleInfo { /** Name of the module (e.g. toolbar, drag-drop, ripple) */ name: string; /** Name of the package that contains this entry point. */ packageName: string; /** Name of the entry-point that contains this module. */ entryPointName: string; } /** Document type for an entry-point. */ export class EntryPointDoc { /** Unique document type for Dgeni. */ docType = 'entry-point'; /** Name of the component group. */ name: string; /** Display name of the entry-point. */ displayName: string; /** Module import path for the entry-point. */ moduleImportPath: string; /** Name of the package, either material or cdk */ packageName: string; /** Display name of the package. */ packageDisplayName: string; /** Unique id for the entry-point. */ id: string; /** Known aliases for the entry-point. This is only needed for the `computeIdsProcessor`. */ aliases: string[] = []; /** List of categorized class docs that are defining a directive. */ directives: CategorizedClassDoc[] = []; /** List of categorized class docs that are defining a service. */ services: CategorizedClassDoc[] = []; /** Classes that belong to the entry-point. */ classes: CategorizedClassDoc[] = []; /** Interfaces that belong to the entry-point. */ interfaces: InterfaceExportDoc[] = []; /** Type aliases that belong to the entry-point. */ typeAliases: TypeAliasExportDoc[] = []; /** Functions that belong to the entry-point. */ functions: FunctionExportDoc[] = []; /** Constants that belong to the entry-point. */ constants: ConstExportDoc[] = []; /** List of NgModules which are exported in the current entry-point. */ exportedNgModules: CategorizedClassDoc[] = []; /** NgModule that defines the current entry-point. Null if no module could be found. */ ngModule: CategorizedClassDoc|null = null; constructor(name: string) { this.name = name; this.id = `entry-point-${name}`; } } /** * Processor to group docs into entry-points that consist of directives, component, classes, * interfaces, functions or type aliases. */ export class EntryPointGrouper implements Processor { name = 'entry-point-grouper'; $runBefore = ['docs-processed']; $process(docs: DocCollection) { const entryPoints = new Map(); docs.forEach(doc => { const moduleInfo = getModulePackageInfo(doc); const packageName = moduleInfo.packageName; const packageDisplayName = packageName === 'cdk' ? 'CDK' : 'Material'; const moduleImportPath = `@angular/${packageName}/${moduleInfo.entryPointName}`; const entryPointName = packageName + '-' + moduleInfo.name; // Compute a public URL that refers to the document. This is helpful if we want to // make references to other API documents. e.g. showing the extended class. doc.publicUrl = computeApiDocumentUrl(doc, moduleInfo); // Get the entry-point for this doc, or, if one does not exist, create it. let entryPoint; if (entryPoints.has(entryPointName)) { entryPoint = entryPoints.get(entryPointName)!; } else { entryPoint = new EntryPointDoc(entryPointName); entryPoints.set(entryPointName, entryPoint); } entryPoint.displayName = moduleInfo.name; entryPoint.moduleImportPath = moduleImportPath; entryPoint.packageName = packageName; entryPoint.packageDisplayName = packageDisplayName; // Put this doc into the appropriate list in the entry-point doc. if (doc.isDirective) { entryPoint.directives.push(doc); } else if (doc.isService) { entryPoint.services.push(doc); } else if (doc.isNgModule) { entryPoint.exportedNgModules.push(doc); // If the module is explicitly marked as primary module using the "@docs-primary-module" // annotation, we set is as primary entry-point module. if (isPrimaryModuleDoc(doc)) { entryPoint.ngModule = doc; } } else if (doc.docType === 'class') { entryPoint.classes.push(doc); } else if (doc.docType === 'interface') { entryPoint.interfaces.push(doc); } else if (doc.docType === 'type-alias') { entryPoint.typeAliases.push(doc); } else if (doc.docType === 'function') { entryPoint.functions.push(doc); } else if (doc.docType === 'const') { entryPoint.constants.push(doc); } }); // For each entry-point we determine a primary NgModule that defines the entry-point // if no primary module has been explicitly declared (using "@docs-primary-module"). entryPoints.forEach(entryPoint => { if (entryPoint.ngModule !== null) { return; } // Usually the first module that is not deprecated is used, but in case there are // only deprecated modules, the last deprecated module is used. We don't want to // always skip deprecated modules as they could be still needed for documentation // of a deprecated entry-point. for (let ngModule of entryPoint.exportedNgModules) { entryPoint.ngModule = ngModule; if (!isDeprecatedDoc(ngModule)) { break; } } }); return Array.from(entryPoints.values()); } } /** Resolves module package information of the given Dgeni document. */ function getModulePackageInfo(doc: Document): ModuleInfo { // Full path to the file for this doc. const basePath = doc.fileInfo.basePath; const filePath = doc.fileInfo.filePath; // All of the component documentation is under either `src/material` or `src/cdk`. // We group the docs up by the directory immediately under that root. const pathSegments = path.relative(basePath, filePath).split(path.sep); // The module name is usually the entry-point (e.g. slide-toggle, toolbar), but this is not // guaranteed because we can also export a module from material/core. e.g. the ripple module. let moduleName = pathSegments[1]; // The ripples are technically part of the `@angular/material/core` entry-point, but we // want to show the ripple API separately in the docs. In order to archive this, we treat // the ripple folder as its own module. if (pathSegments[1] === 'core' && pathSegments[2] === 'ripple') { moduleName = 'ripple'; } return { name: moduleName, packageName: pathSegments[0], entryPointName: pathSegments[1], }; }