const _ = require('lodash');
const commonPathPrefix = require('common-path-prefix');
const env = require('jsdoc/env');
const fs = require('fs');
const helper = require('jsdoc/util/templateHelper');
const { log } = require('@jsdoc/util');
const { lsSync } = require('@jsdoc/util').fs;
const path = require('path');
const { taffy } = require('taffydb');
const template = require('jsdoc/template');
const htmlsafe = helper.htmlsafe;
const linkto = helper.linkto;
const resolveAuthorLinks = helper.resolveAuthorLinks;
const hasOwnProp = Object.prototype.hasOwnProperty;
const FONT_NAMES = [
'OpenSans-Bold',
'OpenSans-BoldItalic',
'OpenSans-Italic',
'OpenSans-Light',
'OpenSans-LightItalic',
'OpenSans-Regular'
];
const PRETTIFIER_CSS_FILES = [
'tomorrow.min.css'
];
const PRETTIFIER_SCRIPT_FILES = [
'lang-css.js',
'prettify.js'
];
let data;
let view;
let outdir = path.normalize(env.opts.destination);
function mkdirpSync(filepath) {
return fs.mkdirSync(filepath, { recursive: true });
}
function find(spec) {
return helper.find(data, spec);
}
function getAncestorLinks(doclet) {
return helper.getAncestorLinks(data, doclet);
}
function hashToLink(doclet, hash) {
let url;
if ( !/^(#.+)/.test(hash) ) {
return hash;
}
url = helper.createLink(doclet);
url = url.replace(/(#.+|$)/, hash);
return `${hash}`;
}
function needsSignature({kind, type, meta}) {
let needsSig = false;
// function and class definitions always get a signature
if (kind === 'function' || kind === 'class') {
needsSig = true;
}
// typedefs that contain functions get a signature, too
else if (kind === 'typedef' && type && type.names &&
type.names.length) {
for (let i = 0, l = type.names.length; i < l; i++) {
if (type.names[i].toLowerCase() === 'function') {
needsSig = true;
break;
}
}
}
// and namespaces that are functions get a signature (but finding them is a
// bit messy)
else if (kind === 'namespace' && meta && meta.code &&
meta.code.type && meta.code.type.match(/[Ff]unction/)) {
needsSig = true;
}
return needsSig;
}
function getSignatureAttributes({optional, nullable}) {
const attributes = [];
if (optional) {
attributes.push('opt');
}
if (nullable === true) {
attributes.push('nullable');
}
else if (nullable === false) {
attributes.push('non-null');
}
return attributes;
}
function updateItemName(item) {
const attributes = getSignatureAttributes(item);
let itemName = item.name || '';
if (item.variable) {
itemName = `…${itemName}`;
}
if (attributes && attributes.length) {
itemName = `${itemName}${attributes.join(', ')}`;
}
return itemName;
}
function addParamAttributes(params) {
return params.filter(({name}) => name && !name.includes('.')).map(updateItemName);
}
function buildItemTypeStrings(item) {
const types = [];
if (item && item.type && item.type.names) {
item.type.names.forEach(name => {
types.push( linkto(name, htmlsafe(name)) );
});
}
return types;
}
function buildAttribsString(attribs) {
let attribsString = '';
if (attribs && attribs.length) {
htmlsafe(`(${attribs.join(', ')}) `);
}
return attribsString;
}
function addNonParamAttributes(items) {
let types = [];
items.forEach(item => {
types = types.concat( buildItemTypeStrings(item) );
});
return types;
}
function addSignatureParams(f) {
const params = f.params ? addParamAttributes(f.params) : [];
f.signature = `${f.signature || ''}(${params.join(', ')})`;
}
function addSignatureReturns(f) {
const attribs = [];
let attribsString = '';
let returnTypes = [];
let returnTypesString = '';
const source = f.yields || f.returns;
// jam all the return-type attributes into an array. this could create odd results (for example,
// if there are both nullable and non-nullable return types), but let's assume that most people
// who use multiple @return tags aren't using Closure Compiler type annotations, and vice-versa.
if (source) {
source.forEach(item => {
helper.getAttribs(item).forEach(attrib => {
if (!attribs.includes(attrib)) {
attribs.push(attrib);
}
});
});
attribsString = buildAttribsString(attribs);
}
if (source) {
returnTypes = addNonParamAttributes(source);
}
if (returnTypes.length) {
returnTypesString = ` → ${attribsString}{${returnTypes.join('|')}}`;
}
f.signature = `${f.signature || ''}` +
`${returnTypesString}`;
}
function addSignatureTypes(f) {
const types = f.type ? buildItemTypeStrings(f) : [];
f.signature = `${f.signature || ''}` +
`${types.length ? ` :${types.join('|')}` : ''}`;
}
function addAttribs(f) {
const attribs = helper.getAttribs(f);
const attribsString = buildAttribsString(attribs);
f.attribs = `${attribsString}`;
}
function shortenPaths(files, commonPrefix) {
Object.keys(files).forEach(file => {
files[file].shortened = files[file].resolved.replace(commonPrefix, '')
// always use forward slashes
.replace(/\\/g, '/');
});
return files;
}
function getPathFromDoclet({meta}) {
if (!meta) {
return null;
}
return meta.path && meta.path !== 'null' ?
path.join(meta.path, meta.filename) :
meta.filename;
}
function generate(title, docs, filename, resolveLinks) {
let docData;
let html;
let outpath;
resolveLinks = resolveLinks !== false;
docData = {
env: env,
title: title,
docs: docs
};
outpath = path.join(outdir, filename);
html = view.render('container.tmpl', docData);
if (resolveLinks) {
html = helper.resolveLinks(html); // turn {@link foo} into foo
}
fs.writeFileSync(outpath, html, 'utf8');
}
function generateSourceFiles(sourceFiles, encoding = 'utf8') {
Object.keys(sourceFiles).forEach(file => {
let source;
// links are keyed to the shortened path in each doclet's `meta.shortpath` property
const sourceOutfile = helper.getUniqueFilename(sourceFiles[file].shortened);
helper.registerLink(sourceFiles[file].shortened, sourceOutfile);
try {
source = {
kind: 'source',
code: helper.htmlsafe( fs.readFileSync(sourceFiles[file].resolved, encoding) )
};
}
catch (e) {
log.error(`Error while generating source file ${file}: ${e.message}`);
}
generate(`Source: ${sourceFiles[file].shortened}`, [source], sourceOutfile,
false);
});
}
/**
* Look for classes or functions with the same name as modules (which indicates that the module
* exports only that class or function), then attach the classes or functions to the `module`
* property of the appropriate module doclets. The name of each class or function is also updated
* for display purposes. This function mutates the original arrays.
*
* @private
* @param {Array.} doclets - The array of classes and functions to
* check.
* @param {Array.} modules - The array of module doclets to search.
*/
function attachModuleSymbols(doclets, modules) {
const symbols = {};
// build a lookup table
doclets.forEach(symbol => {
symbols[symbol.longname] = symbols[symbol.longname] || [];
symbols[symbol.longname].push(symbol);
});
modules.forEach(module => {
if (symbols[module.longname]) {
module.modules = symbols[module.longname]
// Only show symbols that have a description. Make an exception for classes, because
// we want to show the constructor-signature heading no matter what.
.filter(({description, kind}) => description || kind === 'class')
.map(symbol => {
symbol = _.cloneDeep(symbol);
if (symbol.kind === 'class' || symbol.kind === 'function') {
symbol.name = `${symbol.name.replace('module:', '(require("')}"))`;
}
return symbol;
});
}
});
}
function buildMemberNav(items, itemHeading, itemsSeen, linktoFn) {
let nav = '';
if (items.length) {
let itemsNav = '';
//Organize the items into categories based on the 'classcategory' tag.
var organizedItems = {},
categoryNames = [];
for( var i=0; i -1) {
categoryNames.splice(index, 1);
categoryNames.push("Deprecated")
}
categoryNames.forEach(category => {
//Add a heading for the category, only if there is more than one category
// and there is at least one item in the category
if( categoryNames.length > 1 && category != "Other" ){
itemsNav += "
" + category + "
";
}
organizedItems[category].forEach(item => {
let displayName;
if ( !hasOwnProp.call(item, 'longname') ) {
itemsNav += `
`;
itemsSeen[item.longname] = true;
}
});
});
if (itemsNav !== '') {
nav += `
${itemHeading}
${itemsNav}
`;
}
}
return nav;
}
function linktoExternal(longName, name) {
return linkto(longName, name.replace(/(^"|"$)/g, ''));
}
/**
* Create the navigation sidebar.
* @param {object} members The members that will be used to create the sidebar.
* @param {array