mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
feat(jsdoc-doclet): add DocletStore, a reactive tracker for doclets
This commit is contained in:
parent
76ac64eaf2
commit
b2df642b31
@ -16,8 +16,9 @@
|
||||
import * as augment from './lib/augment.js';
|
||||
import { resolveBorrows } from './lib/borrow.js';
|
||||
import { combineDoclets, 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, Package, resolveBorrows, schema };
|
||||
export default { augment, combineDoclets, Doclet, Package, resolveBorrows, schema };
|
||||
export { augment, combineDoclets, Doclet, DocletStore, Package, resolveBorrows, schema };
|
||||
export default { augment, combineDoclets, Doclet, DocletStore, Package, resolveBorrows, schema };
|
||||
|
||||
@ -413,7 +413,7 @@ function updateImplements(implDoclets, implementedLongname) {
|
||||
implDoclets.forEach((implDoclet) => {
|
||||
implDoclet.implements ??= [];
|
||||
|
||||
if (!implDoclet.implements.includes(implementedLongname)) {
|
||||
if (!implDoclet.implements?.includes(implementedLongname)) {
|
||||
implDoclet.implements.push(implementedLongname);
|
||||
}
|
||||
});
|
||||
|
||||
304
packages/jsdoc-doclet/lib/doclet-store.js
Normal file
304
packages/jsdoc-doclet/lib/doclet-store.js
Normal file
@ -0,0 +1,304 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
import commonPathPrefix from 'common-path-prefix';
|
||||
import _ from 'lodash';
|
||||
|
||||
function addToSet(targetMap, key, value) {
|
||||
if (!targetMap.has(key)) {
|
||||
targetMap.set(key, new Set());
|
||||
}
|
||||
|
||||
targetMap.get(key).add(value);
|
||||
}
|
||||
|
||||
function diffArrays(value, previousValue = []) {
|
||||
return {
|
||||
added: _.difference(value, previousValue),
|
||||
removed: _.difference(previousValue, value),
|
||||
};
|
||||
}
|
||||
|
||||
function getSourcePath({ meta }) {
|
||||
return meta.path ? join(meta.path, meta.filename) : meta.filename;
|
||||
}
|
||||
|
||||
function removeFromSet(targetMap, key, value) {
|
||||
const set = targetMap.get(key);
|
||||
|
||||
if (set) {
|
||||
set.delete(value);
|
||||
// If the set is now empty, delete it from the map.
|
||||
if (set.size === 0) {
|
||||
targetMap.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class DocletStore {
|
||||
#commonPathPrefix;
|
||||
#docletChangedHandler;
|
||||
#eventBus;
|
||||
#newDocletHandler;
|
||||
#sourcePaths;
|
||||
|
||||
static #propertiesWithMaps = ['kind', 'longname', 'memberof'];
|
||||
static #propertyToMapName = new Map(
|
||||
DocletStore.#propertiesWithMaps.map((prop) => {
|
||||
return [prop, 'docletsBy' + _.capitalize(prop)];
|
||||
})
|
||||
);
|
||||
|
||||
static #propertiesWithSets = ['augments', 'borrowed', 'implements', 'mixes'];
|
||||
static #propertyToSetName = new Map(
|
||||
DocletStore.#propertiesWithSets.map((prop) => {
|
||||
return [prop, 'docletsWith' + _.capitalize(prop)];
|
||||
})
|
||||
);
|
||||
|
||||
constructor(dependencies) {
|
||||
this.#commonPathPrefix = null;
|
||||
this.#eventBus = dependencies.get('eventBus');
|
||||
this.#sourcePaths = new Map();
|
||||
|
||||
/** Doclets that are used to generate output. */
|
||||
this.doclets = new Set();
|
||||
/** @type Map<string, Set<Doclet>> */
|
||||
this.docletsByKind = new Map();
|
||||
/** @type Map<string, Set<Doclet>> */
|
||||
this.docletsByLongname = new Map();
|
||||
/** @type Map<string, Set<Doclet>> */
|
||||
this.docletsByMemberof = new Map();
|
||||
/** @type Map<string, Set<Doclet>> */
|
||||
this.docletsByNodeId = new Map();
|
||||
this.docletsWithAugments = new Set();
|
||||
this.docletsWithBorrowed = new Set();
|
||||
this.docletsWithImplements = new Set();
|
||||
this.docletsWithMixes = new Set();
|
||||
this.globals = new Set();
|
||||
/** @type Map<string, Set<Doclet>> */
|
||||
this.listenersByListensTo = new Map();
|
||||
|
||||
/** Doclets that aren't used to generate output. */
|
||||
this.unusedDoclets = new Set();
|
||||
|
||||
this.#docletChangedHandler = (e) => this.#handleDocletChanged(e, {});
|
||||
this.#newDocletHandler = (e) => this.#handleDocletChanged(e, { newDoclet: true });
|
||||
this.#eventBus.on('docletChanged', this.#docletChangedHandler);
|
||||
this.#eventBus.on('newDoclet', this.#newDocletHandler);
|
||||
}
|
||||
|
||||
#handleDocletChanged({ doclet, property, oldValue, newValue }, opts) {
|
||||
const isVisible = doclet.isVisible();
|
||||
const newDoclet = opts.newDoclet ?? false;
|
||||
const wasVisible = newDoclet ? false : this.doclets.has(doclet);
|
||||
const visibilityChanged = (() => {
|
||||
return newDoclet || (!wasVisible && isVisible) || (wasVisible && !isVisible);
|
||||
})();
|
||||
const docletInfo = {
|
||||
isGlobal: doclet.isGlobal(),
|
||||
isVisible,
|
||||
newDoclet,
|
||||
newValue,
|
||||
oldValue,
|
||||
setFnName: isVisible ? 'add' : 'delete',
|
||||
visibilityChanged,
|
||||
wasVisible,
|
||||
};
|
||||
|
||||
if (newDoclet) {
|
||||
this.#trackDocletByNodeId(doclet);
|
||||
}
|
||||
|
||||
if (visibilityChanged) {
|
||||
this.#toggleVisibility(doclet, docletInfo);
|
||||
}
|
||||
|
||||
// In the following cases, there's nothing more to do:
|
||||
//
|
||||
// + The doclet isn't visible, and we're seeing it for the first time.
|
||||
// + The doclet isn't visible, and its visibility didn't change.
|
||||
if (!isVisible && (newDoclet || !visibilityChanged)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update all watchable properties.
|
||||
this.#updateWatchableProperties(doclet, property, docletInfo);
|
||||
|
||||
// Update list of source paths for visible doclets.
|
||||
if (visibilityChanged) {
|
||||
this.#updateSourcePaths(doclet, docletInfo);
|
||||
}
|
||||
}
|
||||
|
||||
#toggleGlobal(doclet, { isGlobal, isVisible }) {
|
||||
if (isGlobal && isVisible) {
|
||||
this.globals.add(doclet);
|
||||
} else {
|
||||
this.globals.delete(doclet);
|
||||
}
|
||||
}
|
||||
|
||||
#toggleVisibility(doclet, { isVisible, setFnName }) {
|
||||
this.doclets[setFnName](doclet);
|
||||
this.unusedDoclets[isVisible ? 'delete' : 'add'](doclet);
|
||||
}
|
||||
|
||||
#trackDocletByNodeId(doclet) {
|
||||
const nodeId = doclet.meta?.code?.node?.nodeId;
|
||||
|
||||
if (nodeId) {
|
||||
addToSet(this.docletsByNodeId, nodeId, doclet);
|
||||
}
|
||||
}
|
||||
|
||||
#updateMapProperty(prop, oldKey, newKey, doclet, { isVisible, newDoclet, wasVisible }) {
|
||||
const map = this[DocletStore.#propertyToMapName.get(prop)];
|
||||
|
||||
// For `newDoclet` events, there's no "new key"; just use the one from the doclet.
|
||||
if (newDoclet) {
|
||||
newKey = doclet[prop];
|
||||
}
|
||||
|
||||
if (wasVisible && oldKey) {
|
||||
removeFromSet(map, oldKey, doclet);
|
||||
}
|
||||
if (isVisible && newKey) {
|
||||
addToSet(map, newKey, doclet);
|
||||
}
|
||||
}
|
||||
|
||||
#updateSetProperty(prop, value, setFnName) {
|
||||
const set = this[DocletStore.#propertyToSetName.get(prop)];
|
||||
|
||||
if (Object.hasOwn(value, prop) && value[prop]?.length) {
|
||||
set[setFnName](value);
|
||||
} else {
|
||||
set.delete(value);
|
||||
}
|
||||
}
|
||||
|
||||
#updateSourcePaths(doclet, { isVisible }) {
|
||||
const sourcePath = getSourcePath(doclet);
|
||||
|
||||
if (!sourcePath || !isVisible) {
|
||||
this.#sourcePaths.delete(doclet);
|
||||
} else if (sourcePath) {
|
||||
this.#sourcePaths.set(doclet, sourcePath);
|
||||
}
|
||||
|
||||
// Invalidate the cached common prefix for source paths.
|
||||
this.#commonPathPrefix = null;
|
||||
}
|
||||
|
||||
#updateWatchableProperties(doclet, property, docletInfo) {
|
||||
const {
|
||||
isGlobal,
|
||||
isVisible,
|
||||
newDoclet,
|
||||
newValue,
|
||||
oldValue,
|
||||
setFnName,
|
||||
visibilityChanged,
|
||||
wasVisible,
|
||||
} = docletInfo;
|
||||
|
||||
// `access` only affects visibility, which is handled above, so we ignore it here.
|
||||
if (visibilityChanged || property === 'augments') {
|
||||
this.#updateSetProperty('augments', doclet, setFnName);
|
||||
}
|
||||
if (visibilityChanged || property === 'borrowed') {
|
||||
this.#updateSetProperty('borrowed', doclet, setFnName);
|
||||
}
|
||||
// `ignore` only affects visibility, which is handled above, so we ignore it here.
|
||||
if (visibilityChanged || property === 'implements') {
|
||||
this.#updateSetProperty('implements', doclet, setFnName);
|
||||
}
|
||||
if (visibilityChanged || property === 'kind') {
|
||||
this.#toggleGlobal(doclet, { isGlobal, isVisible });
|
||||
this.#updateMapProperty('kind', oldValue, newValue, doclet, docletInfo);
|
||||
}
|
||||
if (visibilityChanged || property === 'listens') {
|
||||
let added;
|
||||
let diff;
|
||||
let removed;
|
||||
|
||||
if (newDoclet) {
|
||||
added = doclet.listens;
|
||||
removed = [];
|
||||
} else {
|
||||
diff = diffArrays(newValue, oldValue);
|
||||
added = diff.added;
|
||||
removed = diff.removed;
|
||||
}
|
||||
|
||||
if (added && isVisible) {
|
||||
added.forEach((listensTo) => addToSet(this.listenersByListensTo, listensTo, doclet));
|
||||
}
|
||||
if (removed && wasVisible) {
|
||||
removed.forEach((listensTo) => removeFromSet(this.listenersByListensTo, listensTo, doclet));
|
||||
}
|
||||
}
|
||||
if (visibilityChanged || property === 'longname') {
|
||||
this.#updateMapProperty('longname', oldValue, newValue, doclet, docletInfo);
|
||||
}
|
||||
if (visibilityChanged || property === 'memberof') {
|
||||
this.#updateMapProperty('memberof', oldValue, newValue, doclet, docletInfo);
|
||||
}
|
||||
if (visibilityChanged || property === 'mixes') {
|
||||
this.#updateSetProperty('mixes', doclet, setFnName);
|
||||
}
|
||||
if (visibilityChanged || property === 'scope') {
|
||||
this.#toggleGlobal(doclet, { isGlobal, isVisible });
|
||||
}
|
||||
// `undocumented` only affects visibility, which is handled above, so we ignore it here.
|
||||
}
|
||||
|
||||
_removeListeners() {
|
||||
this.#eventBus.removeListener('docletChanged', this.#docletChangedHandler);
|
||||
this.#eventBus.removeListener('newDoclet', this.#newDocletHandler);
|
||||
}
|
||||
|
||||
get commonPathPrefix() {
|
||||
let commonPrefix = '';
|
||||
const sourcePaths = this.sourcePaths;
|
||||
|
||||
if (this.#commonPathPrefix !== null) {
|
||||
return this.#commonPathPrefix;
|
||||
}
|
||||
|
||||
if (sourcePaths.length === 1) {
|
||||
// If there's only one filepath, then the common prefix is just its dirname.
|
||||
commonPrefix = dirname(sourcePaths[0]);
|
||||
} else if (sourcePaths.length > 1) {
|
||||
// Remove the trailing slash if present.
|
||||
commonPrefix = commonPathPrefix(sourcePaths).replace(/[\\/]$/, '');
|
||||
}
|
||||
|
||||
this.#commonPathPrefix = commonPrefix;
|
||||
|
||||
return commonPrefix;
|
||||
}
|
||||
|
||||
get longnames() {
|
||||
return Array.from(this.docletsByLongname.keys());
|
||||
}
|
||||
|
||||
get sourcePaths() {
|
||||
return Array.from(this.#sourcePaths.values());
|
||||
}
|
||||
}
|
||||
@ -19,6 +19,7 @@ import { astNode, Syntax } from '@jsdoc/ast';
|
||||
import { name as jsdocName } from '@jsdoc/core';
|
||||
import { Tag } from '@jsdoc/tag';
|
||||
import _ from 'lodash';
|
||||
import onChange from 'on-change';
|
||||
|
||||
const {
|
||||
applyNamespace,
|
||||
@ -36,6 +37,7 @@ const {
|
||||
const { isFunction } = astNode;
|
||||
|
||||
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'];
|
||||
@ -55,6 +57,8 @@ export const WATCHABLE_PROPS = [
|
||||
'undocumented',
|
||||
];
|
||||
|
||||
WATCHABLE_PROPS.sort();
|
||||
|
||||
function fakeMeta(node) {
|
||||
return {
|
||||
type: node ? node.type : null,
|
||||
@ -318,20 +322,22 @@ function getFilepath(doclet) {
|
||||
return path.join(doclet.meta.path || '', doclet.meta.filename);
|
||||
}
|
||||
|
||||
function emitDocletChanged(eventBus, doclet, property, oldValue, newValue) {
|
||||
eventBus.emit('docletChanged', { doclet, property, oldValue, newValue });
|
||||
}
|
||||
|
||||
function clone(source, target, properties) {
|
||||
properties.forEach((property) => {
|
||||
switch (typeof source[property]) {
|
||||
case 'function':
|
||||
// do nothing
|
||||
break;
|
||||
const sourceProperty = source[property];
|
||||
|
||||
case 'object':
|
||||
target[property] = _.cloneDeep(source[property]);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
target[property] = 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -382,15 +388,26 @@ function copySpecificProperties(primary, secondary, target, include) {
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
export class Doclet {
|
||||
#accessConfig;
|
||||
#dictionary;
|
||||
#eventBus;
|
||||
|
||||
/**
|
||||
* Create a doclet.
|
||||
@ -400,57 +417,66 @@ export class Doclet {
|
||||
* @param {object} dependencies - JSDoc dependencies.
|
||||
*/
|
||||
constructor(docletSrc, meta, dependencies) {
|
||||
const accessConfig = dependencies.get('config')?.opts?.access?.slice() ?? [];
|
||||
const eventBus = dependencies.get('eventBus');
|
||||
const boundDefineWatchableProp = defineWatchableProp.bind(null, this);
|
||||
const boundEmitDocletChanged = emitDocletChanged.bind(null, eventBus, this);
|
||||
let newTags = [];
|
||||
|
||||
meta = meta || {};
|
||||
this.#accessConfig = dependencies.get('config')?.opts?.access ?? [];
|
||||
this.#dictionary = dependencies.get('tags');
|
||||
this.#eventBus = dependencies.get('eventBus');
|
||||
|
||||
Object.defineProperty(this, 'accessConfig', {
|
||||
value: accessConfig,
|
||||
writable: true,
|
||||
});
|
||||
Object.defineProperty(this, 'dependencies', {
|
||||
enumerable: false,
|
||||
value: dependencies,
|
||||
});
|
||||
Object.defineProperty(this, 'watchableProps', {
|
||||
enumerable: false,
|
||||
value: {},
|
||||
writable: true,
|
||||
});
|
||||
for (const prop of WATCHABLE_PROPS) {
|
||||
Object.defineProperty(this, prop, {
|
||||
enumerable: true,
|
||||
get() {
|
||||
return this.watchableProps[prop];
|
||||
},
|
||||
set(newValue) {
|
||||
this.#setWatchableProperty(prop, newValue);
|
||||
},
|
||||
});
|
||||
}
|
||||
WATCHABLE_PROPS.forEach(boundDefineWatchableProp);
|
||||
|
||||
/** The original text of the comment from the source code. */
|
||||
this.comment = docletSrc;
|
||||
meta ??= {};
|
||||
this.setMeta(meta);
|
||||
|
||||
docletSrc = unwrap(docletSrc);
|
||||
docletSrc = fixDescription(docletSrc, 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();
|
||||
}
|
||||
|
||||
#setWatchableProperty(name, newValue) {
|
||||
const oldValue = this.watchableProps[name];
|
||||
// Now that we've set the doclet's initial properties, listen for changes to those properties.
|
||||
this.watchableProps = onChange(
|
||||
this.watchableProps,
|
||||
(propertyPath, newValue, oldValue) => {
|
||||
let index;
|
||||
let newArray;
|
||||
let oldArray;
|
||||
const property = propertyPath[0];
|
||||
|
||||
if (newValue !== oldValue) {
|
||||
this.watchableProps[name] = newValue;
|
||||
this.#eventBus.emit('docletChanged', { doclet: this, property: name, oldValue, newValue });
|
||||
}
|
||||
// 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 }
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: We call this method in the constructor _and_ in `jsdoc/src/handlers`. It appears that
|
||||
@ -521,7 +547,7 @@ export class Doclet {
|
||||
* @returns {boolean} `true` if the doclet should be used to generate output; `false` otherwise.
|
||||
*/
|
||||
isVisible() {
|
||||
const accessConfig = this.#accessConfig;
|
||||
const accessConfig = this.accessConfig;
|
||||
|
||||
// By default, we don't use:
|
||||
//
|
||||
@ -569,10 +595,12 @@ export class Doclet {
|
||||
* The fully resolved symbol name.
|
||||
* @type {string}
|
||||
*/
|
||||
this.longname = removeGlobal(longname);
|
||||
longname = removeGlobal(longname);
|
||||
if (this.#dictionary.isNamespace(this.kind)) {
|
||||
this.longname = applyNamespace(this.longname, this.kind);
|
||||
longname = applyNamespace(longname, this.kind);
|
||||
}
|
||||
|
||||
this.longname = longname;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -600,14 +628,13 @@ export class Doclet {
|
||||
setScope(scope) {
|
||||
let errorMessage;
|
||||
let filepath;
|
||||
const scopeNames = _.values(SCOPE.NAMES);
|
||||
|
||||
if (!scopeNames.includes(scope)) {
|
||||
if (!ALL_SCOPE_NAMES.includes(scope)) {
|
||||
filepath = getFilepath(this);
|
||||
|
||||
errorMessage =
|
||||
`The scope name "${scope}" is not recognized. Use one of the ` +
|
||||
`following values: ${scopeNames}`;
|
||||
`following values: ${ALL_SCOPE_NAMES}`;
|
||||
if (filepath) {
|
||||
errorMessage += ` (Source file: ${filepath})`;
|
||||
}
|
||||
|
||||
453
packages/jsdoc-doclet/package-lock.json
generated
453
packages/jsdoc-doclet/package-lock.json
generated
@ -9,7 +9,12 @@
|
||||
"version": "0.2.4",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jsdoc/ast": "^0.2.4",
|
||||
"@jsdoc/core": "^0.5.4",
|
||||
"@jsdoc/tag": "^0.2.4",
|
||||
"@jsdoc/util": "^0.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"on-change": "^4.0.2",
|
||||
"strip-bom": "^5.0.0"
|
||||
}
|
||||
},
|
||||
@ -76,10 +81,417 @@
|
||||
"node": ">=v18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.22.13",
|
||||
"chalk": "^2.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
||||
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.22.16",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz",
|
||||
"integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==",
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsdoc/ast": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@jsdoc/ast/-/ast-0.2.4.tgz",
|
||||
"integrity": "sha512-PZSf4ivb7SWEaPgJwXYHjYlH/UxbgMJ5inZ1VHIDS+/w7nDKocnqoknhK7oliupWqIoBkT+KkxA2jHwl/43TRQ==",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.22.16",
|
||||
"@jsdoc/core": "^0.5.4",
|
||||
"@jsdoc/util": "^0.3.0",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsdoc/core": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@jsdoc/core/-/core-0.5.4.tgz",
|
||||
"integrity": "sha512-bLqJP6fKGSvgqE41QSdShIe00oaSgkHRGStDjOBXQhWTTqVspX8tp6RERqcixwEesW147gRHFEMfJK/zyGd+bA==",
|
||||
"dependencies": {
|
||||
"bottlejs": "^2.0.1",
|
||||
"cosmiconfig": "^8.3.6",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"strip-bom": "^5.0.0",
|
||||
"strip-json-comments": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsdoc/tag": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@jsdoc/tag/-/tag-0.2.4.tgz",
|
||||
"integrity": "sha512-OEwYdcD+FSKln+XWX6JAGtrQWSrbkAvyKOCknXXorBzNwC/LPmJSYpXYDPIwy80RmMxnsq/jXmfscMeoKrPimA==",
|
||||
"dependencies": {
|
||||
"@jsdoc/ast": "^0.2.4",
|
||||
"@jsdoc/core": "^0.5.4",
|
||||
"@jsdoc/util": "^0.3.0",
|
||||
"catharsis": "^0.9.0",
|
||||
"common-path-prefix": "^3.0.0",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsdoc/util": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@jsdoc/util/-/util-0.3.0.tgz",
|
||||
"integrity": "sha512-l9uSPSKGvul8TAEvahtLHW5cY9VF9ocygEc7cfWHEkopO0QPlgcDagqY8jm93pzsDSM6ITdfJ9OfkfvLlts7PQ==",
|
||||
"dependencies": {
|
||||
"klaw-sync": "^6.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"ow": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sindresorhus/is": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz",
|
||||
"integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==",
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
},
|
||||
"node_modules/bottlejs": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bottlejs/-/bottlejs-2.0.1.tgz",
|
||||
"integrity": "sha512-50T0bzqeAqZ+//kgjdDxNu7UP8Je04isNPyHPwwOOPoeZmtVESkuF9nwkWEqSEd9Sw1yJ1oaoHBAMxe/wG4Zzg=="
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-4.1.0.tgz",
|
||||
"integrity": "sha512-aBMbD1Xxay75ViYezwT40aQONfr+pSXTHwNKvIXhXD6+LY3F1dLIcceoC5OZKBVHbXcysz1hL9D2w0JJIMXpUw==",
|
||||
"engines": {
|
||||
"node": ">=12.20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/catharsis": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz",
|
||||
"integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk/node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
|
||||
},
|
||||
"node_modules/common-path-prefix": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz",
|
||||
"integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w=="
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "8.3.6",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
|
||||
"integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
|
||||
"dependencies": {
|
||||
"import-fresh": "^3.3.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"parse-json": "^5.2.0",
|
||||
"path-type": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/d-fischer"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.9.5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/dot-prop": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-7.2.0.tgz",
|
||||
"integrity": "sha512-Ol/IPXUARn9CSbkrdV4VJo7uCy1I3VuSiWCaFSg+8BdUOzF9n3jefIpcgAydvUZbTdEBZs2vEiTiS9m61ssiDA==",
|
||||
"dependencies": {
|
||||
"type-fest": "^2.11.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
|
||||
"dependencies": {
|
||||
"parent-module": "^1.0.0",
|
||||
"resolve-from": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
|
||||
},
|
||||
"node_modules/klaw-sync": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
|
||||
"integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.1.11"
|
||||
}
|
||||
},
|
||||
"node_modules/lines-and-columns": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
|
||||
},
|
||||
"node_modules/on-change": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/on-change/-/on-change-4.0.2.tgz",
|
||||
"integrity": "sha512-cMtCyuJmTx/bg2HCpHo3ZLeF7FZnBOapLqZHr2AlLeJ5Ul0Zu2mUJJz051Fdwu/Et2YW04ZD+TtU+gVy0ACNCA==",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/on-change?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ow": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ow/-/ow-1.1.1.tgz",
|
||||
"integrity": "sha512-sJBRCbS5vh1Jp9EOgwp1Ws3c16lJrUkJYlvWTYC03oyiYVwS/ns7lKRWow4w4XjDyTrA2pplQv4B2naWSR6yDA==",
|
||||
"dependencies": {
|
||||
"@sindresorhus/is": "^5.3.0",
|
||||
"callsites": "^4.0.0",
|
||||
"dot-prop": "^7.2.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"vali-date": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
||||
"dependencies": {
|
||||
"callsites": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parent-module/node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-json": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"error-ex": "^1.3.1",
|
||||
"json-parse-even-better-errors": "^2.3.0",
|
||||
"lines-and-columns": "^1.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-bom": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-5.0.0.tgz",
|
||||
@ -90,6 +502,47 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz",
|
||||
"integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==",
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
|
||||
"integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
|
||||
"engines": {
|
||||
"node": ">=12.20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/vali-date": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz",
|
||||
"integrity": "sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@
|
||||
"@jsdoc/tag": "^0.2.4",
|
||||
"@jsdoc/util": "^0.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"on-change": "^4.0.2",
|
||||
"strip-bom": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +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 { DocletStore } from '../../lib/doclet-store.js';
|
||||
import { Package } from '../../lib/package.js';
|
||||
import * as schema from '../../lib/schema.js';
|
||||
|
||||
@ -43,6 +44,12 @@ describe('@jsdoc/doclet', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('DocletStore', () => {
|
||||
it('is lib/doclet.DocletStore', () => {
|
||||
expect(doclet.DocletStore).toEqual(DocletStore);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Package', () => {
|
||||
it('is lib/package.Package', () => {
|
||||
expect(doclet.Package).toEqual(Package);
|
||||
|
||||
875
packages/jsdoc-doclet/test/specs/lib/doclet-store.js
Normal file
875
packages/jsdoc-doclet/test/specs/lib/doclet-store.js
Normal file
@ -0,0 +1,875 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/* global jsdoc */
|
||||
import { Doclet } from '../../../lib/doclet.js';
|
||||
import * as docletStore from '../../../lib/doclet-store.js';
|
||||
|
||||
const { DocletStore } = docletStore;
|
||||
|
||||
function makeDoclet(comment, meta, deps) {
|
||||
let doclet;
|
||||
|
||||
deps ??= jsdoc.deps;
|
||||
doclet = new Doclet(`/**\n${comment.join('\n')}\n*/`, meta, deps);
|
||||
deps.get('eventBus').emit('newDoclet', { doclet });
|
||||
|
||||
return doclet;
|
||||
}
|
||||
|
||||
describe('@jsdoc/doclet/lib/doclet-store', () => {
|
||||
it('exists', () => {
|
||||
expect(docletStore).toBeObject();
|
||||
});
|
||||
|
||||
it('has a DocletStore class', () => {
|
||||
expect(docletStore.DocletStore).toBeFunction();
|
||||
});
|
||||
|
||||
describe('DocletStore', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
store = new DocletStore(jsdoc.deps);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store._removeListeners();
|
||||
});
|
||||
|
||||
it('is not constructable with no arguments', () => {
|
||||
expect(() => new DocletStore()).toThrow();
|
||||
});
|
||||
|
||||
it('is constructable when dependencies are passed in', () => {
|
||||
expect(() => new DocletStore(jsdoc.deps)).not.toThrow();
|
||||
});
|
||||
|
||||
it('has a `commonPathPrefix` property', () => {
|
||||
expect(store.commonPathPrefix).toBeEmptyString();
|
||||
});
|
||||
|
||||
it('has a `doclets` property', () => {
|
||||
expect(store.doclets).toBeEmptySet();
|
||||
});
|
||||
|
||||
it('has a `docletsByKind` property', () => {
|
||||
expect(store.docletsByKind).toBeEmptyMap();
|
||||
});
|
||||
|
||||
it('has a `docletsByLongname` property', () => {
|
||||
expect(store.docletsByLongname).toBeEmptyMap();
|
||||
});
|
||||
|
||||
it('has a `docletsByMemberof` property', () => {
|
||||
expect(store.docletsByMemberof).toBeEmptyMap();
|
||||
});
|
||||
|
||||
it('has a `docletsWithBorrowed` property', () => {
|
||||
expect(store.docletsWithBorrowed).toBeEmptySet();
|
||||
});
|
||||
|
||||
it('has a `globals` property', () => {
|
||||
expect(store.globals).toBeEmptySet();
|
||||
});
|
||||
|
||||
it('has a `listenersByListensTo` property', () => {
|
||||
expect(store.listenersByListensTo).toBeEmptyMap();
|
||||
});
|
||||
|
||||
it('has a `longnames` property', () => {
|
||||
expect(store.longnames).toBeEmptyArray();
|
||||
});
|
||||
|
||||
it('has a `sourcePaths` property', () => {
|
||||
expect(store.sourcePaths).toBeEmptyArray();
|
||||
});
|
||||
|
||||
it('has an `unusedDoclets` property', () => {
|
||||
expect(store.unusedDoclets).toBeEmptySet();
|
||||
});
|
||||
|
||||
describe('add', () => {
|
||||
describe('visible doclets', () => {
|
||||
let doclet;
|
||||
|
||||
it('adds the doclet to the list of visible doclets when appropriate', () => {
|
||||
doclet = makeDoclet(['@class', '@memberof foo', '@name Bar']);
|
||||
|
||||
expect(doclet.isVisible()).toBeTrue();
|
||||
expect(store.doclets).toHave(doclet);
|
||||
});
|
||||
|
||||
it('never adds visible doclets to the list of unused doclets', () => {
|
||||
doclet = makeDoclet(['@class', '@memberof foo', '@name Bar']);
|
||||
|
||||
expect(doclet.isVisible()).toBeTrue();
|
||||
expect(store.unusedDoclets).toBeEmptySet();
|
||||
});
|
||||
|
||||
it('adds visible doclets to the map of doclets by kind', () => {
|
||||
doclet = makeDoclet(['@class', '@memberof foo', '@name Bar']);
|
||||
|
||||
expect(doclet.isVisible()).toBeTrue();
|
||||
expect(store.docletsByKind.get('class')).toHave(doclet);
|
||||
});
|
||||
|
||||
it('adds visible doclets to the map of doclets by longname', () => {
|
||||
doclet = makeDoclet(['@class', '@memberof foo', '@name Bar']);
|
||||
|
||||
expect(doclet.isVisible()).toBeTrue();
|
||||
expect(store.docletsByLongname.get('foo.Bar')).toHave(doclet);
|
||||
});
|
||||
|
||||
it('adds visible doclets to the map of doclets by memberof when appropriate', () => {
|
||||
doclet = makeDoclet(['@class', '@memberof foo', '@name Bar']);
|
||||
|
||||
expect(doclet.isVisible()).toBeTrue();
|
||||
expect(store.docletsByMemberof.get('foo')).toHave(doclet);
|
||||
});
|
||||
|
||||
it('adds doclets that augment others to the list of doclets that augment', () => {
|
||||
doclet = makeDoclet(['@class', '@name Qux', '@extends foo.Bar']);
|
||||
|
||||
expect(store.docletsWithAugments).toHave(doclet);
|
||||
});
|
||||
|
||||
it('does not add doclets that do not augment others to the list of doclets that augment', () => {
|
||||
doclet = makeDoclet(['@class', '@name Qux']);
|
||||
|
||||
expect(store.docletsWithAugments).not.toHave(doclet);
|
||||
});
|
||||
|
||||
it('adds doclets that borrow others to the list of doclets that borrow', () => {
|
||||
doclet = makeDoclet(['@class', '@name Qux', '@borrows foo.Bar#baz']);
|
||||
|
||||
expect(store.docletsWithBorrowed).toHave(doclet);
|
||||
});
|
||||
|
||||
it('does not add doclets that do not borrow others to the list of doclets that borrow', () => {
|
||||
doclet = makeDoclet(['@class', '@name Qux']);
|
||||
|
||||
expect(store.docletsWithBorrowed).not.toHave(doclet);
|
||||
});
|
||||
|
||||
it('adds doclets that implement others to the list of doclets that implement', () => {
|
||||
doclet = makeDoclet(['@class', '@name Qux', '@implements IQux']);
|
||||
|
||||
expect(store.docletsWithImplements).toHave(doclet);
|
||||
});
|
||||
|
||||
it('does not add doclets that do not implement others to the list of doclets that implement', () => {
|
||||
doclet = makeDoclet(['@class', '@name Qux']);
|
||||
|
||||
expect(store.docletsWithImplements).not.toHave(doclet);
|
||||
});
|
||||
|
||||
it('adds doclets that mix others to the list of doclets that mix', () => {
|
||||
doclet = makeDoclet(['@class', '@name Qux', '@mixes foo']);
|
||||
|
||||
expect(store.docletsWithMixes).toHave(doclet);
|
||||
});
|
||||
|
||||
it('does not add doclets that do not mix others to the list of doclets that mix', () => {
|
||||
doclet = makeDoclet(['@class', '@name Qux']);
|
||||
|
||||
expect(store.docletsWithMixes).not.toHave(doclet);
|
||||
});
|
||||
|
||||
it('adds visible doclets to the list of globals when appropriate', () => {
|
||||
doclet = makeDoclet(['@function', '@global', '@name baz']);
|
||||
|
||||
expect(doclet.isVisible()).toBeTrue();
|
||||
expect(store.globals).toHave(doclet);
|
||||
});
|
||||
|
||||
it('does not add doclets that are global, but not visible, to the list of globals', () => {
|
||||
doclet = makeDoclet(['@function', '@global', '@name baz', '@ignore']);
|
||||
|
||||
expect(doclet.isVisible()).toBeFalse();
|
||||
expect(store.globals).toBeEmptySet();
|
||||
});
|
||||
|
||||
it('adds visible doclets to the map of listeners by listens to when appropriate', () => {
|
||||
doclet = makeDoclet(['@function', '@listens event:bar', '@name foo']);
|
||||
|
||||
expect(doclet.isVisible()).toBeTrue();
|
||||
expect(store.listenersByListensTo.get('event:bar')).toHave(doclet);
|
||||
});
|
||||
|
||||
it('adds the source paths for visible doclets to the list of source paths', () => {
|
||||
const meta = {
|
||||
filename: '/Users/carolr/code/foo.js',
|
||||
lineno: 1,
|
||||
};
|
||||
|
||||
doclet = makeDoclet(['@function', '@global', '@name baz'], meta);
|
||||
|
||||
expect(doclet.isVisible()).toBeTrue();
|
||||
expect(store.sourcePaths).toContain('/Users/carolr/code/foo.js');
|
||||
});
|
||||
});
|
||||
|
||||
describe('unused doclets', () => {
|
||||
let doclet;
|
||||
|
||||
it('adds the doclet to the list of unused doclets when appropriate', () => {
|
||||
doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@ignore']);
|
||||
|
||||
expect(doclet.isVisible()).toBeFalse();
|
||||
expect(store.unusedDoclets).toHave(doclet);
|
||||
});
|
||||
|
||||
it('does not add unused doclets to the list of visible doclets', () => {
|
||||
doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@ignore']);
|
||||
|
||||
expect(doclet.isVisible()).toBeFalse();
|
||||
expect(store.doclets).toBeEmptySet();
|
||||
});
|
||||
|
||||
it('does not add unused doclets to the map of doclets by kind', () => {
|
||||
doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@ignore']);
|
||||
|
||||
expect(doclet.isVisible()).toBeFalse();
|
||||
expect(store.docletsByKind).toBeEmptyMap();
|
||||
});
|
||||
|
||||
it('does not add unused doclets to the map of doclets by longname', () => {
|
||||
doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@ignore']);
|
||||
|
||||
expect(doclet.isVisible()).toBeFalse();
|
||||
expect(store.docletsByLongname).toBeEmptyMap();
|
||||
});
|
||||
|
||||
it('does not add unused doclets to the map of doclets by memberof', () => {
|
||||
doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@ignore']);
|
||||
|
||||
expect(doclet.isVisible()).toBeFalse();
|
||||
expect(store.docletsByMemberof).toBeEmptyMap();
|
||||
});
|
||||
|
||||
it('does not add doclets that augment others to the list of doclets that augment', () => {
|
||||
doclet = makeDoclet(['@class', '@name Qux', '@extends foo.Bar', '@ignore']);
|
||||
|
||||
expect(doclet.isVisible()).toBeFalse();
|
||||
expect(store.docletsWithAugments).toBeEmptySet();
|
||||
});
|
||||
|
||||
it('does not add doclets that borrow others to the list of doclets that borrow', () => {
|
||||
doclet = makeDoclet(['@class', '@name Qux', '@borrows foo.Bar#baz', '@ignore']);
|
||||
|
||||
expect(doclet.isVisible()).toBeFalse();
|
||||
expect(store.docletsWithBorrowed).toBeEmptySet();
|
||||
});
|
||||
|
||||
it('does not add doclets that implement others to the list of doclets that implement', () => {
|
||||
doclet = makeDoclet(['@class', '@name Qux', '@implements IQux', '@ignore']);
|
||||
|
||||
expect(doclet.isVisible()).toBeFalse();
|
||||
expect(store.docletsWithImplements).toBeEmptySet();
|
||||
});
|
||||
|
||||
it('does not add doclets that mix others to the list of doclets that mix', () => {
|
||||
doclet = makeDoclet(['@class', '@name Qux', '@mixes foo', '@ignore']);
|
||||
|
||||
expect(doclet.isVisible()).toBeFalse();
|
||||
expect(store.docletsWithMixes).toBeEmptySet();
|
||||
});
|
||||
|
||||
it('does not add unused doclets to the list of globals', () => {
|
||||
doclet = makeDoclet(['@function', '@global', '@name baz', '@ignore']);
|
||||
|
||||
expect(doclet.isVisible()).toBeFalse();
|
||||
expect(store.globals).toBeEmptySet();
|
||||
});
|
||||
|
||||
it('does not add unused doclets to the map of listeners by listens to', () => {
|
||||
doclet = makeDoclet(['@function', '@listens event:bar', '@name foo', '@ignore']);
|
||||
|
||||
expect(doclet.isVisible()).toBeFalse();
|
||||
expect(store.listenersByListensTo).toBeEmptyMap();
|
||||
});
|
||||
|
||||
it('does not add longnames for unused doclets to the list of longnames', () => {
|
||||
expect(store.longnames).toBeEmptyArray();
|
||||
});
|
||||
|
||||
it('does not add source paths for unused doclets to the list of source paths', () => {
|
||||
const meta = {
|
||||
filename: '/Users/carolr/code/foo.js',
|
||||
lineno: 1,
|
||||
};
|
||||
|
||||
doclet = makeDoclet(['@function', '@global', '@name baz', '@ignore'], meta);
|
||||
|
||||
expect(doclet.isVisible()).toBeFalse();
|
||||
expect(store.sourcePaths).toBeEmptyArray();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('commonPathPrefix', () => {
|
||||
it('returns an empty string if there are no paths', () => {
|
||||
expect(store.commonPathPrefix).toBeEmptyString();
|
||||
});
|
||||
|
||||
it('returns the dirname if there is only one path', () => {
|
||||
const meta = {
|
||||
filename: '/Users/carolr/code/foo.js',
|
||||
lineno: 1,
|
||||
};
|
||||
|
||||
makeDoclet(['@function', '@global', '@name baz'], meta);
|
||||
|
||||
expect(store.commonPathPrefix).toBe('/Users/carolr/code');
|
||||
});
|
||||
|
||||
// Here for the sake of code coverage; it tests the path where the value is already cached.
|
||||
it('works twice in a row', () => {
|
||||
const meta = {
|
||||
filename: '/Users/carolr/code/foo.js',
|
||||
lineno: 1,
|
||||
};
|
||||
|
||||
makeDoclet(['@function', '@global', '@name baz'], meta);
|
||||
|
||||
expect(store.commonPathPrefix).toBe('/Users/carolr/code');
|
||||
expect(store.commonPathPrefix).toBe('/Users/carolr/code');
|
||||
});
|
||||
|
||||
it('returns the correct common path if one exists', () => {
|
||||
const metaA = {
|
||||
filename: '/Users/carolr/code/foo.js',
|
||||
lineno: 1,
|
||||
};
|
||||
const metaB = {
|
||||
filename: '/Users/carolr/assets/bar.js',
|
||||
lineno: 1,
|
||||
};
|
||||
|
||||
makeDoclet(['@function', '@global', '@name foo'], metaA);
|
||||
makeDoclet(['@function', '@global', '@name bar'], metaB);
|
||||
|
||||
expect(store.commonPathPrefix).toBe('/Users/carolr');
|
||||
});
|
||||
|
||||
it('returns an empty string if there is no common path', () => {
|
||||
const metaA = {
|
||||
filename: 'C:\\carolr\\code\\foo.js',
|
||||
lineno: 1,
|
||||
};
|
||||
const metaB = {
|
||||
filename: 'D:\\code\\bar.js',
|
||||
lineno: 1,
|
||||
};
|
||||
|
||||
makeDoclet(['@function', '@global', '@name foo'], metaA);
|
||||
makeDoclet(['@function', '@global', '@name bar'], metaB);
|
||||
|
||||
expect(store.commonPathPrefix).toBeEmptyString();
|
||||
});
|
||||
|
||||
it('returns the correct value if you get the property, then add a path that does not change the result', () => {
|
||||
const metaA = {
|
||||
filename: '/Users/carolr/code/foo.js',
|
||||
lineno: 1,
|
||||
};
|
||||
const metaB = {
|
||||
filename: '/Users/carolr/code/bar.js',
|
||||
lineno: 1,
|
||||
};
|
||||
let prefix1;
|
||||
let prefix2;
|
||||
|
||||
makeDoclet(['@function', '@global', '@name foo'], metaA);
|
||||
prefix1 = store.commonPathPrefix;
|
||||
makeDoclet(['@function', '@global', '@name bar'], metaB);
|
||||
prefix2 = store.commonPathPrefix;
|
||||
|
||||
expect(prefix1).toBe('/Users/carolr/code');
|
||||
expect(prefix2).toBe('/Users/carolr/code');
|
||||
});
|
||||
|
||||
it('returns the correct value if you get the property, then add a path that changes the result', () => {
|
||||
const metaA = {
|
||||
filename: '/Users/carolr/code/foo.js',
|
||||
lineno: 1,
|
||||
};
|
||||
const metaB = {
|
||||
filename: '/Users/carolr/bar.js',
|
||||
lineno: 1,
|
||||
};
|
||||
let prefix1;
|
||||
let prefix2;
|
||||
|
||||
makeDoclet(['@function', '@global', '@name foo'], metaA);
|
||||
prefix1 = store.commonPathPrefix;
|
||||
makeDoclet(['@function', '@global', '@name bar'], metaB);
|
||||
prefix2 = store.commonPathPrefix;
|
||||
|
||||
expect(prefix1).toBe('/Users/carolr/code');
|
||||
expect(prefix2).toBe('/Users/carolr');
|
||||
});
|
||||
});
|
||||
|
||||
describe('longnames', () => {
|
||||
it('is an empty array if there are no doclets', () => {
|
||||
expect(store.longnames).toBeEmptyArray();
|
||||
});
|
||||
|
||||
it('is a list of longnames if there are doclets', () => {
|
||||
makeDoclet(['@class', '@memberof foo', '@name Bar']);
|
||||
|
||||
expect(store.longnames).toEqual(['foo.Bar']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reactivity', () => {
|
||||
describe('`access`', () => {
|
||||
it('marks a public doclet as unused if it becomes private', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar']);
|
||||
|
||||
expect(store.unusedDoclets).not.toHave(doclet);
|
||||
|
||||
doclet.access = 'private';
|
||||
|
||||
expect(store.unusedDoclets).toHave(doclet);
|
||||
});
|
||||
|
||||
it('marks a private doclet as visible if it becomes public', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@private']);
|
||||
|
||||
expect(store.unusedDoclets).toHave(doclet);
|
||||
|
||||
doclet.access = 'public';
|
||||
|
||||
expect(store.unusedDoclets).not.toHave(doclet);
|
||||
});
|
||||
});
|
||||
|
||||
describe('`augments`', () => {
|
||||
it('adds a doclet to the list of doclets that augment others when the doclet gains an `augments` value', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar']);
|
||||
|
||||
expect(store.docletsWithAugments).not.toHave(doclet);
|
||||
|
||||
doclet.augments = ['Baz'];
|
||||
|
||||
expect(store.docletsWithAugments).toHave(doclet);
|
||||
});
|
||||
|
||||
it('removes a doclet from the list of doclets that augment others when the doclet loses all `augments` values', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@extends Baz']);
|
||||
|
||||
expect(store.docletsWithAugments).toHave(doclet);
|
||||
|
||||
doclet.augments.pop();
|
||||
|
||||
expect(store.docletsWithAugments).not.toHave(doclet);
|
||||
});
|
||||
|
||||
it('removes a doclet from the list of doclets that augment others when the doclet loses its `augments` property', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@extends Baz']);
|
||||
|
||||
expect(store.docletsWithAugments).toHave(doclet);
|
||||
|
||||
doclet.augments = undefined;
|
||||
|
||||
expect(store.docletsWithAugments).not.toHave(doclet);
|
||||
});
|
||||
|
||||
it('does nothing when a doclet augmented A, but it now augments B instead', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@extends Baz']);
|
||||
|
||||
expect(store.docletsWithAugments).toHave(doclet);
|
||||
|
||||
doclet.augments[0] = 'Qux';
|
||||
|
||||
expect(store.docletsWithAugments).toHave(doclet);
|
||||
});
|
||||
});
|
||||
|
||||
describe('`borrowed`', () => {
|
||||
it('adds a doclet to the list of doclets that borrow others when the doclet gains a `borrowed` value', () => {
|
||||
const doclet = makeDoclet(['@function', '@memberof Foo', '@name bar', '@instance']);
|
||||
|
||||
expect(store.docletsWithBorrowed).not.toHave(doclet);
|
||||
|
||||
doclet.borrowed = [{ from: 'Baz#bar' }];
|
||||
|
||||
expect(store.docletsWithBorrowed).toHave(doclet);
|
||||
});
|
||||
|
||||
it('removes a doclet from the list of doclets that borrow others when the doclet loses all `borrowed` values', () => {
|
||||
const doclet = makeDoclet([
|
||||
'@function',
|
||||
'@memberof Foo',
|
||||
'@name bar',
|
||||
'@instance',
|
||||
'@borrows Baz#bar',
|
||||
]);
|
||||
|
||||
expect(store.docletsWithBorrowed).toHave(doclet);
|
||||
|
||||
doclet.borrowed.pop();
|
||||
|
||||
expect(store.docletsWithBorrowed).not.toHave(doclet);
|
||||
});
|
||||
|
||||
it('removes a doclet from the list of doclets that borrow others when the doclet loses its `borrowed` property', () => {
|
||||
const doclet = makeDoclet([
|
||||
'@function',
|
||||
'@memberof Foo',
|
||||
'@name bar',
|
||||
'@instance',
|
||||
'@borrows Baz#bar',
|
||||
]);
|
||||
|
||||
expect(store.docletsWithBorrowed).toHave(doclet);
|
||||
|
||||
doclet.borrowed = undefined;
|
||||
|
||||
expect(store.docletsWithBorrowed).not.toHave(doclet);
|
||||
});
|
||||
|
||||
it('does nothing when a doclet borrowed A, but it now borrows B instead', () => {
|
||||
const doclet = makeDoclet([
|
||||
'@function',
|
||||
'@memberof Foo',
|
||||
'@name bar',
|
||||
'@instance',
|
||||
'@borrows Baz#bar',
|
||||
]);
|
||||
|
||||
expect(store.docletsWithBorrowed).toHave(doclet);
|
||||
|
||||
doclet.borrowed[0] = { from: 'Qux#bar' };
|
||||
|
||||
expect(store.docletsWithBorrowed).toContain(doclet);
|
||||
});
|
||||
});
|
||||
|
||||
describe('`ignore`', () => {
|
||||
it('marks the doclet as unused when added', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar']);
|
||||
|
||||
expect(store.doclets).toHave(doclet);
|
||||
expect(store.unusedDoclets).not.toHave(doclet);
|
||||
|
||||
doclet.ignore = true;
|
||||
|
||||
expect(store.doclets).not.toHave(doclet);
|
||||
expect(store.unusedDoclets).toHave(doclet);
|
||||
});
|
||||
|
||||
it('marks the doclet as visible when removed', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@ignore']);
|
||||
|
||||
expect(store.doclets).not.toHave(doclet);
|
||||
expect(store.unusedDoclets).toHave(doclet);
|
||||
|
||||
doclet.ignore = undefined;
|
||||
|
||||
expect(store.doclets).toHave(doclet);
|
||||
expect(store.unusedDoclets).not.toHave(doclet);
|
||||
});
|
||||
});
|
||||
|
||||
describe('`implements`', () => {
|
||||
it('adds a doclet to the list of doclets that implement others when the doclet gains an `implements` value', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar']);
|
||||
|
||||
expect(store.docletsWithImplements).not.toHave(doclet);
|
||||
|
||||
doclet.implements = ['IBar'];
|
||||
|
||||
expect(store.docletsWithImplements).toHave(doclet);
|
||||
});
|
||||
|
||||
it('removes a doclet from the list of doclets that implement others when the doclet loses all `implements` values', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@implements IBar']);
|
||||
|
||||
expect(store.docletsWithImplements).toHave(doclet);
|
||||
|
||||
doclet.implements.pop();
|
||||
|
||||
expect(store.docletsWithImplements).not.toHave(doclet);
|
||||
});
|
||||
|
||||
it('removes a doclet from the list of doclets that implement others when the doclet loses its `implements` property', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@implements IBar']);
|
||||
|
||||
expect(store.docletsWithImplements).toHave(doclet);
|
||||
|
||||
doclet.implements = undefined;
|
||||
|
||||
expect(store.docletsWithImplements).not.toHave(doclet);
|
||||
});
|
||||
|
||||
it('does nothing when a doclet implemented A, but it now implements B instead', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@implements IBar']);
|
||||
|
||||
expect(store.docletsWithImplements).toHave(doclet);
|
||||
|
||||
doclet.implements[0] = 'IBaz';
|
||||
|
||||
expect(store.docletsWithImplements).toHave(doclet);
|
||||
});
|
||||
});
|
||||
|
||||
describe('`kind`', () => {
|
||||
it('adds a doclet to the list of doclets with that `kind`', () => {
|
||||
const doclet = makeDoclet(['@function', '@name foo']);
|
||||
|
||||
expect(store.docletsByKind.get('function')).toHave(doclet);
|
||||
});
|
||||
|
||||
it('removes a doclet from the list of doclets for a kind when the kind changes', () => {
|
||||
const doclet = makeDoclet(['@function', '@name foo']);
|
||||
|
||||
expect(store.docletsByKind.get('constant')).not.toHave(doclet);
|
||||
expect(store.docletsByKind.get('function')).toHave(doclet);
|
||||
|
||||
doclet.kind = 'constant';
|
||||
|
||||
expect(store.docletsByKind.get('constant')).toHave(doclet);
|
||||
expect(store.docletsByKind.get('function')).not.toHave(doclet);
|
||||
});
|
||||
|
||||
// Doclets should always have a `kind`, so this test is just for completeness.
|
||||
it('removes a doclet from the list of doclets for a kind when the doclet loses its kind', () => {
|
||||
const doclet = makeDoclet(['@function', '@name foo']);
|
||||
|
||||
expect(store.docletsByKind.get('function')).toHave(doclet);
|
||||
|
||||
doclet.kind = 'undefined';
|
||||
|
||||
expect(store.docletsByKind.get('function')).not.toHave(doclet);
|
||||
});
|
||||
|
||||
it('adds a doclet to the list of global doclets when it gains a `kind` that can be global', () => {
|
||||
const doclet = makeDoclet(['@class', '@name foo', '@global']);
|
||||
|
||||
expect(store.globals).toBeEmptySet();
|
||||
|
||||
doclet.kind = 'function';
|
||||
|
||||
expect(store.globals).toHave(doclet);
|
||||
});
|
||||
|
||||
it('removes a doclet from the list of global doclets when its new `kind` cannot be global', () => {
|
||||
const doclet = makeDoclet(['@function', '@name foo', '@global']);
|
||||
|
||||
expect(store.globals).toHave(doclet);
|
||||
|
||||
doclet.kind = 'class';
|
||||
|
||||
expect(store.globals).toBeEmptySet();
|
||||
});
|
||||
});
|
||||
|
||||
describe('`listens`', () => {
|
||||
it('adds a doclet to the list of listeners when the doclet gains a `listens` value', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar']);
|
||||
|
||||
expect(store.listenersByListensTo.get('event:baz')).toBeUndefined();
|
||||
|
||||
doclet.listens = ['event:baz'];
|
||||
|
||||
expect(store.listenersByListensTo.get('event:baz')).toHave(doclet);
|
||||
});
|
||||
|
||||
it('removes a doclet from the list of listeners when the doclet loses all `listens` values', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@listens event:baz']);
|
||||
|
||||
expect(store.listenersByListensTo.get('event:baz')).toHave(doclet);
|
||||
|
||||
doclet.listens.pop();
|
||||
|
||||
expect(store.listenersByListensTo.get('event:baz')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('removes a doclet from the list of listeners when the doclet loses its `listens` property', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@listens event:baz']);
|
||||
|
||||
expect(store.listenersByListensTo.get('event:baz')).toHave(doclet);
|
||||
|
||||
doclet.listens = undefined;
|
||||
|
||||
expect(store.listenersByListensTo.get('event:baz')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('changes the longname that a doclet listens to when needed', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@listens event:baz']);
|
||||
|
||||
expect(store.listenersByListensTo.get('event:baz')).toHave(doclet);
|
||||
expect(store.listenersByListensTo.get('event:qux')).toBeUndefined();
|
||||
|
||||
global.lorgg = true;
|
||||
doclet.listens[0] = 'event:qux';
|
||||
global.lorgg = false;
|
||||
|
||||
expect(store.listenersByListensTo.get('event:baz')).toBeUndefined();
|
||||
expect(store.listenersByListensTo.get('event:qux')).toHave(doclet);
|
||||
});
|
||||
});
|
||||
|
||||
describe('`longname`', () => {
|
||||
it('tracks a doclet by its new longname after it changes', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar']);
|
||||
|
||||
expect(store.docletsByLongname.get('foo.Bar')).toHave(doclet);
|
||||
expect(store.docletsByLongname.get('zoo.Bar')).toBeUndefined();
|
||||
|
||||
doclet.memberof = 'zoo';
|
||||
doclet.longname = 'zoo.Bar';
|
||||
|
||||
expect(store.docletsByLongname.get('foo.Bar')).toBeUndefined();
|
||||
expect(store.docletsByLongname.get('zoo.Bar')).toHave(doclet);
|
||||
});
|
||||
});
|
||||
|
||||
describe('`memberof`', () => {
|
||||
it('tracks a doclet by its new memberof after it changes', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar']);
|
||||
|
||||
expect(store.docletsByMemberof.get('foo')).toHave(doclet);
|
||||
expect(store.docletsByMemberof.get('zoo')).toBeUndefined();
|
||||
|
||||
doclet.memberof = 'zoo';
|
||||
doclet.longname = 'zoo.Bar';
|
||||
|
||||
expect(store.docletsByMemberof.get('foo')).toBeUndefined();
|
||||
expect(store.docletsByMemberof.get('zoo')).toHave(doclet);
|
||||
});
|
||||
});
|
||||
|
||||
describe('`mixes`', () => {
|
||||
it('adds a doclet to the list of doclets that mix in others when the doclet gains a `mixes` value', () => {
|
||||
const doclet = makeDoclet(['@class', '@name Foo']);
|
||||
|
||||
expect(store.docletsWithMixes).not.toHave(doclet);
|
||||
|
||||
doclet.mixes = ['bar'];
|
||||
|
||||
expect(store.docletsWithMixes).toHave(doclet);
|
||||
});
|
||||
|
||||
it('removes a doclet from the list of doclets that mix in others when the doclet loses all `mixes` values', () => {
|
||||
const doclet = makeDoclet(['@class', '@name Foo', '@mixes bar']);
|
||||
|
||||
expect(store.docletsWithMixes).toHave(doclet);
|
||||
|
||||
doclet.mixes.pop();
|
||||
|
||||
expect(store.docletsWithMixes).not.toHave(doclet);
|
||||
});
|
||||
|
||||
it('removes a doclet from the list of doclets that mix in others when the doclet loses its `mixes` property', () => {
|
||||
const doclet = makeDoclet(['@class', '@name Foo', '@mixes bar']);
|
||||
|
||||
expect(store.docletsWithMixes).toHave(doclet);
|
||||
|
||||
doclet.mixes = undefined;
|
||||
|
||||
expect(store.docletsWithMixes).not.toHave(doclet);
|
||||
});
|
||||
|
||||
it('does nothing when a doclet mixed in A, but it now mixes in B instead', () => {
|
||||
const doclet = makeDoclet(['@class', '@name Foo', '@mixes bar']);
|
||||
|
||||
expect(store.docletsWithMixes).toHave(doclet);
|
||||
|
||||
doclet.mixes[0] = 'baz';
|
||||
|
||||
expect(store.docletsWithMixes).toContain(doclet);
|
||||
});
|
||||
});
|
||||
|
||||
describe('`scope`', () => {
|
||||
it('adds a doclet to the list of global doclets when it gains global scope', () => {
|
||||
const doclet = makeDoclet(['@function', '@name foo']);
|
||||
|
||||
expect(store.globals).toBeEmptySet();
|
||||
|
||||
doclet.scope = 'global';
|
||||
|
||||
expect(store.globals).toHave(doclet);
|
||||
});
|
||||
|
||||
it('removes a doclet from the list of global doclets when it loses global scope', () => {
|
||||
const doclet = makeDoclet(['@function', '@name foo', '@global']);
|
||||
|
||||
expect(store.globals).toHave(doclet);
|
||||
|
||||
doclet.scope = 'static';
|
||||
|
||||
expect(store.globals).toBeEmptySet();
|
||||
});
|
||||
});
|
||||
|
||||
describe('`undocumented`', () => {
|
||||
it('marks the doclet as unused when added', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar']);
|
||||
|
||||
expect(store.doclets).toHave(doclet);
|
||||
expect(store.unusedDoclets).not.toHave(doclet);
|
||||
|
||||
doclet.undocumented = true;
|
||||
|
||||
expect(store.doclets).not.toHave(doclet);
|
||||
expect(store.unusedDoclets).toHave(doclet);
|
||||
});
|
||||
|
||||
it('marks the doclet as visible when removed', () => {
|
||||
const doclet = makeDoclet(['@class', '@memberof foo', '@name Bar', '@undocumented']);
|
||||
|
||||
expect(store.doclets).not.toHave(doclet);
|
||||
expect(store.unusedDoclets).toHave(doclet);
|
||||
|
||||
doclet.undocumented = undefined;
|
||||
|
||||
expect(store.doclets).toHave(doclet);
|
||||
expect(store.unusedDoclets).not.toHave(doclet);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sourcePaths', () => {
|
||||
it('is an empty array if there are no visible doclets', () => {
|
||||
expect(store.sourcePaths).toBeEmptyArray();
|
||||
});
|
||||
|
||||
it('is a list of source paths if there are visible doclets', () => {
|
||||
const metaA = {
|
||||
filename: '/Users/carolr/code/foo.js',
|
||||
lineno: 1,
|
||||
};
|
||||
const metaB = {
|
||||
filename: '/Users/carolr/code/bar.js',
|
||||
lineno: 1,
|
||||
};
|
||||
|
||||
makeDoclet(['@function', '@name baz'], metaA);
|
||||
makeDoclet(['@function', '@name qux'], metaB);
|
||||
|
||||
expect(store.sourcePaths).toEqual([
|
||||
'/Users/carolr/code/foo.js',
|
||||
'/Users/carolr/code/bar.js',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -44,6 +44,7 @@ describe('@jsdoc/doclet/lib/doclet', () => {
|
||||
|
||||
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,
|
||||
@ -52,7 +53,12 @@ describe('@jsdoc/doclet/lib/doclet', () => {
|
||||
const secondaryDoclet = new Doclet('/** Hello!\n@version 1.0.0 */', null, jsdoc.deps);
|
||||
const newDoclet = doclet.combineDoclets(primaryDoclet, secondaryDoclet);
|
||||
|
||||
Object.getOwnPropertyNames(newDoclet).forEach((property) => {
|
||||
descriptors = Object.getOwnPropertyDescriptors(newDoclet);
|
||||
Object.keys(descriptors).forEach((property) => {
|
||||
if (!descriptors[property].enumerable) {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(newDoclet[property]).toEqual(primaryDoclet[property]);
|
||||
});
|
||||
});
|
||||
@ -514,15 +520,15 @@ describe('@jsdoc/doclet/lib/doclet', () => {
|
||||
it('sends events to the event bus when watchable properties change', () => {
|
||||
const propValues = {
|
||||
access: 'private',
|
||||
augments: 'Foo',
|
||||
augments: ['Foo'],
|
||||
borrowed: true,
|
||||
ignore: true,
|
||||
implements: 'Foo',
|
||||
implements: ['Foo'],
|
||||
kind: 'class',
|
||||
listens: 'event:foo',
|
||||
listens: ['event:foo'],
|
||||
longname: 'foo',
|
||||
memberof: 'foo',
|
||||
mixes: 'foo',
|
||||
mixes: ['foo'],
|
||||
scope: 'static',
|
||||
undocumented: true,
|
||||
};
|
||||
@ -553,12 +559,12 @@ describe('@jsdoc/doclet/lib/doclet', () => {
|
||||
} else {
|
||||
expect(events[0].oldValue).toBeUndefined();
|
||||
}
|
||||
expect(events[0].newValue).toBe(propValues[key]);
|
||||
expect(events[0].newValue).toEqual(propValues[key]);
|
||||
|
||||
expect(events[1]).toBeObject();
|
||||
expect(events[1].doclet).toBe(newDoclet);
|
||||
expect(events[1].property).toBe(key);
|
||||
expect(events[1].oldValue).toBe(propValues[key]);
|
||||
expect(events[1].oldValue).toEqual(propValues[key]);
|
||||
expect(events[1].newValue).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@ -90,6 +90,7 @@ export class Parser extends EventEmitter {
|
||||
|
||||
this._conf = dependencies.get('config');
|
||||
this._dependencies = dependencies;
|
||||
this._eventBus = dependencies.get('eventBus');
|
||||
this._visitor = new Visitor();
|
||||
this._walker = new Walker();
|
||||
|
||||
@ -130,6 +131,12 @@ export class Parser extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: update other code to always emit parser events directly to the event bus, then remove
|
||||
emit(...args) {
|
||||
super.emit(...args);
|
||||
this._eventBus.emit(...args);
|
||||
}
|
||||
|
||||
// TODO: update docs
|
||||
/**
|
||||
* Parse the given source files for JSDoc comments.
|
||||
|
||||
@ -19,7 +19,7 @@ import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { Syntax, Walker } from '@jsdoc/ast';
|
||||
import _ from 'lodash';
|
||||
import { combineDoclets, Doclet } from '@jsdoc/doclet';
|
||||
|
||||
import { attachTo } from '../../../lib/handlers.js';
|
||||
import * as jsdocParser from '../../../lib/parser.js';
|
||||
@ -146,7 +146,7 @@ describe('@jsdoc/parse/lib/parser', () => {
|
||||
const sourceCode = 'javascript:/** @class */function Foo() {}';
|
||||
|
||||
function handler(e) {
|
||||
e.doclet = _.cloneDeep(e.doclet);
|
||||
e.doclet = combineDoclets(e.doclet, new Doclet('', {}, jsdoc.deps));
|
||||
e.doclet.foo = 'bar';
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user