From 0551bd49ed8a8ba650444b62cdb64fa7d5165b33 Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Fri, 14 Jul 2017 21:39:21 -0700 Subject: [PATCH] add `merge` method to doclets (#1215) --- lib/jsdoc/doclet.js | 79 ++++++++++++++++++++++++++++++++ test/specs/jsdoc/doclet.js | 93 +++++++++++++++++++++++++++++++++++++- 2 files changed, 170 insertions(+), 2 deletions(-) diff --git a/lib/jsdoc/doclet.js b/lib/jsdoc/doclet.js index da153a9c..f3b63ead 100644 --- a/lib/jsdoc/doclet.js +++ b/lib/jsdoc/doclet.js @@ -14,6 +14,9 @@ var jsdoc = { tag: { Tag: require('jsdoc/tag').Tag, dictionary: require('jsdoc/tag/dictionary') + }, + util: { + doop: require('jsdoc/util/doop') } }; var path = require('jsdoc/path'); @@ -178,6 +181,8 @@ exports._replaceDictionary = function _replaceDictionary(dict) { var Doclet = exports.Doclet = function(docletSrc, meta) { var newTags = []; + meta = meta || {}; + /** The original text of the comment from the source code. */ this.comment = docletSrc; this.setMeta(meta); @@ -462,3 +467,77 @@ 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); + + properties.forEach(function(property) { + switch (typeof source[property]) { + case 'function': + // do nothing + break; + + case 'object': + destination[property] = jsdoc.util.doop(source[property]); + + break; + + default: + destination[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. + * + * @private + * @param {module:jsdoc/doclet.Doclet} source - The source doclet. + * @param {module:jsdoc/doclet.Doclet} destination - The destination doclet. + * @param {Array.} include - The names of properties to copy. + */ +function maybeExtend(source, destination, include) { + include.forEach(function(property) { + var shouldExtend = 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; + } + // use the source property if it's at least as long as the destination property + else if (source[property].length >= destination[property].length) { + shouldExtend = true; + } + } + + if (shouldExtend) { + destination[property] = jsdoc.util.doop(source[property]); + } + }); +} + +/** + * Merge another doclet into this doclet. + * + * @param {module:jsdoc/doclet.Doclet} doclet - The doclet to merge into this one. + */ +Doclet.prototype.merge = function(doclet) { + var specialCase = [ + 'params', + 'properties' + ]; + + extend(doclet, this, specialCase); + maybeExtend(doclet, this, specialCase); +}; diff --git a/test/specs/jsdoc/doclet.js b/test/specs/jsdoc/doclet.js index 033a539b..de00cab4 100644 --- a/test/specs/jsdoc/doclet.js +++ b/test/specs/jsdoc/doclet.js @@ -26,7 +26,7 @@ describe('jsdoc/doclet', function() { describe('setScope', function() { it('should accept the correct scope names', function() { function setScope(scopeName) { - var doclet = new Doclet('/** Huzzah, a doclet! */', {}); + var doclet = new Doclet('/** Huzzah, a doclet! */'); doclet.setScope(scopeName); } @@ -38,7 +38,7 @@ describe('jsdoc/doclet', function() { it('should throw an error for invalid scope names', function() { function setScope() { - var doclet = new Doclet('/** Woe betide this doclet. */', {}); + var doclet = new Doclet('/** Woe betide this doclet. */'); doclet.setScope('fiddlesticks'); } @@ -46,4 +46,93 @@ describe('jsdoc/doclet', function() { expect(setScope).toThrow(); }); }); + + 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 */'); + + originalDoclet.merge(newDoclet); + + Object.getOwnPropertyNames(originalDoclet).forEach(function(property) { + expect(originalDoclet[property]).toEqual(newDoclet[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 */'); + + originalDoclet.merge(newDoclet); + + expect(originalDoclet.version).toBe('2.0.0'); + }); + + describe('params and properties', function() { + var properties = [ + 'params', + '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); + + originalDoclet.merge(newDoclet); + + properties.forEach(function(property) { + expect(originalDoclet[property]).toEqual(newDoclet[property]); + }); + }); + + it('should use the new doclet\'s params and properties if the new doclet has at ' + + 'least as many of them as the old doclet', 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); + + 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 fewer ' + + 'of them', 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]); + }); + }); + }); + }); });