/*global env: true */
"use strict"
var doop = require("jsdoc/util/doop")
var fs = require("jsdoc/fs")
var helper = require("jsdoc/util/templateHelper")
var logger = require("jsdoc/util/logger")
var path = require("jsdoc/path")
var taffy = require("taffydb").taffy
var template = require("jsdoc/template")
var util = require("util")
var htmlsafe = helper.htmlsafe
var linkto = helper.linkto
var resolveAuthorLinks = helper.resolveAuthorLinks
var scopeToPunc = helper.scopeToPunc
var hasOwnProp = Object.prototype.hasOwnProperty
var data
var view
let NR_VERSION = process.env.NODE_RED_PACKAGE_VERSION || "X.x";
if (/^\d+/.test(NR_VERSION)) {
NR_VERSION = NR_VERSION.split(".")[0]+".x"
}
var 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) {
if (!/^(#.+)/.test(hash)) {
return hash
}
var url = helper.createLink(doclet)
url = url.replace(/(#.+|$)/, hash)
return '' + hash + ""
}
function needsSignature(doclet) {
var needsSig = false
// function and class definitions always get a signature
if (doclet.kind === "function" || doclet.kind === "class") {
needsSig = true
} else if (
doclet.kind === "typedef" &&
doclet.type &&
doclet.type.names &&
doclet.type.names.length
) {
// typedefs that contain functions get a signature, too
for (var i = 0, l = doclet.type.names.length; i < l; i++) {
if (doclet.type.names[i].toLowerCase() === "function") {
needsSig = true
break
}
}
}
return needsSig
}
function getSignatureAttributes(item) {
var attributes = []
if (item.optional) {
attributes.push("opt")
}
if (item.nullable === true) {
attributes.push("nullable")
} else if (item.nullable === false) {
attributes.push("non-null")
}
return attributes
}
function updateItemName(item) {
var attributes = getSignatureAttributes(item)
var itemName = item.name || ""
if (item.variable) {
itemName = "…" + itemName
}
if (attributes && attributes.length) {
itemName = util.format(
'%s%s',
itemName,
attributes.join(", ")
)
}
return itemName
}
function addParamAttributes(params) {
return params
.filter(function(param) {
return param.name && param.name.indexOf(".") === -1
})
.map(updateItemName)
}
function buildItemTypeStrings(item) {
var types = []
if (item && item.type && item.type.names) {
item.type.names.forEach(function(name) {
types.push(linkto(name, htmlsafe(name)))
})
}
return types
}
function buildAttribsString(attribs) {
var attribsString = ""
if (attribs && attribs.length) {
attribsString = htmlsafe(util.format("(%s) ", attribs.join(", ")))
}
return attribsString
}
function addNonParamAttributes(items) {
var types = []
items.forEach(function(item) {
types = types.concat(buildItemTypeStrings(item))
})
return types
}
function addSignatureParams(f) {
var params = f.params ? addParamAttributes(f.params) : []
f.signature = util.format("%s(%s)", f.signature || "", params.join(", "))
}
function addSignatureReturns(f) {
var attribs = []
var attribsString = ""
var returnTypes = []
var returnTypesString = ""
// 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 (f.returns) {
f.returns.forEach(function(item) {
helper.getAttribs(item).forEach(function(attrib) {
if (attribs.indexOf(attrib) === -1) {
attribs.push(attrib)
}
})
})
attribsString = buildAttribsString(attribs)
}
if (f.returns) {
returnTypes = addNonParamAttributes(f.returns)
}
if (returnTypes.length) {
returnTypesString = util.format(
" → %s{%s}",
attribsString,
returnTypes.join("|")
)
}
f.signature =
'' +
(f.signature || "") +
"" +
'' +
returnTypesString +
""
}
function addSignatureTypes(f) {
var types = f.type ? buildItemTypeStrings(f) : []
if (f.mixes) {
types.push("{ "+linkto(f.mixes[0],"...")+" }")
}
var content = (types.length ? " : " + types.join("|") : "");
f.signature = (f.signature || "") + (content?(''+content+""):"")
}
function addAttribs(f) {
var attribs = helper.getAttribs(f)
var attribsString = buildAttribsString(attribs)
f.attribs = util.format(
'%s',
attribsString
)
}
function shortenPaths(files, commonPrefix) {
Object.keys(files).forEach(function(file) {
files[file].shortened = files[file].resolved
.replace(commonPrefix, "")
// always use forward slashes
.replace(/\\/g, "/")
})
return files
}
function getPathFromDoclet(doclet) {
if (!doclet.meta) {
return null
}
return doclet.meta.path && doclet.meta.path !== "null"
? path.join(doclet.meta.path, doclet.meta.filename)
: doclet.meta.filename
}
function generate(type, title, docs, filename, resolveLinks) {
resolveLinks = resolveLinks === false ? false : true
var docData = {
type: type,
title: title,
docs: docs
}
var 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) {
encoding = encoding || "utf8"
Object.keys(sourceFiles).forEach(function(file) {
var source
// links are keyed to the shortened path in each doclet's `meta.shortpath` property
var 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.} doclets - The array of classes and functions to
* check.
* @param {Array.} modules - The array of module doclets to search.
*/
function attachModuleSymbols(doclets, modules) {
var symbols = {}
// build a lookup table
doclets.forEach(function(symbol) {
symbols[symbol.longname] = symbols[symbol.longname] || []
symbols[symbol.longname].push(symbol)
})
return modules.map(function(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(function(symbol) {
return symbol.description || symbol.kind === "class"
})
.map(function(symbol) {
symbol = doop(symbol)
if (symbol.kind === "class" || symbol.kind === "function") {
symbol.name = symbol.name.replace("module:", '(require("') + '"))'
}
return symbol
})
}
})
}
/**
* Create the navigation sidebar.
* @param {object} members The members that will be used to create the sidebar.
* @param {array