123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705 |
- "use strict";
-
- var fs = require("fs");
-
- // output stream
- var out = null;
-
- // documentation data
- var data = null;
-
- // already handled objects, by name
- var seen = {};
-
- // indentation level
- var indent = 0;
-
- // whether indent has been written for the current line yet
- var indentWritten = false;
-
- // provided options
- var options = {};
-
- // queued interfaces
- var queuedInterfaces = [];
-
- // whether writing the first line
- var firstLine = true;
-
- // JSDoc hook
- exports.publish = function publish(taffy, opts) {
- options = opts || {};
-
- // query overrides options
- if (options.query)
- Object.keys(options.query).forEach(function(key) {
- if (key !== "query")
- switch (options[key] = options.query[key]) {
- case "true":
- options[key] = true;
- break;
- case "false":
- options[key] = false;
- break;
- case "null":
- options[key] = null;
- break;
- }
- });
-
- // remove undocumented
- taffy({ undocumented: true }).remove();
- taffy({ ignore: true }).remove();
- taffy({ inherited: true }).remove();
-
- // remove private
- if (!options.private)
- taffy({ access: "private" }).remove();
-
- // setup output
- out = options.destination
- ? fs.createWriteStream(options.destination)
- : process.stdout;
-
- try {
- // setup environment
- data = taffy().get();
- indent = 0;
- indentWritten = false;
- firstLine = true;
-
- // wrap everything in a module if configured
- if (options.module) {
- writeln("export = ", options.module, ";");
- writeln();
- writeln("declare namespace ", options.module, " {");
- writeln();
- ++indent;
- }
-
- // handle all
- getChildrenOf(undefined).forEach(function(child) {
- handleElement(child, null);
- });
-
- // process queued
- while (queuedInterfaces.length) {
- var element = queuedInterfaces.shift();
- begin(element);
- writeInterface(element);
- writeln(";");
- }
-
- // end wrap
- if (options.module) {
- --indent;
- writeln("}");
- }
-
- // close file output
- if (out !== process.stdout)
- out.end();
-
- } finally {
- // gc environment objects
- out = data = null;
- seen = options = {};
- queuedInterfaces = [];
- }
- };
-
- //
- // Utility
- //
-
- // writes one or multiple strings
- function write() {
- var s = Array.prototype.slice.call(arguments).join("");
- if (!indentWritten) {
- for (var i = 0; i < indent; ++i)
- s = " " + s;
- indentWritten = true;
- }
- out.write(s);
- firstLine = false;
- }
-
- // writes zero or multiple strings, followed by a new line
- function writeln() {
- var s = Array.prototype.slice.call(arguments).join("");
- if (s.length)
- write(s, "\n");
- else if (!firstLine)
- out.write("\n");
- indentWritten = false;
- }
-
- var keepTags = [
- "param",
- "returns",
- "throws",
- "see"
- ];
-
- // parses a comment into text and tags
- function parseComment(comment) {
- var lines = comment.replace(/^ *\/\*\* *|^ *\*\/| *\*\/ *$|^ *\* */mg, "").trim().split(/\r?\n|\r/g); // property.description has just "\r" ?!
- var desc;
- var text = [];
- var tags = null;
- for (var i = 0; i < lines.length; ++i) {
- var match = /^@(\w+)\b/.exec(lines[i]);
- if (match) {
- if (!tags) {
- tags = [];
- desc = text;
- }
- text = [];
- tags.push({ name: match[1], text: text });
- lines[i] = lines[i].substring(match[1].length + 1).trim();
- }
- if (lines[i].length || text.length)
- text.push(lines[i]);
- }
- return {
- text: desc || text,
- tags: tags || []
- };
- }
-
- // writes a comment
- function writeComment(comment, otherwiseNewline) {
- if (!comment || options.comments === false) {
- if (otherwiseNewline)
- writeln();
- return;
- }
- if (typeof comment !== "object")
- comment = parseComment(comment);
- comment.tags = comment.tags.filter(function(tag) {
- return keepTags.indexOf(tag.name) > -1 && (tag.name !== "returns" || tag.text[0] !== "{undefined}");
- });
- writeln();
- if (!comment.tags.length && comment.text.length < 2) {
- writeln("/** " + comment.text[0] + " */");
- return;
- }
- writeln("/**");
- comment.text.forEach(function(line) {
- if (line.length)
- writeln(" * ", line);
- else
- writeln(" *");
- });
- comment.tags.forEach(function(tag) {
- var started = false;
- if (tag.text.length) {
- tag.text.forEach(function(line, i) {
- if (i > 0)
- write(" * ");
- else if (tag.name !== "throws")
- line = line.replace(/^\{[^\s]*} ?/, "");
- if (!line.length)
- return;
- if (!started) {
- write(" * @", tag.name, " ");
- started = true;
- }
- writeln(line);
- });
- }
- });
- writeln(" */");
- }
-
- // recursively replaces all occurencies of re's match
- function replaceRecursive(name, re, fn) {
- var found;
-
- function replacer() {
- found = true;
- return fn.apply(null, arguments);
- }
-
- do {
- found = false;
- name = name.replace(re, replacer);
- } while (found);
- return name;
- }
-
- // tests if an element is considered to be a class or class-like
- function isClassLike(element) {
- return isClass(element) || isInterface(element);
- }
-
- // tests if an element is considered to be a class
- function isClass(element) {
- return element && element.kind === "class";
- }
-
- // tests if an element is considered to be an interface
- function isInterface(element) {
- return element && (element.kind === "interface" || element.kind === "mixin");
- }
-
- // tests if an element is considered to be a namespace
- function isNamespace(element) {
- return element && (element.kind === "namespace" || element.kind === "module");
- }
-
- // gets all children of the specified parent
- function getChildrenOf(parent) {
- var memberof = parent ? parent.longname : undefined;
- return data.filter(function(element) {
- return element.memberof === memberof;
- });
- }
-
- // gets the literal type of an element
- function getTypeOf(element) {
- if (element.tsType)
- return element.tsType.replace(/\r?\n|\r/g, "\n");
- var name = "any";
- var type = element.type;
- if (type && type.names && type.names.length) {
- if (type.names.length === 1)
- name = element.type.names[0].trim();
- else
- name = "(" + element.type.names.join("|") + ")";
- } else
- return name;
-
- // Replace catchalls with any
- name = name.replace(/\*|\bmixed\b/g, "any");
-
- // Ensure upper case Object for map expressions below
- name = name.replace(/\bobject\b/g, "Object");
-
- // Correct Something.<Something> to Something<Something>
- name = replaceRecursive(name, /\b(?!Object|Array)([\w$]+)\.<([^>]*)>/gi, function($0, $1, $2) {
- return $1 + "<" + $2 + ">";
- });
-
- // Replace Array.<string> with string[]
- name = replaceRecursive(name, /\bArray\.?<([^>]*)>/gi, function($0, $1) {
- return $1 + "[]";
- });
-
- // Replace Object.<string,number> with { [k: string]: number }
- name = replaceRecursive(name, /\bObject\.?<([^,]*), *([^>]*)>/gi, function($0, $1, $2) {
- return "{ [k: " + $1 + "]: " + $2 + " }";
- });
-
- // Replace functions (there are no signatures) with Function
- name = name.replace(/\bfunction(?:\(\))?\b/g, "Function");
-
- // Convert plain Object back to just object
- name = name.replace(/\b(Object\b(?!\.))/g, function($0, $1) {
- return $1.toLowerCase();
- });
-
- return name;
- }
-
- // begins writing the definition of the specified element
- function begin(element, is_interface) {
- if (!seen[element.longname]) {
- if (isClass(element)) {
- var comment = parseComment(element.comment);
- var classdesc = comment.tags.find(function(tag) { return tag.name === "classdesc"; });
- if (classdesc) {
- comment.text = classdesc.text;
- comment.tags = [];
- }
- writeComment(comment, true);
- } else
- writeComment(element.comment, is_interface || isClassLike(element) || isNamespace(element) || element.isEnum || element.scope === "global");
- seen[element.longname] = element;
- } else
- writeln();
- // ????: something changed in JSDoc 3.6.0? so that @exports + @enum does
- // no longer yield a 'global' scope, but is some sort of unscoped module
- // element now. The additional condition added below works around this.
- if ((element.scope === "global" || element.isEnum && element.scope === undefined) && !options.module)
- write("export ");
- }
-
- // writes the function signature describing element
- function writeFunctionSignature(element, isConstructor, isTypeDef) {
- write("(");
-
- var params = {};
-
- // this type
- if (element.this)
- params["this"] = {
- type: element.this.replace(/^{|}$/g, ""),
- optional: false
- };
-
- // parameter types
- if (element.params)
- element.params.forEach(function(param) {
- var path = param.name.split(/\./g);
- if (path.length === 1)
- params[param.name] = {
- type: getTypeOf(param),
- variable: param.variable === true,
- optional: param.optional === true,
- defaultValue: param.defaultvalue // Not used yet (TODO)
- };
- else // Property syntax (TODO)
- params[path[0]].type = "{ [k: string]: any }";
- });
-
- var paramNames = Object.keys(params);
- paramNames.forEach(function(name, i) {
- var param = params[name];
- var type = param.type;
- if (param.variable) {
- name = "..." + name;
- type = param.type.charAt(0) === "(" ? "any[]" : param.type + "[]";
- }
- write(name, !param.variable && param.optional ? "?: " : ": ", type);
- if (i < paramNames.length - 1)
- write(", ");
- });
-
- write(")");
-
- // return type
- if (!isConstructor) {
- write(isTypeDef ? " => " : ": ");
- var typeName;
- if (element.returns && element.returns.length && (typeName = getTypeOf(element.returns[0])) !== "undefined")
- write(typeName);
- else
- write("void");
- }
- }
-
- // writes (a typedef as) an interface
- function writeInterface(element) {
- write("interface ", element.name);
- writeInterfaceBody(element);
- writeln();
- }
-
- function writeInterfaceBody(element) {
- writeln("{");
- ++indent;
- if (element.tsType)
- writeln(element.tsType.replace(/\r?\n|\r/g, "\n"));
- else if (element.properties && element.properties.length)
- element.properties.forEach(writeProperty);
- --indent;
- write("}");
- }
-
- function writeProperty(property, declare) {
- writeComment(property.description);
- if (declare)
- write("let ");
- write(property.name);
- if (property.optional)
- write("?");
- writeln(": ", getTypeOf(property), ";");
- }
-
- //
- // Handlers
- //
-
- // handles a single element of any understood type
- function handleElement(element, parent) {
- if (element.scope === "inner")
- return false;
-
- if (element.optional !== true && element.type && element.type.names && element.type.names.length) {
- for (var i = 0; i < element.type.names.length; i++) {
- if (element.type.names[i].toLowerCase() === "undefined") {
- // This element is actually optional. Set optional to true and
- // remove the 'undefined' type
- element.optional = true;
- element.type.names.splice(i, 1);
- i--;
- }
- }
- }
-
- if (seen[element.longname])
- return true;
- if (isClassLike(element))
- handleClass(element, parent);
- else switch (element.kind) {
- case "module":
- if (element.isEnum) {
- handleEnum(element, parent);
- break;
- }
- // eslint-disable-line no-fallthrough
- case "namespace":
- handleNamespace(element, parent);
- break;
- case "constant":
- case "member":
- handleMember(element, parent);
- break;
- case "function":
- handleFunction(element, parent);
- break;
- case "typedef":
- handleTypeDef(element, parent);
- break;
- case "package":
- break;
- }
- seen[element.longname] = element;
- return true;
- }
-
- // handles (just) a namespace
- function handleNamespace(element/*, parent*/) {
- var children = getChildrenOf(element);
- if (!children.length)
- return;
- var first = true;
- if (element.properties)
- element.properties.forEach(function(property) {
- if (!/^[$\w]+$/.test(property.name)) // incompatible in namespace
- return;
- if (first) {
- begin(element);
- writeln("namespace ", element.name, " {");
- ++indent;
- first = false;
- }
- writeProperty(property, true);
- });
- children.forEach(function(child) {
- if (child.scope === "inner" || seen[child.longname])
- return;
- if (first) {
- begin(element);
- writeln("namespace ", element.name, " {");
- ++indent;
- first = false;
- }
- handleElement(child, element);
- });
- if (!first) {
- --indent;
- writeln("}");
- }
- }
-
- // a filter function to remove any module references
- function notAModuleReference(ref) {
- return ref.indexOf("module:") === -1;
- }
-
- // handles a class or class-like
- function handleClass(element, parent) {
- var is_interface = isInterface(element);
- begin(element, is_interface);
- if (is_interface)
- write("interface ");
- else {
- if (element.virtual)
- write("abstract ");
- write("class ");
- }
- write(element.name);
- if (element.templates && element.templates.length)
- write("<", element.templates.join(", "), ">");
- write(" ");
-
- // extended classes
- if (element.augments) {
- var augments = element.augments.filter(notAModuleReference);
- if (augments.length)
- write("extends ", augments[0], " ");
- }
-
- // implemented interfaces
- var impls = [];
- if (element.implements)
- Array.prototype.push.apply(impls, element.implements);
- if (element.mixes)
- Array.prototype.push.apply(impls, element.mixes);
- impls = impls.filter(notAModuleReference);
- if (impls.length)
- write("implements ", impls.join(", "), " ");
-
- writeln("{");
- ++indent;
-
- if (element.tsType)
- writeln(element.tsType.replace(/\r?\n|\r/g, "\n"));
-
- // constructor
- if (!is_interface && !element.virtual)
- handleFunction(element, parent, true);
-
- // properties
- if (is_interface && element.properties)
- element.properties.forEach(function(property) {
- writeProperty(property);
- });
-
- // class-compatible members
- var incompatible = [];
- getChildrenOf(element).forEach(function(child) {
- if (isClassLike(child) || child.kind === "module" || child.kind === "typedef" || child.isEnum) {
- incompatible.push(child);
- return;
- }
- handleElement(child, element);
- });
-
- --indent;
- writeln("}");
-
- // class-incompatible members
- if (incompatible.length) {
- writeln();
- if (element.scope === "global" && !options.module)
- write("export ");
- writeln("namespace ", element.name, " {");
- ++indent;
- incompatible.forEach(function(child) {
- handleElement(child, element);
- });
- --indent;
- writeln("}");
- }
- }
-
- // handles an enum
- function handleEnum(element) {
- begin(element);
-
- var stringEnum = false;
- element.properties.forEach(function(property) {
- if (isNaN(property.defaultvalue)) {
- stringEnum = true;
- }
- });
- if (stringEnum) {
- writeln("type ", element.name, " =");
- ++indent;
- element.properties.forEach(function(property, i) {
- write(i === 0 ? "" : "| ", JSON.stringify(property.defaultvalue));
- });
- --indent;
- writeln(";");
- } else {
- writeln("enum ", element.name, " {");
- ++indent;
- element.properties.forEach(function(property, i) {
- write(property.name);
- if (property.defaultvalue !== undefined)
- write(" = ", JSON.stringify(property.defaultvalue));
- if (i < element.properties.length - 1)
- writeln(",");
- else
- writeln();
- });
- --indent;
- writeln("}");
- }
- }
-
- // handles a namespace or class member
- function handleMember(element, parent) {
- if (element.isEnum) {
- handleEnum(element);
- return;
- }
- begin(element);
-
- var inClass = isClassLike(parent);
- if (inClass) {
- write(element.access || "public", " ");
- if (element.scope === "static")
- write("static ");
- if (element.readonly)
- write("readonly ");
- } else
- write(element.kind === "constant" ? "const " : "let ");
-
- write(element.name);
- if (element.optional)
- write("?");
- write(": ");
-
- if (element.type && element.type.names && /^Object\b/i.test(element.type.names[0]) && element.properties) {
- writeln("{");
- ++indent;
- element.properties.forEach(function(property, i) {
- writeln(JSON.stringify(property.name), ": ", getTypeOf(property), i < element.properties.length - 1 ? "," : "");
- });
- --indent;
- writeln("};");
- } else
- writeln(getTypeOf(element), ";");
- }
-
- // handles a function or method
- function handleFunction(element, parent, isConstructor) {
- var insideClass = true;
- if (isConstructor) {
- writeComment(element.comment);
- write("constructor");
- } else {
- begin(element);
- insideClass = isClassLike(parent);
- if (insideClass) {
- write(element.access || "public", " ");
- if (element.scope === "static")
- write("static ");
- } else
- write("function ");
- write(element.name);
- if (element.templates && element.templates.length)
- write("<", element.templates.join(", "), ">");
- }
- writeFunctionSignature(element, isConstructor, false);
- writeln(";");
- if (!insideClass)
- handleNamespace(element);
- }
-
- // handles a type definition (not a real type)
- function handleTypeDef(element, parent) {
- if (isInterface(element)) {
- if (isClassLike(parent))
- queuedInterfaces.push(element);
- else {
- begin(element);
- writeInterface(element);
- }
- } else {
- writeComment(element.comment, true);
- write("type ", element.name);
- if (element.templates && element.templates.length)
- write("<", element.templates.join(", "), ">");
- write(" = ");
- if (element.tsType)
- write(element.tsType.replace(/\r?\n|\r/g, "\n"));
- else {
- var type = getTypeOf(element);
- if (element.type && element.type.names.length === 1 && element.type.names[0] === "function")
- writeFunctionSignature(element, false, true);
- else if (type === "object") {
- if (element.properties && element.properties.length)
- writeInterfaceBody(element);
- else
- write("{}");
- } else
- write(type);
- }
- writeln(";");
- }
- }
|