mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
Tons of stuff depends on these methods; in contrast, these methods don't depend on any other JSDoc packages.
830 lines
24 KiB
JavaScript
830 lines
24 KiB
JavaScript
/*
|
|
Copyright 2010 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.
|
|
*/
|
|
|
|
import path from 'node:path';
|
|
|
|
import { astNode, Syntax } from '@jsdoc/ast';
|
|
import {
|
|
applyNamespace,
|
|
getLeadingScope,
|
|
getTrailingScope,
|
|
LONGNAMES,
|
|
MODULE_NAMESPACE,
|
|
nameIsLongname,
|
|
prototypeToPunc,
|
|
PUNC_TO_SCOPE,
|
|
SCOPE,
|
|
SCOPE_TO_PUNC,
|
|
toParts,
|
|
} from '@jsdoc/name';
|
|
import { Tag } from '@jsdoc/tag';
|
|
import _ from 'lodash';
|
|
import onChange from 'on-change';
|
|
|
|
const { isFunction } = astNode;
|
|
|
|
// Forward-declare Doclet class.
|
|
export let Doclet;
|
|
|
|
const ACCESS_LEVELS = ['package', 'private', 'protected', 'public'];
|
|
const ALL_SCOPE_NAMES = _.values(SCOPE.NAMES);
|
|
const DEFAULT_SCOPE = SCOPE.NAMES.STATIC;
|
|
// TODO: `class` should be on this list, right? What are the implications of adding it?
|
|
const GLOBAL_KINDS = ['constant', 'function', 'member', 'typedef'];
|
|
|
|
export const WATCHABLE_PROPS = [
|
|
'access',
|
|
'augments',
|
|
'borrowed',
|
|
'ignore',
|
|
'implements',
|
|
'kind',
|
|
'listens',
|
|
'longname',
|
|
'memberof',
|
|
'mixes',
|
|
'scope',
|
|
'undocumented',
|
|
];
|
|
|
|
WATCHABLE_PROPS.sort();
|
|
|
|
function fakeMeta(node) {
|
|
return {
|
|
type: node ? node.type : null,
|
|
node: node,
|
|
};
|
|
}
|
|
|
|
// use the meta info about the source code to guess what the doclet kind should be
|
|
// TODO: set this elsewhere (maybe @jsdoc/ast.astNode.getInfo)
|
|
function codeToKind(code) {
|
|
let kind = 'member';
|
|
const node = code.node;
|
|
|
|
if (isFunction(code.type) && code.type !== Syntax.MethodDefinition) {
|
|
kind = 'function';
|
|
} else if (code.type === Syntax.MethodDefinition && node) {
|
|
if (node.kind === 'constructor') {
|
|
kind = 'class';
|
|
} else if (node.kind !== 'get' && node.kind !== 'set') {
|
|
kind = 'function';
|
|
}
|
|
} else if (code.type === Syntax.ClassDeclaration || code.type === Syntax.ClassExpression) {
|
|
kind = 'class';
|
|
} else if (code.type === Syntax.ExportAllDeclaration) {
|
|
// this value will often be an Identifier for a variable, which isn't very useful
|
|
kind = codeToKind(fakeMeta(node.source));
|
|
} else if (
|
|
code.type === Syntax.ExportDefaultDeclaration ||
|
|
code.type === Syntax.ExportNamedDeclaration
|
|
) {
|
|
kind = codeToKind(fakeMeta(node.declaration));
|
|
} else if (code.type === Syntax.ExportSpecifier) {
|
|
// this value will often be an Identifier for a variable, which isn't very useful
|
|
kind = codeToKind(fakeMeta(node.local));
|
|
} else if (node && node.parent && isFunction(node.parent)) {
|
|
kind = 'param';
|
|
}
|
|
|
|
return kind;
|
|
}
|
|
|
|
function unwrap(docletSrc) {
|
|
if (!docletSrc) {
|
|
return '';
|
|
}
|
|
|
|
// note: keep trailing whitespace for @examples
|
|
// extra opening/closing stars are ignored
|
|
// left margin is considered a star and a space
|
|
// use the /m flag on regex to avoid having to guess what this platform's newline is
|
|
docletSrc =
|
|
// remove opening slash+stars
|
|
docletSrc
|
|
.replace(/^\/\*\*+/, '')
|
|
// replace closing star slash with end-marker
|
|
.replace(/\**\*\/$/, '\\Z')
|
|
// remove left margin like: spaces+star or spaces+end-marker
|
|
.replace(/^\s*(\* ?|\\Z)/gm, '')
|
|
// remove end-marker
|
|
.replace(/\s*\\Z$/g, '');
|
|
|
|
return docletSrc;
|
|
}
|
|
|
|
/**
|
|
* Convert the raw source of the doclet comment into an array of pseudo-Tag objects.
|
|
* @private
|
|
*/
|
|
function toTags(docletSrc) {
|
|
let parsedTag;
|
|
const tagData = [];
|
|
let tagText;
|
|
let tagTitle;
|
|
|
|
// split out the basic tags, keep surrounding whitespace
|
|
// like: @tagTitle tagBody
|
|
docletSrc
|
|
// replace splitter ats with an arbitrary sequence
|
|
.replace(/^(\s*)@(\S)/gm, '$1\\@$2')
|
|
// then split on that arbitrary sequence
|
|
.split('\\@')
|
|
.forEach(($) => {
|
|
if ($) {
|
|
parsedTag = $.match(/^(\S+)(?:\s+(\S[\s\S]*))?/);
|
|
|
|
if (parsedTag) {
|
|
tagTitle = parsedTag[1];
|
|
tagText = parsedTag[2];
|
|
|
|
if (tagTitle) {
|
|
tagData.push({
|
|
title: tagTitle,
|
|
text: tagText,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return tagData;
|
|
}
|
|
|
|
function fixDescription(docletSrc, { code }) {
|
|
let isClass;
|
|
|
|
if (!/^\s*@/.test(docletSrc) && docletSrc.replace(/\s/g, '').length) {
|
|
isClass =
|
|
code && (code.type === Syntax.ClassDeclaration || code.type === Syntax.ClassExpression);
|
|
|
|
docletSrc = `${isClass ? '@classdesc' : '@description'} ${docletSrc}`;
|
|
}
|
|
|
|
return docletSrc;
|
|
}
|
|
|
|
/**
|
|
* Resolves the following properties of a doclet:
|
|
*
|
|
* + `name`
|
|
* + `longname`
|
|
* + `memberof`
|
|
* + `variation`
|
|
*
|
|
* @private
|
|
*/
|
|
function resolve(doclet) {
|
|
let about = {};
|
|
let leadingScope;
|
|
let memberof = doclet.memberof || '';
|
|
let metaName;
|
|
let name = doclet.name ? String(doclet.name) : '';
|
|
let puncAndName;
|
|
let puncAndNameIndex;
|
|
|
|
// Change `MyClass.prototype.instanceMethod` to `MyClass#instanceMethod`
|
|
// (but not in function params, which lack `doclet.kind`).
|
|
// TODO: Check for specific `doclet.kind` values (probably `function`, `class`, and `module`).
|
|
if (name && doclet.kind) {
|
|
name = prototypeToPunc(name);
|
|
}
|
|
doclet.name = name;
|
|
|
|
// Does the doclet have an alias that identifies the memberof? If so, use it
|
|
if (doclet.alias) {
|
|
about = toParts(name);
|
|
|
|
if (about.memberof) {
|
|
memberof = about.memberof;
|
|
}
|
|
}
|
|
// Member of a var in an outer scope?
|
|
else if (name && !memberof && doclet.meta.code && doclet.meta.code.funcscope) {
|
|
name = doclet.longname = doclet.meta.code.funcscope + SCOPE.PUNC.INNER + name;
|
|
}
|
|
|
|
if (memberof || doclet.forceMemberof) {
|
|
// @memberof tag given
|
|
memberof = prototypeToPunc(memberof);
|
|
|
|
// The name is a complete longname, like `@name foo.bar` with `@memberof foo`.
|
|
if (name && nameIsLongname(name, memberof) && name !== memberof) {
|
|
about = toParts(name, doclet.forceMemberof ? memberof : undefined);
|
|
}
|
|
// The name and memberof are identical and refer to a module, like `@name module:foo` with
|
|
// `@memberof module:foo`.
|
|
else if (name && name === memberof && name.indexOf(MODULE_NAMESPACE) === 0) {
|
|
about = toParts(name, doclet.forceMemberof ? memberof : undefined);
|
|
}
|
|
// The name and memberof are identical, like `@name foo` with `@memberof foo`.
|
|
else if (name && name === memberof) {
|
|
doclet.scope = doclet.scope || DEFAULT_SCOPE;
|
|
name = memberof + SCOPE_TO_PUNC[doclet.scope] + name;
|
|
about = toParts(name, doclet.forceMemberof ? memberof : undefined);
|
|
}
|
|
// Like `@memberof foo#` or `@memberof foo~`.
|
|
else if (name && getTrailingScope(memberof)) {
|
|
about = toParts(memberof + name, doclet.forceMemberof ? memberof : undefined);
|
|
} else if (name && doclet.scope) {
|
|
about = toParts(
|
|
memberof + (SCOPE_TO_PUNC[doclet.scope] || '') + name,
|
|
doclet.forceMemberof ? memberof : undefined
|
|
);
|
|
}
|
|
} else {
|
|
// No memberof.
|
|
about = toParts(name);
|
|
}
|
|
|
|
if (about.name) {
|
|
doclet.name = about.name;
|
|
}
|
|
|
|
if (about.memberof) {
|
|
doclet.setMemberof(about.memberof);
|
|
}
|
|
|
|
if (about.longname && (!doclet.longname || doclet.longname === doclet.name)) {
|
|
doclet.setLongname(about.longname);
|
|
}
|
|
|
|
if (doclet.scope === SCOPE.NAMES.GLOBAL) {
|
|
// via @global tag?
|
|
doclet.setLongname(doclet.name);
|
|
doclet.memberof = undefined;
|
|
} else if (about.scope) {
|
|
if (about.memberof === LONGNAMES.GLOBAL) {
|
|
// via @memberof <global> ?
|
|
doclet.scope = SCOPE.NAMES.GLOBAL;
|
|
} else {
|
|
doclet.scope = PUNC_TO_SCOPE[about.scope];
|
|
}
|
|
} else if (doclet.name && doclet.memberof && !doclet.longname) {
|
|
leadingScope = getLeadingScope(doclet.name);
|
|
if (leadingScope) {
|
|
doclet.scope = PUNC_TO_SCOPE[leadingScope];
|
|
doclet.name = doclet.name.substr(1);
|
|
} else if (doclet.meta.code && doclet.meta.code.name) {
|
|
// HACK: Handle cases where an ES 2015 class is a static memberof something else, and
|
|
// the class has instance members. In these cases, we have to detect the instance
|
|
// members' scope by looking at the meta info. There's almost certainly a better way to
|
|
// do this...
|
|
metaName = String(doclet.meta.code.name);
|
|
puncAndName = SCOPE.PUNC.INSTANCE + doclet.name;
|
|
puncAndNameIndex = metaName.indexOf(puncAndName);
|
|
if (puncAndNameIndex !== -1 && puncAndNameIndex === metaName.length - puncAndName.length) {
|
|
doclet.scope = SCOPE.NAMES.INSTANCE;
|
|
}
|
|
}
|
|
|
|
doclet.scope = doclet.scope || DEFAULT_SCOPE;
|
|
doclet.setLongname(doclet.memberof + SCOPE_TO_PUNC[doclet.scope] + doclet.name);
|
|
}
|
|
|
|
if (about.variation) {
|
|
doclet.variation = about.variation;
|
|
}
|
|
|
|
// If we never found a longname, just use an empty string.
|
|
if (!doclet.longname) {
|
|
doclet.longname = '';
|
|
}
|
|
}
|
|
|
|
function removeGlobal(longname) {
|
|
const globalRegexp = new RegExp(`^${LONGNAMES.GLOBAL}\\.?`);
|
|
|
|
return longname.replace(globalRegexp, '');
|
|
}
|
|
|
|
/**
|
|
* Get the full path to the source file that is associated with a doclet.
|
|
*
|
|
* @private
|
|
* @param {module:@jsdoc/doclet.Doclet} The doclet to check for a filepath.
|
|
* @return {string} The path to the doclet's source file, or an empty string if the path is not
|
|
* available.
|
|
*/
|
|
function getFilepath(doclet) {
|
|
if (!doclet || !doclet.meta || !doclet.meta.filename) {
|
|
return '';
|
|
}
|
|
|
|
return path.join(doclet.meta.path || '', doclet.meta.filename);
|
|
}
|
|
|
|
function emitDocletChanged(emitter, doclet, property, oldValue, newValue) {
|
|
emitter.emit('docletChanged', { doclet, property, oldValue, newValue });
|
|
}
|
|
|
|
function clone(source, target, properties) {
|
|
properties.forEach((property) => {
|
|
const sourceProperty = source[property];
|
|
|
|
if (_.isFunction(sourceProperty)) {
|
|
// Do nothing.
|
|
} else if (_.isArray(sourceProperty)) {
|
|
target[property] = sourceProperty.slice();
|
|
} else if (_.isObject(sourceProperty)) {
|
|
target[property] = _.cloneDeep(sourceProperty);
|
|
} else {
|
|
target[property] = sourceProperty;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Copy all but a list of excluded properties from one of two doclets onto a target doclet. Prefers
|
|
* the primary doclet over the secondary doclet.
|
|
*
|
|
* @private
|
|
* @param {module:@jsdoc/doclet.Doclet} primary - The primary doclet.
|
|
* @param {module:@jsdoc/doclet.Doclet} secondary - The secondary doclet.
|
|
* @param {module:@jsdoc/doclet.Doclet} target - The doclet to which properties will be copied.
|
|
* @param {Array.<string>} exclude - The names of properties to exclude from copying.
|
|
*/
|
|
function copyMostProperties(primary, secondary, target, exclude) {
|
|
// Get names of primary and secondary properties that don't contain the value `undefined`.
|
|
const primaryPropertyNames = Object.getOwnPropertyNames(primary).filter(
|
|
(name) => !_.isUndefined(primary[name])
|
|
);
|
|
const primaryProperties = _.difference(primaryPropertyNames, exclude);
|
|
const secondaryPropertyNames = Object.getOwnPropertyNames(secondary).filter(
|
|
(name) => !_.isUndefined(secondary[name])
|
|
);
|
|
const secondaryProperties = _.difference(
|
|
secondaryPropertyNames,
|
|
exclude.concat(primaryProperties)
|
|
);
|
|
|
|
clone(primary, target, primaryProperties);
|
|
clone(secondary, target, secondaryProperties);
|
|
}
|
|
|
|
/**
|
|
* Copy specific properties from one of two doclets onto a target doclet, as long as the property
|
|
* has a non-falsy value and a length greater than 0. Prefers the primary doclet over the secondary
|
|
* doclet.
|
|
*
|
|
* @private
|
|
* @param {module:@jsdoc/doclet.Doclet} primary - The primary doclet.
|
|
* @param {module:@jsdoc/doclet.Doclet} secondary - The secondary doclet.
|
|
* @param {module:@jsdoc/doclet.Doclet} target - The doclet to which properties will be copied.
|
|
* @param {Array.<string>} include - The names of properties to copy.
|
|
*/
|
|
function copySpecificProperties(primary, secondary, target, include) {
|
|
include.forEach((property) => {
|
|
if (Object.hasOwn(primary, property) && primary[property] && primary[property].length) {
|
|
target[property] = _.cloneDeep(primary[property]);
|
|
} else if (
|
|
Object.hasOwn(secondary, property) &&
|
|
secondary[property] &&
|
|
secondary[property].length
|
|
) {
|
|
target[property] = _.cloneDeep(secondary[property]);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Combine 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.
|
|
*/
|
|
export function combineDoclets(primary, secondary) {
|
|
const copyMostPropertiesExclude = ['env', 'params', 'properties', 'undocumented'];
|
|
const copySpecificPropertiesInclude = ['params', 'properties'];
|
|
const target = new Doclet('', null, secondary.env);
|
|
|
|
// First, copy most properties to the target doclet.
|
|
copyMostProperties(primary, secondary, target, copyMostPropertiesExclude);
|
|
// Then copy a few specific properties to the target doclet, as long as they're not falsy and
|
|
// have a length greater than 0.
|
|
copySpecificProperties(primary, secondary, target, copySpecificPropertiesInclude);
|
|
|
|
return target;
|
|
}
|
|
|
|
function defineWatchableProp(doclet, prop) {
|
|
Object.defineProperty(doclet, prop, {
|
|
configurable: false,
|
|
enumerable: true,
|
|
get() {
|
|
return doclet.watchableProps[prop];
|
|
},
|
|
set(newValue) {
|
|
doclet.watchableProps[prop] = newValue;
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Represents a single JSDoc comment.
|
|
*
|
|
* @alias module:@jsdoc/doclet.Doclet
|
|
*/
|
|
Doclet = class {
|
|
#dictionary;
|
|
|
|
/**
|
|
* Create a doclet.
|
|
*
|
|
* @param {string} docletSrc - The raw source code of the jsdoc comment.
|
|
* @param {object} meta - Properties describing the code related to this comment.
|
|
* @param {object} env - JSDoc environment.
|
|
*/
|
|
constructor(docletSrc, meta, env) {
|
|
const accessConfig = env.config?.opts?.access?.slice() ?? [];
|
|
const emitter = env.emitter;
|
|
const boundDefineWatchableProp = defineWatchableProp.bind(null, this);
|
|
const boundEmitDocletChanged = emitDocletChanged.bind(null, emitter, this);
|
|
let newTags = [];
|
|
|
|
this.#dictionary = env.tags;
|
|
|
|
Object.defineProperty(this, 'accessConfig', {
|
|
value: accessConfig,
|
|
writable: true,
|
|
});
|
|
Object.defineProperty(this, 'env', {
|
|
value: env,
|
|
});
|
|
Object.defineProperty(this, 'watchableProps', {
|
|
value: {},
|
|
writable: true,
|
|
});
|
|
WATCHABLE_PROPS.forEach(boundDefineWatchableProp);
|
|
|
|
/** The original text of the comment from the source code. */
|
|
this.comment = docletSrc;
|
|
meta ??= {};
|
|
this.setMeta(meta);
|
|
docletSrc = fixDescription(unwrap(docletSrc), meta);
|
|
|
|
newTags = toTags.call(this, docletSrc);
|
|
for (let i = 0, l = newTags.length; i < l; i++) {
|
|
this.addTag(newTags[i].title, newTags[i].text);
|
|
}
|
|
this.postProcess();
|
|
|
|
// Now that we've set the doclet's initial properties, listen for changes to those properties,
|
|
// unless we were told not to.
|
|
if (meta._watch !== false) {
|
|
this.watchableProps = onChange(
|
|
this.watchableProps,
|
|
(propertyPath, newValue, oldValue) => {
|
|
let index;
|
|
let newArray;
|
|
let oldArray;
|
|
const property = propertyPath[0];
|
|
|
|
// Handle changes to arrays, like: `doclet.listens[0] = 'event:foo';`
|
|
if (propertyPath.length > 1) {
|
|
newArray = this.watchableProps[property].slice();
|
|
|
|
oldArray = newArray.slice();
|
|
// Update `oldArray` to contain the original value.
|
|
index = propertyPath[propertyPath.length - 1];
|
|
oldArray[index] = oldValue;
|
|
|
|
boundEmitDocletChanged(property, oldArray, newArray);
|
|
}
|
|
// Handle changes to primitive values.
|
|
else if (newValue !== oldValue) {
|
|
boundEmitDocletChanged(property, oldValue, newValue);
|
|
}
|
|
},
|
|
{ ignoreDetached: true, pathAsArray: true }
|
|
);
|
|
}
|
|
}
|
|
|
|
static clone(doclet) {
|
|
return combineDoclets(doclet, Doclet.emptyDoclet(doclet.env));
|
|
}
|
|
|
|
static emptyDoclet(env) {
|
|
return new Doclet('', {}, env);
|
|
}
|
|
|
|
// TODO: We call this method in the constructor _and_ in `jsdoc/src/handlers`. It appears that
|
|
// if we don't call the method twice, various doclet properties can be incorrect, including name
|
|
// and memberof.
|
|
/** Called once after all tags have been added. */
|
|
postProcess() {
|
|
if (!this.preserveName) {
|
|
resolve(this);
|
|
}
|
|
if (this.name && !this.longname) {
|
|
this.setLongname(this.name);
|
|
}
|
|
if (this.memberof === '') {
|
|
this.memberof = undefined;
|
|
}
|
|
|
|
if (!this.kind && this.meta && this.meta.code) {
|
|
this.addTag('kind', codeToKind(this.meta.code));
|
|
}
|
|
|
|
if (this.variation && this.longname && !/\)$/.test(this.longname)) {
|
|
this.longname += `(${this.variation})`;
|
|
}
|
|
|
|
// add in any missing param names
|
|
if (this.params && this.meta && this.meta.code && this.meta.code.paramnames) {
|
|
for (let i = 0, l = this.params.length; i < l; i++) {
|
|
if (!this.params[i].name) {
|
|
this.params[i].name = this.meta.code.paramnames[i] || '';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a tag to the doclet.
|
|
*
|
|
* @param {string} title - The title of the tag being added.
|
|
* @param {string} [text] - The text of the tag being added.
|
|
*/
|
|
addTag(title, text) {
|
|
const tagDef = this.#dictionary.lookUp(title);
|
|
const newTag = new Tag(title, text, this.meta, this.env);
|
|
|
|
if (tagDef && tagDef.onTagged) {
|
|
tagDef.onTagged(this, newTag);
|
|
}
|
|
|
|
if (!tagDef) {
|
|
this.tags = this.tags || [];
|
|
this.tags.push(newTag);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether the doclet represents a globally available symbol.
|
|
*
|
|
* @returns {boolean} `true` if the doclet represents a global; `false` otherwise.
|
|
*/
|
|
isGlobal() {
|
|
return this.scope === 'global' && GLOBAL_KINDS.includes(this.kind);
|
|
}
|
|
|
|
/**
|
|
* Check whether the doclet should be used to generate output.
|
|
*
|
|
* @returns {boolean} `true` if the doclet should be used to generate output; `false` otherwise.
|
|
*/
|
|
isVisible() {
|
|
const accessConfig = this.accessConfig;
|
|
|
|
// By default, we don't use:
|
|
//
|
|
// + Doclets that explicitly declare that they should be ignored
|
|
// + Doclets that claim to belong to an anonymous scope
|
|
// + "Undocumented" doclets (usually code with no JSDoc comment; might also include some odd
|
|
// artifacts of the parsing process)
|
|
if (this.ignore === true || this.memberof === '<anonymous>' || this.undocumented === true) {
|
|
return false;
|
|
}
|
|
|
|
// We also don't use private doclets by default, unless the user told us to use them.
|
|
if (
|
|
(!accessConfig.length ||
|
|
(!accessConfig.includes('all') && !accessConfig.includes('private'))) &&
|
|
this.access === 'private'
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (accessConfig.length && !accessConfig.includes('all')) {
|
|
// The string `undefined` needs special treatment.
|
|
if (
|
|
!accessConfig.includes('undefined') &&
|
|
(this.access === null || this.access === undefined)
|
|
) {
|
|
return false;
|
|
}
|
|
// For other access levels, we can just check whether the user asked us to use that level.
|
|
if (ACCESS_LEVELS.some((level) => !accessConfig.includes(level) && this.access === level)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Set the doclet's `longname` property.
|
|
*
|
|
* @param {string} longname - The longname for the doclet.
|
|
*/
|
|
setLongname(longname) {
|
|
/**
|
|
* The fully resolved symbol name.
|
|
* @type {string}
|
|
*/
|
|
longname = removeGlobal(longname);
|
|
if (this.#dictionary.isNamespace(this.kind)) {
|
|
longname = applyNamespace(longname, this.kind);
|
|
}
|
|
|
|
this.longname = longname;
|
|
}
|
|
|
|
/**
|
|
* Set the doclet's `memberof` property.
|
|
*
|
|
* @param {string} sid - The longname of the doclet's parent symbol.
|
|
*/
|
|
setMemberof(sid) {
|
|
/**
|
|
* The longname of the symbol that contains this one, if any.
|
|
* @type {string}
|
|
*/
|
|
this.memberof = removeGlobal(sid)
|
|
// TODO: Use `prototypeToPunc` instead?
|
|
.replace(/\.prototype/g, SCOPE.PUNC.INSTANCE);
|
|
}
|
|
|
|
/**
|
|
* Set the doclet's `scope` property. Must correspond to a scope name that is defined in
|
|
* {@link module:@jsdoc/name.SCOPE.NAMES}.
|
|
*
|
|
* @param {string} scope - The scope for the doclet relative to the symbol's parent.
|
|
* @throws {Error} If the scope name is not recognized.
|
|
*/
|
|
setScope(scope) {
|
|
let errorMessage;
|
|
let filepath;
|
|
|
|
if (!ALL_SCOPE_NAMES.includes(scope)) {
|
|
filepath = getFilepath(this);
|
|
|
|
errorMessage =
|
|
`The scope name "${scope}" is not recognized. Use one of the ` +
|
|
`following values: ${ALL_SCOPE_NAMES}`;
|
|
if (filepath) {
|
|
errorMessage += ` (Source file: ${filepath})`;
|
|
}
|
|
|
|
throw new Error(errorMessage);
|
|
}
|
|
|
|
this.scope = scope;
|
|
}
|
|
|
|
/**
|
|
* Add a symbol to this doclet's `borrowed` array.
|
|
*
|
|
* @param {string} source - The longname of the symbol that is the source.
|
|
* @param {string} target - The name the symbol is being assigned to.
|
|
*/
|
|
borrow(source, target) {
|
|
const about = { from: source };
|
|
|
|
if (target) {
|
|
about.as = target;
|
|
}
|
|
|
|
if (!this.borrowed) {
|
|
/**
|
|
* A list of symbols that are borrowed by this one, if any.
|
|
* @type {Array.<string>}
|
|
*/
|
|
this.borrowed = [];
|
|
}
|
|
this.borrowed.push(about);
|
|
}
|
|
|
|
mix(source) {
|
|
/**
|
|
* A list of symbols that are mixed into this one, if any.
|
|
* @type Array.<string>
|
|
*/
|
|
this.mixes = this.mixes || [];
|
|
this.mixes.push(source);
|
|
}
|
|
|
|
/**
|
|
* Add a symbol to the doclet's `augments` array.
|
|
*
|
|
* @param {string} base - The longname of the base symbol.
|
|
*/
|
|
augment(base) {
|
|
/**
|
|
* A list of symbols that are augmented by this one, if any.
|
|
* @type Array.<string>
|
|
*/
|
|
this.augments = this.augments || [];
|
|
this.augments.push(base);
|
|
}
|
|
|
|
/**
|
|
* Set the `meta` property of this doclet.
|
|
*
|
|
* @param {object} meta
|
|
*/
|
|
setMeta(meta) {
|
|
let pathname;
|
|
|
|
/**
|
|
* Information about the source code associated with this doclet.
|
|
* @namespace
|
|
*/
|
|
this.meta = this.meta || {};
|
|
|
|
if (meta.range) {
|
|
/**
|
|
* The positions of the first and last characters of the code associated with this doclet.
|
|
* @type Array.<number>
|
|
*/
|
|
this.meta.range = meta.range.slice();
|
|
}
|
|
|
|
if (meta.lineno) {
|
|
/**
|
|
* The name of the file containing the code associated with this doclet.
|
|
* @type string
|
|
*/
|
|
this.meta.filename = path.basename(meta.filename);
|
|
/**
|
|
* The line number of the code associated with this doclet.
|
|
* @type number
|
|
*/
|
|
this.meta.lineno = meta.lineno;
|
|
/**
|
|
* The column number of the code associated with this doclet.
|
|
* @type number
|
|
*/
|
|
this.meta.columnno = meta.columnno;
|
|
|
|
pathname = path.dirname(meta.filename);
|
|
if (pathname && pathname !== '.') {
|
|
this.meta.path = pathname;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Information about the code symbol.
|
|
* @namespace
|
|
*/
|
|
this.meta.code = this.meta.code || {};
|
|
if (meta.id) {
|
|
this.meta.code.id = meta.id;
|
|
}
|
|
if (meta.code) {
|
|
if (meta.code.name) {
|
|
/**
|
|
* The name of the symbol in the source code.
|
|
* @type {string}
|
|
*/
|
|
this.meta.code.name = meta.code.name;
|
|
}
|
|
if (meta.code.type) {
|
|
/**
|
|
* The type of the symbol in the source code.
|
|
* @type {string}
|
|
*/
|
|
this.meta.code.type = meta.code.type;
|
|
}
|
|
if (meta.code.node) {
|
|
Object.defineProperty(this.meta.code, 'node', {
|
|
value: meta.code.node,
|
|
enumerable: false,
|
|
});
|
|
}
|
|
if (meta.code.funcscope) {
|
|
this.meta.code.funcscope = meta.code.funcscope;
|
|
}
|
|
if (typeof meta.code.value !== 'undefined') {
|
|
/**
|
|
* The value of the symbol in the source code.
|
|
* @type {*}
|
|
*/
|
|
this.meta.code.value = meta.code.value;
|
|
}
|
|
if (meta.code.paramnames) {
|
|
this.meta.code.paramnames = meta.code.paramnames.slice();
|
|
}
|
|
}
|
|
}
|
|
};
|