make implementations inherit the docs from interfaces where appropriate (#864)

This commit is contained in:
Jeff Williams 2015-01-20 08:10:33 -08:00
parent 16b8aa0a73
commit 3663224f2b
2 changed files with 104 additions and 89 deletions

View File

@ -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);
};
/**

View File

@ -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();
});