diff --git a/packages/jsdoc-doclet/index.js b/packages/jsdoc-doclet/index.js index 6173d8e5..de13536c 100644 --- a/packages/jsdoc-doclet/index.js +++ b/packages/jsdoc-doclet/index.js @@ -22,10 +22,10 @@ */ import * as augment from './lib/augment.js'; import { resolveBorrows } from './lib/borrow.js'; -import { combineDoclets, Doclet } from './lib/doclet.js'; +import { Doclet } from './lib/doclet.js'; import { DocletStore } from './lib/doclet-store.js'; import { Package } from './lib/package.js'; import * as schema from './lib/schema.js'; -export { augment, combineDoclets, Doclet, DocletStore, Package, resolveBorrows, schema }; -export default { augment, combineDoclets, Doclet, DocletStore, Package, resolveBorrows, schema }; +export { augment, Doclet, DocletStore, Package, resolveBorrows, schema }; +export default { augment, Doclet, DocletStore, Package, resolveBorrows, schema }; diff --git a/packages/jsdoc-doclet/lib/augment.js b/packages/jsdoc-doclet/lib/augment.js index ff0820ac..ca80d187 100644 --- a/packages/jsdoc-doclet/lib/augment.js +++ b/packages/jsdoc-doclet/lib/augment.js @@ -23,7 +23,7 @@ import { fromParts, SCOPE, toParts } from '@jsdoc/name'; -import { combineDoclets, Doclet } from './doclet.js'; +import { Doclet } from './doclet.js'; const DEPENDENCY_KINDS = ['class', 'external', 'interface', 'mixin']; @@ -225,7 +225,7 @@ function getInheritedAdditions(depDoclets, docletStore) { childDoclet = {}; } - member = combineDoclets(childDoclet, parentDoclet); + member = Doclet.combineDoclets(childDoclet, parentDoclet); if (!member.inherited) { member.inherits = member.longname; @@ -411,7 +411,7 @@ function getImplementedAdditions(implDoclets, docletStore) { childDoclet = {}; } - implementationDoclet = combineDoclets(childDoclet, parentDoclet); + implementationDoclet = Doclet.combineDoclets(childDoclet, parentDoclet); reparentDoclet(doclet, implementationDoclet); updateImplements(implementationDoclet, parentDoclet.longname); diff --git a/packages/jsdoc-doclet/lib/borrow.js b/packages/jsdoc-doclet/lib/borrow.js index a63ed7eb..8c65a15c 100644 --- a/packages/jsdoc-doclet/lib/borrow.js +++ b/packages/jsdoc-doclet/lib/borrow.js @@ -20,7 +20,7 @@ import { SCOPE } from '@jsdoc/name'; -import { combineDoclets, Doclet } from './doclet.js'; +import { Doclet } from './doclet.js'; function cloneBorrowedDoclets({ borrowed, longname }, docletStore) { borrowed?.forEach(({ from, as }) => { @@ -32,7 +32,7 @@ function cloneBorrowedDoclets({ borrowed, longname }, docletStore) { if (borrowedDoclets) { borrowedAs = borrowedAs.replace(/^prototype\./, SCOPE.PUNC.INSTANCE); borrowedDoclets.forEach((borrowedDoclet) => { - const clone = combineDoclets(borrowedDoclet, Doclet.emptyDoclet(borrowedDoclet.env)); + const clone = Doclet.clone(borrowedDoclet); // TODO: this will fail on longnames like '"Foo#bar".baz' parts = borrowedAs.split(SCOPE.PUNC.INSTANCE); diff --git a/packages/jsdoc-doclet/lib/doclet.js b/packages/jsdoc-doclet/lib/doclet.js index 9d452922..798cdc69 100644 --- a/packages/jsdoc-doclet/lib/doclet.js +++ b/packages/jsdoc-doclet/lib/doclet.js @@ -409,30 +409,6 @@ function copyPropsWithIncludelist(primary, secondary, target, include) { }); } -/** - * Combines two doclets into a new doclet. - * - * @alias module:@jsdoc/doclet.combineDoclets - * @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. - * @returns {module:@jsdoc/doclet.Doclet} A new doclet that combines the primary and secondary - * doclets. - */ -export function combineDoclets(primary, secondary) { - const excludelist = ['env', 'params', 'properties', 'undocumented']; - const includelist = ['params', 'properties']; - const target = Doclet.emptyDoclet(secondary.env); - - // First, copy most properties to the target doclet. - copyPropsWithExcludelist(primary, secondary, target, excludelist); - // Then copy a few specific properties to the target doclet, as long as they're not falsy and - // have a length greater than 0. - copyPropsWithIncludelist(primary, secondary, target, includelist); - - return target; -} - /** * Information about a single JSDoc comment, or a single symbol in a source file. * @@ -504,7 +480,30 @@ Doclet = class { * @returns {module:@jsdoc/doclet.Doclet} A copy of the doclet. */ static clone(doclet) { - return combineDoclets(doclet, Doclet.emptyDoclet(doclet.env)); + return Doclet.combineDoclets(doclet, Doclet.emptyDoclet(doclet.env)); + } + + /** + * Combines two doclets into a new doclet. + * + * @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. + * @returns {module:@jsdoc/doclet.Doclet} A new doclet that combines the primary and secondary + * doclets. + */ + static combineDoclets(primary, secondary) { + const excludelist = ['env', 'params', 'properties', 'undocumented']; + const includelist = ['params', 'properties']; + const target = Doclet.emptyDoclet(secondary.env); + + // First, copy most properties to the target doclet. + copyPropsWithExcludelist(primary, secondary, target, excludelist); + // Then copy a few specific properties to the target doclet, as long as they're not falsy and + // have a length greater than 0. + copyPropsWithIncludelist(primary, secondary, target, includelist); + + return target; } /** diff --git a/packages/jsdoc-doclet/test/specs/index.js b/packages/jsdoc-doclet/test/specs/index.js index 3330a12f..a512aa1c 100644 --- a/packages/jsdoc-doclet/test/specs/index.js +++ b/packages/jsdoc-doclet/test/specs/index.js @@ -17,7 +17,7 @@ import doclet from '../../index.js'; import * as augment from '../../lib/augment.js'; import { resolveBorrows } from '../../lib/borrow.js'; -import { combineDoclets, Doclet } from '../../lib/doclet.js'; +import { Doclet } from '../../lib/doclet.js'; import { DocletStore } from '../../lib/doclet-store.js'; import { Package } from '../../lib/package.js'; import * as schema from '../../lib/schema.js'; @@ -33,12 +33,6 @@ describe('@jsdoc/doclet', () => { }); }); - describe('combineDoclets', () => { - it('is lib/doclet.combineDoclets', () => { - expect(doclet.combineDoclets).toEqual(combineDoclets); - }); - }); - describe('Doclet', () => { it('is lib/doclet.Doclet', () => { expect(doclet.Doclet).toEqual(Doclet); diff --git a/packages/jsdoc-doclet/test/specs/lib/doclet.js b/packages/jsdoc-doclet/test/specs/lib/doclet.js index fa1a443c..5a34caa6 100644 --- a/packages/jsdoc-doclet/test/specs/lib/doclet.js +++ b/packages/jsdoc-doclet/test/specs/lib/doclet.js @@ -29,10 +29,6 @@ describe('@jsdoc/doclet/lib/doclet', () => { expect(doclet).toBeObject(); }); - it('has a combineDoclets method', () => { - expect(doclet.combineDoclets).toBeFunction(); - }); - it('has a Doclet class', () => { expect(doclet.Doclet).toBeFunction(); }); @@ -41,74 +37,6 @@ describe('@jsdoc/doclet/lib/doclet', () => { expect(doclet.WATCHABLE_PROPS).toBeArrayOfStrings(); }); - describe('combineDoclets', () => { - it('overrides most properties of the secondary doclet', () => { - let descriptors; - const primaryDoclet = new Doclet('/** New and improved!\n@version 2.0.0 */', null, jsdoc.env); - const secondaryDoclet = new Doclet('/** Hello!\n@version 1.0.0 */', null, jsdoc.env); - const newDoclet = doclet.combineDoclets(primaryDoclet, secondaryDoclet); - - descriptors = Object.getOwnPropertyDescriptors(newDoclet); - Object.keys(descriptors).forEach((property) => { - if (!descriptors[property].enumerable) { - return; - } - - expect(newDoclet[property]).toEqual(primaryDoclet[property]); - }); - }); - - it('adds properties from the secondary doclet that are missing', () => { - const primaryDoclet = new Doclet('/** Hello!\n@version 2.0.0 */', null, jsdoc.env); - const secondaryDoclet = new Doclet('/** Hello! */', null, jsdoc.env); - const newDoclet = doclet.combineDoclets(primaryDoclet, secondaryDoclet); - - expect(newDoclet.version).toBe('2.0.0'); - }); - - describe('params and properties', () => { - const properties = ['params', 'properties']; - - it('uses params and properties from the secondary doclet if the primary lacks them', () => { - const primaryDoclet = new Doclet('/** Hello! */', null, jsdoc.env); - const secondaryComment = [ - '/**', - ' * @param {string} foo - The foo.', - ' * @property {number} bar - The bar.', - ' */', - ].join('\n'); - const secondaryDoclet = new Doclet(secondaryComment, null, jsdoc.env); - const newDoclet = doclet.combineDoclets(primaryDoclet, secondaryDoclet); - - properties.forEach((property) => { - expect(newDoclet[property]).toEqual(secondaryDoclet[property]); - }); - }); - - it('uses params and properties from the primary doclet, if present', () => { - const primaryComment = [ - '/**', - ' * @param {number} baz - The baz.', - ' * @property {string} qux - The qux.', - ' */', - ].join('\n'); - const primaryDoclet = new Doclet(primaryComment, null, jsdoc.env); - const secondaryComment = [ - '/**', - ' * @param {string} foo - The foo.', - ' * @property {number} bar - The bar.', - ' */', - ].join('\n'); - const secondaryDoclet = new Doclet(secondaryComment, null, jsdoc.env); - const newDoclet = doclet.combineDoclets(primaryDoclet, secondaryDoclet); - - properties.forEach((property) => { - expect(newDoclet[property]).toEqual(primaryDoclet[property]); - }); - }); - }); - }); - describe('Doclet', () => { function makeDoclet(tagStrings, env) { const comment = `/**\n${tagStrings.join('\n')}\n*/`; @@ -305,6 +233,86 @@ describe('@jsdoc/doclet/lib/doclet', () => { xit('TODO: write tests'); }); + xdescribe('clone', () => { + xit('TODO: write tests'); + }); + + describe('combineDoclets', () => { + it('overrides most properties of the secondary doclet', () => { + let descriptors; + const primaryDoclet = new Doclet( + '/** New and improved!\n@version 2.0.0 */', + null, + jsdoc.env + ); + const secondaryDoclet = new Doclet('/** Hello!\n@version 1.0.0 */', null, jsdoc.env); + const newDoclet = Doclet.combineDoclets(primaryDoclet, secondaryDoclet); + + descriptors = Object.getOwnPropertyDescriptors(newDoclet); + Object.keys(descriptors).forEach((property) => { + if (!descriptors[property].enumerable) { + return; + } + + expect(newDoclet[property]).toEqual(primaryDoclet[property]); + }); + }); + + it('adds properties from the secondary doclet that are missing', () => { + const primaryDoclet = new Doclet('/** Hello!\n@version 2.0.0 */', null, jsdoc.env); + const secondaryDoclet = new Doclet('/** Hello! */', null, jsdoc.env); + const newDoclet = Doclet.combineDoclets(primaryDoclet, secondaryDoclet); + + expect(newDoclet.version).toBe('2.0.0'); + }); + + describe('params and properties', () => { + const properties = ['params', 'properties']; + + it('uses params and properties from the secondary doclet if the primary lacks them', () => { + const primaryDoclet = new Doclet('/** Hello! */', null, jsdoc.env); + const secondaryComment = [ + '/**', + ' * @param {string} foo - The foo.', + ' * @property {number} bar - The bar.', + ' */', + ].join('\n'); + const secondaryDoclet = new Doclet(secondaryComment, null, jsdoc.env); + const newDoclet = Doclet.combineDoclets(primaryDoclet, secondaryDoclet); + + properties.forEach((property) => { + expect(newDoclet[property]).toEqual(secondaryDoclet[property]); + }); + }); + + it('uses params and properties from the primary doclet, if present', () => { + const primaryComment = [ + '/**', + ' * @param {number} baz - The baz.', + ' * @property {string} qux - The qux.', + ' */', + ].join('\n'); + const primaryDoclet = new Doclet(primaryComment, null, jsdoc.env); + const secondaryComment = [ + '/**', + ' * @param {string} foo - The foo.', + ' * @property {number} bar - The bar.', + ' */', + ].join('\n'); + const secondaryDoclet = new Doclet(secondaryComment, null, jsdoc.env); + const newDoclet = Doclet.combineDoclets(primaryDoclet, secondaryDoclet); + + properties.forEach((property) => { + expect(newDoclet[property]).toEqual(primaryDoclet[property]); + }); + }); + }); + }); + + xdescribe('emptyDoclet', () => { + xit('TODO: write tests'); + }); + describe('isGlobal', () => { it('identifies global constants', () => { const newDoclet = makeDoclet(['@constant', '@global', '@name foo']); diff --git a/packages/jsdoc-parse/lib/visitor.js b/packages/jsdoc-parse/lib/visitor.js index 7081643e..7d556252 100644 --- a/packages/jsdoc-parse/lib/visitor.js +++ b/packages/jsdoc-parse/lib/visitor.js @@ -17,7 +17,7 @@ import path from 'node:path'; import { astNode, Syntax } from '@jsdoc/ast'; -import { combineDoclets } from '@jsdoc/doclet'; +import { Doclet } from '@jsdoc/doclet'; import * as name from '@jsdoc/name'; const { getBasename, LONGNAMES } = name; @@ -333,7 +333,7 @@ function makeConstructorFinisher(parser) { // We prefer the parent doclet because it has the correct kind, longname, and memberof. // The child doclet might or might not have the correct kind, longname, and memberof. - combined = combineDoclets(parentDoclet, eventDoclet, parser.env); + combined = Doclet.combineDoclets(parentDoclet, eventDoclet, parser.env); parser.addResult(combined); parentDoclet.undocumented = eventDoclet.undocumented = true; diff --git a/packages/jsdoc-parse/test/specs/lib/parser.js b/packages/jsdoc-parse/test/specs/lib/parser.js index 2f4f7733..85b4651a 100644 --- a/packages/jsdoc-parse/test/specs/lib/parser.js +++ b/packages/jsdoc-parse/test/specs/lib/parser.js @@ -20,7 +20,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { Syntax, Walker } from '@jsdoc/ast'; -import { combineDoclets, Doclet } from '@jsdoc/doclet'; +import { Doclet } from '@jsdoc/doclet'; import { attachTo } from '../../../lib/handlers.js'; import * as jsdocParser from '../../../lib/parser.js'; @@ -156,7 +156,7 @@ describe('@jsdoc/parse/lib/parser', () => { const sourceCode = 'javascript:/** @class */function Foo() {}'; function handler(e) { - e.doclet = combineDoclets(e.doclet, new Doclet('', {}, jsdoc.env)); + e.doclet = Doclet.combineDoclets(e.doclet, new Doclet('', {}, jsdoc.env)); e.doclet.foo = 'bar'; }