diff --git a/modules/jsdoc/doclet.js b/modules/jsdoc/doclet.js index 94ce356e..43b03fc3 100644 --- a/modules/jsdoc/doclet.js +++ b/modules/jsdoc/doclet.js @@ -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. + */ 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. + */ 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) { diff --git a/modules/jsdoc/name.js b/modules/jsdoc/name.js index 9eca76ca..bfd7c352 100644 --- a/modules/jsdoc/name.js +++ b/modules/jsdoc/name.js @@ -24,7 +24,7 @@ parentDoc; name = name? (''+name).replace(/\.prototype\.?/g, '#') : ''; - + // member of a var in an outer scope? if (name && !memberof && doclet.meta.code && doclet.meta.code.funcscope) { name = doclet.longname = doclet.meta.code.funcscope + '~' + name; @@ -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 diff --git a/modules/jsdoc/src/handlers.js b/modules/jsdoc/src/handlers.js index 20cc02de..ec67752e 100644 --- a/modules/jsdoc/src/handlers.js +++ b/modules/jsdoc/src/handlers.js @@ -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 } @@ -36,13 +38,13 @@ function newSymbolDoclet(docletSrc, e) { var newDoclet = new jsdoc.doclet.Doclet(docletSrc, e); - + // an undocumented symbol right after a virtual comment? rhino mistakenly connected the two - if (newDoclet.name) { // there was a @name in comment - // try again, without the comment - e.comment = '@undocumented'; - newDoclet = new jsdoc.doclet.Doclet(e.comment, e); - } + if (newDoclet.name) { // there was a @name in comment + // try again, without the comment + e.comment = '@undocumented'; + newDoclet = new jsdoc.doclet.Doclet(e.comment, e); + } if (newDoclet.alias) { newDoclet.addTag('name', newDoclet.alias); @@ -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) { - this.addResult(newDoclet); + 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; + } } })(); \ No newline at end of file diff --git a/modules/jsdoc/src/parser.js b/modules/jsdoc/src/parser.js index a30f75c7..daa6b722 100644 --- a/modules/jsdoc/src/parser.js +++ b/modules/jsdoc/src/parser.js @@ -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} 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 ***/ } -})(); \ No newline at end of file +})(); + +/** + 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. + */ \ No newline at end of file diff --git a/modules/jsdoc/src/scanner.js b/modules/jsdoc/src/scanner.js index df6a92a0..9f2fdb01 100644 --- a/modules/jsdoc/src/scanner.js +++ b/modules/jsdoc/src/scanner.js @@ -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. diff --git a/modules/jsdoc/tag/dictionary/definitions.js b/modules/jsdoc/tag/dictionary/definitions.js index f5e0557d..4e33e3b8 100644 --- a/modules/jsdoc/tag/dictionary/definitions.js +++ b/modules/jsdoc/tag/dictionary/definitions.js @@ -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', { diff --git a/modules/underscore/template.js b/modules/underscore/template.js new file mode 100644 index 00000000..4f911b6b --- /dev/null +++ b/modules/underscore/template.js @@ -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; +}; \ No newline at end of file diff --git a/test/cases/constructstag.js b/test/cases/constructstag.js index 3e19a8ae..a31a1cd0 100644 --- a/test/cases/constructstag.js +++ b/test/cases/constructstag.js @@ -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 */ @@ -17,6 +14,6 @@ Classify('TextBlock', { Document your method here. @memberof TextBlock# */ - align: function() { - } + align: function() { + } }); \ No newline at end of file diff --git a/test/cases/constructstag2.js b/test/cases/constructstag2.js index f539d16e..604d4bad 100644 --- a/test/cases/constructstag2.js +++ b/test/cases/constructstag2.js @@ -1,7 +1,3 @@ -/** - Describe the class here. - @class Menu - */ Classify('Menu', /** @constructs Menu diff --git a/test/cases/innerscope.js b/test/cases/innerscope.js index 6a623f34..5d2d5f5c 100644 --- a/test/cases/innerscope.js +++ b/test/cases/innerscope.js @@ -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 = ''; })() diff --git a/test/cases/lends.js b/test/cases/lends.js new file mode 100644 index 00000000..6d86ae6d --- /dev/null +++ b/test/cases/lends.js @@ -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; + } + } +); \ No newline at end of file diff --git a/test/cases/lends2.js b/test/cases/lends2.js new file mode 100644 index 00000000..d2dd2207 --- /dev/null +++ b/test/cases/lends2.js @@ -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; + } + } +); \ No newline at end of file diff --git a/test/cases/memberoftag2.js b/test/cases/memberoftag2.js new file mode 100644 index 00000000..b926dc6a --- /dev/null +++ b/test/cases/memberoftag2.js @@ -0,0 +1,10 @@ +create( + 'Observable', + { + /** @memberof Observable */ + cache: [], + + /** @memberof Observable.prototype */ + publish: function(msg) {} + } +); \ No newline at end of file diff --git a/test/runner.js b/test/runner.js index d17dfead..3a608af9 100644 --- a/test/runner.js +++ b/test/runner.js @@ -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'); diff --git a/test/t/cases/borrowstag2.js b/test/t/cases/borrowstag2.js index e7fcd17d..151d5f0a 100644 --- a/test/t/cases/borrowstag2.js +++ b/test/t/cases/borrowstag2.js @@ -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); }); })(); \ No newline at end of file diff --git a/test/t/cases/constructstag.js b/test/t/cases/constructstag.js index 98baad9b..ec8c9d61 100644 --- a/test/t/cases/constructstag.js +++ b/test/t/cases/constructstag.js @@ -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 tag, the doclet has the longname of ".constructor".', function() { - assert.equal(textblockConstructor.name, 'constructor'); - assert.equal(textblockConstructor.memberof, 'TextBlock'); - assert.equal(textblockConstructor.kind, 'function'); + assert.equal(textblock.longname, 'TextBlock'); }); })(); \ No newline at end of file diff --git a/test/t/cases/constructstag2.js b/test/t/cases/constructstag2.js index ca338542..2cd8e5fc 100644 --- a/test/t/cases/constructstag2.js +++ b/test/t/cases/constructstag2.js @@ -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 tag, the doclet has the longname of ".constructor".', function() { - assert.equal(menuConstructor.name, 'constructor'); - assert.equal(menuConstructor.memberof, 'Menu'); - assert.equal(menuConstructor.kind, 'function'); - }); })(); \ No newline at end of file diff --git a/test/t/cases/innerscope.js b/test/t/cases/innerscope.js index df8ef3ce..8e6bee7d 100644 --- a/test/t/cases/innerscope.js +++ b/test/t/cases/innerscope.js @@ -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.'); }); diff --git a/test/t/cases/lends.js b/test/t/cases/lends.js new file mode 100644 index 00000000..e745d432 --- /dev/null +++ b/test/t/cases/lends.js @@ -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.'); + }); + +})(); \ No newline at end of file diff --git a/test/t/cases/memberoftag2.js b/test/t/cases/memberoftag2.js new file mode 100644 index 00000000..dc40e577 --- /dev/null +++ b/test/t/cases/memberoftag2.js @@ -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'); + }); +})(); \ No newline at end of file