2014-10-20 16:56:45 -04:00
|
|
|
/**
|
2014-10-22 10:11:40 -04:00
|
|
|
* @overview
|
|
|
|
* @author Michael Mathews <micmath@gmail.com>
|
|
|
|
* @license Apache License 2.0 - See file 'LICENSE.md' in this project.
|
2014-10-20 16:56:45 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2014-10-22 10:11:40 -04:00
|
|
|
* @module jsdoc/doclet
|
2014-10-20 16:56:45 -04:00
|
|
|
*/
|
2014-10-22 10:11:40 -04:00
|
|
|
'use strict';
|
2014-10-20 16:56:45 -04:00
|
|
|
|
2014-10-22 10:11:40 -04:00
|
|
|
var _ = require('underscore');
|
2014-10-20 16:56:45 -04:00
|
|
|
var jsdoc = {
|
2014-10-22 10:11:40 -04:00
|
|
|
name: require('jsdoc/name'),
|
|
|
|
src: {
|
|
|
|
astnode: require('jsdoc/src/astnode'),
|
|
|
|
Syntax: require('jsdoc/src/syntax').Syntax
|
|
|
|
},
|
2014-10-20 16:56:45 -04:00
|
|
|
tag: {
|
|
|
|
Tag: require('jsdoc/tag').Tag,
|
|
|
|
dictionary: require('jsdoc/tag/dictionary')
|
|
|
|
},
|
2014-10-22 10:11:40 -04:00
|
|
|
util: {
|
|
|
|
doop: require('jsdoc/util/doop')
|
|
|
|
}
|
2014-10-20 16:56:45 -04:00
|
|
|
};
|
2014-10-22 10:11:40 -04:00
|
|
|
var path = require('jsdoc/path');
|
|
|
|
var Syntax = jsdoc.src.Syntax;
|
|
|
|
var util = require('util');
|
2014-10-20 16:56:45 -04:00
|
|
|
|
2014-10-22 10:11:40 -04:00
|
|
|
function applyTag(doclet, tag) {
|
2014-10-20 16:56:45 -04:00
|
|
|
if (tag.title === 'name') {
|
2014-10-22 10:11:40 -04:00
|
|
|
doclet.name = tag.value;
|
2014-10-20 16:56:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (tag.title === 'kind') {
|
2014-10-22 10:11:40 -04:00
|
|
|
doclet.kind = tag.value;
|
2014-10-20 16:56:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (tag.title === 'description') {
|
2014-10-22 10:11:40 -04:00
|
|
|
doclet.description = tag.value;
|
2014-10-20 16:56:45 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// use the meta info about the source code to guess what the doclet kind should be
|
2014-10-22 10:11:40 -04:00
|
|
|
function codeToKind(code) {
|
|
|
|
var parent;
|
|
|
|
|
|
|
|
var isFunction = jsdoc.src.astnode.isFunction;
|
|
|
|
|
|
|
|
// default
|
|
|
|
var kind = 'member';
|
|
|
|
|
|
|
|
if (code.type === Syntax.FunctionDeclaration || code.type === Syntax.FunctionExpression) {
|
|
|
|
kind = 'function';
|
|
|
|
}
|
|
|
|
else if (code.node && code.node.parent) {
|
|
|
|
parent = code.node.parent;
|
|
|
|
if ( isFunction(parent) ) {
|
|
|
|
kind = 'param';
|
|
|
|
}
|
2014-10-20 16:56:45 -04:00
|
|
|
}
|
2014-10-22 10:11:40 -04:00
|
|
|
|
2014-10-20 16:56:45 -04:00
|
|
|
return kind;
|
|
|
|
}
|
|
|
|
|
|
|
|
function unwrap(docletSrc) {
|
|
|
|
if (!docletSrc) { return ''; }
|
|
|
|
|
|
|
|
// note: keep trailing whitespace for @examples
|
|
|
|
// extra opening/closing stars are ignored
|
|
|
|
// left margin is considered a star and a space
|
|
|
|
// use the /m flag on regex to avoid having to guess what this platform's newline is
|
|
|
|
docletSrc =
|
|
|
|
docletSrc.replace(/^\/\*\*+/, '') // remove opening slash+stars
|
2014-10-22 10:11:40 -04:00
|
|
|
.replace(/\**\*\/$/, '\\Z') // replace closing star slash with end-marker
|
2014-10-20 16:56:45 -04:00
|
|
|
.replace(/^\s*(\* ?|\\Z)/gm, '') // remove left margin like: spaces+star or spaces+end-marker
|
|
|
|
.replace(/\s*\\Z$/g, ''); // remove end-marker
|
|
|
|
|
|
|
|
return docletSrc;
|
|
|
|
}
|
|
|
|
|
|
|
|
function split(docletSrc) {
|
2014-10-22 10:11:40 -04:00
|
|
|
var tagSrcs = [];
|
|
|
|
|
2014-10-20 16:56:45 -04:00
|
|
|
// split out the basic tags, keep surrounding whitespace
|
|
|
|
// like: @tagTitle tagBody
|
|
|
|
docletSrc
|
2014-10-22 10:11:40 -04:00
|
|
|
.replace(/^(\s*)@(\S)/gm, '$1\\@$2') // replace splitter ats with an arbitrary sequence
|
|
|
|
.split('\\@') // then split on that arbitrary sequence
|
|
|
|
.forEach(function($) {
|
|
|
|
var parsedTag;
|
|
|
|
var tagText;
|
|
|
|
var tagTitle;
|
|
|
|
|
|
|
|
if ($) {
|
|
|
|
parsedTag = $.match(/^(\S+)(:?\s+(\S[\s\S]*))?/);
|
|
|
|
|
|
|
|
if (parsedTag) {
|
|
|
|
// we don't need parsedTag[0]
|
|
|
|
tagTitle = parsedTag[1];
|
|
|
|
tagText = parsedTag[2];
|
|
|
|
|
|
|
|
if (tagTitle) {
|
|
|
|
tagSrcs.push({
|
|
|
|
title: tagTitle,
|
|
|
|
text: tagText
|
|
|
|
});
|
|
|
|
}
|
2014-10-20 16:56:45 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2014-10-22 10:11:40 -04:00
|
|
|
|
2014-10-20 16:56:45 -04:00
|
|
|
return tagSrcs;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-10-22 10:11:40 -04:00
|
|
|
* Convert the raw source of the doclet comment into an array of Tag objects.
|
|
|
|
* @private
|
2014-10-20 16:56:45 -04:00
|
|
|
*/
|
|
|
|
function toTags(docletSrc) {
|
2014-10-22 10:11:40 -04:00
|
|
|
var tags = [];
|
|
|
|
var tagSrcs = split(docletSrc);
|
|
|
|
|
2014-10-20 16:56:45 -04:00
|
|
|
for (var i = 0, l = tagSrcs.length; i < l; i++) {
|
2014-10-22 10:11:40 -04:00
|
|
|
tags.push({ title: tagSrcs[i].title, text: tagSrcs[i].text });
|
2014-10-20 16:56:45 -04:00
|
|
|
}
|
2014-10-22 10:11:40 -04:00
|
|
|
|
2014-10-20 16:56:45 -04:00
|
|
|
return tags;
|
|
|
|
}
|
|
|
|
|
|
|
|
function fixDescription(docletSrc) {
|
|
|
|
if (!/^\s*@/.test(docletSrc)) {
|
|
|
|
docletSrc = '@description ' + docletSrc;
|
|
|
|
}
|
|
|
|
return docletSrc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-10-22 10:11:40 -04:00
|
|
|
* @class
|
|
|
|
* @classdesc Represents a single JSDoc comment.
|
|
|
|
* @param {string} docletSrc - The raw source code of the jsdoc comment.
|
|
|
|
* @param {object=} meta - Properties describing the code related to this comment.
|
2014-10-20 16:56:45 -04:00
|
|
|
*/
|
2014-10-22 10:11:40 -04:00
|
|
|
var Doclet = exports.Doclet = function(docletSrc, meta) {
|
2014-10-20 16:56:45 -04:00
|
|
|
var newTags = [];
|
|
|
|
|
|
|
|
/** The original text of the comment from the source code. */
|
|
|
|
this.comment = docletSrc;
|
|
|
|
this.setMeta(meta);
|
2014-10-22 10:11:40 -04:00
|
|
|
|
2014-10-20 16:56:45 -04:00
|
|
|
docletSrc = unwrap(docletSrc);
|
|
|
|
docletSrc = fixDescription(docletSrc);
|
|
|
|
|
|
|
|
newTags = toTags.call(this, docletSrc);
|
|
|
|
|
2014-10-22 10:11:40 -04:00
|
|
|
for (var i = 0, l = newTags.length; i < l; i++) {
|
2014-10-20 16:56:45 -04:00
|
|
|
this.addTag(newTags[i].title, newTags[i].text);
|
|
|
|
}
|
2014-10-22 10:11:40 -04:00
|
|
|
|
2014-10-20 16:56:45 -04:00
|
|
|
this.postProcess();
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Called once after all tags have been added. */
|
2014-10-22 10:11:40 -04:00
|
|
|
Doclet.prototype.postProcess = function() {
|
|
|
|
var i;
|
|
|
|
var l;
|
|
|
|
|
|
|
|
if (!this.preserveName) {
|
|
|
|
jsdoc.name.resolve(this);
|
|
|
|
}
|
2014-10-20 16:56:45 -04:00
|
|
|
if (this.name && !this.longname) {
|
|
|
|
this.setLongname(this.name);
|
|
|
|
}
|
|
|
|
if (this.memberof === '') {
|
2014-10-22 10:11:40 -04:00
|
|
|
delete this.memberof;
|
2014-10-20 16:56:45 -04:00
|
|
|
}
|
2014-10-22 10:11:40 -04:00
|
|
|
|
2014-10-20 16:56:45 -04:00
|
|
|
if (!this.kind && this.meta && this.meta.code) {
|
2014-10-22 10:11:40 -04:00
|
|
|
this.addTag( 'kind', codeToKind(this.meta.code) );
|
2014-10-20 16:56:45 -04:00
|
|
|
}
|
2014-10-22 10:11:40 -04:00
|
|
|
|
2014-10-20 16:56:45 -04:00
|
|
|
if (this.variation && this.longname && !/\)$/.test(this.longname) ) {
|
2014-10-22 10:11:40 -04:00
|
|
|
this.longname += '(' + this.variation + ')';
|
2014-10-20 16:56:45 -04:00
|
|
|
}
|
2014-10-22 10:11:40 -04:00
|
|
|
|
2014-10-20 16:56:45 -04:00
|
|
|
// add in any missing param names
|
|
|
|
if (this.params && this.meta && this.meta.code && this.meta.code.paramnames) {
|
2014-10-22 10:11:40 -04:00
|
|
|
for (i = 0, l = this.params.length; i < l; i++) {
|
2014-10-20 16:56:45 -04:00
|
|
|
if (!this.params[i].name) {
|
|
|
|
this.params[i].name = this.meta.code.paramnames[i] || '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-10-22 10:11:40 -04:00
|
|
|
/**
|
|
|
|
* Add a tag to the doclet.
|
|
|
|
*
|
|
|
|
* @param {string} title - The title of the tag being added.
|
|
|
|
* @param {string} [text] - The text of the tag being added.
|
|
|
|
*/
|
|
|
|
Doclet.prototype.addTag = function(title, text) {
|
2014-10-20 16:56:45 -04:00
|
|
|
var tagDef = jsdoc.tag.dictionary.lookUp(title),
|
|
|
|
newTag = new jsdoc.tag.Tag(title, text, this.meta);
|
|
|
|
|
|
|
|
if (tagDef && tagDef.onTagged) {
|
|
|
|
tagDef.onTagged(this, newTag);
|
|
|
|
}
|
2014-10-22 10:11:40 -04:00
|
|
|
|
2014-10-20 16:56:45 -04:00
|
|
|
if (!tagDef) {
|
|
|
|
this.tags = this.tags || [];
|
|
|
|
this.tags.push(newTag);
|
|
|
|
}
|
2014-10-22 10:11:40 -04:00
|
|
|
|
|
|
|
applyTag(this, newTag);
|
2014-10-20 16:56:45 -04:00
|
|
|
};
|
|
|
|
|
2014-10-22 10:11:40 -04:00
|
|
|
function removeGlobal(longname) {
|
|
|
|
var globalRegexp = new RegExp('^' + jsdoc.name.GLOBAL_LONGNAME + '\\.?');
|
|
|
|
|
|
|
|
return longname.replace(globalRegexp, '');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the doclet's `memberof` property.
|
|
|
|
*
|
|
|
|
* @param {string} sid - The longname of the doclet's parent symbol.
|
|
|
|
*/
|
|
|
|
Doclet.prototype.setMemberof = function(sid) {
|
2014-10-20 16:56:45 -04:00
|
|
|
/**
|
2014-10-22 10:11:40 -04:00
|
|
|
* The longname of the symbol that contains this one, if any.
|
|
|
|
* @type string
|
2014-10-20 16:56:45 -04:00
|
|
|
*/
|
2014-10-22 10:11:40 -04:00
|
|
|
this.memberof = removeGlobal(sid)
|
|
|
|
.replace(/\.prototype/g, jsdoc.name.INSTANCE);
|
2014-10-20 16:56:45 -04:00
|
|
|
};
|
|
|
|
|
2014-10-22 10:11:40 -04:00
|
|
|
/**
|
|
|
|
* Set the doclet's `longname` property.
|
|
|
|
*
|
|
|
|
* @param {string} name - The longname for the doclet.
|
|
|
|
*/
|
|
|
|
Doclet.prototype.setLongname = function(name) {
|
2014-10-20 16:56:45 -04:00
|
|
|
/**
|
2014-10-22 10:11:40 -04:00
|
|
|
* The fully resolved symbol name.
|
|
|
|
* @type string
|
2014-10-20 16:56:45 -04:00
|
|
|
*/
|
2014-10-22 10:11:40 -04:00
|
|
|
this.longname = removeGlobal(name);
|
2014-10-20 16:56:45 -04:00
|
|
|
if (jsdoc.tag.dictionary.isNamespace(this.kind)) {
|
|
|
|
this.longname = jsdoc.name.applyNamespace(this.longname, this.kind);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-10-22 10:11:40 -04:00
|
|
|
/**
|
|
|
|
* Get the full path to the source file that is associated with a doclet.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {module:jsdoc/doclet.Doclet} The doclet to check for a filepath.
|
|
|
|
* @return {string} The path to the doclet's source file, or an empty string if the path is not
|
|
|
|
* available.
|
|
|
|
*/
|
|
|
|
function getFilepath(doclet) {
|
|
|
|
if (!doclet || !doclet.meta || !doclet.meta.filename) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
return path.join(doclet.meta.path || '', doclet.meta.filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the doclet's `scope` property. Must correspond to a scope name that is defined in
|
|
|
|
* {@link module:jsdoc/name.SCOPE_NAMES}.
|
|
|
|
*
|
|
|
|
* @param {module:jsdoc/name.SCOPE_NAMES} scope - The scope for the doclet relative to the symbol's
|
|
|
|
* parent.
|
|
|
|
* @throws {Error} If the scope name is not recognized.
|
|
|
|
*/
|
|
|
|
Doclet.prototype.setScope = function(scope) {
|
|
|
|
var errorMessage;
|
|
|
|
var filepath;
|
|
|
|
var scopeNames = Object.keys(jsdoc.name.SCOPE_NAMES);
|
|
|
|
|
|
|
|
if (scopeNames.indexOf(scope) === -1) {
|
|
|
|
filepath = getFilepath(this);
|
|
|
|
|
|
|
|
errorMessage = util.format('The scope name "%s" is not recognized. Use one of the names ' +
|
|
|
|
'defined in module:jsdoc/name.SCOPE_NAMES.', scope);
|
|
|
|
if (filepath) {
|
|
|
|
errorMessage += util.format(' (Source file: %s)', filepath);
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error(errorMessage);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.scope = scope;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a symbol to this doclet's `borrowed` array.
|
|
|
|
*
|
|
|
|
* @param {string} source - The longname of the symbol that is the source.
|
|
|
|
* @param {string} target - The name the symbol is being assigned to.
|
|
|
|
*/
|
|
|
|
Doclet.prototype.borrow = function(source, target) {
|
|
|
|
var about = { from: source };
|
|
|
|
if (target) {
|
|
|
|
about.as = target;
|
|
|
|
}
|
|
|
|
|
2014-10-20 16:56:45 -04:00
|
|
|
if (!this.borrowed) {
|
|
|
|
/**
|
2014-10-22 10:11:40 -04:00
|
|
|
* A list of symbols that are borrowed by this one, if any.
|
|
|
|
* @type Array.<string>
|
2014-10-20 16:56:45 -04:00
|
|
|
*/
|
|
|
|
this.borrowed = [];
|
|
|
|
}
|
|
|
|
this.borrowed.push(about);
|
|
|
|
};
|
|
|
|
|
2014-10-22 10:11:40 -04:00
|
|
|
Doclet.prototype.mix = function(source) {
|
|
|
|
/**
|
|
|
|
* A list of symbols that are mixed into this one, if any.
|
|
|
|
* @type Array.<string>
|
|
|
|
*/
|
|
|
|
this.mixes = this.mixes || [];
|
2014-10-20 16:56:45 -04:00
|
|
|
this.mixes.push(source);
|
|
|
|
};
|
|
|
|
|
2014-10-22 10:11:40 -04:00
|
|
|
/**
|
|
|
|
* Add a symbol to the doclet's `augments` array.
|
|
|
|
*
|
|
|
|
* @param {string} base - The longname of the base symbol.
|
|
|
|
*/
|
|
|
|
Doclet.prototype.augment = function(base) {
|
|
|
|
/**
|
|
|
|
* A list of symbols that are augmented by this one, if any.
|
|
|
|
* @type Array.<string>
|
|
|
|
*/
|
|
|
|
this.augments = this.augments || [];
|
2014-10-20 16:56:45 -04:00
|
|
|
this.augments.push(base);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2014-10-22 10:11:40 -04:00
|
|
|
* Set the `meta` property of this doclet.
|
|
|
|
*
|
|
|
|
* @param {object} meta
|
|
|
|
*/
|
|
|
|
Doclet.prototype.setMeta = function(meta) {
|
|
|
|
/**
|
|
|
|
* Information about the source code associated with this doclet.
|
|
|
|
* @namespace
|
|
|
|
*/
|
|
|
|
this.meta = this.meta || {};
|
|
|
|
|
2014-10-20 16:56:45 -04:00
|
|
|
if (meta.range) {
|
|
|
|
/**
|
2014-10-22 10:11:40 -04:00
|
|
|
* The positions of the first and last characters of the code associated with this doclet.
|
|
|
|
* @type Array.<number>
|
2014-10-20 16:56:45 -04:00
|
|
|
*/
|
|
|
|
this.meta.range = meta.range.slice(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (meta.lineno) {
|
|
|
|
/**
|
2014-10-22 10:11:40 -04:00
|
|
|
* The name of the file containing the code associated with this doclet.
|
|
|
|
* @type string
|
2014-10-20 16:56:45 -04:00
|
|
|
*/
|
|
|
|
this.meta.filename = path.basename(meta.filename);
|
|
|
|
/**
|
2014-10-22 10:11:40 -04:00
|
|
|
* The line number of the code associated with this doclet.
|
|
|
|
* @type number
|
|
|
|
*/
|
2014-10-20 16:56:45 -04:00
|
|
|
this.meta.lineno = meta.lineno;
|
|
|
|
|
|
|
|
var pathname = path.dirname(meta.filename);
|
|
|
|
if (pathname && pathname !== '.') {
|
|
|
|
this.meta.path = pathname;
|
|
|
|
}
|
|
|
|
}
|
2014-10-22 10:11:40 -04:00
|
|
|
|
2014-10-20 16:56:45 -04:00
|
|
|
/**
|
2014-10-22 10:11:40 -04:00
|
|
|
* Information about the code symbol.
|
|
|
|
* @namespace
|
2014-10-20 16:56:45 -04:00
|
|
|
*/
|
2014-10-22 10:11:40 -04:00
|
|
|
this.meta.code = this.meta.code || {};
|
2014-10-20 16:56:45 -04:00
|
|
|
if (meta.id) { this.meta.code.id = meta.id; }
|
|
|
|
if (meta.code) {
|
|
|
|
if (meta.code.name) {
|
|
|
|
/** The name of the symbol in the source code. */
|
|
|
|
this.meta.code.name = meta.code.name;
|
|
|
|
}
|
|
|
|
if (meta.code.type) {
|
|
|
|
/** The type of the symbol in the source code. */
|
|
|
|
this.meta.code.type = meta.code.type;
|
|
|
|
}
|
2014-10-22 10:11:40 -04:00
|
|
|
// the AST node is only enumerable in debug mode, which reduces clutter for the
|
|
|
|
// --explain/-X option
|
2014-10-20 16:56:45 -04:00
|
|
|
if (meta.code.node) {
|
2014-10-22 10:11:40 -04:00
|
|
|
Object.defineProperty(this.meta.code, 'node', {
|
|
|
|
value: meta.code.node,
|
|
|
|
enumerable: global.env.opts.debug ? true : false
|
|
|
|
});
|
2014-10-20 16:56:45 -04:00
|
|
|
}
|
|
|
|
if (meta.code.funcscope) {
|
|
|
|
this.meta.code.funcscope = meta.code.funcscope;
|
|
|
|
}
|
|
|
|
if (meta.code.value) {
|
|
|
|
/** The value of the symbol in the source code. */
|
|
|
|
this.meta.code.value = meta.code.value;
|
|
|
|
}
|
|
|
|
if (meta.code.paramnames) {
|
2014-10-22 10:11:40 -04:00
|
|
|
this.meta.code.paramnames = meta.code.paramnames.slice(0);
|
2014-10-20 16:56:45 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|