@compodoc/compodoc
Version:
The missing documentation tool for your Angular application
1,177 lines (1,054 loc) • 119 kB
text/typescript
import * as fs from 'fs-extra';
import * as _ from '../utils/collection.util';
import * as path from 'path';
import { SyntaxKind } from 'ts-morph';
const chokidar = require('chokidar');
const traverse = require('neotraverse/legacy');
const crypto = require('crypto');
import { logger } from '../utils/logger';
import Configuration from './configuration';
import DependenciesEngine from './engines/dependencies.engine';
import ExportEngine from './engines/export.engine';
import ExportJsonEngine from './engines/export-json.engine';
import FileEngine from './engines/file.engine';
import HtmlEngine from './engines/html.engine';
import I18nEngine from './engines/i18n.engine';
import MarkdownEngine, { markdownReadedDatas } from './engines/markdown.engine';
import NgdEngine from './engines/ngd.engine';
import SearchEngine from './engines/search.engine';
import { AngularDependencies } from './compiler/angular-dependencies';
import { AngularJSDependencies } from './compiler/angularjs-dependencies';
import { ProjectTypeDetector } from './application/project-type-detector';
import { DocumentationCoverageUseCase } from './application/documentation-coverage-use-case';
import {
applyPackageDocumentationMetadata,
copyDocumentedPackageProperties
} from './application/package-json-metadata';
import { RouteTreeMetrics } from './application/route-tree-metrics';
import { WatchFileClassifier } from './application/watch-file-classifier';
import { COMPODOC_CONSTANTS } from '../utils/constants';
import { COMPODOC_DEFAULTS } from '../utils/defaults';
import { promiseSequential } from '../utils/promise-sequential';
import RouterParserUtil from '../utils/router-parser.util';
import {
cleanNameWithoutSpaceAndToLowerCase,
cleanSourcesForWatch,
findMainSourceFolder,
normalizeWatchFilePath
} from '../utils/utils';
import { AdditionalNode } from './interfaces/additional-node.interface';
import { CoverageData } from './interfaces/coverageData.interface';
import { markedAcl } from '../utils/marked.acl';
import { ComponentHelper } from './compiler/angular/deps/helpers/component-helper';
import { renderLlmMd } from '../llm-md';
import {
DocumentationServerConfiguration,
startDocumentationServer
} from '../utils/documentation-server';
const cwd = process.cwd();
let startTime = new Date();
type AnyRecord = Record<string, any>;
type GenerationPromiseResolve = (value: boolean | PromiseLike<boolean>) => void;
type GenerationPromiseReject = (reason?: any) => void;
let generationPromiseResolve: GenerationPromiseResolve = () => {};
let generationPromiseReject: GenerationPromiseReject = () => {};
const generationPromise = new Promise<boolean>((resolve, reject) => {
generationPromiseResolve = resolve;
generationPromiseReject = reject;
});
type DependenciesCrawler = AngularDependencies | AngularJSDependencies;
type DependenciesCrawlerConstructor = new (
files: string[],
options: any,
...legacyDependencies: any[]
) => DependenciesCrawler;
export class Application {
private readonly projectTypeDetector = new ProjectTypeDetector();
private readonly routeTreeMetrics = new RouteTreeMetrics();
private readonly watchFileClassifier = new WatchFileClassifier();
/**
* Files processed during initial scanning
*/
public files: Array<string> = [];
/**
* Files processed during watch scanning
*/
public updatedFiles: Array<string> = [];
/**
* Files changed during watch scanning
*/
public watchChangedFiles: Array<string> = [];
/**
* Boolean for watching status
* @type {boolean}
*/
public isWatching: boolean = false;
/**
* Store package.json data
*/
private packageJsonData: AnyRecord = {};
/**
* Create a new compodoc application instance.
*
* @param options An object containing the options that should be used.
*/
constructor(options?: AnyRecord) {
const mainData = Configuration.mainData as AnyRecord;
for (let option in options) {
if (typeof mainData[option] !== 'undefined') {
mainData[option] = options[option];
}
// For documentationMainName, process it outside the loop, for handling conflict with pages name
if (option === 'name') {
Configuration.mainData.documentationMainName = options[option];
}
// For documentationMainName, process it outside the loop, for handling conflict with pages name
if (option === 'silent') {
logger.silent = false;
}
}
}
/**
* Start compodoc process
*/
protected generate(): Promise<boolean> {
process.on('unhandledRejection', this.unhandledRejectionListener);
process.on('uncaughtException', this.uncaughtExceptionListener);
I18nEngine.init(Configuration.mainData.language);
if (
Configuration.mainData.output.charAt(Configuration.mainData.output.length - 1) !== '/'
) {
Configuration.mainData.output += '/';
}
if (Configuration.mainData.exportFormat !== COMPODOC_DEFAULTS.exportFormat) {
this.processPackageJson();
} else {
HtmlEngine.init(Configuration.mainData.templates).then(() => this.processPackageJson());
}
return generationPromise;
}
private endCallback() {
process.removeListener('unhandledRejection', this.unhandledRejectionListener);
process.removeListener('uncaughtException', this.uncaughtExceptionListener);
}
private unhandledRejectionListener(err: any, p: any) {
console.log('Unhandled Rejection at:', p, 'reason:', err);
logger.error(
'Sorry, but there was a problem during parsing or generation of the documentation. Please fill an issue on github. (https://github.com/compodoc/compodoc/issues/new)'
); // tslint:disable-line
process.exit(1);
}
private uncaughtExceptionListener(err: any) {
logger.error(err);
logger.error(
'Sorry, but there was a problem during parsing or generation of the documentation. Please fill an issue on github. (https://github.com/compodoc/compodoc/issues/new)'
); // tslint:disable-line
process.exit(1);
}
/**
* Start compodoc documentation coverage
*/
protected testCoverage() {
this.getDependenciesData();
}
/**
* Store files for initial processing
* @param {Array<string>} files Files found during source folder and tsconfig scan
*/
public setFiles(files: Array<string>) {
this.files = files;
}
/**
* Store files for watch processing
* @param {Array<string>} files Files found during source folder and tsconfig scan
*/
public setUpdatedFiles(files: Array<string>) {
this.updatedFiles = files;
}
/**
* Return a boolean indicating presence of one TypeScript file in updatedFiles list
* @return {boolean} Result of scan
*/
public hasWatchedFilesTSFiles(): boolean {
return this.watchFileClassifier.hasTypeScriptFile(this.updatedFiles);
}
/**
* Return a boolean indicating presence of one root markdown files in updatedFiles list
* @return {boolean} Result of scan
*/
public hasWatchedFilesRootMarkdownFiles(): boolean {
return this.watchFileClassifier.hasRootMarkdownFile(this.updatedFiles, cwd);
}
/**
* Clear files for watch processing
*/
public clearUpdatedFiles(): void {
this.updatedFiles = [];
this.watchChangedFiles = [];
}
private processPackageJson(): void {
logger.info('Searching package.json file');
FileEngine.get(cwd + path.sep + 'package.json').then(
packageData => {
let parsedData = JSON.parse(packageData);
this.packageJsonData = parsedData;
applyPackageDocumentationMetadata(parsedData);
logger.info('package.json file found');
if (!Configuration.mainData.disableDependencies) {
if (typeof parsedData.dependencies !== 'undefined') {
this.processPackageDependencies(parsedData.dependencies);
}
if (typeof parsedData.peerDependencies !== 'undefined') {
this.processPackagePeerDependencies(parsedData.peerDependencies);
}
}
if (!Configuration.mainData.disableProperties) {
if (copyDocumentedPackageProperties(parsedData)) {
Configuration.addPage({
name: 'properties',
id: 'packageProperties',
context: 'package-properties',
depth: 0,
pageType: COMPODOC_DEFAULTS.PAGE_TYPES.ROOT
});
}
}
this.processMarkdowns().then(
() => {
this.getDependenciesData();
},
errorMessage => {
logger.error(errorMessage);
process.exit(1);
}
);
},
errorMessage => {
logger.error(errorMessage);
logger.error('Continuing without package.json file');
this.processMarkdowns().then(
() => {
this.getDependenciesData();
},
errorMessage1 => {
logger.error(errorMessage1);
process.exit(1);
}
);
}
);
}
private processPackagePeerDependencies(dependencies: any): void {
logger.info('Processing package.json peerDependencies');
Configuration.mainData.packagePeerDependencies = dependencies;
if (!Configuration.hasPage('dependencies')) {
Configuration.addPage({
name: 'dependencies',
id: 'packageDependencies',
context: 'package-dependencies',
depth: 0,
pageType: COMPODOC_DEFAULTS.PAGE_TYPES.ROOT
});
}
}
private processPackageDependencies(dependencies: any): void {
logger.info('Processing package.json dependencies');
Configuration.mainData.packageDependencies = dependencies;
Configuration.addPage({
name: 'dependencies',
id: 'packageDependencies',
context: 'package-dependencies',
depth: 0,
pageType: COMPODOC_DEFAULTS.PAGE_TYPES.ROOT
});
}
private processMarkdowns(): Promise<any> {
logger.info(
'Searching README.md, CHANGELOG.md, CONTRIBUTING.md, LICENSE.md, TODO.md files'
);
return new Promise((resolve, reject) => {
let i = 0;
const markdowns = ['readme', 'changelog', 'contributing', 'license', 'todo'];
const numberOfMarkdowns = 5;
const loop = () => {
if (i < numberOfMarkdowns) {
MarkdownEngine.getTraditionalMarkdown(markdowns[i].toUpperCase())
.then((readmeData: markdownReadedDatas) => {
logger.info(`${markdowns[i].toUpperCase()}.md file found`);
if (markdowns[i] === 'readme') {
Configuration.mainData.readme = true;
// Always create index.html as main page with README content
Configuration.addPage({
name: 'index',
context: 'readme',
id: 'index',
markdown: readmeData.markdown,
data: readmeData.rawData,
depth: 0,
pageType: COMPODOC_DEFAULTS.PAGE_TYPES.ROOT
});
// If overview is not disabled, also create separate overview page
if (!Configuration.mainData.disableOverview) {
Configuration.addPage({
name: 'overview',
context: 'overview',
id: 'overview',
depth: 0,
pageType: COMPODOC_DEFAULTS.PAGE_TYPES.ROOT
});
}
} else {
// For other markdown files (changelog, contributing, etc.)
Configuration.addPage({
name: markdowns[i],
context: markdowns[i],
id: markdowns[i],
markdown: readmeData.markdown,
data: readmeData.rawData,
depth: 0,
pageType: COMPODOC_DEFAULTS.PAGE_TYPES.ROOT
});
Configuration.mainData.markdowns.push({
name: markdowns[i],
uppername: markdowns[i].toUpperCase(),
depth: 0,
pageType: COMPODOC_DEFAULTS.PAGE_TYPES.ROOT
});
}
i++;
loop();
})
.catch(errorMessage => {
logger.warn(errorMessage);
logger.warn(`Continuing without ${markdowns[i].toUpperCase()}.md file`);
if (markdowns[i] === 'readme') {
if (!Configuration.mainData.disableOverview) {
Configuration.addPage({
name: 'index',
id: 'index',
context: 'overview',
depth: 0,
pageType: COMPODOC_DEFAULTS.PAGE_TYPES.ROOT
});
} else {
// When README doesn't exist and overview is disabled,
// generate overview page anyway but show warning
logger.warn(
'No README.md found and --disableOverview is enabled.'
);
logger.warn(
'Generating overview page as landing page. Consider adding a README.md file.'
);
Configuration.addPage({
name: 'index',
id: 'index',
context: 'overview',
depth: 0,
pageType: COMPODOC_DEFAULTS.PAGE_TYPES.ROOT
});
}
}
i++;
loop();
});
} else {
if (!Configuration.hasPage('architecture')) {
Configuration.addPage({
name: 'architecture',
id: 'architecture',
context: 'architecture',
depth: 0,
pageType: COMPODOC_DEFAULTS.PAGE_TYPES.ROOT
});
}
resolve(true);
}
};
loop();
});
}
private rebuildRootMarkdowns(): void {
logger.info(
'Regenerating README.md, CHANGELOG.md, CONTRIBUTING.md, LICENSE.md, TODO.md pages'
);
let actions = [];
Configuration.resetRootMarkdownPages();
actions.push(() => {
return this.processMarkdowns();
});
promiseSequential(actions)
.then(res => {
this.processPages();
this.clearUpdatedFiles();
})
.catch(errorMessage => {
logger.error(errorMessage);
});
}
/**
* Get dependency data for small group of updated files during watch process
*/
private getMicroDependenciesData(): void {
logger.info('Get diff dependencies data');
let dependenciesClass: DependenciesCrawlerConstructor = AngularDependencies;
Configuration.mainData.angularProject = true;
if (this.detectAngularJSProjects()) {
logger.info('AngularJS project detected');
Configuration.mainData.angularProject = false;
Configuration.mainData.angularJSProject = true;
dependenciesClass = AngularJSDependencies;
}
let crawler = new dependenciesClass(
this.updatedFiles,
{
tsconfigDirectory: path.dirname(Configuration.mainData.tsconfig)
},
Configuration,
RouterParserUtil
);
let dependenciesData = crawler.getDependencies();
DependenciesEngine.update(dependenciesData);
this.prepareJustAFewThings(dependenciesData);
}
/**
* Rebuild external documentation during watch process
*/
private rebuildExternalDocumentation(): void {
logger.info('Rebuild external documentation');
let actions = [];
Configuration.resetAdditionalPages();
if (Configuration.mainData.includes !== '') {
actions.push(() => {
return this.prepareExternalIncludes();
});
}
promiseSequential(actions)
.then(res => {
this.processPages();
this.clearUpdatedFiles();
})
.catch(errorMessage => {
logger.error(errorMessage);
});
}
private detectAngularJSProjects() {
return this.projectTypeDetector.isAngularJSProject(this.packageJsonData, this.files);
}
private getDependenciesData(): void {
logger.info('Get dependencies data');
/**
* AngularJS detection strategy :
* - if in package.json
* - if 75% of scanned files are *.js files
*/
let dependenciesClass: DependenciesCrawlerConstructor = AngularDependencies;
Configuration.mainData.angularProject = true;
if (this.detectAngularJSProjects()) {
logger.info('AngularJS project detected');
Configuration.mainData.angularProject = false;
Configuration.mainData.angularJSProject = true;
dependenciesClass = AngularJSDependencies;
}
let crawler = new dependenciesClass(
this.files,
{
tsconfigDirectory: path.dirname(Configuration.mainData.tsconfig)
},
Configuration,
RouterParserUtil
);
let dependenciesData = crawler.getDependencies();
DependenciesEngine.init(dependenciesData as any);
if (dependenciesData.routesTree) {
Configuration.mainData.routesLength = this.routesLengthFromTree(
dependenciesData.routesTree
);
} else {
Configuration.mainData.routesLength = RouterParserUtil.routesLength();
}
this.printStatistics();
this.prepareEverything();
}
private routesLengthFromTree(routesTree: any): number {
return this.routeTreeMetrics.countRoutes(routesTree);
}
private prepareJustAFewThings(diffCrawledData: any): void {
let actions: Array<() => any> = [];
Configuration.resetPages();
if (!Configuration.mainData.disableRoutesGraph) {
actions.push(() => this.prepareRoutes());
}
if (diffCrawledData.components.length > 0) {
actions.push(() => this.prepareComponents());
}
if (diffCrawledData.controllers.length > 0) {
actions.push(() => this.prepareControllers());
}
if (diffCrawledData.entities.length > 0) {
actions.push(() => this.prepareEntities());
}
if (diffCrawledData.modules.length > 0) {
actions.push(() => this.prepareModules());
}
if (diffCrawledData.directives.length > 0) {
actions.push(() => this.prepareDirectives());
}
if (diffCrawledData.injectables.length > 0) {
actions.push(() => this.prepareInjectables());
}
if (diffCrawledData.interceptors.length > 0) {
actions.push(() => this.prepareInterceptors());
}
if (diffCrawledData.guards.length > 0) {
actions.push(() => this.prepareGuards());
}
if (diffCrawledData.pipes.length > 0) {
actions.push(() => this.preparePipes());
}
if (diffCrawledData.classes.length > 0) {
actions.push(() => this.prepareClasses());
}
if (diffCrawledData.interfaces.length > 0) {
actions.push(() => this.prepareInterfaces());
}
if (
diffCrawledData.miscellaneous.variables.length > 0 ||
diffCrawledData.miscellaneous.functions.length > 0 ||
diffCrawledData.miscellaneous.typealiases.length > 0 ||
diffCrawledData.miscellaneous.enumerations.length > 0
) {
actions.push(() => this.prepareMiscellaneous());
}
if (!Configuration.mainData.disableCoverage) {
actions.push(() => this.prepareCoverage());
}
promiseSequential(actions)
.then(res => {
if (Configuration.mainData.exportFormat !== COMPODOC_DEFAULTS.exportFormat) {
if (
COMPODOC_DEFAULTS.exportFormatsSupported.indexOf(
Configuration.mainData.exportFormat
) > -1
) {
logger.info(
`Generating documentation in export format ${Configuration.mainData.exportFormat}`
);
Promise.resolve(
ExportEngine.export(
Configuration.mainData.output,
Configuration.mainData
)
).then(() => {
generationPromiseResolve(true);
this.endCallback();
logger.info(
'Documentation generated in ' +
Configuration.mainData.output +
' in ' +
this.getElapsedTime() +
' seconds'
);
if (Configuration.mainData.serve) {
logger.info(
`Serving documentation from ${Configuration.mainData.output} at http://${Configuration.mainData.hostname}:${Configuration.mainData.port}`
);
this.runWebServer(Configuration.mainData.output);
}
});
} else {
logger.warn(`Exported format not supported`);
}
} else {
this.processGraphs();
this.clearUpdatedFiles();
}
})
.catch(errorMessage => {
logger.error(errorMessage);
});
}
private printStatistics() {
logger.info('-------------------');
logger.info('Project statistics ');
if (DependenciesEngine.modules.length > 0) {
logger.info(`- files : ${this.files.length}`);
}
if (DependenciesEngine.modules.length > 0) {
logger.info(`- module : ${DependenciesEngine.modules.length}`);
}
if (DependenciesEngine.components.length > 0) {
logger.info(`- component : ${DependenciesEngine.components.length}`);
}
if (DependenciesEngine.controllers.length > 0) {
logger.info(`- controller : ${DependenciesEngine.controllers.length}`);
}
if (DependenciesEngine.entities.length > 0) {
logger.info(`- entity : ${DependenciesEngine.entities.length}`);
}
if (DependenciesEngine.directives.length > 0) {
logger.info(`- directive : ${DependenciesEngine.directives.length}`);
}
if (DependenciesEngine.injectables.length > 0) {
logger.info(`- injectable : ${DependenciesEngine.injectables.length}`);
}
if (DependenciesEngine.interceptors.length > 0) {
logger.info(`- injector : ${DependenciesEngine.interceptors.length}`);
}
if (DependenciesEngine.guards.length > 0) {
logger.info(`- guard : ${DependenciesEngine.guards.length}`);
}
if (DependenciesEngine.pipes.length > 0) {
logger.info(`- pipe : ${DependenciesEngine.pipes.length}`);
}
if (DependenciesEngine.classes.length > 0) {
logger.info(`- class : ${DependenciesEngine.classes.length}`);
}
if (DependenciesEngine.interfaces.length > 0) {
logger.info(`- interface : ${DependenciesEngine.interfaces.length}`);
}
if (Configuration.mainData.routesLength > 0) {
logger.info(`- route : ${Configuration.mainData.routesLength}`);
}
if (DependenciesEngine.miscellaneous.typealiases.length > 0) {
logger.info(`- type aliases : ${DependenciesEngine.miscellaneous.typealiases.length}`);
}
logger.info('-------------------');
}
private prepareEverything() {
let actions = [];
actions.push(() => {
return this.prepareComponents();
});
actions.push(() => {
return this.prepareModules();
});
if (DependenciesEngine.directives.length > 0) {
actions.push(() => {
return this.prepareDirectives();
});
}
if (DependenciesEngine.controllers.length > 0) {
actions.push(() => {
return this.prepareControllers();
});
}
if (DependenciesEngine.entities.length > 0) {
actions.push(() => {
return this.prepareEntities();
});
}
if (DependenciesEngine.injectables.length > 0) {
actions.push(() => {
return this.prepareInjectables();
});
}
if (DependenciesEngine.interceptors.length > 0) {
actions.push(() => {
return this.prepareInterceptors();
});
}
if (DependenciesEngine.guards.length > 0) {
actions.push(() => {
return this.prepareGuards();
});
}
if (DependenciesEngine.routes && !Configuration.mainData.disableRoutesGraph) {
actions.push(() => {
return this.prepareRoutes();
});
}
if (DependenciesEngine.pipes.length > 0) {
actions.push(() => {
return this.preparePipes();
});
}
if (DependenciesEngine.classes.length > 0) {
actions.push(() => {
return this.prepareClasses();
});
}
if (DependenciesEngine.interfaces.length > 0) {
actions.push(() => {
return this.prepareInterfaces();
});
}
if (
DependenciesEngine.miscellaneous.variables.length > 0 ||
DependenciesEngine.miscellaneous.functions.length > 0 ||
DependenciesEngine.miscellaneous.typealiases.length > 0 ||
DependenciesEngine.miscellaneous.enumerations.length > 0
) {
actions.push(() => {
return this.prepareMiscellaneous();
});
}
if (!Configuration.mainData.disableCoverage) {
actions.push(() => {
return this.prepareCoverage();
});
}
if (Configuration.mainData.unitTestCoverage !== '') {
actions.push(() => {
return this.prepareUnitTestCoverage();
});
}
if (Configuration.mainData.includes !== '') {
actions.push(() => {
return this.prepareExternalIncludes();
});
}
promiseSequential(actions)
.then(res => {
if (Configuration.mainData.exportFormat !== COMPODOC_DEFAULTS.exportFormat) {
if (
COMPODOC_DEFAULTS.exportFormatsSupported.indexOf(
Configuration.mainData.exportFormat
) > -1
) {
logger.info(
`Generating documentation in export format ${Configuration.mainData.exportFormat}`
);
Promise.resolve(
ExportEngine.export(
Configuration.mainData.output,
Configuration.mainData
)
).then(() => {
generationPromiseResolve(true);
this.endCallback();
logger.info(
'Documentation generated in ' +
Configuration.mainData.output +
' in ' +
this.getElapsedTime() +
' seconds'
);
if (Configuration.mainData.serve) {
logger.info(
`Serving documentation from ${Configuration.mainData.output} at http://${Configuration.mainData.hostname}:${Configuration.mainData.port}`
);
this.runWebServer(Configuration.mainData.output);
}
});
} else {
logger.warn(`Exported format not supported`);
}
} else {
this.processGraphs();
}
})
.catch(errorMessage => {
logger.error(errorMessage);
process.exit(1);
});
}
private getIncludedPathForFile(file: string) {
return path.join(Configuration.mainData.includes, file);
}
private prepareExternalIncludes() {
logger.info('Adding external markdown files');
// Scan include folder for files detailed in summary.json
// For each file, add to Configuration.mainData.additionalPages
// Each file will be converted to html page, inside COMPODOC_DEFAULTS.additionalEntryPath
return new Promise((resolve, reject) => {
FileEngine.get(this.getIncludedPathForFile('summary.json')).then(
summaryData => {
logger.info('Additional documentation: summary.json file found');
const parsedSummaryData = JSON.parse(summaryData);
let that = this;
let lastLevelOnePage: any = undefined;
traverse(parsedSummaryData).forEach(function (this: any) {
// tslint:disable-next-line:no-invalid-this
if (this.notRoot && typeof this.node === 'object') {
// tslint:disable-next-line:no-invalid-this
let rawPath = this.path;
// tslint:disable-next-line:no-invalid-this
let additionalNode: AdditionalNode = this.node;
let file = additionalNode.file;
let title = additionalNode.title;
let finalPath = Configuration.mainData.includesFolder;
let finalDepth = rawPath.filter((el: any) => {
return !isNaN(parseInt(el, 10));
});
if (typeof file !== 'undefined' && typeof title !== 'undefined') {
const url = cleanNameWithoutSpaceAndToLowerCase(title);
/**
* Id created with title + file path hash, seems to be hypothetically unique here
*/
const id = crypto
.createHash('sha512')
.update(title + file)
.digest('hex');
// tslint:disable-next-line:no-invalid-this
this.node.id = id;
let lastElementRootTree: any = undefined;
finalDepth.forEach((el: any) => {
let elementTree =
typeof lastElementRootTree === 'undefined'
? parsedSummaryData
: lastElementRootTree;
if (typeof elementTree.children !== 'undefined') {
elementTree = elementTree.children[el];
} else {
elementTree = elementTree[el];
}
finalPath +=
'/' +
cleanNameWithoutSpaceAndToLowerCase(elementTree.title);
lastElementRootTree = elementTree;
});
finalPath = finalPath.replace('/' + url, '');
let markdownFile = MarkdownEngine.getTraditionalMarkdownSync(
that.getIncludedPathForFile(file)
);
if (finalDepth.length > 5) {
logger.error('Only 5 levels of depth are supported');
} else {
let _page: any = {
name: title,
id: id,
filename: url,
context: 'additional-page',
path: finalPath,
additionalPage: markdownFile,
depth: finalDepth.length,
childrenLength: additionalNode.children
? additionalNode.children.length
: 0,
children: [],
lastChild: false,
pageType: COMPODOC_DEFAULTS.PAGE_TYPES.INTERNAL
};
if (finalDepth.length === 1) {
lastLevelOnePage = _page;
}
if (finalDepth.length > 1) {
// store all child pages of the last root level 1 page inside it
lastLevelOnePage.children.push(_page);
} else {
Configuration.addAdditionalPage(_page);
}
}
}
}
});
resolve(true);
},
errorMessage => {
logger.error(errorMessage);
reject('Error during Additional documentation generation');
}
);
});
}
public prepareModules(someModules?: any): Promise<any> {
logger.info('Prepare modules');
let i = 0;
let _modules = someModules ? someModules : DependenciesEngine.getModules();
return new Promise((resolve, reject) => {
Configuration.mainData.modules = _modules.map((ngModule: any) => {
ngModule.compodocLinks = {
components: [],
controllers: [],
directives: [],
injectables: [],
pipes: []
};
['declarations', 'bootstrap', 'imports', 'exports', 'controllers'].forEach(
metadataType => {
ngModule[metadataType] = ngModule[metadataType].filter(
(metaDataItem: any) => {
switch (metaDataItem.type) {
case 'directive':
return DependenciesEngine.getDirectives().some(
directive => {
let selectedDirective;
if (typeof metaDataItem.id !== 'undefined') {
selectedDirective =
(directive as any).id === metaDataItem.id;
} else {
selectedDirective =
(directive as any).name ===
metaDataItem.name;
}
if (
selectedDirective &&
!ngModule.compodocLinks.directives.includes(
directive
)
) {
ngModule.compodocLinks.directives.push(
directive
);
}
return selectedDirective;
}
);
case 'component':
return DependenciesEngine.getComponents().some(
(component: any) => {
let selectedComponent;
if (typeof metaDataItem.id !== 'undefined') {
selectedComponent =
(component as any).id === metaDataItem.id;
} else {
selectedComponent =
(component as any).name ===
metaDataItem.name;
}
if (
selectedComponent &&
!ngModule.compodocLinks.components.includes(
component
)
) {
if (!component.standalone) {
ngModule.compodocLinks.components.push(
component
);
}
}
return selectedComponent;
}
);
case 'controller':
return DependenciesEngine.getControllers().some(
controller => {
let selectedController;
if (typeof metaDataItem.id !== 'undefined') {
selectedController =
(controller as any).id === metaDataItem.id;
} else {
selectedController =
(controller as any).name ===
metaDataItem.name;
}
if (
selectedController &&
!ngModule.compodocLinks.controllers.includes(
controller
)
) {
ngModule.compodocLinks.controllers.push(
controller
);
}
return selectedController;
}
);
case 'module':
return DependenciesEngine.getModules().some(
module => (module as any).name === metaDataItem.name
);
case 'pipe':
return DependenciesEngine.getPipes().some(pipe => {
let selectedPipe;
if (typeof metaDataItem.id !== 'undefined') {
selectedPipe = (pipe as any).id === metaDataItem.id;
} else {
selectedPipe =
(pipe as any).name === metaDataItem.name;
}
if (
selectedPipe &&
!ngModule.compodocLinks.pipes.includes(pipe)
) {
ngModule.compodocLinks.pipes.push(pipe);
}
return selectedPipe;
});
default:
return true;
}
}
);
}
);
ngModule.providers = ngModule.providers.filter((provider: any) => {
return (
DependenciesEngine.getInjectables().some(injectable => {
let selectedInjectable = (injectable as any).name === provider.name;
if (
selectedInjectable &&
!ngModule.compodocLinks.injectables.includes(injectable)
) {
ngModule.compodocLinks.injectables.push(injectable);
}
return selectedInjectable;
}) ||
DependenciesEngine.getInterceptors().some(
interceptor => (interceptor as any).name === provider.name
)
);
});
// Try fixing type undefined for each providers
_.forEach(ngModule.providers, provider => {
if (
DependenciesEngine.getInjectables().find(
injectable => (injectable as any).name === provider.name
)
) {
provider.type = 'injectable';
}
if (
DependenciesEngine.getInterceptors().find(
interceptor => (interceptor as any).name === provider.name
)
) {
provider.type = 'interceptor';
}
});
// Order things
ngModule.compodocLinks.components = _.sortBy(ngModule.compodocLinks.components, [
'name'
]);
ngModule.compodocLinks.controllers = _.sortBy(ngModule.compodocLinks.controllers, [
'name'
]);
ngModule.compodocLinks.directives = _.sortBy(ngModule.compodocLinks.directives, [
'name'
]);
ngModule.compodocLinks.injectables = _.sortBy(ngModule.compodocLinks.injectables, [
'name'
]);
ngModule.compodocLinks.pipes = _.sortBy(ngModule.compodocLinks.pipes, ['name']);
ngModule.declarations = _.sortBy(ngModule.declarations, ['name']);
ngModule.entryComponents = _.sortBy(ngModule.entryComponents, ['name']);
ngModule.providers = _.sortBy(ngModule.providers, ['name']);
ngModule.imports = _.sortBy(ngModule.imports, ['name']);
ngModule.exports = _.sortBy(ngModule.exports, ['name']);
return ngModule;
});
Configuration.addPage({
name: 'modules',
id: 'modules',
context: 'modules',
depth: 0,
pageType: COMPODOC_DEFAULTS.PAGE_TYPES.ROOT
});
const len = Configuration.mainData.modules.length;
const loop = () => {
if (i < len) {
if (
MarkdownEngine.hasNeighbourReadmeFile(
Configuration.mainData.modules[i].file
)
) {
logger.info(
` ${Configuration.mainData.modules[i].name} has a README file, include it`
);
const readme = MarkdownEngine.readNeighbourReadmeFile(
Configuration.mainData.modules[i].file
);
Configuration.mainData.modules[i].readme = markedAcl(readme);
}
Configuration.mainData.modules[i].relationships = this.getRelationships(
Configuration.mainData.modules[i]
);
Configuration.addPage({
path: 'modules',
name: Configuration.mainData.modules[i].name,
id: Configuration.mainData.modules[i].id,
navTabs: this.getNavTabs(Configuration.mainData.modules[i]),
context: 'module',
module: Configuratio