Fixes for var scoping, @lends and @borrows.

git-svn-id: https://jsdoc.googlecode.com/svn/trunk/@17 d5942f49-e6af-b5c1-9d01-85772c7ca168
This commit is contained in:
micmath 2011-02-24 17:11:05 +00:00
parent b70a9bf0ef
commit 81cc2e4939
20 changed files with 235 additions and 72 deletions

View File

@ -79,15 +79,21 @@
@param {string} sid - The longname of the symbol that this doclet is a member of.
*/
exports.Doclet.prototype.setMemberof = function(sid) {
/** The symbol that contains this one, if any. */
this.memberof = sid;
/**
The longname of the symbol that contains this one, if any.
@type string
*/
this.memberof = sid.replace(/\.prototype/g, '#');
}
/** Set the `longname` property of this doclet.
@param {string} name
*/
exports.Doclet.prototype.setLongname = function(name) {
/** The fully resolved symbol name. */
/**
The fully resolved symbol name.
@type string
*/
this.longname = name;
if (jsdoc.tag.dictionary.isNamespace(this.kind)) {
this.longname = jsdoc.name.applyNamespace(this.longname, this.kind);
@ -99,11 +105,17 @@
@param {string} target - The name the symbol is being assigned to.
*/
exports.Doclet.prototype.borrow = function(source, target) {
var about = {from: source};
if (target) about.as = target;
if (!this.borrowed) {
/** A list of symbols that are borrowed by this one, if any. */
/**
A list of symbols that are borrowed by this one, if any.
@type Array.<string>
*/
this.borrowed = [];
}
this.borrowed.push( {from: source, as: (target||source)} );
this.borrowed.push(about);
}
/** Add a symbol to this doclet's `augments` array.
@ -111,13 +123,17 @@
*/
exports.Doclet.prototype.augment = function(base) {
if (!this.augments) {
/** A list of symbols that are augmented by this one, if any. */
/**
A list of symbols that are augmented by this one, if any.
@type Array.<string>
*/
this.augments = [];
}
this.augments.push(base);
}
/** Set the `meta` property of this doclet.
/**
Set the `meta` property of this doclet.
@param {object} meta
*/
exports.Doclet.prototype.setMeta = function(meta) {

View File

@ -34,13 +34,13 @@
memberof = memberof.replace(/\.prototype\.?/g, '#');
// the name is a fullname, like @name foo.bar, @memberof foo
if (name.indexOf(memberof) === 0) {
if (name && name.indexOf(memberof) === 0) {
about = exports.shorten(name);
}
else if ( /([#.~])$/.test(memberof) ) { // like @memberof foo# or @memberof foo~
else if (name && /([#.~])$/.test(memberof) ) { // like @memberof foo# or @memberof foo~
about = exports.shorten(memberof + name);
}
else if ( doclet.scope ) { // like @memberof foo# or @memberof foo~
else if (name && doclet.scope ) { // like @memberof foo# or @memberof foo~
about = exports.shorten(memberof + scopeToPunc[doclet.scope] + name);
}
}
@ -52,8 +52,6 @@
doclet.name = about.name;
}
if (about.memberof) {
doclet.setMemberof(about.memberof);
}
@ -80,6 +78,8 @@
if (about.variation) {
doclet.variation = about.variation;
}
//dump('doclet', doclet);
}
function quoteUnsafe(name, kind) { // docspaced names may have unsafe characters which need to be quoted by us

View File

@ -7,6 +7,7 @@
/**
Attach these event handlers to a particular instance of a parser.
@param parser
*/
exports.attachTo = function(parser) {
var jsdoc = {doclet: require('jsdoc/doclet')};
@ -14,6 +15,7 @@
// handles JSDoc comments that include a @name tag -- the code is ignored in such a case
parser.on('jsdocCommentFound', function(e) {
var newDoclet = new jsdoc.doclet.Doclet(e.comment, e);
if (!newDoclet.name) {
return false; // only interested in virtual comments (with a @name) here
}
@ -50,7 +52,6 @@
}
else if (e.code && e.code.name) { // we need to get the symbol name from code
newDoclet.addTag('name', e.code.name);
if (!newDoclet.memberof && e.astnode) {
var memberofName,
scope;
@ -114,9 +115,20 @@
this.fire('newDoclet', e);
if (!e.defaultPrevented) {
if ( !filter(newDoclet) ) {
this.addResult(newDoclet);
}
}
}
}
function filter(doclet) {
// you can't document prototypes
if ( /#$/.test(doclet.longname) ) return true;
// you can't document symbols added by the parser with a dummy name
if (doclet.meta.code && doclet.meta.code.name === '____') return true;
return false;
}
}
})();

View File

@ -12,7 +12,7 @@
/**
* @class
* @mixes module:common/events
* @mixes module:common/events.*
*
* @example
* var jsdocParser = new (require('jsdoc/src/parser').Parser)();
@ -24,6 +24,7 @@
require('common/util').mixin(exports.Parser.prototype, require('common/events'));
/**
* Parse the given source files for JSDoc comments.
* @param {Array<string>} sourceFiles
* @param {string} [encoding=utf8]
*
@ -76,7 +77,7 @@
}
/**
* @param {Object} The parse result to add to the result buffer.
* @param {Object} o The parse result to add to the result buffer.
*/
exports.Parser.prototype.addResult = function(o) {
this._resultBuffer.push(o);
@ -95,8 +96,7 @@
exports.Parser.prototype._parseSourceCode = function(sourceCode, sourceName) {
currentSourceName = sourceName;
// merge adjacent doclets
sourceCode = sourceCode.replace(/\*\/\/\*\*+/g, '@also');
sourceCode = pretreat(sourceCode);
var ast = parserFactory().parse(sourceCode, sourceName, 1);
@ -116,6 +116,14 @@
currentSourceName = '';
}
function pretreat(code) {
return code
// merge adjacent doclets
.replace(/\*\/\/\*\*+/g, '@also')
// make lent objectliterals documentable by giving them a dummy name
.replace(/(\/\*\*[\s\S]*@lends\b[\s\S]*\*\/\s*)\{/g, '$1____ = {');
}
/**
* Given a node, determine what the node is a member of.
* @param {astnode} node
@ -204,7 +212,7 @@
doclet = this.refs['astnode'+enclosingFunction.hashCode()];
if ( doclet && doclet.vars && ~doclet.vars.indexOf(basename) ) {
if ( doclet && doclet.meta.vars && ~doclet.meta.vars.indexOf(basename) ) {
return doclet.longname;
}
@ -303,8 +311,8 @@
funcDoc = currentParser.refs[func];
if (funcDoc) {
funcDoc.vars = func.vars || [];
funcDoc.vars.push(e.code.name);
funcDoc.meta.vars = funcDoc.meta.vars || [];
funcDoc.meta.vars.push(e.code.name);
}
}
@ -362,6 +370,7 @@
/**
* Attempts to find the name and type of the given node.
* @private
* @memberof module:src/parser.Parser
*/
function aboutNode(node) {
about = {};
@ -408,7 +417,9 @@
return about;
}
/** @private */
/** @private
@memberof module:src/parser.Parser
*/
function nodeToString(node) {
var str;
@ -442,7 +453,9 @@
return '' + str;
};
/** @private */
/** @private
@memberof module:src/parser.Parser
*/
function getTypeName(node) {
var type = '';
@ -453,9 +466,21 @@
return type;
}
/** @private */
/** @private
@memberof module:src/parser.Parser
*/
function isValidJsdoc(commentSrc) {
return commentSrc.indexOf('/***') !== 0; /*** ignore comments that start with many stars ***/
}
})();
/**
Fired whenever the parser encounters a JSDoc comment in the current source code.
@event jsdocCommentFound
@memberof module:jsdoc/src/parser.Parser
@param e
@param e.comment The text content of the JSDoc comment
@param e.lineno The line number associated with the found comment.
@param e.filename The file name associated with the found comment.
*/

View File

@ -15,10 +15,11 @@
/**
@constructor
@mixes module:common.events.*
*/
var Scanner = exports.Scanner = function() {
exports.Scanner = function() {
}
common.mixin(Scanner.prototype, common.events);
common.mixin(exports.Scanner.prototype, common.events);
/**
Recursively searches the given searchPaths for js files.

View File

@ -26,7 +26,8 @@
onTagged: function(doclet, tag) {
doclet.alias = tag.value;
}
});
})
.synonym('lends');
dictionary.defineTag('author', {
mustHaveValue: true,
@ -93,11 +94,12 @@
});
dictionary.defineTag('constructs', {
mustHaveValue: true,
onTagged: function(doclet, tag) {
var ownerClassName = firstWordOf(tag.value);
doclet.addTag('alias', ownerClassName + '.constructor');
doclet.addTag('memberof', ownerClassName);
doclet.addTag('kind', 'function');
doclet.addTag('alias', ownerClassName/* + '.constructor'*/);
//doclet.addTag('memberof', ownerClassName);
doclet.addTag('kind', 'class');
}
});
@ -188,7 +190,11 @@
.synonym('overview');
dictionary.defineTag('fires', {
mustHaveValue: true
mustHaveValue: true,
onTagged: function(doclet, tag) {
if (!doclet.fires) { doclet.fires = []; }
doclet.fires.push(tag.value);
}
});
dictionary.defineTag('function', {

View File

@ -0,0 +1,34 @@
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
exports.settings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g
};
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
exports.render = function(templateStr, data) {
var settings = exports.settings,
compiledTemplate,
renderFunction;
compiledTemplate = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
'with(data||{}){__p.push(\'' +
templateStr.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(settings.interpolate, function(match, code) {
return "'," + code.replace(/\\'/g, "'") + ",'";
})
.replace(settings.evaluate || null, function(match, code) {
return "');" + code.replace(/\\'/g, "'")
.replace(/[\r\n\t]/g, ' ') + "__p.push('";
})
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
.replace(/\t/g, '\\t')
+ "');}return __p.join('');";
renderFunction = new Function('data', compiledTemplate);
return data ? renderFunction(data) : renderFunction;
};

View File

@ -1,12 +1,9 @@
/**
Describe your class here
@class TextBlock
*/
Classify('TextBlock', {
/**
Document your constructor function here.
@constructs TextBlock
@classdesc Describe your class here
@param {object} opts
@throws MissingNode
*/

View File

@ -1,7 +1,3 @@
/**
Describe the class here.
@class Menu
*/
Classify('Menu',
/**
@constructs Menu

View File

@ -1,12 +1,16 @@
/** @constructor */
function Message(to) {
var headers = {};
var headers = {},
response;
/** document me */
headers.to = to;
(function() {
/** document me */
response.code = '200';
/** document me */
headers.from = '';
})()

16
test/cases/lends.js Normal file
View File

@ -0,0 +1,16 @@
/** @class */
var Person = makeClass(
/** @lends Person# */
{
/** Set up initial values. */
initialize: function(name) {
/** The name of the person. */
this.name = name;
},
/** Speak a message. */
say: function(message) {
return this.name + " says: " + message;
}
}
);

16
test/cases/lends2.js Normal file
View File

@ -0,0 +1,16 @@
var Person = makeClass(
/** @lends Person# */
{
/** @constructs Person */
initialize: function(name) {
/** The name of the person. */
this.name = name;
},
/** Speak a message. */
say: function(message) {
return this.name + " says: " + message;
}
}
);

View File

@ -0,0 +1,10 @@
create(
'Observable',
{
/** @memberof Observable */
cache: [],
/** @memberof Observable.prototype */
publish: function(msg) {}
}
);

View File

@ -113,7 +113,9 @@ testFile('test/t/cases/exportstag3.js');
testFile('test/t/cases/exceptiontag.js');
testFile('test/t/cases/globaltag.js');
testFile('test/t/cases/ignoretag.js');
testFile('test/t/cases/lends.js');
testFile('test/t/cases/memberoftag.js');
testFile('test/t/cases/memberoftag2.js');
testFile('test/t/cases/moduletag.js');
testFile('test/t/cases/paramtag.js');
testFile('test/t/cases/privatetag.js');

View File

@ -4,11 +4,10 @@
return ! $.undocumented;
})[0];
//dump(found);
//dump(str); exit();
test('When a symbol has a @borrows tag, that is added to the symbol\'s "borrowed" property and the from is the same as the as property.', function() {
assert.equal(str.borrowed.length, 1);
assert.equal(str.borrowed[0].from, 'rtrim');
assert.equal(str.borrowed[0].as, str.borrowed[0].from);
});
})();

View File

@ -1,18 +1,11 @@
(function() {
var docSet = testhelpers.getDocSetFromFile('test/cases/constructstag.js'),
textblock = docSet.getByLongname('TextBlock')[0],
textblockConstructor = docSet.getByLongname('TextBlock.constructor')[0];
textblock = docSet.getByLongname('TextBlock')[0];
//dump(docSet.doclets); exit(0);
test('When a symbol has an @class tag, it is documented as a class.', function() {
assert.equal(textblock.name, 'TextBlock');
test('When a symbol has an @constructs tag, it is documented as a class with that name.', function() {
assert.equal(textblock.kind, 'class');
});
test('When a symbol has an @constructs <someClass> tag, the doclet has the longname of "<someClass>.constructor".', function() {
assert.equal(textblockConstructor.name, 'constructor');
assert.equal(textblockConstructor.memberof, 'TextBlock');
assert.equal(textblockConstructor.kind, 'function');
assert.equal(textblock.longname, 'TextBlock');
});
})();

View File

@ -1,18 +1,12 @@
(function() {
var docSet = testhelpers.getDocSetFromFile('test/cases/constructstag2.js'),
menu = docSet.getByLongname('Menu')[0],
menuConstructor = docSet.getByLongname('Menu.constructor')[0];
menu = docSet.getByLongname('Menu')[0];
//dump(docSet.doclets); exit(0);
test('When a symbol has an @class tag, it is documented as a class.', function() {
test('When a symbol has an @constructs tag, it is documented as a class.', function() {
assert.equal(menu.name, 'Menu');
assert.equal(menu.kind, 'class');
});
test('When an anonymous function symbol has an @constructs <someClass> tag, the doclet has the longname of "<someClass>.constructor".', function() {
assert.equal(menuConstructor.name, 'constructor');
assert.equal(menuConstructor.memberof, 'Menu');
assert.equal(menuConstructor.kind, 'function');
});
})();

View File

@ -1,14 +1,19 @@
(function() {
var docSet = testhelpers.getDocSetFromFile('test/cases/innerscope.js'),
to = docSet.getByLongname('Message~headers.to'),
from = docSet.getByLongname('Message~headers.from');
from = docSet.getByLongname('Message~headers.from'),
response = docSet.getByLongname('Message~response.code');
//dump(docSet);
//dump(docSet); exit();
test('When a member of a var member is documented.', function() {
assert.equal(to.length, 1, 'It is like Outer~inner.member.');
});
test('When a second member of a var member is documented.', function() {
assert.equal(response.length, 1, 'It is like Outer~inner.member.');
});
test('When a deeply nested member of a var member is documented.', function() {
assert.equal(from.length, 1, 'It is still like Outer~inner.member.');
});

14
test/t/cases/lends.js Normal file
View File

@ -0,0 +1,14 @@
(function() {
var docSet = testhelpers.getDocSetFromFile('test/cases/lends.js'),
init = docSet.getByLongname('Person#initialize'),
say = docSet.getByLongname('Person#say'),
name = docSet.getByLongname('Person#say');
//dump(docSet);
test('When a documented member is inside an objlit associated with a @lends tag.', function() {
assert.equal(init.length, 1, 'The member should be documented as a member of the lendee.');
assert.equal(name.length, 1, 'The this member should be documented as a member of the lendee.');
});
})();

View File

@ -0,0 +1,23 @@
(function() {
var docSet = testhelpers.getDocSetFromFile('test/cases/memberoftag2.js'),
publish = docSet.getByLongname('Observable#publish')[0],
cache = docSet.getByLongname('Observable.cache')[0];
//dump(docSet.doclets); exit(0);
test('A symbol is documented as a static @memberof a class.', function() {
assert.equal(typeof cache, 'object', 'it should appear as a static member of that class.');
assert.equal(cache.memberof, 'Observable');
assert.equal(cache.scope, 'static');
assert.equal(cache.name, 'cache');
assert.equal(cache.longname, 'Observable.cache');
});
test('A symbol is documented as a static @memberof a class prototype.', function() {
assert.equal(typeof publish, 'object', 'it should appear as an instance member of that class.');
assert.equal(publish.memberof, 'Observable');
assert.equal(publish.scope, 'instance');
assert.equal(publish.name, 'publish');
assert.equal(publish.longname, 'Observable#publish');
});
})();