UNPKG

builddocs

Version:

Build documentation files from commented source code

192 lines (170 loc) 6.78 kB
let fs = require("fs") let Mold = require("mold-template") let builtins = require("./builtins") exports.browserImports = require("./browser") exports.build = function(config, items) { if (!items) items = require("getdocs-ts").gather(config) let format = config.format || "html" let renderItem = format == "html" ? name => '<div data-item="' + name + '"></div>' : format == "markdown" ? (() => { let mold = loadMarkdownTemplates(config, items) return name => mold.defs.item({item: items[name], name}).replace(/[\n␤]{2}$/g, "\n") })() : null let instantiateTemplate = template => { if (format == "html") template = template.replace(/(^|\n)(@[\w\$]+\n+)*@[\w\$]+(?=$|\n)/g, "<dl>\n$&\n</dl>") let placed = Object.create(null), problems = [] let result = template.replace(/(^|\n)@([\w\$]+)(?=$|\n)/g, function(_, before, name) { if (placed[name]) problems.push("Item " + name + " is included in doc template twice") if (!items[name]) problems.push("Unknown item " + name + " included in doc template") placed[name] = true return before + renderItem(name) }) for (let name in items) if (!placed[name]) problems.push("Item " + name + " is missing from the doc template") return {result, problems} } let main if (config.main || config.mainText) { let {problems, result} = instantiateTemplate(config.mainText || fs.readFileSync(config.main, "utf8")) if (problems.length) for (let prob of problems) console.log(prob) else main = result } if (!main) main = instantiateTemplate(Object.keys(items).map(name => "@" + name).join("\n\n")).result if (format == "markdown") { return main.replace(/␤/g, "\n") } else if (format == "html") { let mdOptions = {html: true} if (config.markdownOptions) for (let prop in config.markdownOptions) mdOptions[prop] = config.markdownOptions[prop] let markdown = require("markdown-it")(mdOptions).use(require("markdown-it-deflist")) if (config.extendMarkdown) markdown = config.extendMarkdown(markdown) let mold = loadHTMLTemplates(markdown, config, items) let doc = markdown.render(main) return doc.replace(/<div data-item="([^"]+)"><\/div>/g, function(_, name) { return mold.defs.item({item: items[name], name}) }) } } function prefix(config) { let prefix = config.anchorPrefix if (prefix == null) prefix = config.name ? config.name + "." : "" return prefix } function templateDir(mold, dir, ext) { fs.readdirSync(dir).forEach(function(filename) { let match = /^(.*?)\.(\w+)$/.exec(filename) if (match && match[2] == ext && !has(mold.defs, match[1])) mold.bake(match[1], fs.readFileSync(dir + "/" + filename, "utf8").trim()) }) } function loadHTMLTemplates(markdown, config, items) { let mold = new Mold(moldEnv(config, items)) mold.defs.markdown = function(text) { if (!text) return "" return markdown.render(config.markdownFilter ? config.markdownFilter(text) : text) } mold.defs.markdownFile = function(name) { return mold.defs.markdown(fs.readFileSync(name + ".md", "utf8")) } if (config.templates) templateDir(mold, config.templates, "html") templateDir(mold, __dirname + "/../templates", "html") return mold } function loadMarkdownTemplates(config, items) { let mold = new Mold(moldEnv(config, items)) if (config.templates) templateDir(mold, config.templates, "md") templateDir(mold, __dirname + "/../templates/markdown", "md") mold.defs.indent = function({text, depth}) { return text.trim().split("\n").map(line => /\S/.test(line) ? " ".repeat(depth) + line : "").join("\n") } return mold } function has(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop) } function isLiteral(type) { return /^(\"|\'|-?\d)/.test(type) } function maybeLinkType(config, items, type) { let name = type.type if (type.typeParamSource) return "#" + prefix(config) + type.typeParamSource if (has(items, name) && items[name].kind != "reexport") return "#" + prefix(config) + name if (isLiteral(name)) return false let imports = config.imports, qualified = config.qualifiedImports if (imports) for (let i = 0; i < imports.length; i++) { let set = imports[i] if (typeof set == "function") { let result = set(type) if (result) return result } else if (has(set, name)) { return set[name] } } if (qualified) for (let pref in qualified) if (name.indexOf(pref + ".") == 0) { let inner = name.slice(pref.length + 1) if (has(qualified[pref], inner)) return qualified[pref][inner] } if (builtins.hasOwnProperty(name)) return builtins[name] } function moldEnv(config, items) { let env = { prefix: prefix(config), linkType: function(type) { let link = maybeLinkType(config, items, type) if (!link && link !== false && !config.allowUnresolvedTypes) throw new Error("Unknown type '" + type.type + "'" + (type.loc ? " at " + type.loc.file + ":" + type.loc.line : "")) return link }, hasDescription: function(type) { if (type.description) return true if (type.properties) for (let prop in type.properties) if (env.hasDescription(type.properties[prop])) return true if (type.params) for (let i = 0; i < type.params.length; i++) if (env.hasDescription(type.params[i])) return true if (type.returns && type.returns.description) return true return false }, breakType: function(type) { return config.breakAt != null && typeLen(type) >= config.breakAt }, processType: function(type) { return (config.processType && config.processType(type)) || type } } if (config.env) for (let prop in config.env) env[prop] = config.env[prop] return env } const typeLenMap = { union: 1, intersection: 1, tuple: 2, Array: 2, ReadonlyArray: 11, indexed: 2, conditional: 6, mapped: 10, Function: 4 } // Estimate the length of a type function typeLen(type, extra = 0) { if (!type) return 0 if (Array.isArray(type)) return type.reduce((compl, t, i) => compl + typeLen(t) + (i ? 2 : 0), extra) let val = extra + (typeLenMap.hasOwnProperty(type.type) ? typeLenMap[type.type] : type.type.length) if (type.kind == "parameter") val += (type.name?.length || 0) + 2 val += typeLen(type.implements) + (type.default ? type.default.length + 3 : 0) + typeLen(type.typeArgs, 2) if (type.signatures) { let sig = type.signatures[0] val += typeLen(sig.params) + typeLen(sig.typeArgs, 2) + typeLen(sig.returns, 3) } if (type.properties) for (let name in type.properties) { let prop = type.properties[name] if (!prop.description) val += name.length + 2 + typeLen(prop) } return val }