From cad6e9c01ac9c687c3960497b0f7f0ddcc433b69 Mon Sep 17 00:00:00 2001 From: Michael Mathews Date: Fri, 30 Jul 2010 21:38:31 +0100 Subject: [PATCH] Added support for @mixin tag. --- modules/jsdoc/doclet.js | 21 +++++++-- modules/jsdoc/name.js | 83 ++++++++++++++-------------------- modules/jsdoc/parser.js | 6 +-- modules/jsdoc/schema.js | 2 +- modules/jsdoc/tag.js | 14 ++++++ modules/jsdoc/tagdictionary.js | 8 ++++ test/samples/tag_name.js | 19 ++++++++ test/tests/08_tag_name.js | 26 +---------- 8 files changed, 98 insertions(+), 81 deletions(-) create mode 100644 test/samples/tag_name.js diff --git a/modules/jsdoc/doclet.js b/modules/jsdoc/doclet.js index a90a3617..bdccef7d 100644 --- a/modules/jsdoc/doclet.js +++ b/modules/jsdoc/doclet.js @@ -261,7 +261,7 @@ var name = '', taggedName = '', kind = '', - taggedIsa = '', + taggedKind = '', memberof = '', taggedMemberof = '', isFile = false, // TODO this should be handled by an event handler in tag dictionary @@ -300,7 +300,7 @@ if (kind && kind !== tags[i].value) { throw new DocTagConflictError('Symbol has too many isas, cannot be both: ' + kind + ' and ' + tags[i].value); } - taggedIsa = kind = tags[i].value; + taggedKind = kind = tags[i].value; } else if (tags[i].name === 'memberof') { if (memberof) { @@ -337,6 +337,19 @@ } } + if ( /^\s*(\S+)\s*=>\s*(\S+)/.test(taggedName) ) { + taggedName = RegExp.$1; + var refersto = RegExp.$2; + + tags.setTag('name', taggedName); + + taggedKind = 'mixin'; + tags.setTag('kind', taggedKind); + + tags.addTag('refersto', refersto); + + } + if (name && !taggedName) { tags.addTag('name', name); } @@ -345,7 +358,7 @@ tags.addTag('name', 'file:'+meta.file); } - if (kind && !taggedIsa) { + if (kind && !taggedKind) { tags.addTag('kind', kind); } @@ -354,7 +367,7 @@ } } - // now that we have a doclet object we can do some final adjustments + // now that we have a doclet object we can do some final adjustments function postprocess(doclet) { var tags = doclet.tags; diff --git a/modules/jsdoc/name.js b/modules/jsdoc/name.js index c96868d9..a5c3eccd 100644 --- a/modules/jsdoc/name.js +++ b/modules/jsdoc/name.js @@ -10,12 +10,11 @@ */ (function() { - var Token = Packages.org.mozilla.javascript.Token, - currentModule = ''; - - var jsdoc = { - tagDictionary: require('jsdoc/tagdictionary') - }; + var Token = Packages.org.mozilla.javascript.Token, + currentModule = '', + jsdoc = { + tagDictionary: require('jsdoc/tagdictionary') + }; exports.setCurrentModule = function(moduleName) { currentModule = moduleName; @@ -54,7 +53,7 @@ [prefix, scope, name] = exports.shorten(name); } else { // like @name bar, @memberof foo - if ( /([.~#])$/.test(memberof) ) { // like @memberof foo# or @memberof foo~ + if ( /([#.~])$/.test(memberof) ) { // like @memberof foo# or @memberof foo~ path = memberof + name; scope = RegExp.$1; doclet.setTag('scope', puncToScope[scope]); @@ -153,77 +152,65 @@ return [prefix, scope, name]; } - /** Given an AST node, return the path to the enclosing node. */ - function getEnclosingPath(node) { - var enclosingNode, - enclosingDoc; - - if (node.parent && node.parent.type === Token.OBJECTLIT) { - if (enclosingNode = node.parent) { - enclosingDoc = exports.docFromNode(enclosingNode); - } - } - else { - if ( enclosingNode = node.getEnclosingFunction() ) { - enclosingDoc = exports.docFromNode(enclosingNode); - } - } - - if (enclosingDoc) { - return (enclosingDoc.tagValue('path') || '').replace(/\.prototype\.?/g, '#'); - } + function docToPath(doclet, tagName) { + // TODO protect quoted parts of the path that may contain the string "prototype" + return (doclet.tagValue(tagName) || '').replace(/\.prototype\.?/g, '#'); } /** - Resolve how to document the `this.` portion of a symbol name. - */ - exports.resolveThis = function(name, node, doclet) { - + Apply information about how nested this AST node is to what we know about + the name. + */ + exports.resolvePath = function(name, node, doclet) { var enclosing, enclosingDoc, enclosingPath, - memberof = (doclet.tagValue('memberof') || '').replace(/\.prototype\.?/g, '#'); - + memberof; + + // documented member of an undocumented object literal? + // like foo = { /** a bar. */ bar: 1}; if (node.parent && node.parent.type === Token.OBJECTLIT) { - if ( enclosingPath = getEnclosingPath(node) ) { - name = enclosingPath + (/([#.~])$/.test(enclosingPath) ? '' : '.') + name; + if ( enclosingDoc = exports.docFromNode(node.parent) ) { + if ( enclosingPath = docToPath(enclosingDoc, 'path') ) { + name = enclosingPath + (/([#.~])$/.test(enclosingPath) ? '' : '.') + name; + } } } - else if (name.indexOf('this.') === 0) { + // what's all this then? + else if ( name.indexOf('this.') === 0 ) { + memberof = docToPath(doclet, 'memberof'); + + // need to examine the source code to determine the full path :( if (!memberof || memberof === 'this') { enclosing = node.getEnclosingFunction() - enclosingDoc = exports.docFromNode(enclosing); - if (enclosingDoc) { + if (enclosingDoc) { // documented enclosing symbol if (enclosingDoc.tagValue('scope') === 'inner') { - memberof = ''; // inner functions have `this` scope of global + memberof = ''; // inner functions always have `this` resolve to the global object } else { - memberof = enclosingDoc.tagValue('path'); + memberof = docToPath(enclosingDoc, 'path'); } } - else { - memberof = ''; - } - if (enclosing && !memberof) { - memberof = ''; // [[anonymousFunction]] + if (enclosing && !memberof) { // inside an anonymous function, this resolves to the global object + memberof = ''; name = name.slice(5); // remove `this.` } else if (!enclosing) { - memberof = ''; // [[globalObject]] + memberof = ''; // no enclosing function, this resolves to the global object } if (memberof || !enclosing) { // `this` refers to nearest non-inner member in the name path if (enclosingDoc && enclosingDoc.tagValue('kind') !== 'constructor') { - var parts = memberof.split(/[#~.]/); + var parts = memberof.split(/[#.~]/); var suffix = parts.pop(); memberof = memberof.slice(0, -suffix.length); // remove suffix from memberof } - var joiner = (memberof === '')? '' : (/[#~.]$/.test(memberof))? '' : '#'; + var joiner = (memberof === '')? '' : (/[#.~]$/.test(memberof))? '' : '#'; name = memberof + joiner + name.slice(5); // replace `this.` with memberof } } @@ -276,7 +263,7 @@ return null; } - // tuples, like [ [noderef, doclet], [noderef, doclet] ] + // a linking map, like [ [noderef, doclet], [noderef, doclet] ] exports.refs = []; function getTypeName(node) { diff --git a/modules/jsdoc/parser.js b/modules/jsdoc/parser.js index fba98cf4..17ed7137 100644 --- a/modules/jsdoc/parser.js +++ b/modules/jsdoc/parser.js @@ -62,7 +62,7 @@ // this thing may have commented members, so keep a ref to the thing but don't add it to the doclets list thisDoclet = jsdoc.doclet.makeDoclet('[[undocumented]]', node, currentSourceName); - nodeName = jsdoc.name.resolveThis(node.name, node, thisDoclet); + nodeName = jsdoc.name.resolvePath(node.name, node, thisDoclet); thisDoclet.setName(nodeName); jsdoc.name.refs.push([ node, @@ -97,7 +97,7 @@ } if (!thisDocletName) { // guess name from the source code - nodeName = jsdoc.name.resolveThis(nodeName, node, thisDoclet); + nodeName = jsdoc.name.resolvePath(nodeName, node, thisDoclet); thisDoclet.setName(nodeName); jsdoc.doclets.addDoclet(thisDoclet); @@ -109,7 +109,7 @@ // this thing may have commented members, so keep a ref to the thing but don't add it to the doclets list thisDoclet = jsdoc.doclet.makeDoclet('[[undocumented]]', node, currentSourceName); - nodeName = jsdoc.name.resolveThis(nodeName, node, thisDoclet); + nodeName = jsdoc.name.resolvePath(nodeName, node, thisDoclet); thisDoclet.setName(nodeName); jsdoc.name.refs.push([ diff --git a/modules/jsdoc/schema.js b/modules/jsdoc/schema.js index 566f9754..35e16a64 100644 --- a/modules/jsdoc/schema.js +++ b/modules/jsdoc/schema.js @@ -33,7 +33,7 @@ exports.jsdocSchema = { "kind": { "type": "string", "maxItems": 1, - "enum": ["constructor", "module", "event", "namespace", "method", "property", "enum", "class", "interface", "constant", "file", "version"] + "enum": ["constructor", "module", "event", "namespace", "method", "property", "enum", "class", "interface", "constant", "mixin", "file", "version"] }, "access": { "type": "string", diff --git a/modules/jsdoc/tag.js b/modules/jsdoc/tag.js index dfcf57a6..5fce4c66 100644 --- a/modules/jsdoc/tag.js +++ b/modules/jsdoc/tag.js @@ -135,6 +135,19 @@ return false; } + function setTag(tagName, tagValue) { + var i = this.length; + while(i--) { + if (this[i].name === tagName) { + this[i].value = tagValue; + return true; + } + } + + this.addTag(tagName, tagValue); + return false; + } + /** Given the source of a jsdoc comment, finds the tags. @private @@ -146,6 +159,7 @@ var tags = []; tags.addTag = addTag; tags.hasTag = hasTag; + tags.setTag = setTag; // split out the basic tags, keep surrounding whitespace commentSrc diff --git a/modules/jsdoc/tagdictionary.js b/modules/jsdoc/tagdictionary.js index 468a2f46..90f32002 100644 --- a/modules/jsdoc/tagdictionary.js +++ b/modules/jsdoc/tagdictionary.js @@ -466,4 +466,12 @@ new TagDefinition('see', { isExported: true }); + + /** Syntax: @refersto + @property {module:jsdoc/tagdictionary~TagDefinition} refersto + @memberOf module:jsdoc/tagdictionary~tagDefinitions + */ + new TagDefinition('refersto', { + isExported: true + }); })(); \ No newline at end of file diff --git a/test/samples/tag_name.js b/test/samples/tag_name.js new file mode 100644 index 00000000..24b3a7e2 --- /dev/null +++ b/test/samples/tag_name.js @@ -0,0 +1,19 @@ +/** + @name Tipsy + @kind property +*/ + +/** + @name Tubbie.LaLa + @kind property +*/ + +/** + @name Tubbie."and.don't.forget#Po!" + @kind property +*/ + +/** + @name Custards.0 + @kind property +*/ \ No newline at end of file diff --git a/test/tests/08_tag_name.js b/test/tests/08_tag_name.js index d7e13e19..799b7432 100644 --- a/test/tests/08_tag_name.js +++ b/test/tests/08_tag_name.js @@ -10,7 +10,7 @@ tag: require('jsdoc/tag'), parser: require('jsdoc/parser') }; - jsdoc.parser.parseFiles(BASEDIR + 'test/tests/08_tag_name.js'); + jsdoc.parser.parseFiles(BASEDIR + 'test/samples/tag_name.js'); doclets = jsdoc.parser.result; }); @@ -47,27 +47,3 @@ }); }); })(); - -(function testarea() { - - /** - @name Tipsy - @kind property - */ - - /** - @name Tubbie.LaLa - @kind property - */ - - /** - @name Tubbie."and.don't.forget#Po!" - @kind property - */ - - /** - @name Custards.0 - @kind property - */ - -})(); \ No newline at end of file