diff --git a/lib/jsdoc/augment.js b/lib/jsdoc/augment.js index 2c4d0392..60756340 100644 --- a/lib/jsdoc/augment.js +++ b/lib/jsdoc/augment.js @@ -159,6 +159,7 @@ function explicitlyInherits(doclets) { return inherits; } +// TODO: try to reduce overlap with similar methods function getInheritedAdditions(doclets, docs, documented) { var additionIndexes; var additions = []; @@ -276,7 +277,7 @@ function updateMixes(mixedDoclet, mixedLongname) { } } -// TODO: try to reduce overlap with getInheritedAdditions +// TODO: try to reduce overlap with similar methods function getMixedInAdditions(mixinDoclets, allDoclets, commentedDoclets) { var additionIndexes; var additions = []; @@ -322,6 +323,99 @@ function getMixedInAdditions(mixinDoclets, allDoclets, commentedDoclets) { return additions; } +function updateImplements(implDoclets, implementedLongname) { + if ( !Array.isArray(implDoclets) ) { + implDoclets = [implDoclets]; + } + + implDoclets.forEach(function(implDoclet) { + if ( !hasOwnProp.call(implDoclet, 'implements') ) { + implDoclet.implements = []; + } + + implDoclet.implements.push(implementedLongname); + }); +} + +// TODO: try to reduce overlap with similar methods +function getImplementedAdditions(implDoclets, allDoclets, commentedDoclets) { + var additionIndexes; + var additions = []; + var doclet; + var idx; + var implementations; + var implExists; + var implementationDoclet; + var interfaceDoclets; + + // interfaceDoclets will be undefined if the implemented symbol isn't documented + implDoclets = implDoclets || []; + + for (var i = 0, ii = implDoclets.length; i < ii; i++) { + doclet = implDoclets[i]; + implementations = doclet.implements; + + if (implementations) { + // reset the lookup table of added doclet indexes by longname + additionIndexes = {}; + + for (var j = 0, jj = implementations.length; j < jj; j++) { + interfaceDoclets = getMembers(implementations[j], allDoclets, ['instance']); + + for (var k = 0, kk = interfaceDoclets.length; k < kk; k++) { + implementationDoclet = doop(interfaceDoclets[k]); + + reparentDoclet(doclet, implementationDoclet); + updateImplements(implementationDoclet, interfaceDoclets[k].longname); + + // If there's no implementation, move along. + implExists = hasOwnProp.call(allDoclets.index.longname, + implementationDoclet.longname); + if (!implExists) { + continue; + } + + // Add the interface's docs unless the implementation is already documented. + if ( !hasOwnProp.call(commentedDoclets, implementationDoclet.longname) ) { + updateAddedDoclets(implementationDoclet, additions, additionIndexes); + } + // If the implementation used an @inheritdoc or @override tag, add the + // interface's docs, and ignore the existing doclets. + else if ( explicitlyInherits(commentedDoclets[implementationDoclet.longname]) ) { + // Ignore any existing doclets. (This is safe because we only get here if + // `implementationDoclet.longname` is an own property of + // `commentedDoclets`.) + addDocletProperty(commentedDoclets[implementationDoclet.longname], 'ignore', + true); + + updateAddedDoclets(implementationDoclet, additions, additionIndexes); + + // Remove property that's no longer accurate. + if (implementationDoclet.virtual) { + delete implementationDoclet.virtual; + } + // Remove properties that we no longer need. + if (implementationDoclet.inheritdoc) { + delete implementationDoclet.inheritdoc; + } + if (implementationDoclet.override) { + delete implementationDoclet.override; + } + } + // If there's an implementation, and it's documented, update the doclets to + // indicate what the implementation is implementing. + else { + updateImplements(commentedDoclets[implementationDoclet.longname], + interfaceDoclets[k].longname); + } + } + } + } + } + + return additions; +} + function augment(doclets, propertyName, docletFinder) { var index = doclets.index.longname; var dependencies = sort( mapDependencies(index, propertyName) ); @@ -389,94 +483,8 @@ exports.addMixedIn = function(doclets) { * @param {!Object} doclets.index - The doclet index added by {@link module:jsdoc/borrow.indexAll}. * @return {void} */ -exports.addImplemented = function(docs) { - var docMap = {}; - var interfaces = {}; - var implemented = {}; - var memberInfo = {}; - - docs.forEach(function(doc) { - var memberof = doc.memberof || doc.name; - - if (!hasOwnProp.call(docMap, memberof)) { - docMap[memberof] = []; - } - docMap[memberof].push(doc); - - if (doc.kind === 'interface') { - interfaces[doc.longname] = doc; - } - else if (doc.implements && doc.implements.length) { - if (!hasOwnProp.call(implemented, doc.memberof)) { - implemented[memberof] = []; - } - implemented[memberof].push(doc); - } - }); - - // create an dictionary of interface doclets - Object.keys(interfaces).forEach(function(ifaceName) { - var iface = interfaces[ifaceName]; - if (hasOwnProp.call(docMap, iface.name)) { - docMap[iface.name].forEach(function(doc) { - var members = memberInfo[doc.memberof]; - - if (!members) { - members = memberInfo[doc.memberof] = {}; - } - members[doc.name] = doc; - }); - } - }); - - Object.keys(implemented).forEach(function(key) { - // implemented classes namespace. - var owner = implemented[key]; - - owner.forEach(function(klass) { - // class's interfaces - klass.implements.forEach(function(impl) { - var interfaceMember; - var interfaceMembers = memberInfo[impl]; - var member; - var members; - - // mark the interface as being implemented by the class - if (hasOwnProp.call(interfaces, impl)) { - interfaces[impl].implementations = interfaces[impl].implementations || []; - interfaces[impl].implementations.push(klass.longname); - } - - // if the interface has no members, skip to the next owner - if (!interfaceMembers) { - return; - } - - if (!hasOwnProp.call(docMap, klass.longname)) { - docMap[klass.longname] = []; - } - members = docMap[klass.longname]; - - for (var i = 0, len = members.length; i < len; i++) { - member = members[i]; - interfaceMember = interfaceMembers && interfaceMembers[member.name]; - - // if we didn't find the member name in the interface, skip to the next member - if (!interfaceMember) { - continue; - } - - // mark members that implement an interface - member.implements = member.implements || []; - member.implements.push(interfaceMember.longname); - - // mark interface members that the symbol implements - interfaceMember.implementations = interfaceMember.implementations || []; - interfaceMember.implementations.push(member.longname); - } - }); - }); - }); +exports.addImplemented = function(doclets) { + augment(doclets, 'implements', getImplementedAdditions); }; /** diff --git a/test/specs/tags/implementstag.js b/test/specs/tags/implementstag.js index 474d2728..94a94374 100644 --- a/test/specs/tags/implementstag.js +++ b/test/specs/tags/implementstag.js @@ -6,6 +6,9 @@ describe('@implements tag', function() { var myTester = docSet.getByLongname('MyTester')[0]; var myIncompleteWorker = docSet.getByLongname('MyWorker')[0]; var beforeEachMethod = docSet.getByLongname('MyTester#beforeEach')[0]; + var itMethod = docSet.getByLongname('MyTester#it').filter(function($) { + return !$.undocumented; + })[0]; var processMethod = docSet.getByLongname('MyWorker#process')[0]; it('MyTester has an "implements" array', function() { @@ -20,6 +23,10 @@ describe('@implements tag', function() { expect(beforeEachMethod.implements[0]).toBe('ITester#beforeEach'); }); + it('MyTester#it inherits the docs from ITester#it', function() { + expect(itMethod.description).toBe('it method.'); + }); + it('MyWorker\'s process() method does not implement an interface', function() { expect(processMethod.implements).toBeUndefined(); });