UNPKG

coddoc

Version:

Documentation generator

593 lines (513 loc) 21.3 kB
(function () { "use strict"; var util = require("./../util.js"); var TAG_REGEXP = /@(\w+)/; var TAG_REGEXP_ONE = /@\w+\s([^\s]*)/; var TAG_REGEXP_DESCRIPTION = /@\w+(.*)/; var TAG_REGEXP_AS = /@\w+\s([^\s]*)\s+as\s+([^\s]*)/; var TAG_REGEXP_PARAM = /@\w+\s(\{[^\}]*\})?\s*?(\[[^\]]*\])\s/; var TAG_REGEXP_RETURNS = /@\w+\s(\{(?:\w+(?:\.\w+)*|\*)(?:\|?(?:\w+(?:\.\w+)*|\*))*\})?/; var tags = {}; var exportTags = exports.tags = {}; /** * Parses types from a comment fragment. * * @example * * var types = coddoc.tags.parseTypes("{Type1|Boolean|String} rest of the comment"); * //types => {comment : "res of the comment" types : ["Type1", "Boolean", "String"]}; * * @memberOf coddoc.tags * @param {String} comment the comment fragment to parse. * @return {Object} an object containing : * <ul> * <li>comment : the the comment fragment with the types removed</li> * <li>tags : array of strings with the type names</li> * </ul> */ exportTags.parseTypes = function (comment) { var typeArr = []; if (comment[0] === "{") { var types = util.getTokensBetween(comment, "{").join(""); comment = comment.replace("{" + types + "}", "").trim(); typeArr = types.split("|").map(function (t) { return t.trim(); }); } return {comment: comment, types: typeArr}; }; /** * Parses an individual name from a comment fragment. * * @example * * var name = coddoc.tags.parseName("someValue description"); * //name => {comment : description, name : {name : "someValue", optional : false, defaultValue : undefined}}; * * var name = coddoc.tags.parseName("[someValue] description"); * //name => {comment : description, name : {name : "someValue", optional : true, defaultValue : undefined}}; * * var name = coddoc.tags.parseName("[someValue=true] description"); * //name => {comment : description, name : {name : "someValue", optional : true, defaultValue : true}}; * * @memberof coddoc.tags * @param {String} comment the comment fragment to parse * @return {Object} an object containing: * <ul> * <li>comment : the comment fragement with the name removed</li> * <li>name * <ul> * <li>name : the name</li> * <li>optional : boolean indicating if it is optional</li> * <li>defaultValue : undefined if there isnt one otherwise a string representation of the * default value</li> * </ul> * </li> * </ul> */ exportTags.parseName = function (comment) { var optional = false, defaultValue, name; if (comment[0] === "[") { optional = true; var param = util.getTokensBetween(comment, "[").join(""); comment = comment.replace("[" + param + "]", "").trim(); if (param.indexOf("=") !== -1) { parts = param.split("="); name = parts[0]; defaultValue = parts[1]; } else { name = param; } } else { var parts = comment.split(/\s/); name = parts.shift(); comment = parts.join(" ").trim(); } return {comment: comment, name: {name: name, optional: optional, defaultValue: defaultValue}}; }; var parseTypes = exportTags.parseTypes; var parseName = exportTags.parseName; var parseConstructorName = function (memberof, symbol) { var name = util.splitName(memberof.replace(".prototype", "")); symbol.name = name.name; symbol.memberof = name.memberof; }; /** * Returns a regular expression that can be used to parse tags * * @protected * @memberOf coddoc * * @return {RegExp} a regular expression to parse valid tags. */ exports.getTagRegexp = function () { return new RegExp("@(" + Object.keys(tags).join("|") + ")"); }; /** * Parses a tag and the coresponding comment using a matching tag handler. Each parsed tag * could add a new property to the {@link coddoc.Symbol}. The parsed tag will be added the the * {@link coddoc.Symbol#tags} array. * * @example * * coddoc.parseTag("someTag", "@someTag props...", sym, src, index, context); * //would add a new tag to the symbols property * { * tag : "tagname", * props : {...} * } * * //the props value would also be added to the symbols params array. * * @protected * @memberOf coddoc * @param {String} tag the tag name being parsed from the comment * @param {String} comment the comment fragment being parsed * @param {coddoc.Symbol} sym the symbol that the comment corresponds to. The code object and values * will have already been set. * @param {coddoc.Context} context the currect context object. The context allows tags to set new scopes and namespaces. */ exports.parseTag = function (comment, sym, context) { var tag = comment.match(TAG_REGEXP), ret = {}; if (tag && tag.length === 2) { var t = tags[tag[1]]; if (t) { t.parse(comment, sym, context); } else { throw new Error("Invalid tag " + tag); } } }; /** * Adds a new tag to be parsed. You can use this to add custom tags. {@link coddoc} will * not do anything with the new tag by default, however you can add functionality to handle the * new tag in the template. * * @example * * //if a tag is contains a '|' character then each variation will resolve the the same parser function. * coddoc.addTagHandler("void|VOID|Void", function(comment, symbol, context){ * //do something with the tag or add properties to the symbol. * symbol.isVoid = true; * symbol.tags.push({tag : "void", props : {}}); * }); * //in the template you can add functionality to handle the new tag. For example: * //in the html symbol.tmpl you could add a new label to the name header * <h3> * {{name}} * {{#if isStatic}} * <span class="label label-info">Static</span> * {{/if}} * {{#if isFunction}} * <span class="label label-label">Function</span> * {{/if}} * {{#if isPrivate}} * <span class="label label-important">Private</span> * {{else}} * {{#if isProtected}} * <span class="label label-warning">Protected</span> * {{else}} * <span class="label label-success">Public</span> * {{/if}} * {{/if}} * {{#if isVoid}} * <span class="label label-label">Void</span> * {{/if}} * </h3> * * @memberOf coddoc * @param {String} tag the tag to parse, if a tag is contains a '|' character then the string will be split * and each variation will resolve to the same parse function. If the tag already exists then the old implementation * will be replaced by the new one. * * @param {Function} parse a parser function to invoke when a tag that matches the name is encountered. */ var addTagHandler = function (tag, parse) { tag.split("|").forEach(function (tag) { tags[tag] = { parse: parse || function () { return {tag: tag, props: {}}; }}; }); }; exports.addTagHandler = addTagHandler; addTagHandler("augments|extends", function (comment, symbol) { var t = "augments"; var augmented = comment.match(TAG_REGEXP_ONE); if (augmented) { augmented = augmented[1]; } var props = {augments: augmented.trim()}; symbol.augments.push(props); symbol.tags.push({tag: t, props: props}); }); addTagHandler("lends", function (comment, symbol, context) { var t = "lends"; var lends = comment.match(TAG_REGEXP_ONE); if (lends) { lends = util.resolveName(lends[1]); context.activateScope(lends); } symbol.lends = lends; symbol.tags.push({tag: t, props: {lends: lends.trim()}}); }); addTagHandler("namespace", function (comment, symbol, context) { var t = "namespace"; var descr = comment.replace(/@(namespace)\s*/, "").trim(); symbol.isNamespace = true; symbol.description = (symbol.description || "") + descr; symbol.tags.push({tag: t, props: {description: descr}}); }); addTagHandler("param|parameter|arg|argument", function (comment, symbol, context) { var t = "param", props = {}; comment = comment.replace(/@(param(eter)?|arg(ument)?)\s*/, "").trim(); var types = parseTypes(comment); props.types = types.types; comment = types.comment; var name = parseName(comment); props.name = name.name; comment = name.comment; props.description = comment.trim(); var currParam = symbol.params.filter(function (p) { return p.name.name === props.name.name; }); if (currParam.length) { currParam = currParam[0]; currParam.name = props.name; currParam.description = props.description; } else { symbol.params.push(props); } symbol.tags.push({tag: t, props: props}); }); addTagHandler("return|returns", function (comment, symbol, context) { var t = "returns", props = {}; comment = comment.replace(/@(return(s)?)\s*/, "").trim(); var types = parseTypes(comment); props.types = types.types; comment = types.comment; props.description = comment; symbol.returns.push(props); symbol.tags.push({tag: t, props: props}); }); addTagHandler("memberof|memberOf", function (comment, symbol, context) { var t = "memberof"; var descr = comment.replace(/@(member[O|o]f?)\s*/, "").trim(); if (descr) { descr = util.resolveName(descr.trim()); } symbol.memberof = descr; symbol.tags.push({tag: t, props: {of: descr}}); }); addTagHandler("name", function (comment, symbol, context) { var t = "name"; var name = comment.replace(/@name\s*/, "").trim(); symbol.name = name; symbol.tags.push({tag: t, props: {name: name}}); }); addTagHandler("constructor|constructs", function (comment, symbol) { var memberof = symbol.memberof; if (memberof.match(".prototype")) { parseConstructorName(memberof, symbol); } symbol.isConstructor = true; symbol.tags.push({tag: "constructor", props: {}}); }); addTagHandler("class|classdesc", function (comment, symbol) { var t = "class"; var descr = comment.replace(/@class(desc)?\s*/, "").trim(); var memberof = symbol.memberof || ""; if (memberof.match(".prototype")) { parseConstructorName(memberof, symbol); } symbol.isConstructor = true; symbol.description += descr; symbol.tags.push({tag: t, props: {description: descr}}); }); addTagHandler("example", function (comment, symbol) { var props = {code: comment.replace(/@example\s*/, "").trim()}; symbol.examples.push(props); symbol.tags.push({tag: "example", props: props}); }); addTagHandler("see", function (comment, symbol) { var props = {description: comment.replace(/@see\s*/, "").trim()}; symbol.see.push(props); symbol.tags.push({tag: "see", props: props}); }); addTagHandler("private", function (comment, symbol) { symbol.isPrivate = true; symbol.tags.push({tag: "private", props: {}}); }); addTagHandler("protected", function (comment, symbol) { symbol.isPrivate = false; symbol.isProtected = true; symbol.tags.push({tag: "protected", props: {}}); }); addTagHandler("static", function (comment, symbol) { symbol.isStatic = true; symbol.tags.push({tag: "static", props: {}}); }); addTagHandler("public", function (comment, symbol) { symbol.isPrivate = false; symbol.isPublic = true; symbol.tags.push({tag: "public", props: {}}); }); addTagHandler("ignore", function (comment, symbol) { symbol.ignore = true; symbol.tags.push({tag: "ignore", props: {}}); }); addTagHandler("ignoreCode", function (comment, symbol) { symbol.ignoreCode = true; symbol.code = null; symbol.tags.push({tag: "ignoreCode", props: {}}); }); addTagHandler("method|function", function (comment, symbol) { symbol.isFunction = true; symbol.tags.push({tag: "method", props: {}}); }); addTagHandler("field", function (comment, symbol) { symbol.isFunction = false; symbol.tags.push({tag: "field", props: {}}); }); addTagHandler("type", function (comment, symbol) { var props = {type: comment.replace(/@type\s*/, "").trim()}; symbol.type = props.type; symbol.tags.push({tag: "type", props: props}); }); addTagHandler("default", function (comment, symbol) { var props = {defaultValue: comment.replace(/@default\s*/, "")}; symbol.code = props.defaultValue; symbol.tags.push({tag: "default", props: props}); }); addTagHandler("throws|throw|exception", function (comment, symbol) { var t = "throws", props = {}; comment = comment.replace(/@(throw(s)?|exception)?\s*/, "").trim(); var types = parseTypes(comment); props.types = types.types; comment = types.comment; props.description = comment; symbol.throws.push(props); symbol.tags.push({tag: t, props: props}); }); addTagHandler("property", function (comment, symbol) { var t = "property", props = {}; comment = comment.replace(/@(property)\s*/, "").trim(); var types = parseTypes(comment); props.types = types.types; comment = types.comment; var name = parseName(comment); props.name = name.name; comment = name.comment; props.description = comment.trim(); symbol.properties.push({name: props.name.name, type: types.types, code: props.name.defaultValue, description: props.description, isStatic: false}); symbol.tags.push({tag: t, props: props}); }); addTagHandler("borrows", function (comment, symbol) { var tag = "borrows", props = {}; comment = comment.replace(/@(borrows)\s*/, ""); var parts = comment.split(/\s*as\s*/).map(function (p) { return p.trim(); }); if (parts) { var as = parts[1]; props.borrows = util.splitName(util.resolveName(parts[0])); props.as = as.replace("#", "").replace(/^\./, ""); props.isStatic = as.indexOf("#") === -1; } symbol.borrows.push(props); symbol.tags.push({tag: tag, props: props}); }); addTagHandler("constant|const", function (comment, symbol) { symbol.isConstant = true; symbol.tags.push({tag: "constant", props: {}}); }); addTagHandler("desc|description", function (comment, symbol) { var t = "description", description = comment.replace(/@desc(ription)*\s*/, "").trim(); symbol.description = (symbol.description || "") + description; symbol.tags.push({tag: "description", props: {description: description}}); }); addTagHandler("projectName", function (comment, symbol) { var t = "projectName", description = comment.replace(/@projectName*\s*/, "").trim(); symbol.isProjectName = true; symbol.projectName = (symbol.projectName || "") + description; symbol.tags.push({tag: t, props: {description: description}}); }); addTagHandler("header", function (comment, symbol) { var t = "header", description = comment.replace(/@header*\s*/, "").trim(); symbol.isHeader = true; var location = util.getTokensBetween(description, "[").join(""); if (location && util.isWhiteSpace(description.replace("[" + location + "]", ""))) { symbol.header = {location: location.trim(), file: symbol.file}; } else { symbol.header = {content: (symbol.header || "") + description}; } symbol.tags.push({tag: t, props: symbol.header}); }); addTagHandler("footer", function (comment, symbol) { var t = "footer", description = comment.replace(/@footer*\s*/, "").trim(); symbol.isFooter = true; var location = util.getTokensBetween(description, "[").join(""); if (location && util.isWhiteSpace(description.replace("[" + location + "]", ""))) { symbol.footer = {location: location.trim(), file: symbol.file}; } else { symbol.footer = {content: (symbol.footer || "") + description}; } symbol.tags.push({tag: t, props: symbol.footer}); }); addTagHandler("github", function (comment, symbol) { var t = "github", description = comment.replace(/@github*\s*/, "").trim(); symbol.isGithub = true; symbol.github = description; symbol.tags.push({tag: t, props: {link: description}}); }); addTagHandler("includeDoc", function (comment, symbol) { var t = "includeDoc", comment = comment.replace(/@includeDoc*\s*/, "").trim(), compile = true; var docName = util.getTokensBetween(comment, "[").join(""); var location = comment.replace("[" + docName + "]", "").trim(); if (location.match(/^\[([^\]]*)\]$/)) { compile = false; location = location.replace(/^\[|\]$/ig, ""); } var props = {docName: docName, file: symbol.file, location: location, compile: compile}; symbol.includedDocs.push(props); symbol.tags.push({tag: t, props: props}); }); /*addTagHandler("copyright", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("deprecated", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("enum", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("event", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("exports", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("file|overview|fileoverview|fileOverview", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("fires", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("global", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("inner", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("instance", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("kind", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("license", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("member", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("var", function (comment, symbol, context) { return this.member(comment) }); addTagHandler("mixes", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("mixin", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("module", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("readonly", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("requires", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("since", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("summary", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("this", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("todo", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("typedef", function (comment, symbol, context) { throw new Error("not implemented"); }); addTagHandler("version", function (comment, symbol, context) { throw new Error("not implemented"); }); */ }());