Added tests for @file, @returns, and @type.

This commit is contained in:
Michael Mathews 2010-07-04 01:48:04 +01:00
parent 666dd53143
commit 22cb3d2ef6
18 changed files with 713 additions and 261 deletions

View File

@ -33,7 +33,7 @@
tags = parse_tag.parse(commentSrc);
try {
preprocess(tags);
preprocess(tags, meta);
}
catch(e) {
e.message = 'Cannot make doclet from JSDoc comment found at '+ meta.file + ' ' + meta.line
@ -45,7 +45,7 @@
doclet = new Doclet(tags);
doclet.meta = meta;
postprocess(doclet);
name.resolve(doclet);
@ -73,33 +73,44 @@
@param {string name
*/
Doclet.prototype.setName = function(nameToSet) {
this.tagText('name', nameToSet);
this.setTag('name', nameToSet);
nameToSet = name.resolve(this);
}
/**
Return the text of the last tag with the given name.
@method Doclet#tagText
Return the value of the last tag with the given name.
@method Doclet#tagValue
@param {String} tagName
@returns {String} The text of the found tag.
@returns {*} The value of the found tag.
*/
Doclet.prototype.tagText = function(tagName, text) {
var i = this.tags.length;
while(i--) {
Doclet.prototype.tagValue = function(tagName) {
for (var i = 0, leni = this.tags.length; i < leni; i++) {
if (this.tags[i].name === tagName) {
if (text) { this.tags[i].text = text; }
return this.tags[i].text;
return this.tags[i].value;
}
}
// still here?
if (text) {
this.tags.push( parse_tag.fromTagText(tagName + ' ' + text) );
return text;
return null;
}
/**
Return the value of the last tag with the given name.
@method Doclet#setTag
@param {String} tagName
@returns {*} The value of the found tag.
*/
Doclet.prototype.setTag = function(tagName, tagValue) {
for (var i = 0, leni = this.tags.length; i < leni; i++) {
if (this.tags[i].name === tagName) {
this.tags[i].value = tagValue;
return ;
}
}
return '';
this.tags[this.tags.length] = parse_tag.fromText(tagName + ' ' + tagValue);
}
/**
@ -154,10 +165,10 @@
// tag value is not an object, it's just a simple string
if (!tag.pname && !tag.pdesc && !(tag.type && tag.type.length)) { // TODO: should check the list instead?
if (flavor === 'xml' && tagName === 'example') {
tagValue['#cdata'] = tag.text; // TODO this is only meaningful to XML, move to a tag.format(style) method?
tagValue['#cdata'] = tag.value; // TODO this is only meaningful to XML, move to a tag.format(style) method?
}
else {
tagValue = tag.text;
tagValue = tag.value;
}
}
@ -176,6 +187,7 @@
o.meta = this.meta;
}
return o;
}
@ -228,48 +240,49 @@
@param {Array.<Object>} tags
@returns undefined
*/
function preprocess(tags) {
function preprocess(tags, meta) {
var name = '',
taggedName = '',
isa = '',
taggedDenom = '',
taggedIsa = '',
memberof = '',
taggedMemberof = '';
taggedMemberof = '',
isFile = false;
for (var i = 0, leni = tags.length; i < leni; i++) {
if (tags[i].name === 'private') {
tags[tags.length] = parse_tag.fromTagText('access private');
tags[tags.length] = parse_tag.fromText('access private');
}
else if (tags[i].name === 'protected') {
tags[tags.length] = parse_tag.fromTagText('access protected');
tags[tags.length] = parse_tag.fromText('access protected');
}
else if (tags[i].name === 'public') {
tags[tags.length] = parse_tag.fromTagText('access public');
tags[tags.length] = parse_tag.fromText('access public');
}
else if (tags[i].name === 'const') {
tags[tags.length] = parse_tag.fromTagText('attribute constant');
tags[tags.length] = parse_tag.fromText('attribute constant');
}
else if (tags[i].name === 'readonly') {
tags[tags.length] = parse_tag.fromTagText('attribute readonly');
tags[tags.length] = parse_tag.fromText('attribute readonly');
}
else if (tags[i].name === 'name') {
if (name && name !== tags[i].text) {
throw new DocTagConflictError('Conflicting names in documentation: '+name+', '+tags[i].text);
if (name && name !== tags[i].value) {
throw new DocTagConflictError('Conflicting names in documentation: '+name+', '+tags[i].value);
}
taggedName = name = tags[i].text;
taggedName = name = tags[i].value;
}
else if (tags[i].name === 'isa') {
if (isa && isa !== tags[i].text) {
throw new DocTagConflictError('Symbol has too many denominations, cannot be both: ' + isa + ' and ' + tags[i].text);
if (isa && isa !== tags[i].value) {
throw new DocTagConflictError('Symbol has too many denominations, cannot be both: ' + isa + ' and ' + tags[i].value);
}
taggedDenom = isa = tags[i].text;
taggedIsa = isa = tags[i].value;
}
else if (tags[i].name === 'memberof') {
if (memberof) {
throw new DocTagConflictError('doclet has too many tags of type: @memberof.');
}
taggedMemberof = memberof = tags[i].text;
taggedMemberof = memberof = tags[i].value;
}
if ( nameables.indexOf(tags[i].name) > -1 ) {
@ -277,19 +290,15 @@
// for backwards compatability we ignore a @property in a doclet after a @constructor
}
else {
if (tags[i].text) {
if (name && name !== tags[i].text) {
throw new DocTagConflictError('Conflicting names in documentation: '+name+', '+tags[i].text);
if (tags[i].value) {
if (name && name !== tags[i].value) {
throw new DocTagConflictError('Conflicting names in documentation: '+name+', '+tags[i].value);
}
name = tags[i].text;
name = tags[i].value;
}
if (tags[i].pdesc) {
tags[tags.length] = parse_tag.fromTagText('desc ' + tags[i].pdesc);
}
if (tags[i].type) {
tags[tags.length] = parse_tag.fromTagText('type ' + tags[i].type.join('|'));
tags[tags.length] = parse_tag.fromText('desc ' + tags[i].pdesc);
}
if (isa && isa !== tags[i].name) {
@ -299,13 +308,17 @@
if (isa === 'const') { isa = 'property'; } // an exception to the namebale rule
}
}
else if (tags[i].name === 'file') { // the only isa which cannot have a name after @file
isFile = true;
isa = 'file';
}
if ( memberofs.hasOwnProperty(tags[i].name) ) {
if (tags[i].text) {
if (tags[i].value) {
if (memberof) {
throw new DocTagConflictError('doclet has too many tags of type: @memberof.');
}
memberof = tags[i].text;
memberof = tags[i].value;
}
if (isa && isa !== memberofs[tags[i].name]) {
@ -316,47 +329,51 @@
}
if (name && !taggedName) {
tags[tags.length] = parse_tag.fromTagText('name ' + name);
tags[tags.length] = parse_tag.fromText('name ' + name);
}
if (isa && !taggedDenom) {
tags[tags.length] = parse_tag.fromTagText('isa ' + isa);
if ( isFile && !(name || taggedName) ) {
tags[tags.length] = parse_tag.fromText('name file:'+meta.file+'');
}
if (isa && !taggedIsa) {
tags[tags.length] = parse_tag.fromText('isa ' + isa);
}
if (memberof && !taggedMemberof) {
tags[tags.length] = parse_tag.fromTagText('memberof ' + memberof);
tags[tags.length] = parse_tag.fromText('memberof ' + memberof);
}
}
function postprocess(doclet) {
if ( doclet.hasTag('class') && !doclet.hasTag('constructor') ) {
doclet.tags[doclet.tags.length] = parse_tag.fromTagText('isa constructor');
doclet.tags[doclet.tags.length] = parse_tag.fromText('isa constructor');
}
if ( doclet.hasTag('enum')) {
if (!doclet.hasTag('type')) {
doclet.tags[doclet.tags.length] = parse_tag.fromTagText('type number');
if ( doclet.hasTag('enum') ) {
if ( !doclet.hasTag('type') ) {
doclet.tags[doclet.tags.length] = parse_tag.fromText('type number');
}
if (!doclet.hasTag('readonly') && !doclet.hasTag('const')) {
doclet.tags[doclet.tags.length] = parse_tag.fromTagText('attribute constant');
if ( !doclet.hasTag('readonly') && !doclet.hasTag('const') ) {
doclet.tags[doclet.tags.length] = parse_tag.fromText('attribute constant');
}
}
if ( doclet.hasTag('const')) {
if (!doclet.hasTag('isa')) {
doclet.tags[doclet.tags.length] = parse_tag.fromTagText('isa property');
if ( doclet.hasTag('const') ) {
if ( !doclet.hasTag('isa') ) {
doclet.tags[doclet.tags.length] = parse_tag.fromText('isa property');
}
if (!doclet.hasTag('readonly') && !doclet.hasTag('const')) {
doclet.tags[doclet.tags.length] = parse_tag.fromTagText('attribute constant');
doclet.tags[doclet.tags.length] = parse_tag.fromText('attribute constant');
}
}
}
function DocTagConflictError(message) {
this.name = "DocTagConflictError";
this.message = (message || "");
this.name = 'DocTagConflictError';
this.message = (message || '');
}
DocTagConflictError.prototype = Error.prototype;

View File

@ -13,7 +13,7 @@
i = doclets.length;
while (i--) {
if (doclets[i].tagText('path') === docName) {
if (doclets[i].tagValue('path') === docName) {
foundDocs.unshift( doclets[i] );
}
}

View File

@ -23,15 +23,15 @@
@param {Doclet} doclet
*/
exports.resolve = function(doclet) {
var isa = doclet.tagText('isa'),
var isa = doclet.tagValue('isa'),
ns = '',
name = doclet.tagText('name'),
memberof = doclet.tagText('memberof'),
name = doclet.tagValue('name') || '',
memberof = doclet.tagValue('memberof') || '',
path,
shortname,
prefix,
supportedNamespaces = ['module', 'event'];
supportedNamespaces = ['module', 'event', 'file'];
// only keep the first word of the first tagged name
name = name.split(/\s+/g)[0];
@ -42,9 +42,7 @@
name = name.replace(/\.prototype\.?/g, '#');
path = shortname = name;
if (memberof) {
// like @name foo.bar, @memberof foo
if (name.indexOf(memberof) === 0) {
@ -52,9 +50,9 @@
[prefix, name] = exports.shorten(name);
}
}
else {
else if (isa !== 'file') {
[memberof, name] = exports.shorten(name);
doclet.tagText('memberof', memberof);
if (memberof) { doclet.setTag('memberof', memberof); }
}
// if name doesn't already have a doc-namespace and needs one
@ -67,28 +65,43 @@
// add doc-namespace to path
ns = isa + ':';
}
doclet.tagText('name', name);
if (name) doclet.setTag('name', name);
if (memberof && name.indexOf(memberof) !== 0) {
path = memberof + (/#$/.test(memberof)? '' : '.') + ns + name;
}
if (path) {
doclet.tagText('path', path);
doclet.setTag('path', path);
}
return path;
}
exports.shorten = function(path) {
// quoted strings in a path are atomic
var atoms = [],
cursor = 0;
path = path.replace(/(".+?")/g, function($) {
var token = '@' + atoms.length + '@';
atoms.push($);
return token;
});
var shortname = path.split(/([#.-])/).pop(),
splitOn = RegExp.$1,
splitAt = path.lastIndexOf(splitOn),
prefix = (splitOn && splitAt !== -1)? path.slice(0, splitAt) : '';
if (splitOn === '#') { prefix = prefix + splitOn; }
// restore quoted strings back again
for (var i = 0, leni = atoms.length; i < leni; i++) {
prefix = prefix.replace('@'+i+'@', atoms[i]);
shortname = shortname.replace('@'+i+'@', atoms[i]);
}
return [prefix, shortname];
}
@ -98,12 +111,12 @@
exports.resolveThis = function(name, node, doclet) {
var enclosing,
enclosingDoc,
memberof = (doclet.tagText('memberof') || '').replace(/\.prototype\.?/g, '#');
memberof = (doclet.tagValue('memberof') || '').replace(/\.prototype\.?/g, '#');
if (node.parent && node.parent.type === Token.OBJECTLIT) {
if (enclosing = node.parent) {
enclosingDoc = exports.docFromNode(enclosing) || {};
memberof = (enclosingDoc.tagText('path') || '').replace(/\.prototype\.?/g, '#');
memberof = (enclosingDoc.tagValue('path') || '').replace(/\.prototype\.?/g, '#');
if (!memberof) {
memberof = enclosingDoc.path;
@ -120,7 +133,7 @@
enclosing = node.getEnclosingFunction()
enclosingDoc = exports.docFromNode(enclosing);
memberof = enclosingDoc? enclosingDoc.tagText('path') : '';
memberof = enclosingDoc? enclosingDoc.tagValue('path') : '';
if (enclosing && !memberof) {
memberof = ''; //[[anonymousFunction]]
@ -132,7 +145,7 @@
if (memberof || !enclosing) {
// `this` refers to nearest instance in the name path
if (enclosingDoc && enclosingDoc.tagText('isa') !== 'constructor') {
if (enclosingDoc && enclosingDoc.tagValue('isa') !== 'constructor') {
var parts = memberof.split('#');
parts.pop();
memberof = parts.join('#');

View File

@ -12,37 +12,40 @@
var commentSrc = '',
thisDoclet = null,
thisDocletName = '';
// look for all comments that have names provided
if (node.type === Token.SCRIPT && node.comments) {
for each (var comment in node.comments.toArray()) {
if (comment.commentType === Token.CommentType.JSDOC) {
commentSrc = '' + comment.toSource();
if (commentSrc) {
thisDoclet = doclet.makeDoclet(commentSrc, comment, currentSourceName);
if ( thisDoclet.hasTag('name') ) {
doclets.push(thisDoclet);
if (thisDoclet.tagText('isa') === 'module') {
name.setCurrentModule( thisDoclet.tagText('path') );
}
}
}
}
}
}
// look for all comments that have names provided
if (node.type === Token.SCRIPT && node.comments) {
for each (var comment in node.comments.toArray()) {
if (comment.commentType === Token.CommentType.JSDOC) {
commentSrc = '' + comment.toSource();
if (commentSrc) {
thisDoclet = doclet.makeDoclet(commentSrc, comment, currentSourceName);
if ( thisDoclet.hasTag('name') ) {
doclets.push(thisDoclet);
if (thisDoclet.tagValue('isa') === 'module') {
name.setCurrentModule( thisDoclet.tagValue('path') );
}
}
}
}
}
}
// like function foo() {}
if (node.type == Token.FUNCTION) {
if (node.jsDoc) {
commentSrc = '' + node.jsDoc;
if (commentSrc) {
thisDoclet = doclet.makeDoclet(commentSrc, node, currentSourceName);
thisDocletName = thisDoclet.tagText('path');
thisDocletName = thisDoclet.tagValue('path');
if (!thisDocletName) {
thisDoclet.setName('' + node.name);
doclets.push(thisDoclet);
}
@ -63,8 +66,8 @@
commentSrc = '' + commentSrc;
thisDoclet = doclet.makeDoclet(commentSrc, node, currentSourceName);
thisDocletName = thisDoclet.tagText('name');
nodeKind = thisDoclet.tagText('isa');
thisDocletName = thisDoclet.tagValue('name');
nodeKind = thisDoclet.tagValue('isa');
if (!thisDocletName) {
nodeName = name.resolveThis( nodeName, node, thisDoclet );
@ -88,8 +91,8 @@
commentSrc = (counter++ === 0 && !n.jsDoc)? node.jsDoc : n.jsDoc;
if (commentSrc) {
thisDoclet = doclet.makeDoclet('' + commentSrc, node, currentSourceName);
thisDocletName = thisDoclet.tagText('path');
nodeKind = thisDoclet.tagText('isa');
thisDocletName = thisDoclet.tagValue('path');
nodeKind = thisDoclet.tagValue('isa');
if ( !thisDocletName ) {
thisDocletName = n.target.string;

View File

@ -5,16 +5,22 @@
@see <http://tools.ietf.org/html/draft-zyp-json-schema-02>
*/
var jsdoc = jsdoc || {};
jsdoc.schema = (typeof exports === 'undefined')? {} : exports; // like commonjs
jsdoc.schema.jsdocSchema = {
exports.jsdocSchema = {
"properties": {
"doc": {
"docnode": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": {
"id": {
"type": "string",
"maxItems": 1
},
"summary": {
"type": "string",
"maxItems": 1
},
"desc": {
"type": "string",
"maxItems": 1
},
@ -30,30 +36,130 @@ jsdoc.schema.jsdocSchema = {
"isa": {
"type": "string",
"maxItems": 1,
"enum": ["constructor", "module", "event", "namespace", "method", "member", "enum"]
"enum": ["constructor", "module", "event", "namespace", "method", "property", "enum", "class", "interface", "constant", "file"]
},
"access": {
"type": "string",
"maxItems": 1,
"enum": ["private", "protected", "public"]
},
"type": {
"type": "array",
"optional": true,
"items": {
"type": "string"
}
},
"param" : {
"type": "array",
"optional": true,
"items": {
"type": "object",
"properties": {
"type": {
"type": "array",
"optional": true,
"items": {
"type": "string"
}
},
"isoptional": {
"type": "boolean",
"optional": true,
"default": true
},
"isnullable": {
"type": "boolean",
"optional": true,
"default": true
},
"defaultvalue": {
"optional": true
},
"name": {
"type": "string",
},
"desc": {
"type": "string",
"optional": true
}
}
}
},
"meta": {
"type": "object",
"optional": true,
"maxItems": 1,
"file": {
"type": "string",
"optional": true,
"maxItems": 1
},
"line": {
"type": "number",
"optional": true,
"maxItems": 1
},
"category": {
"type": "string",
"optional": true,
"maxItems": 1
},
"optional": true,
"maxItems": 1
"tags": {
"type": "array",
"optional": true,
"items": {
"type": "object",
"properties": {
"tagname": {
"type": "string"
},
"tagtext": {
"type": "string",
"optional": true
}
}
}
}
}
}
}
},
"meta": {
"type": "object",
"optional": true,
"date": {
"type": "string",
"maxItems": 1
"maxItems": 1,
"project": {
"type": "object",
"optional": true,
"maxItems": 1,
"name": {
"type": "string",
"maxItems": 1
},
"uri": {
"type": "string",
"maxItems": 1,
"format": "uri"
}
},
"generated": {
"type": "object",
"optional": true,
"maxItems": 1,
"date": {
"type": "string",
"maxItems": 1,
"optional": true,
"format": "date-time"
},
"parser": {
"type": "string",
"maxItems": 1,
"optional": true
}
}
}
}

View File

@ -9,73 +9,95 @@
@module jsdoc/tag
*/
(function() {
var jsdoc_type = require('jsdoc/type');
var jsdoc_type = require('jsdoc/type'),
tagz = require('jsdoc/tagdictionary').TagDictionary;
exports.fromTagText = function(tagText) {
return new Tag(tagText);
exports.fromText = function(tagText) {
var tag = new Tag(tagText);
return tag;
}
// tags that have {type} (name desc|text)
var longTags = ['param', 'constructor', 'const', 'module', 'event', 'namespace', 'method', 'member', 'function', 'variable', 'enum', 'returns'];
var longTags = ['param', 'constructor', 'type', 'const', 'module', 'event', 'namespace', 'method', 'member', 'function', 'variable', 'enum', 'returns'];
// tags that have {type} text
var anonTags = ['returns'];
/**
@private
@constructor Tag
@constructor module:jsdoc/tag.Tag
@param {string} tagText
*/
function Tag(tagText) {
/** @property {string} - The raw text of this tag, include everything after the @. */
this.raw = tagText;
/** @property {string} - The name of this tag, the word adjacent to the @. */
this.name = '';
/** @property {Array} - Zero or more type specifiers. */
this.type = [];
this.text = '';
/** @property {*} - The value of this tag. */
this.value = null;
/** @property {string} - If this is a long tag, then this will be the parameter name. */
this.pname = '';
/** @property {string} - If this is a long tag, then this will be the parameter description. */
this.pdesc = '';
// tagText is like: "tagname tag text"
var bits = tagText.match(/^\s*(\S+)(?:\s([\s\S]*))?$/);
if (bits) {
this.name = (bits[1] || '').toLowerCase(); // like @name
this.name = trim( resolveSynonyms(this.name) );
this.text = bits[2] || ''; // all the rest of the tag
// raw is like: "tagname andsometagtext"
var parts = this.raw.match(/^\s*(\S+)(?:\s+([\s\S]*))?$/);
if (this.name !== 'example') { // example is the only tag that preserves whitespace
this.text = trim( this.text );
if (parts) {
this.name = (parts[1] || '').toLowerCase(); // like @name
this.name = resolveSynonyms(this.name);
tagText = parts[2] || ''; // all the rest of the tag
if (tagz.lookUp(this.name).keepsWhitespace) {
this.value = tagText;
}
else {
this.value = trim(tagText);
}
if (longTags.indexOf(this.name) > -1) { // is a tag that uses the long format
var /*Array.<string>*/ type,
/*string*/ text,
/*any*/ value,
/*?boolean*/ optional,
/*?boolean*/ nullable;
[type, text, optional, nullable] = jsdoc_type.parse(this.text);
// @type tags are the only tag that is not allowed to have a {type}!
if (this.name === 'type') {
text = text || type.join('|');
type = [];
}
[type, value, optional, nullable] = jsdoc_type.parse(this.value);
// don't add an empty type or null attributes
if (type && type.length) { this.type = type; }
// @type tags are special: the only tag that is not allowed to have a {type}
// their type becomes their value
if (this.name === 'type') {
value = (this.type[0] === '')? this.value.split(/\s*\|\s*/g) : this.type;
if (value.length === 1) value = value[0]; // single values don't need to be arrays
this.type = [];
}
if (optional !== null) { this.poptional = optional; }
if (nullable !== null) { this.pnullable = nullable; }
this.text = text;
if (anonTags.indexOf(this.name) > -1) {
this.pdesc = this.text;
// TODO protect @example from being overwritten?
this.value = value;
if (tagz.lookUp(this.name).canHavePname && tagz.lookUp(this.name).canHavePdesc) { // some tags just have {type} desc
if (typeof this.value === 'string') {
var [pname, pdesc, poptional, pdefault] = parsePname(this.value);
this.pname = pname;
this.pdesc = pdesc;
if (typeof poptional !== 'undefined') this.poptional = poptional;
this.pdefault = pdefault;
}
}
else {
var [pname, pdesc, poptional, pdefault] = parsePname(this.text);
this.pname = pname;
this.pdesc = pdesc;
if (typeof poptional !== 'undefined') this.poptional = poptional;
this.pdefault = pdefault;
else if (tagz.lookUp(this.name).canHavePdesc) {
this.pdesc = this.value;
}
}
}
@ -85,8 +107,8 @@
Given the source of a jsdoc comment, finds the tags.
@private
@function parse
@param {string} commentSrc Unwrapped.
@returns Array.<Object>
@param {string} commentSrc Unwrapped raw source of the doc comment.
@returns {Array.<module:jsdoc/tag.Tag>}
*/
exports.parse = function(commentSrc) {
var tags = [];
@ -96,7 +118,7 @@
.replace(/^(\s*)@(\S)/gm, '$1\\@$2') // replace splitter ats with an arbitrary sequence (unicode_recordseperator+@)
.split('\\@') // then split on that arbitrary sequence
.forEach(function($) {
var newTag = exports.fromTagText($);
var newTag = exports.fromText($);
if (newTag.name) { tags.push(newTag); }
});
@ -109,19 +131,20 @@
}
/**
Split the parameter name and parameter desc from the tag text.
Parse the parameter name and parameter desc from the tag text.
@private
@method parsePname
@param {string} tagText
@returns Array.<string> The pname and the pdesc.
@returns {Array.<string, string, boolean, boolean>} [pname, pdesc, poptional, pdefault].
*/
function parsePname(tagText) {
var pname, pdesc, poptional, pdefault;
tagText.match(/^(\[[^\]]+\]|\S+)(\s+(\S[\s\S]*))?$/);
// like: pname, pname pdesc, or name - pdesc
tagText.match(/^(\[[^\]]+\]|\S+)((?:\s*\-\s*|\s+)(\S[\s\S]*))?$/);
pname = RegExp.$1;
pdesc = RegExp.$3;
if ( /^\[\s*(.+?)\s*\]$/.test(pname) ) {
pname = RegExp.$1;
poptional = true;
@ -143,11 +166,14 @@
}
}
exports.synonyms = {
/*synonym*/ /*canonical*/
'description': 'desc',
'function': 'method',
'variable': 'property',
'return': 'returns',
'member': 'memberof'
'member': 'memberof',
'overview': 'file',
'fileoverview':'file'
}
//TODO: move into a shared module?

View File

@ -0,0 +1,88 @@
/**
@overview Provides information about the various differnt types of tags.
*/
(function() {
/** */
exports.TagDictionary = {};
exports.TagDictionary.lookUp = function(tagTitle) {
return this['@'+tagTitle] || {};
}
exports.TagDictionary.synonyms = {
};
/** */
function TagDefinition(tagTitle, opts) {
this.title = tagTitle;
this.isIsa = false; // the name of this tag is used to define the doclet's isa property
this.canProvideName = false; // this tag can be used to name the doclet
this.isDocspace = false; // The name of this tag becomes the docspace for the doclet name, like event:
this.canHaveType = false; // this tag can have a {type}
this.canHavePname = false; // this tag can have a parameter-type name
this.canHavePdesc = false;
this.keepsWhitespace = false;
for (var p in opts) {
if (typeof opts[p] !== 'undefined') {
this[p] = opts[p];
}
}
exports.TagDictionary['@'+tagTitle] = this;
}
// event handlers?
TagDefinition.prototype.onDoclet = function(tag, doclet) {
if (this.isIsa) {
if (doclet.isa) {
throw 'Overwriting isa: "'+doclet.isa+'" with "'+this.title+'"';
}
doclet.isa = this.title;
}
if (this.canProvideName) {
if (doclet.isa) {
throw 'Overwriting isa: "'+doclet.isa+'" with "'+this.title+'"';
}
doclet.isa = this.title;
}
}
new TagDefinition('namespace', {
isIsa: true,
canProvideName: true
});
new TagDefinition('constructor', {
isIsa: true,
canProvideName: true
});
new TagDefinition('file', {
isIsa: true,
canProvideName: true,
isDocspace: true
});
new TagDefinition('event', {
isIsa: true,
canProvideName: true,
isDocspace: true
});
new TagDefinition('example', {
keepsWhitespace: true
});
new TagDefinition('param', {
canHaveType: true,
canHavePname: true,
canHavePdesc: true
});
new TagDefinition('returns', {
canHaveType: true,
canHavePdesc: true
});
})();

View File

@ -11,37 +11,37 @@
(function() {
/**
@param {string} tagText
@param {string} tagValue
@returns {Array.<string>}
*/
exports.parse = function(tagText) {
if (typeof tagText !== 'string') { tagText = ''; }
exports.parse = function(tagValue) {
if (typeof tagValue !== 'string') { tagValue = ''; }
var type = '',
text = '',
count = 0;
// type expressions start with '{'
if (tagText[0] === '{') {
if (tagValue[0] === '{') {
count++;
// find matching closer '}'
for (var i = 1, leni = tagText.length; i < leni; i++) {
if (tagText[i] === '{') { count++; }
else if (tagText[i] === '}') { count--; }
for (var i = 1, leni = tagValue.length; i < leni; i++) {
if (tagValue[i] === '{') { count++; }
else if (tagValue[i] === '}') { count--; }
if (count === 0) {
type = trim(tagText.slice(1, i));
text = trim(tagText.slice(i+1));
type = trim(tagValue.slice(1, i));
text = trim(tagValue.slice(i+1));
break;
}
}
}
if (type === '') { text = tagText; }
if (type === '') { text = tagValue; }
[type, optional] = parseOptional(type);
[type, nullable] = parseNullable(type);
type = parseTypes(type); // make it into an array
return [type, text, optional, nullable];
@ -87,7 +87,7 @@
return types;
}
/** @private */
function trim(text) {
return text.replace(/^\s+|\s+$/g, '');

View File

@ -10,6 +10,10 @@ load(BASEDIR + '/test/tests/11_tag_namespace.js');
load(BASEDIR + '/test/tests/12_tag_property.js');
load(BASEDIR + '/test/tests/13_tag_method.js');
load(BASEDIR + '/test/tests/14_tag_member.js');
load(BASEDIR + '/test/tests/15_tag_type.js');
load(BASEDIR + '/test/tests/16_tag_return.js');
load(BASEDIR + '/test/tests/20_tag_file.js');
// see http://visionmedia.github.com/jspec/
JSpec.run({

View File

@ -0,0 +1,18 @@
/**
@name Foo
@constructor
*/
/**
@constructor Bar
*/
/** @constructor */
function Pez() {
}
/** @constructor */
Qux = function() {
}

View File

@ -0,0 +1,21 @@
/**
* @fileoverview This file is to be used for testing the JSDoc parser
* It is not intended to be an example of good JavaScript OO-programming,
* nor is it intended to fulfill any specific purpose apart from
* demonstrating the functionality of the
* {@link http://sourceforge.net/projects/jsdoc JSDoc} parser
*
* @author Michael Mathews <micmath@gmail.com>
* @version 0.1
*/
function Shape(){
this.getClassName = function(){
return "Shape";
}
function addReference(){
// Do nothing...
}
}

View File

@ -16,7 +16,7 @@
expect(doclet.constructor.name).to(eql, 'Doclet');
});
it('should have a `tagText` method', function() {
it('should have a `tagValue` method', function() {
expect(doclet).to(respond_to, 'toObject');
});
@ -35,20 +35,20 @@
});
});
describe('The returned value of jsdoc.Doclet#tagText', function() {
describe('The returned value of jsdoc.Doclet#tagValue', function() {
it('should be a string', function() {
var returnedValue = doclet.tagText('name');
var returnedValue = doclet.tagValue('name');
expect(returnedValue).to(be_a, String);
});
it('should be the text of the tag that matches the given tag name', function() {
var returnedValue = doclet.tagText('name');
var returnedValue = doclet.tagValue('name');
expect(returnedValue).to(eql, 'Foo');
});
it('should be the text of the last tag that matches the given tag name if there are more than 1', function() {
var returnedValue = doclet.tagText('param');
expect(returnedValue).to(eql, 'b');
it('should be the text of the first tag that matches the given tag name if there are more than 1', function() {
var returnedValue = doclet.tagValue('param');
expect(returnedValue).to(eql, 'a');
});
});

View File

@ -19,8 +19,8 @@
expect(jsdoc.tag).to(be_an, Object);
});
it('should have a `fromTagText` method', function() {
expect(jsdoc.tag).to(respond_to, 'fromTagText');
it('should have a `fromText` method', function() {
expect(jsdoc.tag).to(respond_to, 'fromText');
});
it('should have a `parse` method', function() {
@ -48,8 +48,7 @@
it('should have a `text` property which is an string', function() {
var tag = tags[0];
expect(tag).to(have_property, 'text');
expect(tag.text).to(be_an, String);
expect(tag).to(have_property, 'value');
});
it('should have a `type` property which is an array', function() {
@ -73,10 +72,10 @@
});
});
describe('The tag#text property', function() {
describe('The tag#value property', function() {
it('should be set to the text after the @name', function() {
var tag = tags[0];
expect(tag.text).to(eql, 'Hello world');
expect(tag.value).to(eql, 'Hello world');
});
});

View File

@ -10,51 +10,51 @@
tag: require('jsdoc/tag'),
parser: require('jsdoc/parser')
};
jsdoc.parser.parseFiles(BASEDIR + 'test/tests/10_tag_constructor.js');
jsdoc.parser.parseFiles(BASEDIR + 'test/samples/tag_constructor.js');
doclets = jsdoc.parser.result;
});
describe('A doclet from a constructor tag with a name tag and no code', function() {
it('should have an `isa` property set to "constructor"', function() {
var doclet = doclets[0].toObject();
expect(doclet).to(have_property, 'isa');
expect(doclet.isa).to(eql, 'constructor');
});
it('should have a `name` property set to the given name"', function() {
var doclet = doclets[0].toObject();
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'Foo');
});
});
describe('A doclet from a named constructor tag and no code', function() {
it('should have an `isa` property set to "constructor"', function() {
var doclet = doclets[1].toObject();
expect(doclet).to(have_property, 'isa');
expect(doclet.isa).to(eql, 'constructor');
});
it('should have a `name` property set to the given name"', function() {
var doclet = doclets[1].toObject();
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'Bar');
});
});
describe('A doclet from a constructor tag and named code', function() {
it('should have an `isa` property set to "constructor"', function() {
var doclet = doclets[2].toObject();
expect(doclet).to(have_property, 'isa');
expect(doclet.isa).to(eql, 'constructor');
});
it('should have a `name` property set to the given name"', function() {
var doclet = doclets[2].toObject();
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'Pez');
});
});
describe('A doclet from a constructor tag with a name tag and no code', function() {
it('should have an `isa` property set to "constructor"', function() {
var doclet = doclets[0].toObject();
expect(doclet).to(have_property, 'isa');
expect(doclet.isa).to(eql, 'constructor');
});
it('should have a `name` property set to the given name"', function() {
var doclet = doclets[0].toObject();
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'Foo');
});
});
describe('A doclet from a named constructor tag and no code', function() {
it('should have an `isa` property set to "constructor"', function() {
var doclet = doclets[1].toObject();
expect(doclet).to(have_property, 'isa');
expect(doclet.isa).to(eql, 'constructor');
});
it('should have a `name` property set to the given name"', function() {
var doclet = doclets[1].toObject();
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'Bar');
});
});
describe('A doclet from a constructor tag and named code', function() {
it('should have an `isa` property set to "constructor"', function() {
var doclet = doclets[2].toObject();
expect(doclet).to(have_property, 'isa');
expect(doclet.isa).to(eql, 'constructor');
});
it('should have a `name` property set to the given name"', function() {
var doclet = doclets[2].toObject();
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'Pez');
});
});
describe('A doclet from a constructor tag and named anonymous function', function() {
it('should have an `isa` property set to "constructor"', function() {
@ -71,28 +71,4 @@
});
});
})();
(function testarea() {
/**
@name Foo
@constructor
*/
/**
@constructor Bar
*/
/**
@constructor
*/
function Pez() {
}
/**
@constructor
*/
Qux = function() {
}
})();

View File

@ -11,47 +11,60 @@
parser: require('jsdoc/parser')
};
jsdoc.parser.parseFiles(BASEDIR + 'test/tests/14_tag_member.js');
doclets = jsdoc.parser.result;
doclets = jsdoc.parser.result.map(function($){ return $.toObject(); });
});
describe('A doclet with a method tag and a memberof tag', function() {
it('should have an `isa` property set to "method"', function() {
var doclet = doclets[2].toObject();
var doclet = doclets[2];
expect(doclet).to(have_property, 'isa');
expect(doclet.isa).to(eql, 'method');
});
it('should have a `name` property set to the given name"', function() {
var doclet = doclets[2].toObject();
var doclet = doclets[2];
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'fah');
});
it('should have a `memberof` property set to the given member name', function() {
var doclet = doclets[2].toObject();
var doclet = doclets[2];
expect(doclet).to(have_property, 'memberof');
expect(doclet.memberof).to(eql, 'foo');
expect(doclet.memberof).to(eql, 'foo#');
});
it('should have a `path` property set to the parent+member names', function() {
var doclet = doclets[2];
expect(doclet).to(have_property, 'path');
expect(doclet.path).to(eql, 'foo#fah');
});
});
describe('A doclet with a property tag and a member tag', function() {
it('should have an `isa` property set to "property"', function() {
var doclet = doclets[3].toObject();
var doclet = doclets[3];
expect(doclet).to(have_property, 'isa');
expect(doclet.isa).to(eql, 'property');
});
it('should have a `name` property set to the given name"', function() {
var doclet = doclets[3].toObject();
var doclet = doclets[3];
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'bah');
});
it('should have a `memberof` property set to the given member name', function() {
var doclet = doclets[3].toObject();
var doclet = doclets[3];
expect(doclet).to(have_property, 'memberof');
expect(doclet.memberof).to(eql, 'bar');
});
it('should have a `path` property set to the parent+member names', function() {
var doclet = doclets[3];
expect(doclet).to(have_property, 'path');
expect(doclet.path).to(eql, 'bar.bah');
});
});
});
@ -65,7 +78,7 @@
/**
@method fah
@memberof foo
@memberof foo#
*/
/**

67
test/tests/15_tag_type.js Normal file
View File

@ -0,0 +1,67 @@
(function() {
var jsdoc,
doclets;
JSpec.describe('@type', function() {
before(function() {
// docsets can only be created by parsers
jsdoc = {
tag: require('jsdoc/tag'),
parser: require('jsdoc/parser')
};
jsdoc.parser.parseFiles(BASEDIR + 'test/tests/15_tag_type.js');
doclets = jsdoc.parser.result.map(function($){ return $.toObject(); });
});
describe('A doclet with a type tag whose value is a simple string like "number"', function() {
it('should have an `type` property set to string "number"', function() {
var doclet = doclets[2];
expect(doclet).to(have_property, 'type');
expect(doclet.type).to(eql, 'number');
});
});
describe('A doclet with a type tag whose value is a series of piped strings like "number | Array.<number>"', function() {
it('should have an `type` property set to [number, Array.<number>]', function() {
var doclet = doclets[3];
expect(doclet).to(have_property, 'type');
expect(doclet.type).to(eql, ['number', 'Array.<number>']);
});
});
describe('A doclet with a type tag whose value contains braces like "{number|function(string:a, string:b){}:number}"', function() {
it('should have an `type` property set to [number, Array.<number>]', function() {
var doclet = doclets[4];
expect(doclet).to(have_property, 'type');
expect(doclet.type).to(eql, ['number', 'function(string:a, string:b){}:number']);
});
});
});
})();
(function testarea() {
/** @namespace foo */
/** @constructor bar */
/**
@property foo#fah
@type number
*/
/**
@property foo#fahfah
@type number | Array.<number>
*/
/**
@property bar.bah
@type {number|function(string:a, string:b){}:number}
*/
})();

View File

@ -0,0 +1,69 @@
(function() {
var jsdoc,
doclets;
JSpec.describe('@return', function() {
before(function() {
// docsets can only be created by parsers
jsdoc = {
tag: require('jsdoc/tag'),
parser: require('jsdoc/parser')
};
jsdoc.parser.parseFiles(BASEDIR + 'test/tests/16_tag_return.js');
doclets = jsdoc.parser.result.map(function($){ return $.toObject(); });
});
describe('A doclet with a returns tag whose value has a type and desc', function() {
it('should have an `returns` property', function() {
var doclet = doclets[0];
expect(doclet).to(have_property, 'returns');
});
});
describe('The returns value of that doclet', function() {
it('should have an `type` property set to the given type', function() {
var returns = doclets[0].returns;
expect(returns).to(have_property, 'type');
expect(returns.type).to(eql, ['number']);
});
it('should have an `desc` property set to the given desc', function() {
var returns = doclets[0].returns;
expect(returns).to(have_property, 'desc');
expect(returns.desc).to(eql, 'The size of the foo.');
});
});
describe('A doclet with a (synonym) return tag whose value has a desc', function() {
it('should have an `returns` property', function() {
var doclet = doclets[1];
expect(doclet).to(have_property, 'returns');
});
});
describe('The returns value of that doclet', function() {
it('should have an `desc` property set to the given desc', function() {
var returns = doclets[1].returns;
expect(returns).to(have_property, 'desc');
expect(returns.desc).to(eql, 'So a horse walks into a....');
});
});
});
})();
(function testarea() {
/**
@function foo
@returns {number} The size of the foo.
*/
/**
@function bar
@return So a horse walks into a....
*/
})();

32
test/tests/20_tag_file.js Normal file
View File

@ -0,0 +1,32 @@
(function() {
var jsdoc,
doclets;
JSpec.describe('@file', function() {
before(function() {
// docsets can only be created by parsers
jsdoc = {
tag: require('jsdoc/tag'),
parser: require('jsdoc/parser')
};
jsdoc.parser.parseFiles(BASEDIR + 'test/samples/tag_file_1.js');
doclets = jsdoc.parser.result.map(function($){ return $.toObject(); });
});
describe('A doclet with a fileoverview tag and no name tag', function() {
it('should have an `isa` property set to "file"', function() {
var doclet = doclets[0];
expect(doclet).to(have_property, 'isa');
expect(doclet.isa).to(eql, 'file');
});
it('should have an `name` property set to a string equal to the files name', function() {
var doclet = doclets[0];
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(match, /test\/samples\/tag_file_1\.js$/);
});
});
});
})();