diff --git a/lib/jsdoc/doclet.js b/lib/jsdoc/doclet.js index 9fa58280..cb05e721 100644 --- a/lib/jsdoc/doclet.js +++ b/lib/jsdoc/doclet.js @@ -468,18 +468,7 @@ Doclet.prototype.setMeta = function(meta) { } }; -/** - * Extend a destination object with properties from the source object, ignoring properties that - * should be excluded. - * - * @private - * @param {Object} source - The source object. - * @param {Object} destination - The destination object. - * @param {Array.} exclude - The names of properties to exclude from copying. - */ -function extend(source, destination, exclude) { - var properties = _.difference(Object.getOwnPropertyNames(source), exclude); - +function dooper(source, target, properties) { properties.forEach(function(property) { switch (typeof source[property]) { case 'function': @@ -487,57 +476,86 @@ function extend(source, destination, exclude) { break; case 'object': - destination[property] = jsdoc.util.doop(source[property]); + target[property] = jsdoc.util.doop(source[property]); break; default: - destination[property] = source[property]; + target[property] = source[property]; } }); } /** - * Extend a destination doclet with the specified properties from the source doclet, provided that - * the properties from the source doclet appear to be a better fit. + * Combine two doclets into a target doclet, using properties from the secondary doclet only when + * those properties do not exist on the primary doclet, and ignoring properties that should be + * excluded. * * @private - * @param {module:jsdoc/doclet.Doclet} source - The source doclet. - * @param {module:jsdoc/doclet.Doclet} destination - The destination doclet. + * @param {module:jsdoc/doclet.Doclet} primary - The primary doclet. + * @param {module:jsdoc/doclet.Doclet} secondary - The secondary doclet. + * @param {module:jsdoc/doclet.Doclet} target - The doclet to which properties will be copied. + * @param {Array.} exclude - The names of properties to exclude from copying. + */ +function combine(primary, secondary, target, exclude) { + var primaryProperties = _.difference(Object.getOwnPropertyNames(primary), exclude); + var secondaryProperties = _.difference(Object.getOwnPropertyNames(secondary), + exclude.concat(primaryProperties)); + + dooper(primary, target, primaryProperties); + dooper(secondary, target, secondaryProperties); +} + +/** + * Combine specified properties from two doclets into a target doclet, using the properties of the + * primary doclet unless the properties of the secondary doclet appear to be a better fit. + * + * @private + * @param {module:jsdoc/doclet.Doclet} primary - The primary doclet. + * @param {module:jsdoc/doclet.Doclet} secondary - The secondary doclet. + * @param {module:jsdoc/doclet.Doclet} target - The doclet to which properties will be copied. * @param {Array.} include - The names of properties to copy. */ -function maybeExtend(source, destination, include) { +function combineWithLogic(primary, secondary, target, include) { include.forEach(function(property) { - var shouldExtend = false; + var shouldUsePrimary = false; - if ({}.hasOwnProperty.call(source, property)) { - // use the source property if the destination property is missing or empty - if (!destination[property] || !destination[property].length) { - shouldExtend = true; + if ({}.hasOwnProperty.call(primary, property)) { + // use the primary property if the secondary property is missing or empty + if (!secondary[property] || !secondary[property].length) { + shouldUsePrimary = true; } // use the source property if it's not empty - else if (source[property].length) { - shouldExtend = true; + else if (primary[property].length) { + shouldUsePrimary = true; } } - if (shouldExtend) { - destination[property] = jsdoc.util.doop(source[property]); + if (shouldUsePrimary) { + target[property] = jsdoc.util.doop(primary[property]); + } + else { + target[property] = jsdoc.util.doop(secondary[property]); } }); } /** - * Merge another doclet into this doclet. + * Combine two doclets into a new doclet. * - * @param {module:jsdoc/doclet.Doclet} doclet - The doclet to merge into this one. + * @param {module:jsdoc/doclet.Doclet} primary - The doclet whose properties will be used. + * @param {module:jsdoc/doclet.Doclet} secondary - The doclet to use as a fallback for properties + * that the primary doclet does not have. */ -Doclet.prototype.merge = function(doclet) { +exports.combine = function(primary, secondary) { var specialCase = [ 'params', 'properties' ]; + var target = new Doclet(''); - extend(doclet, this, specialCase); - maybeExtend(doclet, this, specialCase); + combine(primary, secondary, target, specialCase); + combineWithLogic(primary, secondary, target, specialCase); + + return target; }; diff --git a/test/specs/jsdoc/doclet.js b/test/specs/jsdoc/doclet.js index f1ea3a87..1cdb73ea 100644 --- a/test/specs/jsdoc/doclet.js +++ b/test/specs/jsdoc/doclet.js @@ -3,7 +3,10 @@ describe('jsdoc/doclet', function() { // TODO: more tests var _ = require('underscore'); - var Doclet = require('jsdoc/doclet').Doclet; + var jsdoc = { + doclet: require('jsdoc/doclet') + }; + var Doclet = jsdoc.doclet.Doclet; var docSet = jasmine.getDocSetFromFile('test/fixtures/doclet.js'); var test1 = docSet.getByLongname('test1')[0]; @@ -47,25 +50,23 @@ describe('jsdoc/doclet', function() { }); }); - describe('merge', function() { - it('should override most properties of the original doclet', function() { - var originalDoclet = new Doclet('/** Hello!\n@version 1.0.0 */'); - var newDoclet = new Doclet('/** New and improved!\n@version 2.0.0 */'); + describe('combine', function() { + it('should override most properties of the secondary doclet', function() { + var primaryDoclet = new Doclet('/** New and improved!\n@version 2.0.0 */'); + var secondaryDoclet = new Doclet('/** Hello!\n@version 1.0.0 */'); + var newDoclet = jsdoc.doclet.combine(primaryDoclet, secondaryDoclet); - originalDoclet.merge(newDoclet); - - Object.getOwnPropertyNames(originalDoclet).forEach(function(property) { - expect(originalDoclet[property]).toEqual(newDoclet[property]); + Object.getOwnPropertyNames(newDoclet).forEach(function(property) { + expect(newDoclet[property]).toEqual(primaryDoclet[property]); }); }); - it('should add properties that are missing from the original doclet', function() { - var originalDoclet = new Doclet('/** Hello! */'); - var newDoclet = new Doclet('/** Hello!\n@version 2.0.0 */'); + it('should add properties that are missing from the secondary doclet', function() { + var primaryDoclet = new Doclet('/** Hello!\n@version 2.0.0 */'); + var secondaryDoclet = new Doclet('/** Hello! */'); + var newDoclet = jsdoc.doclet.combine(primaryDoclet, secondaryDoclet); - originalDoclet.merge(newDoclet); - - expect(originalDoclet.version).toBe('2.0.0'); + expect(newDoclet.version).toBe('2.0.0'); }); describe('params and properties', function() { @@ -74,65 +75,45 @@ describe('jsdoc/doclet', function() { 'properties' ]; - it('should use the new doclet\'s params and properties if the original doclet had none', - function() { - var originalDoclet = new Doclet('/** Hello! */'); - var newComment = [ - '/**', - ' * @param {string} foo - The foo.', - ' * @property {number} bar - The bar.', - ' */' - ].join('\n'); - var newDoclet = new Doclet(newComment); + it('should use the secondary doclet\'s params and properties if the primary doclet ' + + 'had none', function() { + var primaryDoclet = new Doclet('/** Hello! */'); + var secondaryComment = [ + '/**', + ' * @param {string} foo - The foo.', + ' * @property {number} bar - The bar.', + ' */' + ].join('\n'); + var secondaryDoclet = new Doclet(secondaryComment); + var newDoclet = jsdoc.doclet.combine(primaryDoclet, secondaryDoclet); - originalDoclet.merge(newDoclet); - - properties.forEach(function(property) { - expect(originalDoclet[property]).toEqual(newDoclet[property]); - }); + properties.forEach(function(property) { + expect(newDoclet[property]).toEqual(secondaryDoclet[property]); }); + }); - it('should use the new doclet\'s params and properties if the new doclet has some', - function() { - var originalComment = [ - '/**', - ' * @param {string} foo - The foo.', - ' * @property {number} bar - The bar.', - ' */' - ].join('\n'); - var originalDoclet = new Doclet(originalComment); - var newComment = [ - '/**', - ' * @param {number} baz - The baz.', - ' * @property {string} qux - The qux.', - ' */' - ].join('\n'); - var newDoclet = new Doclet(newComment); + it('should use the primary doclet\'s params and properties if the primary doclet has ' + + 'some', function() { + var primaryComment = [ + '/**', + ' * @param {number} baz - The baz.', + ' * @property {string} qux - The qux.', + ' */' + ].join('\n'); + var primaryDoclet = new Doclet(primaryComment); + var secondaryComment = [ + '/**', + ' * @param {string} foo - The foo.', + ' * @property {number} bar - The bar.', + ' */' + ].join('\n'); + var secondaryDoclet = new Doclet(secondaryComment); + var newDoclet = jsdoc.doclet.combine(primaryDoclet, secondaryDoclet); - originalDoclet.merge(newDoclet); - - properties.forEach(function(property) { - expect(originalDoclet[property]).toEqual(newDoclet[property]); - }); - }); - - it('should use the old doclet\'s params and properties if the new doclet has none', - function() { - var originalComment = [ - '/**', - ' * @param {string} foo - The foo.', - ' * @property {number} bar - The bar.', - ' */' - ].join('\n'); - var originalDoclet = new Doclet(originalComment); - var newDoclet = new Doclet('/** Hello! */'); - - originalDoclet.merge(newDoclet); - - properties.forEach(function(property) { - expect(originalDoclet[property]).not.toEqual(newDoclet[property]); - }); + properties.forEach(function(property) { + expect(newDoclet[property]).toEqual(primaryDoclet[property]); }); + }); }); }); });