mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
fix(jsdoc-parse): infer the existence of a module from @alias tags
If a class has a tag like `@alias module:foo.Bar`, then we can infer that the module for the current file is `module:foo`, even if there's no `/** @module foo */` comment in the file.
This commit is contained in:
parent
aa49b841bb
commit
65da78e6bb
@ -22,14 +22,16 @@ const PROTOTYPE_OWNER_REGEXP = /^(.+?)(\.prototype|#)$/;
|
||||
const { SCOPE } = name;
|
||||
|
||||
let currentModule = null;
|
||||
// Modules inferred from the value of an `@alias` tag, like `@alias module:foo.bar`.
|
||||
let inferredModules = [];
|
||||
|
||||
class CurrentModule {
|
||||
class ModuleInfo {
|
||||
constructor(doclet) {
|
||||
this.doclet = doclet;
|
||||
this.longname = doclet.longname;
|
||||
this.originalName = doclet.meta.code.name || '';
|
||||
this.originalName = doclet.meta?.code?.name ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
function filterByLongname({ longname }) {
|
||||
// you can't document prototypes
|
||||
if (/#$/.test(longname)) {
|
||||
@ -89,37 +91,46 @@ function createSymbolDoclet(comment, e, deps) {
|
||||
return doclet;
|
||||
}
|
||||
|
||||
function setCurrentModule(doclet) {
|
||||
function getModule() {
|
||||
return inferredModules.length ? inferredModules[inferredModules.length - 1] : currentModule;
|
||||
}
|
||||
|
||||
function setModule(doclet) {
|
||||
if (doclet.kind === 'module') {
|
||||
currentModule = new CurrentModule(doclet);
|
||||
currentModule = new ModuleInfo(doclet);
|
||||
} else if (doclet.longname.startsWith('module:')) {
|
||||
inferredModules.push(
|
||||
new ModuleInfo({
|
||||
longname: name.getBasename(doclet.longname),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function setModuleScopeMemberOf(parser, doclet) {
|
||||
const moduleInfo = getModule();
|
||||
let parentDoclet;
|
||||
let skipMemberof;
|
||||
|
||||
// handle module symbols that are _not_ assigned to module.exports
|
||||
if (currentModule && currentModule.longname !== doclet.name) {
|
||||
if (moduleInfo && moduleInfo.longname !== doclet.name) {
|
||||
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);
|
||||
// special case for constructors of classes that have @alias tags
|
||||
if (doclet.meta.code.node.kind === 'constructor') {
|
||||
parentDoclet = parser._getDocletById(doclet.meta.code.node.parent.parent.nodeId);
|
||||
|
||||
if (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 if (doclet.meta.code.node.static) {
|
||||
doclet.addTag('static');
|
||||
if (doclet.meta.code.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('instance');
|
||||
doclet.addTag(doclet.meta.code.node.static ? 'static' : 'instance');
|
||||
// The doclet should be a member of the parent doclet's alias.
|
||||
if (parentDoclet?.alias) {
|
||||
doclet.memberof = parentDoclet.alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
// is this something that the module exports? if so, it's a static member
|
||||
@ -135,7 +146,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
|
||||
// the current module (unless we were told to skip adding memberof)
|
||||
if (!doclet.memberof && doclet.scope !== SCOPE.NAMES.GLOBAL && !skipMemberof) {
|
||||
doclet.addTag('memberof', currentModule.longname);
|
||||
doclet.addTag('memberof', moduleInfo.longname);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -151,7 +162,7 @@ function addDoclet(parser, newDoclet) {
|
||||
let e;
|
||||
|
||||
if (newDoclet) {
|
||||
setCurrentModule(newDoclet);
|
||||
setModule(newDoclet);
|
||||
e = { doclet: newDoclet };
|
||||
parser.emit('newDoclet', e);
|
||||
|
||||
@ -365,5 +376,6 @@ export function attachTo(parser) {
|
||||
|
||||
parser.on('fileComplete', () => {
|
||||
currentModule = null;
|
||||
inferredModules = [];
|
||||
});
|
||||
}
|
||||
|
||||
18
packages/jsdoc/test/fixtures/aliasfores6export.js
vendored
Normal file
18
packages/jsdoc/test/fixtures/aliasfores6export.js
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* A basket of delicious fruits.
|
||||
*
|
||||
* @alias module:fruits.Basket
|
||||
*/
|
||||
export default class Basket {
|
||||
constructor() {
|
||||
/**
|
||||
* The material that the basket is made from.
|
||||
*/
|
||||
this.material = 'canvas';
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties the basket.
|
||||
*/
|
||||
empty() {}
|
||||
}
|
||||
32
packages/jsdoc/test/fixtures/aliasfores6export2.js
vendored
Normal file
32
packages/jsdoc/test/fixtures/aliasfores6export2.js
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
/** @module vegetables */
|
||||
|
||||
/**
|
||||
* A basket of delicious fruits.
|
||||
*
|
||||
* @alias module:fruits.Basket
|
||||
*/
|
||||
export default class Basket {
|
||||
constructor() {
|
||||
/**
|
||||
* The material that the basket is made from.
|
||||
*/
|
||||
this.material = 'canvas';
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties the basket.
|
||||
*/
|
||||
empty() {}
|
||||
|
||||
/**
|
||||
* Adds a sock to the basket.
|
||||
*
|
||||
* @alias module:laundry.Basket#addSock
|
||||
*/
|
||||
addSock() {}
|
||||
|
||||
/**
|
||||
* Fills the basket.
|
||||
*/
|
||||
fill() {}
|
||||
}
|
||||
73
packages/jsdoc/test/specs/documentation/aliasfores6export.js
Normal file
73
packages/jsdoc/test/specs/documentation/aliasfores6export.js
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright 2023 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('aliased default export for ES6 module', () => {
|
||||
describe('no `@module` tag', () => {
|
||||
const docSet = jsdoc.getDocSetFromFile('test/fixtures/aliasfores6export.js');
|
||||
|
||||
it('uses the alias for the class to create longnames for its methods', () => {
|
||||
const empty = docSet.getByLongname('module:fruits.Basket#empty')[0];
|
||||
|
||||
expect(empty).toBeObject();
|
||||
expect(empty.memberof).toBe('module:fruits.Basket');
|
||||
expect(empty.name).toBe('empty');
|
||||
});
|
||||
|
||||
it('uses the alias for the class to create longnames for its other members', () => {
|
||||
const material = docSet.getByLongname('module:fruits.Basket#material')[0];
|
||||
|
||||
expect(material).toBeObject();
|
||||
expect(material.memberof).toBe('module:fruits.Basket');
|
||||
expect(material.name).toBe('material');
|
||||
});
|
||||
});
|
||||
|
||||
describe('`@module` tag', () => {
|
||||
const docSet = jsdoc.getDocSetFromFile('test/fixtures/aliasfores6export2.js');
|
||||
|
||||
it('uses the alias for the class to create longnames for its methods', () => {
|
||||
const empty = docSet.getByLongname('module:fruits.Basket#empty')[0];
|
||||
|
||||
expect(empty).toBeObject();
|
||||
expect(empty.memberof).toBe('module:fruits.Basket');
|
||||
expect(empty.name).toBe('empty');
|
||||
});
|
||||
|
||||
it('uses the alias for the class to create longnames for its other members', () => {
|
||||
const material = docSet.getByLongname('module:fruits.Basket#material')[0];
|
||||
|
||||
expect(material).toBeObject();
|
||||
expect(material.memberof).toBe('module:fruits.Basket');
|
||||
expect(material.name).toBe('material');
|
||||
});
|
||||
|
||||
it('uses the correct longname if there is a nested alias', () => {
|
||||
const addSock = docSet.getByLongname('module:laundry.Basket#addSock')[0];
|
||||
|
||||
expect(addSock).toBeObject();
|
||||
expect(addSock.memberof).toBe('module:laundry.Basket');
|
||||
expect(addSock.name).toBe('addSock');
|
||||
});
|
||||
|
||||
it('uses the correct longname if a method with no alias follows a method with an alias', () => {
|
||||
const fill = docSet.getByLongname('module:fruits.Basket#fill')[0];
|
||||
|
||||
expect(fill).toBeObject();
|
||||
expect(fill.memberof).toBe('module:fruits.Basket');
|
||||
expect(fill.name).toBe('fill');
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user