Added support for params, nullable, optional.

This commit is contained in:
Michael Mathews 2010-06-15 00:09:09 +01:00
parent 1da08819b7
commit 9a45e4a8a4
7 changed files with 284 additions and 84 deletions

View File

@ -131,27 +131,48 @@
o = {};
for (var i = 0, leni = this.tags.length; i < leni; i++) {
if (exportTags.indexOf(this.tags[i].name) === -1) { continue; }
tag = this.tags[i];
if ( exportTags.indexOf(tag.name) === -1 ) { continue; }
tagName = tag.name;
tagValue = {};
if (tag.type) {
tagValue.type = tag.type;
// not a long tag
if (!tag.pname && tag.text) { tagValue.text = tag.text; }
}
// if (tag.type && tag.type.length) {
// tagValue.type = tag.type;
// // not a long tag
// if (!tag.pname && tag.text) { tagValue.text = tag.text; }
// }
// a long tag
if (tag.pname) { tagValue.name = tag.pname; }
if (tag.pname) {
if ( /^\[(.+)\]$/.test(tag.pname) ) {
tagValue.name = RegExp.$1;
tag.poptional = true;
}
else {
tagValue.name = tag.pname;
}
tagValue.type = tag.type;
// print('```` name is '+tagName+': '+tagValue);
}
if (tag.pdesc) { tagValue.desc = tag.pdesc; }
if (typeof tag.poptional === 'boolean') { tagValue.optional = tag.poptional; }
if (typeof tag.pnullable === 'boolean') { tagValue.nullable = tag.pnullable; }
// tag value is not an object, it's just a simple string
if (!tag.pname && !tag.type) { tagValue = tag.text; }
if (!o[tagName]) { o[tagName] = tagValue; }
else if (o[tagName].push) { o[tagName].push(tagValue); }
else {
if (!tag.pname) {
tagValue = tag.text;
}
if (typeof o[tagName] === 'undefined') { // not defined
o[tagName] = tagValue;
}
else if (o[tagName].push) { // is an array
o[tagName].push(tagValue);
}
else { // is a string, but needs to be an array
o[tagName] = [ o[tagName] ];
o[tagName].push(tagValue);
}
@ -266,7 +287,7 @@
if (memberof) {
throw new DocTagConflictError('doclet has too many tags of type: @memberof.');
}
taggedMemberof = memberof = tags[i].text;
taggedMemberof = memberof = tags[i].text+'ZZZ_0';
}
if ( nameables.indexOf(tags[i].name) > -1 ) {
@ -282,7 +303,7 @@
}
if (tags[i].type) {
tags[tags.length] = tag.fromTagText('type ' + tags[i].type);
tags[tags.length] = tag.fromTagText('type ' + tags[i].type.join('|'));
}
if (denom && denom !== tags[i].name) {
@ -297,7 +318,7 @@
if (memberof) {
throw new DocTagConflictError('doclet has too many tags of type: @memberof.');
}
memberof = tags[i].text;
memberof = tags[i].text+'ZZZ_1';
}
if (denom && denom !== memberofs[tags[i].name]) {
@ -316,7 +337,7 @@
}
if (memberof && !taggedMemberof) {
tags[tags.length] = tag.fromTagText('memberof ' + memberof);
tags[tags.length] = tag.fromTagText('memberof ' + memberof+'ZZZ_2');
}
}

View File

