From 13cdfd18b74cbee310fdeddfa046948b6822e892 Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Sat, 4 Oct 2025 17:40:13 -0700 Subject: [PATCH] fix: use the correct name and longname for an exported symbol with a `@memberof` tag --- packages/jsdoc-ast/lib/ast-node.js | 4 +-- packages/jsdoc-parse/lib/handlers.js | 14 ++++++---- .../jsdoc/test/fixtures/modulememberof.js | 7 +++++ .../specs/documentation/modulememberof.js | 26 +++++++++++++++++++ 4 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 packages/jsdoc/test/fixtures/modulememberof.js create mode 100644 packages/jsdoc/test/specs/documentation/modulememberof.js diff --git a/packages/jsdoc-ast/lib/ast-node.js b/packages/jsdoc-ast/lib/ast-node.js index 595c5e09..271be51e 100644 --- a/packages/jsdoc-ast/lib/ast-node.js +++ b/packages/jsdoc-ast/lib/ast-node.js @@ -173,9 +173,9 @@ export function nodeToValue(node) { // Like the declaration in: `export const foo = 'bar';` // We need a single value, so we use the first variable name. if (node.declaration.declarations) { - str = `${LONGNAMES.MODULE_EXPORT}.${nodeToValue(node.declaration.declarations[0])}`; + str = `${nodeToValue(node.declaration.declarations[0])}`; } else { - str = `${LONGNAMES.MODULE_EXPORT}.${nodeToValue(node.declaration)}`; + str = `${nodeToValue(node.declaration)}`; } } diff --git a/packages/jsdoc-parse/lib/handlers.js b/packages/jsdoc-parse/lib/handlers.js index ecae3703..8698d9bf 100644 --- a/packages/jsdoc-parse/lib/handlers.js +++ b/packages/jsdoc-parse/lib/handlers.js @@ -139,6 +139,7 @@ function findAncestorWithType(node, ancestorType) { function setModuleScopeMemberOf(parser, doclet) { const moduleInfo = getModule(); + const node = doclet.meta?.code?.node; let parentDoclet; let skipMemberof; @@ -146,17 +147,17 @@ function setModuleScopeMemberOf(parser, doclet) { if (moduleInfo && !isModuleExports(moduleInfo, doclet)) { if (!doclet.scope) { // is this a method definition? if so, we usually get the scope from the node directly - if (doclet.meta?.code?.node?.type === Syntax.MethodDefinition) { - parentDoclet = parser._getDocletById(doclet.meta.code.node.parent.parent.nodeId); + if (node?.type === Syntax.MethodDefinition) { + parentDoclet = parser._getDocletById(node.parent.parent.nodeId); // special case for constructors of classes that have @alias tags - if (doclet.meta.code.node.kind === 'constructor' && parentDoclet?.alias) { + if (node.kind === 'constructor' && parentDoclet?.alias) { // the constructor should use the same name as the class doclet.addTag('alias', parentDoclet.alias); doclet.addTag('name', parentDoclet.alias); // and we shouldn't try to set a memberof value skipMemberof = true; } else { - doclet.addTag(doclet.meta.code.node.static ? 'static' : 'instance'); + doclet.addTag(node.static ? 'static' : 'instance'); // The doclet should be a member of the parent doclet's alias. if (parentDoclet?.alias) { doclet.memberof = parentDoclet.alias; @@ -164,7 +165,10 @@ function setModuleScopeMemberOf(parser, doclet) { } } // Is this something that the module exports? if so, it's a static member. - else if (findAncestorWithType(doclet.meta?.code?.node, Syntax.ExportNamedDeclaration)) { + else if ( + node?.type === Syntax.ExportNamedDeclaration || + findAncestorWithType(node, Syntax.ExportNamedDeclaration) + ) { doclet.addTag('static'); } // Otherwise, it must be an inner member. diff --git a/packages/jsdoc/test/fixtures/modulememberof.js b/packages/jsdoc/test/fixtures/modulememberof.js new file mode 100644 index 00000000..438a298b --- /dev/null +++ b/packages/jsdoc/test/fixtures/modulememberof.js @@ -0,0 +1,7 @@ +/** + * The bar namespace. + * + * @namespace + * @memberof module:foo + */ +export const bar = {}; diff --git a/packages/jsdoc/test/specs/documentation/modulememberof.js b/packages/jsdoc/test/specs/documentation/modulememberof.js new file mode 100644 index 00000000..4a0351f0 --- /dev/null +++ b/packages/jsdoc/test/specs/documentation/modulememberof.js @@ -0,0 +1,26 @@ +/* + Copyright 2025 the JSDoc Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +describe('memberof a module', () => { + const docSet = jsdoc.getDocSetFromFile('test/fixtures/modulememberof.js'); + + it('uses the correct name and longname for an exported symbol with a @memberof tag', () => { + const bar = docSet.getByLongname('module:foo.bar').filter((d) => !d.undocumented)[0]; + + expect(bar).toBeObject(); + expect(bar.name).toBe('bar'); + }); +});