refactor(jsdoc-doclet): make combineDoclets a static method on Doclet

This change puts `combineDoclets` in the same place as methods like `clone` and `emptyDoclet`.
This commit is contained in:
Jeff Williams 2025-07-05 09:49:44 -07:00
parent 4c4a58260d
commit 8a0b40502e
No known key found for this signature in database
8 changed files with 117 additions and 116 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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']);

View File

@ -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;

View File

@ -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';
}