mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
Compare commits
154 Commits
@jsdoc/esl
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49dbb37f74 | ||
|
|
ea821957e1 | ||
|
|
4120dae2de | ||
|
|
a836bb623d | ||
|
|
46678337d8 | ||
|
|
7d8f083f6e | ||
|
|
e668135e77 | ||
|
|
3cebdc8c90 | ||
|
|
ace19d938f | ||
|
|
77e39cf892 | ||
|
|
2ea0705c55 | ||
|
|
a14e7c6b7a | ||
|
|
892ff706c7 | ||
|
|
acb9ced243 | ||
|
|
cd24b82d29 | ||
|
|
ea8b08db71 | ||
|
|
ee72a9d3e8 | ||
|
|
9938011597 | ||
|
|
5ced5a3d45 | ||
|
|
7a30116c1b | ||
|
|
2979bf9559 | ||
|
|
279529a97f | ||
|
|
0f899c01a9 | ||
|
|
c804735739 | ||
|
|
4b5d017dd2 | ||
|
|
59fcecdd04 | ||
|
|
083286c59f | ||
|
|
78eada9c47 | ||
|
|
9495d511e2 | ||
|
|
9ff5121bf8 | ||
|
|
d93065705c | ||
|
|
cdc9fe4eba | ||
|
|
73fa170135 | ||
|
|
291434048c | ||
|
|
aac6f335cb | ||
|
|
1c2aea88ba | ||
|
|
83eba455e1 | ||
|
|
9260ddd6a4 | ||
|
|
beb6639522 | ||
|
|
de12f99a42 | ||
|
|
6fb7ec895e | ||
|
|
c099ed70e5 | ||
|
|
053ace645b | ||
|
|
13cdfd18b7 | ||
|
|
4555ff0b68 | ||
|
|
74d3b73eb5 | ||
|
|
2a2d950b56 | ||
|
|
c076b5d8f1 | ||
|
|
f1e9667815 | ||
|
|
141e27df09 | ||
|
|
e8e7350d93 | ||
|
|
e97ca7592b | ||
|
|
efc8dac1a9 | ||
|
|
a6307578e5 | ||
|
|
9705c7295e | ||
|
|
c51b539b57 | ||
|
|
5ec5199263 | ||
|
|
9f1a442581 | ||
|
|
944ad72964 | ||
|
|
ee198d85ca | ||
|
|
cde67f6f58 | ||
|
|
65faaba82c | ||
|
|
5f51d8623d | ||
|
|
8d9fa98561 | ||
|
|
1c057e63a0 | ||
|
|
544db1ec8e | ||
|
|
ab1fb91431 | ||
|
|
0621d6391a | ||
|
|
2de6b34ab0 | ||
|
|
6555d1e454 | ||
|
|
bb6c83e93d | ||
|
|
1955041df7 | ||
|
|
11d92f85d1 | ||
|
|
8a0b40502e | ||
|
|
4c4a58260d | ||
|
|
3ca5db9db5 | ||
|
|
3c21fd21de | ||
|
|
b4570c6c71 | ||
|
|
7a6b3afc57 | ||
|
|
62c9924dca | ||
|
|
72153299ef | ||
|
|
72c1e79892 | ||
|
|
68bffc795b | ||
|
|
7772195487 | ||
|
|
2892faaf4e | ||
|
|
8c2d6acca8 | ||
|
|
d4a2b0fba6 | ||
|
|
56c2fc8e09 | ||
|
|
2cbfdd9ad6 | ||
|
|
71ccf46189 | ||
|
|
ee53851057 | ||
|
|
68d5ee6a96 | ||
|
|
013fa9370b | ||
|
|
89014bbb79 | ||
|
|
9cb4b74727 | ||
|
|
43151bc46b | ||
|
|
30d87034e4 | ||
|
|
13c52db01e | ||
|
|
e96de763bb | ||
|
|
7a2e561e11 | ||
|
|
30df56712e | ||
|
|
69fc7ae586 | ||
|
|
7898aa800a | ||
|
|
7f4704f766 | ||
|
|
55325dc623 | ||
|
|
7af5d1d55a | ||
|
|
1983cda4e3 | ||
|
|
dfe25ba285 | ||
|
|
bd52c21788 | ||
|
|
76dae361bb | ||
|
|
8ca09d5845 | ||
|
|
5d918e0ace | ||
|
|
54ff30e6b2 | ||
|
|
087eaf5da0 | ||
|
|
4f14e9df3a | ||
|
|
6f032cc874 | ||
|
|
acaeaaeada | ||
|
|
58b1279d3e | ||
|
|
a40ed69834 | ||
|
|
94a8b8c369 | ||
|
|
3c6a04d0a3 | ||
|
|
ffae2ff857 | ||
|
|
dd364023f8 | ||
|
|
5e21e9925b | ||
|
|
9fc769ecdc | ||
|
|
64ca8f1ddf | ||
|
|
41aa13b7e3 | ||
|
|
1864c7d238 | ||
|
|
f556f5e615 | ||
|
|
325d4f4c4e | ||
|
|
d7f17e2f65 | ||
|
|
5f2e6f459d | ||
|
|
f09568673d | ||
|
|
cc4a661106 | ||
|
|
e54340c961 | ||
|
|
7b8c3c750f | ||
|
|
2d33171cda | ||
|
|
e5ba496e8f | ||
|
|
394481ebaa | ||
|
|
84522cc085 | ||
|
|
153b899c2e | ||
|
|
457c38fd4d | ||
|
|
360abb78b6 | ||
|
|
943faa7953 | ||
|
|
8f408f73c2 | ||
|
|
787059b7b8 | ||
|
|
23dfa8e1f9 | ||
|
|
09a5646241 | ||
|
|
857801651a | ||
|
|
c605b99303 | ||
|
|
edd911322f | ||
|
|
b209812374 | ||
|
|
1faff383a4 | ||
|
|
a0d8bddb9a |
25
.github/workflows/ci.yaml
vendored
25
.github/workflows/ci.yaml
vendored
@ -14,6 +14,7 @@
|
||||
|
||||
name: build
|
||||
on: [push, pull_request]
|
||||
permissions: {}
|
||||
env:
|
||||
# Don't install Git hooks.
|
||||
HUSKY: 0
|
||||
@ -23,10 +24,12 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node: [20]
|
||||
node: ['20']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: 'npm'
|
||||
@ -39,10 +42,12 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node: [20]
|
||||
node: ['20']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: 'npm'
|
||||
@ -55,10 +60,12 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node: [18, 20]
|
||||
node: ['20.19.0', '22.12.0', '24']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: 'npm'
|
||||
|
||||
@ -13,6 +13,7 @@ CODE_OF_CONDUCT.md
|
||||
CONTRIBUTING.md
|
||||
Herebyfile*
|
||||
js-green-licenses.json
|
||||
knip.json
|
||||
lerna.json
|
||||
packages/
|
||||
test/
|
||||
|
||||
@ -43,6 +43,16 @@ export const coverage = task({
|
||||
},
|
||||
});
|
||||
|
||||
export const dependencyCleanup = task({
|
||||
name: 'dependency-cleanup',
|
||||
run: async () => {
|
||||
await execa(bin('knip'), [], {
|
||||
stdout: 'inherit',
|
||||
stderr: 'inherit',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const dependencyEngines = task({
|
||||
name: 'dependency-engines',
|
||||
run: async () => {
|
||||
@ -91,7 +101,7 @@ export const dependencyLicenses = task({
|
||||
|
||||
export const dependencies = task({
|
||||
name: 'dependencies',
|
||||
dependencies: [dependencyEngines, dependencyLicenses],
|
||||
dependencies: [dependencyCleanup, dependencyEngines, dependencyLicenses],
|
||||
});
|
||||
|
||||
export const format = task({
|
||||
|
||||
@ -80,7 +80,6 @@ and customize your documentation. Here are a few of them:
|
||||
- Documentation is available at [jsdoc.app](https://jsdoc.app/).
|
||||
- Contribute to the docs at
|
||||
[jsdoc/jsdoc.github.io](https://github.com/jsdoc/jsdoc.github.io).
|
||||
- [Join JSDoc's Slack channel](https://jsdoc-slack.appspot.com/).
|
||||
- Ask for help on the
|
||||
[JSDoc Users mailing list](http://groups.google.com/group/jsdoc-users).
|
||||
- Post questions tagged `jsdoc` to
|
||||
|
||||
61
knip.json
Normal file
61
knip.json
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"workspaces": {
|
||||
".": {
|
||||
"entry": ["Herebyfile.js"],
|
||||
"ignoreDependencies": ["ajv", "c8", "installed-check", "license-check-and-add", "mock-fs"]
|
||||
},
|
||||
"packages/jsdoc": {
|
||||
"project": ["*.js", "test/index.js"]
|
||||
},
|
||||
"packages/jsdoc-ast": {
|
||||
"project": ["lib/**/*.js"]
|
||||
},
|
||||
"packages/jsdoc-cli": {
|
||||
"project": ["lib/**/*.js"]
|
||||
},
|
||||
"packages/jsdoc-core": {
|
||||
"project": ["lib/**/*.js"]
|
||||
},
|
||||
"packages/jsdoc-doclet": {
|
||||
"project": ["lib/**/*.js"]
|
||||
},
|
||||
"packages/jsdoc-eslint-config": {
|
||||
"project": ["*.js"]
|
||||
},
|
||||
"packages/jsdoc-name": {
|
||||
"project": ["lib/**/*.js"]
|
||||
},
|
||||
"packages/jsdoc-parse": {
|
||||
"project": ["lib/**/*.js"]
|
||||
},
|
||||
"packages/jsdoc-plugins": {
|
||||
"ignore": ["*.js", "test/**/*.js"]
|
||||
},
|
||||
"packages/jsdoc-prettier-config": {
|
||||
"project": ["*.js"]
|
||||
},
|
||||
"packages/jsdoc-salty": {
|
||||
"project": ["lib/**/*.js"]
|
||||
},
|
||||
"packages/jsdoc-tag": {
|
||||
"project": ["lib/**/*.js"]
|
||||
},
|
||||
"packages/jsdoc-task-runner": {
|
||||
"project": ["lib/**/*.js"]
|
||||
},
|
||||
"packages/jsdoc-template-legacy": {
|
||||
"project": ["*.js", "lib/**/*.js"],
|
||||
"ignoreDependencies": [
|
||||
"@fontsource-variable/open-sans",
|
||||
"code-prettify",
|
||||
"color-themes-for-google-code-prettify"
|
||||
]
|
||||
},
|
||||
"packages/jsdoc-test-matchers": {
|
||||
"project": ["*.js"]
|
||||
},
|
||||
"packages/jsdoc-util": {
|
||||
"project": ["lib/**/*.js"]
|
||||
}
|
||||
}
|
||||
}
|
||||
7666
package-lock.json
generated
7666
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
35
package.json
35
package.json
@ -3,44 +3,35 @@
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "7.26.5",
|
||||
"@jsdoc/ast": "^0.2.5",
|
||||
"@jsdoc/cli": "^0.3.5",
|
||||
"@jsdoc/core": "^0.5.4",
|
||||
"@jsdoc/doclet": "^0.2.5",
|
||||
"@jsdoc/eslint-config": "^2.0.0",
|
||||
"@jsdoc/parse": "^0.3.5",
|
||||
"@jsdoc/prettier-config": "^0.2.3",
|
||||
"@jsdoc/salty": "^0.2.5",
|
||||
"@jsdoc/tag": "^0.2.5",
|
||||
"@jsdoc/task-runner": "^0.2.2",
|
||||
"@jsdoc/test-matchers": "^0.2.4",
|
||||
"@jsdoc/util": "^0.3.0",
|
||||
"@jsdoc/core": "^0.5.10",
|
||||
"@jsdoc/eslint-config": "^2.0.2",
|
||||
"@jsdoc/tag": "^0.2.13",
|
||||
"@jsdoc/util": "^0.3.4",
|
||||
"ajv": "^8.17.1",
|
||||
"c8": "^10.1.3",
|
||||
"eslint": "^9.18.0",
|
||||
"execa": "^9.5.2",
|
||||
"hereby": "^1.10.0",
|
||||
"eslint": "^9.39.1",
|
||||
"execa": "^9.6.1",
|
||||
"hereby": "^1.11.1",
|
||||
"husky": "^9.1.7",
|
||||
"installed-check": "^9.3.0",
|
||||
"jasmine": "^5.5.0",
|
||||
"jasmine": "^5.13.0",
|
||||
"jasmine-console-reporter": "^3.1.0",
|
||||
"js-green-licenses": "^4.0.0",
|
||||
"klaw-sync": "^6.0.0",
|
||||
"lerna": "^8.1.9",
|
||||
"knip": "^5.72.0",
|
||||
"license-check-and-add": "^4.0.5",
|
||||
"lodash": "^4.17.21",
|
||||
"mock-fs": "^5.4.1",
|
||||
"prettier": "^3.4.2"
|
||||
"mock-fs": "^5.5.0",
|
||||
"prettier": "^3.7.4"
|
||||
},
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"coverage": "node_modules/.bin/hereby coverage",
|
||||
"default": "node_modules/.bin/hereby",
|
||||
"dependencies": "node_modules/.bin/hereby dependencies",
|
||||
"dependency-cleanup": "node_modules/.bin/hereby dependency-cleanup",
|
||||
"dependency-engines": "node_modules/.bin/hereby dependency-engines",
|
||||
"dependency-licenses": "node_modules/.bin/hereby dependency-licenses",
|
||||
"format": "node_modules/.bin/hereby format",
|
||||
|
||||
@ -173,9 +173,9 @@ export function nodeToValue(node) {
|
||||
// Like the declaration in: `export const foo = 'bar';`
|
||||
// We need a single value, so we use the first variable name.
|
||||
if (node.declaration.declarations) {
|
||||
str = `${LONGNAMES.MODULE_EXPORT}.${nodeToValue(node.declaration.declarations[0])}`;
|
||||
str = `${nodeToValue(node.declaration.declarations[0])}`;
|
||||
} else {
|
||||
str = `${LONGNAMES.MODULE_EXPORT}.${nodeToValue(node.declaration)}`;
|
||||
str = `${nodeToValue(node.declaration)}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -31,13 +31,13 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.26.5",
|
||||
"@babel/parser": "^7.28.5",
|
||||
"@jsdoc/name": "^0.1.1",
|
||||
"@jsdoc/util": "^0.3.4",
|
||||
"ast-module-types": "^6.0.0",
|
||||
"ast-module-types": "^6.0.1",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,12 +34,11 @@
|
||||
"@jsdoc/core": "^0.5.10",
|
||||
"@jsdoc/util": "^0.3.4",
|
||||
"lodash": "^4.17.21",
|
||||
"ow": "^2.0.0",
|
||||
"strip-bom": "^5.0.0",
|
||||
"yargs-parser": "^21.1.1"
|
||||
"ow": "^3.1.1",
|
||||
"yargs-parser": "^22.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23.0.0"
|
||||
},
|
||||
"gitHead": "a595f7f2a525f32da9a0498a027667af3d938158"
|
||||
}
|
||||
|
||||
@ -17,7 +17,8 @@
|
||||
/**
|
||||
* Manages configuration settings for JSDoc.
|
||||
*
|
||||
* @alias module:@jsdoc/core.config
|
||||
* @namespace config
|
||||
* @memberof module:@jsdoc/core
|
||||
*/
|
||||
import { cosmiconfig, defaultLoaders } from 'cosmiconfig';
|
||||
import _ from 'lodash';
|
||||
@ -26,67 +27,124 @@ import stripJsonComments from 'strip-json-comments';
|
||||
|
||||
const MODULE_NAME = 'jsdoc';
|
||||
|
||||
/**
|
||||
* The default configuration settings for JSDoc.
|
||||
*
|
||||
* @namespace defaultConfig
|
||||
* @memberof module:@jsdoc/core.config
|
||||
*/
|
||||
export const defaultConfig = {
|
||||
// TODO(hegemonic): Integrate CLI options with other options.
|
||||
// TODO: Integrate CLI options with other options.
|
||||
/**
|
||||
* JSDoc options that can be specified on the command line.
|
||||
*
|
||||
* @namespace opts
|
||||
* @memberof module:@jsdoc/core.config.defaultConfig
|
||||
*/
|
||||
opts: {
|
||||
/**
|
||||
* The output directory for the generated documentation. If this directory does not exist, then
|
||||
* JSDoc creates it. The default value is `./out`.
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof module:@jsdoc/core.config.defaultConfig.opts
|
||||
*/
|
||||
destination: './out',
|
||||
|
||||
/**
|
||||
* The character encoding to use. Use an
|
||||
* [encoding supported by Node.js](https://nodejs.org/api/buffer.html#buffers-and-character-encodings).
|
||||
* The default value is `utf8`.
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof module:@jsdoc/core.config.defaultConfig.opts
|
||||
*/
|
||||
encoding: 'utf8',
|
||||
},
|
||||
/**
|
||||
* The JSDoc plugins to load.
|
||||
* The paths to the JSDoc plugins to load. Use the same paths that you would use to import each
|
||||
* plugin as a module.
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof module:@jsdoc/core.config.defaultConfig
|
||||
*/
|
||||
plugins: [],
|
||||
|
||||
/**
|
||||
* Settings for loading and parsing source files.
|
||||
* The source files to parse.
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof module:@jsdoc/core.config.defaultConfig
|
||||
*/
|
||||
sourceFiles: [],
|
||||
|
||||
/**
|
||||
* The type of source file. In general, you should use the value `module`. If none of your
|
||||
* source files use ECMAScript >=2015 syntax, you can use the value `script`.
|
||||
* The type of source file. In most cases, you should use the value `module`, which is the default
|
||||
* value.
|
||||
*
|
||||
* If none of your source files use syntax from ECMAScript 2015 or later, then use the value
|
||||
* `script`.
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof module:@jsdoc/core.config.defaultConfig
|
||||
*/
|
||||
sourceType: 'module',
|
||||
|
||||
/**
|
||||
* Settings for interpreting JSDoc tags.
|
||||
*
|
||||
* @namespace tags
|
||||
* @memberof module:@jsdoc/core.config.defaultConfig
|
||||
*/
|
||||
tags: {
|
||||
/**
|
||||
* Set to `true` to allow tags that JSDoc does not recognize.
|
||||
* Whether to allow tags that JSDoc does not recognize. The default value is `true`.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof module:@jsdoc/core.config.defaultConfig.tags
|
||||
*/
|
||||
allowUnknownTags: true,
|
||||
// TODO(hegemonic): Use module paths, not magic strings.
|
||||
|
||||
// TODO: Use module paths, not magic strings.
|
||||
/**
|
||||
* The JSDoc tag dictionaries to load.
|
||||
*
|
||||
* If you specify two or more tag dictionaries, and a tag is defined in multiple
|
||||
* dictionaries, JSDoc uses the definition from the first dictionary that includes that tag.
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof module:@jsdoc/core.config.defaultConfig.tags
|
||||
*/
|
||||
dictionaries: ['jsdoc', 'closure'],
|
||||
},
|
||||
|
||||
/**
|
||||
* Settings for generating output with JSDoc templates. Some JSDoc templates might ignore these
|
||||
* settings.
|
||||
*
|
||||
* @namespace
|
||||
* @memberof module:@jsdoc/core.config.defaultConfig
|
||||
*/
|
||||
templates: {
|
||||
/**
|
||||
* Set to `true` to use a monospaced font for links to other code symbols, but not links to
|
||||
* websites.
|
||||
* Whether to use a monospaced font for links to other code symbols, but not links to websites.
|
||||
* The default value is `false`.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof module:@jsdoc/core.config.defaultConfig.templates
|
||||
*/
|
||||
cleverLinks: false,
|
||||
|
||||
/**
|
||||
* Set to `true` to use a monospaced font for all links.
|
||||
* Whether to use a monospaced font for all links. The default value is `false`.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof module:@jsdoc/core.config.defaultConfig.templates
|
||||
*/
|
||||
monospaceLinks: false,
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: Consider exporting this class.
|
||||
class Config {
|
||||
constructor(filepath, config) {
|
||||
this.config = config;
|
||||
this.filepath = filepath;
|
||||
}
|
||||
}
|
||||
|
||||
function loadJson(filepath, content) {
|
||||
return defaultLoaders['.json'](filepath, stripBom(stripJsonComments(content)));
|
||||
}
|
||||
@ -114,6 +172,43 @@ const explorer = cosmiconfig(MODULE_NAME, {
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* Information about a JSDoc configuration file.
|
||||
*
|
||||
* @typedef {Object<string, string>} module:@jsdoc/core.config~ConfigInfo
|
||||
* @property {Object<string, *>} config - The configuration settings.
|
||||
* @property {string} filepath - The path to the configuration file that was loaded.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Loads JSDoc configuration settings from the specified filepath.
|
||||
*
|
||||
* You can provide configuration settings in the following formats:
|
||||
*
|
||||
* + A CommonJS or ES2015 module that exports the configuration settings. For CommonJS modules, use
|
||||
* the extension `.cjs`.
|
||||
* + A JSON file that contains the configuration settings.
|
||||
* + A YAML file that contains the configuration settings.
|
||||
* + A `jsdoc` property in a `package.json` file that contains the configuration settings.
|
||||
*
|
||||
* If `filepath` is a configuration file, then JSDoc loads that configuration file.
|
||||
*
|
||||
* If `filepath` is a directory, then JSDoc looks for the following files in that directory and
|
||||
* loads the first one it finds:
|
||||
*
|
||||
* 1. `package.json` (`jsdoc` property)
|
||||
* 2. `.jsdocrc` (JSON or YAML)
|
||||
* 3. `.jsdocrc.json`
|
||||
* 4. `.jsdocrc.yaml`
|
||||
* 5. `.jsdocrc.yml`
|
||||
* 6. `.jsdocrc.js` (ES2015 module)
|
||||
* 7. `jsdoc.config.js` (ES2015 module)
|
||||
*
|
||||
* @alias module:@jsdoc/core.config.load
|
||||
* @param {string} filepath - The path to the configuration file, or a directory that contains the
|
||||
* configuration file.
|
||||
* @returns {module:@jsdoc/core.config~ConfigInfo} The configuration settings.
|
||||
*/
|
||||
export async function load(filepath) {
|
||||
let loaded;
|
||||
|
||||
@ -123,5 +218,8 @@ export async function load(filepath) {
|
||||
loaded = (await explorer.search()) ?? {};
|
||||
}
|
||||
|
||||
return new Config(loaded.filepath, _.defaultsDeep({}, loaded.config, defaultConfig));
|
||||
return {
|
||||
config: _.defaultsDeep({}, loaded.config, defaultConfig),
|
||||
filepath: loaded.filepath,
|
||||
};
|
||||
}
|
||||
|
||||
@ -43,22 +43,21 @@ export default class Env {
|
||||
/**
|
||||
* The event emitter shared across JSDoc.
|
||||
*
|
||||
* @type {Object}
|
||||
* @type {node:events}
|
||||
*/
|
||||
this.emitter = new EventEmitter();
|
||||
|
||||
/**
|
||||
* Logging functions shared across JSDoc.
|
||||
*
|
||||
* @type {Object<string, function>}
|
||||
* @type {module:@jsdoc/util~logFunctions}
|
||||
*/
|
||||
this.log = getLogFunctions(this.emitter);
|
||||
|
||||
/**
|
||||
* The command-line arguments, parsed into a key/value hash.
|
||||
* The command-line arguments, expressed as key-value pairs.
|
||||
*
|
||||
* @type {Object}
|
||||
* @example if (global.env.opts.help) { console.log('Helpful message.'); }
|
||||
* @type {Object<string, *>}
|
||||
*/
|
||||
this.opts = {};
|
||||
|
||||
@ -67,7 +66,7 @@ export default class Env {
|
||||
*
|
||||
* @type {Object}
|
||||
* @property {Date} start - The time at which JSDoc started running.
|
||||
* @property {Date} finish - The time at which JSDoc finished running.
|
||||
* @property {?Date} finish - The time at which JSDoc finished running.
|
||||
*/
|
||||
this.run = {
|
||||
start: new Date(),
|
||||
@ -101,20 +100,20 @@ export default class Env {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The data parsed from JSDoc's configuration file.
|
||||
*
|
||||
* @type Object<string, *>
|
||||
*/
|
||||
get config() {
|
||||
return this.conf;
|
||||
}
|
||||
|
||||
// TODO: Remove.
|
||||
get env() {
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO: Remove.
|
||||
get(key) {
|
||||
return this[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* The command-line arguments, expressed as key-value pairs.
|
||||
*
|
||||
* @type {Object<string, *>}
|
||||
*/
|
||||
get options() {
|
||||
return this.opts;
|
||||
}
|
||||
|
||||
@ -38,10 +38,10 @@
|
||||
"fast-glob": "^3.3.3",
|
||||
"lodash": "^4.17.21",
|
||||
"strip-bom": "^5.0.0",
|
||||
"strip-json-comments": "^5.0.1"
|
||||
"strip-json-comments": "^5.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23.0.0"
|
||||
},
|
||||
"gitHead": "a595f7f2a525f32da9a0498a027667af3d938158"
|
||||
}
|
||||
|
||||
@ -46,14 +46,6 @@ describe('@jsdoc/core.Env', () => {
|
||||
expect(env.emitter).toBeInstanceOf(EventEmitter);
|
||||
});
|
||||
|
||||
it('has an `env` getter', () => {
|
||||
expect(env.env).toBe(env);
|
||||
});
|
||||
|
||||
it('has a `get` method that returns the value of the specified property', () => {
|
||||
expect(env.get('sourceFiles')).toBe(env.sourceFiles);
|
||||
});
|
||||
|
||||
it('has a `log` property that contains logging functions', () => {
|
||||
expect(env.log).toBeObject();
|
||||
|
||||
|
||||
@ -22,10 +22,10 @@
|
||||
*/
|
||||
import * as augment from './lib/augment.js';
|
||||
import { resolveBorrows } from './lib/borrow.js';
|
||||
import { combineDoclets, Doclet } from './lib/doclet.js';
|
||||
import { 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, DocletStore, Package, resolveBorrows, schema };
|
||||
export default { augment, combineDoclets, Doclet, DocletStore, Package, resolveBorrows, schema };
|
||||
export { augment, Doclet, DocletStore, Package, resolveBorrows, schema };
|
||||
export default { augment, Doclet, DocletStore, Package, resolveBorrows, schema };
|
||||
|
||||
@ -16,11 +16,14 @@
|
||||
|
||||
/**
|
||||
* Provides methods for augmenting the parse results based on their content.
|
||||
*
|
||||
* @namespace augment
|
||||
* @memberof module:@jsdoc/doclet
|
||||
*/
|
||||
|
||||
import { fromParts, SCOPE, toParts } from '@jsdoc/name';
|
||||
|
||||
import { combineDoclets, Doclet } from './doclet.js';
|
||||
import { Doclet } from './doclet.js';
|
||||
|
||||
const DEPENDENCY_KINDS = ['class', 'external', 'interface', 'mixin'];
|
||||
|
||||
@ -133,7 +136,7 @@ function staticToInstance(doclet) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the list of doclets to be added to another symbol.
|
||||
* Updates the list of doclets to be added to another symbol.
|
||||
*
|
||||
* We add only one doclet per longname. For example: If `ClassA` inherits from two classes that both
|
||||
* use the same method name, `ClassA` gets docs for one method rather than two.
|
||||
@ -222,7 +225,7 @@ function getInheritedAdditions(depDoclets, docletStore) {
|
||||
childDoclet = {};
|
||||
}
|
||||
|
||||
member = combineDoclets(childDoclet, parentDoclet);
|
||||
member = Doclet.combineDoclets(childDoclet, parentDoclet);
|
||||
|
||||
if (!member.inherited) {
|
||||
member.inherits = member.longname;
|
||||
@ -408,7 +411,7 @@ function getImplementedAdditions(implDoclets, docletStore) {
|
||||
childDoclet = {};
|
||||
}
|
||||
|
||||
implementationDoclet = combineDoclets(childDoclet, parentDoclet);
|
||||
implementationDoclet = Doclet.combineDoclets(childDoclet, parentDoclet);
|
||||
|
||||
reparentDoclet(doclet, implementationDoclet);
|
||||
updateImplements(implementationDoclet, parentDoclet.longname);
|
||||
@ -472,19 +475,20 @@ function augment(docletStore, propertyName, docletFinder, env) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add doclets to reflect class inheritance.
|
||||
* Adds doclets to reflect class inheritance.
|
||||
*
|
||||
* For example, if `ClassA` has the instance method `myMethod`, and `ClassB` inherits from `ClassA`,
|
||||
* calling this method creates a new doclet for `ClassB#myMethod`.
|
||||
*
|
||||
* @return {void}
|
||||
* @alias module:@jsdoc/doclet.augment.addInherited
|
||||
* @param {!module:@jsdoc/doclet.DocletStore} docletStore - The doclet store to update.
|
||||
*/
|
||||
export function addInherited(docletStore) {
|
||||
augment(docletStore, 'augments', getInheritedAdditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add doclets to reflect mixins. When a symbol is mixed into a class, the class' version of the
|
||||
* Adds doclets to reflect mixins. When a symbol is mixed into a class, the class' version of the
|
||||
* mixed-in symbol is treated as an instance member.
|
||||
*
|
||||
* For example:
|
||||
@ -494,16 +498,15 @@ export function addInherited(docletStore) {
|
||||
* + If `MixinA` has the static method `myMethod`, and `ClassA` mixes `MixinA`, calling this method
|
||||
* creates a new doclet for the instance method `ClassA#myMethod`.
|
||||
*
|
||||
* @param {!Array.<module:@jsdoc/doclet.Doclet>} doclets - The doclets generated by JSDoc.
|
||||
* @param {!Object} doclets.index - The doclet index.
|
||||
* @return {void}
|
||||
* @alias module:@jsdoc/doclet.augment.addMixedIn
|
||||
* @param {!module:@jsdoc/doclet.DocletStore} docletStore - The doclet store to update.
|
||||
*/
|
||||
export function addMixedIn(doclets) {
|
||||
augment(doclets, 'mixes', getMixedInAdditions);
|
||||
export function addMixedIn(docletStore) {
|
||||
augment(docletStore, 'mixes', getMixedInAdditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add and update doclets to reflect implementations of interfaces.
|
||||
* Adds and updates doclets to reflect implementations of interfaces.
|
||||
*
|
||||
* For example, if `InterfaceA` has the instance method `myMethod`, and `ClassA` implements
|
||||
* `InterfaceA`, calling this method does the following:
|
||||
@ -515,14 +518,15 @@ export function addMixedIn(doclets) {
|
||||
* If `ClassA#myMethod` used the `@override` or `@inheritdoc` tag, calling this method would also
|
||||
* generate a new doclet that reflects the interface's documentation for `InterfaceA#myMethod`.
|
||||
*
|
||||
* @return {void}
|
||||
* @alias module:@jsdoc/doclet.augment.addImplemented
|
||||
* @param {!module:@jsdoc/doclet.DocletStore} docletStore - The doclet store to update.
|
||||
*/
|
||||
export function addImplemented(doclets) {
|
||||
augment(doclets, 'implements', getImplementedAdditions);
|
||||
export function addImplemented(docletStore) {
|
||||
augment(docletStore, 'implements', getImplementedAdditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add and update doclets to reflect all of the following:
|
||||
* Adds and updates doclets to reflect all of the following:
|
||||
*
|
||||
* + Inherited classes
|
||||
* + Mixins
|
||||
@ -530,7 +534,8 @@ export function addImplemented(doclets) {
|
||||
*
|
||||
* Calling this method is equivalent to calling all other methods exported by this module.
|
||||
*
|
||||
* @return {void}
|
||||
* @alias module:@jsdoc/doclet.augment.augmentAll
|
||||
* @param {!module:@jsdoc/doclet.DocletStore} docletStore - The doclet store to update.
|
||||
*/
|
||||
export function augmentAll(docletStore) {
|
||||
addMixedIn(docletStore);
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
|
||||
import { SCOPE } from '@jsdoc/name';
|
||||
|
||||
import { combineDoclets, Doclet } from './doclet.js';
|
||||
import { Doclet } from './doclet.js';
|
||||
|
||||
function cloneBorrowedDoclets({ borrowed, longname }, docletStore) {
|
||||
borrowed?.forEach(({ from, as }) => {
|
||||
@ -32,7 +32,7 @@ function cloneBorrowedDoclets({ borrowed, longname }, docletStore) {
|
||||
if (borrowedDoclets) {
|
||||
borrowedAs = borrowedAs.replace(/^prototype\./, SCOPE.PUNC.INSTANCE);
|
||||
borrowedDoclets.forEach((borrowedDoclet) => {
|
||||
const clone = combineDoclets(borrowedDoclet, Doclet.emptyDoclet(borrowedDoclet.env));
|
||||
const clone = Doclet.clone(borrowedDoclet);
|
||||
|
||||
// TODO: this will fail on longnames like '"Foo#bar".baz'
|
||||
parts = borrowedAs.split(SCOPE.PUNC.INSTANCE);
|
||||
@ -55,10 +55,15 @@ function cloneBorrowedDoclets({ borrowed, longname }, docletStore) {
|
||||
}
|
||||
|
||||
/**
|
||||
Take a copy of the docs for borrowed symbols and attach them to the
|
||||
docs for the borrowing symbol. This process changes the symbols involved,
|
||||
moving docs from the "borrowed" array and into the general docs, then
|
||||
deleting the "borrowed" array.
|
||||
* Creates doclets for borrowed symbols, and adds them to the doclet store.
|
||||
*
|
||||
* The `name`, `memberof`, and `longname` properties for the new doclets are rewritten to show that
|
||||
* they belong to the borrowing symbol.
|
||||
*
|
||||
* This method also removes the `borrowed` property from each borrowed doclet.
|
||||
*
|
||||
* @alias module:@jsdoc/doclet.resolveBorrows
|
||||
* @param {!module:@jsdoc/doclet.DocletStore} docletStore - The doclet store to update.
|
||||
*/
|
||||
export function resolveBorrows(docletStore) {
|
||||
for (const doclet of docletStore.docletsWithBorrowed) {
|
||||
|
||||
@ -56,17 +56,25 @@ function removeFromSet(targetMap, key, value) {
|
||||
/**
|
||||
* Stores and classifies the doclets that JSDoc creates as it parses your source files.
|
||||
*
|
||||
* The doclet store categorizes doclets based on their properties, so that the JSDoc template can
|
||||
* A doclet store categorizes doclets based on their properties, so that the JSDoc template can
|
||||
* efficiently retrieve the doclets that it needs. For example, when the template generates
|
||||
* documentation for a class, it can retrieve all of the doclets that represent members of that
|
||||
* class.
|
||||
*
|
||||
* To retrieve the doclets that you need, use the doclet store's instance properties. For example,
|
||||
* {@link module:@jsdoc/doclet.DocletStore#docletsByLongname} maps longnames to the doclets with
|
||||
* that longname.
|
||||
*
|
||||
* After you add a doclet to the store, the store automatically tracks changes to a doclet's
|
||||
* properties and recategorizes the doclet as needed. For example, if a doclet's `kind` property
|
||||
* changes from `class` to `interface`, then the doclet store automatically recategorizes the doclet
|
||||
* as an interface.
|
||||
*
|
||||
* @alias @jsdoc/doclet.DocletStore
|
||||
* Doclets can be _visible_, meaning that they should be used to generate output, or _hidden_,
|
||||
* meaning that they're ignored when generating output. Except as noted, the doclet store exposes
|
||||
* only visible doclets.
|
||||
*
|
||||
* @alias module:@jsdoc/doclet.DocletStore
|
||||
*/
|
||||
export class DocletStore {
|
||||
#commonPathPrefix;
|
||||
@ -91,9 +99,9 @@ export class DocletStore {
|
||||
*
|
||||
* When you create a doclet store, you provide a JSDoc environment object. The doclet store
|
||||
* listens for new doclets that are created in that environment. When a new doclet is created, the
|
||||
* doclet store tracks it automatically.
|
||||
* doclet store adds it automatically and tracks updates to the doclet.
|
||||
*
|
||||
* @param {@jsdoc/core.Env} env - The JSDoc environment to use.
|
||||
* @param {module:@jsdoc/core.Env} env - The JSDoc environment to use.
|
||||
*/
|
||||
constructor(env) {
|
||||
this.#commonPathPrefix = null;
|
||||
@ -101,28 +109,85 @@ export class DocletStore {
|
||||
this.#isListening = false;
|
||||
this.#sourcePaths = new Map();
|
||||
|
||||
// TODO: Add descriptions and types for public properties.
|
||||
/** @type Map<string, Set<Doclet>> */
|
||||
/**
|
||||
* Map of all doclet longnames to a `Set` of all doclets with that longname. Includes both
|
||||
* visible and hidden doclets.
|
||||
*
|
||||
* @type Map<string, Set<module:@jsdoc/doclet.Doclet>>
|
||||
*/
|
||||
this.allDocletsByLongname = new Map();
|
||||
/** Doclets that are used to generate output. */
|
||||
/**
|
||||
* All visible doclets.
|
||||
*
|
||||
* @type Set<module:@jsdoc/doclet.Doclet>
|
||||
*/
|
||||
this.doclets = new Set();
|
||||
/** @type Map<string, Set<Doclet>> */
|
||||
/**
|
||||
* Map from a doclet kind to a `Set` of doclets with that kind.
|
||||
*
|
||||
* @type Map<string, Set<module:@jsdoc/doclet.Doclet>>
|
||||
*/
|
||||
this.docletsByKind = new Map();
|
||||
/** @type Map<string, Set<Doclet>> */
|
||||
/**
|
||||
* Map from a doclet longname to a `Set` of doclets with that longname.
|
||||
*
|
||||
* @type Map<string, Set<module:@jsdoc/doclet.Doclet>>
|
||||
*/
|
||||
this.docletsByLongname = new Map();
|
||||
/** @type Map<string, Set<Doclet>> */
|
||||
/**
|
||||
* Map from a doclet `memberof` value to a `Set` of doclets with that `memberof`.
|
||||
*
|
||||
* @type Map<string, Set<module:@jsdoc/doclet.Doclet>>
|
||||
*/
|
||||
this.docletsByMemberof = new Map();
|
||||
/** @type Map<string, Set<Doclet>> */
|
||||
/**
|
||||
* Map from an AST node ID, generated during parsing, to a `Set` of doclets for that node ID.
|
||||
*
|
||||
* @type Map<string, Set<module:@jsdoc/doclet.Doclet>>
|
||||
*/
|
||||
this.docletsByNodeId = new Map();
|
||||
/**
|
||||
* Doclets that have an `augments` property.
|
||||
*
|
||||
* @type Set<module:@jsdoc/doclet.Doclet>
|
||||
*/
|
||||
this.docletsWithAugments = new Set();
|
||||
/**
|
||||
* Doclets that have a `borrowed` property.
|
||||
*
|
||||
* @type Set<module:@jsdoc/doclet.Doclet>
|
||||
*/
|
||||
this.docletsWithBorrowed = new Set();
|
||||
/**
|
||||
* Doclets that have an `implements` property.
|
||||
*
|
||||
* @type Set<module:@jsdoc/doclet.Doclet>
|
||||
*/
|
||||
this.docletsWithImplements = new Set();
|
||||
/**
|
||||
* Doclets that have a `mixes` property.
|
||||
*
|
||||
* @type Set<module:@jsdoc/doclet.Doclet>
|
||||
*/
|
||||
this.docletsWithMixes = new Set();
|
||||
/**
|
||||
* Doclets that belong to the global scope.
|
||||
*
|
||||
* @type Set<module:@jsdoc/doclet.Doclet>
|
||||
*/
|
||||
this.globals = new Set();
|
||||
/** @type Map<string, Set<Doclet>> */
|
||||
/**
|
||||
* Map from an event's longname to a `Set` of doclets that listen to that event.
|
||||
*
|
||||
* @type Map<string, Set<module:@jsdoc/doclet.Doclet>>
|
||||
*/
|
||||
this.listenersByListensTo = new Map();
|
||||
|
||||
/** Doclets that aren't used to generate output. */
|
||||
/**
|
||||
* Doclets that are hidden and shouldn't be used to generate output.
|
||||
*
|
||||
* @type Set<module:@jsdoc/doclet.Doclet>
|
||||
*/
|
||||
this.unusedDoclets = new Set();
|
||||
|
||||
this.#docletChangedHandler = (e) => this.#handleDocletChanged(e, {});
|
||||
@ -318,7 +383,15 @@ export class DocletStore {
|
||||
// `undocumented` only affects visibility, which is handled above, so we ignore it here.
|
||||
}
|
||||
|
||||
// Adds a doclet to the store directly, rather than by listening to events.
|
||||
/**
|
||||
* Adds a doclet to the store directly, rather than by listening to events from the JSDoc
|
||||
* environment.
|
||||
*
|
||||
* Use this method if you need to track a doclet that's generated outside of JSDoc's parsing
|
||||
* process.
|
||||
*
|
||||
* @param {module:@jsdoc/doclet.Doclet} doclet - The doclet to add.
|
||||
*/
|
||||
add(doclet) {
|
||||
let doclets;
|
||||
let nodeId;
|
||||
@ -337,10 +410,25 @@ export class DocletStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All known doclets, including both visible and hidden doclets.
|
||||
*
|
||||
* @type Set<module:@jsdoc/doclet.Doclet>
|
||||
*/
|
||||
get allDoclets() {
|
||||
return new Set([...this.doclets, ...this.unusedDoclets]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The longest filepath prefix that's shared by the source files that were parsed.
|
||||
*
|
||||
* + If there's only one source file, then the prefix is the source file's directory name.
|
||||
* + If the source files don't have a common prefix, then the prefix is an empty string.
|
||||
*
|
||||
* If a doclet is hidden, then its source filepath is ignored when determining the prefix.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
get commonPathPrefix() {
|
||||
let commonPrefix;
|
||||
let sourcePaths;
|
||||
@ -365,14 +453,30 @@ export class DocletStore {
|
||||
return commonPrefix ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* The longnames of all visible doclets.
|
||||
*
|
||||
* @type Array<string>
|
||||
*/
|
||||
get longnames() {
|
||||
return Array.from(this.docletsByLongname.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* The source paths associated with all visible doclets.
|
||||
*
|
||||
* @type Array<string>
|
||||
*/
|
||||
get sourcePaths() {
|
||||
return Array.from(this.#sourcePaths.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to events from the JSDoc environment.
|
||||
*
|
||||
* In general, you don't need to call this method. A `DocletStore` always listens for events by
|
||||
* default.
|
||||
*/
|
||||
startListening() {
|
||||
if (!this.#isListening) {
|
||||
this.#emitter.on('docletChanged', this.#docletChangedHandler);
|
||||
@ -382,6 +486,12 @@ export class DocletStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop listening to events from the JSDoc environment.
|
||||
*
|
||||
* Call this method if you're done using a `DocletStore`, and you don't want it to listen to
|
||||
* future events.
|
||||
*/
|
||||
stopListening() {
|
||||
if (this.#isListening) {
|
||||
this.#emitter.removeListener('docletChanged', this.#docletChangedHandler);
|
||||
|
||||
@ -410,30 +410,7 @@ function copyPropsWithIncludelist(primary, secondary, target, include) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 excludelist = ['env', 'params', 'properties', 'undocumented'];
|
||||
const includelist = ['params', 'properties'];
|
||||
const target = Doclet.emptyDoclet(secondary.env);
|
||||
|
||||
// First, copy most properties to the target doclet.
|
||||
copyPropsWithExcludelist(primary, secondary, target, excludelist);
|
||||
// Then copy a few specific properties to the target doclet, as long as they're not falsy and
|
||||
// have a length greater than 0.
|
||||
copyPropsWithIncludelist(primary, secondary, target, includelist);
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single JSDoc comment.
|
||||
* Information about a single JSDoc comment, or a single symbol in a source file.
|
||||
*
|
||||
* @alias module:@jsdoc/doclet.Doclet
|
||||
*/
|
||||
@ -441,7 +418,7 @@ Doclet = class {
|
||||
#dictionary;
|
||||
|
||||
/**
|
||||
* Create a doclet.
|
||||
* Creates a doclet.
|
||||
*
|
||||
* @param {string} docletSrc - The raw source code of the jsdoc comment.
|
||||
* @param {object} meta - Properties describing the code related to this comment.
|
||||
@ -468,7 +445,11 @@ Doclet = class {
|
||||
});
|
||||
WATCHABLE_PROPS.forEach((prop) => this.#defineWatchableProp(prop));
|
||||
|
||||
/** The original text of the comment from the source code. */
|
||||
/**
|
||||
* The text of the comment from the source code.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
this.comment = docletSrc;
|
||||
meta ??= {};
|
||||
this.setMeta(meta);
|
||||
@ -492,10 +473,45 @@ Doclet = class {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of an existing doclet.
|
||||
*
|
||||
* @param {module:@jsdoc/doclet.Doclet} doclet - The doclet to copy.
|
||||
* @returns {module:@jsdoc/doclet.Doclet} A copy of the doclet.
|
||||
*/
|
||||
static clone(doclet) {
|
||||
return combineDoclets(doclet, Doclet.emptyDoclet(doclet.env));
|
||||
return Doclet.combineDoclets(doclet, Doclet.emptyDoclet(doclet.env));
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines 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.
|
||||
*/
|
||||
static combineDoclets(primary, secondary) {
|
||||
const excludelist = ['env', 'params', 'properties', 'undocumented'];
|
||||
const includelist = ['params', 'properties'];
|
||||
const target = Doclet.emptyDoclet(secondary.env);
|
||||
|
||||
// First, copy most properties to the target doclet.
|
||||
copyPropsWithExcludelist(primary, secondary, target, excludelist);
|
||||
// Then copy a few specific properties to the target doclet, as long as they're not falsy and
|
||||
// have a length greater than 0.
|
||||
copyPropsWithIncludelist(primary, secondary, target, includelist);
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty doclet.
|
||||
*
|
||||
* @param {module:@jsdoc/core.Env} env - The JSDoc environment to use.
|
||||
* @returns {module:@jsdoc/doclet.Doclet} An empty doclet.
|
||||
*/
|
||||
static emptyDoclet(env) {
|
||||
return new Doclet('', {}, env);
|
||||
}
|
||||
@ -534,7 +550,7 @@ Doclet = class {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tag to the doclet.
|
||||
* Adds 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.
|
||||
@ -554,7 +570,7 @@ Doclet = class {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the doclet represents a globally available symbol.
|
||||
* Checks whether the doclet represents a globally available symbol.
|
||||
*
|
||||
* @returns {boolean} `true` if the doclet represents a global; `false` otherwise.
|
||||
*/
|
||||
@ -563,7 +579,7 @@ Doclet = class {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the doclet should be used to generate output.
|
||||
* Checks whether the doclet should be used to generate output.
|
||||
*
|
||||
* @returns {boolean} `true` if the doclet should be used to generate output; `false` otherwise.
|
||||
*/
|
||||
@ -647,20 +663,21 @@ Doclet = class {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the doclet's `longname` property.
|
||||
* Sets 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* The fully resolved symbol name.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
this.longname = longname;
|
||||
}
|
||||
|
||||
@ -672,6 +689,7 @@ Doclet = class {
|
||||
setMemberof(sid) {
|
||||
/**
|
||||
* The longname of the symbol that contains this one, if any.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
this.memberof = removeGlobal(sid)
|
||||
@ -680,7 +698,7 @@ Doclet = class {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the doclet's `scope` property. Must correspond to a scope name that is defined in
|
||||
* Sets 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.
|
||||
@ -707,10 +725,10 @@ Doclet = class {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a symbol to this doclet's `borrowed` array.
|
||||
* Adds a symbol to the 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.
|
||||
* @param {string} source - The longname of the symbol that is borrowed.
|
||||
* @param {string} target - The name that the borrowed symbol is assigned to.
|
||||
*/
|
||||
borrow(source, target) {
|
||||
const about = { from: source };
|
||||
@ -728,6 +746,11 @@ Doclet = class {
|
||||
this.borrowed.push(about);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a symbol to the doclet's `mixes` array.
|
||||
*
|
||||
* @param {string} source - The longname of the symbol that is mixed in.
|
||||
*/
|
||||
mix(source) {
|
||||
/**
|
||||
* A list of symbols that are mixed into this one, if any.
|
||||
@ -739,7 +762,7 @@ Doclet = class {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a symbol to the doclet's `augments` array.
|
||||
* Adds a symbol to the doclet's `augments` array.
|
||||
*
|
||||
* @param {string} base - The longname of the base symbol.
|
||||
*/
|
||||
@ -753,10 +776,12 @@ Doclet = class {
|
||||
this.augments.push(base);
|
||||
}
|
||||
|
||||
// TODO: Add typedef for `meta`.
|
||||
/**
|
||||
* Set the `meta` property of this doclet.
|
||||
* Sets the `meta` property of the doclet, which contains metadata about the source code that the
|
||||
* doclet corresponds to.
|
||||
*
|
||||
* @param {object} meta
|
||||
* @param {object} meta - The data to add to the doclet.
|
||||
*/
|
||||
setMeta(meta) {
|
||||
let pathname;
|
||||
|
||||
@ -16,12 +16,6 @@
|
||||
|
||||
import stripBom from 'strip-bom';
|
||||
|
||||
/**
|
||||
* Provides access to information about a JavaScript package.
|
||||
*
|
||||
* @see https://www.npmjs.org/doc/files/package.json.html
|
||||
*/
|
||||
|
||||
// Collect all of the license information from a `package.json` file.
|
||||
function getLicenses(packageInfo) {
|
||||
const licenses = packageInfo.licenses ? packageInfo.licenses.slice() : [];
|
||||
@ -75,9 +69,13 @@ function getLicenses(packageInfo) {
|
||||
* **Note**: JSDoc does not validate or normalize the contents of `package.json` files. If your
|
||||
* `package.json` file does not follow the npm specification, some properties of the `Package`
|
||||
* object may not use the format documented here.
|
||||
*
|
||||
* @alias module:@jsdoc/doclet.Package
|
||||
*/
|
||||
export class Package {
|
||||
/**
|
||||
* Creates an object that represents a package.
|
||||
*
|
||||
* @param {string} json - The contents of the `package.json` file.
|
||||
* @param {Object} env - The JSDoc environment.
|
||||
*/
|
||||
@ -142,7 +140,7 @@ export class Package {
|
||||
/**
|
||||
* The contributors to this package.
|
||||
*
|
||||
* @type {Array.<(module:@jsdoc/doclet.Package~PersonInfo|string)>}
|
||||
* @type {Array<(module:@jsdoc/doclet.Package~PersonInfo|string)>}
|
||||
*/
|
||||
this.contributors = packageInfo.contributors;
|
||||
}
|
||||
@ -195,7 +193,7 @@ export class Package {
|
||||
* After JSDoc parses your input files, it sets this property to a list of paths to your input
|
||||
* files.
|
||||
*
|
||||
* @type {Array.<string>}
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
this.files = [];
|
||||
|
||||
@ -212,7 +210,7 @@ export class Package {
|
||||
/**
|
||||
* Keywords to help users find the package.
|
||||
*
|
||||
* @type {Array.<string>}
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
this.keywords = packageInfo.keywords;
|
||||
}
|
||||
@ -222,7 +220,7 @@ export class Package {
|
||||
* The licenses used by this package. Combines information from the `package.json` file's
|
||||
* `license` property and the deprecated `licenses` property.
|
||||
*
|
||||
* @type {Array.<module:@jsdoc/doclet.Package~LicenseInfo>}
|
||||
* @type {Array<module:@jsdoc/doclet.Package~LicenseInfo>}
|
||||
*/
|
||||
this.licenses = getLicenses(packageInfo);
|
||||
}
|
||||
|
||||
@ -34,12 +34,12 @@
|
||||
"@jsdoc/ast": "^0.2.13",
|
||||
"@jsdoc/name": "^0.1.1",
|
||||
"@jsdoc/tag": "^0.2.13",
|
||||
"@jsdoc/util": "^0.3.4",
|
||||
"common-path-prefix": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"on-change": "^5.0.1",
|
||||
"on-change": "^6.0.1",
|
||||
"strip-bom": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,7 +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 { 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';
|
||||
@ -33,12 +33,6 @@ describe('@jsdoc/doclet', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('combineDoclets', () => {
|
||||
it('is lib/doclet.combineDoclets', () => {
|
||||
expect(doclet.combineDoclets).toEqual(combineDoclets);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Doclet', () => {
|
||||
it('is lib/doclet.Doclet', () => {
|
||||
expect(doclet.Doclet).toEqual(Doclet);
|
||||
|
||||
@ -29,7 +29,7 @@ function makeDoclet(comment, meta, env) {
|
||||
env ??= jsdoc.env;
|
||||
doclet = new Doclet(`/**\n${comment.join('\n')}\n*/`, meta, env);
|
||||
if (meta?._emitEvent !== false) {
|
||||
env.get('emitter').emit('newDoclet', { doclet });
|
||||
env.emitter.emit('newDoclet', { doclet });
|
||||
}
|
||||
|
||||
return doclet;
|
||||
|
||||
@ -29,10 +29,6 @@ describe('@jsdoc/doclet/lib/doclet', () => {
|
||||
expect(doclet).toBeObject();
|
||||
});
|
||||
|
||||
it('has a combineDoclets method', () => {
|
||||
expect(doclet.combineDoclets).toBeFunction();
|
||||
});
|
||||
|
||||
it('has a Doclet class', () => {
|
||||
expect(doclet.Doclet).toBeFunction();
|
||||
});
|
||||
@ -41,74 +37,6 @@ describe('@jsdoc/doclet/lib/doclet', () => {
|
||||
expect(doclet.WATCHABLE_PROPS).toBeArrayOfStrings();
|
||||
});
|
||||
|
||||
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, jsdoc.env);
|
||||
const secondaryDoclet = new Doclet('/** Hello!\n@version 1.0.0 */', null, jsdoc.env);
|
||||
const newDoclet = doclet.combineDoclets(primaryDoclet, secondaryDoclet);
|
||||
|
||||
descriptors = Object.getOwnPropertyDescriptors(newDoclet);
|
||||
Object.keys(descriptors).forEach((property) => {
|
||||
if (!descriptors[property].enumerable) {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(newDoclet[property]).toEqual(primaryDoclet[property]);
|
||||
});
|
||||
});
|
||||
|
||||
it('adds properties from the secondary doclet that are missing', () => {
|
||||
const primaryDoclet = new Doclet('/** Hello!\n@version 2.0.0 */', null, jsdoc.env);
|
||||
const secondaryDoclet = new Doclet('/** Hello! */', null, jsdoc.env);
|
||||
const newDoclet = doclet.combineDoclets(primaryDoclet, secondaryDoclet);
|
||||
|
||||
expect(newDoclet.version).toBe('2.0.0');
|
||||
});
|
||||
|
||||
describe('params and properties', () => {
|
||||
const properties = ['params', 'properties'];
|
||||
|
||||
it('uses params and properties from the secondary doclet if the primary lacks them', () => {
|
||||
const primaryDoclet = new Doclet('/** Hello! */', null, jsdoc.env);
|
||||
const secondaryComment = [
|
||||
'/**',
|
||||
' * @param {string} foo - The foo.',
|
||||
' * @property {number} bar - The bar.',
|
||||
' */',
|
||||
].join('\n');
|
||||
const secondaryDoclet = new Doclet(secondaryComment, null, jsdoc.env);
|
||||
const newDoclet = doclet.combineDoclets(primaryDoclet, secondaryDoclet);
|
||||
|
||||
properties.forEach((property) => {
|
||||
expect(newDoclet[property]).toEqual(secondaryDoclet[property]);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses params and properties from the primary doclet, if present', () => {
|
||||
const primaryComment = [
|
||||
'/**',
|
||||
' * @param {number} baz - The baz.',
|
||||
' * @property {string} qux - The qux.',
|
||||
' */',
|
||||
].join('\n');
|
||||
const primaryDoclet = new Doclet(primaryComment, null, jsdoc.env);
|
||||
const secondaryComment = [
|
||||
'/**',
|
||||
' * @param {string} foo - The foo.',
|
||||
' * @property {number} bar - The bar.',
|
||||
' */',
|
||||
].join('\n');
|
||||
const secondaryDoclet = new Doclet(secondaryComment, null, jsdoc.env);
|
||||
const newDoclet = doclet.combineDoclets(primaryDoclet, secondaryDoclet);
|
||||
|
||||
properties.forEach((property) => {
|
||||
expect(newDoclet[property]).toEqual(primaryDoclet[property]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Doclet', () => {
|
||||
function makeDoclet(tagStrings, env) {
|
||||
const comment = `/**\n${tagStrings.join('\n')}\n*/`;
|
||||
@ -305,6 +233,86 @@ describe('@jsdoc/doclet/lib/doclet', () => {
|
||||
xit('TODO: write tests');
|
||||
});
|
||||
|
||||
xdescribe('clone', () => {
|
||||
xit('TODO: write tests');
|
||||
});
|
||||
|
||||
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,
|
||||
jsdoc.env
|
||||
);
|
||||
const secondaryDoclet = new Doclet('/** Hello!\n@version 1.0.0 */', null, jsdoc.env);
|
||||
const newDoclet = Doclet.combineDoclets(primaryDoclet, secondaryDoclet);
|
||||
|
||||
descriptors = Object.getOwnPropertyDescriptors(newDoclet);
|
||||
Object.keys(descriptors).forEach((property) => {
|
||||
if (!descriptors[property].enumerable) {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(newDoclet[property]).toEqual(primaryDoclet[property]);
|
||||
});
|
||||
});
|
||||
|
||||
it('adds properties from the secondary doclet that are missing', () => {
|
||||
const primaryDoclet = new Doclet('/** Hello!\n@version 2.0.0 */', null, jsdoc.env);
|
||||
const secondaryDoclet = new Doclet('/** Hello! */', null, jsdoc.env);
|
||||
const newDoclet = Doclet.combineDoclets(primaryDoclet, secondaryDoclet);
|
||||
|
||||
expect(newDoclet.version).toBe('2.0.0');
|
||||
});
|
||||
|
||||
describe('params and properties', () => {
|
||||
const properties = ['params', 'properties'];
|
||||
|
||||
it('uses params and properties from the secondary doclet if the primary lacks them', () => {
|
||||
const primaryDoclet = new Doclet('/** Hello! */', null, jsdoc.env);
|
||||
const secondaryComment = [
|
||||
'/**',
|
||||
' * @param {string} foo - The foo.',
|
||||
' * @property {number} bar - The bar.',
|
||||
' */',
|
||||
].join('\n');
|
||||
const secondaryDoclet = new Doclet(secondaryComment, null, jsdoc.env);
|
||||
const newDoclet = Doclet.combineDoclets(primaryDoclet, secondaryDoclet);
|
||||
|
||||
properties.forEach((property) => {
|
||||
expect(newDoclet[property]).toEqual(secondaryDoclet[property]);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses params and properties from the primary doclet, if present', () => {
|
||||
const primaryComment = [
|
||||
'/**',
|
||||
' * @param {number} baz - The baz.',
|
||||
' * @property {string} qux - The qux.',
|
||||
' */',
|
||||
].join('\n');
|
||||
const primaryDoclet = new Doclet(primaryComment, null, jsdoc.env);
|
||||
const secondaryComment = [
|
||||
'/**',
|
||||
' * @param {string} foo - The foo.',
|
||||
' * @property {number} bar - The bar.',
|
||||
' */',
|
||||
].join('\n');
|
||||
const secondaryDoclet = new Doclet(secondaryComment, null, jsdoc.env);
|
||||
const newDoclet = Doclet.combineDoclets(primaryDoclet, secondaryDoclet);
|
||||
|
||||
properties.forEach((property) => {
|
||||
expect(newDoclet[property]).toEqual(primaryDoclet[property]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
xdescribe('emptyDoclet', () => {
|
||||
xit('TODO: write tests');
|
||||
});
|
||||
|
||||
describe('isGlobal', () => {
|
||||
it('identifies global constants', () => {
|
||||
const newDoclet = makeDoclet(['@constant', '@global', '@name foo']);
|
||||
|
||||
@ -11,15 +11,15 @@
|
||||
"license": "Apache-2.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@babel/eslint-parser": "^7.26.5",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-prettier": "^5.2.2",
|
||||
"@babel/eslint-parser": "^7.28.5",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"globals": "^15.14.0"
|
||||
"globals": "^16.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^9.18.0",
|
||||
"prettier": "^3.4.2"
|
||||
"eslint": "^9.39.1",
|
||||
"prettier": "^3.7.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@ -41,7 +41,7 @@
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23.0.0"
|
||||
},
|
||||
"gitHead": "81a824eb0968851f836fc6069376d0337775f9ba"
|
||||
}
|
||||
|
||||
@ -14,10 +14,4 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Methods for working with namepaths in JSDoc.
|
||||
*
|
||||
* @module @jsdoc/name
|
||||
*/
|
||||
|
||||
export * from './lib/name.js';
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
/**
|
||||
* Methods for working with namepaths in JSDoc.
|
||||
*
|
||||
* @alias module:@jsdoc/name
|
||||
* @module @jsdoc/name
|
||||
*/
|
||||
|
||||
import escape from 'escape-string-regexp';
|
||||
@ -27,7 +27,6 @@ import _ from 'lodash';
|
||||
* Longnames that have a special meaning in JSDoc.
|
||||
*
|
||||
* @enum {string}
|
||||
* @static
|
||||
*/
|
||||
export const LONGNAMES = {
|
||||
/** Longname used for doclets that do not have a longname, such as anonymous functions. */
|
||||
@ -47,30 +46,89 @@ export const MODULE_NAMESPACE = 'module:';
|
||||
* Names and punctuation marks that identify doclet scopes.
|
||||
*
|
||||
* @enum {string}
|
||||
* @static
|
||||
*/
|
||||
export const SCOPE = {
|
||||
/**
|
||||
* Scope names.
|
||||
*/
|
||||
NAMES: {
|
||||
/**
|
||||
* Global scope. The symbol is available globally.
|
||||
*/
|
||||
GLOBAL: 'global',
|
||||
/**
|
||||
* Inner scope. The symbol is available only within the enclosing scope.
|
||||
*/
|
||||
INNER: 'inner',
|
||||
/**
|
||||
* Instance scope. The symbol is available on instances of its parent.
|
||||
*/
|
||||
INSTANCE: 'instance',
|
||||
/**
|
||||
* Static scope. The symbol is a static property of its parent.
|
||||
*/
|
||||
STATIC: 'static',
|
||||
},
|
||||
/**
|
||||
* Punctuation used in JSDoc namepaths to identify a symbol's scope.
|
||||
*
|
||||
* Global scope does not have a punctuation equivalent.
|
||||
*/
|
||||
PUNC: {
|
||||
/**
|
||||
* The abbreviation for inner scope: `~`
|
||||
*/
|
||||
INNER: '~',
|
||||
/**
|
||||
* The abbreviation for instance scope: `#`
|
||||
*/
|
||||
INSTANCE: '#',
|
||||
/**
|
||||
* The abbreviation for static scope: `.`
|
||||
*/
|
||||
STATIC: '.',
|
||||
},
|
||||
};
|
||||
|
||||
// Keys must be lowercase.
|
||||
/**
|
||||
* Doclet scope identifiers mapped to the equivalent punctuation.
|
||||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
export const SCOPE_TO_PUNC = {
|
||||
/**
|
||||
* The abbreviation for inner scope.
|
||||
*/
|
||||
inner: SCOPE.PUNC.INNER,
|
||||
/**
|
||||
* The abbreviation for instance scope.
|
||||
*/
|
||||
instance: SCOPE.PUNC.INSTANCE,
|
||||
/**
|
||||
* The abbreviation for static scope.
|
||||
*/
|
||||
static: SCOPE.PUNC.STATIC,
|
||||
};
|
||||
|
||||
export const PUNC_TO_SCOPE = _.invert(SCOPE_TO_PUNC);
|
||||
/**
|
||||
* Doclet scope punctuation mapped to the equivalent identifiers.
|
||||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
export const PUNC_TO_SCOPE = {
|
||||
/**
|
||||
* The identifier for inner scope.
|
||||
*/
|
||||
'~': 'inner',
|
||||
/**
|
||||
* The identifier for instance scope.
|
||||
*/
|
||||
'#': 'instance',
|
||||
/**
|
||||
* The identifier for static scope.
|
||||
*/
|
||||
'.': 'static',
|
||||
};
|
||||
|
||||
const SCOPE_PUNC = _.values(SCOPE.PUNC);
|
||||
const SCOPE_PUNC_STRING = `[${SCOPE_PUNC.join()}]`;
|
||||
@ -82,7 +140,7 @@ const REGEXP_DESCRIPTION = new RegExp(DESCRIPTION);
|
||||
const REGEXP_NAME_DESCRIPTION = new RegExp(`^(\\[[^\\]]+\\]|\\S+)${DESCRIPTION}`);
|
||||
|
||||
/**
|
||||
* Check whether a name appears to represent a complete longname that is a member of the specified
|
||||
* Checks whether a name appears to represent a complete longname that is a member of the specified
|
||||
* parent.
|
||||
*
|
||||
* @example
|
||||
@ -101,7 +159,7 @@ export function nameIsLongname(name, memberof) {
|
||||
}
|
||||
|
||||
/**
|
||||
* For names that identify a property of a prototype, replace the `prototype` portion of the name
|
||||
* For names that identify a property of a prototype, replaces the `prototype` portion of the name
|
||||
* with `#`, which indicates instance-level scope. For example, `Foo.prototype.bar` becomes
|
||||
* `Foo#bar`.
|
||||
*
|
||||
@ -114,7 +172,8 @@ export function prototypeToPunc(name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
return name.replace(/(?:^|\.)prototype\.?/g, SCOPE.PUNC.INSTANCE);
|
||||
// If there's a trailing open bracket ([), as in `Foo.prototype['bar']`, keep it.
|
||||
return name.replace(/(?:^|\.)prototype(?:$|\.|(\[))/g, `${SCOPE.PUNC.INSTANCE}$1`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -123,11 +182,11 @@ export function prototypeToPunc(name) {
|
||||
* @param {string} name - The name to check.
|
||||
* @returns {?string} The leading scope character, if one is present.
|
||||
*/
|
||||
export const getLeadingScope = (name) => {
|
||||
export function getLeadingScope(name) {
|
||||
const match = name.match(REGEXP_LEADING_SCOPE);
|
||||
|
||||
return match?.[1];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the trailing scope character, if any, from a name.
|
||||
@ -135,14 +194,14 @@ export const getLeadingScope = (name) => {
|
||||
* @param {string} name - The name to check.
|
||||
* @returns {?string} The trailing scope character, if one is present.
|
||||
*/
|
||||
export const getTrailingScope = (name) => {
|
||||
export function getTrailingScope(name) {
|
||||
const match = name.match(REGEXP_TRAILING_SCOPE);
|
||||
|
||||
return match?.[1];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a symbol's basename, which is the first part of its full name before any scope punctuation.
|
||||
* Gets a symbol's basename, which is the first part of its full name before any scope punctuation.
|
||||
* For example, all of the following names have the basename `Foo`:
|
||||
*
|
||||
* + `Foo`
|
||||
@ -164,7 +223,9 @@ export function getBasename(name) {
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
export const stripNamespace = (longname) => longname.replace(/^[a-zA-Z]+:/, '');
|
||||
export function stripNamespace(longname) {
|
||||
return longname.replace(/^[a-zA-Z]+:/, '');
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
function slice(longname, sliceChars, forcedMemberof) {
|
||||
@ -252,8 +313,9 @@ function slice(longname, sliceChars, forcedMemberof) {
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Document this with a typedef.
|
||||
/**
|
||||
* Given a longname like `a.b#c(2)`, split it into the following parts:
|
||||
* Given a longname like `a.b#c(2)`, this method splits it into the following parts:
|
||||
*
|
||||
* + `longname`
|
||||
* + `memberof`
|
||||
@ -265,12 +327,17 @@ function slice(longname, sliceChars, forcedMemberof) {
|
||||
* @param {string} forcedMemberof
|
||||
* @returns {object} Representing the properties of the given name.
|
||||
*/
|
||||
export const toParts = (longname, forcedMemberof) => slice(longname, null, forcedMemberof);
|
||||
export function toParts(longname, forcedMemberof) {
|
||||
return slice(longname, null, forcedMemberof);
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
/**
|
||||
* @param {string} longname The full longname of the symbol.
|
||||
* @param {string} ns The namespace to be applied.
|
||||
* Applies a namespace to a longname.
|
||||
*
|
||||
* If the longname already has a namespace, then the namespace is not applied.
|
||||
*
|
||||
* @param {string} longname - The longname of the symbol.
|
||||
* @param {string} ns - The namespace to be applied.
|
||||
* @returns {string} The longname with the namespace applied.
|
||||
*/
|
||||
export function applyNamespace(longname, ns) {
|
||||
@ -287,11 +354,11 @@ export function applyNamespace(longname, ns) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a parent longname is an ancestor of a child longname.
|
||||
* Checks whether a parent longname is an ancestor of a child longname.
|
||||
*
|
||||
* @param {string} parent - The parent longname.
|
||||
* @param {string} child - The child longname.
|
||||
* @return {boolean} `true` if the parent is an ancestor of the child; otherwise, `false`.
|
||||
* @returns {boolean} `true` if the parent is an ancestor of the child; otherwise, `false`.
|
||||
*/
|
||||
export function hasAncestor(parent, child) {
|
||||
let parentIsAncestor = false;
|
||||
@ -360,19 +427,19 @@ function splitLongname(longname, options) {
|
||||
// TODO: Document this with a typedef.
|
||||
// TODO: Add at least one or two basic tests, so we know if this completely breaks.
|
||||
/**
|
||||
* Convert an array of doclet longnames into a tree structure, optionally attaching doclets to the
|
||||
* Converts an array of doclet longnames into a tree structure, optionally attaching doclets to the
|
||||
* tree.
|
||||
*
|
||||
* Each level of the tree is an object with the following properties:
|
||||
*
|
||||
* + `longname {string}`: The longname.
|
||||
* + `memberof {string?}`: The memberof.
|
||||
* + `scope {string?}`: The longname's scope, represented as a punctuation mark (for example, `#`
|
||||
* + `memberof {?string}`: The memberof.
|
||||
* + `scope {?string}`: The longname's scope, represented as a punctuation mark (for example, `#`
|
||||
* for instance and `.` for static).
|
||||
* + `name {string}`: The short name.
|
||||
* + `doclet {Object?}`: The doclet associated with the longname, or `null` if the doclet was not
|
||||
* + `doclet {?Object}`: The doclet associated with the longname, or `null` if the doclet was not
|
||||
* provided.
|
||||
* + `children {Object?}`: The children of the current longname. Not present if there are no
|
||||
* + `children {?Object}`: The children of the current longname. Not present if there are no
|
||||
* children.
|
||||
*
|
||||
* For example, suppose you have the following array of doclet longnames:
|
||||
@ -439,7 +506,7 @@ function splitLongname(longname, options) {
|
||||
* @param {Object<string, module:@jsdoc/doclet.Doclet>} doclets - The doclets to attach to a tree.
|
||||
* Each property should be the longname of a doclet, and each value should be the doclet for that
|
||||
* longname.
|
||||
* @return {Object} A tree with information about each longname in the format shown above.
|
||||
* @returns {Object} A tree with information about each longname in the format shown above.
|
||||
*/
|
||||
export function longnamesToTree(longnames, doclets) {
|
||||
const splitOptions = { includeVariation: false };
|
||||
@ -481,13 +548,14 @@ export function longnamesToTree(longnames, doclets) {
|
||||
return tree;
|
||||
}
|
||||
|
||||
// TODO: Document this with a typedef.
|
||||
/**
|
||||
* Split a string that starts with a name and ends with a description into its parts. Allows the
|
||||
* Splits a string that starts with a name and ends with a description into its parts. Allows the
|
||||
* default value (if present) to contain brackets. Returns `null` if the name contains mismatched
|
||||
* brackets.
|
||||
*
|
||||
* @param {string} nameDesc
|
||||
* @returns {?Object} Hash with "name" and "description" properties.
|
||||
* @param {string} nameDesc - The combined name and description.
|
||||
* @returns {?Object} An object with `name` and `description` properties.
|
||||
*/
|
||||
function splitNameMatchingBrackets(nameDesc) {
|
||||
const buffer = [];
|
||||
@ -529,10 +597,12 @@ function splitNameMatchingBrackets(nameDesc) {
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Document this with a typedef.
|
||||
/**
|
||||
* Split a string that starts with a name and ends with a description into separate parts.
|
||||
* @param {string} str - The string that contains the name and description.
|
||||
* @returns {object} An object with `name` and `description` properties.
|
||||
* Splits a string that starts with a name and ends with a description into separate parts.
|
||||
*
|
||||
* @param {string} str - The combined name and description.
|
||||
* @returns {Object} An object with `name` and `description` properties.
|
||||
*/
|
||||
export function splitNameAndDescription(str) {
|
||||
// Like: `name`, `[name]`, `name text`, `[name] text`, `name - text`, or `[name] - text`.
|
||||
@ -540,7 +610,7 @@ export function splitNameAndDescription(str) {
|
||||
// must be on the same line as the name.
|
||||
|
||||
let match;
|
||||
// Optional values get special treatment,
|
||||
// Optional values get special treatment.
|
||||
let result = null;
|
||||
|
||||
if (str[0] === '[') {
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23.0.0"
|
||||
},
|
||||
"gitHead": "a595f7f2a525f32da9a0498a027667af3d938158"
|
||||
}
|
||||
|
||||
@ -115,37 +115,63 @@ function isModuleExports(module, doclet) {
|
||||
return module.longname === doclet.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an AST node's closest ancestor with the specified type.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} node - The AST node.
|
||||
* @param {(module:@jsdoc/ast.Syntax|string)} ancestorType - The type of ancestor node to find.
|
||||
* @return {?Object} The closest ancestor with the specified type.
|
||||
*/
|
||||
function findAncestorWithType(node, ancestorType) {
|
||||
let parent = node?.parent;
|
||||
|
||||
while (parent) {
|
||||
if (parent.type === ancestorType) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function setModuleScopeMemberOf(parser, doclet) {
|
||||
const moduleInfo = getModule();
|
||||
const node = doclet.meta?.code?.node;
|
||||
let parentDoclet;
|
||||
let skipMemberof;
|
||||
|
||||
// Handle CommonJS module symbols that are _not_ assigned to `module.exports`.
|
||||
// Handle module symbols, excluding CommonJS `module.exports`.
|
||||
if (moduleInfo && !isModuleExports(moduleInfo, doclet)) {
|
||||
if (!doclet.scope) {
|
||||
// is this a method definition? if so, we usually get the scope from the node directly
|
||||
if (doclet.meta?.code?.node?.type === Syntax.MethodDefinition) {
|
||||
parentDoclet = parser._getDocletById(doclet.meta.code.node.parent.parent.nodeId);
|
||||
if (node?.type === Syntax.MethodDefinition) {
|
||||
parentDoclet = parser._getDocletById(node.parent.parent.nodeId);
|
||||
// special case for constructors of classes that have @alias tags
|
||||
if (doclet.meta.code.node.kind === 'constructor' && parentDoclet?.alias) {
|
||||
if (node.kind === 'constructor' && parentDoclet?.alias) {
|
||||
// the constructor should use the same name as the class
|
||||
doclet.addTag('alias', parentDoclet.alias);
|
||||
doclet.addTag('name', parentDoclet.alias);
|
||||
// and we shouldn't try to set a memberof value
|
||||
skipMemberof = true;
|
||||
} else {
|
||||
doclet.addTag(doclet.meta.code.node.static ? 'static' : 'instance');
|
||||
doclet.addTag(node.static ? 'static' : 'instance');
|
||||
// The doclet should be a member of the parent doclet's alias.
|
||||
if (parentDoclet?.alias) {
|
||||
doclet.memberof = parentDoclet.alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
// is this something that the module exports? if so, it's a static member
|
||||
else if (doclet.meta?.code?.node?.parent?.type === Syntax.ExportNamedDeclaration) {
|
||||
// Is this something that the module exports? if so, it's a static member.
|
||||
else if (
|
||||
node?.type === Syntax.ExportNamedDeclaration ||
|
||||
findAncestorWithType(node, Syntax.ExportNamedDeclaration)
|
||||
) {
|
||||
doclet.addTag('static');
|
||||
}
|
||||
// otherwise, it must be an inner member
|
||||
// Otherwise, it must be an inner member.
|
||||
else {
|
||||
doclet.addTag('inner');
|
||||
}
|
||||
@ -153,7 +179,7 @@ function setModuleScopeMemberOf(parser, doclet) {
|
||||
|
||||
// if the doclet isn't a memberof anything yet, and it's not a global, it must be a memberof
|
||||
// the current module (unless we were told to skip adding memberof)
|
||||
if (!doclet.memberof && doclet.scope !== SCOPE.NAMES.GLOBAL && !skipMemberof) {
|
||||
if (!skipMemberof && !doclet.memberof && doclet.scope !== SCOPE.NAMES.GLOBAL) {
|
||||
doclet.addTag('memberof', moduleInfo.longname);
|
||||
}
|
||||
}
|
||||
@ -180,12 +206,12 @@ function addDoclet(parser, newDoclet) {
|
||||
}
|
||||
}
|
||||
|
||||
function processAlias(parser, doclet, astNode) {
|
||||
function processAlias(parser, doclet, node) {
|
||||
let match;
|
||||
let memberofName;
|
||||
|
||||
if (doclet.alias === '{@thisClass}') {
|
||||
memberofName = parser.resolveThis(astNode);
|
||||
memberofName = parser.resolveThis(node);
|
||||
|
||||
// "class" refers to the owner of the prototype, not the prototype itself
|
||||
match = memberofName.match(PROTOTYPE_OWNER_REGEXP);
|
||||
@ -204,7 +230,7 @@ function isModuleObject(doclet) {
|
||||
}
|
||||
|
||||
// TODO: separate code that resolves `this` from code that resolves the module object
|
||||
function findSymbolMemberof(parser, doclet, astNode, nameStartsWith, trailingPunc) {
|
||||
function findSymbolMemberof(parser, doclet, node, nameStartsWith, trailingPunc) {
|
||||
const docletIsModuleObject = isModuleObject(doclet);
|
||||
let memberof = '';
|
||||
let nameAndPunc;
|
||||
@ -237,7 +263,7 @@ function findSymbolMemberof(parser, doclet, astNode, nameStartsWith, trailingPun
|
||||
doclet.addTag('name', currentModule.longname);
|
||||
doclet.postProcess();
|
||||
} else {
|
||||
memberof = parser.resolveThis(astNode);
|
||||
memberof = parser.resolveThis(node);
|
||||
|
||||
// like the following at the top level of a module:
|
||||
// this.foo = 1;
|
||||
@ -255,7 +281,7 @@ function findSymbolMemberof(parser, doclet, astNode, nameStartsWith, trailingPun
|
||||
};
|
||||
}
|
||||
|
||||
function addSymbolMemberof(parser, doclet, astNode) {
|
||||
function addSymbolMemberof(parser, doclet, node) {
|
||||
let basename;
|
||||
let memberof;
|
||||
let memberofInfo;
|
||||
@ -264,7 +290,7 @@ function addSymbolMemberof(parser, doclet, astNode) {
|
||||
let scopePunc;
|
||||
let unresolved;
|
||||
|
||||
if (!astNode) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -279,7 +305,7 @@ function addSymbolMemberof(parser, doclet, astNode) {
|
||||
unresolved = resolveTargetRegExp.exec(doclet.name);
|
||||
|
||||
if (unresolved) {
|
||||
memberofInfo = findSymbolMemberof(parser, doclet, astNode, unresolved[1], unresolved[2]);
|
||||
memberofInfo = findSymbolMemberof(parser, doclet, node, unresolved[1], unresolved[2]);
|
||||
memberof = memberofInfo.memberof;
|
||||
scopePunc = memberofInfo.scopePunc;
|
||||
|
||||
@ -287,7 +313,7 @@ function addSymbolMemberof(parser, doclet, astNode) {
|
||||
doclet.name = doclet.name ? memberof + scopePunc + doclet.name : memberof;
|
||||
}
|
||||
} else {
|
||||
memberofInfo = parser.astnodeToMemberof(astNode);
|
||||
memberofInfo = parser.astnodeToMemberof(node);
|
||||
basename = memberofInfo.basename;
|
||||
memberof = memberofInfo.memberof;
|
||||
}
|
||||
|
||||
@ -60,6 +60,30 @@ function getLastValue(set) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function isClassMethodFromArrowFunction(node) {
|
||||
return (
|
||||
node.type === Syntax.MethodDefinition &&
|
||||
node.parent.parent.parent?.type === Syntax.ArrowFunctionExpression
|
||||
);
|
||||
}
|
||||
|
||||
function isClassProperty(node) {
|
||||
return node.type === Syntax.ClassPrivateProperty || node.type === Syntax.ClassProperty;
|
||||
}
|
||||
|
||||
function isConstructor(node) {
|
||||
return node.type === Syntax.MethodDefinition && node.kind === 'constructor';
|
||||
}
|
||||
|
||||
function isFunctionOrVariableDeclarator({ type }) {
|
||||
return (
|
||||
type === Syntax.FunctionDeclaration ||
|
||||
type === Syntax.FunctionExpression ||
|
||||
type === Syntax.ArrowFunctionExpression ||
|
||||
type === Syntax.VariableDeclarator
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
/**
|
||||
* @alias module:jsdoc/src/parser.Parser
|
||||
@ -324,18 +348,14 @@ export class Parser extends EventEmitter {
|
||||
astnodeToMemberof(node) {
|
||||
let basename;
|
||||
let doclet;
|
||||
let scope;
|
||||
let memberof;
|
||||
|
||||
const result = {};
|
||||
const type = node.type;
|
||||
const result = {
|
||||
basename: null,
|
||||
memberof: null,
|
||||
};
|
||||
|
||||
if (
|
||||
(type === Syntax.FunctionDeclaration ||
|
||||
type === Syntax.FunctionExpression ||
|
||||
type === Syntax.ArrowFunctionExpression ||
|
||||
type === Syntax.VariableDeclarator) &&
|
||||
node.enclosingScope
|
||||
) {
|
||||
if (isFunctionOrVariableDeclarator(node) && node.enclosingScope) {
|
||||
doclet = this._getDocletById(node.enclosingScope.nodeId);
|
||||
|
||||
if (!doclet) {
|
||||
@ -343,7 +363,7 @@ export class Parser extends EventEmitter {
|
||||
} else {
|
||||
result.memberof = doclet.longname + SCOPE.PUNC.INNER;
|
||||
}
|
||||
} else if (type === Syntax.ClassPrivateProperty || type === Syntax.ClassProperty) {
|
||||
} else if (isClassProperty(node)) {
|
||||
doclet = this._getDocletById(node.enclosingScope.nodeId);
|
||||
|
||||
if (!doclet) {
|
||||
@ -351,21 +371,17 @@ export class Parser extends EventEmitter {
|
||||
} else {
|
||||
result.memberof = doclet.longname + SCOPE.PUNC.INSTANCE;
|
||||
}
|
||||
} else if (type === Syntax.MethodDefinition && node.kind === 'constructor') {
|
||||
} else if (isConstructor(node)) {
|
||||
doclet = this._getDocletById(node.enclosingScope.nodeId);
|
||||
|
||||
// global classes aren't a member of anything
|
||||
// Global classes aren't a member of anything.
|
||||
if (doclet.memberof) {
|
||||
result.memberof = doclet.memberof + SCOPE.PUNC.INNER;
|
||||
}
|
||||
}
|
||||
// special case for methods in classes that are returned by arrow function expressions; for
|
||||
// other method definitions, we get the memberof from the node name elsewhere. yes, this is
|
||||
// confusing...
|
||||
else if (
|
||||
type === Syntax.MethodDefinition &&
|
||||
node.parent.parent.parent?.type === Syntax.ArrowFunctionExpression
|
||||
) {
|
||||
// Special case for methods in classes that are returned by arrow function expressions. For
|
||||
// other method declarations, we get the memberof from the node name elsewhere.
|
||||
else if (isClassMethodFromArrowFunction(node)) {
|
||||
doclet = this._getDocletById(node.enclosingScope.nodeId);
|
||||
|
||||
if (doclet) {
|
||||
@ -373,35 +389,25 @@ export class Parser extends EventEmitter {
|
||||
doclet.longname + (node.static === true ? SCOPE.PUNC.STATIC : SCOPE.PUNC.INSTANCE);
|
||||
}
|
||||
} else {
|
||||
// check local references for aliases
|
||||
scope = node;
|
||||
basename = getBasename(astNode.nodeToValue(node));
|
||||
|
||||
// walk up the scope chain until we find the scope in which the node is defined
|
||||
while (scope.enclosingScope) {
|
||||
doclet = this._getDocletById(scope.enclosingScope.nodeId);
|
||||
if (doclet && definedInScope(doclet, basename)) {
|
||||
result.memberof = doclet.meta.vars[basename];
|
||||
result.basename = basename;
|
||||
break;
|
||||
} else {
|
||||
// move up
|
||||
scope = scope.enclosingScope;
|
||||
}
|
||||
memberof = this._getMemberofFromScopes(node, basename);
|
||||
if (memberof) {
|
||||
result.basename = basename;
|
||||
result.memberof = memberof;
|
||||
}
|
||||
|
||||
// do we know that it's a global?
|
||||
// Do we know that it's a global?
|
||||
doclet = this._getDocletByLongname(LONGNAMES.GLOBAL);
|
||||
if (doclet && definedInScope(doclet, basename)) {
|
||||
result.memberof = doclet.meta.vars[basename];
|
||||
result.basename = basename;
|
||||
result.memberof = doclet.meta.vars[basename];
|
||||
} else {
|
||||
doclet = this._getDocletById(node.parent.nodeId);
|
||||
|
||||
// set the result if we found a doclet. (if we didn't, the AST node may describe a
|
||||
// Set the result if we found a doclet. (If we didn't, then the AST node might represent a
|
||||
// global symbol.)
|
||||
if (doclet) {
|
||||
result.memberof = doclet.longname || doclet.name;
|
||||
result.memberof = doclet.longname ?? doclet.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -453,6 +459,26 @@ export class Parser extends EventEmitter {
|
||||
return isClass(doclet) ? doclet : null;
|
||||
}
|
||||
|
||||
_getMemberofFromScopes(node, basename) {
|
||||
let doclet;
|
||||
let memberof;
|
||||
let scope = node;
|
||||
|
||||
// Walk up the scope chain until we find the scope in which the node is declared.
|
||||
while (scope.enclosingScope) {
|
||||
doclet = this._getDocletById(scope.enclosingScope.nodeId);
|
||||
if (doclet && definedInScope(doclet, basename)) {
|
||||
memberof = doclet.meta.vars[basename];
|
||||
break;
|
||||
} else {
|
||||
// Move up.
|
||||
scope = scope.enclosingScope;
|
||||
}
|
||||
}
|
||||
|
||||
return memberof;
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
/**
|
||||
* Resolve what "this" refers to relative to a node.
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
import path from 'node:path';
|
||||
|
||||
import { astNode, Syntax } from '@jsdoc/ast';
|
||||
import { combineDoclets } from '@jsdoc/doclet';
|
||||
import { Doclet } from '@jsdoc/doclet';
|
||||
import * as name from '@jsdoc/name';
|
||||
|
||||
const { getBasename, LONGNAMES } = name;
|
||||
@ -333,7 +333,7 @@ function makeConstructorFinisher(parser) {
|
||||
|
||||
// We prefer the parent doclet because it has the correct kind, longname, and memberof.
|
||||
// The child doclet might or might not have the correct kind, longname, and memberof.
|
||||
combined = combineDoclets(parentDoclet, eventDoclet, parser.env);
|
||||
combined = Doclet.combineDoclets(parentDoclet, eventDoclet, parser.env);
|
||||
parser.addResult(combined);
|
||||
|
||||
parentDoclet.undocumented = eventDoclet.undocumented = true;
|
||||
|
||||
@ -16,11 +16,10 @@
|
||||
"@jsdoc/ast": "^0.2.13",
|
||||
"@jsdoc/doclet": "^0.2.13",
|
||||
"@jsdoc/name": "^0.1.1",
|
||||
"@jsdoc/util": "^0.3.4",
|
||||
"escape-string-regexp": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@ -20,7 +20,7 @@ import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { Syntax, Walker } from '@jsdoc/ast';
|
||||
import { combineDoclets, Doclet } from '@jsdoc/doclet';
|
||||
import { Doclet } from '@jsdoc/doclet';
|
||||
|
||||
import { attachTo } from '../../../lib/handlers.js';
|
||||
import * as jsdocParser from '../../../lib/parser.js';
|
||||
@ -156,7 +156,7 @@ describe('@jsdoc/parse/lib/parser', () => {
|
||||
const sourceCode = 'javascript:/** @class */function Foo() {}';
|
||||
|
||||
function handler(e) {
|
||||
e.doclet = combineDoclets(e.doclet, new Doclet('', {}, jsdoc.env));
|
||||
e.doclet = Doclet.combineDoclets(e.doclet, new Doclet('', {}, jsdoc.env));
|
||||
e.doclet.foo = 'bar';
|
||||
}
|
||||
|
||||
|
||||
@ -28,13 +28,6 @@
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jsdoc/core": "^0.5.10",
|
||||
"@jsdoc/parse": "^0.3.13"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jsdoc/util": "^0.3.4"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,9 +11,9 @@
|
||||
"license": "Apache-2.0",
|
||||
"main": "index.js",
|
||||
"peerDependencies": {
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-prettier": "^5.2.2",
|
||||
"prettier": "^3.4.2"
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"prettier": "^3.7.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@ -40,9 +40,9 @@
|
||||
"catharsis": "^0.11.0",
|
||||
"common-path-prefix": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"memize": "^2.1.0"
|
||||
"memize": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import dependencyGraph from 'dependency-graph';
|
||||
import Emittery from 'emittery';
|
||||
import _ from 'lodash';
|
||||
@ -23,22 +24,28 @@ import v from './validators.js';
|
||||
|
||||
const { DepGraph } = dependencyGraph;
|
||||
|
||||
// Work around an annoying typo in a method name.
|
||||
DepGraph.prototype.dependentsOf = DepGraph.prototype.dependantsOf;
|
||||
|
||||
export class TaskRunner extends Emittery {
|
||||
#context;
|
||||
#deps;
|
||||
#error;
|
||||
#nameToTask;
|
||||
#queue;
|
||||
#running;
|
||||
#taskToName;
|
||||
#unsubscribers;
|
||||
|
||||
constructor(context) {
|
||||
super();
|
||||
|
||||
ow(context, ow.optional.object);
|
||||
|
||||
this._init(context);
|
||||
this.#init(context);
|
||||
}
|
||||
|
||||
_addOrRemoveTasks(tasks, func, action) {
|
||||
#addOrRemoveTasks(tasks, func, action) {
|
||||
func = _.bind(func, this);
|
||||
|
||||
if (Array.isArray(tasks)) {
|
||||
if (_.isArray(tasks)) {
|
||||
tasks.forEach((task, i) => {
|
||||
try {
|
||||
func(task);
|
||||
@ -47,7 +54,7 @@ export class TaskRunner extends Emittery {
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
} else if (tasks !== null && typeof tasks === 'object') {
|
||||
} else if (_.isObject(tasks)) {
|
||||
for (const task of Object.keys(tasks)) {
|
||||
try {
|
||||
func(tasks[task]);
|
||||
@ -59,7 +66,7 @@ export class TaskRunner extends Emittery {
|
||||
}
|
||||
}
|
||||
|
||||
_addTaskEmitters(task) {
|
||||
#addTaskEmitters(task) {
|
||||
const u = {};
|
||||
|
||||
u.start = task.on('start', (t) => this.emit('taskStart', t));
|
||||
@ -70,56 +77,56 @@ export class TaskRunner extends Emittery {
|
||||
error: e.error,
|
||||
});
|
||||
|
||||
if (!this._error) {
|
||||
this._error = e.error;
|
||||
if (!this.#error) {
|
||||
this.#error = e.error;
|
||||
}
|
||||
});
|
||||
|
||||
this._unsubscribers.set(task.name, u);
|
||||
this.#unsubscribers.set(task.name, u);
|
||||
}
|
||||
|
||||
_bindTaskFunc(task) {
|
||||
return _.bind(task.run, task, this._context);
|
||||
#bindTaskFunc(task) {
|
||||
return _.bind(task.run, task, this.#context);
|
||||
}
|
||||
|
||||
_createTaskSequence(tasks) {
|
||||
#createTaskSequence(tasks) {
|
||||
if (!tasks.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return () =>
|
||||
tasks.reduce((p, taskName) => {
|
||||
const task = this._nameToTask.get(taskName);
|
||||
const task = this.#nameToTask.get(taskName);
|
||||
|
||||
return p.then(this._bindTaskFunc(task), (e) => Promise.reject(e));
|
||||
return p.then(this.#bindTaskFunc(task), (e) => Promise.reject(e));
|
||||
}, Promise.resolve());
|
||||
}
|
||||
|
||||
_init(context) {
|
||||
this._context = context;
|
||||
this._deps = new Map();
|
||||
this._error = null;
|
||||
this._queue = new Queue();
|
||||
this._taskToName = new WeakMap();
|
||||
this._nameToTask = new Map();
|
||||
this._running = false;
|
||||
this._unsubscribers = new Map();
|
||||
#init(context) {
|
||||
this.#context = context;
|
||||
this.#deps = new Map();
|
||||
this.#error = null;
|
||||
this.#nameToTask = new Map();
|
||||
this.#queue = new Queue();
|
||||
this.#running = false;
|
||||
this.#taskToName = new WeakMap();
|
||||
this.#unsubscribers = new Map();
|
||||
|
||||
this._queue.pause();
|
||||
this.#queue.pause();
|
||||
}
|
||||
|
||||
_newDependencyCycleError(cyclePath) {
|
||||
#newDependencyCycleError(cyclePath) {
|
||||
return new v.DependencyCycleError(
|
||||
`Tasks have circular dependencies: ${cyclePath.join(' > ')}`,
|
||||
cyclePath
|
||||
);
|
||||
}
|
||||
|
||||
_newStateError() {
|
||||
#newStateError() {
|
||||
return new v.StateError('The task runner is already running.');
|
||||
}
|
||||
|
||||
_newUnknownDepsError(dependent, unknownDeps) {
|
||||
#newUnknownDepsError(dependent, unknownDeps) {
|
||||
let errorText;
|
||||
|
||||
if (unknownDeps.length === 1) {
|
||||
@ -133,28 +140,28 @@ export class TaskRunner extends Emittery {
|
||||
);
|
||||
}
|
||||
|
||||
_orderTasks() {
|
||||
#orderTasks() {
|
||||
let error;
|
||||
const graph = new DepGraph();
|
||||
let parallel;
|
||||
let sequential;
|
||||
|
||||
for (const [task] of this._nameToTask) {
|
||||
for (const [task] of this.#nameToTask) {
|
||||
graph.addNode(task);
|
||||
}
|
||||
|
||||
for (const [dependent] of this._deps) {
|
||||
for (const [dependent] of this.#deps) {
|
||||
const unknownDeps = [];
|
||||
|
||||
for (const dependency of this._deps.get(dependent)) {
|
||||
if (!this._nameToTask.has(dependency)) {
|
||||
for (const dependency of this.#deps.get(dependent)) {
|
||||
if (!this.#nameToTask.has(dependency)) {
|
||||
unknownDeps.push(dependency);
|
||||
} else {
|
||||
graph.addDependency(dependent, dependency);
|
||||
}
|
||||
|
||||
if (unknownDeps.length) {
|
||||
error = this._newUnknownDepsError(dependency, unknownDeps);
|
||||
error = this.#newUnknownDepsError(dependency, unknownDeps);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -167,7 +174,7 @@ export class TaskRunner extends Emittery {
|
||||
// Get tasks with dependencies, in a correctly ordered list.
|
||||
sequential = graph.overallOrder().filter((task) => !parallel.includes(task));
|
||||
} catch (e) {
|
||||
error = this._newDependencyCycleError(e.cyclePath);
|
||||
error = this.#newDependencyCycleError(e.cyclePath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,92 +185,84 @@ export class TaskRunner extends Emittery {
|
||||
};
|
||||
}
|
||||
|
||||
_rejectIfRunning() {
|
||||
#rejectIfRunning() {
|
||||
if (this.running) {
|
||||
return Promise.reject(this._newStateError());
|
||||
return Promise.reject(this.#newStateError());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_throwIfRunning() {
|
||||
#throwIfRunning() {
|
||||
if (this.running) {
|
||||
throw this._newStateError();
|
||||
throw this.#newStateError();
|
||||
}
|
||||
}
|
||||
|
||||
_throwIfUnknownDeps(dependent, unknownDeps) {
|
||||
if (!unknownDeps.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw this._newUnknownDepsError(dependent, unknownDeps);
|
||||
}
|
||||
|
||||
addTask(task) {
|
||||
ow(task, v.checkTaskOrString);
|
||||
|
||||
this._throwIfRunning();
|
||||
this.#throwIfRunning();
|
||||
|
||||
this._nameToTask.set(task.name, task);
|
||||
this.#nameToTask.set(task.name, task);
|
||||
if (task.dependsOn) {
|
||||
this._deps.set(task.name, task.dependsOn);
|
||||
this.#deps.set(task.name, task.dependsOn);
|
||||
}
|
||||
this._taskToName.set(task, task.name);
|
||||
this._addTaskEmitters(task);
|
||||
this.#taskToName.set(task, task.name);
|
||||
this.#addTaskEmitters(task);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
addTasks(tasks) {
|
||||
ow(tasks, ow.any(ow.array, ow.object));
|
||||
this._addOrRemoveTasks(tasks, this.addTask, 'add');
|
||||
this.#addOrRemoveTasks(tasks, this.addTask, 'add');
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
end() {
|
||||
this.emit('end', {
|
||||
error: this._error,
|
||||
error: this.#error,
|
||||
});
|
||||
this._queue.clear();
|
||||
this._init();
|
||||
this.#queue.clear();
|
||||
this.#init();
|
||||
}
|
||||
|
||||
removeTask(task) {
|
||||
let unsubscribers;
|
||||
|
||||
ow(task, v.checkTaskOrString);
|
||||
this._throwIfRunning();
|
||||
this.#throwIfRunning();
|
||||
|
||||
if (typeof task === 'string') {
|
||||
task = this._nameToTask.get(task);
|
||||
if (_.isString(task)) {
|
||||
task = this.#nameToTask.get(task);
|
||||
|
||||
if (!task) {
|
||||
throw new v.UnknownTaskError(`Unknown task: ${task}`);
|
||||
}
|
||||
} else if (typeof task === 'object') {
|
||||
if (!this._taskToName.has(task)) {
|
||||
} else if (_.isObject(task)) {
|
||||
if (!this.#taskToName.has(task)) {
|
||||
throw new v.UnknownTaskError(`Unknown task: ${task}`);
|
||||
}
|
||||
}
|
||||
|
||||
this._nameToTask.delete(task.name);
|
||||
this._taskToName.delete(task);
|
||||
this._deps.delete(task.name);
|
||||
this.#nameToTask.delete(task.name);
|
||||
this.#taskToName.delete(task);
|
||||
this.#deps.delete(task.name);
|
||||
|
||||
unsubscribers = this._unsubscribers.get(task.name);
|
||||
unsubscribers = this.#unsubscribers.get(task.name);
|
||||
for (const u of Object.keys(unsubscribers)) {
|
||||
unsubscribers[u]();
|
||||
}
|
||||
this._unsubscribers.delete(task.name);
|
||||
this.#unsubscribers.delete(task.name);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
removeTasks(tasks) {
|
||||
ow(tasks, ow.any(ow.array, ow.object));
|
||||
this._addOrRemoveTasks(tasks, this.removeTask, 'remove');
|
||||
this.#addOrRemoveTasks(tasks, this.removeTask, 'remove');
|
||||
|
||||
return this;
|
||||
}
|
||||
@ -272,13 +271,13 @@ export class TaskRunner extends Emittery {
|
||||
ow(context, ow.optional.object);
|
||||
|
||||
let endPromise;
|
||||
const { error, parallel, sequential } = this._orderTasks();
|
||||
const { error, parallel, sequential } = this.#orderTasks();
|
||||
let runningPromise;
|
||||
let taskFuncs = [];
|
||||
let taskSequence;
|
||||
|
||||
// First, fail if the runner is already running.
|
||||
runningPromise = this._rejectIfRunning();
|
||||
runningPromise = this.#rejectIfRunning();
|
||||
if (runningPromise) {
|
||||
return runningPromise;
|
||||
}
|
||||
@ -288,23 +287,23 @@ export class TaskRunner extends Emittery {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
this._context = context || this._context;
|
||||
this.#context = context || this.#context;
|
||||
|
||||
for (const taskName of parallel) {
|
||||
taskFuncs.push(this._bindTaskFunc(this._nameToTask.get(taskName)));
|
||||
taskFuncs.push(this.#bindTaskFunc(this.#nameToTask.get(taskName)));
|
||||
}
|
||||
|
||||
taskSequence = this._createTaskSequence(sequential);
|
||||
taskSequence = this.#createTaskSequence(sequential);
|
||||
if (taskSequence) {
|
||||
taskFuncs.push(taskSequence);
|
||||
}
|
||||
|
||||
endPromise = this._queue.addAll(taskFuncs).then(
|
||||
endPromise = this.#queue.addAll(taskFuncs).then(
|
||||
() => {
|
||||
this.end();
|
||||
|
||||
if (this._error) {
|
||||
return Promise.reject(this._error);
|
||||
if (this.#error) {
|
||||
return Promise.reject(this.#error);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@ -317,13 +316,13 @@ export class TaskRunner extends Emittery {
|
||||
);
|
||||
|
||||
this.emit('start');
|
||||
this._running = true;
|
||||
this.#running = true;
|
||||
try {
|
||||
this._queue.start();
|
||||
this.#queue.start();
|
||||
|
||||
return endPromise;
|
||||
} catch (e) {
|
||||
this._error = e;
|
||||
this.#error = e;
|
||||
this.end();
|
||||
|
||||
return Promise.reject(e);
|
||||
@ -331,13 +330,13 @@ export class TaskRunner extends Emittery {
|
||||
}
|
||||
|
||||
get running() {
|
||||
return this._running;
|
||||
return this.#running;
|
||||
}
|
||||
|
||||
get tasks() {
|
||||
const entries = [];
|
||||
|
||||
for (const entry of this._nameToTask.entries()) {
|
||||
for (const entry of this.#nameToTask.entries()) {
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
|
||||
@ -33,11 +33,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"dependency-graph": "^1.0.0",
|
||||
"emittery": "^1.0.3",
|
||||
"ow": "^2.0.0",
|
||||
"p-queue": "^8.0.1"
|
||||
"emittery": "^1.2.0",
|
||||
"ow": "^3.1.1",
|
||||
"p-queue": "^9.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -720,6 +720,26 @@ export function getSignatureReturns({ yields, returns }, cssClass) {
|
||||
return returnTypes;
|
||||
}
|
||||
|
||||
// Cache for memberof index to speed up ancestor lookups
|
||||
let memberofIndex = null;
|
||||
|
||||
function buildMemberofIndex(data) {
|
||||
if (memberofIndex) {
|
||||
return memberofIndex;
|
||||
}
|
||||
|
||||
memberofIndex = new Map();
|
||||
const allDoclets = data().get();
|
||||
|
||||
for (const doclet of allDoclets) {
|
||||
if (doclet.longname) {
|
||||
memberofIndex.set(doclet.longname, doclet);
|
||||
}
|
||||
}
|
||||
|
||||
return memberofIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an ordered list of doclets for a symbol's ancestors.
|
||||
*
|
||||
@ -733,9 +753,13 @@ export function getAncestors(data, doclet) {
|
||||
let doc = doclet;
|
||||
let previousDoc;
|
||||
|
||||
// Build index once for all lookups
|
||||
const index = buildMemberofIndex(data);
|
||||
|
||||
while (doc) {
|
||||
previousDoc = doc;
|
||||
doc = find(data, { longname: doc.memberof })[0];
|
||||
// Use index instead of database query
|
||||
doc = index.get(doc.memberof);
|
||||
|
||||
// prevent infinite loop that can be caused by duplicated module definitions
|
||||
if (previousDoc === doc) {
|
||||
|
||||
@ -8,16 +8,15 @@
|
||||
"author": "Jeff Williams <jeffrey.l.williams@gmail.com>",
|
||||
"homepage": "https://github.com/jsdoc/jsdoc",
|
||||
"license": "Apache-2.0",
|
||||
"main": "index.js",
|
||||
"main": "publish.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/open-sans": "^5.1.1",
|
||||
"@fontsource-variable/open-sans": "^5.2.7",
|
||||
"@jsdoc/name": "^0.1.1",
|
||||
"@jsdoc/salty": "^0.2.9",
|
||||
"@jsdoc/tag": "^0.2.13",
|
||||
"@jsdoc/util": "^0.3.4",
|
||||
"catharsis": "^0.11.0",
|
||||
"code-prettify": "^0.1.0",
|
||||
"color-themes-for-google-code-prettify": "^2.0.4",
|
||||
@ -28,7 +27,7 @@
|
||||
"markdown-it-anchor": "^9.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@ -29,6 +29,6 @@
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,9 +19,8 @@
|
||||
*
|
||||
* @module @jsdoc/util
|
||||
*/
|
||||
import EventBus from './lib/bus.js';
|
||||
import cast from './lib/cast.js';
|
||||
import getLogFunctions from './lib/log.js';
|
||||
|
||||
export { cast, EventBus, getLogFunctions };
|
||||
export default { cast, EventBus, getLogFunctions };
|
||||
export { cast, getLogFunctions };
|
||||
export default { cast, getLogFunctions };
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 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 EventEmitter from 'node:events';
|
||||
|
||||
import _ from 'lodash';
|
||||
import ow from 'ow';
|
||||
|
||||
let cache = {};
|
||||
|
||||
/**
|
||||
* An event bus that works the same way as a standard Node.js event emitter, with a few key
|
||||
* differences:
|
||||
*
|
||||
* + When you create an event bus, you must specify its name. Consider using your package's name.
|
||||
* + Event buses are cached and shared by default. If module A and module B both create an event bus
|
||||
* called `foo`, both modules get the same event bus. This behavior makes it easier to share one
|
||||
* event bus among all of the modules in your package.
|
||||
*
|
||||
* To prevent a new event bus from being cached and shared, set the `opts.cache` property to
|
||||
* `false` when you create the event bus. Setting this property to `false` also forces a new
|
||||
* event bus to be created, even if there's a cached event bus with the same name.
|
||||
*
|
||||
* @alias module:@jsdoc/util.EventBus
|
||||
* @extends module:events.EventEmitter
|
||||
*/
|
||||
export default class EventBus extends EventEmitter {
|
||||
/**
|
||||
* Create a new event bus, or retrieve the cached event bus for the ID you specify.
|
||||
*
|
||||
* @param {(string|Symbol)} id - The ID for the event bus.
|
||||
* @param {Object} opts - Options for the event bus.
|
||||
* @param {boolean} [opts.cache=true] - Set to `false` to prevent the event bus from being
|
||||
* cached, and to return a new event bus even if there is already an event bus with the same ID.
|
||||
*/
|
||||
constructor(id, opts = {}) {
|
||||
super();
|
||||
|
||||
ow(id, ow.any(ow.string, ow.symbol));
|
||||
|
||||
const shouldCache = _.isBoolean(opts.cache) ? opts.cache : true;
|
||||
|
||||
if (Object.hasOwn(cache, id) && shouldCache) {
|
||||
return cache[id];
|
||||
}
|
||||
|
||||
this._id = id;
|
||||
|
||||
if (shouldCache) {
|
||||
cache[id] = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,33 @@
|
||||
|
||||
export const LOG_TYPES = ['debug', 'error', 'info', 'fatal', 'verbose', 'warn'];
|
||||
|
||||
/**
|
||||
* Logging functions for JSDoc.
|
||||
*
|
||||
* @typedef {Object} module:@jsdoc/util~logFunctions
|
||||
* @property {function(...*)} debug - The `debug` logging function.
|
||||
* @property {function(...*)} error - The `error` logging function.
|
||||
* @property {function(...*)} info - The `info` logging function.
|
||||
* @property {function(...*)} fatal - The `fatal` logging function.
|
||||
* @property {function(...*)} verbose - The `verbose` logging function.
|
||||
* @property {function(...*)} warn - The `warn` logging function.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates shared logging functions for JSDoc.
|
||||
*
|
||||
* Calling a logging function has the following effects:
|
||||
*
|
||||
* + The specified `emitter` emits an event with the name `logger:LOG_TYPE`, where `LOG_TYPE` is a
|
||||
* value like `debug` or `verbose`.
|
||||
* + If JSDoc's CLI is running, and if the user asked to see log messages of the specified type,
|
||||
* then the message is written to the console.
|
||||
*
|
||||
* @alias module:@jsdoc/util.getLogFunctions
|
||||
* @param {node:events} emitter - The event emitter to use. In general, you should use the emitter
|
||||
* stored in {@link module:@jsdoc/core.Env#emitter Env#emitter}.
|
||||
* @returns {module:@jsdoc/util~logFunctions} The logging functions.
|
||||
*/
|
||||
export default function getLogFunctions(emitter) {
|
||||
const logFunctions = {};
|
||||
|
||||
|
||||
@ -30,12 +30,8 @@
|
||||
"import": "./lib/*"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21",
|
||||
"ow": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23.0.0"
|
||||
},
|
||||
"gitHead": "a595f7f2a525f32da9a0498a027667af3d938158"
|
||||
}
|
||||
|
||||
@ -15,8 +15,8 @@
|
||||
*/
|
||||
|
||||
import util from '../../index.js';
|
||||
import bus from '../../lib/bus.js';
|
||||
import cast from '../../lib/cast.js';
|
||||
import log from '../../lib/log.js';
|
||||
|
||||
describe('@jsdoc/util', () => {
|
||||
it('is an object', () => {
|
||||
@ -29,9 +29,9 @@ describe('@jsdoc/util', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('EventBus', () => {
|
||||
it('is lib/bus', () => {
|
||||
expect(util.EventBus).toEqual(bus);
|
||||
describe('getLogFunctions', () => {
|
||||
it('is lib/log', () => {
|
||||
expect(util.getLogFunctions).toEqual(log);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,83 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 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 EventEmitter from 'node:events';
|
||||
|
||||
import EventBus from '../../../lib/bus.js';
|
||||
|
||||
describe('@jsdoc/util/lib/bus', () => {
|
||||
const ignoreCache = { cache: false };
|
||||
|
||||
it('inherits from EventEmitter', () => {
|
||||
expect(new EventBus('foo', ignoreCache) instanceof EventEmitter).toBeTrue();
|
||||
});
|
||||
|
||||
it('accepts a string for the ID', () => {
|
||||
function makeBus() {
|
||||
return new EventBus('foo', ignoreCache);
|
||||
}
|
||||
|
||||
expect(makeBus).not.toThrow();
|
||||
});
|
||||
|
||||
it('accepts a Symbol for the ID', () => {
|
||||
function makeBus() {
|
||||
return new EventBus(Symbol('foo'), ignoreCache);
|
||||
}
|
||||
|
||||
expect(makeBus).not.toThrow();
|
||||
});
|
||||
|
||||
it('throws on bad IDs', () => {
|
||||
function crashBus() {
|
||||
return new EventBus(true, ignoreCache);
|
||||
}
|
||||
|
||||
expect(crashBus).toThrowError();
|
||||
});
|
||||
|
||||
it('uses a cache by default', () => {
|
||||
let fired = false;
|
||||
const id = Symbol('cache-test');
|
||||
const bus1 = new EventBus(id);
|
||||
const bus2 = new EventBus(id);
|
||||
|
||||
bus1.once('foo', () => {
|
||||
fired = true;
|
||||
});
|
||||
|
||||
bus2.emit('foo');
|
||||
|
||||
expect(bus1).toBe(bus2);
|
||||
expect(fired).toBeTrue();
|
||||
});
|
||||
|
||||
it('ignores the cache when asked', () => {
|
||||
let fired = false;
|
||||
const id = Symbol('cache-test');
|
||||
const bus1 = new EventBus(id, ignoreCache);
|
||||
const bus2 = new EventBus(id, ignoreCache);
|
||||
|
||||
bus1.once('foo', () => {
|
||||
fired = true;
|
||||
});
|
||||
|
||||
bus2.emit('foo');
|
||||
|
||||
expect(bus1).not.toBe(bus2);
|
||||
expect(fired).toBeFalse();
|
||||
});
|
||||
});
|
||||
@ -2,7 +2,7 @@
|
||||
"name": "jsdoc",
|
||||
"private": true,
|
||||
"version": "5.0.0-dev.19",
|
||||
"revision": "1667509813080",
|
||||
"revision": "1748665787180",
|
||||
"description": "An API documentation generator for JavaScript.",
|
||||
"keywords": [
|
||||
"documentation",
|
||||
@ -15,18 +15,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@jsdoc/cli": "^0.3.12",
|
||||
"@jsdoc/core": "^0.5.10",
|
||||
"@jsdoc/doclet": "^0.2.13",
|
||||
"@jsdoc/parse": "^0.3.13",
|
||||
"@jsdoc/tag": "^0.2.13",
|
||||
"@jsdoc/template-legacy": "^0.1.13",
|
||||
"@jsdoc/util": "^0.3.4",
|
||||
"lodash": "^4.17.21",
|
||||
"strip-bom": "^5.0.0",
|
||||
"strip-json-comments": "^5.0.1"
|
||||
"strip-bom": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18.12.0"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23.0.0"
|
||||
},
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
||||
13
packages/jsdoc/test/fixtures/moduleexport.js
vendored
Normal file
13
packages/jsdoc/test/fixtures/moduleexport.js
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
/** @module icecream */
|
||||
|
||||
/**
|
||||
* Ice cream flavors.
|
||||
*
|
||||
* @enum
|
||||
*/
|
||||
export const FLAVORS = {
|
||||
/** Vanilla. */
|
||||
VANILLA: 0,
|
||||
/** Chocolate. */
|
||||
CHOCOLATE: 1,
|
||||
};
|
||||
7
packages/jsdoc/test/fixtures/modulememberof.js
vendored
Normal file
7
packages/jsdoc/test/fixtures/modulememberof.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* The bar namespace.
|
||||
*
|
||||
* @namespace
|
||||
* @memberof module:foo
|
||||
*/
|
||||
export const bar = {};
|
||||
3
packages/jsdoc/test/fixtures/specialnames.js
vendored
3
packages/jsdoc/test/fixtures/specialnames.js
vendored
@ -12,3 +12,6 @@ var prototype = {
|
||||
|
||||
/** document me */
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
/** document me */
|
||||
function prototypeMethod() {}
|
||||
|
||||
27
packages/jsdoc/test/specs/documentation/moduleexport.js
Normal file
27
packages/jsdoc/test/specs/documentation/moduleexport.js
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright 2025 the JSDoc Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
describe('symbols exported by an ES2015 module', () => {
|
||||
const docSet = jsdoc.getDocSetFromFile('test/fixtures/moduleexport.js');
|
||||
|
||||
it('uses the correct scopes when exported objects have properties', () => {
|
||||
const chocolate = docSet.getByLongname('module:icecream.FLAVORS.CHOCOLATE')[0];
|
||||
const vanilla = docSet.getByLongname('module:icecream.FLAVORS.VANILLA')[0];
|
||||
|
||||
expect(chocolate).toBeObject();
|
||||
expect(vanilla).toBeObject();
|
||||
});
|
||||
});
|
||||
26
packages/jsdoc/test/specs/documentation/modulememberof.js
Normal file
26
packages/jsdoc/test/specs/documentation/modulememberof.js
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
Copyright 2025 the JSDoc Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
describe('memberof a module', () => {
|
||||
const docSet = jsdoc.getDocSetFromFile('test/fixtures/modulememberof.js');
|
||||
|
||||
it('uses the correct name and longname for an exported symbol with a @memberof tag', () => {
|
||||
const bar = docSet.getByLongname('module:foo.bar').filter((d) => !d.undocumented)[0];
|
||||
|
||||
expect(bar).toBeObject();
|
||||
expect(bar.name).toBe('bar');
|
||||
});
|
||||
});
|
||||
@ -20,6 +20,7 @@ describe('documenting symbols with special names', () => {
|
||||
const hasOwnProp = docSet.getByLongname('hasOwnProperty')[0];
|
||||
const proto = docSet.getByLongname('prototype')[0];
|
||||
const protoValueOf = docSet.getByLongname('prototype.valueOf')[0];
|
||||
const protoMethod = docSet.getByLongname('prototypeMethod')[0];
|
||||
|
||||
it('When a symbol is named "constructor", the symbol should appear in the docs.', () => {
|
||||
expect(construct).toBeObject();
|
||||
@ -40,4 +41,8 @@ describe('documenting symbols with special names', () => {
|
||||
it('When a symbol is named "prototype", its members are resolved correctly.', () => {
|
||||
expect(protoValueOf).toBeObject();
|
||||
});
|
||||
|
||||
it('preserves names that start with `prototype`', () => {
|
||||
expect(protoMethod).toBeObject();
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user