mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
fix(jsdoc-parse): use the correct scopes when exported objects have properties
Previously, JSDoc parsed the test code and found the namepath `module:icecream.FLAVORS` (correct), but also `module:icecream~FLAVORS.VANILLA` (wrong, because `FLAVORS` is a static member of the module, not an inner member). With this change, JSDoc should consistently identify `FLAVORS` as a static member of `module:icecream`.
This commit is contained in:
parent
30df56712e
commit
7a2e561e11
@ -115,12 +115,34 @@ function isModuleExports(module, doclet) {
|
|||||||
return module.longname === doclet.name;
|
return module.longname === doclet.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds an AST node's closest ancestor with the specified type.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object} node - The AST node.
|
||||||
|
* @param {(module:@jsdoc/ast.Syntax|string)} ancestorType - The type of ancestor node to find.
|
||||||
|
* @return {?Object} The closest ancestor with the specified type.
|
||||||
|
*/
|
||||||
|
function findAncestorWithType(node, ancestorType) {
|
||||||
|
let parent = node?.parent;
|
||||||
|
|
||||||
|
while (parent) {
|
||||||
|
if (parent.type === ancestorType) {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = parent.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function setModuleScopeMemberOf(parser, doclet) {
|
function setModuleScopeMemberOf(parser, doclet) {
|
||||||
const moduleInfo = getModule();
|
const moduleInfo = getModule();
|
||||||
let parentDoclet;
|
let parentDoclet;
|
||||||
let skipMemberof;
|
let skipMemberof;
|
||||||
|
|
||||||
// Handle CommonJS module symbols that are _not_ assigned to `module.exports`.
|
// Handle module symbols, excluding CommonJS `module.exports`.
|
||||||
if (moduleInfo && !isModuleExports(moduleInfo, doclet)) {
|
if (moduleInfo && !isModuleExports(moduleInfo, doclet)) {
|
||||||
if (!doclet.scope) {
|
if (!doclet.scope) {
|
||||||
// is this a method definition? if so, we usually get the scope from the node directly
|
// is this a method definition? if so, we usually get the scope from the node directly
|
||||||
@ -141,11 +163,11 @@ function setModuleScopeMemberOf(parser, doclet) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// is this something that the module exports? if so, it's a static member
|
// Is this something that the module exports? if so, it's a static member.
|
||||||
else if (doclet.meta?.code?.node?.parent?.type === Syntax.ExportNamedDeclaration) {
|
else if (findAncestorWithType(doclet.meta?.code?.node, Syntax.ExportNamedDeclaration)) {
|
||||||
doclet.addTag('static');
|
doclet.addTag('static');
|
||||||
}
|
}
|
||||||
// otherwise, it must be an inner member
|
// Otherwise, it must be an inner member.
|
||||||
else {
|
else {
|
||||||
doclet.addTag('inner');
|
doclet.addTag('inner');
|
||||||
}
|
}
|
||||||
@ -153,7 +175,7 @@ function setModuleScopeMemberOf(parser, doclet) {
|
|||||||
|
|
||||||
// if the doclet isn't a memberof anything yet, and it's not a global, it must be a memberof
|
// if the doclet isn't a memberof anything yet, and it's not a global, it must be a memberof
|
||||||
// the current module (unless we were told to skip adding memberof)
|
// the current module (unless we were told to skip adding memberof)
|
||||||
if (!doclet.memberof && doclet.scope !== SCOPE.NAMES.GLOBAL && !skipMemberof) {
|
if (!skipMemberof && !doclet.memberof && doclet.scope !== SCOPE.NAMES.GLOBAL) {
|
||||||
doclet.addTag('memberof', moduleInfo.longname);
|
doclet.addTag('memberof', moduleInfo.longname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -180,12 +202,12 @@ function addDoclet(parser, newDoclet) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processAlias(parser, doclet, astNode) {
|
function processAlias(parser, doclet, node) {
|
||||||
let match;
|
let match;
|
||||||
let memberofName;
|
let memberofName;
|
||||||
|
|
||||||
if (doclet.alias === '{@thisClass}') {
|
if (doclet.alias === '{@thisClass}') {
|
||||||
memberofName = parser.resolveThis(astNode);
|
memberofName = parser.resolveThis(node);
|
||||||
|
|
||||||
// "class" refers to the owner of the prototype, not the prototype itself
|
// "class" refers to the owner of the prototype, not the prototype itself
|
||||||
match = memberofName.match(PROTOTYPE_OWNER_REGEXP);
|
match = memberofName.match(PROTOTYPE_OWNER_REGEXP);
|
||||||
@ -204,7 +226,7 @@ function isModuleObject(doclet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: separate code that resolves `this` from code that resolves the module object
|
// TODO: separate code that resolves `this` from code that resolves the module object
|
||||||
function findSymbolMemberof(parser, doclet, astNode, nameStartsWith, trailingPunc) {
|
function findSymbolMemberof(parser, doclet, node, nameStartsWith, trailingPunc) {
|
||||||
const docletIsModuleObject = isModuleObject(doclet);
|
const docletIsModuleObject = isModuleObject(doclet);
|
||||||
let memberof = '';
|
let memberof = '';
|
||||||
let nameAndPunc;
|
let nameAndPunc;
|
||||||
@ -237,7 +259,7 @@ function findSymbolMemberof(parser, doclet, astNode, nameStartsWith, trailingPun
|
|||||||
doclet.addTag('name', currentModule.longname);
|
doclet.addTag('name', currentModule.longname);
|
||||||
doclet.postProcess();
|
doclet.postProcess();
|
||||||
} else {
|
} else {
|
||||||
memberof = parser.resolveThis(astNode);
|
memberof = parser.resolveThis(node);
|
||||||
|
|
||||||
// like the following at the top level of a module:
|
// like the following at the top level of a module:
|
||||||
// this.foo = 1;
|
// this.foo = 1;
|
||||||
@ -255,7 +277,7 @@ function findSymbolMemberof(parser, doclet, astNode, nameStartsWith, trailingPun
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSymbolMemberof(parser, doclet, astNode) {
|
function addSymbolMemberof(parser, doclet, node) {
|
||||||
let basename;
|
let basename;
|
||||||
let memberof;
|
let memberof;
|
||||||
let memberofInfo;
|
let memberofInfo;
|
||||||
@ -264,7 +286,7 @@ function addSymbolMemberof(parser, doclet, astNode) {
|
|||||||
let scopePunc;
|
let scopePunc;
|
||||||
let unresolved;
|
let unresolved;
|
||||||
|
|
||||||
if (!astNode) {
|
if (!node) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,7 +301,7 @@ function addSymbolMemberof(parser, doclet, astNode) {
|
|||||||
unresolved = resolveTargetRegExp.exec(doclet.name);
|
unresolved = resolveTargetRegExp.exec(doclet.name);
|
||||||
|
|
||||||
if (unresolved) {
|
if (unresolved) {
|
||||||
memberofInfo = findSymbolMemberof(parser, doclet, astNode, unresolved[1], unresolved[2]);
|
memberofInfo = findSymbolMemberof(parser, doclet, node, unresolved[1], unresolved[2]);
|
||||||
memberof = memberofInfo.memberof;
|
memberof = memberofInfo.memberof;
|
||||||
scopePunc = memberofInfo.scopePunc;
|
scopePunc = memberofInfo.scopePunc;
|
||||||
|
|
||||||
@ -287,7 +309,7 @@ function addSymbolMemberof(parser, doclet, astNode) {
|
|||||||
doclet.name = doclet.name ? memberof + scopePunc + doclet.name : memberof;
|
doclet.name = doclet.name ? memberof + scopePunc + doclet.name : memberof;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
memberofInfo = parser.astnodeToMemberof(astNode);
|
memberofInfo = parser.astnodeToMemberof(node);
|
||||||
basename = memberofInfo.basename;
|
basename = memberofInfo.basename;
|
||||||
memberof = memberofInfo.memberof;
|
memberof = memberofInfo.memberof;
|
||||||
}
|
}
|
||||||
|
|||||||
13
packages/jsdoc/test/fixtures/moduleexport.js
vendored
Normal file
13
packages/jsdoc/test/fixtures/moduleexport.js
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/** @module icecream */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ice cream flavors.
|
||||||
|
*
|
||||||
|
* @enum
|
||||||
|
*/
|
||||||
|
export const FLAVORS = {
|
||||||
|
/** Vanilla. */
|
||||||
|
VANILLA: 0,
|
||||||
|
/** Chocolate. */
|
||||||
|
CHOCOLATE: 1,
|
||||||
|
};
|
||||||
27
packages/jsdoc/test/specs/documentation/moduleexport.js
Normal file
27
packages/jsdoc/test/specs/documentation/moduleexport.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
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('symbols exported by an ES2015 module', () => {
|
||||||
|
const docSet = jsdoc.getDocSetFromFile('test/fixtures/moduleexport.js');
|
||||||
|
|
||||||
|
it('uses the correct scopes when exported objects have properties', () => {
|
||||||
|
const chocolate = docSet.getByLongname('module:icecream.FLAVORS.CHOCOLATE')[0];
|
||||||
|
const vanilla = docSet.getByLongname('module:icecream.FLAVORS.VANILLA')[0];
|
||||||
|
|
||||||
|
expect(chocolate).toBeObject();
|
||||||
|
expect(vanilla).toBeObject();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user