| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692 | const doop = require('jsdoc/util/doop');const env = require('jsdoc/env');const fs = require('jsdoc/fs');const helper = require('jsdoc/util/templateHelper');const logger = require('jsdoc/util/logger');const path = require('jsdoc/path');const { taffy } = require('@jsdoc/salty');const template = require('jsdoc/template');const util = require('util');const htmlsafe = helper.htmlsafe;const linkto = helper.linkto;const resolveAuthorLinks = helper.resolveAuthorLinks;const hasOwnProp = Object.prototype.hasOwnProperty;let data;let view;let outdir = path.normalize(env.opts.destination);function find(spec) {    return helper.find(data, spec);}function tutoriallink(tutorial) {    return helper.toTutorial(tutorial, null, {        tag: 'em',        classname: 'disabled',        prefix: 'Tutorial: '    });}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 `<a href="${url}">${hash}</a>`;}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 = util.format( '%s<span class="signature-attributes">%s</span>', 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) {        attribsString = htmlsafe( util.format('(%s) ', 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 = util.format( '%s(%s)', (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 = util.format( ' → %s{%s}', attribsString, returnTypes.join('|') );    }    f.signature = `<span class="signature">${f.signature || ''}</span><span class="type-signature">${returnTypesString}</span>`;}function addSignatureTypes(f) {    const types = f.type ? buildItemTypeStrings(f) : [];    f.signature = `${f.signature || ''}<span class="type-signature">${types.length ? ` :${types.join('|')}` : ''}</span>`;}function addAttribs(f) {    const attribs = helper.getAttribs(f);    const attribsString = buildAttribsString(attribs);    f.attribs = util.format('<span class="type-signature">%s</span>', 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 <a href="foodoc.html">foo</a>    }    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) {            logger.error('Error while generating source file %s: %s', 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.<module:jsdoc/doclet.Doclet>} doclets - The array of classes and functions to * check. * @param {Array.<module:jsdoc/doclet.Doclet>} 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 = doop(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 = '';        items.forEach(item => {            let displayName;            if ( !hasOwnProp.call(item, 'longname') ) {                itemsNav += `<li>${linktoFn('', item.name)}</li>`;            }            else if ( !hasOwnProp.call(itemsSeen, item.longname) ) {                if (env.conf.templates.default.useLongnameInNav) {                    displayName = item.longname;                } else {                    displayName = item.name;                }                itemsNav += `<li>${linktoFn(item.longname, displayName.replace(/\b(module|event):/g, ''))}</li>`;                itemsSeen[item.longname] = true;            }        });        if (itemsNav !== '') {            nav += `<h3>${itemHeading}</h3><ul>${itemsNav}</ul>`;        }    }    return nav;}function linktoTutorial(longName, name) {    return tutoriallink(name);}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<object>} members.classes * @param {array<object>} members.externals * @param {array<object>} members.globals * @param {array<object>} members.mixins * @param {array<object>} members.modules * @param {array<object>} members.namespaces * @param {array<object>} members.tutorials * @param {array<object>} members.events * @param {array<object>} members.interfaces * @return {string} The HTML for the navigation sidebar. */function buildNav(members) {    let globalNav;    let nav = '<h2><a href="index.html">Home</a></h2>';    const seen = {};    const seenTutorials = {};    nav += buildMemberNav(members.modules, 'Modules', {}, linkto);    nav += buildMemberNav(members.externals, 'Externals', seen, linktoExternal);    nav += buildMemberNav(members.namespaces, 'Namespaces', seen, linkto);    nav += buildMemberNav(members.classes, 'Classes', seen, linkto);    nav += buildMemberNav(members.interfaces, 'Interfaces', seen, linkto);    nav += buildMemberNav(members.events, 'Events', seen, linkto);    nav += buildMemberNav(members.mixins, 'Mixins', seen, linkto);    nav += buildMemberNav(members.tutorials, 'Tutorials', seenTutorials, linktoTutorial);    if (members.globals.length) {        globalNav = '';        members.globals.forEach(({kind, longname, name}) => {            if ( kind !== 'typedef' && !hasOwnProp.call(seen, longname) ) {                globalNav += `<li>${linkto(longname, name)}</li>`;            }            seen[longname] = true;        });        if (!globalNav) {            // turn the heading into a link so you can actually get to the global page            nav += `<h3>${linkto('global', 'Global')}</h3>`;        }        else {            nav += `<h3>Global</h3><ul>${globalNav}</ul>`;        }    }    return nav;}/**    @param {TAFFY} taffyData See <http://taffydb.com/>.    @param {object} opts    @param {Tutorial} tutorials */exports.publish = (taffyData, opts, tutorials) => {    let classes;    let conf;    let externals;    let files;    let fromDir;    let globalUrl;    let indexUrl;    let interfaces;    let members;    let mixins;    let modules;    let namespaces;    let outputSourceFiles;    let packageInfo;    let packages;    const sourceFilePaths = [];    let sourceFiles = {};    let staticFileFilter;    let staticFilePaths;    let staticFiles;    let staticFileScanner;    let templatePath;    data = taffyData;    conf = env.conf.templates || {};    conf.default = conf.default || {};    templatePath = path.normalize(opts.template);    view = new template.Template( path.join(templatePath, 'tmpl') );    // claim some special filenames in advance, so the All-Powerful Overseer of Filename Uniqueness    // doesn't try to hand them out later    indexUrl = helper.getUniqueFilename('index');    // don't call registerLink() on this one! 'index' is also a valid longname    globalUrl = helper.getUniqueFilename('global');    helper.registerLink('global', globalUrl);    // set up templating    view.layout = conf.default.layoutFile ?        path.getResourcePath(path.dirname(conf.default.layoutFile),            path.basename(conf.default.layoutFile) ) :        'layout.tmpl';    // set up tutorials for helper    helper.setTutorials(tutorials);    data = helper.prune(data);    data.sort('longname, version, since');    helper.addEventListeners(data);    data().each(doclet => {        let sourcePath;        doclet.attribs = '';        if (doclet.examples) {            doclet.examples = doclet.examples.map(example => {                let caption;                let code;                if (example.match(/^\s*<caption>([\s\S]+?)<\/caption>(\s*[\n\r])([\s\S]+)$/i)) {                    caption = RegExp.$1;                    code = RegExp.$3;                }                return {                    caption: caption || '',                    code: code || example                };            });        }        if (doclet.see) {            doclet.see.forEach((seeItem, i) => {                doclet.see[i] = hashToLink(doclet, seeItem);            });        }        // build a list of source files        if (doclet.meta) {            sourcePath = getPathFromDoclet(doclet);            sourceFiles[sourcePath] = {                resolved: sourcePath,                shortened: null            };            if (!sourceFilePaths.includes(sourcePath)) {                sourceFilePaths.push(sourcePath);            }        }    });    // update outdir if necessary, then create outdir    packageInfo = ( find({kind: 'package'}) || [] )[0];    if (packageInfo && packageInfo.name) {        outdir = path.join( outdir, packageInfo.name, (packageInfo.version || '') );    }    fs.mkPath(outdir);    // copy the template's static files to outdir    fromDir = path.join(templatePath, 'static');    staticFiles = fs.ls(fromDir, 3);    staticFiles.forEach(fileName => {        const toDir = fs.toDir( fileName.replace(fromDir, outdir) );        fs.mkPath(toDir);        fs.copyFileSync(fileName, toDir);    });    // copy user-specified static files to outdir    if (conf.default.staticFiles) {        // The canonical property name is `include`. We accept `paths` for backwards compatibility        // with a bug in JSDoc 3.2.x.        staticFilePaths = conf.default.staticFiles.include ||            conf.default.staticFiles.paths ||            [];        staticFileFilter = new (require('jsdoc/src/filter').Filter)(conf.default.staticFiles);        staticFileScanner = new (require('jsdoc/src/scanner').Scanner)();        staticFilePaths.forEach(filePath => {            let extraStaticFiles;            filePath = path.resolve(env.pwd, filePath);            extraStaticFiles = staticFileScanner.scan([filePath], 10, staticFileFilter);            extraStaticFiles.forEach(fileName => {                const sourcePath = fs.toDir(filePath);                const toDir = fs.toDir( fileName.replace(sourcePath, outdir) );                fs.mkPath(toDir);                fs.copyFileSync(fileName, toDir);            });        });    }    if (sourceFilePaths.length) {        sourceFiles = shortenPaths( sourceFiles, path.commonPrefix(sourceFilePaths) );    }    data().each(doclet => {        let docletPath;        const url = helper.createLink(doclet);        helper.registerLink(doclet.longname, url);        // add a shortened version of the full path        if (doclet.meta) {            docletPath = getPathFromDoclet(doclet);            docletPath = sourceFiles[docletPath].shortened;            if (docletPath) {                doclet.meta.shortpath = docletPath;            }        }    });    data().each(doclet => {        const url = helper.longnameToUrl[doclet.longname];        if (url.includes('#')) {            doclet.id = helper.longnameToUrl[doclet.longname].split(/#/).pop();        }        else {            doclet.id = doclet.name;        }        if ( needsSignature(doclet) ) {            addSignatureParams(doclet);            addSignatureReturns(doclet);            addAttribs(doclet);        }    });    // do this after the urls have all been generated    data().each(doclet => {        doclet.ancestors = getAncestorLinks(doclet);        if (doclet.kind === 'member') {            addSignatureTypes(doclet);            addAttribs(doclet);        }        if (doclet.kind === 'constant') {            addSignatureTypes(doclet);            addAttribs(doclet);            doclet.kind = 'member';        }    });    members = helper.getMembers(data);    members.tutorials = tutorials.children;    // output pretty-printed source files by default    outputSourceFiles = conf.default && conf.default.outputSourceFiles !== false;    // add template helpers    view.find = find;    view.linkto = linkto;    view.resolveAuthorLinks = resolveAuthorLinks;    view.tutoriallink = tutoriallink;    view.htmlsafe = htmlsafe;    view.outputSourceFiles = outputSourceFiles;    // once for all    view.nav = buildNav(members);    attachModuleSymbols( find({ longname: {left: 'module:'} }), members.modules );    // generate the pretty-printed source files first so other pages can link to them    if (outputSourceFiles) {        generateSourceFiles(sourceFiles, opts.encoding);    }    if (members.globals.length) { generate('Global', [{kind: 'globalobj'}], globalUrl); }    // index page displays information from package.json and lists files    files = find({kind: 'file'});    packages = find({kind: 'package'});    generate('Home',        packages.concat(            [{                kind: 'mainpage',                readme: opts.readme,                longname: (opts.mainpagetitle) ? opts.mainpagetitle : 'Main Page'            }]        ).concat(files), indexUrl);    // set up the lists that we'll use to generate pages    classes = taffy(members.classes);    modules = taffy(members.modules);    namespaces = taffy(members.namespaces);    mixins = taffy(members.mixins);    externals = taffy(members.externals);    interfaces = taffy(members.interfaces);    Object.keys(helper.longnameToUrl).forEach(longname => {        const myClasses = helper.find(classes, {longname: longname});        const myExternals = helper.find(externals, {longname: longname});        const myInterfaces = helper.find(interfaces, {longname: longname});        const myMixins = helper.find(mixins, {longname: longname});        const myModules = helper.find(modules, {longname: longname});        const myNamespaces = helper.find(namespaces, {longname: longname});        if (myModules.length) {            generate(`Module: ${myModules[0].name}`, myModules, helper.longnameToUrl[longname]);        }        if (myClasses.length) {            generate(`Class: ${myClasses[0].name}`, myClasses, helper.longnameToUrl[longname]);        }        if (myNamespaces.length) {            generate(`Namespace: ${myNamespaces[0].name}`, myNamespaces, helper.longnameToUrl[longname]);        }        if (myMixins.length) {            generate(`Mixin: ${myMixins[0].name}`, myMixins, helper.longnameToUrl[longname]);        }        if (myExternals.length) {            generate(`External: ${myExternals[0].name}`, myExternals, helper.longnameToUrl[longname]);        }        if (myInterfaces.length) {            generate(`Interface: ${myInterfaces[0].name}`, myInterfaces, helper.longnameToUrl[longname]);        }    });    // TODO: move the tutorial functions to templateHelper.js    function generateTutorial(title, tutorial, filename) {        const tutorialData = {            title: title,            header: tutorial.title,            content: tutorial.parse(),            children: tutorial.children        };        const tutorialPath = path.join(outdir, filename);        let html = view.render('tutorial.tmpl', tutorialData);        // yes, you can use {@link} in tutorials too!        html = helper.resolveLinks(html); // turn {@link foo} into <a href="foodoc.html">foo</a>        fs.writeFileSync(tutorialPath, html, 'utf8');    }    // tutorials can have only one parent so there is no risk for loops    function saveChildren({children}) {        children.forEach(child => {            generateTutorial(`Tutorial: ${child.title}`, child, helper.tutorialToUrl(child.name));            saveChildren(child);        });    }    saveChildren(tutorials);};
 |