| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811 |
- 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;
- const outdir = path.normalize( env.opts.destination );
- const themeOpts = ( env.opts.themeOpts ) || {};
- function mkdirSync( 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 `<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;
- } else if ( kind === 'typedef' && type && type.names && type.names.length ) {
- // typedefs that contain functions get a signature, too
- for ( let i = 0, l = type.names.length; i < l; i ++ ) {
- if ( type.names[ i ].toLowerCase() === 'function' ) {
- needsSig = true;
- break;
- }
- }
- } else if ( kind === 'namespace' && meta && meta.code && meta.code.type && meta.code.type.match( /[Ff]unction/ ) ) {
- // and namespaces that are functions get a signature (but finding them is a
- // bit messy)
- needsSig = true;
- }
- return needsSig;
- }
- function updateItemName( item ) {
- let itemName = item.name || '';
- if ( item.variable ) {
- itemName = `…${itemName}`;
- }
- 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 buildSearchListForData() {
- const searchList = [];
- data().each( ( item ) => {
- if ( item.kind !== 'package' && item.kind !== 'typedef' && ! item.inherited ) {
- searchList.push( {
- title: item.longname,
- link: linkto( item.longname, item.name ),
- description: item.description,
- } );
- }
- } );
- return searchList;
- }
- 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 html;
- resolveLinks = resolveLinks !== false;
- const docData = {
- env: env,
- title: title,
- docs: docs
- };
- const 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 );
- } );
- }
- function buildMainNav( items, itemsSeen, linktoFn ) {
- const coreDirectory = 'src';
- const addonsDirectory = 'examples/jsm';
- const hierarchy = new Map();
- hierarchy.set( 'Core', new Map() );
- hierarchy.set( 'Addons', new Map() );
- let nav = '';
- if ( items.length ) {
- items.forEach( item => {
- let displayName;
- let itemNav = '';
- if ( ! hasOwnProp.call( itemsSeen, item.longname ) ) {
- if ( env.conf.templates.default.useLongnameInNav ) {
- displayName = item.longname;
- } else {
- displayName = item.name;
- }
- itemNav += `<li data-name="${item.name}">${linktoFn( item.longname, displayName.replace( /\b(module|event):/g, '' ) )}</li>`;
- itemsSeen[ item.longname ] = true;
- const path = item.meta.shortpath;
- if ( path.startsWith( coreDirectory ) ) {
- const subCategory = path.split( '/' )[ 1 ];
- pushNavItem( hierarchy, 'Core', subCategory, itemNav );
- } else if ( path.startsWith( addonsDirectory ) ) {
- const subCategory = path.split( '/' )[ 2 ];
- pushNavItem( hierarchy, 'Addons', subCategory, itemNav );
- }
- }
- } );
- for ( const [ mainCategory, map ] of hierarchy ) {
- nav += `<h2>${mainCategory}</h2>`;
- const sortedMap = new Map( [ ...map.entries() ].sort() ); // sort sub categories
- for ( const [ subCategory, links ] of sortedMap ) {
- nav += `<h3>${subCategory}</h3>`;
- let navItems = '';
- links.sort();
- for ( const link of links ) {
- navItems += link;
- }
- nav += `<ul>${navItems}</ul>`;
- }
- }
- }
- return nav;
- }
- function buildGlobalsNav( globals, seen ) {
- let globalNav;
- let nav = '';
- if ( globals.length ) {
- // TSL
- let tslNav = '';
- globals.forEach( ( { kind, longname, name, tags } ) => {
- if ( kind !== 'typedef' && ! hasOwnProp.call( seen, longname ) && Array.isArray( tags ) ) {
- const tslTag = tags.find( tag => tag.title === 'tsl' );
- if ( tslTag !== undefined ) {
- tslNav += `<li data-name="${longname}">${linkto( longname, name )}</li>`;
- seen[ longname ] = true;
- }
- }
- } );
- nav += `<h2>TSL</h2><ul>${tslNav}</ul>`;
- // Globals
- globalNav = '';
- globals.forEach( ( { kind, longname, name } ) => {
- if ( kind !== 'typedef' && ! hasOwnProp.call( seen, longname ) ) {
- globalNav += `<li data-name="${longname}">${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 += `<h2>Global</h2><ul>${globalNav}</ul>`;
- }
- }
- return nav;
- }
- function pushNavItem( hierarchy, mainCategory, subCategory, itemNav ) {
- subCategory = subCategory[ 0 ].toUpperCase() + subCategory.slice( 1 ); // capitalize
- if ( hierarchy.get( mainCategory ).get( subCategory ) === undefined ) {
- hierarchy.get( mainCategory ).set( subCategory, [] );
- }
- const categoryList = hierarchy.get( mainCategory ).get( subCategory );
- categoryList.push( itemNav );
- }
- /**
- * Create the navigation sidebar.
- * @param {Object} members The members that will be used to create the sidebar.
- * @return {string} The HTML for the navigation sidebar.
- */
- function buildNav( members ) {
- let nav = '';
- const seen = {};
- nav += buildMainNav( [ ...members.classes, ...members.modules ], seen, linkto );
- nav += buildGlobalsNav( members.globals, seen );
- return nav;
- }
- /**
- @param {TAFFY} taffyData See <http://taffydb.com/>.
- @param {Object} opts
- @param {Tutorial} tutorials
- */
- exports.publish = ( taffyData, opts, tutorials ) => {
- const sourceFilePaths = [];
- let sourceFiles = {};
- let staticFileFilter;
- let staticFilePaths;
- let staticFileScanner;
- data = taffyData;
- const conf = env.conf.templates || {};
- conf.default = conf.default || {};
- const 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
- const indexUrl = helper.getUniqueFilename( 'index' );
- // don't call registerLink() on this one! 'index' is also a valid longname
- const 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.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 );
- }
- }
- } );
- fs.mkPath( outdir );
- // copy the template's static files to outdir
- const fromDir = path.join( templatePath, 'static' );
- const 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 => {
- filePath = path.resolve( env.pwd, filePath );
- const 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';
- }
- } );
- // prepare import statements
- data().each( doclet => {
- if ( doclet.kind === 'class' || doclet.kind === 'module' ) {
- const tags = doclet.tags;
- if ( Array.isArray( tags ) ) {
- const importTag = tags.find( tag => tag.title === 'three_import' );
- doclet.import = ( importTag !== undefined ) ? importTag.text : null;
- }
- }
- } );
- const members = helper.getMembers( data );
- members.tutorials = tutorials.children;
- // output pretty-printed source files by default
- const outputSourceFiles = conf.default && conf.default.outputSourceFiles !== false;
- // add template helpers
- view.find = find;
- view.linkto = linkto;
- view.resolveAuthorLinks = resolveAuthorLinks;
- view.htmlsafe = htmlsafe;
- view.outputSourceFiles = outputSourceFiles;
- view.ignoreInheritedSymbols = themeOpts.ignoreInheritedSymbols;
- // once for all
- view.nav = buildNav( members );
- // 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
- const files = find( { kind: 'file' } );
- const packages = find( { kind: 'package' } );
- generate( '', // MODIFIED (Remove Home title)
- 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
- const classes = taffy( members.classes );
- const modules = taffy( members.modules );
- Object.keys( helper.longnameToUrl ).forEach( longname => {
- const myClasses = helper.find( classes, { longname: longname } );
- const myModules = helper.find( modules, { longname: longname } );
- if ( myClasses.length ) {
- generate( `${myClasses[ 0 ].name}`, myClasses, helper.longnameToUrl[ longname ] );
- }
- if ( myModules.length ) {
- generate( `${myModules[ 0 ].name}`, myModules, helper.longnameToUrl[ longname ] );
- }
- } );
- // search
- const searchList = buildSearchListForData();
- mkdirSync( path.join( outdir, 'data' ) );
- fs.writeFileSync(
- path.join( outdir, 'data', 'search.json' ),
- JSON.stringify( {
- list: searchList,
- } )
- );
- };
|