@ -76,7 +76,7 @@
var shortname = path.split(/([#.-])/).pop(),
splitOn = RegExp.$1,
splitAt = path.lastIndexOf(splitOn),
prefix = (splitAt === -1)? '' : path.slice(0, splitAt);
prefix = (splitOn && splitAt !== -1)? path.slice(0, splitAt) : '';
if (splitOn === '#') { prefix = prefix + splitOn; }
return [prefix, shortname];

View File

@ -9,6 +9,7 @@
@module jsdoc/tag
*/
(function() {
var jsdoc_type = require('jsdoc/type');
exports.fromCommentText = function(commentText) {
var tag,
@ -45,7 +46,7 @@
function Tag(tagText) {
this.raw = tagText;
this.name = '';
this.type = '';
this.type = [];
this.text = '';
this.pname = '';
this.pdesc = '';
@ -54,24 +55,29 @@
var bits = tagText.match(/^(\S+)(?:\s+([\s\S]*))?$/);
if (bits) {
this.name = (bits[1] || '').toLowerCase();
this.text = bits[2] || '';
this.name = (bits[1] || '').toLowerCase(); // like @name
this.name = synonym(this.name);
var typeText = splitType(this.text);
this.text = bits[2] || ''; // all the rest of the tag
var type, text, optional, 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') {
typeText.text = typeText.text || typeText.type;
delete typeText.type;
text = text || type.join('|');
type = [];
}
this.type = typeText.type;
this.text = trim(typeText.text);
if (type && type.length) {
this.type = type;
}
if (optional !== null) { this.poptional = optional; }
if (nullable !== null) { this.pnullable = nullable; }
this.text = text;
if (longTags.indexOf(this.name) > -1) { // is a tag that uses the long format
var [pname, pdesc] = splitPname(this.text);
var [pname, pdesc] = parsePname(this.text);
this.pname = pname;
this.pdesc = pdesc;
}
@ -85,55 +91,28 @@
/**
Split the parameter name and parameter desc from the tag text.
@private
@method splitPname
@method parsePname
@param {string} tagText
@returns Array.<string> The pname and the pdesc.
*/
function splitPname(tagText) {
function parsePname(tagText) {
tagText.match(/^(\S+)(\s+(\S.*))?$/);
return [RegExp.$1, RegExp.$3];
}
/**
Split the tag type and remaining tag text from the tag text.
@private
@method splitType
@param {string} tagText
@returns Object Like {type: tagType, text: tagText}
*/
function splitType(tagText) {
var type = '',
text = tagText,
count = 0;
// I reserve the right to use {@whatever ...} for something unrelated to type
if (tagText[0] === '{' && tagText[1] !== '@') {
count++;
for (var i = 1, leni = tagText.length; i < leni; i++) {
if (tagText[i] === '{') { count++; }
if (tagText[i] === '}') { count--; }
if (count === 0) {
type = trim(tagText.slice(1, i));
text = trim(tagText.slice(i+1));
break;
}
}
function synonym(name) {
if ( synonym.map.hasOwnProperty(name) ) {
return synonym.map[name];
}
else {
return name;
}
return { type: type, text: text };
}
/**
Remove leading and trailing whitespace.
@private
@method trim
@param {string} text
@returns {string}
*/
function trim(text) {
return text.replace(/^\s+|\s+$/g, '');
synonym.map = {
'description': 'desc',
'function': 'method',
'variable': 'member'
}
})();

View File

@ -14,12 +14,13 @@
load(BASEDIR + 'lib/jsunity.js');
testSuites = [];
load(BASEDIR + 'tests/opts.js');
load(BASEDIR + 'tests/docset.js');
load(BASEDIR + 'tests/tag_namespace.js');
load(BASEDIR + 'tests/opts.js');
load(BASEDIR + 'tests/docset.js');
load(BASEDIR + 'tests/tag_namespace.js');
load(BASEDIR + 'tests/tag_constructor.js');
load(BASEDIR + 'tests/tag_const.js');
load(BASEDIR + 'tests/tag_enum.js');
load(BASEDIR + 'tests/tag_const.js');
load(BASEDIR + 'tests/tag_enum.js');
load(BASEDIR + 'tests/tag_param.js');
jsUnity.attachAssertions();
jsUnity.log = function (s) { print(s); };

96
modules/jsdoc/type.js Normal file
View File

@ -0,0 +1,96 @@
/**
@overview
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
/**
Parse type expressions.
@module jsdoc/type
*/
(function() {
/**
@param {string} tagText
@returns {Array.<string>}
*/
exports.parse = function(tagText) {
if (typeof tagText !== 'string') { tagText = ''; }
var type = '',
types = [],
text = '',
count = 0;
// type expressions start with '{'
if (tagText[0] === '{') {
count++;
// find matching closer '}'
for (var i = 1, leni = tagText.length; i < leni; i++) {
if (tagText[i] === '{') { count++; }
else if (tagText[i] === '}') { count--; }
if (count === 0) {
type = trim(tagText.slice(1, i));
text = trim(tagText.slice(i+1));
break;
}
}
}
if (type === '') { text = tagText; }
[type, optional] = parseOptional(type);
[type, nullable] = parseNullable(type);
types = parseTypes(type); // make it into an array
return [types, text, optional, nullable];
}
function parseOptional(type) {
var optional = null;
// {sometype=} means optional
if ( /(.+)=$/.test(type) ) {
type = RegExp.$1;
optional = true;
}
return [type, optional];
}
function parseNullable(type) {
var nullable = null;
// {?sometype} means nullable, {!sometype} means not-nullable
if ( /^([\?\!])(.+)$/.test(type) ) {
type = RegExp.$2;
nullable = (RegExp.$1 === '?')? true : false;
}
return [type, nullable];
}
function parseTypes(type) {
var types = [];
if (type.indexOf('|') > -1) {
// remove optional parens
if ( /^\s*\(\s*(.+)\s*\)\s*$/.test(type) ) {
type = RegExp.$1;
}
types = type.split(/\s*\|\s*/g);
}
else {
types = [type];
}
return types;
}
/** @private */
function trim(text) {
return text.replace(/^\s+|\s+$/g, '');
}
})();

View File

@ -18,15 +18,15 @@
},
testConstCompactTag: function() {
var doc = docset.getDocsByPath('pi');
assertEqual(doc.length, 1, '1 doclet by that name is found.');
doc = doc[0];
var docs = docset.getDocsByPath('pi');
assertEqual(docs.length, 1, '1 doclet by that name is found.');
var doc = docs[0].toObject();
assertEqual(typeof doc, 'object', 'The found doclet is an object.');
assertEqual(doc.tagText('path'), 'pi', 'The found doclet has the expected path.');
assertEqual(doc.tagText('type'), 'number', 'The found doclet has the expected type.');
assertEqual(doc.tagText('desc'), "The ratio of any circle's circumference to its diameter.", 'The found doclet has the expected desc.');
assertEqual(doc.path, 'pi', 'The found doclet has the expected path.');
assertEqual(doc.type, 'number', 'The found doclet has the expected type.');
assertEqual(doc.desc, "The ratio of any circle's circumference to its diameter.", 'The found doclet has the expected desc.');
},
testConstCompactVerbose: function() {
@ -65,9 +65,7 @@ function sample() {
/**
* Euler's number.
* @const
* @name e
* @type number
* @const {number} e
*/
/**

105
tests/tag_param.js Normal file
View File

@ -0,0 +1,105 @@
(function() {
var jsdoc = { parser: require('jsdoc/parser') };
jsdoc.parser.parseFiles(BASEDIR + 'tests/tag_param.js');
var docset = jsdoc.parser.result;
var testSuite = {
suiteName: 'tag_param',
setUp: function() {
},
tearDown: function() {
},
testParamWithSimpleType: function() {
var docs = docset.getDocsByPath('Shape');
assertEqual(docs.length, 1, 'All constructor doclets by that path name are found.');
var doc = docs[0].toObject(),
params = doc.param;
//print('>>> doc is '+doc.toSource());
//print('>>> params is '+params.toSource());
assertEqual(params[0].name, 'top', 'The found parameter has the correct name.');
assertEqual(typeof params[0].type, 'object', 'The found parameter has types.');
assertEqual(params[0].type.length, 1, 'The found parameter has the correct number of types.');
assertEqual(params[0].type[0], 'number', 'The found parameter has the correct type value.');
},
testParamWithNullableType: function() {
var docs = docset.getDocsByPath('Shape');
assertEqual(docs.length, 1, 'All constructor doclets by that path name are found.');
var doc = docs[0].toObject(),
params = doc.param;
assertEqual(params[1].name, 'left', 'The found parameter has the correct name.');
assertEqual(typeof params[1].type, 'object', 'The found parameter has types.');
assertEqual(params[1].type.length, 1, 'The found parameter has the correct number of types.');
assertEqual(params[1].type[0], 'number', 'The found parameter has the correct type value.');
assertEqual(params[1].nullable, false, 'The found parameter has the correct !nullable value.');
assertEqual(params[2].nullable, true, 'The found parameter has the correct ?nullable value.');
},
testParamWithOptionalType: function() {
var docs = docset.getDocsByPath('Shape');
assertEqual(docs.length, 1, 'All doclets by that path name are found.');
var doc = docs[0].toObject(),
params = doc.param;
assertEqual(params[3].name, 'fixed', 'The found parameter has the correct name.');
assertEqual(typeof params[1].type, 'object', 'The found parameter has types.');
assertEqual(params[3].type.length, 1, 'The found parameter has the correct number of types.');
assertEqual(params[3].type[0], 'boolean', 'The found parameter has the correct type value.');
assertEqual(params[3].nullable, undefined, 'The found parameter has the default nullable value.');
assertEqual(params[3].optional, true, 'The found parameter has the correct optional value.');
},
testParamWithMultipleType: function() {
var docs = docset.getDocsByPath('rotate');
assertEqual(docs.length, 1, 'All doclets by that path name are found.');
var doc = docs[0].toObject(),
params = doc.param;
assertEqual(params[0].name, 'deg', 'The found parameter has the correct name.');
assertEqual(typeof params[0].type, 'object', 'The found parameter has types.');
assertEqual(params[0].type.length, 2, 'The found parameter has the correct number of types.');
assertEqual(params[0].type[0], 'Degree', 'The found parameter has the correct type[0] value.');
assertEqual(params[0].type[1], 'number', 'The found parameter has the correct type[1] value.');
assertEqual(params[1].name, 'axis', 'The found parameter has the correct name.');
assertEqual(params[1].optional, true, 'The found parameter has the correct optional.');
}
};
testSuites.push(testSuite);
})();
function sample() {
/** @constructor
@param {number} top
@param {!number} left
@param {?number} sides
@param {boolean=} fixed
*/
function Shape(top, left, sides, fixed) {
}
/** @method
@param {Degree|number} deg
@param [axis]
*/
function rotate(deg, axis) {
}
}