chore: use Prettier to format source files

This commit is contained in:
Jeff Williams 2021-09-19 13:20:31 -07:00
parent 3025520e15
commit 1305499207
277 changed files with 38661 additions and 21761 deletions

View File

@ -9,4 +9,4 @@ indent_size = 2
[{**/*.js,**/*.css,**/*.json}]
indent_style = space
indent_size = 4
indent_size = 2

View File

@ -1,3 +1,3 @@
module.exports = {
extends: '@jsdoc'
extends: ['@jsdoc', 'plugin:prettier/recommended'],
};

View File

@ -40,8 +40,8 @@ Your debug output here
### Your environment
| Software | Version
| ---------------- | -------
| Software | Version |
| ---------------- | ------- |
| JSDoc |
| Node.js |
| npm |

View File

@ -5,14 +5,14 @@ https://github.com/jsdoc3/jsdoc/blob/master/CONTRIBUTING.md
https://github.com/jsdoc3/jsdoc/blob/master/CODE_OF_CONDUCT.md
-->
| Q | A
| ---------------- | ---
| Bug fix? | yes/no
| New feature? | yes/no
| Breaking change? | yes/no
| Deprecations? | yes/no
| Tests added? | yes/no
| Fixed issues | comma-separated list of issues fixed by the pull request, if any
| License | Apache-2.0
| Q | A |
| ---------------- | ---------------------------------------------------------------- |
| Bug fix? | yes/no |
| New feature? | yes/no |
| Breaking change? | yes/no |
| Deprecations? | yes/no |
| Tests added? | yes/no |
| Fixed issues | comma-separated list of issues fixed by the pull request, if any |
| License | Apache-2.0 |
<!-- Describe your changes below in as much detail as possible. -->

5
.prettierignore Normal file
View File

@ -0,0 +1,5 @@
# Ignore test fixtures.
**/test/fixtures/**
# Ignore code coverage reports.
.nyc_output/

3
.prettierrc.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
...require('./packages/jsdoc-prettier-config'),
};

View File

@ -1,12 +1,8 @@
{
"extends": [
"config:base"
],
"statusCheckVerify": true,
"ignoreDeps": [
"taffydb"
],
"automerge": true,
"automergeType": "branch",
"rangeStrategy": "bump"
"extends": ["config:base"],
"statusCheckVerify": true,
"ignoreDeps": ["taffydb"],
"automerge": true,
"automergeType": "branch",
"rangeStrategy": "bump"
}

1170
CHANGES.md

File diff suppressed because it is too large Load Diff

View File

@ -11,20 +11,20 @@ of experience, nationality, personal appearance, race, religion, or sexual ident
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit
permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit
permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities

View File

@ -1,69 +1,68 @@
Pull Requests
-------------
## Pull Requests
If you're thinking about making some changes, maybe fixing a bug, or adding a
snazzy new feature, first, thank you. Contributions are very welcome. Things
need to be manageable for the maintainers, however. So below you'll find **The
fastest way to get your pull request merged in.** Some things, particularly how
snazzy new feature, first, thank you. Contributions are very welcome. Things
need to be manageable for the maintainers, however. So below you'll find **The
fastest way to get your pull request merged in.** Some things, particularly how
you set up your branches and work with git, are just suggestions, but pretty good
ones.
1. **Create a remote to track the base jsdoc/jsdoc repository**
This is just a convenience to make it easier to update your ```<tracking branch>```
(more on that shortly). You would execute something like:
1. **Create a remote to track the base jsdoc/jsdoc repository**
This is just a convenience to make it easier to update your `<tracking branch>`
(more on that shortly). You would execute something like:
git remote add base git://github.com/jsdoc/jsdoc.git
git remote add base git://github.com/jsdoc/jsdoc.git
Here 'base' is the name of the remote. Feel free to use whatever you want.
Here 'base' is the name of the remote. Feel free to use whatever you want.
2. **Set up a tracking branch for the base repository**
We're gonna call this your ```<tracking branch>```. You will only ever update
this branch by pulling from the 'base' remote. (as opposed to 'origin')
2. **Set up a tracking branch for the base repository**
We're gonna call this your `<tracking branch>`. You will only ever update
this branch by pulling from the 'base' remote. (as opposed to 'origin')
git branch --track pullpost base/master
git checkout pullpost
git branch --track pullpost base/master
git checkout pullpost
Here 'pullpost' is the name of the branch. Fell free to use whatever you want.
Here 'pullpost' is the name of the branch. Fell free to use whatever you want.
3. **Create your change branch**
Once you are in ```<tracking branch>```, make sure it's up to date, then create
a branch for your changes off of that one.
3. **Create your change branch**
Once you are in `<tracking branch>`, make sure it's up to date, then create
a branch for your changes off of that one.
git branch fix-for-issue-395
git checkout fix-for-issue-395
git branch fix-for-issue-395
git checkout fix-for-issue-395
Here 'fix-for-issue-395' is the name of the branch. Feel free to use whatever
you want. We'll call this the ```<change branch>```. This is the branch that
you will eventually issue your pull request from.
Here 'fix-for-issue-395' is the name of the branch. Feel free to use whatever
you want. We'll call this the `<change branch>`. This is the branch that
you will eventually issue your pull request from.
The purpose of these first three steps is to make sure that your merge request
has a nice clean diff that only involves the changes related to your fix/feature.
The purpose of these first three steps is to make sure that your merge request
has a nice clean diff that only involves the changes related to your fix/feature.
4. **Make your changes**
On your ```<change branch>``` make any changes relevant to your fix/feature. Don't
group fixes for multiple unrelated issues or multiple unrelated features together.
Create a separate branch for each unrelated changeset. For instance, if you're
fixing a bug in the parser and adding some new UI to the default template, those
should be separate branches and merge requests.
4. **Make your changes**
On your `<change branch>` make any changes relevant to your fix/feature. Don't
group fixes for multiple unrelated issues or multiple unrelated features together.
Create a separate branch for each unrelated changeset. For instance, if you're
fixing a bug in the parser and adding some new UI to the default template, those
should be separate branches and merge requests.
5. **Add tests**
Add tests for your change. If you are submitting a bugfix, include a test that
verifies the existence of the bug along with your fix. If you are submitting
a new feature, include tests that verify proper feature function, if applicable.
See the readme in the 'test' directory for more information
5. **Add tests**
Add tests for your change. If you are submitting a bugfix, include a test that
verifies the existence of the bug along with your fix. If you are submitting
a new feature, include tests that verify proper feature function, if applicable.
See the readme in the 'test' directory for more information
6. **Commit and publish**
Commit your changes and publish your branch (or push it if it's already published)
6. **Commit and publish**
Commit your changes and publish your branch (or push it if it's already published)
7. **Issue your pull request**
On github.com, switch to your ```<change branch>``` and click the 'Pull Request'
button. Enter some meaningful information about the pull request. If it's a bugfix,
that doesn't already have an issue associated with it, provide some info on what
situations that bug occurs in and a sense of it's severity. If it does already have
an issue, make sure the include the hash and issue number (e.g. '#100') so github
links it.
7. **Issue your pull request**
On github.com, switch to your `<change branch>` and click the 'Pull Request'
button. Enter some meaningful information about the pull request. If it's a bugfix,
that doesn't already have an issue associated with it, provide some info on what
situations that bug occurs in and a sense of it's severity. If it does already have
an issue, make sure the include the hash and issue number (e.g. '#100') so github
links it.
If it's a feature, provide some context about the motivations behind the feature,
why it's important/useful/cool/necessary and what it does/how it works. Don't
worry about being too verbose. Folks will be much more amenable to reading through
your code if they know what its supposed to be about.
If it's a feature, provide some context about the motivations behind the feature,
why it's important/useful/cool/necessary and what it does/how it works. Don't
worry about being too verbose. Folks will be much more amenable to reading through
your code if they know what its supposed to be about.

View File

@ -6,8 +6,7 @@ An API documentation generator for JavaScript.
Want to contribute to JSDoc? Please read [`CONTRIBUTING.md`](CONTRIBUTING.md).
Installation and Usage
----------------------
## Installation and Usage
JSDoc supports stable versions of Node.js 8.15.0 and later. You can install
JSDoc globally or in your project's `node_modules` folder.
@ -51,41 +50,41 @@ and customize your documentation. Here are a few of them:
### Templates
+ [jaguarjs-jsdoc](https://github.com/davidshimjs/jaguarjs-jsdoc)
+ [DocStrap](https://github.com/docstrap/docstrap)
([example](https://docstrap.github.io/docstrap))
+ [jsdoc3Template](https://github.com/DBCDK/jsdoc3Template)
- [jaguarjs-jsdoc](https://github.com/davidshimjs/jaguarjs-jsdoc)
- [DocStrap](https://github.com/docstrap/docstrap)
([example](https://docstrap.github.io/docstrap))
- [jsdoc3Template](https://github.com/DBCDK/jsdoc3Template)
([example](https://github.com/danyg/jsdoc3Template/wiki#wiki-screenshots))
+ [minami](https://github.com/Nijikokun/minami)
+ [docdash](https://github.com/clenemt/docdash)
([example](http://clenemt.github.io/docdash/))
+ [tui-jsdoc-template](https://github.com/nhnent/tui.jsdoc-template)
([example](https://nhnent.github.io/tui.jsdoc-template/latest/))
+ [better-docs](https://github.com/SoftwareBrothers/better-docs)
([example](https://softwarebrothers.github.io/admin-bro-dev/index.html))
- [minami](https://github.com/Nijikokun/minami)
- [docdash](https://github.com/clenemt/docdash)
([example](http://clenemt.github.io/docdash/))
- [tui-jsdoc-template](https://github.com/nhnent/tui.jsdoc-template)
([example](https://nhnent.github.io/tui.jsdoc-template/latest/))
- [better-docs](https://github.com/SoftwareBrothers/better-docs)
([example](https://softwarebrothers.github.io/admin-bro-dev/index.html))
### Build tools
+ [JSDoc Grunt plugin](https://github.com/krampstudio/grunt-jsdoc)
+ [JSDoc Gulp plugin](https://github.com/mlucool/gulp-jsdoc3)
+ [JSDoc GitHub Action](https://github.com/andstor/jsdoc-action)
- [JSDoc Grunt plugin](https://github.com/krampstudio/grunt-jsdoc)
- [JSDoc Gulp plugin](https://github.com/mlucool/gulp-jsdoc3)
- [JSDoc GitHub Action](https://github.com/andstor/jsdoc-action)
### Other tools
+ [jsdoc-to-markdown](https://github.com/jsdoc2md/jsdoc-to-markdown)
+ [Integrating GitBook with
JSDoc](https://medium.com/@kevinast/integrate-gitbook-jsdoc-974be8df6fb3)
- [jsdoc-to-markdown](https://github.com/jsdoc2md/jsdoc-to-markdown)
- [Integrating GitBook with
JSDoc](https://medium.com/@kevinast/integrate-gitbook-jsdoc-974be8df6fb3)
## For more information
+ 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
[Stack Overflow](http://stackoverflow.com/questions/tagged/jsdoc).
- 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
[Stack Overflow](http://stackoverflow.com/questions/tagged/jsdoc).
## License

View File

@ -2,50 +2,53 @@ const eslint = require('gulp-eslint');
const { exec } = require('child_process');
const gulp = require('gulp');
const path = require('path');
const prettier = require('gulp-prettier');
function execCb(cb, err, stdout, stderr) {
console.log(stdout);
console.error(stderr);
cb(err);
console.log(stdout);
console.error(stderr);
cb(err);
}
const options = {
coveragePaths: [
'*.js',
'packages/**/*.js',
'!packages/**/test/*.js',
'!packages/**/test/**/*.js',
'!packages/**/static/*.js'
],
lintPaths: [
'*.js',
'packages/**/*.js',
'!packages/**/static/*.js'
],
nodeBin: path.resolve(__dirname, './packages/jsdoc/jsdoc.js'),
nodePath: process.execPath
coveragePaths: [
'*.js',
'packages/**/*.js',
'!packages/**/test/*.js',
'!packages/**/test/**/*.js',
'!packages/**/static/*.js',
],
lintPaths: ['*.js', 'packages/**/*.js', '!packages/**/static/*.js'],
nodeBin: path.resolve(__dirname, './packages/jsdoc/jsdoc.js'),
nodePath: process.execPath,
};
function coverage(cb) {
const cmd = `./node_modules/.bin/nyc --reporter=html ${options.nodeBin} -T`;
const cmd = `./node_modules/.bin/nyc --reporter=html ${options.nodeBin} -T`;
return exec(cmd, execCb.bind(null, cb));
return exec(cmd, execCb.bind(null, cb));
}
function format() {
return gulp.src(options.lintPaths).pipe(prettier());
}
function lint() {
return gulp.src(options.lintPaths)
.pipe(eslint())
.pipe(eslint.formatEach())
.pipe(eslint.failAfterError());
return gulp
.src(options.lintPaths)
.pipe(eslint())
.pipe(eslint.formatEach())
.pipe(eslint.failAfterError());
}
function test(cb) {
const cmd = `${options.nodePath} "${options.nodeBin}" -T`;
const cmd = `${options.nodePath} "${options.nodeBin}" -T`;
return exec(cmd, execCb.bind(null, cb));
return exec(cmd, execCb.bind(null, cb));
}
exports.coverage = coverage;
exports.default = gulp.series(lint, test);
exports.format = format;
exports.lint = lint;
exports.test = test;

View File

@ -1,6 +1,4 @@
{
"packages": [
"packages/*"
],
"packages": ["packages/*"],
"version": "independent"
}

16671
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,14 +7,18 @@
"@jsdoc/test-matchers": "^0.1.6",
"ajv": "^8.6.3",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"gulp": "^4.0.2",
"gulp-eslint": "^6.0.0",
"gulp-prettier": "^4.0.0",
"jasmine": "^3.9.0",
"jasmine-console-reporter": "^3.1.0",
"klaw-sync": "^6.0.0",
"lerna": "^4.0.0",
"mock-fs": "^5.1.0",
"nyc": "^15.1.0"
"nyc": "^15.1.0",
"prettier": "^2.4.1"
},
"engines": {
"node": ">=v14.17.6"

View File

@ -3,21 +3,19 @@ const { EventBus } = require('@jsdoc/util');
const flags = require('./flags');
const help = require('./help');
const { LEVELS, Logger } = require('./logger');
const {default: ow} = require('ow');
const { default: ow } = require('ow');
const yargs = require('yargs-parser');
function validateChoice(flagInfo, choices, values) {
let flagNames = flagInfo.alias ? `-${flagInfo.alias}/` : '';
let flagNames = flagInfo.alias ? `-${flagInfo.alias}/` : '';
flagNames += `--${flagInfo.name}`;
flagNames += `--${flagInfo.name}`;
for (let value of values) {
if (!choices.includes(value)) {
throw new TypeError(
`The flag ${flagNames} accepts only these values: ${choices.join(', ')}`
);
}
for (let value of values) {
if (!choices.includes(value)) {
throw new TypeError(`The flag ${flagNames} accepts only these values: ${choices.join(', ')}`);
}
}
}
/**
@ -30,54 +28,54 @@ function validateChoice(flagInfo, choices, values) {
* @private
*/
const { KNOWN_FLAGS, YARGS_FLAGS } = (() => {
const names = new Set();
const opts = {
alias: {},
array: [],
boolean: [],
coerce: {},
narg: {},
normalize: []
};
const names = new Set();
const opts = {
alias: {},
array: [],
boolean: [],
coerce: {},
narg: {},
normalize: [],
};
// `_` contains unparsed arguments.
names.add('_');
// `_` contains unparsed arguments.
names.add('_');
Object.keys(flags).forEach(flag => {
const value = flags[flag];
Object.keys(flags).forEach((flag) => {
const value = flags[flag];
names.add(flag);
names.add(flag);
if (value.alias) {
names.add(value.alias);
opts.alias[flag] = [value.alias];
}
if (value.alias) {
names.add(value.alias);
opts.alias[flag] = [value.alias];
}
if (value.array) {
opts.array.push(flag);
}
if (value.array) {
opts.array.push(flag);
}
if (value.boolean) {
opts.boolean.push(flag);
}
if (value.boolean) {
opts.boolean.push(flag);
}
if (value.coerce) {
opts.coerce[flag] = value.coerce;
}
if (value.coerce) {
opts.coerce[flag] = value.coerce;
}
if (value.normalize) {
opts.normalize.push(flag);
}
if (value.normalize) {
opts.normalize.push(flag);
}
if (value.requiresArg) {
opts.narg[flag] = 1;
}
});
if (value.requiresArg) {
opts.narg[flag] = 1;
}
});
return {
KNOWN_FLAGS: names,
YARGS_FLAGS: opts
};
return {
KNOWN_FLAGS: names,
YARGS_FLAGS: opts,
};
})();
/**
@ -86,148 +84,146 @@ const { KNOWN_FLAGS, YARGS_FLAGS } = (() => {
* @alias module:@jsdoc/cli
*/
class Engine {
/**
* Create an instance of the CLI engine.
*
* @param {Object} opts - Options for the CLI engine.
* @param {number} [opts.logLevel] - The maximum logging level to print to the console. Must be
* an enumerated value of `module:@jsdoc/cli.LOG_LEVELS`. The default value is
* `module:@jsdoc/cli.LOG_LEVELS.WARN`.
* @param {string} [opts.version] - The version of JSDoc that is running.
* @param {Date} [opts.revision] - A timestamp for the version of JSDoc that is running.
*/
constructor(opts = {}) {
ow(opts, ow.object);
// The `Logger` class validates `opts.level`, so no need to validate it here.
ow(opts.revision, ow.optional.date);
ow(opts.version, ow.optional.string);
/**
* Create an instance of the CLI engine.
*
* @param {Object} opts - Options for the CLI engine.
* @param {number} [opts.logLevel] - The maximum logging level to print to the console. Must be
* an enumerated value of `module:@jsdoc/cli.LOG_LEVELS`. The default value is
* `module:@jsdoc/cli.LOG_LEVELS.WARN`.
* @param {string} [opts.version] - The version of JSDoc that is running.
* @param {Date} [opts.revision] - A timestamp for the version of JSDoc that is running.
*/
constructor(opts = {}) {
ow(opts, ow.object);
// The `Logger` class validates `opts.level`, so no need to validate it here.
ow(opts.revision, ow.optional.date);
ow(opts.version, ow.optional.string);
this._bus = new EventBus('jsdoc', {
cache: _.isBoolean(opts._cacheEventBus) ? opts._cacheEventBus : true
});
this._logger = new Logger({
emitter: this._bus,
level: opts.logLevel
});
this.flags = [];
this.revision = opts.revision;
this.version = opts.version;
this._bus = new EventBus('jsdoc', {
cache: _.isBoolean(opts._cacheEventBus) ? opts._cacheEventBus : true,
});
this._logger = new Logger({
emitter: this._bus,
level: opts.logLevel,
});
this.flags = [];
this.revision = opts.revision;
this.version = opts.version;
}
/**
* The log level to use. Messages are logged only if they are at or above this level.
* Must be an enumerated value of {@link module:@jsdoc/cli.LOG_LEVELS}.
*
* The default value is `module:@jsdoc/cli.LOG_LEVELS.WARN`.
*/
get logLevel() {
return this._logger.level;
}
set logLevel(level) {
this._logger.level = level;
}
/**
* Get help text for JSDoc.
*
* You can specify the maximum line length for the help text. This method attempts to fit each
* line within the maximum length, but it only splits on word boundaries. If you specify a small
* length, such as `10`, some lines will exceed that length.
*
* @param {Object} [opts] - Options for formatting the help text.
* @param {number} [opts.maxLength=Infinity] - The desired maximum length of each line in the
* formatted text.
* @return {string} The formatted help text.
*/
help(opts = {}) {
ow(opts, ow.object);
ow(opts.maxLength, ow.optional.number);
const maxLength = opts.maxLength || Infinity;
return (
`Options:\n${help({ maxLength })}\n\n` + 'Visit https://jsdoc.app/ for more information.'
);
}
/**
* Details about the command-line flags that JSDoc recognizes.
*/
get knownFlags() {
return flags;
}
/**
* Parse an array of command-line flags (also known as "options").
*
* Use the instance's `flags` property to retrieve the parsed flags later.
*
* @param {Array<string>} cliFlags - The command-line flags to parse.
* @returns {Object} The name and value for each flag. The `_` property contains all arguments
* other than flags and their values.
*/
parseFlags(cliFlags) {
ow(cliFlags, ow.array);
let normalizedFlags;
let parsed;
let parsedFlags;
let parsedFlagNames;
normalizedFlags = Object.keys(flags);
parsed = yargs.detailed(cliFlags, YARGS_FLAGS);
if (parsed.error) {
throw parsed.error;
}
parsedFlags = parsed.argv;
parsedFlagNames = new Set(Object.keys(parsedFlags));
/**
* The log level to use. Messages are logged only if they are at or above this level.
* Must be an enumerated value of {@link module:@jsdoc/cli.LOG_LEVELS}.
*
* The default value is `module:@jsdoc/cli.LOG_LEVELS.WARN`.
*/
get logLevel() {
return this._logger.level;
}
set logLevel(level) {
this._logger.level = level;
}
/**
* Get help text for JSDoc.
*
* You can specify the maximum line length for the help text. This method attempts to fit each
* line within the maximum length, but it only splits on word boundaries. If you specify a small
* length, such as `10`, some lines will exceed that length.
*
* @param {Object} [opts] - Options for formatting the help text.
* @param {number} [opts.maxLength=Infinity] - The desired maximum length of each line in the
* formatted text.
* @return {string} The formatted help text.
*/
help(opts = {}) {
ow(opts, ow.object);
ow(opts.maxLength, ow.optional.number);
const maxLength = opts.maxLength || Infinity;
return (
`Options:\n${help({ maxLength })}\n\n` +
'Visit https://jsdoc.app/ for more information.'
// Check all parsed flags for unknown flag names.
for (let flag of parsedFlagNames) {
if (!KNOWN_FLAGS.has(flag)) {
throw new TypeError(
'Unknown command-line option: ' + (flag.length === 1 ? `-${flag}` : `--${flag}`)
);
}
}
/**
* Details about the command-line flags that JSDoc recognizes.
*/
get knownFlags() {
return flags;
// Validate the values of known flags.
for (let flag of normalizedFlags) {
if (parsedFlags[flag] && flags[flag].choices) {
let flagInfo = {
name: flag,
alias: flags[flag].alias,
};
validateChoice(flagInfo, flags[flag].choices, parsedFlags[flag]);
}
}
/**
* Parse an array of command-line flags (also known as "options").
*
* Use the instance's `flags` property to retrieve the parsed flags later.
*
* @param {Array<string>} cliFlags - The command-line flags to parse.
* @returns {Object} The name and value for each flag. The `_` property contains all arguments
* other than flags and their values.
*/
parseFlags(cliFlags) {
ow(cliFlags, ow.array);
// Only keep the long name of each flag.
this.flags = _.pick(parsedFlags, normalizedFlags.concat(['_']));
let normalizedFlags;
let parsed;
let parsedFlags;
let parsedFlagNames;
return this.flags;
}
normalizedFlags = Object.keys(flags);
parsed = yargs.detailed(cliFlags, YARGS_FLAGS);
if (parsed.error) {
throw parsed.error;
}
parsedFlags = parsed.argv;
parsedFlagNames = new Set(Object.keys(parsedFlags));
/**
* A string that describes the current JSDoc version.
*/
get versionDetails() {
let revision = '';
// Check all parsed flags for unknown flag names.
for (let flag of parsedFlagNames) {
if (!KNOWN_FLAGS.has(flag)) {
throw new TypeError(
'Unknown command-line option: ' +
(flag.length === 1 ? `-${flag}` : `--${flag}`)
);
}
}
// Validate the values of known flags.
for (let flag of normalizedFlags) {
if (parsedFlags[flag] && flags[flag].choices) {
let flagInfo = {
name: flag,
alias: flags[flag].alias
};
validateChoice(flagInfo, flags[flag].choices, parsedFlags[flag]);
}
}
// Only keep the long name of each flag.
this.flags = _.pick(parsedFlags, normalizedFlags.concat(['_']));
return this.flags;
if (!this.version) {
return '';
}
/**
* A string that describes the current JSDoc version.
*/
get versionDetails() {
let revision = '';
if (!this.version) {
return '';
}
if (this.revision) {
revision = `(${this.revision.toUTCString()})`;
}
return `JSDoc ${this.version} ${revision}`.trim();
if (this.revision) {
revision = `(${this.revision.toUTCString()})`;
}
return `JSDoc ${this.version} ${revision}`.trim();
}
}
Engine.LOG_LEVELS = LEVELS;

View File

@ -8,100 +8,100 @@ const querystring = require('querystring');
* @alias module:@jsdoc/cli/lib/flags
*/
module.exports = {
access: {
alias: 'a',
array: true,
choices: ['all', 'package', 'private', 'protected', 'public', 'undefined'],
defaultDescription: 'All except `private`',
description: 'Document only symbols with the specified access level.',
requiresArg: true
},
configure: {
alias: 'c',
description: 'The configuration file to use.',
normalize: true,
requiresArg: true
},
debug: {
boolean: true,
description: 'Log information to help with debugging.'
},
destination: {
alias: 'd',
default: './out',
description: 'The output directory.',
normalize: true,
requiresArg: true
},
encoding: {
alias: 'e',
default: 'utf8',
description: 'The encoding to assume when reading source files.',
requiresArg: true
},
explain: {
alias: 'X',
boolean: true,
description: 'Print the parse results to the console and exit.'
},
help: {
alias: 'h',
boolean: true,
description: 'Print help information and exit.'
},
match: {
description: 'Run only tests whose names contain this value.',
requiresArg: true
},
package: {
alias: 'P',
description: 'The path to the `package.json` file to use.',
normalize: true,
requiresArg: true
},
pedantic: {
boolean: true,
description: 'Treat errors as fatal errors, and treat warnings as errors.'
},
private: {
alias: 'p',
boolean: true,
description: 'Document private symbols (equivalent to `--access all`).'
},
query: {
alias: 'q',
coerce: ((str) => cast(querystring.parse(str))),
description: 'A query string to parse and store (for example, `foo=bar&baz=true`).',
requiresArg: true
},
readme: {
alias: 'R',
description: 'The `README` file to include in the documentation.',
normalize: true,
requiresArg: true
},
recurse: {
alias: 'r',
boolean: true,
description: 'Recurse into subdirectories to find source files.'
},
template: {
alias: 't',
description: 'The template package to use.',
requiresArg: true
},
test: {
alias: 'T',
boolean: true,
description: 'Run all tests and exit.'
},
verbose: {
boolean: true,
description: 'Log detailed information to the console.'
},
version: {
alias: 'v',
boolean: true,
description: 'Display the version number and exit.'
}
access: {
alias: 'a',
array: true,
choices: ['all', 'package', 'private', 'protected', 'public', 'undefined'],
defaultDescription: 'All except `private`',
description: 'Document only symbols with the specified access level.',
requiresArg: true,
},
configure: {
alias: 'c',
description: 'The configuration file to use.',
normalize: true,
requiresArg: true,
},
debug: {
boolean: true,
description: 'Log information to help with debugging.',
},
destination: {
alias: 'd',
default: './out',
description: 'The output directory.',
normalize: true,
requiresArg: true,
},
encoding: {
alias: 'e',
default: 'utf8',
description: 'The encoding to assume when reading source files.',
requiresArg: true,
},
explain: {
alias: 'X',
boolean: true,
description: 'Print the parse results to the console and exit.',
},
help: {
alias: 'h',
boolean: true,
description: 'Print help information and exit.',
},
match: {
description: 'Run only tests whose names contain this value.',
requiresArg: true,
},
package: {
alias: 'P',
description: 'The path to the `package.json` file to use.',
normalize: true,
requiresArg: true,
},
pedantic: {
boolean: true,
description: 'Treat errors as fatal errors, and treat warnings as errors.',
},
private: {
alias: 'p',
boolean: true,
description: 'Document private symbols (equivalent to `--access all`).',
},
query: {
alias: 'q',
coerce: (str) => cast(querystring.parse(str)),
description: 'A query string to parse and store (for example, `foo=bar&baz=true`).',
requiresArg: true,
},
readme: {
alias: 'R',
description: 'The `README` file to include in the documentation.',
normalize: true,
requiresArg: true,
},
recurse: {
alias: 'r',
boolean: true,
description: 'Recurse into subdirectories to find source files.',
},
template: {
alias: 't',
description: 'The template package to use.',
requiresArg: true,
},
test: {
alias: 'T',
boolean: true,
description: 'Run all tests and exit.',
},
verbose: {
boolean: true,
description: 'Log detailed information to the console.',
},
version: {
alias: 'v',
boolean: true,
description: 'Display the version number and exit.',
},
};

View File

@ -1,34 +1,34 @@
const flags = require('./flags');
function padLeft(str, length) {
return str.padStart(str.length + length);
return str.padStart(str.length + length);
}
function padRight(str, length) {
return str.padEnd(str.length + length);
return str.padEnd(str.length + length);
}
function findMaxLength(arr) {
let max = 0;
let max = 0;
arr.forEach(({length}) => {
max = Math.max(max, length);
});
arr.forEach(({ length }) => {
max = Math.max(max, length);
});
return max;
return max;
}
function concatWithMaxLength(items, maxLength) {
let result = '';
let result = '';
// to prevent endless loops, always use the first item, regardless of length
result += items.shift();
// to prevent endless loops, always use the first item, regardless of length
result += items.shift();
while (items.length && (result.length + items[0].length < maxLength)) {
result += ` ${items.shift()}`;
}
while (items.length && result.length + items[0].length < maxLength) {
result += ` ${items.shift()}`;
}
return result;
return result;
}
/**
@ -48,41 +48,41 @@ function concatWithMaxLength(items, maxLength) {
* @param {Object} opts - Options for formatting the text.
* @param {number} opts.maxLength - The maximum length of each line.
*/
function formatHelpInfo({names, descriptions}, {maxLength}) {
const MARGIN_SIZE = 4;
const GUTTER_SIZE = MARGIN_SIZE;
const results = [];
function formatHelpInfo({ names, descriptions }, { maxLength }) {
const MARGIN_SIZE = 4;
const GUTTER_SIZE = MARGIN_SIZE;
const results = [];
const maxNameLength = findMaxLength(names);
const wrapDescriptionAt = maxLength - (MARGIN_SIZE * 2) - GUTTER_SIZE - maxNameLength;
const maxNameLength = findMaxLength(names);
const wrapDescriptionAt = maxLength - MARGIN_SIZE * 2 - GUTTER_SIZE - maxNameLength;
// Build the string for each flag.
names.forEach((name, i) => {
let result;
let partialDescription;
let words;
// Build the string for each flag.
names.forEach((name, i) => {
let result;
let partialDescription;
let words;
// Add some whitespace before the name.
result = padLeft(name, MARGIN_SIZE);
// Make the descriptions left-justified, with a gutter between the names and descriptions.
result = padRight(result, maxNameLength - name.length + GUTTER_SIZE);
// Add some whitespace before the name.
result = padLeft(name, MARGIN_SIZE);
// Make the descriptions left-justified, with a gutter between the names and descriptions.
result = padRight(result, maxNameLength - name.length + GUTTER_SIZE);
// Split the description on spaces.
words = descriptions[i].split(' ');
// Add as much of the description as we can fit on the first line.
result += concatWithMaxLength(words, wrapDescriptionAt);
// If there's anything left, keep going until we've consumed the entire description.
while (words.length) {
// Add whitespace for the name column and the gutter.
partialDescription = padLeft('', MARGIN_SIZE + maxNameLength + GUTTER_SIZE);
partialDescription += concatWithMaxLength(words, wrapDescriptionAt);
result += `\n${partialDescription}`;
}
// Split the description on spaces.
words = descriptions[i].split(' ');
// Add as much of the description as we can fit on the first line.
result += concatWithMaxLength(words, wrapDescriptionAt);
// If there's anything left, keep going until we've consumed the entire description.
while (words.length) {
// Add whitespace for the name column and the gutter.
partialDescription = padLeft('', MARGIN_SIZE + maxNameLength + GUTTER_SIZE);
partialDescription += concatWithMaxLength(words, wrapDescriptionAt);
result += `\n${partialDescription}`;
}
results.push(result);
});
results.push(result);
});
return results;
return results;
}
/**
@ -95,41 +95,41 @@ function formatHelpInfo({names, descriptions}, {maxLength}) {
* @private
*/
module.exports = ({ maxLength }) => {
const flagInfo = {
names: [],
descriptions: []
};
const flagInfo = {
names: [],
descriptions: [],
};
Object.keys(flags)
.sort()
.forEach(flagName => {
const flagDetail = flags[flagName];
let description = '';
let name = '';
Object.keys(flags)
.sort()
.forEach((flagName) => {
const flagDetail = flags[flagName];
let description = '';
let name = '';
if (flagDetail.alias) {
name += `-${flagDetail.alias}, `;
}
if (flagDetail.alias) {
name += `-${flagDetail.alias}, `;
}
name += `--${flagName}`;
name += `--${flagName}`;
if (flagDetail.requiresArg) {
name += ' <value>';
}
if (flagDetail.requiresArg) {
name += ' <value>';
}
description += flagDetail.description;
description += flagDetail.description;
if (flagDetail.array) {
description += ' Can be specified more than once.';
}
if (flagDetail.array) {
description += ' Can be specified more than once.';
}
if (flagDetail.choices) {
description += ` Accepts these values: ${flagDetail.choices.join(', ')}`;
}
if (flagDetail.choices) {
description += ` Accepts these values: ${flagDetail.choices.join(', ')}`;
}
flagInfo.names.push(name);
flagInfo.descriptions.push(description);
});
flagInfo.names.push(name);
flagInfo.descriptions.push(description);
});
return `${formatHelpInfo(flagInfo, {maxLength}).join('\n')}`;
return `${formatHelpInfo(flagInfo, { maxLength }).join('\n')}`;
};

View File

@ -1,5 +1,5 @@
const _ = require('lodash');
const {default: ow} = require('ow');
const { default: ow } = require('ow');
/**
* Logging levels for the JSDoc logger. The default logging level is
@ -10,150 +10,152 @@ const {default: ow} = require('ow');
* @type {number}
*/
const LEVELS = {
/**
* Do not log any messages.
*
* @alias module:@jsdoc/cli.LOG_LEVELS.SILENT
*/
SILENT: 0,
/**
* Log fatal errors that prevent JSDoc from running.
*
* @alias module:@jsdoc/cli.LOG_LEVELS.FATAL
*/
FATAL: 10,
/**
* Log all errors, including errors from which JSDoc can recover.
*
* @alias module:@jsdoc/cli.LOG_LEVELS.ERROR
*/
ERROR: 20,
/**
* Log the following messages:
*
* + Warnings
* + Errors
*
* @alias module:@jsdoc/cli.LOG_LEVELS.WARN
*/
WARN: 30,
/**
* Log the following messages:
*
* + Informational messages
* + Warnings
* + Errors
*
* @alias module:@jsdoc/cli.LOG_LEVELS.INFO
*/
INFO: 40,
/**
* Log the following messages:
*
* + Debugging messages
* + Informational messages
* + Warnings
* + Errors
*
* @alias module:@jsdoc/cli.LOG_LEVELS.DEBUG
*/
DEBUG: 50,
/**
* Log all messages.
*
* @alias module:@jsdoc/cli.LOG_LEVELS.VERBOSE
*/
VERBOSE: 1000
/**
* Do not log any messages.
*
* @alias module:@jsdoc/cli.LOG_LEVELS.SILENT
*/
SILENT: 0,
/**
* Log fatal errors that prevent JSDoc from running.
*
* @alias module:@jsdoc/cli.LOG_LEVELS.FATAL
*/
FATAL: 10,
/**
* Log all errors, including errors from which JSDoc can recover.
*
* @alias module:@jsdoc/cli.LOG_LEVELS.ERROR
*/
ERROR: 20,
/**
* Log the following messages:
*
* + Warnings
* + Errors
*
* @alias module:@jsdoc/cli.LOG_LEVELS.WARN
*/
WARN: 30,
/**
* Log the following messages:
*
* + Informational messages
* + Warnings
* + Errors
*
* @alias module:@jsdoc/cli.LOG_LEVELS.INFO
*/
INFO: 40,
/**
* Log the following messages:
*
* + Debugging messages
* + Informational messages
* + Warnings
* + Errors
*
* @alias module:@jsdoc/cli.LOG_LEVELS.DEBUG
*/
DEBUG: 50,
/**
* Log all messages.
*
* @alias module:@jsdoc/cli.LOG_LEVELS.VERBOSE
*/
VERBOSE: 1000,
};
const DEFAULT_LEVEL = LEVELS.WARN;
const FUNCS = {
[LEVELS.DEBUG]: 'debug',
[LEVELS.ERROR]: 'error',
[LEVELS.INFO]: 'info',
[LEVELS.FATAL]: 'error',
[LEVELS.VERBOSE]: 'debug',
[LEVELS.WARN]: 'warn'
[LEVELS.DEBUG]: 'debug',
[LEVELS.ERROR]: 'error',
[LEVELS.INFO]: 'info',
[LEVELS.FATAL]: 'error',
[LEVELS.VERBOSE]: 'debug',
[LEVELS.WARN]: 'warn',
};
const LEVELS_BY_NUMBER = _.invert(LEVELS);
const PREFIXES = {
[LEVELS.DEBUG]: 'DEBUG: ',
[LEVELS.ERROR]: 'ERROR: ',
[LEVELS.FATAL]: 'FATAL: ',
[LEVELS.WARN]: 'WARNING: '
[LEVELS.DEBUG]: 'DEBUG: ',
[LEVELS.ERROR]: 'ERROR: ',
[LEVELS.FATAL]: 'FATAL: ',
[LEVELS.WARN]: 'WARNING: ',
};
// Add a prefix to a log message if necessary.
function addPrefix(level, args) {
const prefix = PREFIXES[level];
const prefix = PREFIXES[level];
if (prefix && _.isString(args[0])) {
args[0] = prefix + args[0];
}
if (prefix && _.isString(args[0])) {
args[0] = prefix + args[0];
}
return args;
return args;
}
class Logger {
constructor(opts) {
ow(opts, ow.object);
// We validate `opts.level` in the setter, so no need to validate it here.
ow(opts.emitter, ow.object.partialShape({
off: ow.function,
on: ow.function,
once: ow.function
}));
constructor(opts) {
ow(opts, ow.object);
// We validate `opts.level` in the setter, so no need to validate it here.
ow(
opts.emitter,
ow.object.partialShape({
off: ow.function,
on: ow.function,
once: ow.function,
})
);
this._console = opts._console || console;
const emitter = this._emitter = opts.emitter;
this._console = opts._console || console;
const emitter = (this._emitter = opts.emitter);
this.level = opts.level || DEFAULT_LEVEL;
this.level = opts.level || DEFAULT_LEVEL;
for (const levelName of Object.keys(LEVELS)) {
let levelNameLower;
let levelNumber;
for (const levelName of Object.keys(LEVELS)) {
let levelNameLower;
let levelNumber;
// `logger:silent` events are not a thing.
if (levelName === 'SILENT') {
continue;
}
// `logger:silent` events are not a thing.
if (levelName === 'SILENT') {
continue;
}
levelNameLower = levelName.toLowerCase();
levelNumber = LEVELS[levelName];
levelNameLower = levelName.toLowerCase();
levelNumber = LEVELS[levelName];
emitter.on(
`logger:${levelNameLower}`,
(...args) => this._maybeLog(levelNumber, args)
);
}
emitter.on(`logger:${levelNameLower}`, (...args) => this._maybeLog(levelNumber, args));
}
}
_maybeLog(level, args) {
if (this._level >= level) {
args = addPrefix(level, args);
this._console[FUNCS[level]](...args);
}
}
get level() {
return this._level;
}
set level(level) {
let errorMsg;
if (_.isUndefined(LEVELS_BY_NUMBER[level])) {
errorMsg = `Unrecognized logging level ${level}. Known levels are: `;
errorMsg += Object.keys(LEVELS)
.map((k) => `${k}: ${LEVELS[k]}`)
.join(', ');
throw new TypeError(errorMsg);
}
_maybeLog(level, args) {
if (this._level >= level) {
args = addPrefix(level, args);
this._console[FUNCS[level]](...args);
}
}
get level() {
return this._level;
}
set level(level) {
let errorMsg;
if (_.isUndefined(LEVELS_BY_NUMBER[level])) {
errorMsg = `Unrecognized logging level ${level}. Known levels are: `;
errorMsg += Object.keys(LEVELS).map(k => `${k}: ${LEVELS[k]}`).join(', ');
throw new TypeError(errorMsg);
}
this._level = level;
}
this._level = level;
}
}
module.exports = {
LEVELS,
Logger
LEVELS,
Logger,
};

View File

@ -1,8 +1,129 @@
{
"name": "@jsdoc/cli",
"version": "0.2.5",
"lockfileVersion": 1,
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@jsdoc/cli",
"version": "0.2.5",
"license": "Apache-2.0",
"dependencies": {
"lodash": "^4.17.21",
"ow": "^0.27.0",
"strip-bom": "^4.0.0",
"yargs-parser": "^20.2.9"
},
"engines": {
"node": ">=v14.17.6"
}
},
"node_modules/@sindresorhus/is": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz",
"integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/dot-prop": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
"integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
"dependencies": {
"is-obj": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
"engines": {
"node": ">=8"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"node_modules/ow": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/ow/-/ow-0.27.0.tgz",
"integrity": "sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==",
"dependencies": {
"@sindresorhus/is": "^4.0.1",
"callsites": "^3.1.0",
"dot-prop": "^6.0.1",
"lodash.isequal": "^4.5.0",
"type-fest": "^1.2.1",
"vali-date": "^1.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-bom": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
"integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
"engines": {
"node": ">=8"
}
},
"node_modules/type-fest": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.2.2.tgz",
"integrity": "sha512-pfkPYCcuV0TJoo/jlsUeWNV8rk7uMU6ocnYNvca1Vu+pyKi8Rl8Zo2scPt9O72gCsXIm+dMxOOWuA3VFDSdzWA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/vali-date": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz",
"integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/yargs-parser": {
"version": "20.2.9",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"engines": {
"node": ">=10"
}
}
},
"dependencies": {
"@sindresorhus/is": {
"version": "4.0.1",

View File

@ -1,9 +1,9 @@
const Engine = require('../../index');
describe('@jsdoc/cli', () => {
it('is lib/engine', () => {
const engine = require('../../lib/engine');
it('is lib/engine', () => {
const engine = require('../../lib/engine');
expect(Engine).toBe(engine);
});
expect(Engine).toBe(engine);
});
});

View File

@ -6,221 +6,221 @@ const TYPE_ERROR = 'TypeError';
// Wrapper to prevent reuse of the event bus, which leads to `MaxListenersExceededWarning` messages.
class Engine extends RealEngine {
constructor(opts) {
opts = opts || {};
opts._cacheEventBus = false;
constructor(opts) {
opts = opts || {};
opts._cacheEventBus = false;
super(opts);
}
super(opts);
}
}
describe('@jsdoc/cli/lib/engine', () => {
it('exists', () => {
expect(Engine).toBeFunction();
});
it('exists', () => {
expect(Engine).toBeFunction();
});
it('works with no input', () => {
expect(() => new Engine()).not.toThrow();
});
it('has a static LOG_LEVELS property', () => {
expect(Engine.LOG_LEVELS).toBeObject();
});
it('has an empty array of flags by default', () => {
expect(new Engine().flags).toBeEmptyArray();
});
it('has a property that contains the known flags', () => {
expect(new Engine().knownFlags).toBe(flags);
});
it('has a logLevel property that defaults to LEVELS.WARN', () => {
expect(new Engine().logLevel).toBe(LEVELS.WARN);
});
it('has an undefined revision property by default', () => {
expect(new Engine().revision).toBeUndefined();
});
it('has an undefined version property by default', () => {
expect(new Engine().version).toBeUndefined();
});
it('has a versionDetails property that is an empty string by default', () => {
expect(new Engine().versionDetails).toBeEmptyString();
});
it('throws if the input is not an object', () => {
expect(() => new Engine('hi')).toThrow();
});
it('sets the logLevel if provided', () => {
const logLevel = LEVELS.VERBOSE;
const instance = new Engine({ logLevel });
expect(instance.logLevel).toBe(logLevel);
});
it('throws if the logLevel is invalid', () => {
const logLevel = LEVELS.VERBOSE + 1;
expect(() => new Engine({ logLevel })).toThrowErrorOfType(TYPE_ERROR);
});
it('sets the revision if provided', () => {
const revision = new Date();
const instance = new Engine({ revision });
expect(instance.revision).toBe(revision);
});
it('throws if the revision is not a date', () => {
expect(() => new Engine({ revision: '1' })).toThrow();
});
it('sets the version if provided', () => {
expect(new Engine({ version: '1.2.3' }).version).toBe('1.2.3');
});
it('throws if the version is not a string', () => {
expect(() => new Engine({ version: 1 })).toThrow();
});
describe('help', () => {
const instance = new Engine();
it('works with no input', () => {
expect(() => new Engine()).not.toThrow();
expect(() => instance.help()).not.toThrow();
});
it('has a static LOG_LEVELS property', () => {
expect(Engine.LOG_LEVELS).toBeObject();
it('throws on bad input', () => {
expect(() => instance.help('hi')).toThrow();
});
it('has an empty array of flags by default', () => {
expect(new Engine().flags).toBeEmptyArray();
it('returns a string', () => {
expect(instance.help()).toBeNonEmptyString();
});
it('has a property that contains the known flags', () => {
expect(new Engine().knownFlags).toBe(flags);
it('honors a reasonable maxLength option', () => {
const max = 70;
const help = instance.help({ maxLength: max }).split('\n');
for (let line of help) {
expect(line.length).toBeLessThanOrEqualTo(max);
}
});
it('has a logLevel property that defaults to LEVELS.WARN', () => {
expect(new Engine().logLevel).toBe(LEVELS.WARN);
it('throws on a bad maxLength option', () => {
expect(() => instance.help({ maxLength: 'long' })).toThrow();
});
});
describe('LOG_LEVELS', () => {
it('is lib/logger.LEVELS', () => {
expect(Engine.LOG_LEVELS).toBe(LEVELS);
});
});
describe('parseFlags', () => {
it('throws with no input', () => {
expect(() => new Engine().parseFlags()).toThrow();
});
it('has an undefined revision property by default', () => {
expect(new Engine().revision).toBeUndefined();
it('throws if the input is not an array', () => {
expect(() => new Engine().parseFlags({ foo: 'bar' })).toThrow();
});
it('has an undefined version property by default', () => {
expect(new Engine().version).toBeUndefined();
it('parses flags with no values', () => {
expect(new Engine().parseFlags(['--help']).help).toBeTrue();
});
it('has a versionDetails property that is an empty string by default', () => {
expect(new Engine().versionDetails).toBeEmptyString();
it('parses flags with values', () => {
const parsed = new Engine().parseFlags(['--configure', 'conf.json']);
expect(parsed.configure).toBe('conf.json');
});
it('throws if the input is not an object', () => {
expect(() => new Engine('hi')).toThrow();
it('stores the flags in the `flags` property', () => {
const instance = new Engine();
instance.parseFlags(['--help']);
expect(instance.flags.help).toBeTrue();
});
it('sets the logLevel if provided', () => {
const logLevel = LEVELS.VERBOSE;
const instance = new Engine({ logLevel });
expect(instance.logLevel).toBe(logLevel);
it('throws on unrecognized flags', () => {
expect(() => new Engine().parseFlags(['--notarealflag'])).toThrow();
});
it('throws if the logLevel is invalid', () => {
const logLevel = LEVELS.VERBOSE + 1;
expect(() => new Engine({ logLevel })).toThrowErrorOfType(TYPE_ERROR);
it('throws on invalid flag values', () => {
expect(() => new Engine().parseFlags(['--access', 'maybe'])).toThrow();
});
it('sets the revision if provided', () => {
const revision = new Date();
const instance = new Engine({ revision });
it('includes the long and short name in the error if a value is invalid', () => {
let error;
expect(instance.revision).toBe(revision);
try {
new Engine().parseFlags(['--access', 'just-this-once']);
} catch (e) {
error = e;
}
expect(error.message).toContain('-a/--access');
});
it('throws if the revision is not a date', () => {
expect(() => new Engine({ revision: '1' })).toThrow();
it('includes the allowed values in the error if a value is invalid', () => {
let error;
try {
new Engine().parseFlags(['--access', 'maybe-later']);
} catch (e) {
error = e;
}
expect(error.message).toContain(flags.access.choices.join(', '));
});
it('sets the version if provided', () => {
expect(new Engine({ version: '1.2.3' }).version).toBe('1.2.3');
it('throws if a required value is missing', () => {
expect(() => new Engine().parseFlags(['--template'])).toThrow();
});
it('throws if the version is not a string', () => {
expect(() => new Engine({ version: 1 })).toThrow();
it('always uses the long flag name in the parsed flags', () => {
expect(new Engine().parseFlags(['-h']).help).toBeTrue();
});
describe('help', () => {
const instance = new Engine();
it('coerces values to other types when appropriate', () => {
const parsed = new Engine().parseFlags(['--query', 'foo=bar&baz=true']);
it('works with no input', () => {
expect(() => instance.help()).not.toThrow();
});
expect(parsed.query).toEqual({
foo: 'bar',
baz: true,
});
});
});
it('throws on bad input', () => {
expect(() => instance.help('hi')).toThrow();
});
describe('versionDetails', () => {
it('works with a version but no revision', () => {
const instance = new Engine({ version: '1.2.3' });
it('returns a string', () => {
expect(instance.help()).toBeNonEmptyString();
});
it('honors a reasonable maxLength option', () => {
const max = 70;
const help = instance.help({ maxLength: max }).split('\n');
for (let line of help) {
expect(line.length).toBeLessThanOrEqualTo(max);
}
});
it('throws on a bad maxLength option', () => {
expect(() => instance.help({ maxLength: 'long' })).toThrow();
});
expect(instance.versionDetails).toBe('JSDoc 1.2.3');
});
describe('LOG_LEVELS', () => {
it('is lib/logger.LEVELS', () => {
expect(Engine.LOG_LEVELS).toBe(LEVELS);
});
it('contains an empty string with a revision but no version', () => {
const revision = new Date();
const instance = new Engine({ revision });
expect(instance.versionDetails).toBeEmptyString();
});
describe('parseFlags', () => {
it('throws with no input', () => {
expect(() => new Engine().parseFlags()).toThrow();
});
it('works with a version and a revision', () => {
const revision = new Date();
const instance = new Engine({
version: '1.2.3',
revision,
});
it('throws if the input is not an array', () => {
expect(() => new Engine().parseFlags({ foo: 'bar' })).toThrow();
});
it('parses flags with no values', () => {
expect(new Engine().parseFlags(['--help']).help).toBeTrue();
});
it('parses flags with values', () => {
const parsed = new Engine().parseFlags(['--configure', 'conf.json']);
expect(parsed.configure).toBe('conf.json');
});
it('stores the flags in the `flags` property', () => {
const instance = new Engine();
instance.parseFlags(['--help']);
expect(instance.flags.help).toBeTrue();
});
it('throws on unrecognized flags', () => {
expect(() => new Engine().parseFlags(['--notarealflag'])).toThrow();
});
it('throws on invalid flag values', () => {
expect(() => new Engine().parseFlags(['--access', 'maybe'])).toThrow();
});
it('includes the long and short name in the error if a value is invalid', () => {
let error;
try {
new Engine().parseFlags(['--access', 'just-this-once']);
} catch (e) {
error = e;
}
expect(error.message).toContain('-a/--access');
});
it('includes the allowed values in the error if a value is invalid', () => {
let error;
try {
new Engine().parseFlags(['--access', 'maybe-later']);
} catch (e) {
error = e;
}
expect(error.message).toContain(flags.access.choices.join(', '));
});
it('throws if a required value is missing', () => {
expect(() => new Engine().parseFlags(['--template'])).toThrow();
});
it('always uses the long flag name in the parsed flags', () => {
expect(new Engine().parseFlags(['-h']).help).toBeTrue();
});
it('coerces values to other types when appropriate', () => {
const parsed = new Engine().parseFlags(['--query', 'foo=bar&baz=true']);
expect(parsed.query).toEqual({
foo: 'bar',
baz: true
});
});
});
describe('versionDetails', () => {
it('works with a version but no revision', () => {
const instance = new Engine({ version: '1.2.3' });
expect(instance.versionDetails).toBe('JSDoc 1.2.3');
});
it('contains an empty string with a revision but no version', () => {
const revision = new Date();
const instance = new Engine({ revision });
expect(instance.versionDetails).toBeEmptyString();
});
it('works with a version and a revision', () => {
const revision = new Date();
const instance = new Engine({
version: '1.2.3',
revision
});
expect(instance.versionDetails).toBe(`JSDoc 1.2.3 (${revision.toUTCString()})`);
});
expect(instance.versionDetails).toBe(`JSDoc 1.2.3 (${revision.toUTCString()})`);
});
});
});

View File

@ -1,40 +1,40 @@
const flags = require('../../../lib/flags');
const {default: ow} = require('ow');
const { default: ow } = require('ow');
function validate(name, opts) {
name = `--${name}`;
name = `--${name}`;
if (!opts.description) {
throw new TypeError(`${name} is missing its description`);
}
if (!opts.description) {
throw new TypeError(`${name} is missing its description`);
}
if (opts.array && opts.boolean) {
throw new TypeError(`${name} can be an array or a boolean, but not both`);
}
if (opts.array && opts.boolean) {
throw new TypeError(`${name} can be an array or a boolean, but not both`);
}
if (opts.requiresArg && opts.boolean) {
throw new TypeError(`${name} can require an argument or be a boolean, but not both`);
}
if (opts.requiresArg && opts.boolean) {
throw new TypeError(`${name} can require an argument or be a boolean, but not both`);
}
try {
ow(opts.coerce, ow.optional.function);
} catch (e) {
throw new TypeError(`The coerce value for ${name} is not a function`);
}
try {
ow(opts.coerce, ow.optional.function);
} catch (e) {
throw new TypeError(`The coerce value for ${name} is not a function`);
}
if (opts.choices && !opts.requiresArg) {
throw new TypeError(`${name} specifies choices, but not requiresArg`);
}
if (opts.choices && !opts.requiresArg) {
throw new TypeError(`${name} specifies choices, but not requiresArg`);
}
}
describe('@jsdoc/cli/lib/flags', () => {
it('is an object', () => {
expect(flags).toBeObject();
});
it('is an object', () => {
expect(flags).toBeObject();
});
it('has reasonable settings for each flag', () => {
for (let flag of Object.keys(flags)) {
expect(() => validate(flag, flags[flag])).not.toThrow();
}
});
it('has reasonable settings for each flag', () => {
for (let flag of Object.keys(flags)) {
expect(() => validate(flag, flags[flag])).not.toThrow();
}
});
});

View File

@ -1,3 +1,3 @@
describe('@jsdoc/cli/lib/help', () => {
// Tested indirectly by the tests for `@jsdoc/cli/lib/engine`.
// Tested indirectly by the tests for `@jsdoc/cli/lib/engine`.
});

View File

@ -5,203 +5,204 @@ const ARGUMENT_ERROR = 'ArgumentError';
const TYPE_ERROR = 'TypeError';
describe('@jsdoc/cli/lib/logger', () => {
describe('Logger', () => {
let bus;
let logger;
describe('Logger', () => {
let bus;
let logger;
beforeEach(() => {
bus = new EventBus('loggerTest', {
_console: console,
cache: false
});
logger = new Logger({ emitter: bus });
beforeEach(() => {
bus = new EventBus('loggerTest', {
_console: console,
cache: false,
});
logger = new Logger({ emitter: bus });
['debug', 'error', 'info', 'warn'].forEach(func => spyOn(console, func));
});
it('exports a Logger constructor', () => {
expect(() => new Logger({ emitter: bus })).not.toThrow();
});
it('exports a LEVELS enum', () => {
expect(LEVELS).toBeNonEmptyObject();
});
describe('constructor', () => {
it('throws on invalid input', () => {
expect(() => new Logger()).toThrowErrorOfType(ARGUMENT_ERROR);
});
it('accepts a valid emitter', () => {
expect(() => new Logger({ emitter: bus })).not.toThrow();
});
it('throws on an invalid emitter', () => {
expect(() => new Logger({ emitter: {} })).toThrowErrorOfType(ARGUMENT_ERROR);
});
it('accepts a valid level', () => {
expect(() => new Logger({
emitter: bus,
level: LEVELS.VERBOSE
})).not.toThrow();
});
it('throws on an invalid level', () => {
expect(() => new Logger({
emitter: bus,
level: LEVELS.VERBOSE + 1
})).toThrowErrorOfType(TYPE_ERROR);
});
});
describe('events', () => {
it('passes all event arguments through', () => {
const args = [
'My name is %s %s %s',
'foo',
'bar',
'baz'
];
const eventType = 'logger:info';
logger.level = LEVELS.VERBOSE;
bus.emit(eventType, ...args);
expect(console.info).toHaveBeenCalledWith(...args);
});
it('logs logger:fatal events by default', () => {
bus.emit('logger:fatal');
expect(console.error).toHaveBeenCalled();
});
it('does not log logger:fatal events when level is SILENT', () => {
logger.level = LEVELS.SILENT;
bus.emit('logger:fatal');
expect(console.error).not.toHaveBeenCalled();
});
it('logs logger:error events by default', () => {
bus.emit('logger:error');
expect(console.error).toHaveBeenCalled();
});
it('does not log logger:error events when level is FATAL', () => {
logger.level = LEVELS.FATAL;
bus.emit('logger:error');
expect(console.error).not.toHaveBeenCalled();
});
it('logs logger:warn events by default', () => {
bus.emit('logger:warn');
expect(console.warn).toHaveBeenCalled();
});
it('does not log logger:warn events when level is ERROR', () => {
logger.level = LEVELS.ERROR;
bus.emit('logger:warn');
expect(console.warn).not.toHaveBeenCalled();
});
it('does not log logger:info events by default', () => {
bus.emit('logger:info');
expect(console.info).not.toHaveBeenCalled();
});
it('logs logger:info events when level is INFO', () => {
logger.level = LEVELS.INFO;
bus.emit('logger:info');
expect(console.info).toHaveBeenCalled();
});
it('does not log logger:debug events by default', () => {
bus.emit('logger:debug');
expect(console.debug).not.toHaveBeenCalled();
});
it('logs logger:debug events when level is DEBUG', () => {
logger.level = LEVELS.DEBUG;
bus.emit('logger:debug');
expect(console.debug).toHaveBeenCalled();
});
it('does not log logger:verbose events by default', () => {
bus.emit('logger:verbose');
expect(console.debug).not.toHaveBeenCalled();
});
it('logs logger:verbose events when level is VERBOSE', () => {
logger.level = LEVELS.VERBOSE;
bus.emit('logger:verbose');
expect(console.debug).toHaveBeenCalled();
});
});
describe('level', () => {
it('contains the current log level', () => {
expect(logger.level).toBe(LEVELS.WARN);
});
it('throws when set to an invalid value', () => {
expect(() => {
logger.level = LEVELS.VERBOSE + 1;
}).toThrowErrorOfType(TYPE_ERROR);
});
// The `events` tests set this property to valid values, so no need to test that
// behavior again here.
});
['debug', 'error', 'info', 'warn'].forEach((func) => spyOn(console, func));
});
describe('LEVELS', () => {
it('has a numeric SILENT property', () => {
expect(LEVELS.SILENT).toBeWholeNumber();
});
it('has a numeric FATAL property', () => {
expect(LEVELS.FATAL).toBeWholeNumber();
});
it('has a numeric ERROR property', () => {
expect(LEVELS.ERROR).toBeWholeNumber();
});
it('has a numeric WARN property', () => {
expect(LEVELS.WARN).toBeWholeNumber();
});
it('has a numeric INFO property', () => {
expect(LEVELS.INFO).toBeWholeNumber();
});
it('has a numeric DEBUG property', () => {
expect(LEVELS.DEBUG).toBeWholeNumber();
});
it('has a numeric VERBOSE property', () => {
expect(LEVELS.VERBOSE).toBeWholeNumber();
});
it('orders the log levels correctly', () => {
expect(LEVELS.SILENT).toBeLessThan(LEVELS.FATAL);
expect(LEVELS.FATAL).toBeLessThan(LEVELS.ERROR);
expect(LEVELS.ERROR).toBeLessThan(LEVELS.WARN);
expect(LEVELS.WARN).toBeLessThan(LEVELS.INFO);
expect(LEVELS.INFO).toBeLessThan(LEVELS.DEBUG);
expect(LEVELS.DEBUG).toBeLessThan(LEVELS.VERBOSE);
});
it('exports a Logger constructor', () => {
expect(() => new Logger({ emitter: bus })).not.toThrow();
});
it('exports a LEVELS enum', () => {
expect(LEVELS).toBeNonEmptyObject();
});
describe('constructor', () => {
it('throws on invalid input', () => {
expect(() => new Logger()).toThrowErrorOfType(ARGUMENT_ERROR);
});
it('accepts a valid emitter', () => {
expect(() => new Logger({ emitter: bus })).not.toThrow();
});
it('throws on an invalid emitter', () => {
expect(() => new Logger({ emitter: {} })).toThrowErrorOfType(ARGUMENT_ERROR);
});
it('accepts a valid level', () => {
expect(
() =>
new Logger({
emitter: bus,
level: LEVELS.VERBOSE,
})
).not.toThrow();
});
it('throws on an invalid level', () => {
expect(
() =>
new Logger({
emitter: bus,
level: LEVELS.VERBOSE + 1,
})
).toThrowErrorOfType(TYPE_ERROR);
});
});
describe('events', () => {
it('passes all event arguments through', () => {
const args = ['My name is %s %s %s', 'foo', 'bar', 'baz'];
const eventType = 'logger:info';
logger.level = LEVELS.VERBOSE;
bus.emit(eventType, ...args);
expect(console.info).toHaveBeenCalledWith(...args);
});
it('logs logger:fatal events by default', () => {
bus.emit('logger:fatal');
expect(console.error).toHaveBeenCalled();
});
it('does not log logger:fatal events when level is SILENT', () => {
logger.level = LEVELS.SILENT;
bus.emit('logger:fatal');
expect(console.error).not.toHaveBeenCalled();
});
it('logs logger:error events by default', () => {
bus.emit('logger:error');
expect(console.error).toHaveBeenCalled();
});
it('does not log logger:error events when level is FATAL', () => {
logger.level = LEVELS.FATAL;
bus.emit('logger:error');
expect(console.error).not.toHaveBeenCalled();
});
it('logs logger:warn events by default', () => {
bus.emit('logger:warn');
expect(console.warn).toHaveBeenCalled();
});
it('does not log logger:warn events when level is ERROR', () => {
logger.level = LEVELS.ERROR;
bus.emit('logger:warn');
expect(console.warn).not.toHaveBeenCalled();
});
it('does not log logger:info events by default', () => {
bus.emit('logger:info');
expect(console.info).not.toHaveBeenCalled();
});
it('logs logger:info events when level is INFO', () => {
logger.level = LEVELS.INFO;
bus.emit('logger:info');
expect(console.info).toHaveBeenCalled();
});
it('does not log logger:debug events by default', () => {
bus.emit('logger:debug');
expect(console.debug).not.toHaveBeenCalled();
});
it('logs logger:debug events when level is DEBUG', () => {
logger.level = LEVELS.DEBUG;
bus.emit('logger:debug');
expect(console.debug).toHaveBeenCalled();
});
it('does not log logger:verbose events by default', () => {
bus.emit('logger:verbose');
expect(console.debug).not.toHaveBeenCalled();
});
it('logs logger:verbose events when level is VERBOSE', () => {
logger.level = LEVELS.VERBOSE;
bus.emit('logger:verbose');
expect(console.debug).toHaveBeenCalled();
});
});
describe('level', () => {
it('contains the current log level', () => {
expect(logger.level).toBe(LEVELS.WARN);
});
it('throws when set to an invalid value', () => {
expect(() => {
logger.level = LEVELS.VERBOSE + 1;
}).toThrowErrorOfType(TYPE_ERROR);
});
// The `events` tests set this property to valid values, so no need to test that
// behavior again here.
});
});
describe('LEVELS', () => {
it('has a numeric SILENT property', () => {
expect(LEVELS.SILENT).toBeWholeNumber();
});
it('has a numeric FATAL property', () => {
expect(LEVELS.FATAL).toBeWholeNumber();
});
it('has a numeric ERROR property', () => {
expect(LEVELS.ERROR).toBeWholeNumber();
});
it('has a numeric WARN property', () => {
expect(LEVELS.WARN).toBeWholeNumber();
});
it('has a numeric INFO property', () => {
expect(LEVELS.INFO).toBeWholeNumber();
});
it('has a numeric DEBUG property', () => {
expect(LEVELS.DEBUG).toBeWholeNumber();
});
it('has a numeric VERBOSE property', () => {
expect(LEVELS.VERBOSE).toBeWholeNumber();
});
it('orders the log levels correctly', () => {
expect(LEVELS.SILENT).toBeLessThan(LEVELS.FATAL);
expect(LEVELS.FATAL).toBeLessThan(LEVELS.ERROR);
expect(LEVELS.ERROR).toBeLessThan(LEVELS.WARN);
expect(LEVELS.WARN).toBeLessThan(LEVELS.INFO);
expect(LEVELS.INFO).toBeLessThan(LEVELS.DEBUG);
expect(LEVELS.DEBUG).toBeLessThan(LEVELS.VERBOSE);
});
});
});

View File

@ -8,6 +8,6 @@ const config = require('./lib/config');
const name = require('./lib/name');
module.exports = {
config,
name
config,
name,
};

View File

@ -11,125 +11,119 @@ const stripJsonComments = require('strip-json-comments');
const MODULE_NAME = 'jsdoc';
const defaults = exports.defaults = {
// TODO(hegemonic): Integrate CLI options with other options.
opts: {
destination: './out',
encoding: 'utf8'
},
const defaults = (exports.defaults = {
// TODO(hegemonic): Integrate CLI options with other options.
opts: {
destination: './out',
encoding: 'utf8',
},
/**
* The JSDoc plugins to load.
*/
plugins: [],
// TODO(hegemonic): Move to `source` or remove.
recurseDepth: 10,
/**
* Settings for loading and parsing source files.
*/
source: {
/**
* The JSDoc plugins to load.
* A regular expression that matches source files to exclude from processing.
*
* To exclude files if any portion of their path begins with an underscore, use the value
* `(^|\\/|\\\\)_`.
*/
plugins: [],
// TODO(hegemonic): Move to `source` or remove.
recurseDepth: 10,
excludePattern: '',
/**
* Settings for loading and parsing source files.
* A regular expression that matches source files that JSDoc should process.
*
* By default, all source files with the extensions `.js`, `.jsdoc`, and `.jsx` are
* processed.
*/
source: {
/**
* A regular expression that matches source files to exclude from processing.
*
* To exclude files if any portion of their path begins with an underscore, use the value
* `(^|\\/|\\\\)_`.
*/
excludePattern: '',
/**
* A regular expression that matches source files that JSDoc should process.
*
* By default, all source files with the extensions `.js`, `.jsdoc`, and `.jsx` are
* processed.
*/
includePattern: '.+\\.js(doc|x)?$',
/**
* 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`.
*/
type: 'module'
},
includePattern: '.+\\.js(doc|x)?$',
/**
* Settings for interpreting JSDoc tags.
* 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`.
*/
tags: {
/**
* Set to `true` to allow tags that JSDoc does not recognize.
*/
allowUnknownTags: true,
// TODO(hegemonic): 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.
*/
dictionaries: [
'jsdoc',
'closure'
]
},
type: 'module',
},
/**
* Settings for interpreting JSDoc tags.
*/
tags: {
/**
* Settings for generating output with JSDoc templates. Some JSDoc templates might ignore these
* settings.
* Set to `true` to allow tags that JSDoc does not recognize.
*/
templates: {
/**
* Set to `true` to use a monospaced font for links to other code symbols, but not links to
* websites.
*/
cleverLinks: false,
/**
* Set to `true` to use a monospaced font for all links.
*/
monospaceLinks: false
}
};
allowUnknownTags: true,
// TODO(hegemonic): 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.
*/
dictionaries: ['jsdoc', 'closure'],
},
/**
* Settings for generating output with JSDoc templates. Some JSDoc templates might ignore these
* settings.
*/
templates: {
/**
* Set to `true` to use a monospaced font for links to other code symbols, but not links to
* websites.
*/
cleverLinks: false,
/**
* Set to `true` to use a monospaced font for all links.
*/
monospaceLinks: false,
},
});
// TODO: Consider exporting this class.
class Config {
constructor(filepath, config) {
this.config = config;
this.filepath = filepath;
}
constructor(filepath, config) {
this.config = config;
this.filepath = filepath;
}
}
function loadJson(filepath, content) {
return defaultLoaders['.json'](filepath, stripBom(stripJsonComments(content)));
return defaultLoaders['.json'](filepath, stripBom(stripJsonComments(content)));
}
function loadYaml(filepath, content) {
return defaultLoaders['.yaml'](filepath, stripBom(content));
return defaultLoaders['.yaml'](filepath, stripBom(content));
}
const explorerSync = cosmiconfigSync(MODULE_NAME, {
cache: false,
loaders: {
'.json': loadJson,
'.yaml': loadYaml,
'.yml': loadYaml,
noExt: loadYaml
},
searchPlaces: [
'package.json',
`.${MODULE_NAME}rc`,
`.${MODULE_NAME}rc.json`,
`.${MODULE_NAME}rc.yaml`,
`.${MODULE_NAME}rc.yml`,
`.${MODULE_NAME}rc.js`,
`${MODULE_NAME}.config.js`
]
cache: false,
loaders: {
'.json': loadJson,
'.yaml': loadYaml,
'.yml': loadYaml,
noExt: loadYaml,
},
searchPlaces: [
'package.json',
`.${MODULE_NAME}rc`,
`.${MODULE_NAME}rc.json`,
`.${MODULE_NAME}rc.yaml`,
`.${MODULE_NAME}rc.yml`,
`.${MODULE_NAME}rc.js`,
`${MODULE_NAME}.config.js`,
],
});
exports.loadSync = (filepath) => {
let loaded;
let loaded;
if (filepath) {
loaded = explorerSync.load(filepath);
} else {
loaded = explorerSync.search() || {};
}
if (filepath) {
loaded = explorerSync.load(filepath);
} else {
loaded = explorerSync.search() || {};
}
return new Config(
loaded.filepath,
_.defaultsDeep({}, loaded.config, defaults)
);
return new Config(loaded.filepath, _.defaultsDeep({}, loaded.config, defaults));
};

View File

@ -16,10 +16,10 @@ const hasOwnProp = Object.prototype.hasOwnProperty;
* @memberof module:jsdoc/name
*/
exports.LONGNAMES = {
/** Longname used for doclets that do not have a longname, such as anonymous functions. */
ANONYMOUS: '<anonymous>',
/** Longname that represents global scope. */
GLOBAL: '<global>'
/** Longname used for doclets that do not have a longname, such as anonymous functions. */
ANONYMOUS: '<anonymous>',
/** Longname that represents global scope. */
GLOBAL: '<global>',
};
// Module namespace prefix.
@ -32,26 +32,26 @@ exports.MODULE_NAMESPACE = 'module:';
* @static
* @memberof module:jsdoc/name
*/
const SCOPE = exports.SCOPE = {
NAMES: {
GLOBAL: 'global',
INNER: 'inner',
INSTANCE: 'instance',
STATIC: 'static'
},
PUNC: {
INNER: '~',
INSTANCE: '#',
STATIC: '.'
}
};
const SCOPE = (exports.SCOPE = {
NAMES: {
GLOBAL: 'global',
INNER: 'inner',
INSTANCE: 'instance',
STATIC: 'static',
},
PUNC: {
INNER: '~',
INSTANCE: '#',
STATIC: '.',
},
});
// Keys must be lowercase.
const SCOPE_TO_PUNC = exports.SCOPE_TO_PUNC = {
inner: SCOPE.PUNC.INNER,
instance: SCOPE.PUNC.INSTANCE,
static: SCOPE.PUNC.STATIC
};
const SCOPE_TO_PUNC = (exports.SCOPE_TO_PUNC = {
inner: SCOPE.PUNC.INNER,
instance: SCOPE.PUNC.INSTANCE,
static: SCOPE.PUNC.STATIC,
});
exports.PUNC_TO_SCOPE = _.invert(SCOPE_TO_PUNC);
@ -78,9 +78,9 @@ const REGEXP_NAME_DESCRIPTION = new RegExp(`^(\\[[^\\]]+\\]|\\S+)${DESCRIPTION}`
* parent; otherwise, `false`.
*/
exports.nameIsLongname = (name, memberof) => {
const regexp = new RegExp(`^${escape(memberof)}${SCOPE_PUNC_STRING}`);
const regexp = new RegExp(`^${escape(memberof)}${SCOPE_PUNC_STRING}`);
return regexp.test(name);
return regexp.test(name);
};
/**
@ -91,14 +91,14 @@ exports.nameIsLongname = (name, memberof) => {
* @param {string} name - The name in which to change `prototype` to `#`.
* @returns {string} The updated name.
*/
const prototypeToPunc = exports.prototypeToPunc = name => {
// Don't mangle symbols named `prototype`.
if (name === 'prototype') {
return name;
}
const prototypeToPunc = (exports.prototypeToPunc = (name) => {
// Don't mangle symbols named `prototype`.
if (name === 'prototype') {
return name;
}
return name.replace(/(?:^|\.)prototype\.?/g, SCOPE.PUNC.INSTANCE);
};
return name.replace(/(?:^|\.)prototype\.?/g, SCOPE.PUNC.INSTANCE);
});
/**
* Check whether a name begins with a character that identifies a scope.
@ -106,7 +106,7 @@ const prototypeToPunc = exports.prototypeToPunc = name => {
* @param {string} name - The name to check.
* @returns {boolean} `true` if the name begins with a scope character; otherwise, `false`.
*/
exports.hasLeadingScope = name => REGEXP_LEADING_SCOPE.test(name);
exports.hasLeadingScope = (name) => REGEXP_LEADING_SCOPE.test(name);
/**
* Check whether a name ends with a character that identifies a scope.
@ -114,7 +114,7 @@ exports.hasLeadingScope = name => REGEXP_LEADING_SCOPE.test(name);
* @param {string} name - The name to check.
* @returns {boolean} `true` if the name ends with a scope character; otherwise, `false`.
*/
exports.hasTrailingScope = name => REGEXP_TRAILING_SCOPE.test(name);
exports.hasTrailingScope = (name) => REGEXP_TRAILING_SCOPE.test(name);
/**
* Get a symbol's basename, which is the first part of its full name before any punctuation (other
@ -128,94 +128,92 @@ exports.hasTrailingScope = name => REGEXP_TRAILING_SCOPE.test(name);
* @param {?string} [name] - The symbol's full name.
* @returns {?string} The symbol's basename.
*/
exports.getBasename = name => {
if (!name) {
return null;
}
exports.getBasename = (name) => {
if (!name) {
return null;
}
return name.replace(/^([$a-z_][$a-z_0-9]*).*?$/i, '$1');
return name.replace(/^([$a-z_][$a-z_0-9]*).*?$/i, '$1');
};
// TODO: docs
exports.stripNamespace = longname => longname.replace(/^[a-zA-Z]+:/, '');
exports.stripNamespace = (longname) => longname.replace(/^[a-zA-Z]+:/, '');
// TODO: docs
function slice(longname, sliceChars, forcedMemberof) {
let i;
let memberof = '';
let name = '';
let parts;
let partsRegExp;
let scopePunc = '';
let token;
const tokens = [];
let variation;
let i;
let memberof = '';
let name = '';
let parts;
let partsRegExp;
let scopePunc = '';
let token;
const tokens = [];
let variation;
sliceChars = sliceChars || SCOPE_PUNC;
sliceChars = sliceChars || SCOPE_PUNC;
// Quoted strings in a longname are atomic, so we convert them to tokens:
// foo["bar"] => foo.@{1}@
// Foo.prototype["bar"] => Foo#@{1}
longname = longname.replace(/(prototype|#)?(\[?["'].+?["']\]?)/g, ($, p1, p2) => {
let punc = '';
// Quoted strings in a longname are atomic, so we convert them to tokens:
// foo["bar"] => foo.@{1}@
// Foo.prototype["bar"] => Foo#@{1}
longname = longname.replace(/(prototype|#)?(\[?["'].+?["']\]?)/g, ($, p1, p2) => {
let punc = '';
// Is there a leading bracket?
if ( /^\[/.test(p2) ) {
// Is it a static or instance member?
punc = p1 ? SCOPE.PUNC.INSTANCE : SCOPE.PUNC.STATIC;
p2 = p2.replace(/^\[/g, '')
.replace(/\]$/g, '');
}
token = `@{${tokens.length}}@`;
tokens.push(p2);
return punc + token;
});
longname = prototypeToPunc(longname);
if (typeof forcedMemberof !== 'undefined') {
partsRegExp = new RegExp(`^(.*?)([${sliceChars.join()}]?)$`);
name = longname.substr(forcedMemberof.length);
parts = forcedMemberof.match(partsRegExp);
if (parts[1]) {
memberof = parts[1] || forcedMemberof;
}
if (parts[2]) {
scopePunc = parts[2];
}
}
else if (longname) {
parts = longname.match(new RegExp(`^(:?(.+)([${sliceChars.join()}]))?(.+?)$`)) || [];
name = parts.pop() || '';
scopePunc = parts.pop() || '';
memberof = parts.pop() || '';
// Is there a leading bracket?
if (/^\[/.test(p2)) {
// Is it a static or instance member?
punc = p1 ? SCOPE.PUNC.INSTANCE : SCOPE.PUNC.STATIC;
p2 = p2.replace(/^\[/g, '').replace(/\]$/g, '');
}
// Like `@name foo.bar(2)`.
if (/(.+)\(([^)]+)\)$/.test(name)) {
name = RegExp.$1;
variation = RegExp.$2;
}
token = `@{${tokens.length}}@`;
tokens.push(p2);
// Restore quoted strings.
i = tokens.length;
while (i--) {
longname = longname.replace(`@{${i}}@`, tokens[i]);
memberof = memberof.replace(`@{${i}}@`, tokens[i]);
scopePunc = scopePunc.replace(`@{${i}}@`, tokens[i]);
name = name.replace(`@{${i}}@`, tokens[i]);
}
return punc + token;
});
return {
longname: longname,
memberof: memberof,
scope: scopePunc,
name: name,
variation: variation
};
longname = prototypeToPunc(longname);
if (typeof forcedMemberof !== 'undefined') {
partsRegExp = new RegExp(`^(.*?)([${sliceChars.join()}]?)$`);
name = longname.substr(forcedMemberof.length);
parts = forcedMemberof.match(partsRegExp);
if (parts[1]) {
memberof = parts[1] || forcedMemberof;
}
if (parts[2]) {
scopePunc = parts[2];
}
} else if (longname) {
parts = longname.match(new RegExp(`^(:?(.+)([${sliceChars.join()}]))?(.+?)$`)) || [];
name = parts.pop() || '';
scopePunc = parts.pop() || '';
memberof = parts.pop() || '';
}
// Like `@name foo.bar(2)`.
if (/(.+)\(([^)]+)\)$/.test(name)) {
name = RegExp.$1;
variation = RegExp.$2;
}
// Restore quoted strings.
i = tokens.length;
while (i--) {
longname = longname.replace(`@{${i}}@`, tokens[i]);
memberof = memberof.replace(`@{${i}}@`, tokens[i]);
scopePunc = scopePunc.replace(`@{${i}}@`, tokens[i]);
name = name.replace(`@{${i}}@`, tokens[i]);
}
return {
longname: longname,
memberof: memberof,
scope: scopePunc,
name: name,
variation: variation,
};
}
/**
@ -231,9 +229,7 @@ function slice(longname, sliceChars, forcedMemberof) {
* @param {string} forcedMemberof
* @returns {object} Representing the properties of the given name.
*/
exports.toParts = (longname, forcedMemberof) => slice(
longname, null, forcedMemberof
);
exports.toParts = (longname, forcedMemberof) => slice(longname, null, forcedMemberof);
// TODO: docs
/**
@ -242,16 +238,16 @@ exports.toParts = (longname, forcedMemberof) => slice(
* @returns {string} The longname with the namespace applied.
*/
exports.applyNamespace = (longname, ns) => {
const nameParts = slice(longname);
const name = nameParts.name;
const nameParts = slice(longname);
const name = nameParts.name;
longname = nameParts.longname;
longname = nameParts.longname;
if (!/^[a-zA-Z]+?:.+$/i.test(name)) {
longname = longname.replace(new RegExp(`${escape(name)}$`), `${ns}:${name}`);
}
if (!/^[a-zA-Z]+?:.+$/i.test(name)) {
longname = longname.replace(new RegExp(`${escape(name)}$`), `${ns}:${name}`);
}
return longname;
return longname;
};
/**
@ -262,70 +258,66 @@ exports.applyNamespace = (longname, ns) => {
* @return {boolean} `true` if the parent is an ancestor of the child; otherwise, `false`.
*/
exports.hasAncestor = (parent, child) => {
let hasAncestor = false;
let memberof = child;
if (!parent || !child) {
return hasAncestor;
}
// Fast path for obvious non-ancestors.
if (child.indexOf(parent) !== 0) {
return hasAncestor;
}
do {
memberof = slice(memberof).memberof;
if (memberof === parent) {
hasAncestor = true;
}
} while (!hasAncestor && memberof);
let hasAncestor = false;
let memberof = child;
if (!parent || !child) {
return hasAncestor;
}
// Fast path for obvious non-ancestors.
if (child.indexOf(parent) !== 0) {
return hasAncestor;
}
do {
memberof = slice(memberof).memberof;
if (memberof === parent) {
hasAncestor = true;
}
} while (!hasAncestor && memberof);
return hasAncestor;
};
// TODO: docs
const fromParts = exports.fromParts = ({memberof, scope, name, variation}) => [
(memberof || ''),
(scope || ''),
(name || ''),
(variation ? `(${variation})` : '')
].join('');
const fromParts = (exports.fromParts = ({ memberof, scope, name, variation }) =>
[memberof || '', scope || '', name || '', variation ? `(${variation})` : ''].join(''));
// TODO: docs
exports.stripVariation = name => {
const parts = slice(name);
exports.stripVariation = (name) => {
const parts = slice(name);
parts.variation = '';
parts.variation = '';
return fromParts(parts);
return fromParts(parts);
};
function splitLongname(longname, options) {
const chunks = [];
let currentNameInfo;
const nameInfo = {};
let previousName = longname;
const splitters = SCOPE_PUNC.concat('/');
const chunks = [];
let currentNameInfo;
const nameInfo = {};
let previousName = longname;
const splitters = SCOPE_PUNC.concat('/');
options = _.defaults(options || {}, {
includeVariation: true
});
options = _.defaults(options || {}, {
includeVariation: true,
});
do {
if (!options.includeVariation) {
previousName = exports.stripVariation(previousName);
}
currentNameInfo = nameInfo[previousName] = slice(previousName, splitters);
previousName = currentNameInfo.memberof;
chunks.push(currentNameInfo.scope + currentNameInfo.name);
} while (previousName);
do {
if (!options.includeVariation) {
previousName = exports.stripVariation(previousName);
}
currentNameInfo = nameInfo[previousName] = slice(previousName, splitters);
previousName = currentNameInfo.memberof;
chunks.push(currentNameInfo.scope + currentNameInfo.name);
} while (previousName);
return {
chunks: chunks.reverse(),
nameInfo: nameInfo
};
return {
chunks: chunks.reverse(),
nameInfo: nameInfo,
};
}
/**
@ -411,43 +403,43 @@ function splitLongname(longname, options) {
* @return {Object} A tree with information about each longname in the format shown above.
*/
exports.longnamesToTree = (longnames, doclets) => {
const splitOptions = { includeVariation: false };
const tree = {};
const splitOptions = { includeVariation: false };
const tree = {};
longnames.forEach(longname => {
let currentLongname = '';
let currentParent = tree;
let nameInfo;
let processed;
longnames.forEach((longname) => {
let currentLongname = '';
let currentParent = tree;
let nameInfo;
let processed;
// Don't try to add empty longnames to the tree.
if (!longname) {
return;
}
// Don't try to add empty longnames to the tree.
if (!longname) {
return;
}
processed = splitLongname(longname, splitOptions);
nameInfo = processed.nameInfo;
processed = splitLongname(longname, splitOptions);
nameInfo = processed.nameInfo;
processed.chunks.forEach(chunk => {
currentLongname += chunk;
processed.chunks.forEach((chunk) => {
currentLongname += chunk;
if (currentParent !== tree) {
currentParent.children = currentParent.children || {};
currentParent = currentParent.children;
}
if (currentParent !== tree) {
currentParent.children = currentParent.children || {};
currentParent = currentParent.children;
}
if (!hasOwnProp.call(currentParent, chunk)) {
currentParent[chunk] = nameInfo[currentLongname];
}
if (!hasOwnProp.call(currentParent, chunk)) {
currentParent[chunk] = nameInfo[currentLongname];
}
if (currentParent[chunk]) {
currentParent[chunk].doclet = doclets ? doclets[currentLongname] : null;
currentParent = currentParent[chunk];
}
});
if (currentParent[chunk]) {
currentParent[chunk].doclet = doclets ? doclets[currentLongname] : null;
currentParent = currentParent[chunk];
}
});
});
return tree;
return tree;
};
/**
@ -459,69 +451,68 @@ exports.longnamesToTree = (longnames, doclets) => {
* @returns {?Object} Hash with "name" and "description" properties.
*/
function splitNameMatchingBrackets(nameDesc) {
const buffer = [];
let c;
let stack = 0;
let stringEnd = null;
const buffer = [];
let c;
let stack = 0;
let stringEnd = null;
for (var i = 0; i < nameDesc.length; ++i) {
c = nameDesc[i];
buffer.push(c);
for (var i = 0; i < nameDesc.length; ++i) {
c = nameDesc[i];
buffer.push(c);
if (stringEnd) {
if (c === '\\' && i + 1 < nameDesc.length) {
buffer.push(nameDesc[++i]);
} else if (c === stringEnd) {
stringEnd = null;
}
} else if (c === '"' || c === "'") {
stringEnd = c;
} else if (c === '[') {
++stack;
} else if (c === ']') {
if (--stack === 0) {
break;
}
}
if (stringEnd) {
if (c === '\\' && i + 1 < nameDesc.length) {
buffer.push(nameDesc[++i]);
} else if (c === stringEnd) {
stringEnd = null;
}
} else if (c === '"' || c === "'") {
stringEnd = c;
} else if (c === '[') {
++stack;
} else if (c === ']') {
if (--stack === 0) {
break;
}
}
}
if (stack || stringEnd) {
return null;
}
if (stack || stringEnd) {
return null;
}
nameDesc.substr(i).match(REGEXP_DESCRIPTION);
nameDesc.substr(i).match(REGEXP_DESCRIPTION);
return {
name: buffer.join(''),
description: RegExp.$1
};
return {
name: buffer.join(''),
description: RegExp.$1,
};
}
/**
* 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.
*/
exports.splitNameAndDescription = str => {
// Like: `name`, `[name]`, `name text`, `[name] text`, `name - text`, or `[name] - text`.
// To ensure that we don't get confused by leading dashes in Markdown list items, the hyphen
// must be on the same line as the name.
exports.splitNameAndDescription = (str) => {
// Like: `name`, `[name]`, `name text`, `[name] text`, `name - text`, or `[name] - text`.
// To ensure that we don't get confused by leading dashes in Markdown list items, the hyphen
// must be on the same line as the name.
// Optional values get special treatment,
let result = null;
// Optional values get special treatment,
let result = null;
if (str[0] === '[') {
result = splitNameMatchingBrackets(str);
if (result !== null) {
return result;
}
if (str[0] === '[') {
result = splitNameMatchingBrackets(str);
if (result !== null) {
return result;
}
}
str.match(REGEXP_NAME_DESCRIPTION);
str.match(REGEXP_NAME_DESCRIPTION);
return {
name: RegExp.$1,
description: RegExp.$2
};
return {
name: RegExp.$1,
description: RegExp.$2,
};
};

View File

@ -1,8 +1,279 @@
{
"name": "@jsdoc/core",
"version": "0.4.0",
"lockfileVersion": 1,
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@jsdoc/core",
"version": "0.4.0",
"license": "Apache-2.0",
"dependencies": {
"cosmiconfig": "^7.0.1",
"escape-string-regexp": "^4.0.0",
"lodash": "^4.17.21",
"strip-bom": "^4.0.0",
"strip-json-comments": "^3.1.1"
},
"engines": {
"node": ">=v14.17.6"
}
},
"node_modules/@babel/code-frame": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz",
"integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==",
"dependencies": {
"@babel/highlight": "^7.14.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.14.9",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz",
"integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz",
"integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==",
"dependencies": {
"@babel/helper-validator-identifier": "^7.14.5",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
},
"node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/chalk/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"node_modules/cosmiconfig": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
"integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
"dependencies": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.2.1",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.10.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dependencies": {
"is-arrayish": "^0.2.1"
}
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"engines": {
"node": ">=4"
}
},
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
},
"node_modules/lines-and-columns": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA="
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dependencies": {
"callsites": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"dependencies": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
"json-parse-even-better-errors": "^2.3.0",
"lines-and-columns": "^1.1.6"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"engines": {
"node": ">=8"
}
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"engines": {
"node": ">=4"
}
},
"node_modules/strip-bom": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
"integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
"engines": {
"node": ">=8"
}
},
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"engines": {
"node": ">= 6"
}
}
},
"dependencies": {
"@babel/code-frame": {
"version": "7.14.5",

View File

@ -1,23 +1,23 @@
const core = require('../../index');
describe('@jsdoc/core', () => {
it('is an object', () => {
expect(core).toBeObject();
it('is an object', () => {
expect(core).toBeObject();
});
describe('config', () => {
it('is lib/config', () => {
const config = require('../../lib/config');
expect(core.config).toBe(config);
});
});
describe('config', () => {
it('is lib/config', () => {
const config = require('../../lib/config');
describe('name', () => {
it('is lib/name', () => {
const name = require('../../lib/name');
expect(core.config).toBe(config);
});
});
describe('name', () => {
it('is lib/name', () => {
const name = require('../../lib/name');
expect(core.name).toBe(name);
});
expect(core.name).toBe(name);
});
});
});

View File

@ -2,189 +2,189 @@ const mockFs = require('mock-fs');
const config = require('../../../lib/config');
describe('@jsdoc/core/lib/config', () => {
// Explicitly require `yaml` before we run any tests. `cosmiconfig` tries to load `yaml` lazily,
// but that doesn't work when the file system is mocked.
beforeAll(() => require('yaml'));
// Explicitly require `yaml` before we run any tests. `cosmiconfig` tries to load `yaml` lazily,
// but that doesn't work when the file system is mocked.
beforeAll(() => require('yaml'));
afterEach(() => mockFs.restore());
afterEach(() => mockFs.restore());
it('is an object', () => {
expect(config).toBeObject();
});
describe('loadSync', () => {
it('is a function', () => {
expect(config.loadSync).toBeFunction();
});
it('returns an object with `config` and `filepath` properties', () => {
mockFs({
'conf.json': '{}',
});
const conf = config.loadSync('conf.json');
expect(conf.config).toBeObject();
expect(conf.filepath).toEndWith('conf.json');
});
it('loads settings from the specified filepath if there is one', () => {
mockFs({
'conf.json': '{"foo":"bar"}',
});
const conf = config.loadSync('conf.json');
expect(conf.config.foo).toBe('bar');
});
it('finds the config file when no filepath is specified', () => {
mockFs({
'package.json': '{"jsdoc":{"foo":"bar"}}',
});
const conf = config.loadSync();
expect(conf.config.foo).toBe('bar');
});
it('parses JSON config files that have an extension and contain comments', () => {
mockFs({
'.jsdocrc.json': '// comment\n{"foo":"bar"}',
});
const conf = config.loadSync();
expect(conf.config.foo).toBe('bar');
});
it('parses JSON files that start with a BOM', () => {
mockFs({
'.jsdocrc.json': '\uFEFF{"foo":"bar"}',
});
const conf = config.loadSync();
expect(conf.config.foo).toBe('bar');
});
it('parses YAML files that start with a BOM', () => {
mockFs({
'.jsdocrc.yaml': '\uFEFF{"foo":"bar"}',
});
const conf = config.loadSync();
expect(conf.config.foo).toBe('bar');
});
it('provides the default config if the user config is an empty object', () => {
mockFs({
'.jsdocrc.json': '{}',
});
const conf = config.loadSync();
expect(conf.config).toEqual(config.defaults);
});
it('provides the default config if there is no user config', () => {
const conf = config.loadSync();
expect(conf.config).toEqual(config.defaults);
});
it('merges nested defaults with nested user settings as expected', () => {
mockFs({
'.jsdocrc.json': '{"tags":{"foo":"bar"}}',
});
const conf = config.loadSync();
expect(conf.config.tags.allowUnknownTags).toBe(config.defaults.tags.allowUnknownTags);
expect(conf.config.tags.foo).toBe('bar');
});
});
describe('defaults', () => {
const { defaults } = config;
it('is an object', () => {
expect(config).toBeObject();
expect(defaults).toBeObject();
});
describe('loadSync', () => {
it('is a function', () => {
expect(config.loadSync).toBeFunction();
});
it('returns an object with `config` and `filepath` properties', () => {
mockFs({
'conf.json': '{}'
});
const conf = config.loadSync('conf.json');
expect(conf.config).toBeObject();
expect(conf.filepath).toEndWith('conf.json');
});
it('loads settings from the specified filepath if there is one', () => {
mockFs({
'conf.json': '{"foo":"bar"}'
});
const conf = config.loadSync('conf.json');
expect(conf.config.foo).toBe('bar');
});
it('finds the config file when no filepath is specified', () => {
mockFs({
'package.json': '{"jsdoc":{"foo":"bar"}}'
});
const conf = config.loadSync();
expect(conf.config.foo).toBe('bar');
});
it('parses JSON config files that have an extension and contain comments', () => {
mockFs({
'.jsdocrc.json': '// comment\n{"foo":"bar"}'
});
const conf = config.loadSync();
expect(conf.config.foo).toBe('bar');
});
it('parses JSON files that start with a BOM', () => {
mockFs({
'.jsdocrc.json': '\uFEFF{"foo":"bar"}'
});
const conf = config.loadSync();
expect(conf.config.foo).toBe('bar');
});
it('parses YAML files that start with a BOM', () => {
mockFs({
'.jsdocrc.yaml': '\uFEFF{"foo":"bar"}'
});
const conf = config.loadSync();
expect(conf.config.foo).toBe('bar');
});
it('provides the default config if the user config is an empty object', () => {
mockFs({
'.jsdocrc.json': '{}'
});
const conf = config.loadSync();
expect(conf.config).toEqual(config.defaults);
});
it('provides the default config if there is no user config', () => {
const conf = config.loadSync();
expect(conf.config).toEqual(config.defaults);
});
it('merges nested defaults with nested user settings as expected', () => {
mockFs({
'.jsdocrc.json': '{"tags":{"foo":"bar"}}'
});
const conf = config.loadSync();
expect(conf.config.tags.allowUnknownTags).toBe(config.defaults.tags.allowUnknownTags);
expect(conf.config.tags.foo).toBe('bar');
});
describe('plugins', () => {
it('is an array', () => {
expect(defaults.plugins).toBeArray();
});
});
describe('defaults', () => {
const { defaults } = config;
describe('source', () => {
it('is an object', () => {
expect(defaults.source).toBeObject();
});
describe('excludePattern', () => {
it('is a string', () => {
expect(defaults.source.excludePattern).toBeString();
});
it('represents a valid regexp', () => {
expect(() => new RegExp(defaults.source.excludePattern)).not.toThrow();
});
});
describe('includePattern', () => {
it('is a string', () => {
expect(defaults.source.includePattern).toBeString();
});
it('represents a valid regexp', () => {
expect(() => new RegExp(defaults.source.includePattern)).not.toThrow();
});
});
describe('type', () => {
it('is a string', () => {
expect(defaults.source.type).toBeString();
});
});
describe('tags', () => {
it('is an object', () => {
expect(defaults).toBeObject();
expect(defaults.tags).toBeObject();
});
describe('plugins', () => {
it('is an array', () => {
expect(defaults.plugins).toBeArray();
});
describe('allowUnknownTags', () => {
it('is a boolean', () => {
expect(defaults.tags.allowUnknownTags).toBeBoolean();
});
});
describe('source', () => {
it('is an object', () => {
expect(defaults.source).toBeObject();
});
describe('excludePattern', () => {
it('is a string', () => {
expect(defaults.source.excludePattern).toBeString();
});
it('represents a valid regexp', () => {
expect(() => new RegExp(defaults.source.excludePattern)).not.toThrow();
});
});
describe('includePattern', () => {
it('is a string', () => {
expect(defaults.source.includePattern).toBeString();
});
it('represents a valid regexp', () => {
expect(() => new RegExp(defaults.source.includePattern)).not.toThrow();
});
});
describe('type', () => {
it('is a string', () => {
expect(defaults.source.type).toBeString();
});
});
describe('tags', () => {
it('is an object', () => {
expect(defaults.tags).toBeObject();
});
describe('allowUnknownTags', () => {
it('is a boolean', () => {
expect(defaults.tags.allowUnknownTags).toBeBoolean();
});
});
describe('dictionaries', () => {
it('is an array of strings', () => {
expect(defaults.tags.dictionaries).toBeArrayOfStrings();
});
});
});
describe('templates', () => {
it('is an object', () => {
expect(defaults.templates).toBeObject();
});
describe('cleverLinks', () => {
it('is a boolean', () => {
expect(defaults.templates.cleverLinks).toBeBoolean();
});
});
describe('monospaceLinks', () => {
it('is a boolean', () => {
expect(defaults.templates.monospaceLinks).toBeBoolean();
});
});
});
describe('dictionaries', () => {
it('is an array of strings', () => {
expect(defaults.tags.dictionaries).toBeArrayOfStrings();
});
});
});
describe('templates', () => {
it('is an object', () => {
expect(defaults.templates).toBeObject();
});
describe('cleverLinks', () => {
it('is a boolean', () => {
expect(defaults.templates.cleverLinks).toBeBoolean();
});
});
describe('monospaceLinks', () => {
it('is a boolean', () => {
expect(defaults.templates.monospaceLinks).toBeBoolean();
});
});
});
});
});
});

View File

@ -1,439 +1,434 @@
describe('@jsdoc/core.name', () => {
const { name } = require('@jsdoc/core');
const { name } = require('@jsdoc/core');
it('exists', () => {
expect(name).toBeObject();
it('exists', () => {
expect(name).toBeObject();
});
it('has an applyNamespace method', () => {
expect(name.applyNamespace).toBeFunction();
});
it('has a fromParts method', () => {
expect(name.fromParts).toBeFunction();
});
it('has a getBasename method', () => {
expect(name.getBasename).toBeFunction();
});
it('has a hasAncestor method', () => {
expect(name.hasAncestor).toBeFunction();
});
it('has a hasLeadingScope method', () => {
expect(name.hasLeadingScope).toBeFunction();
});
it('has a hasTrailingScope method', () => {
expect(name.hasTrailingScope).toBeFunction();
});
it('has a LONGNAMES enum', () => {
expect(name.LONGNAMES).toBeObject();
});
it('has a longnamesToTree method', () => {
expect(name.longnamesToTree).toBeFunction();
});
it('has a MODULE_NAMESPACE property', () => {
expect(name.MODULE_NAMESPACE).toBeString();
});
it('has a nameIsLongname method', () => {
expect(name.nameIsLongname).toBeFunction();
});
it('has a prototypeToPunc method', () => {
expect(name.prototypeToPunc).toBeFunction();
});
it('has a PUNC_TO_SCOPE enum', () => {
expect(name.PUNC_TO_SCOPE).toBeObject();
});
it('has a SCOPE enum', () => {
expect(name.SCOPE).toBeObject();
});
it('has a SCOPE_TO_PUNC enum', () => {
expect(name.SCOPE_TO_PUNC).toBeObject();
});
it('has a splitNameAndDescription method', () => {
expect(name.splitNameAndDescription).toBeFunction();
});
it('has a stripNamespace method', () => {
expect(name.stripNamespace).toBeFunction();
});
it('has a stripVariation method', () => {
expect(name.stripVariation).toBeFunction();
});
it('has a toParts method', () => {
expect(name.toParts).toBeFunction();
});
describe('applyNamespace', () => {
it('applies the namespace to the name part of the longname', () => {
expect(name.applyNamespace('lib.Panel#open', 'event')).toBe('lib.Panel#event:open');
});
it('has an applyNamespace method', () => {
expect(name.applyNamespace).toBeFunction();
it('applies the namespace to the start of a top-level longname', () => {
expect(name.applyNamespace('math/bigint', 'module')).toBe('module:math/bigint');
});
it('has a fromParts method', () => {
expect(name.fromParts).toBeFunction();
// TODO(hegemonic): This has never worked
xit('handles longnames with quoted portions', () => {
expect(name.applyNamespace('foo."*don\'t.look~in#here!"', 'event')).toBe(
'foo.event:"*don\'t.look~in#here!"'
);
});
it('has a getBasename method', () => {
expect(name.getBasename).toBeFunction();
it('handles longnames that already have namespaces', () => {
expect(name.applyNamespace('lib.Panel#event:open', 'event')).toBe('lib.Panel#event:open');
});
});
xdescribe('fromParts', () => {
// TODO: tests
});
describe('getBasename', () => {
it('returns null on empty input', () => {
expect(name.getBasename()).toBeNull();
});
it('has a hasAncestor method', () => {
expect(name.hasAncestor).toBeFunction();
it('returns the original value if it has no punctuation except underscores', () => {
expect(name.getBasename('foo_bar')).toBe('foo_bar');
});
it('has a hasLeadingScope method', () => {
expect(name.hasLeadingScope).toBeFunction();
it('returns the basename if the original value has punctuation', () => {
expect(name.getBasename('foo.Bar#baz')).toBe('foo');
});
});
describe('hasAncestor', () => {
it('returns false if no parent is specified', () => {
expect(name.hasAncestor(null, 'foo')).toBe(false);
});
it('has a hasTrailingScope method', () => {
expect(name.hasTrailingScope).toBeFunction();
it('returns false if no child is specified', () => {
expect(name.hasAncestor('foo')).toBe(false);
});
it('has a LONGNAMES enum', () => {
expect(name.LONGNAMES).toBeObject();
it('returns true when the ancestor is the immediate parent', () => {
expect(name.hasAncestor('module:foo', 'module:foo~bar')).toBe(true);
});
it('has a longnamesToTree method', () => {
expect(name.longnamesToTree).toBeFunction();
it('returns true when the ancestor is not the immediate parent', () => {
expect(name.hasAncestor('module:foo', 'module:foo~bar.Baz#qux')).toBe(true);
});
it('has a MODULE_NAMESPACE property', () => {
expect(name.MODULE_NAMESPACE).toBeString();
it('returns false when a non-ancestor is passed in', () => {
expect(name.hasAncestor('module:foo', 'foo')).toBe(false);
});
it('has a nameIsLongname method', () => {
expect(name.nameIsLongname).toBeFunction();
it('returns false if the parent and child are the same', () => {
expect(name.hasAncestor('module:foo', 'module:foo')).toBe(false);
});
});
describe('hasLeadingScope', () => {
it('returns true if the string starts with a scope character', () => {
expect(name.hasLeadingScope('#foo')).toBeTrue();
});
it('has a prototypeToPunc method', () => {
expect(name.prototypeToPunc).toBeFunction();
it('returns false if the string does not start with a scope character', () => {
expect(name.hasLeadingScope('!foo')).toBeFalse();
});
});
describe('hasTrailingScope', () => {
it('returns true if the string ends with a scope character', () => {
expect(name.hasTrailingScope('Foo#')).toBeTrue();
});
it('has a PUNC_TO_SCOPE enum', () => {
expect(name.PUNC_TO_SCOPE).toBeObject();
it('returns false if the string does not end with a scope character', () => {
expect(name.hasTrailingScope('Foo!')).toBeFalse();
});
});
describe('LONGNAMES', () => {
it('has an ANONYMOUS property', () => {
expect(name.LONGNAMES.ANONYMOUS).toBeString();
});
it('has a SCOPE enum', () => {
expect(name.SCOPE).toBeObject();
it('has a GLOBAL property', () => {
expect(name.LONGNAMES.GLOBAL).toBeString();
});
});
xdescribe('longnamesToTree', () => {
// TODO: tests
});
describe('MODULE_NAMESPACE', () => {
// This is just a string, so nothing to test.
});
xdescribe('nameIsLongname', () => {
// TODO(hegemonic)
});
xdescribe('prototypeToPunc', () => {
// TODO(hegemonic)
});
describe('PUNC_TO_SCOPE', () => {
it('has the same number of properties as SCOPE_TO_PUNC', () => {
expect(Object.keys(name.PUNC_TO_SCOPE).length).toBe(Object.keys(name.SCOPE_TO_PUNC).length);
});
});
describe('SCOPE', () => {
const SCOPE = name.SCOPE;
it('has a NAMES enum', () => {
expect(SCOPE.NAMES).toBeObject();
});
it('has a SCOPE_TO_PUNC enum', () => {
expect(name.SCOPE_TO_PUNC).toBeObject();
it('has a PUNC enum', () => {
expect(SCOPE.PUNC).toBeObject();
});
it('has a splitNameAndDescription method', () => {
expect(name.splitNameAndDescription).toBeFunction();
describe('NAMES', () => {
it('has a GLOBAL property', () => {
expect(SCOPE.NAMES.GLOBAL).toBeString();
});
it('has an INNER property', () => {
expect(SCOPE.NAMES.INNER).toBeString();
});
it('has an INSTANCE property', () => {
expect(SCOPE.NAMES.INSTANCE).toBeString();
});
it('has a STATIC property', () => {
expect(SCOPE.NAMES.STATIC).toBeString();
});
});
it('has a stripNamespace method', () => {
expect(name.stripNamespace).toBeFunction();
describe('PUNC', () => {
it('has an INNER property', () => {
expect(SCOPE.PUNC.INNER).toBeString();
});
it('has an INSTANCE property', () => {
expect(SCOPE.PUNC.INSTANCE).toBeString();
});
it('has a STATIC property', () => {
expect(SCOPE.PUNC.STATIC).toBeString();
});
});
});
describe('SCOPE_TO_PUNC', () => {
it('has an inner property', () => {
expect(name.SCOPE_TO_PUNC.inner).toBeString();
});
it('has a stripVariation method', () => {
expect(name.stripVariation).toBeFunction();
it('has an instance property', () => {
expect(name.SCOPE_TO_PUNC.instance).toBeString();
});
it('has a toParts method', () => {
expect(name.toParts).toBeFunction();
it('has a static property', () => {
expect(name.SCOPE_TO_PUNC.static).toBeString();
});
});
describe('splitNameAndDescription', () => {
// TODO(hegemonic): This has never worked
xit('separates the name from the description', () => {
const parts = name.splitNameAndDescription(
'ns.Page#"last \\"sentence\\"".words~sort(2) - This is a description. '
);
expect(parts.name).toBe('ns.Page#"last \\"sentence\\"".words~sort(2)');
expect(parts.description).toBe('This is a description.');
});
describe('applyNamespace', () => {
it('applies the namespace to the name part of the longname', () => {
expect(name.applyNamespace('lib.Panel#open', 'event')).toBe('lib.Panel#event:open');
});
it('strips a separator when it starts on the same line as the name', () => {
const parts = name.splitNameAndDescription('socket - The networking kind, not the wrench.');
it('applies the namespace to the start of a top-level longname', () => {
expect(name.applyNamespace('math/bigint', 'module')).toBe('module:math/bigint');
});
// TODO(hegemonic): This has never worked
xit('handles longnames with quoted portions', () => {
expect(name.applyNamespace('foo."*don\'t.look~in#here!"', 'event'))
.toBe('foo.event:"*don\'t.look~in#here!"');
});
it('handles longnames that already have namespaces', () => {
expect(name.applyNamespace('lib.Panel#event:open', 'event'))
.toBe('lib.Panel#event:open');
});
expect(parts.name).toBe('socket');
expect(parts.description).toBe('The networking kind, not the wrench.');
});
xdescribe('fromParts', () => {
// TODO: tests
it('does not strip a separator that is preceded by a line break', () => {
const parts = name.splitNameAndDescription('socket\n - The networking kind, not the wrench.');
expect(parts.name).toBe('socket');
expect(parts.description).toBe('- The networking kind, not the wrench.');
});
describe('getBasename', () => {
it('returns null on empty input', () => {
expect(name.getBasename()).toBeNull();
});
it('allows default values to contain square brackets', () => {
const parts = name.splitNameAndDescription(
'[path=["home", "user"]] - Path split into components'
);
it('returns the original value if it has no punctuation except underscores', () => {
expect(name.getBasename('foo_bar')).toBe('foo_bar');
});
it('returns the basename if the original value has punctuation', () => {
expect(name.getBasename('foo.Bar#baz')).toBe('foo');
});
expect(parts.name).toBe('[path=["home", "user"]]');
expect(parts.description).toBe('Path split into components');
});
describe('hasAncestor', () => {
it('returns false if no parent is specified', () => {
expect(name.hasAncestor(null, 'foo')).toBe(false);
});
it('allows default values to contain unmatched square brackets inside strings', () => {
const parts = name.splitNameAndDescription(
'[path=["Unmatched begin: ["]] - Path split into components'
);
it('returns false if no child is specified', () => {
expect(name.hasAncestor('foo')).toBe(false);
});
it('returns true when the ancestor is the immediate parent', () => {
expect(name.hasAncestor('module:foo', 'module:foo~bar')).toBe(true);
});
it('returns true when the ancestor is not the immediate parent', () => {
expect(name.hasAncestor('module:foo', 'module:foo~bar.Baz#qux')).toBe(true);
});
it('returns false when a non-ancestor is passed in', () => {
expect(name.hasAncestor('module:foo', 'foo')).toBe(false);
});
it('returns false if the parent and child are the same', () => {
expect(name.hasAncestor('module:foo', 'module:foo')).toBe(false);
});
expect(parts.name).toBe('[path=["Unmatched begin: ["]]');
expect(parts.description).toBe('Path split into components');
});
describe('hasLeadingScope', () => {
it('returns true if the string starts with a scope character', () => {
expect(name.hasLeadingScope('#foo')).toBeTrue();
});
it('fails gracefully when the default value has an unmatched square bracket', () => {
const parts = name.splitNameAndDescription(
'[path=["home", "user"] - Path split into components'
);
it('returns false if the string does not start with a scope character', () => {
expect(name.hasLeadingScope('!foo')).toBeFalse();
});
expect(parts).toBeObject();
expect(parts.name).toBe('[path=["home", "user"]');
expect(parts.description).toBe('Path split into components');
});
describe('hasTrailingScope', () => {
it('returns true if the string ends with a scope character', () => {
expect(name.hasTrailingScope('Foo#')).toBeTrue();
});
it('fails gracefully when the default value has an unmatched quote', () => {
const parts = name.splitNameAndDescription(
'[path=["home", "user] - Path split into components'
);
it('returns false if the string does not end with a scope character', () => {
expect(name.hasTrailingScope('Foo!')).toBeFalse();
});
expect(parts).toBeObject();
expect(parts.name).toBe('[path=["home", "user]');
expect(parts.description).toBe('Path split into components');
});
});
describe('stripNamespace', () => {
it('removes the namespace from the longname', () => {
expect(name.stripNamespace('module:foo/bar/baz')).toBe('foo/bar/baz');
});
describe('LONGNAMES', () => {
it('has an ANONYMOUS property', () => {
expect(name.LONGNAMES.ANONYMOUS).toBeString();
});
it('has a GLOBAL property', () => {
expect(name.LONGNAMES.GLOBAL).toBeString();
});
it('does not remove the namespace from a child member', () => {
expect(name.stripNamespace('foo/bar.baz~event:qux')).toBe('foo/bar.baz~event:qux');
});
xdescribe('longnamesToTree', () => {
// TODO: tests
it('does not change longnames that do not have a namespace', () => {
expect(name.stripNamespace('Foo#bar')).toBe('Foo#bar');
});
});
describe('stripVariation', () => {
it('does not change longnames with no variation', () => {
expect(name.stripVariation('Foo#bar')).toBe('Foo#bar');
});
describe('MODULE_NAMESPACE', () => {
// This is just a string, so nothing to test.
it('removes the variation if present', () => {
expect(name.stripVariation('Foo#bar(qux)')).toBe('Foo#bar');
});
});
describe('toParts', () => {
it('breaks up a longname into the correct parts', () => {
const parts = name.toParts('lib.Panel#open');
expect(parts.name).toBe('open');
expect(parts.memberof).toBe('lib.Panel');
expect(parts.scope).toBe('#');
});
xdescribe('nameIsLongname', () => {
// TODO(hegemonic)
it('handles static names', () => {
const parts = name.toParts('elements.selected.getVisible');
expect(parts.name).toBe('getVisible');
expect(parts.memberof).toBe('elements.selected');
expect(parts.scope).toBe('.');
});
xdescribe('prototypeToPunc', () => {
// TODO(hegemonic)
it('handles members of a prototype', () => {
const parts = name.toParts('Validator.prototype.$element');
expect(parts.name).toEqual('$element');
expect(parts.memberof).toEqual('Validator');
expect(parts.scope).toEqual('#');
});
describe('PUNC_TO_SCOPE', () => {
it('has the same number of properties as SCOPE_TO_PUNC', () => {
expect(Object.keys(name.PUNC_TO_SCOPE).length)
.toBe(Object.keys(name.SCOPE_TO_PUNC).length);
});
it('handles inner names', () => {
const parts = name.toParts('Button~_onclick');
expect(parts.name).toEqual('_onclick');
expect(parts.memberof).toEqual('Button');
expect(parts.scope).toEqual('~');
});
describe('SCOPE', () => {
const SCOPE = name.SCOPE;
it('handles global names', () => {
const parts = name.toParts('close');
it('has a NAMES enum', () => {
expect(SCOPE.NAMES).toBeObject();
});
it('has a PUNC enum', () => {
expect(SCOPE.PUNC).toBeObject();
});
describe('NAMES', () => {
it('has a GLOBAL property', () => {
expect(SCOPE.NAMES.GLOBAL).toBeString();
});
it('has an INNER property', () => {
expect(SCOPE.NAMES.INNER).toBeString();
});
it('has an INSTANCE property', () => {
expect(SCOPE.NAMES.INSTANCE).toBeString();
});
it('has a STATIC property', () => {
expect(SCOPE.NAMES.STATIC).toBeString();
});
});
describe('PUNC', () => {
it('has an INNER property', () => {
expect(SCOPE.PUNC.INNER).toBeString();
});
it('has an INSTANCE property', () => {
expect(SCOPE.PUNC.INSTANCE).toBeString();
});
it('has a STATIC property', () => {
expect(SCOPE.PUNC.STATIC).toBeString();
});
});
expect(parts.name).toEqual('close');
expect(parts.memberof).toEqual('');
expect(parts.scope).toEqual('');
});
describe('SCOPE_TO_PUNC', () => {
it('has an inner property', () => {
expect(name.SCOPE_TO_PUNC.inner).toBeString();
});
it('handles a single property that uses bracket notation', () => {
const parts = name.toParts('channels["#ops"]#open');
it('has an instance property', () => {
expect(name.SCOPE_TO_PUNC.instance).toBeString();
});
it('has a static property', () => {
expect(name.SCOPE_TO_PUNC.static).toBeString();
});
expect(parts.name).toEqual('open');
expect(parts.memberof).toEqual('channels."#ops"');
expect(parts.scope).toEqual('#');
});
describe('splitNameAndDescription', () => {
// TODO(hegemonic): This has never worked
xit('separates the name from the description', () => {
const parts = name.splitNameAndDescription(
'ns.Page#"last \\"sentence\\"".words~sort(2) - This is a description. '
);
it('handles consecutive properties that use bracket notation', () => {
const parts = name.toParts('channels["#bots"]["log.max"]');
expect(parts.name).toBe('ns.Page#"last \\"sentence\\"".words~sort(2)');
expect(parts.description).toBe('This is a description.');
});
it('strips a separator when it starts on the same line as the name', () => {
const parts = name.splitNameAndDescription(
'socket - The networking kind, not the wrench.'
);
expect(parts.name).toBe('socket');
expect(parts.description).toBe('The networking kind, not the wrench.');
});
it('does not strip a separator that is preceded by a line break', () => {
const parts = name.splitNameAndDescription(
'socket\n - The networking kind, not the wrench.'
);
expect(parts.name).toBe('socket');
expect(parts.description).toBe('- The networking kind, not the wrench.');
});
it('allows default values to contain square brackets', () => {
const parts = name.splitNameAndDescription(
'[path=["home", "user"]] - Path split into components'
);
expect(parts.name).toBe('[path=["home", "user"]]');
expect(parts.description).toBe('Path split into components');
});
it('allows default values to contain unmatched square brackets inside strings', () => {
const parts = name.splitNameAndDescription(
'[path=["Unmatched begin: ["]] - Path split into components'
);
expect(parts.name).toBe('[path=["Unmatched begin: ["]]');
expect(parts.description).toBe('Path split into components');
});
it('fails gracefully when the default value has an unmatched square bracket', () => {
const parts = name.splitNameAndDescription(
'[path=["home", "user"] - Path split into components'
);
expect(parts).toBeObject();
expect(parts.name).toBe('[path=["home", "user"]');
expect(parts.description).toBe('Path split into components');
});
it('fails gracefully when the default value has an unmatched quote', () => {
const parts = name.splitNameAndDescription(
'[path=["home", "user] - Path split into components'
);
expect(parts).toBeObject();
expect(parts.name).toBe('[path=["home", "user]');
expect(parts.description).toBe('Path split into components');
});
expect(parts.name).toEqual('"log.max"');
expect(parts.memberof).toEqual('channels."#bots"');
expect(parts.scope).toEqual('.');
});
describe('stripNamespace', () => {
it('removes the namespace from the longname', () => {
expect(name.stripNamespace('module:foo/bar/baz')).toBe('foo/bar/baz');
});
it('handles a property that uses single-quoted bracket notation', () => {
const parts = name.toParts("channels['#ops']");
it('does not remove the namespace from a child member', () => {
expect(name.stripNamespace('foo/bar.baz~event:qux')).toBe('foo/bar.baz~event:qux');
});
it('does not change longnames that do not have a namespace', () => {
expect(name.stripNamespace('Foo#bar')).toBe('Foo#bar');
});
expect(parts.name).toBe("'#ops'");
expect(parts.memberof).toBe('channels');
expect(parts.scope).toBe('.');
});
describe('stripVariation', () => {
it('does not change longnames with no variation', () => {
expect(name.stripVariation('Foo#bar')).toBe('Foo#bar');
});
it('handles double-quoted strings', () => {
const parts = name.toParts('"foo.bar"');
it('removes the variation if present', () => {
expect(name.stripVariation('Foo#bar(qux)')).toBe('Foo#bar');
});
expect(parts.name).toEqual('"foo.bar"');
expect(parts.longname).toEqual('"foo.bar"');
expect(parts.memberof).toEqual('');
expect(parts.scope).toEqual('');
});
describe('toParts', () => {
it('breaks up a longname into the correct parts', () => {
const parts = name.toParts('lib.Panel#open');
it('handles single-quoted strings', () => {
const parts = name.toParts("'foo.bar'");
expect(parts.name).toBe('open');
expect(parts.memberof).toBe('lib.Panel');
expect(parts.scope).toBe('#');
});
it('handles static names', () => {
const parts = name.toParts('elements.selected.getVisible');
expect(parts.name).toBe('getVisible');
expect(parts.memberof).toBe('elements.selected');
expect(parts.scope).toBe('.');
});
it('handles members of a prototype', () => {
const parts = name.toParts('Validator.prototype.$element');
expect(parts.name).toEqual('$element');
expect(parts.memberof).toEqual('Validator');
expect(parts.scope).toEqual('#');
});
it('handles inner names', () => {
const parts = name.toParts('Button~_onclick');
expect(parts.name).toEqual('_onclick');
expect(parts.memberof).toEqual('Button');
expect(parts.scope).toEqual('~');
});
it('handles global names', () => {
const parts = name.toParts('close');
expect(parts.name).toEqual('close');
expect(parts.memberof).toEqual('');
expect(parts.scope).toEqual('');
});
it('handles a single property that uses bracket notation', () => {
const parts = name.toParts('channels["#ops"]#open');
expect(parts.name).toEqual('open');
expect(parts.memberof).toEqual('channels."#ops"');
expect(parts.scope).toEqual('#');
});
it('handles consecutive properties that use bracket notation', () => {
const parts = name.toParts('channels["#bots"]["log.max"]');
expect(parts.name).toEqual('"log.max"');
expect(parts.memberof).toEqual('channels."#bots"');
expect(parts.scope).toEqual('.');
});
it('handles a property that uses single-quoted bracket notation', () => {
const parts = name.toParts("channels['#ops']");
expect(parts.name).toBe("'#ops'");
expect(parts.memberof).toBe('channels');
expect(parts.scope).toBe('.');
});
it('handles double-quoted strings', () => {
const parts = name.toParts('"foo.bar"');
expect(parts.name).toEqual('"foo.bar"');
expect(parts.longname).toEqual('"foo.bar"');
expect(parts.memberof).toEqual('');
expect(parts.scope).toEqual('');
});
it('handles single-quoted strings', () => {
const parts = name.toParts("'foo.bar'");
expect(parts.name).toBe("'foo.bar'");
expect(parts.longname).toBe("'foo.bar'");
expect(parts.memberof).toBe('');
expect(parts.scope).toBe('');
});
it('handles variations', () => {
const parts = name.toParts('anim.fadein(2)');
expect(parts.variation).toEqual('2');
expect(parts.name).toEqual('fadein');
expect(parts.longname).toEqual('anim.fadein(2)');
});
expect(parts.name).toBe("'foo.bar'");
expect(parts.longname).toBe("'foo.bar'");
expect(parts.memberof).toBe('');
expect(parts.scope).toBe('');
});
it('handles variations', () => {
const parts = name.toParts('anim.fadein(2)');
expect(parts.variation).toEqual('2');
expect(parts.name).toEqual('fadein');
expect(parts.longname).toEqual('anim.fadein(2)');
});
});
});

View File

@ -1,359 +1,362 @@
module.exports = {
env: {
es6: true,
jasmine: true,
node: true
},
env: {
es6: true,
jasmine: true,
node: true,
},
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module'
},
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
rules: {
// Possible errors
'for-direction': 'error',
'getter-return': 'error',
'no-async-promise-executor': 'error',
'no-await-in-loop': 'error',
'no-compare-neg-zero': 'error',
'no-cond-assign': 'error',
'no-console': 'off',
'no-constant-condition': 'off',
'no-control-regex': 'error',
'no-debugger': 'error',
'no-dupe-args': 'error',
'no-dupe-else-if': 'error',
'no-dupe-keys': 'error',
'no-duplicate-case': 'error',
'no-empty': 'error',
'no-empty-character-class': 'error',
'no-ex-assign': 'error',
'no-extra-boolean-cast': 'error',
'no-extra-parens': 'off',
'no-extra-semi': 'error',
'no-func-assign': 'error',
'no-import-assign': 'error',
'no-inner-declarations': ['error', 'functions'],
'no-invalid-regexp': 'error',
'no-irregular-whitespace': 'error',
'no-loss-of-precision': 'error',
'no-misleading-character-class': 'error',
'no-obj-calls': 'error',
'no-prototype-builtins': 'error',
'no-regex-spaces': 'error',
'no-setter-return': 'error',
'no-sparse-arrays': 'error',
'no-template-curly-in-string': 'error',
'no-unexpected-multiline': 'error',
'no-unreachable': 'error',
'no-unreachable-loop': 'error',
'no-unsafe-finally': 'error',
'no-unsafe-negation': 'error',
'no-unsafe-optional-chaining': 'error',
'no-useless-backreference': 'error',
'require-atomic-updates': 'error',
'use-isnan': 'error',
'valid-typeof': 'error',
rules: {
// Possible errors
'for-direction': 'error',
'getter-return': 'error',
'no-async-promise-executor': 'error',
'no-await-in-loop': 'error',
'no-compare-neg-zero': 'error',
'no-cond-assign': 'error',
'no-console': 'off',
'no-constant-condition': 'off',
'no-control-regex': 'error',
'no-debugger': 'error',
'no-dupe-args': 'error',
'no-dupe-else-if': 'error',
'no-dupe-keys': 'error',
'no-duplicate-case': 'error',
'no-empty': 'error',
'no-empty-character-class': 'error',
'no-ex-assign': 'error',
'no-extra-boolean-cast': 'error',
'no-extra-parens': 'off',
'no-extra-semi': 'error',
'no-func-assign': 'error',
'no-import-assign': 'error',
'no-inner-declarations': ['error', 'functions'],
'no-invalid-regexp': 'error',
'no-irregular-whitespace': 'error',
'no-loss-of-precision': 'error',
'no-misleading-character-class': 'error',
'no-obj-calls': 'error',
'no-prototype-builtins': 'error',
'no-regex-spaces': 'error',
'no-setter-return': 'error',
'no-sparse-arrays': 'error',
'no-template-curly-in-string': 'error',
'no-unexpected-multiline': 'error',
'no-unreachable': 'error',
'no-unreachable-loop': 'error',
'no-unsafe-finally': 'error',
'no-unsafe-negation': 'error',
'no-unsafe-optional-chaining': 'error',
'no-useless-backreference': 'error',
'require-atomic-updates': 'error',
'use-isnan': 'error',
'valid-typeof': 'error',
// Best practices
'accessor-pairs': 'error',
'array-callback-return': 'error',
'block-scoped-var': 'off',
'class-methods-use-this': 'off',
complexity: 'off', // TODO: enable
'consistent-return': 'error',
curly: ['error', 'all'],
'default-case': 'error',
'default-case-last': 'error',
'default-param-last': 'error',
'dot-location': ['error', 'property'],
'dot-notation': 'error',
eqeqeq: ['error', 'smart'],
'grouped-accessor-pairs': 'error',
'guard-for-in': 'error',
'max-classes-per-file': 'off',
'no-alert': 'error',
'no-caller': 'error',
'no-case-declarations': 'error',
'no-constructor-return': 'off',
'no-div-regex': 'error',
'no-else-return': 'off',
'no-empty-function': 'error',
'no-empty-pattern': 'error',
'no-eq-null': 'error',
'no-eval': 'error',
'no-extend-native': 'error',
'no-extra-bind': 'error',
'no-extra-label': 'error',
'no-fallthrough': 'off', // disabled due to bug in ESLint
'no-floating-decimal': 'error',
'no-global-assign': 'error',
'no-implicit-coercion': 'error',
'no-implicit-globals': 'error',
'no-implied-eval': 'error',
'no-invalid-this': 'error',
'no-iterator': 'error',
'no-labels': 'error',
'no-lone-blocks': 'error',
'no-loop-func': 'error',
'no-magic-numbers': 'off', // TODO: enable?
'no-multi-spaces': 'error',
'no-multi-str': 'error',
'no-new': 'error',
'no-new-func': 'error',
'no-new-wrappers': 'error',
'no-nonoctal-decimal-escape': 'error',
'no-octal': 'error',
'no-octal-escape': 'error',
'no-param-reassign': 'off',
'no-proto': 'error',
'no-redeclare': 'error',
'no-restricted-properties': 'off',
'no-return-assign': 'error',
'no-return-await': 'error',
'no-script-url': 'error',
'no-self-assign': 'error',
'no-self-compare': 'error',
'no-sequences': 'error',
'no-throw-literal': 'error',
'no-unmodified-loop-condition': 'error',
'no-unused-expressions': 'error',
'no-unused-labels': 'error',
'no-useless-call': 'error',
'no-useless-catch': 'error',
'no-useless-concat': 'error',
'no-useless-escape': 'error',
'no-useless-return': 'error',
'no-void': 'error',
'no-warning-comments': 'off',
'no-with': 'error',
'prefer-named-capture-group': 'off', // TODO: enable
'prefer-promise-reject-errors': 'error',
'prefer-regex-literals': 'error',
radix: 'error',
'require-await': 'error',
'require-unicode-regexp': 'off',
'vars-on-top': 'off', // TODO: enable
'wrap-iife': ['error', 'inside'],
yoda: 'error',
// Best practices
'accessor-pairs': 'error',
'array-callback-return': 'error',
'block-scoped-var': 'off',
'class-methods-use-this': 'off',
complexity: 'off', // TODO: enable
'consistent-return': 'error',
curly: ['error', 'all'],
'default-case': 'error',
'default-case-last': 'error',
'default-param-last': 'error',
'dot-location': ['error', 'property'],
'dot-notation': 'error',
eqeqeq: ['error', 'smart'],
'grouped-accessor-pairs': 'error',
'guard-for-in': 'error',
'max-classes-per-file': 'off',
'no-alert': 'error',
'no-caller': 'error',
'no-case-declarations': 'error',
'no-constructor-return': 'off',
'no-div-regex': 'error',
'no-else-return': 'off',
'no-empty-function': 'error',
'no-empty-pattern': 'error',
'no-eq-null': 'error',
'no-eval': 'error',
'no-extend-native': 'error',
'no-extra-bind': 'error',
'no-extra-label': 'error',
'no-fallthrough': 'off', // disabled due to bug in ESLint
'no-floating-decimal': 'error',
'no-global-assign': 'error',
'no-implicit-coercion': 'error',
'no-implicit-globals': 'error',
'no-implied-eval': 'error',
'no-invalid-this': 'error',
'no-iterator': 'error',
'no-labels': 'error',
'no-lone-blocks': 'error',
'no-loop-func': 'error',
'no-magic-numbers': 'off', // TODO: enable?
'no-multi-spaces': 'error',
'no-multi-str': 'error',
'no-new': 'error',
'no-new-func': 'error',
'no-new-wrappers': 'error',
'no-nonoctal-decimal-escape': 'error',
'no-octal': 'error',
'no-octal-escape': 'error',
'no-param-reassign': 'off',
'no-proto': 'error',
'no-redeclare': 'error',
'no-restricted-properties': 'off',
'no-return-assign': 'error',
'no-return-await': 'error',
'no-script-url': 'error',
'no-self-assign': 'error',
'no-self-compare': 'error',
'no-sequences': 'error',
'no-throw-literal': 'error',
'no-unmodified-loop-condition': 'error',
'no-unused-expressions': 'error',
'no-unused-labels': 'error',
'no-useless-call': 'error',
'no-useless-catch': 'error',
'no-useless-concat': 'error',
'no-useless-escape': 'error',
'no-useless-return': 'error',
'no-void': 'error',
'no-warning-comments': 'off',
'no-with': 'error',
'prefer-named-capture-group': 'off', // TODO: enable
'prefer-promise-reject-errors': 'error',
'prefer-regex-literals': 'error',
radix: 'error',
'require-await': 'error',
'require-unicode-regexp': 'off',
'vars-on-top': 'off', // TODO: enable
'wrap-iife': ['error', 'inside'],
yoda: 'error',
// Strict mode
strict: ['error', 'global'],
// Strict mode
strict: ['error', 'global'],
// Variables
'init-declarations': 'off',
'no-delete-var': 'error',
'no-label-var': 'error',
'no-restricted-globals': ['error', 'app', 'env'],
'no-shadow': 'error',
'no-shadow-restricted-names': 'error',
'no-undef': 'error',
'no-undef-init': 'error',
'no-undefined': 'off',
'no-unused-vars': 'error',
'no-use-before-define': 'error',
// Variables
'init-declarations': 'off',
'no-delete-var': 'error',
'no-label-var': 'error',
'no-restricted-globals': ['error', 'app', 'env'],
'no-shadow': 'error',
'no-shadow-restricted-names': 'error',
'no-undef': 'error',
'no-undef-init': 'error',
'no-undefined': 'off',
'no-unused-vars': 'error',
'no-use-before-define': 'error',
// Stylistic issues
'array-bracket-newline': 'off',
'array-bracket-spacing': ['error', 'never'],
'array-element-newline': 'off',
'block-spacing': ['error', 'always'],
'brace-style': 'off', // TODO: enable with "stroustrup" (or "1tbsp" + lots of cleanup)
camelcase: 'error',
'capitalized-comments': 'off',
'comma-dangle': 'error',
'comma-spacing': [
'error',
{
before: false,
after: true
}
],
'comma-style': ['error', 'last'],
'computed-property-spacing': ['error', 'never'],
'consistent-this': ['error', 'self'],
'eol-last': 'error',
'func-call-spacing': ['error', 'never'],
'func-name-matching': ['error', 'always'],
'func-names': 'off',
'func-style': 'off',
'function-call-argument-newline:': 'off',
'function-paren-newline': 'off',
'id-denylist': 'off',
'id-length': 'off',
'id-match': 'off',
'implicit-arrow-linebreak': 'off',
indent: [
'error',
4,
{
SwitchCase: 1
}
],
'jsx-quotes': ['error', 'prefer-double'],
'key-spacing': [
'error',
{
beforeColon: false,
afterColon: true
}
],
'keyword-spacing': [
'error',
{
before: true,
after: true
}
],
'line-comment-position': 'off',
'linebreak-style': 'off',
'lines-around-comment': 'off',
'lines-between-class-members': 'off',
'max-depth': 'off', // TODO: enable
'max-len': 'off', // TODO: enable
'max-lines': 'off',
'max-lines-per-function': 'off',
'max-nested-callbacks': 'off',
'max-params': 'off', // TODO: enable
'max-statements': 'off',
'max-statements-per-line': 'off',
'multiline-comment-style': 'off',
'multiline-ternary': 'off',
'new-cap': 'error',
'new-parens': 'error',
'newline-per-chained-call': 'off', // TODO: enable
'no-array-constructor': 'error',
'no-bitwise': 'error',
'no-continue': 'off',
'no-inline-comments': 'off',
'no-lonely-if': 'error',
'no-mixed-operators': 'error',
'no-mixed-spaces-and-tabs': 'error',
'no-multi-assign': 'off',
'no-multiple-empty-lines': [
'error',
{
max: 2
}
],
'no-negated-condition': 'off',
'no-nested-ternary': 'error',
'no-new-object': 'error',
'no-plusplus': 'off',
'no-restricted-syntax': 'off',
'no-tabs': 'error',
'no-ternary': 'off',
'no-trailing-spaces': 'error',
'no-underscore-dangle': 'off',
'no-unneeded-ternary': 'error',
'no-whitespace-before-property': 'error',
'nonblock-statement-body-position': 'off',
'object-curly-newline': 'off',
'object-curly-spacing': 'off',
'object-property-newline': 'error',
'one-var': 'off',
'one-var-declaration-per-line': 'error',
'operator-assignment': 'off',
'operator-linebreak': ['error', 'after'],
'padded-blocks': ['error', 'never'],
'padding-line-between-statements': [
'error',
{
blankLine: 'always',
prev: '*',
next: 'return'
},
{
blankLine: 'always',
prev: ['const', 'let', 'var'],
next: '*'
},
{
blankLine: 'any',
prev: ['const', 'let', 'var'],
next: ['const', 'let', 'var']
}
],
'prefer-exponentiation-operator': 'error',
'prefer-object-spread': 'off',
'quote-props': 'off',
quotes: ['error', 'single', 'avoid-escape'],
semi: ['error', 'always'],
'semi-spacing': 'error',
'semi-style': ['error', 'last'],
'sort-keys': 'off',
'sort-vars': 'off', // TODO: enable?
'space-before-blocks': ['error', 'always'],
'space-before-function-paren': ['error', {
anonymous: 'never',
named: 'never',
asyncArrow: 'always'
}],
'space-in-parens': 'off', // TODO: enable?
'space-infix-ops': 'error',
'space-unary-ops': 'error',
'spaced-comment': ['error', 'always'],
'switch-colon-spacing': [
'error',
{
after: true,
before: false
}
],
'template-tag-spacing': ['error', 'never'],
'unicode-bom': ['error', 'never'],
'wrap-regex': 'off',
// Stylistic issues
'array-bracket-newline': 'off',
'array-bracket-spacing': ['error', 'never'],
'array-element-newline': 'off',
'block-spacing': ['error', 'always'],
'brace-style': 'off', // TODO: enable with "stroustrup" (or "1tbsp" + lots of cleanup)
camelcase: 'error',
'capitalized-comments': 'off',
'comma-dangle': 'error',
'comma-spacing': [
'error',
{
before: false,
after: true,
},
],
'comma-style': ['error', 'last'],
'computed-property-spacing': ['error', 'never'],
'consistent-this': ['error', 'self'],
'eol-last': 'error',
'func-call-spacing': ['error', 'never'],
'func-name-matching': ['error', 'always'],
'func-names': 'off',
'func-style': 'off',
'function-call-argument-newline:': 'off',
'function-paren-newline': 'off',
'id-denylist': 'off',
'id-length': 'off',
'id-match': 'off',
'implicit-arrow-linebreak': 'off',
indent: [
'error',
2,
{
SwitchCase: 1,
},
],
'jsx-quotes': ['error', 'prefer-double'],
'key-spacing': [
'error',
{
beforeColon: false,
afterColon: true,
},
],
'keyword-spacing': [
'error',
{
before: true,
after: true,
},
],
'line-comment-position': 'off',
'linebreak-style': 'off',
'lines-around-comment': 'off',
'lines-between-class-members': 'off',
'max-depth': 'off', // TODO: enable
'max-len': 'off', // TODO: enable
'max-lines': 'off',
'max-lines-per-function': 'off',
'max-nested-callbacks': 'off',
'max-params': 'off', // TODO: enable
'max-statements': 'off',
'max-statements-per-line': 'off',
'multiline-comment-style': 'off',
'multiline-ternary': 'off',
'new-cap': 'error',
'new-parens': 'error',
'newline-per-chained-call': 'off', // TODO: enable
'no-array-constructor': 'error',
'no-bitwise': 'error',
'no-continue': 'off',
'no-inline-comments': 'off',
'no-lonely-if': 'error',
'no-mixed-operators': 'error',
'no-mixed-spaces-and-tabs': 'error',
'no-multi-assign': 'off',
'no-multiple-empty-lines': [
'error',
{
max: 2,
},
],
'no-negated-condition': 'off',
'no-nested-ternary': 'error',
'no-new-object': 'error',
'no-plusplus': 'off',
'no-restricted-syntax': 'off',
'no-tabs': 'error',
'no-ternary': 'off',
'no-trailing-spaces': 'error',
'no-underscore-dangle': 'off',
'no-unneeded-ternary': 'error',
'no-whitespace-before-property': 'error',
'nonblock-statement-body-position': 'off',
'object-curly-newline': 'off',
'object-curly-spacing': 'off',
'object-property-newline': 'error',
'one-var': 'off',
'one-var-declaration-per-line': 'error',
'operator-assignment': 'off',
'operator-linebreak': ['error', 'after'],
'padded-blocks': ['error', 'never'],
'padding-line-between-statements': [
'error',
{
blankLine: 'always',
prev: '*',
next: 'return',
},
{
blankLine: 'always',
prev: ['const', 'let', 'var'],
next: '*',
},
{
blankLine: 'any',
prev: ['const', 'let', 'var'],
next: ['const', 'let', 'var'],
},
],
'prefer-exponentiation-operator': 'error',
'prefer-object-spread': 'off',
'quote-props': 'off',
quotes: ['error', 'single', 'avoid-escape'],
semi: ['error', 'always'],
'semi-spacing': 'error',
'semi-style': ['error', 'last'],
'sort-keys': 'off',
'sort-vars': 'off', // TODO: enable?
'space-before-blocks': ['error', 'always'],
'space-before-function-paren': [
'error',
{
anonymous: 'never',
named: 'never',
asyncArrow: 'always',
},
],
'space-in-parens': 'off', // TODO: enable?
'space-infix-ops': 'error',
'space-unary-ops': 'error',
'spaced-comment': ['error', 'always'],
'switch-colon-spacing': [
'error',
{
after: true,
before: false,
},
],
'template-tag-spacing': ['error', 'never'],
'unicode-bom': ['error', 'never'],
'wrap-regex': 'off',
// ECMAScript 2015
'arrow-body-style': ['error', 'as-needed'],
'arrow-parens': 'off',
'arrow-spacing': [
'error',
{
before: true,
after: true
}
],
'constructor-super': 'error',
'generator-star-spacing': [
'error',
{
before: true,
after: false
}
],
'no-class-assign': 'error',
'no-confusing-arrow': 'error',
'no-const-assign': 'error',
'no-dupe-class-members': 'error',
'no-duplicate-imports': [
'error',
{
includeExports: true
}
],
'no-new-symbol': 'error',
'no-restricted-exports': 'off',
'no-restricted-imports': 'off',
'no-this-before-super': 'error',
'no-useless-computed-key': 'error',
'no-useless-constructor': 'off',
'no-useless-rename': 'error',
'no-var': 'off', // TODO: enable
'object-shorthand': 'off',
'prefer-arrow-callback': 'off',
'prefer-const': 'off',
'prefer-destructuring': 'off',
'prefer-numeric-literals': 'off',
'prefer-rest-params': 'off',
'prefer-spread': 'off',
'prefer-template': 'off',
'require-yield': 'error',
'rest-spread-spacing': ['error', 'never'],
'sort-imports': 'error',
'symbol-description': 'error',
'template-curly-spacing': ['error', 'never'],
'yield-star-spacing': ['error', 'before']
}
// ECMAScript 2015
'arrow-body-style': ['error', 'as-needed'],
'arrow-parens': 'off',
'arrow-spacing': [
'error',
{
before: true,
after: true,
},
],
'constructor-super': 'error',
'generator-star-spacing': [
'error',
{
before: true,
after: false,
},
],
'no-class-assign': 'error',
'no-confusing-arrow': 'error',
'no-const-assign': 'error',
'no-dupe-class-members': 'error',
'no-duplicate-imports': [
'error',
{
includeExports: true,
},
],
'no-new-symbol': 'error',
'no-restricted-exports': 'off',
'no-restricted-imports': 'off',
'no-this-before-super': 'error',
'no-useless-computed-key': 'error',
'no-useless-constructor': 'off',
'no-useless-rename': 'error',
'no-var': 'off', // TODO: enable
'object-shorthand': 'off',
'prefer-arrow-callback': 'off',
'prefer-const': 'off',
'prefer-destructuring': 'off',
'prefer-numeric-literals': 'off',
'prefer-rest-params': 'off',
'prefer-spread': 'off',
'prefer-template': 'off',
'require-yield': 'error',
'rest-spread-spacing': ['error', 'never'],
'sort-imports': 'error',
'symbol-description': 'error',
'template-curly-spacing': ['error', 'never'],
'yield-star-spacing': ['error', 'before'],
},
};

View File

@ -3,7 +3,7 @@ const astNode = require('./lib/ast-node');
const { Syntax } = require('./lib/syntax');
module.exports = {
AstBuilder,
astNode,
Syntax
AstBuilder,
astNode,
Syntax,
};

View File

@ -3,63 +3,68 @@ const babelParser = require('@babel/parser');
const { log } = require('@jsdoc/util');
// Exported so we can use them in tests.
const parserOptions = exports.parserOptions = {
allowAwaitOutsideFunction: true,
allowImportExportEverywhere: true,
allowReturnOutsideFunction: true,
allowSuperOutsideMethod: true,
allowUndeclaredExports: true,
plugins: [
'asyncGenerators',
'bigInt',
'classPrivateMethods',
'classPrivateProperties',
'classProperties',
['decorators', {
decoratorsBeforeExport: true
}],
'doExpressions',
'dynamicImport',
'estree',
'exportDefaultFrom',
'exportNamespaceFrom',
'functionBind',
'functionSent',
'importMeta',
'jsx',
'logicalAssignment',
'nullishCoalescingOperator',
'numericSeparator',
'objectRestSpread',
'optionalCatchBinding',
'optionalChaining',
['pipelineOperator', {
proposal: 'minimal'
}],
'throwExpressions'
const parserOptions = (exports.parserOptions = {
allowAwaitOutsideFunction: true,
allowImportExportEverywhere: true,
allowReturnOutsideFunction: true,
allowSuperOutsideMethod: true,
allowUndeclaredExports: true,
plugins: [
'asyncGenerators',
'bigInt',
'classPrivateMethods',
'classPrivateProperties',
'classProperties',
[
'decorators',
{
decoratorsBeforeExport: true,
},
],
ranges: true
};
'doExpressions',
'dynamicImport',
'estree',
'exportDefaultFrom',
'exportNamespaceFrom',
'functionBind',
'functionSent',
'importMeta',
'jsx',
'logicalAssignment',
'nullishCoalescingOperator',
'numericSeparator',
'objectRestSpread',
'optionalCatchBinding',
'optionalChaining',
[
'pipelineOperator',
{
proposal: 'minimal',
},
],
'throwExpressions',
],
ranges: true,
});
function parse(source, filename, sourceType) {
let ast;
const options = _.defaults({}, parserOptions, {sourceType});
let ast;
const options = _.defaults({}, parserOptions, { sourceType });
try {
ast = babelParser.parse(source, options);
}
catch (e) {
log.error(`Unable to parse ${filename}: ${e.message}`);
}
try {
ast = babelParser.parse(source, options);
} catch (e) {
log.error(`Unable to parse ${filename}: ${e.message}`);
}
return ast;
return ast;
}
// TODO: docs
class AstBuilder {
// TODO: docs
static build(source, filename, sourceType) {
return parse(source, filename, sourceType);
}
// TODO: docs
static build(source, filename, sourceType) {
return parse(source, filename, sourceType);
}
}
exports.AstBuilder = AstBuilder;

View File

@ -15,23 +15,26 @@ let uid = 100000000;
* @param {(Object|string)} node - The AST node to check, or the `type` property of a node.
* @return {boolean} Set to `true` if the node is a function or `false` in all other cases.
*/
const isFunction = exports.isFunction = node => {
let type;
const isFunction = (exports.isFunction = (node) => {
let type;
if (!node) {
return false;
}
if (!node) {
return false;
}
if (typeof node === 'string') {
type = node;
}
else {
type = node.type;
}
if (typeof node === 'string') {
type = node;
} else {
type = node.type;
}
return type === Syntax.FunctionDeclaration || type === Syntax.FunctionExpression ||
type === Syntax.MethodDefinition || type === Syntax.ArrowFunctionExpression;
};
return (
type === Syntax.FunctionDeclaration ||
type === Syntax.FunctionExpression ||
type === Syntax.MethodDefinition ||
type === Syntax.ArrowFunctionExpression
);
});
/**
* Check whether an AST node creates a new scope.
@ -40,513 +43,521 @@ const isFunction = exports.isFunction = node => {
* @param {Object} node - The AST node to check.
* @return {Boolean} Set to `true` if the node creates a new scope, or `false` in all other cases.
*/
exports.isScope = node => // TODO: handle blocks with "let" declarations
Boolean(node) && typeof node === 'object' && (node.type === Syntax.CatchClause ||
node.type === Syntax.ClassDeclaration || node.type === Syntax.ClassExpression || isFunction(node));
exports.isScope = (
node // TODO: handle blocks with "let" declarations
) =>
Boolean(node) &&
typeof node === 'object' &&
(node.type === Syntax.CatchClause ||
node.type === Syntax.ClassDeclaration ||
node.type === Syntax.ClassExpression ||
isFunction(node));
// TODO: docs
exports.addNodeProperties = node => {
const newProperties = {};
exports.addNodeProperties = (node) => {
const newProperties = {};
if (!node || typeof node !== 'object') {
return null;
}
if (!node || typeof node !== 'object') {
return null;
}
if (!node.nodeId) {
newProperties.nodeId = {
value: `astnode${uid++}`,
enumerable: true
};
}
if (!node.nodeId) {
newProperties.nodeId = {
value: `astnode${uid++}`,
enumerable: true,
};
}
if (_.isUndefined(node.parent)) {
newProperties.parent = {
// `null` means 'no parent', so use `undefined` for now
value: undefined,
writable: true
};
}
if (_.isUndefined(node.parent)) {
newProperties.parent = {
// `null` means 'no parent', so use `undefined` for now
value: undefined,
writable: true,
};
}
if (_.isUndefined(node.enclosingScope)) {
newProperties.enclosingScope = {
// `null` means 'no enclosing scope', so use `undefined` for now
value: undefined,
writable: true
};
}
if (_.isUndefined(node.enclosingScope)) {
newProperties.enclosingScope = {
// `null` means 'no enclosing scope', so use `undefined` for now
value: undefined,
writable: true,
};
}
if (_.isUndefined(node.parentId)) {
newProperties.parentId = {
enumerable: true,
get() {
return this.parent ? this.parent.nodeId : null;
}
};
}
if (_.isUndefined(node.parentId)) {
newProperties.parentId = {
enumerable: true,
get() {
return this.parent ? this.parent.nodeId : null;
},
};
}
if (_.isUndefined(node.enclosingScopeId)) {
newProperties.enclosingScopeId = {
enumerable: true,
get() {
return this.enclosingScope ? this.enclosingScope.nodeId : null;
}
};
}
if (_.isUndefined(node.enclosingScopeId)) {
newProperties.enclosingScopeId = {
enumerable: true,
get() {
return this.enclosingScope ? this.enclosingScope.nodeId : null;
},
};
}
Object.defineProperties(node, newProperties);
Object.defineProperties(node, newProperties);
return node;
return node;
};
// TODO: docs
const nodeToValue = exports.nodeToValue = node => {
let key;
let parent;
let str;
let tempObject;
const nodeToValue = (exports.nodeToValue = (node) => {
let key;
let parent;
let str;
let tempObject;
switch (node.type) {
case Syntax.ArrayExpression:
tempObject = [];
node.elements.forEach((el, i) => {
// handle sparse arrays. use `null` to represent missing values, consistent with
// JSON.stringify([,]).
if (!el) {
tempObject[i] = null;
}
else {
tempObject[i] = nodeToValue(el);
}
});
switch (node.type) {
case Syntax.ArrayExpression:
tempObject = [];
node.elements.forEach((el, i) => {
// handle sparse arrays. use `null` to represent missing values, consistent with
// JSON.stringify([,]).
if (!el) {
tempObject[i] = null;
} else {
tempObject[i] = nodeToValue(el);
}
});
str = JSON.stringify(tempObject);
break;
str = JSON.stringify(tempObject);
break;
case Syntax.AssignmentExpression:
// falls through
case Syntax.AssignmentExpression:
// falls through
case Syntax.AssignmentPattern:
str = nodeToValue(node.left);
break;
case Syntax.AssignmentPattern:
str = nodeToValue(node.left);
break;
case Syntax.BigIntLiteral:
str = node.value;
break;
case Syntax.BigIntLiteral:
str = node.value;
break;
case Syntax.ClassDeclaration:
str = nodeToValue(node.id);
break;
case Syntax.ClassDeclaration:
str = nodeToValue(node.id);
break;
case Syntax.ClassPrivateProperty:
// TODO: Strictly speaking, the name should be '#' plus node.key, but because we
// already use '#' as scope punctuation, that causes JSDoc to get extremely confused.
// The solution probably involves quoting part or all of the name, but JSDoc doesn't
// deal with quoted names very nicely right now, and most people probably won't want to
// document class private properties anyhow. So for now, we'll just cheat and omit the
// leading '#'.
str = nodeToValue(node.key.id);
break;
case Syntax.ClassPrivateProperty:
// TODO: Strictly speaking, the name should be '#' plus node.key, but because we
// already use '#' as scope punctuation, that causes JSDoc to get extremely confused.
// The solution probably involves quoting part or all of the name, but JSDoc doesn't
// deal with quoted names very nicely right now, and most people probably won't want to
// document class private properties anyhow. So for now, we'll just cheat and omit the
// leading '#'.
str = nodeToValue(node.key.id);
break;
case Syntax.ClassProperty:
str = nodeToValue(node.key);
break;
case Syntax.ClassProperty:
str = nodeToValue(node.key);
break;
case Syntax.ExportAllDeclaration:
// falls through
case Syntax.ExportAllDeclaration:
// falls through
case Syntax.ExportDefaultDeclaration:
str = 'module.exports';
break;
case Syntax.ExportDefaultDeclaration:
str = 'module.exports';
break;
case Syntax.ExportNamedDeclaration:
if (node.declaration) {
// like `var` in: export var foo = 'bar';
// we need a single value, so we use the first variable name
if (node.declaration.declarations) {
str = `exports.${nodeToValue(node.declaration.declarations[0])}`;
}
else {
str = `exports.${nodeToValue(node.declaration)}`;
}
}
case Syntax.ExportNamedDeclaration:
if (node.declaration) {
// like `var` in: export var foo = 'bar';
// we need a single value, so we use the first variable name
if (node.declaration.declarations) {
str = `exports.${nodeToValue(node.declaration.declarations[0])}`;
} else {
str = `exports.${nodeToValue(node.declaration)}`;
}
}
// otherwise we'll use the ExportSpecifier nodes
break;
// otherwise we'll use the ExportSpecifier nodes
break;
case Syntax.ExportSpecifier:
str = `exports.${nodeToValue(node.exported)}`;
break;
case Syntax.ExportSpecifier:
str = `exports.${nodeToValue(node.exported)}`;
break;
case Syntax.ArrowFunctionExpression:
// falls through
case Syntax.ArrowFunctionExpression:
// falls through
case Syntax.FunctionDeclaration:
// falls through
case Syntax.FunctionDeclaration:
// falls through
case Syntax.FunctionExpression:
if (node.id && node.id.name) {
str = node.id.name;
}
break;
case Syntax.FunctionExpression:
if (node.id && node.id.name) {
str = node.id.name;
}
break;
case Syntax.Identifier:
str = node.name;
break;
case Syntax.Identifier:
str = node.name;
break;
case Syntax.Literal:
str = node.value;
break;
case Syntax.Literal:
str = node.value;
break;
case Syntax.MemberExpression:
// could be computed (like foo['bar']) or not (like foo.bar)
str = nodeToValue(node.object);
if (node.computed) {
str += `[${node.property.raw}]`;
}
else {
str += `.${nodeToValue(node.property)}`;
}
break;
case Syntax.MemberExpression:
// could be computed (like foo['bar']) or not (like foo.bar)
str = nodeToValue(node.object);
if (node.computed) {
str += `[${node.property.raw}]`;
} else {
str += `.${nodeToValue(node.property)}`;
}
break;
case Syntax.MethodDefinition:
parent = node.parent.parent;
// for class expressions, we want the name of the variable the class is assigned to
// (but there won't be a name if the class is returned by an arrow function expression)
// TODO: we should use `LONGNAMES.ANONYMOUS` instead of an empty string, but that
// causes problems downstream if the parent class has an `@alias` tag
if (parent.type === Syntax.ClassExpression) {
str = nodeToValue(parent.parent) || '';
}
// for the constructor of a module's default export, use a special name
else if (node.kind === 'constructor' && parent.parent &&
parent.parent.type === Syntax.ExportDefaultDeclaration) {
str = 'module.exports';
}
// for the constructor of a module's named export, use the name of the export
// declaration
else if (node.kind === 'constructor' && parent.parent &&
parent.parent.type === Syntax.ExportNamedDeclaration) {
str = nodeToValue(parent.parent);
}
// for other constructors, use the name of the parent class
else if (node.kind === 'constructor') {
str = nodeToValue(parent);
}
// if the method is a member of a module's default export, ignore the name, because it's
// irrelevant
else if (parent.parent && parent.parent.type === Syntax.ExportDefaultDeclaration) {
str = '';
}
// otherwise, use the class's name
else {
str = parent.id ? nodeToValue(parent.id) : '';
}
case Syntax.MethodDefinition:
parent = node.parent.parent;
// for class expressions, we want the name of the variable the class is assigned to
// (but there won't be a name if the class is returned by an arrow function expression)
// TODO: we should use `LONGNAMES.ANONYMOUS` instead of an empty string, but that
// causes problems downstream if the parent class has an `@alias` tag
if (parent.type === Syntax.ClassExpression) {
str = nodeToValue(parent.parent) || '';
}
// for the constructor of a module's default export, use a special name
else if (
node.kind === 'constructor' &&
parent.parent &&
parent.parent.type === Syntax.ExportDefaultDeclaration
) {
str = 'module.exports';
}
// for the constructor of a module's named export, use the name of the export
// declaration
else if (
node.kind === 'constructor' &&
parent.parent &&
parent.parent.type === Syntax.ExportNamedDeclaration
) {
str = nodeToValue(parent.parent);
}
// for other constructors, use the name of the parent class
else if (node.kind === 'constructor') {
str = nodeToValue(parent);
}
// if the method is a member of a module's default export, ignore the name, because it's
// irrelevant
else if (parent.parent && parent.parent.type === Syntax.ExportDefaultDeclaration) {
str = '';
}
// otherwise, use the class's name
else {
str = parent.id ? nodeToValue(parent.id) : '';
}
if (node.kind !== 'constructor') {
if (str) {
str += node.static ? SCOPE.PUNC.STATIC : SCOPE.PUNC.INSTANCE;
}
str += nodeToValue(node.key);
}
break;
if (node.kind !== 'constructor') {
if (str) {
str += node.static ? SCOPE.PUNC.STATIC : SCOPE.PUNC.INSTANCE;
}
str += nodeToValue(node.key);
}
break;
case Syntax.ObjectExpression:
tempObject = {};
node.properties.forEach(prop => {
// ExperimentalSpreadProperty have no key
// like var hello = {...hi};
if (!prop.key) {
return;
}
case Syntax.ObjectExpression:
tempObject = {};
node.properties.forEach((prop) => {
// ExperimentalSpreadProperty have no key
// like var hello = {...hi};
if (!prop.key) {
return;
}
key = prop.key.name;
key = prop.key.name;
// preserve literal values so that the JSON form shows the correct type
if (prop.value.type === Syntax.Literal) {
tempObject[key] = prop.value.value;
}
else {
tempObject[key] = nodeToValue(prop);
}
});
// preserve literal values so that the JSON form shows the correct type
if (prop.value.type === Syntax.Literal) {
tempObject[key] = prop.value.value;
} else {
tempObject[key] = nodeToValue(prop);
}
});
str = JSON.stringify(tempObject);
break;
str = JSON.stringify(tempObject);
break;
case Syntax.RestElement:
str = nodeToValue(node.argument);
break;
case Syntax.RestElement:
str = nodeToValue(node.argument);
break;
case Syntax.ThisExpression:
str = 'this';
break;
case Syntax.ThisExpression:
str = 'this';
break;
case Syntax.UnaryExpression:
// like -1. in theory, operator can be prefix or postfix. in practice, any value with a
// valid postfix operator (such as -- or ++) is not a UnaryExpression.
str = nodeToValue(node.argument);
case Syntax.UnaryExpression:
// like -1. in theory, operator can be prefix or postfix. in practice, any value with a
// valid postfix operator (such as -- or ++) is not a UnaryExpression.
str = nodeToValue(node.argument);
if (node.prefix === true) {
str = cast(node.operator + str);
}
else {
// this shouldn't happen
throw new Error(`Found a UnaryExpression with a postfix operator: ${node}`);
}
break;
if (node.prefix === true) {
str = cast(node.operator + str);
} else {
// this shouldn't happen
throw new Error(`Found a UnaryExpression with a postfix operator: ${node}`);
}
break;
case Syntax.VariableDeclarator:
str = nodeToValue(node.id);
break;
case Syntax.VariableDeclarator:
str = nodeToValue(node.id);
break;
default:
str = '';
}
default:
str = '';
}
return str;
};
return str;
});
// backwards compatibility
exports.nodeToString = nodeToValue;
// TODO: docs
const getParamNames = exports.getParamNames = node => {
let params;
const getParamNames = (exports.getParamNames = (node) => {
let params;
if (!node || !node.params) {
return [];
}
if (!node || !node.params) {
return [];
}
params = node.params.slice(0);
params = node.params.slice(0);
return params.map(param => nodeToValue(param));
};
return params.map((param) => nodeToValue(param));
});
// TODO: docs
const isAccessor = exports.isAccessor = node => Boolean(node) && typeof node === 'object' &&
(node.type === Syntax.Property || node.type === Syntax.MethodDefinition) &&
(node.kind === 'get' || node.kind === 'set');
const isAccessor = (exports.isAccessor = (node) =>
Boolean(node) &&
typeof node === 'object' &&
(node.type === Syntax.Property || node.type === Syntax.MethodDefinition) &&
(node.kind === 'get' || node.kind === 'set'));
// TODO: docs
exports.isAssignment = node => Boolean(node) && typeof node === 'object' &&
(node.type === Syntax.AssignmentExpression || node.type === Syntax.VariableDeclarator);
exports.isAssignment = (node) =>
Boolean(node) &&
typeof node === 'object' &&
(node.type === Syntax.AssignmentExpression || node.type === Syntax.VariableDeclarator);
// TODO: docs
/**
* Retrieve information about the node, including its name and type.
*/
exports.getInfo = node => {
const info = {};
exports.getInfo = (node) => {
const info = {};
switch (node.type) {
// like the function in: "var foo = () => {}"
case Syntax.ArrowFunctionExpression:
info.node = node;
info.name = '';
info.type = info.node.type;
info.paramnames = getParamNames(node);
break;
switch (node.type) {
// like the function in: "var foo = () => {}"
case Syntax.ArrowFunctionExpression:
info.node = node;
info.name = '';
info.type = info.node.type;
info.paramnames = getParamNames(node);
break;
// like: "foo = 'bar'" (after declaring foo)
// like: "MyClass.prototype.myMethod = function() {}" (after declaring MyClass)
case Syntax.AssignmentExpression:
info.node = node.right;
info.name = nodeToValue(node.left);
info.type = info.node.type;
info.value = nodeToValue(info.node);
// if the assigned value is a function, we need to capture the parameter names here
info.paramnames = getParamNames(node.right);
break;
// like: "foo = 'bar'" (after declaring foo)
// like: "MyClass.prototype.myMethod = function() {}" (after declaring MyClass)
case Syntax.AssignmentExpression:
info.node = node.right;
info.name = nodeToValue(node.left);
info.type = info.node.type;
info.value = nodeToValue(info.node);
// if the assigned value is a function, we need to capture the parameter names here
info.paramnames = getParamNames(node.right);
break;
// like "bar='baz'" in: function foo(bar='baz') {}
case Syntax.AssignmentPattern:
info.node = node;
info.name = nodeToValue(node.left);
info.type = info.node.type;
info.value = nodeToValue(info.node);
// like "bar='baz'" in: function foo(bar='baz') {}
case Syntax.AssignmentPattern:
info.node = node;
info.name = nodeToValue(node.left);
info.type = info.node.type;
info.value = nodeToValue(info.node);
break;
break;
// like: "class Foo {}"
// or "class" in: "export default class {}"
case Syntax.ClassDeclaration:
info.node = node;
// if this class is the default export, we need to use a special name
if (node.parent && node.parent.type === Syntax.ExportDefaultDeclaration) {
info.name = 'module.exports';
}
else {
info.name = node.id ? nodeToValue(node.id) : '';
}
info.type = info.node.type;
info.paramnames = [];
// like: "class Foo {}"
// or "class" in: "export default class {}"
case Syntax.ClassDeclaration:
info.node = node;
// if this class is the default export, we need to use a special name
if (node.parent && node.parent.type === Syntax.ExportDefaultDeclaration) {
info.name = 'module.exports';
} else {
info.name = node.id ? nodeToValue(node.id) : '';
}
info.type = info.node.type;
info.paramnames = [];
node.body.body.some(({kind, value}) => {
if (kind === 'constructor') {
info.paramnames = getParamNames(value);
node.body.body.some(({ kind, value }) => {
if (kind === 'constructor') {
info.paramnames = getParamNames(value);
return true;
}
return true;
}
return false;
});
return false;
});
break;
break;
// like "#b = 1;" in: "class A { #b = 1; }"
case Syntax.ClassPrivateProperty:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.type;
break;
// like "#b = 1;" in: "class A { #b = 1; }"
case Syntax.ClassPrivateProperty:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.type;
break;
// like "b = 1;" in: "class A { b = 1; }"
case Syntax.ClassProperty:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.type;
break;
// like "b = 1;" in: "class A { b = 1; }"
case Syntax.ClassProperty:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.type;
break;
// like: "export * from 'foo'"
case Syntax.ExportAllDeclaration:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.type;
break;
// like: "export * from 'foo'"
case Syntax.ExportAllDeclaration:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.type;
break;
// like: "export default 'foo'"
case Syntax.ExportDefaultDeclaration:
info.node = node.declaration;
info.name = nodeToValue(node);
info.type = info.node.type;
// like: "export default 'foo'"
case Syntax.ExportDefaultDeclaration:
info.node = node.declaration;
info.name = nodeToValue(node);
info.type = info.node.type;
if ( isFunction(info.node) ) {
info.paramnames = getParamNames(info.node);
}
if (isFunction(info.node)) {
info.paramnames = getParamNames(info.node);
}
break;
break;
// like: "export var foo;" (has declaration)
// or: "export {foo}" (no declaration)
case Syntax.ExportNamedDeclaration:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.declaration ? info.node.declaration.type :
Syntax.ObjectExpression;
// like: "export var foo;" (has declaration)
// or: "export {foo}" (no declaration)
case Syntax.ExportNamedDeclaration:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.declaration ? info.node.declaration.type : Syntax.ObjectExpression;
if (info.node.declaration) {
if ( isFunction(info.node.declaration) ) {
info.paramnames = getParamNames(info.node.declaration);
}
if (info.node.declaration) {
if (isFunction(info.node.declaration)) {
info.paramnames = getParamNames(info.node.declaration);
}
// TODO: This duplicates logic for another node type in `jsdoc/src/visitor` in
// `makeSymbolFoundEvent()`. Is there a way to combine the logic for both node types
// into a single module?
if (info.node.declaration.kind === 'const') {
info.kind = 'constant';
}
}
// TODO: This duplicates logic for another node type in `jsdoc/src/visitor` in
// `makeSymbolFoundEvent()`. Is there a way to combine the logic for both node types
// into a single module?
if (info.node.declaration.kind === 'const') {
info.kind = 'constant';
}
}
break;
break;
// like "foo as bar" in: "export {foo as bar}"
case Syntax.ExportSpecifier:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.local.type;
// like "foo as bar" in: "export {foo as bar}"
case Syntax.ExportSpecifier:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.local.type;
if ( isFunction(info.node.local) ) {
info.paramnames = getParamNames(info.node.local);
}
if (isFunction(info.node.local)) {
info.paramnames = getParamNames(info.node.local);
}
break;
break;
// like: "function foo() {}"
// or the function in: "export default function() {}"
case Syntax.FunctionDeclaration:
info.node = node;
info.name = node.id ? nodeToValue(node.id) : '';
info.type = info.node.type;
info.paramnames = getParamNames(node);
break;
// like: "function foo() {}"
// or the function in: "export default function() {}"
case Syntax.FunctionDeclaration:
info.node = node;
info.name = node.id ? nodeToValue(node.id) : '';
info.type = info.node.type;
info.paramnames = getParamNames(node);
break;
// like the function in: "var foo = function() {}"
case Syntax.FunctionExpression:
info.node = node;
// TODO: should we add a name for, e.g., "var foo = function bar() {}"?
info.name = '';
info.type = info.node.type;
info.paramnames = getParamNames(node);
break;
// like the function in: "var foo = function() {}"
case Syntax.FunctionExpression:
info.node = node;
// TODO: should we add a name for, e.g., "var foo = function bar() {}"?
info.name = '';
info.type = info.node.type;
info.paramnames = getParamNames(node);
break;
// like the param "bar" in: "function foo(bar) {}"
case Syntax.Identifier:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.type;
break;
// like the param "bar" in: "function foo(bar) {}"
case Syntax.Identifier:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.type;
break;
// like "a.b.c"
case Syntax.MemberExpression:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.type;
break;
// like "a.b.c"
case Syntax.MemberExpression:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.type;
break;
// like: "foo() {}"
case Syntax.MethodDefinition:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.type;
info.paramnames = getParamNames(node.value);
break;
// like: "foo() {}"
case Syntax.MethodDefinition:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.type;
info.paramnames = getParamNames(node.value);
break;
// like "a: 0" in "var foo = {a: 0}"
case Syntax.Property:
info.node = node.value;
info.name = nodeToValue(node.key);
info.value = nodeToValue(info.node);
// like "a: 0" in "var foo = {a: 0}"
case Syntax.Property:
info.node = node.value;
info.name = nodeToValue(node.key);
info.value = nodeToValue(info.node);
// property names with unsafe characters must be quoted
if ( !/^[$_a-zA-Z0-9]*$/.test(info.name) ) {
info.name = `"${String(info.name).replace(/"/g, '\\"')}"`;
}
// property names with unsafe characters must be quoted
if (!/^[$_a-zA-Z0-9]*$/.test(info.name)) {
info.name = `"${String(info.name).replace(/"/g, '\\"')}"`;
}
if ( isAccessor(node) ) {
info.type = nodeToValue(info.node);
info.paramnames = getParamNames(info.node);
}
else {
info.type = info.node.type;
}
if (isAccessor(node)) {
info.type = nodeToValue(info.node);
info.paramnames = getParamNames(info.node);
} else {
info.type = info.node.type;
}
break;
break;
// like "...bar" in: function foo(...bar) {}
case Syntax.RestElement:
info.node = node;
info.name = nodeToValue(info.node.argument);
info.type = info.node.type;
// like "...bar" in: function foo(...bar) {}
case Syntax.RestElement:
info.node = node;
info.name = nodeToValue(info.node.argument);
info.type = info.node.type;
break;
break;
// like: "var i = 0" (has init property)
// like: "var i" (no init property)
case Syntax.VariableDeclarator:
info.node = node.init || node.id;
info.name = node.id.name;
// like: "var i = 0" (has init property)
// like: "var i" (no init property)
case Syntax.VariableDeclarator:
info.node = node.init || node.id;
info.name = node.id.name;
if (node.init) {
info.type = info.node.type;
info.value = nodeToValue(info.node);
}
if (node.init) {
info.type = info.node.type;
info.value = nodeToValue(info.node);
}
break;
break;
default:
info.node = node;
info.type = info.node.type;
}
default:
info.node = node;
info.type = info.node.type;
}
return info;
return info;
};

View File

@ -1,96 +1,96 @@
// TODO: docs
exports.Syntax = {
ArrayExpression: 'ArrayExpression',
ArrayPattern: 'ArrayPattern',
ArrowFunctionExpression: 'ArrowFunctionExpression',
AssignmentExpression: 'AssignmentExpression',
AssignmentPattern: 'AssignmentPattern',
AwaitExpression: 'AwaitExpression',
BigIntLiteral: 'BigIntLiteral',
BinaryExpression: 'BinaryExpression',
BindExpression: 'BindExpression',
BlockStatement: 'BlockStatement',
BreakStatement: 'BreakStatement',
CallExpression: 'CallExpression',
CatchClause: 'CatchClause',
ClassBody: 'ClassBody',
ClassDeclaration: 'ClassDeclaration',
ClassExpression: 'ClassExpression',
ClassPrivateProperty: 'ClassPrivateProperty',
ClassProperty: 'ClassProperty',
ComprehensionBlock: 'ComprehensionBlock',
ComprehensionExpression: 'ComprehensionExpression',
ConditionalExpression: 'ConditionalExpression',
ContinueStatement: 'ContinueStatement',
DebuggerStatement: 'DebuggerStatement',
Decorator: 'Decorator',
DoExpression: 'DoExpression',
DoWhileStatement: 'DoWhileStatement',
EmptyStatement: 'EmptyStatement',
ExperimentalRestProperty: 'ExperimentalRestProperty',
ExperimentalSpreadProperty: 'ExperimentalSpreadProperty',
ExportAllDeclaration: 'ExportAllDeclaration',
ExportDefaultDeclaration: 'ExportDefaultDeclaration',
ExportDefaultSpecifier: 'ExportDefaultSpecifier',
ExportNamedDeclaration: 'ExportNamedDeclaration',
ExportNamespaceSpecifier: 'ExportNamespaceSpecifier',
ExportSpecifier: 'ExportSpecifier',
ExpressionStatement: 'ExpressionStatement',
File: 'File',
ForInStatement: 'ForInStatement',
ForOfStatement: 'ForOfStatement',
ForStatement: 'ForStatement',
FunctionDeclaration: 'FunctionDeclaration',
FunctionExpression: 'FunctionExpression',
Identifier: 'Identifier',
IfStatement: 'IfStatement',
Import: 'Import',
ImportDeclaration: 'ImportDeclaration',
ImportDefaultSpecifier: 'ImportDefaultSpecifier',
ImportNamespaceSpecifier: 'ImportNamespaceSpecifier',
ImportSpecifier: 'ImportSpecifier',
JSXAttribute: 'JSXAttribute',
JSXClosingElement: 'JSXClosingElement',
JSXElement: 'JSXElement',
JSXEmptyExpression: 'JSXEmptyExpression',
JSXExpressionContainer: 'JSXExpressionContainer',
JSXIdentifier: 'JSXIdentifier',
JSXMemberExpression: 'JSXMemberExpression',
JSXNamespacedName: 'JSXNamespacedName',
JSXOpeningElement: 'JSXOpeningElement',
JSXSpreadAttribute: 'JSXSpreadAttribute',
JSXText: 'JSXText',
LabeledStatement: 'LabeledStatement',
LetStatement: 'LetStatement',
Literal: 'Literal',
LogicalExpression: 'LogicalExpression',
MemberExpression: 'MemberExpression',
MetaProperty: 'MetaProperty',
MethodDefinition: 'MethodDefinition',
NewExpression: 'NewExpression',
ObjectExpression: 'ObjectExpression',
ObjectPattern: 'ObjectPattern',
PrivateName: 'PrivateName',
Program: 'Program',
Property: 'Property',
RestElement: 'RestElement',
ReturnStatement: 'ReturnStatement',
SequenceExpression: 'SequenceExpression',
SpreadElement: 'SpreadElement',
Super: 'Super',
SwitchCase: 'SwitchCase',
SwitchStatement: 'SwitchStatement',
TaggedTemplateExpression: 'TaggedTemplateExpression',
TemplateElement: 'TemplateElement',
TemplateLiteral: 'TemplateLiteral',
ThisExpression: 'ThisExpression',
ThrowStatement: 'ThrowStatement',
TryStatement: 'TryStatement',
UnaryExpression: 'UnaryExpression',
UpdateExpression: 'UpdateExpression',
VariableDeclaration: 'VariableDeclaration',
VariableDeclarator: 'VariableDeclarator',
WhileStatement: 'WhileStatement',
WithStatement: 'WithStatement',
YieldExpression: 'YieldExpression'
ArrayExpression: 'ArrayExpression',
ArrayPattern: 'ArrayPattern',
ArrowFunctionExpression: 'ArrowFunctionExpression',
AssignmentExpression: 'AssignmentExpression',
AssignmentPattern: 'AssignmentPattern',
AwaitExpression: 'AwaitExpression',
BigIntLiteral: 'BigIntLiteral',
BinaryExpression: 'BinaryExpression',
BindExpression: 'BindExpression',
BlockStatement: 'BlockStatement',
BreakStatement: 'BreakStatement',
CallExpression: 'CallExpression',
CatchClause: 'CatchClause',
ClassBody: 'ClassBody',
ClassDeclaration: 'ClassDeclaration',
ClassExpression: 'ClassExpression',
ClassPrivateProperty: 'ClassPrivateProperty',
ClassProperty: 'ClassProperty',
ComprehensionBlock: 'ComprehensionBlock',
ComprehensionExpression: 'ComprehensionExpression',
ConditionalExpression: 'ConditionalExpression',
ContinueStatement: 'ContinueStatement',
DebuggerStatement: 'DebuggerStatement',
Decorator: 'Decorator',
DoExpression: 'DoExpression',
DoWhileStatement: 'DoWhileStatement',
EmptyStatement: 'EmptyStatement',
ExperimentalRestProperty: 'ExperimentalRestProperty',
ExperimentalSpreadProperty: 'ExperimentalSpreadProperty',
ExportAllDeclaration: 'ExportAllDeclaration',
ExportDefaultDeclaration: 'ExportDefaultDeclaration',
ExportDefaultSpecifier: 'ExportDefaultSpecifier',
ExportNamedDeclaration: 'ExportNamedDeclaration',
ExportNamespaceSpecifier: 'ExportNamespaceSpecifier',
ExportSpecifier: 'ExportSpecifier',
ExpressionStatement: 'ExpressionStatement',
File: 'File',
ForInStatement: 'ForInStatement',
ForOfStatement: 'ForOfStatement',
ForStatement: 'ForStatement',
FunctionDeclaration: 'FunctionDeclaration',
FunctionExpression: 'FunctionExpression',
Identifier: 'Identifier',
IfStatement: 'IfStatement',
Import: 'Import',
ImportDeclaration: 'ImportDeclaration',
ImportDefaultSpecifier: 'ImportDefaultSpecifier',
ImportNamespaceSpecifier: 'ImportNamespaceSpecifier',
ImportSpecifier: 'ImportSpecifier',
JSXAttribute: 'JSXAttribute',
JSXClosingElement: 'JSXClosingElement',
JSXElement: 'JSXElement',
JSXEmptyExpression: 'JSXEmptyExpression',
JSXExpressionContainer: 'JSXExpressionContainer',
JSXIdentifier: 'JSXIdentifier',
JSXMemberExpression: 'JSXMemberExpression',
JSXNamespacedName: 'JSXNamespacedName',
JSXOpeningElement: 'JSXOpeningElement',
JSXSpreadAttribute: 'JSXSpreadAttribute',
JSXText: 'JSXText',
LabeledStatement: 'LabeledStatement',
LetStatement: 'LetStatement',
Literal: 'Literal',
LogicalExpression: 'LogicalExpression',
MemberExpression: 'MemberExpression',
MetaProperty: 'MetaProperty',
MethodDefinition: 'MethodDefinition',
NewExpression: 'NewExpression',
ObjectExpression: 'ObjectExpression',
ObjectPattern: 'ObjectPattern',
PrivateName: 'PrivateName',
Program: 'Program',
Property: 'Property',
RestElement: 'RestElement',
ReturnStatement: 'ReturnStatement',
SequenceExpression: 'SequenceExpression',
SpreadElement: 'SpreadElement',
Super: 'Super',
SwitchCase: 'SwitchCase',
SwitchStatement: 'SwitchStatement',
TaggedTemplateExpression: 'TaggedTemplateExpression',
TemplateElement: 'TemplateElement',
TemplateLiteral: 'TemplateLiteral',
ThisExpression: 'ThisExpression',
ThrowStatement: 'ThrowStatement',
TryStatement: 'TryStatement',
UnaryExpression: 'UnaryExpression',
UpdateExpression: 'UpdateExpression',
VariableDeclaration: 'VariableDeclaration',
VariableDeclarator: 'VariableDeclarator',
WhileStatement: 'WhileStatement',
WithStatement: 'WithStatement',
YieldExpression: 'YieldExpression',
};

View File

@ -1,31 +1,31 @@
const parse = require('../../index');
describe('@jsdoc/parse', () => {
it('is an object', () => {
expect(parse).toBeObject();
it('is an object', () => {
expect(parse).toBeObject();
});
describe('AstBuilder', () => {
it('is lib/ast-builder.AstBuilder', () => {
const { AstBuilder } = require('../../lib/ast-builder');
expect(parse.AstBuilder).toBe(AstBuilder);
});
});
describe('AstBuilder', () => {
it('is lib/ast-builder.AstBuilder', () => {
const { AstBuilder } = require('../../lib/ast-builder');
describe('astNode', () => {
it('is lib/ast-node', () => {
const astNode = require('../../lib/ast-node');
expect(parse.AstBuilder).toBe(AstBuilder);
});
expect(parse.astNode).toBe(astNode);
});
});
describe('astNode', () => {
it('is lib/ast-node', () => {
const astNode = require('../../lib/ast-node');
describe('Syntax', () => {
it('is lib/syntax.Syntax', () => {
const { Syntax } = require('../../lib/syntax');
expect(parse.astNode).toBe(astNode);
});
});
describe('Syntax', () => {
it('is lib/syntax.Syntax', () => {
const { Syntax } = require('../../lib/syntax');
expect(parse.Syntax).toBe(Syntax);
});
expect(parse.Syntax).toBe(Syntax);
});
});
});

View File

@ -1,41 +1,41 @@
/* global jsdoc */
describe('@jsdoc/parse/lib/ast-builder', () => {
const astBuilder = require('../../../lib/ast-builder');
const astBuilder = require('../../../lib/ast-builder');
it('is an object', () => {
expect(astBuilder).toBeObject();
it('is an object', () => {
expect(astBuilder).toBeObject();
});
it('exports an AstBuilder class', () => {
expect(astBuilder.AstBuilder).toBeFunction();
});
it('exports a parserOptions object', () => {
expect(astBuilder.parserOptions).toBeObject();
});
describe('AstBuilder', () => {
const { AstBuilder } = astBuilder;
// TODO: more tests
it('has a "build" static method', () => {
expect(AstBuilder.build).toBeFunction();
});
it('exports an AstBuilder class', () => {
expect(astBuilder.AstBuilder).toBeFunction();
describe('build', () => {
// TODO: more tests
it('logs (not throws) an error when a file cannot be parsed', () => {
function parse() {
AstBuilder.build('qwerty!!!!!', 'bad.js');
}
expect(parse).not.toThrow();
expect(jsdoc.didLog(parse, 'error')).toBeTrue();
});
});
});
it('exports a parserOptions object', () => {
expect(astBuilder.parserOptions).toBeObject();
});
describe('AstBuilder', () => {
const { AstBuilder } = astBuilder;
// TODO: more tests
it('has a "build" static method', () => {
expect(AstBuilder.build).toBeFunction();
});
describe('build', () => {
// TODO: more tests
it('logs (not throws) an error when a file cannot be parsed', () => {
function parse() {
AstBuilder.build('qwerty!!!!!', 'bad.js');
}
expect(parse).not.toThrow();
expect(jsdoc.didLog(parse, 'error')).toBeTrue();
});
});
});
describe('parserOptions', () => {
// TODO: tests
});
describe('parserOptions', () => {
// TODO: tests
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
describe('@jsdoc/parse.Syntax', () => {
const { Syntax } = require('../../../index');
const { Syntax } = require('../../../index');
it('is an object', () => {
expect(Syntax).toBeObject();
});
it('is an object', () => {
expect(Syntax).toBeObject();
});
it('has values identical to their keys', () => {
for (const key of Object.keys(Syntax)) {
expect(key).toBe(Syntax[key]);
}
});
it('has values identical to their keys', () => {
for (const key of Object.keys(Syntax)) {
expect(key).toBe(Syntax[key]);
}
});
});

View File

@ -0,0 +1,14 @@
.editorconfig
.eslintignore
.eslintrc.js
.gitignore
.github/
.renovaterc.json
.travis.yml
CHANGES.md
CODE_OF_CONDUCT.md
CONTRIBUTING.md
gulpfile.js
lerna.json
packages/
test/

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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
http://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.

View File

@ -0,0 +1,3 @@
# `@jsdoc/prettier-config`
A Prettier (https://prettier.io/) configuration for JSDoc.

View File

@ -0,0 +1,5 @@
// https://prettier.io/docs/en/options.html
module.exports = {
printWidth: 100,
singleQuote: true,
};

View File

@ -0,0 +1,31 @@
{
"name": "@jsdoc/prettier-config",
"version": "0.0.1",
"description": "A Prettier (https://prettier.io/) configuration for JSDoc.",
"keywords": [
"prettier",
"jsdoc"
],
"author": "Jeff Williams <jeffrey.l.williams@gmail.com>",
"homepage": "https://github.com/jsdoc/jsdoc",
"license": "Apache-2.0",
"main": "index.js",
"peerDependencies": {
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"prettier": "^2.4.1"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jsdoc/jsdoc.git"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
},
"bugs": {
"url": "https://github.com/jsdoc/jsdoc/issues"
}
}

View File

@ -2,6 +2,6 @@ const inline = require('./lib/inline');
const type = require('./lib/type');
module.exports = {
inline,
type
inline,
type,
};

View File

@ -45,7 +45,7 @@
* @returns {RegExp} A regular expression that matches the requested inline tag.
*/
function regExpFactory(tagName = '\\S+', prefix = '', suffix = '') {
return new RegExp(`${prefix}\\{@${tagName}\\s+((?:.|\n)+?)\\}${suffix}`, 'i');
return new RegExp(`${prefix}\\{@${tagName}\\s+((?:.|\n)+?)\\}${suffix}`, 'i');
}
/**
@ -70,43 +70,43 @@ exports.isInlineTag = (string, tagName) => regExpFactory(tagName, '^', '$').test
* @return {module:@jsdoc/tag.inline.InlineTagResult} The updated string, as well as information
* about the inline tags that were found.
*/
const replaceInlineTags = exports.replaceInlineTags = (string, replacers) => {
const tagInfo = [];
const replaceInlineTags = (exports.replaceInlineTags = (string, replacers) => {
const tagInfo = [];
function replaceMatch(replacer, tag, match, text) {
const matchedTag = {
completeTag: match,
tag: tag,
text: text
};
tagInfo.push(matchedTag);
return replacer(string, matchedTag);
}
string = string || '';
Object.keys(replacers).forEach(replacer => {
const tagRegExp = regExpFactory(replacer);
let matches;
let previousString;
// call the replacer once for each match
do {
matches = tagRegExp.exec(string);
if (matches) {
previousString = string;
string = replaceMatch(replacers[replacer], replacer, matches[0], matches[1]);
}
} while (matches && previousString !== string);
});
return {
tags: tagInfo,
newString: string.trim()
function replaceMatch(replacer, tag, match, text) {
const matchedTag = {
completeTag: match,
tag: tag,
text: text,
};
};
tagInfo.push(matchedTag);
return replacer(string, matchedTag);
}
string = string || '';
Object.keys(replacers).forEach((replacer) => {
const tagRegExp = regExpFactory(replacer);
let matches;
let previousString;
// call the replacer once for each match
do {
matches = tagRegExp.exec(string);
if (matches) {
previousString = string;
string = replaceMatch(replacers[replacer], replacer, matches[0], matches[1]);
}
} while (matches && previousString !== string);
});
return {
tags: tagInfo,
newString: string.trim(),
};
});
/**
* Replace all instances of an inline tag with other text.
@ -118,13 +118,13 @@ const replaceInlineTags = exports.replaceInlineTags = (string, replacers) => {
* @return {module:@jsdoc/tag.inline.InlineTagResult} The updated string, as well as information
* about the inline tags that were found.
*/
const replaceInlineTag = exports.replaceInlineTag = (string, tag, replacer) => {
const replacers = {};
const replaceInlineTag = (exports.replaceInlineTag = (string, tag, replacer) => {
const replacers = {};
replacers[tag] = replacer;
replacers[tag] = replacer;
return replaceInlineTags(string, replacers);
};
return replaceInlineTags(string, replacers);
});
/**
* Extract inline tags from a string, replacing them with an empty string.
@ -135,5 +135,4 @@ const replaceInlineTag = exports.replaceInlineTag = (string, tag, replacer) => {
* about the inline tags that were found.
*/
exports.extractInlineTag = (string, tag) =>
replaceInlineTag(string, tag, (str, {completeTag}) =>
str.replace(completeTag, ''));
replaceInlineTag(string, tag, (str, { completeTag }) => str.replace(completeTag, ''));

View File

@ -18,8 +18,7 @@ const { splitNameAndDescription } = require('@jsdoc/core').name;
/** @private */
function unescapeBraces(text) {
return text.replace(/\\\{/g, '{')
.replace(/\\\}/g, '}');
return text.replace(/\\\{/g, '{').replace(/\\\}/g, '}');
}
/**
@ -30,87 +29,87 @@ function unescapeBraces(text) {
* @return {module:@jsdoc/tag.type.TypeExpressionInfo} The type expression and updated tag text.
*/
function extractTypeExpression(string) {
let completeExpression;
let count = 0;
let position = 0;
let expression = '';
const startIndex = string.search(/\{[^@]/);
let textStartIndex;
let completeExpression;
let count = 0;
let position = 0;
let expression = '';
const startIndex = string.search(/\{[^@]/);
let textStartIndex;
if (startIndex !== -1) {
// advance to the first character in the type expression
position = textStartIndex = startIndex + 1;
count++;
if (startIndex !== -1) {
// advance to the first character in the type expression
position = textStartIndex = startIndex + 1;
count++;
while (position < string.length) {
switch (string[position]) {
case '\\':
// backslash is an escape character, so skip the next character
position++;
break;
case '{':
count++;
break;
case '}':
count--;
break;
default:
// do nothing
}
while (position < string.length) {
switch (string[position]) {
case '\\':
// backslash is an escape character, so skip the next character
position++;
break;
case '{':
count++;
break;
case '}':
count--;
break;
default:
// do nothing
}
if (count === 0) {
completeExpression = string.slice(startIndex, position + 1);
expression = string.slice(textStartIndex, position).trim();
break;
}
if (count === 0) {
completeExpression = string.slice(startIndex, position + 1);
expression = string.slice(textStartIndex, position).trim();
break;
}
position++;
}
position++;
}
}
string = completeExpression ? string.replace(completeExpression, '') : string;
string = completeExpression ? string.replace(completeExpression, '') : string;
return {
expression: unescapeBraces(expression),
newString: string.trim()
};
return {
expression: unescapeBraces(expression),
newString: string.trim(),
};
}
/** @private */
function getTagInfo(tagValue, canHaveName, canHaveType) {
let name = '';
let typeExpression = '';
let text = tagValue;
let expressionAndText;
let nameAndDescription;
let typeOverride;
let name = '';
let typeExpression = '';
let text = tagValue;
let expressionAndText;
let nameAndDescription;
let typeOverride;
if (canHaveType) {
expressionAndText = extractTypeExpression(text);
typeExpression = expressionAndText.expression;
text = expressionAndText.newString;
if (canHaveType) {
expressionAndText = extractTypeExpression(text);
typeExpression = expressionAndText.expression;
text = expressionAndText.newString;
}
if (canHaveName) {
nameAndDescription = splitNameAndDescription(text);
name = nameAndDescription.name;
text = nameAndDescription.description;
}
// an inline @type tag, like {@type Foo}, overrides the type expression
if (canHaveType) {
typeOverride = extractInlineTag(text, 'type');
if (typeOverride.tags && typeOverride.tags[0]) {
typeExpression = typeOverride.tags[0].text;
}
text = typeOverride.newString;
}
if (canHaveName) {
nameAndDescription = splitNameAndDescription(text);
name = nameAndDescription.name;
text = nameAndDescription.description;
}
// an inline @type tag, like {@type Foo}, overrides the type expression
if (canHaveType) {
typeOverride = extractInlineTag(text, 'type');
if (typeOverride.tags && typeOverride.tags[0]) {
typeExpression = typeOverride.tags[0].text;
}
text = typeOverride.newString;
}
return {
name: name,
typeExpression: typeExpression,
text: text
};
return {
name: name,
typeExpression: typeExpression,
text: text,
};
}
/**
@ -142,80 +141,80 @@ function getTagInfo(tagValue, canHaveName, canHaveType) {
* @return {module:@jsdoc/tag.type.TagInfo} Updated information from the tag.
*/
function parseName(tagInfo) {
// like '[foo]' or '[ foo ]' or '[foo=bar]' or '[ foo=bar ]' or '[ foo = bar ]'
// or 'foo=bar' or 'foo = bar'
if ( /^(\[)?\s*(.+?)\s*(\])?$/.test(tagInfo.name) ) {
tagInfo.name = RegExp.$2;
// were the "optional" brackets present?
if (RegExp.$1 && RegExp.$3) {
tagInfo.optional = true;
}
// like 'foo=bar' or 'foo = bar'
if ( /^(.+?)\s*=\s*(.+)$/.test(tagInfo.name) ) {
tagInfo.name = RegExp.$1;
tagInfo.defaultvalue = cast(RegExp.$2);
}
// like '[foo]' or '[ foo ]' or '[foo=bar]' or '[ foo=bar ]' or '[ foo = bar ]'
// or 'foo=bar' or 'foo = bar'
if (/^(\[)?\s*(.+?)\s*(\])?$/.test(tagInfo.name)) {
tagInfo.name = RegExp.$2;
// were the "optional" brackets present?
if (RegExp.$1 && RegExp.$3) {
tagInfo.optional = true;
}
return tagInfo;
// like 'foo=bar' or 'foo = bar'
if (/^(.+?)\s*=\s*(.+)$/.test(tagInfo.name)) {
tagInfo.name = RegExp.$1;
tagInfo.defaultvalue = cast(RegExp.$2);
}
}
return tagInfo;
}
/** @private */
function getTypeStrings(parsedType, isOutermostType) {
let applications;
let typeString;
let applications;
let typeString;
let types = [];
let types = [];
const TYPES = catharsis.Types;
const TYPES = catharsis.Types;
switch (parsedType.type) {
case TYPES.AllLiteral:
types.push('*');
break;
case TYPES.FunctionType:
types.push('function');
break;
case TYPES.NameExpression:
types.push(parsedType.name);
break;
case TYPES.NullLiteral:
types.push('null');
break;
case TYPES.RecordType:
types.push('Object');
break;
case TYPES.TypeApplication:
// if this is the outermost type, we strip the modifiers; otherwise, we keep them
if (isOutermostType) {
applications = parsedType.applications.map(application =>
catharsis.stringify(application)).join(', ');
typeString = `${getTypeStrings(parsedType.expression)[0]}.<${applications}>`;
switch (parsedType.type) {
case TYPES.AllLiteral:
types.push('*');
break;
case TYPES.FunctionType:
types.push('function');
break;
case TYPES.NameExpression:
types.push(parsedType.name);
break;
case TYPES.NullLiteral:
types.push('null');
break;
case TYPES.RecordType:
types.push('Object');
break;
case TYPES.TypeApplication:
// if this is the outermost type, we strip the modifiers; otherwise, we keep them
if (isOutermostType) {
applications = parsedType.applications
.map((application) => catharsis.stringify(application))
.join(', ');
typeString = `${getTypeStrings(parsedType.expression)[0]}.<${applications}>`;
types.push(typeString);
}
else {
types.push( catharsis.stringify(parsedType) );
}
break;
case TYPES.TypeUnion:
parsedType.elements.forEach(element => {
types = types.concat( getTypeStrings(element) );
});
break;
case TYPES.UndefinedLiteral:
types.push('undefined');
break;
case TYPES.UnknownLiteral:
types.push('?');
break;
default:
// this shouldn't happen
throw new Error(`unrecognized type ${parsedType.type} in parsed type: ${parsedType}`);
}
types.push(typeString);
} else {
types.push(catharsis.stringify(parsedType));
}
break;
case TYPES.TypeUnion:
parsedType.elements.forEach((element) => {
types = types.concat(getTypeStrings(element));
});
break;
case TYPES.UndefinedLiteral:
types.push('undefined');
break;
case TYPES.UnknownLiteral:
types.push('?');
break;
default:
// this shouldn't happen
throw new Error(`unrecognized type ${parsedType.type} in parsed type: ${parsedType}`);
}
return types;
return types;
}
/**
@ -227,40 +226,39 @@ function getTypeStrings(parsedType, isOutermostType) {
* @return {module:@jsdoc/tag.type.TagInfo} Updated information from the tag.
*/
function parseTypeExpression(tagInfo) {
let parsedType;
// don't try to parse empty type expressions
if (!tagInfo.typeExpression) {
return tagInfo;
}
try {
parsedType = catharsis.parse(tagInfo.typeExpression, {
jsdoc: true,
useCache: false
});
}
catch (e) {
// always re-throw so the caller has a chance to report which file was bad
throw new Error(`Invalid type expression "${tagInfo.typeExpression}": ${e.message}`);
}
tagInfo.type = tagInfo.type.concat( getTypeStrings(parsedType, true) );
tagInfo.parsedType = parsedType;
// Catharsis and JSDoc use the same names for 'optional' and 'nullable'...
['optional', 'nullable'].forEach(key => {
if (parsedType[key] !== null && parsedType[key] !== undefined) {
tagInfo[key] = parsedType[key];
}
});
// ...but not 'variable'.
if (parsedType.repeatable !== null && parsedType.repeatable !== undefined) {
tagInfo.variable = parsedType.repeatable;
}
let parsedType;
// don't try to parse empty type expressions
if (!tagInfo.typeExpression) {
return tagInfo;
}
try {
parsedType = catharsis.parse(tagInfo.typeExpression, {
jsdoc: true,
useCache: false,
});
} catch (e) {
// always re-throw so the caller has a chance to report which file was bad
throw new Error(`Invalid type expression "${tagInfo.typeExpression}": ${e.message}`);
}
tagInfo.type = tagInfo.type.concat(getTypeStrings(parsedType, true));
tagInfo.parsedType = parsedType;
// Catharsis and JSDoc use the same names for 'optional' and 'nullable'...
['optional', 'nullable'].forEach((key) => {
if (parsedType[key] !== null && parsedType[key] !== undefined) {
tagInfo[key] = parsedType[key];
}
});
// ...but not 'variable'.
if (parsedType.repeatable !== null && parsedType.repeatable !== undefined) {
tagInfo.variable = parsedType.repeatable;
}
return tagInfo;
}
// TODO: allow users to add/remove type parsers (perhaps via plugins)
@ -278,23 +276,23 @@ const typeParsers = [parseName, parseTypeExpression];
* @throws {Error} Thrown if a type expression cannot be parsed.
*/
exports.parse = (tagValue, canHaveName, canHaveType) => {
let tagInfo;
let tagInfo;
if (typeof tagValue !== 'string') {
tagValue = '';
}
if (typeof tagValue !== 'string') {
tagValue = '';
}
tagInfo = getTagInfo(tagValue, canHaveName, canHaveType);
tagInfo.type = tagInfo.type || [];
tagInfo = getTagInfo(tagValue, canHaveName, canHaveType);
tagInfo.type = tagInfo.type || [];
typeParsers.forEach(parser => {
tagInfo = parser(tagInfo);
});
typeParsers.forEach((parser) => {
tagInfo = parser(tagInfo);
});
// if we wanted a type, but the parsers didn't add any type names, use the type expression
if (canHaveType && !tagInfo.type.length && tagInfo.typeExpression) {
tagInfo.type = [tagInfo.typeExpression];
}
// if we wanted a type, but the parsers didn't add any type names, use the type expression
if (canHaveType && !tagInfo.type.length && tagInfo.typeExpression) {
tagInfo.type = [tagInfo.typeExpression];
}
return tagInfo;
return tagInfo;
};

View File

@ -1,23 +1,23 @@
const tag = require('../../index');
describe('@jsdoc/tag', () => {
it('is an object', () => {
expect(tag).toBeObject();
it('is an object', () => {
expect(tag).toBeObject();
});
describe('inline', () => {
it('is lib/inline', () => {
const inline = require('../../lib/inline');
expect(tag.inline).toBe(inline);
});
});
describe('inline', () => {
it('is lib/inline', () => {
const inline = require('../../lib/inline');
describe('type', () => {
it('is lib/type', () => {
const type = require('../../lib/type');
expect(tag.inline).toBe(inline);
});
});
describe('type', () => {
it('is lib/type', () => {
const type = require('../../lib/type');
expect(tag.type).toBe(type);
});
expect(tag.type).toBe(type);
});
});
});

View File

@ -1,256 +1,256 @@
describe('@jsdoc/tag/lib/inline', () => {
const inline = require('../../../lib/inline');
const inline = require('../../../lib/inline');
it('is an object', () => {
expect(inline).toBeObject();
it('is an object', () => {
expect(inline).toBeObject();
});
it('exports an isInlineTag function', () => {
expect(inline.isInlineTag).toBeFunction();
});
it('exports a replaceInlineTag function', () => {
expect(inline.replaceInlineTag).toBeFunction();
});
it('exports an extractInlineTag function', () => {
expect(inline.extractInlineTag).toBeFunction();
});
describe('isInlineTag', () => {
const isInlineTag = inline.isInlineTag;
it('identifies an inline tag', () => {
expect(isInlineTag('{@mytag hooray}', 'mytag')).toBeTrue();
});
it('exports an isInlineTag function', () => {
expect(inline.isInlineTag).toBeFunction();
it('identifies when something is not an inline tag', () => {
expect(isInlineTag('mytag hooray', 'mytag')).toBeFalse();
});
it('exports a replaceInlineTag function', () => {
expect(inline.replaceInlineTag).toBeFunction();
it('reports that a string containing an inline tag is not an inline tag', () => {
expect(isInlineTag('this is {@mytag hooray}', 'mytag')).toBeFalse();
});
it('exports an extractInlineTag function', () => {
expect(inline.extractInlineTag).toBeFunction();
it('allows any inline tag by default', () => {
expect(isInlineTag('{@anyoldtag will do}')).toBeTrue();
});
describe('isInlineTag', () => {
const isInlineTag = inline.isInlineTag;
it('identifies an inline tag', () => {
expect(isInlineTag('{@mytag hooray}', 'mytag')).toBeTrue();
});
it('identifies when something is not an inline tag', () => {
expect(isInlineTag('mytag hooray', 'mytag')).toBeFalse();
});
it('reports that a string containing an inline tag is not an inline tag', () => {
expect(isInlineTag('this is {@mytag hooray}', 'mytag')).toBeFalse();
});
it('allows any inline tag by default', () => {
expect(isInlineTag('{@anyoldtag will do}')).toBeTrue();
});
it('identifies things that are not inline tags when a tag name is not provided', () => {
expect(isInlineTag('mytag hooray')).toBeFalse();
});
it('allows regexp characters in the tag name', () => {
expect( isInlineTag('{@mytags hooray}', 'mytag\\S') ).toBeTrue();
});
it('returns false (rather than throwing) with invalid input', () => {
function badInput() {
return isInlineTag();
}
expect(badInput).not.toThrow();
expect(badInput()).toBeFalse();
});
it('identifies things that are not inline tags when a tag name is not provided', () => {
expect(isInlineTag('mytag hooray')).toBeFalse();
});
describe('replaceInlineTag', () => {
it('throws if the tag is matched and the replacer is invalid', () => {
function badReplacerUndefined() {
inline.replaceInlineTag('{@foo tag}', 'foo');
}
function badReplacerString() {
inline.replaceInlineTag('{@foo tag}', 'foo', 'hello');
}
expect(badReplacerUndefined).toThrow();
expect(badReplacerString).toThrow();
});
it('does not find anything if there is no text in braces', () => {
const replacer = jasmine.createSpy('replacer');
inline.replaceInlineTag('braceless text', 'foo', replacer);
expect(replacer).not.toHaveBeenCalled();
});
it('copes with bad escapement at the end of the string', () => {
const replacer = jasmine.createSpy('replacer');
inline.replaceInlineTag('bad {@foo escapement \\', 'foo', replacer);
expect(replacer).not.toHaveBeenCalled();
});
it('works if the tag is the entire string', () => {
function replacer(string, {completeTag, text}) {
expect(string).toBe('{@foo text in braces}');
expect(completeTag).toBe('{@foo text in braces}');
expect(text).toBe('text in braces');
return completeTag;
}
const result = inline.replaceInlineTag('{@foo text in braces}', 'foo',
replacer);
expect(result.tags[0]).toBeObject();
expect(result.tags[0].tag).toBe('foo');
expect(result.tags[0].text).toBe('text in braces');
expect(result.newString).toBe('{@foo text in braces}');
});
it('works if the tag is at the beginning of the string', () => {
function replacer(string, {completeTag, text}) {
expect(string).toBe('{@foo test string} ahoy');
expect(completeTag).toBe('{@foo test string}');
expect(text).toBe('test string');
return string;
}
const result = inline.replaceInlineTag('{@foo test string} ahoy', 'foo',
replacer);
expect(result.tags[0]).toBeObject();
expect(result.tags[0].tag).toBe('foo');
expect(result.tags[0].text).toBe('test string');
expect(result.newString).toBe('{@foo test string} ahoy');
});
it('works if the tag is in the middle of the string', () => {
function replacer(string, {completeTag, text}) {
expect(string).toBe('a {@foo test string} yay');
expect(completeTag).toBe('{@foo test string}');
expect(text).toBe('test string');
return string;
}
const result = inline.replaceInlineTag('a {@foo test string} yay', 'foo',
replacer);
expect(result.tags[0]).toBeObject();
expect(result.tags[0].tag).toBe('foo');
expect(result.tags[0].text).toBe('test string');
expect(result.newString).toBe('a {@foo test string} yay');
});
it('works if the tag is at the end of the string', () => {
function replacer(string, {completeTag, text}) {
expect(string).toBe('a {@foo test string}');
expect(completeTag).toBe('{@foo test string}');
expect(text).toBe('test string');
return string;
}
const result = inline.replaceInlineTag('a {@foo test string}', 'foo', replacer);
expect(result.tags[0]).toBeObject();
expect(result.tags[0].tag).toBe('foo');
expect(result.tags[0].text).toBe('test string');
expect(result.newString).toBe('a {@foo test string}');
});
it('replaces the string with the specified value', () => {
function replacer() {
return 'REPLACED!';
}
const result = inline.replaceInlineTag('a {@foo test string}', 'foo', replacer);
expect(result.newString).toBe('REPLACED!');
});
it('processes all occurrences of a tag', () => {
function replacer(string, {completeTag}) {
return string.replace(completeTag, 'stuff');
}
const result = inline.replaceInlineTag('some {@foo text} with multiple ' +
'{@foo tags}, {@foo like} {@foo this}', 'foo', replacer);
expect(result.tags.length).toBe(4);
expect(result.tags[0]).toBeObject();
expect(result.tags[0].tag).toBe('foo');
expect(result.tags[0].text).toBe('text');
expect(result.tags[1]).toBeObject();
expect(result.tags[1].tag).toBe('foo');
expect(result.tags[1].text).toBe('tags');
expect(result.tags[2]).toBeObject();
expect(result.tags[2].tag).toBe('foo');
expect(result.tags[2].text).toBe('like');
expect(result.tags[3]).toBeObject();
expect(result.tags[3].tag).toBe('foo');
expect(result.tags[3].text).toBe('this');
expect(result.newString).toBe('some stuff with multiple stuff, stuff stuff');
});
it('allows regexp characters in the tag name', () => {
expect(isInlineTag('{@mytags hooray}', 'mytag\\S')).toBeTrue();
});
// Largely covered by the `replaceInlineTag()` tests.
describe('replaceInlineTags', () => {
it('works with an empty replacer object', () => {
const replacers = {};
const text = 'some {@foo text} to parse';
const result = inline.replaceInlineTags(text, replacers);
it('returns false (rather than throwing) with invalid input', () => {
function badInput() {
return isInlineTag();
}
expect(result.newString).toBe(text);
});
expect(badInput).not.toThrow();
expect(badInput()).toBeFalse();
});
});
it('works with one replacer', () => {
const text = 'some {@foo text} with {@bar multiple} tags';
const replacers = {
foo(string, tagInfo) {
expect(tagInfo.completeTag).toBe('{@foo text}');
expect(tagInfo.text).toBe('text');
describe('replaceInlineTag', () => {
it('throws if the tag is matched and the replacer is invalid', () => {
function badReplacerUndefined() {
inline.replaceInlineTag('{@foo tag}', 'foo');
}
return string.replace(tagInfo.completeTag, 'stuff');
}
};
const result = inline.replaceInlineTags(text, replacers);
function badReplacerString() {
inline.replaceInlineTag('{@foo tag}', 'foo', 'hello');
}
expect(result.newString).toBe('some stuff with {@bar multiple} tags');
});
it('works with multiple replacers', () => {
const text = 'some {@foo text} with {@bar multiple} tags';
const replacers = {
foo(string, tagInfo) {
expect(tagInfo.completeTag).toBe('{@foo text}');
expect(tagInfo.text).toBe('text');
return string.replace(tagInfo.completeTag, 'stuff');
},
bar(string, tagInfo) {
expect(tagInfo.completeTag).toBe('{@bar multiple}');
expect(tagInfo.text).toBe('multiple');
return string.replace(tagInfo.completeTag, 'awesome');
}
};
const result = inline.replaceInlineTags(text, replacers);
expect(result.newString).toBe('some stuff with awesome tags');
});
expect(badReplacerUndefined).toThrow();
expect(badReplacerString).toThrow();
});
// Largely covered by the `replaceInlineTag()` tests.
describe('extractInlineTag', () => {
it('works when a tag is specified', () => {
const result = inline.extractInlineTag('some {@tagged text}', 'tagged');
it('does not find anything if there is no text in braces', () => {
const replacer = jasmine.createSpy('replacer');
expect(result.tags[0]).toBeObject();
expect(result.tags[0].tag).toBe('tagged');
expect(result.tags[0].text).toBe('text');
expect(result.newString).toBe('some');
});
inline.replaceInlineTag('braceless text', 'foo', replacer);
expect(replacer).not.toHaveBeenCalled();
});
it('copes with bad escapement at the end of the string', () => {
const replacer = jasmine.createSpy('replacer');
inline.replaceInlineTag('bad {@foo escapement \\', 'foo', replacer);
expect(replacer).not.toHaveBeenCalled();
});
it('works if the tag is the entire string', () => {
function replacer(string, { completeTag, text }) {
expect(string).toBe('{@foo text in braces}');
expect(completeTag).toBe('{@foo text in braces}');
expect(text).toBe('text in braces');
return completeTag;
}
const result = inline.replaceInlineTag('{@foo text in braces}', 'foo', replacer);
expect(result.tags[0]).toBeObject();
expect(result.tags[0].tag).toBe('foo');
expect(result.tags[0].text).toBe('text in braces');
expect(result.newString).toBe('{@foo text in braces}');
});
it('works if the tag is at the beginning of the string', () => {
function replacer(string, { completeTag, text }) {
expect(string).toBe('{@foo test string} ahoy');
expect(completeTag).toBe('{@foo test string}');
expect(text).toBe('test string');
return string;
}
const result = inline.replaceInlineTag('{@foo test string} ahoy', 'foo', replacer);
expect(result.tags[0]).toBeObject();
expect(result.tags[0].tag).toBe('foo');
expect(result.tags[0].text).toBe('test string');
expect(result.newString).toBe('{@foo test string} ahoy');
});
it('works if the tag is in the middle of the string', () => {
function replacer(string, { completeTag, text }) {
expect(string).toBe('a {@foo test string} yay');
expect(completeTag).toBe('{@foo test string}');
expect(text).toBe('test string');
return string;
}
const result = inline.replaceInlineTag('a {@foo test string} yay', 'foo', replacer);
expect(result.tags[0]).toBeObject();
expect(result.tags[0].tag).toBe('foo');
expect(result.tags[0].text).toBe('test string');
expect(result.newString).toBe('a {@foo test string} yay');
});
it('works if the tag is at the end of the string', () => {
function replacer(string, { completeTag, text }) {
expect(string).toBe('a {@foo test string}');
expect(completeTag).toBe('{@foo test string}');
expect(text).toBe('test string');
return string;
}
const result = inline.replaceInlineTag('a {@foo test string}', 'foo', replacer);
expect(result.tags[0]).toBeObject();
expect(result.tags[0].tag).toBe('foo');
expect(result.tags[0].text).toBe('test string');
expect(result.newString).toBe('a {@foo test string}');
});
it('replaces the string with the specified value', () => {
function replacer() {
return 'REPLACED!';
}
const result = inline.replaceInlineTag('a {@foo test string}', 'foo', replacer);
expect(result.newString).toBe('REPLACED!');
});
it('processes all occurrences of a tag', () => {
function replacer(string, { completeTag }) {
return string.replace(completeTag, 'stuff');
}
const result = inline.replaceInlineTag(
'some {@foo text} with multiple ' + '{@foo tags}, {@foo like} {@foo this}',
'foo',
replacer
);
expect(result.tags.length).toBe(4);
expect(result.tags[0]).toBeObject();
expect(result.tags[0].tag).toBe('foo');
expect(result.tags[0].text).toBe('text');
expect(result.tags[1]).toBeObject();
expect(result.tags[1].tag).toBe('foo');
expect(result.tags[1].text).toBe('tags');
expect(result.tags[2]).toBeObject();
expect(result.tags[2].tag).toBe('foo');
expect(result.tags[2].text).toBe('like');
expect(result.tags[3]).toBeObject();
expect(result.tags[3].tag).toBe('foo');
expect(result.tags[3].text).toBe('this');
expect(result.newString).toBe('some stuff with multiple stuff, stuff stuff');
});
});
// Largely covered by the `replaceInlineTag()` tests.
describe('replaceInlineTags', () => {
it('works with an empty replacer object', () => {
const replacers = {};
const text = 'some {@foo text} to parse';
const result = inline.replaceInlineTags(text, replacers);
expect(result.newString).toBe(text);
});
it('works with one replacer', () => {
const text = 'some {@foo text} with {@bar multiple} tags';
const replacers = {
foo(string, tagInfo) {
expect(tagInfo.completeTag).toBe('{@foo text}');
expect(tagInfo.text).toBe('text');
return string.replace(tagInfo.completeTag, 'stuff');
},
};
const result = inline.replaceInlineTags(text, replacers);
expect(result.newString).toBe('some stuff with {@bar multiple} tags');
});
it('works with multiple replacers', () => {
const text = 'some {@foo text} with {@bar multiple} tags';
const replacers = {
foo(string, tagInfo) {
expect(tagInfo.completeTag).toBe('{@foo text}');
expect(tagInfo.text).toBe('text');
return string.replace(tagInfo.completeTag, 'stuff');
},
bar(string, tagInfo) {
expect(tagInfo.completeTag).toBe('{@bar multiple}');
expect(tagInfo.text).toBe('multiple');
return string.replace(tagInfo.completeTag, 'awesome');
},
};
const result = inline.replaceInlineTags(text, replacers);
expect(result.newString).toBe('some stuff with awesome tags');
});
});
// Largely covered by the `replaceInlineTag()` tests.
describe('extractInlineTag', () => {
it('works when a tag is specified', () => {
const result = inline.extractInlineTag('some {@tagged text}', 'tagged');
expect(result.tags[0]).toBeObject();
expect(result.tags[0].tag).toBe('tagged');
expect(result.tags[0].text).toBe('text');
expect(result.newString).toBe('some');
});
});
});

View File

@ -1,266 +1,266 @@
function buildText(type, name, desc) {
let text = '';
let text = '';
if (type) {
text += `{${type}}`;
if (name || desc) {
text += ' ';
}
}
if (name) {
text += name;
if (desc) {
text += ' ';
}
if (type) {
text += `{${type}}`;
if (name || desc) {
text += ' ';
}
}
if (name) {
text += name;
if (desc) {
text += desc;
text += ' ';
}
}
return text;
if (desc) {
text += desc;
}
return text;
}
describe('@jsdoc/tag/lib/type', () => {
const type = require('../../../lib/type');
const type = require('../../../lib/type');
it('is an object', () => {
expect(type).toBeObject();
it('is an object', () => {
expect(type).toBeObject();
});
it('exports a parse function', () => {
expect(type.parse).toBeFunction();
});
describe('parse', () => {
it('returns an object with name, type, and text properties', () => {
const info = type.parse('');
expect(info.name).toBeString();
expect(info.type).toBeArray();
expect(info.text).toBeString();
});
it('exports a parse function', () => {
expect(type.parse).toBeFunction();
it('does not extract a name or type if canHaveName and canHaveType are not set', () => {
const desc = '{number} foo The foo parameter.';
const info = type.parse(desc);
expect(info.type).toBeEmptyArray();
expect(info.name).toBe('');
expect(info.text).toBe(desc);
});
describe('parse', () => {
it('returns an object with name, type, and text properties', () => {
const info = type.parse('');
it('extracts a name, but not a type, if canHaveName is true and canHaveType is false', () => {
const name = 'bar';
const desc = 'The bar parameter.';
const info = type.parse(buildText(null, name, desc), true, false);
expect(info.name).toBeString();
expect(info.type).toBeArray();
expect(info.text).toBeString();
});
it('does not extract a name or type if canHaveName and canHaveType are not set', () => {
const desc = '{number} foo The foo parameter.';
const info = type.parse(desc);
expect(info.type).toBeEmptyArray();
expect(info.name).toBe('');
expect(info.text).toBe(desc);
});
it('extracts a name, but not a type, if canHaveName is true and canHaveType is false', () => {
const name = 'bar';
const desc = 'The bar parameter.';
const info = type.parse( buildText(null, name, desc), true, false );
expect(info.type).toBeEmptyArray();
expect(info.name).toBe(name);
expect(info.text).toBe(desc);
});
it('extracts a type, but not a name, if canHaveName is false and canHaveType is true', () => {
const typeString = 'boolean';
const desc = 'Set to true on alternate Thursdays.';
const info = type.parse(buildText(typeString, null, desc), false, true);
expect(info.type).toEqual([typeString]);
expect(info.name).toBe('');
expect(info.text).toBe(desc);
});
it('extracts a name and type if canHaveName and canHaveType are true', () => {
const typeString = 'string';
const name = 'baz';
const desc = 'The baz parameter.';
const info = type.parse(buildText(typeString, name, desc), true, true);
expect(info.type).toEqual([typeString]);
expect(info.name).toBe(name);
expect(info.text).toBe(desc);
});
it('reports optional types correctly for both JSDoc and Closure syntax', () => {
let desc = '{string} [foo]';
let info = type.parse(desc, true, true);
expect(info.optional).toBeTrue();
desc = '{string=} [foo]';
info = type.parse(desc, true, true);
expect(info.optional).toBeTrue();
desc = '[foo]';
info = type.parse(desc, true, true);
expect(info.optional).toBeTrue();
});
it('returnsthe types as an array', () => {
const desc = '{string} foo';
const info = type.parse(desc, true, true);
expect(info.type).toEqual( ['string'] );
});
it('recognizes the entire list of possible types', () => {
let desc = '{(string|number)} foo';
let info = type.parse(desc, true, true);
expect(info.type).toEqual( ['string', 'number'] );
desc = '{ ( string | number ) } foo';
info = type.parse(desc, true, true);
expect(info.type).toEqual( ['string', 'number'] );
desc = '{ ( string | number)} foo';
info = type.parse(desc, true, true);
expect(info.type).toEqual( ['string', 'number'] );
desc = '{(string|number|boolean|function)} foo';
info = type.parse(desc, true, true);
expect(info.type).toEqual( ['string', 'number', 'boolean', 'function'] );
});
it('does not find any type if there is no text in braces', () => {
const desc = 'braceless text';
const info = type.parse(desc, false, true);
expect(info.type).toBeEmptyArray();
});
it('copes with bad escapement at the end of the string', () => {
const desc = 'bad {escapement \\';
const info = type.parse(desc, false, true);
expect(info.type).toBeEmptyArray();
expect(info.text).toBe(desc);
});
it('handles escaped braces correctly', () => {
const desc = '{weirdObject."with\\}AnnoyingProperty"}';
const info = type.parse(desc, false, true);
expect(info.type[0]).toBe('weirdObject."with}AnnoyingProperty"');
});
it('works if the type expression is the entire string', () => {
const desc = '{textInBraces}';
const info = type.parse(desc, false, true);
expect(info.type[0]).toBe('textInBraces');
});
it('works if the type expression is at the beginning of the string', () => {
const desc = '{testString} ahoy';
const info = type.parse(desc, false, true);
expect(info.type[0]).toBe('testString');
expect(info.text).toBe('ahoy');
});
it('works if the type expression is in the middle of the string', () => {
const desc = 'a {testString} yay';
const info = type.parse(desc, false, true);
expect(info.type[0]).toBe('testString');
expect(info.text).toBe('a yay');
});
it('works if the tag is at the end of the string', () => {
const desc = 'a {testString}';
const info = type.parse(desc, false, true);
expect(info.type[0]).toBe('testString');
expect(info.text).toBe('a');
});
it('works when there are nested braces', () => {
const desc = 'some {{double}} braces';
const info = type.parse(desc, false, true);
// we currently stringify all record types as 'Object'
expect(info.type[0]).toBe('Object');
expect(info.text).toBe('some braces');
});
it('overrides the type expression if an inline @type tag is specified', () => {
let desc = '{Object} cookie {@type Monster}';
let info = type.parse(desc, true, true);
expect(info.type).toEqual( ['Monster'] );
expect(info.text).toBe('');
desc = '{Object} cookie - {@type Monster}';
info = type.parse(desc, true, true);
expect(info.type).toEqual( ['Monster'] );
expect(info.text).toBe('');
desc = '{Object} cookie - The cookie parameter. {@type Monster}';
info = type.parse(desc, true, true);
expect(info.type).toEqual( ['Monster'] );
expect(info.text).toBe('The cookie parameter.');
desc = '{Object} cookie - The cookie parameter. {@type (Monster|Jar)}';
info = type.parse(desc, true, true);
expect(info.type).toEqual( ['Monster', 'Jar'] );
expect(info.text).toBe('The cookie parameter.');
desc = '{Object} cookie - The cookie parameter. {@type (Monster|Jar)} Mmm, cookie.';
info = type.parse(desc, true, true);
expect(info.type).toEqual( ['Monster', 'Jar'] );
expect(info.text).toBe('The cookie parameter. Mmm, cookie.');
});
describe('JSDoc-style type info', () => {
it('parses JSDoc-style optional parameters', () => {
let name = '[qux]';
const desc = 'The qux parameter.';
let info = type.parse( buildText(null, name, desc), true, false );
expect(info.name).toBe('qux');
expect(info.text).toBe(desc);
expect(info.optional).toBeTrue();
name = '[ qux ]';
info = type.parse( buildText(null, name, desc), true, false );
expect(info.name).toBe('qux');
expect(info.text).toBe(desc);
expect(info.optional).toBeTrue();
name = '[qux=hooray]';
info = type.parse( buildText(null, name, desc), true, false );
expect(info.name).toBe('qux');
expect(info.text).toBe(desc);
expect(info.optional).toBeTrue();
expect(info.defaultvalue).toBe('hooray');
name = '[ qux = hooray ]';
info = type.parse( buildText(null, name, desc), true, false );
expect(info.name).toBe('qux');
expect(info.text).toBe(desc);
expect(info.optional).toBeTrue();
expect(info.defaultvalue).toBe('hooray');
});
});
// TODO: Add more tests related to how JSDoc mangles the Catharsis parse results.
describe('Closure Compiler-style type info', () => {
it('recognizes variable (repeatable) parameters', () => {
const desc = '{...string} foo - Foo.';
const info = type.parse(desc, true, true);
expect(info.type).toEqual( ['string'] );
expect(info.variable).toBeTrue();
});
it('sets the type correctly for type applications that contain type unions', () => {
const desc = '{Array.<(string|number)>} foo - Foo.';
const info = type.parse(desc, true, true);
expect(info.type).toEqual(['Array.<(string|number)>']);
});
});
expect(info.type).toBeEmptyArray();
expect(info.name).toBe(name);
expect(info.text).toBe(desc);
});
it('extracts a type, but not a name, if canHaveName is false and canHaveType is true', () => {
const typeString = 'boolean';
const desc = 'Set to true on alternate Thursdays.';
const info = type.parse(buildText(typeString, null, desc), false, true);
expect(info.type).toEqual([typeString]);
expect(info.name).toBe('');
expect(info.text).toBe(desc);
});
it('extracts a name and type if canHaveName and canHaveType are true', () => {
const typeString = 'string';
const name = 'baz';
const desc = 'The baz parameter.';
const info = type.parse(buildText(typeString, name, desc), true, true);
expect(info.type).toEqual([typeString]);
expect(info.name).toBe(name);
expect(info.text).toBe(desc);
});
it('reports optional types correctly for both JSDoc and Closure syntax', () => {
let desc = '{string} [foo]';
let info = type.parse(desc, true, true);
expect(info.optional).toBeTrue();
desc = '{string=} [foo]';
info = type.parse(desc, true, true);
expect(info.optional).toBeTrue();
desc = '[foo]';
info = type.parse(desc, true, true);
expect(info.optional).toBeTrue();
});
it('returnsthe types as an array', () => {
const desc = '{string} foo';
const info = type.parse(desc, true, true);
expect(info.type).toEqual(['string']);
});
it('recognizes the entire list of possible types', () => {
let desc = '{(string|number)} foo';
let info = type.parse(desc, true, true);
expect(info.type).toEqual(['string', 'number']);
desc = '{ ( string | number ) } foo';
info = type.parse(desc, true, true);
expect(info.type).toEqual(['string', 'number']);
desc = '{ ( string | number)} foo';
info = type.parse(desc, true, true);
expect(info.type).toEqual(['string', 'number']);
desc = '{(string|number|boolean|function)} foo';
info = type.parse(desc, true, true);
expect(info.type).toEqual(['string', 'number', 'boolean', 'function']);
});
it('does not find any type if there is no text in braces', () => {
const desc = 'braceless text';
const info = type.parse(desc, false, true);
expect(info.type).toBeEmptyArray();
});
it('copes with bad escapement at the end of the string', () => {
const desc = 'bad {escapement \\';
const info = type.parse(desc, false, true);
expect(info.type).toBeEmptyArray();
expect(info.text).toBe(desc);
});
it('handles escaped braces correctly', () => {
const desc = '{weirdObject."with\\}AnnoyingProperty"}';
const info = type.parse(desc, false, true);
expect(info.type[0]).toBe('weirdObject."with}AnnoyingProperty"');
});
it('works if the type expression is the entire string', () => {
const desc = '{textInBraces}';
const info = type.parse(desc, false, true);
expect(info.type[0]).toBe('textInBraces');
});
it('works if the type expression is at the beginning of the string', () => {
const desc = '{testString} ahoy';
const info = type.parse(desc, false, true);
expect(info.type[0]).toBe('testString');
expect(info.text).toBe('ahoy');
});
it('works if the type expression is in the middle of the string', () => {
const desc = 'a {testString} yay';
const info = type.parse(desc, false, true);
expect(info.type[0]).toBe('testString');
expect(info.text).toBe('a yay');
});
it('works if the tag is at the end of the string', () => {
const desc = 'a {testString}';
const info = type.parse(desc, false, true);
expect(info.type[0]).toBe('testString');
expect(info.text).toBe('a');
});
it('works when there are nested braces', () => {
const desc = 'some {{double}} braces';
const info = type.parse(desc, false, true);
// we currently stringify all record types as 'Object'
expect(info.type[0]).toBe('Object');
expect(info.text).toBe('some braces');
});
it('overrides the type expression if an inline @type tag is specified', () => {
let desc = '{Object} cookie {@type Monster}';
let info = type.parse(desc, true, true);
expect(info.type).toEqual(['Monster']);
expect(info.text).toBe('');
desc = '{Object} cookie - {@type Monster}';
info = type.parse(desc, true, true);
expect(info.type).toEqual(['Monster']);
expect(info.text).toBe('');
desc = '{Object} cookie - The cookie parameter. {@type Monster}';
info = type.parse(desc, true, true);
expect(info.type).toEqual(['Monster']);
expect(info.text).toBe('The cookie parameter.');
desc = '{Object} cookie - The cookie parameter. {@type (Monster|Jar)}';
info = type.parse(desc, true, true);
expect(info.type).toEqual(['Monster', 'Jar']);
expect(info.text).toBe('The cookie parameter.');
desc = '{Object} cookie - The cookie parameter. {@type (Monster|Jar)} Mmm, cookie.';
info = type.parse(desc, true, true);
expect(info.type).toEqual(['Monster', 'Jar']);
expect(info.text).toBe('The cookie parameter. Mmm, cookie.');
});
describe('JSDoc-style type info', () => {
it('parses JSDoc-style optional parameters', () => {
let name = '[qux]';
const desc = 'The qux parameter.';
let info = type.parse(buildText(null, name, desc), true, false);
expect(info.name).toBe('qux');
expect(info.text).toBe(desc);
expect(info.optional).toBeTrue();
name = '[ qux ]';
info = type.parse(buildText(null, name, desc), true, false);
expect(info.name).toBe('qux');
expect(info.text).toBe(desc);
expect(info.optional).toBeTrue();
name = '[qux=hooray]';
info = type.parse(buildText(null, name, desc), true, false);
expect(info.name).toBe('qux');
expect(info.text).toBe(desc);
expect(info.optional).toBeTrue();
expect(info.defaultvalue).toBe('hooray');
name = '[ qux = hooray ]';
info = type.parse(buildText(null, name, desc), true, false);
expect(info.name).toBe('qux');
expect(info.text).toBe(desc);
expect(info.optional).toBeTrue();
expect(info.defaultvalue).toBe('hooray');
});
});
// TODO: Add more tests related to how JSDoc mangles the Catharsis parse results.
describe('Closure Compiler-style type info', () => {
it('recognizes variable (repeatable) parameters', () => {
const desc = '{...string} foo - Foo.';
const info = type.parse(desc, true, true);
expect(info.type).toEqual(['string']);
expect(info.variable).toBeTrue();
});
it('sets the type correctly for type applications that contain type unions', () => {
const desc = '{Array.<(string|number)>} foo - Foo.';
const info = type.parse(desc, true, true);
expect(info.type).toEqual(['Array.<(string|number)>']);
});
});
});
});

View File

@ -2,6 +2,6 @@ const Task = require('./lib/task');
const TaskRunner = require('./lib/task-runner');
module.exports = {
Task,
TaskRunner
Task,
TaskRunner,
};

View File

@ -1,7 +1,7 @@
const _ = require('lodash');
const { DepGraph } = require('dependency-graph');
const Emittery = require('emittery');
const {default: ow} = require('ow');
const { default: ow } = require('ow');
const Queue = require('p-queue').default;
const v = require('./validators');
@ -9,319 +9,320 @@ const v = require('./validators');
DepGraph.prototype.dependentsOf = DepGraph.prototype.dependantsOf;
module.exports = class TaskRunner extends Emittery {
constructor(context) {
super();
constructor(context) {
super();
ow(context, ow.optional.object);
ow(context, ow.optional.object);
this._init(context);
}
this._init(context);
}
_addOrRemoveTasks(tasks, func, action) {
func = _.bind(func, this);
_addOrRemoveTasks(tasks, func, action) {
func = _.bind(func, this);
if (Array.isArray(tasks)) {
tasks.forEach((task, i) => {
try {
func(task);
} catch (e) {
e.message = `Can't ${action} task ${i}: ${e.message}`;
throw e;
}
});
} else if (tasks !== null && typeof tasks === 'object') {
for (const task of Object.keys(tasks)) {
try {
func(tasks[task]);
} catch (e) {
e.message = `Can't ${action} task "${task}": ${e.message}`;
throw e;
}
}
}
}
_addTaskEmitters(task) {
const u = {};
u.start = task.on('start', t => this.emit('taskStart', t));
u.end = task.on('end', t => this.emit('taskEnd', t));
u.error = task.on('error', (e => {
this.emit('taskError', {
task: e.task,
error: e.error
});
if (!this._error) {
this._error = e.error;
}
}));
this._unsubscribers.set(task.name, u);
}
_bindTaskFunc(task) {
return _.bind(task.run, task, this._context);
}
_createTaskSequence(tasks) {
if (!tasks.length) {
return null;
}
return () => tasks.reduce((p, taskName) => {
const task = this._nameToTask.get(taskName);
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();
this._queue.pause();
}
_newDependencyCycleError(cyclePath) {
return new v.DependencyCycleError(
`Tasks have circular dependencies: ${cyclePath.join(' > ')}`,
cyclePath
);
}
_newStateError() {
return new v.StateError('The task runner is already running.');
}
_newUnknownDepsError(dependent, unknownDeps) {
let errorText;
if (unknownDeps.length === 1) {
errorText = 'an unknown task';
} else {
errorText = 'unknown tasks';
}
return new v.UnknownDependencyError(`The task ${dependent} depends on ${errorText}: ` +
`${unknownDeps.join(', ')}`);
}
_orderTasks() {
let error;
const graph = new DepGraph();
let parallel;
let sequential;
for (const [task] of this._nameToTask) {
graph.addNode(task);
}
for (const [dependent] of this._deps) {
const unknownDeps = [];
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);
break;
}
}
}
if (!error) {
try {
// Get standalone tasks with no dependencies and no dependents.
parallel = graph.overallOrder(true)
.filter(task => !(graph.dependentsOf(task).length));
// Get tasks with dependencies, in a correctly ordered list.
sequential = graph.overallOrder().filter(task => !parallel.includes(task));
} catch (e) {
error = this._newDependencyCycleError(e.cyclePath);
}
}
return {
error,
parallel,
sequential
};
}
_rejectIfRunning() {
if (this.running) {
return Promise.reject(this._newStateError());
}
return null;
}
_throwIfRunning() {
if (this.running) {
throw this._newStateError();
}
}
_throwIfUnknownDeps(dependent, unknownDeps) {
if (!unknownDeps.length) {
return;
}
throw this._newUnknownDepsError(dependent, unknownDeps);
}
addTask(task) {
ow(task, v.checkTaskOrString);
this._throwIfRunning();
this._nameToTask.set(task.name, task);
if (task.dependsOn) {
this._deps.set(task.name, task.dependsOn);
}
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');
return this;
}
end() {
this.emit('end', {
error: this._error
});
this._queue.clear();
this._init();
}
removeTask(task) {
let unsubscribers;
ow(task, v.checkTaskOrString);
this._throwIfRunning();
if (typeof task === 'string') {
task = this._nameToTask.get(task);
if (!task) {
throw new v.UnknownTaskError(`Unknown task: ${task}`);
}
} else if (typeof task === 'object') {
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);
unsubscribers = this._unsubscribers.get(task.name);
for (const u of Object.keys(unsubscribers)) {
unsubscribers[u]();
}
this._unsubscribers.delete(task.name);
return this;
}
removeTasks(tasks) {
ow(tasks, ow.any(ow.array, ow.object));
this._addOrRemoveTasks(tasks, this.removeTask, 'remove');
return this;
}
run(context) {
ow(context, ow.optional.object);
let endPromise;
const { error, parallel, sequential } = this._orderTasks();
let runningPromise;
let taskFuncs = [];
let taskSequence;
// First, fail if the runner is already running.
runningPromise = this._rejectIfRunning();
if (runningPromise) {
return runningPromise;
}
// Then fail if the tasks couldn't be ordered.
if (error) {
return Promise.reject(error);
}
this._context = context || this._context;
for (const taskName of parallel) {
taskFuncs.push(this._bindTaskFunc(this._nameToTask.get(taskName)));
}
taskSequence = this._createTaskSequence(sequential);
if (taskSequence) {
taskFuncs.push(taskSequence);
}
endPromise = this._queue.addAll(taskFuncs).then(() => {
this.end();
if (this._error) {
return Promise.reject(this._error);
} else {
return Promise.resolve();
}
}, e => {
this.end();
return Promise.reject(e);
});
this.emit('start');
this._running = true;
if (Array.isArray(tasks)) {
tasks.forEach((task, i) => {
try {
this._queue.start();
return endPromise;
func(task);
} catch (e) {
this._error = e;
this.end();
return Promise.reject(e);
e.message = `Can't ${action} task ${i}: ${e.message}`;
throw e;
}
});
} else if (tasks !== null && typeof tasks === 'object') {
for (const task of Object.keys(tasks)) {
try {
func(tasks[task]);
} catch (e) {
e.message = `Can't ${action} task "${task}": ${e.message}`;
throw e;
}
}
}
}
_addTaskEmitters(task) {
const u = {};
u.start = task.on('start', (t) => this.emit('taskStart', t));
u.end = task.on('end', (t) => this.emit('taskEnd', t));
u.error = task.on('error', (e) => {
this.emit('taskError', {
task: e.task,
error: e.error,
});
if (!this._error) {
this._error = e.error;
}
});
this._unsubscribers.set(task.name, u);
}
_bindTaskFunc(task) {
return _.bind(task.run, task, this._context);
}
_createTaskSequence(tasks) {
if (!tasks.length) {
return null;
}
get running() {
return this._running;
return () =>
tasks.reduce((p, taskName) => {
const task = this._nameToTask.get(taskName);
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();
this._queue.pause();
}
_newDependencyCycleError(cyclePath) {
return new v.DependencyCycleError(
`Tasks have circular dependencies: ${cyclePath.join(' > ')}`,
cyclePath
);
}
_newStateError() {
return new v.StateError('The task runner is already running.');
}
_newUnknownDepsError(dependent, unknownDeps) {
let errorText;
if (unknownDeps.length === 1) {
errorText = 'an unknown task';
} else {
errorText = 'unknown tasks';
}
get tasks() {
const entries = [];
return new v.UnknownDependencyError(
`The task ${dependent} depends on ${errorText}: ` + `${unknownDeps.join(', ')}`
);
}
for (const entry of this._nameToTask.entries()) {
entries.push(entry);
_orderTasks() {
let error;
const graph = new DepGraph();
let parallel;
let sequential;
for (const [task] of this._nameToTask) {
graph.addNode(task);
}
for (const [dependent] of this._deps) {
const unknownDeps = [];
for (const dependency of this._deps.get(dependent)) {
if (!this._nameToTask.has(dependency)) {
unknownDeps.push(dependency);
} else {
graph.addDependency(dependent, dependency);
}
return _.fromPairs(entries);
if (unknownDeps.length) {
error = this._newUnknownDepsError(dependency, unknownDeps);
break;
}
}
}
if (!error) {
try {
// Get standalone tasks with no dependencies and no dependents.
parallel = graph.overallOrder(true).filter((task) => !graph.dependentsOf(task).length);
// Get tasks with dependencies, in a correctly ordered list.
sequential = graph.overallOrder().filter((task) => !parallel.includes(task));
} catch (e) {
error = this._newDependencyCycleError(e.cyclePath);
}
}
return {
error,
parallel,
sequential,
};
}
_rejectIfRunning() {
if (this.running) {
return Promise.reject(this._newStateError());
}
return null;
}
_throwIfRunning() {
if (this.running) {
throw this._newStateError();
}
}
_throwIfUnknownDeps(dependent, unknownDeps) {
if (!unknownDeps.length) {
return;
}
throw this._newUnknownDepsError(dependent, unknownDeps);
}
addTask(task) {
ow(task, v.checkTaskOrString);
this._throwIfRunning();
this._nameToTask.set(task.name, task);
if (task.dependsOn) {
this._deps.set(task.name, task.dependsOn);
}
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');
return this;
}
end() {
this.emit('end', {
error: this._error,
});
this._queue.clear();
this._init();
}
removeTask(task) {
let unsubscribers;
ow(task, v.checkTaskOrString);
this._throwIfRunning();
if (typeof task === 'string') {
task = this._nameToTask.get(task);
if (!task) {
throw new v.UnknownTaskError(`Unknown task: ${task}`);
}
} else if (typeof task === 'object') {
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);
unsubscribers = this._unsubscribers.get(task.name);
for (const u of Object.keys(unsubscribers)) {
unsubscribers[u]();
}
this._unsubscribers.delete(task.name);
return this;
}
removeTasks(tasks) {
ow(tasks, ow.any(ow.array, ow.object));
this._addOrRemoveTasks(tasks, this.removeTask, 'remove');
return this;
}
run(context) {
ow(context, ow.optional.object);
let endPromise;
const { error, parallel, sequential } = this._orderTasks();
let runningPromise;
let taskFuncs = [];
let taskSequence;
// First, fail if the runner is already running.
runningPromise = this._rejectIfRunning();
if (runningPromise) {
return runningPromise;
}
// Then fail if the tasks couldn't be ordered.
if (error) {
return Promise.reject(error);
}
this._context = context || this._context;
for (const taskName of parallel) {
taskFuncs.push(this._bindTaskFunc(this._nameToTask.get(taskName)));
}
taskSequence = this._createTaskSequence(sequential);
if (taskSequence) {
taskFuncs.push(taskSequence);
}
endPromise = this._queue.addAll(taskFuncs).then(
() => {
this.end();
if (this._error) {
return Promise.reject(this._error);
} else {
return Promise.resolve();
}
},
(e) => {
this.end();
return Promise.reject(e);
}
);
this.emit('start');
this._running = true;
try {
this._queue.start();
return endPromise;
} catch (e) {
this._error = e;
this.end();
return Promise.reject(e);
}
}
get running() {
return this._running;
}
get tasks() {
const entries = [];
for (const entry of this._nameToTask.entries()) {
entries.push(entry);
}
return _.fromPairs(entries);
}
};

View File

@ -1,52 +1,49 @@
const Emittery = require('emittery');
const {default: ow} = require('ow');
const { default: ow } = require('ow');
module.exports = class Task extends Emittery {
constructor(opts = {}) {
let deps;
constructor(opts = {}) {
let deps;
super();
super();
ow(opts.name, ow.optional.string);
ow(opts.func, ow.optional.function);
ow(opts.dependsOn, ow.any(
ow.optional.string,
ow.optional.array.ofType(ow.string)
));
ow(opts.name, ow.optional.string);
ow(opts.func, ow.optional.function);
ow(opts.dependsOn, ow.any(ow.optional.string, ow.optional.array.ofType(ow.string)));
if (typeof opts.dependsOn === 'string') {
deps = [opts.dependsOn];
} else if (Array.isArray(opts.dependsOn)) {
deps = opts.dependsOn.slice(0);
}
this.name = opts.name || null;
this.func = opts.func || null;
this.dependsOn = deps || [];
if (typeof opts.dependsOn === 'string') {
deps = [opts.dependsOn];
} else if (Array.isArray(opts.dependsOn)) {
deps = opts.dependsOn.slice(0);
}
run(context) {
ow(this.name, ow.string);
ow(this.func, ow.function);
ow(this.dependsOn, ow.array.ofType(ow.string));
this.name = opts.name || null;
this.func = opts.func || null;
this.dependsOn = deps || [];
}
this.emit('start', this);
run(context) {
ow(this.name, ow.string);
ow(this.func, ow.function);
ow(this.dependsOn, ow.array.ofType(ow.string));
return this.func(context).then(
() => {
this.emit('end', this);
this.emit('start', this);
return Promise.resolve();
},
error => {
this.emit('error', {
task: this,
error
});
this.emit('end', this);
return this.func(context).then(
() => {
this.emit('end', this);
return Promise.reject(error);
}
);
}
return Promise.resolve();
},
(error) => {
this.emit('error', {
task: this,
error,
});
this.emit('end', this);
return Promise.reject(error);
}
);
}
};

View File

@ -1,47 +1,47 @@
const {default: ow} = require('ow');
const { default: ow } = require('ow');
const Task = require('./task');
function checkTask(t) {
return {
validator: t instanceof Task,
message: `Expected ${t} to be a Task object`
};
return {
validator: t instanceof Task,
message: `Expected ${t} to be a Task object`,
};
}
module.exports = {
checkTaskOrString: ow.any(ow.object.validate(checkTask), ow.string),
DependencyCycleError: class DependencyCycleError extends Error {
constructor(message, cyclePath) {
ow(message, ow.string);
ow(cyclePath, ow.array.ofType(ow.string));
super(message);
checkTaskOrString: ow.any(ow.object.validate(checkTask), ow.string),
DependencyCycleError: class DependencyCycleError extends Error {
constructor(message, cyclePath) {
ow(message, ow.string);
ow(cyclePath, ow.array.ofType(ow.string));
super(message);
this.cyclePath = cyclePath;
this.name = 'DependencyCycleError';
}
},
StateError: class StateError extends Error {
constructor(message) {
ow(message, ow.string);
super(message);
this.name = 'StateError';
}
},
UnknownDependencyError: class UnknownDependencyError extends Error {
constructor(message) {
ow(message, ow.string);
super(message);
this.name = 'UnknownDependencyError';
}
},
UnknownTaskError: class UnknownTaskError extends Error {
constructor(message) {
ow(message, ow.string);
super(message);
this.name = 'UnknownTaskError';
}
this.cyclePath = cyclePath;
this.name = 'DependencyCycleError';
}
},
StateError: class StateError extends Error {
constructor(message) {
ow(message, ow.string);
super(message);
this.name = 'StateError';
}
},
UnknownDependencyError: class UnknownDependencyError extends Error {
constructor(message) {
ow(message, ow.string);
super(message);
this.name = 'UnknownDependencyError';
}
},
UnknownTaskError: class UnknownTaskError extends Error {
constructor(message) {
ow(message, ow.string);
super(message);
this.name = 'UnknownTaskError';
}
},
};

View File

@ -1,8 +1,166 @@
{
"name": "@jsdoc/task-runner",
"version": "0.1.10",
"lockfileVersion": 1,
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@jsdoc/task-runner",
"version": "0.1.10",
"license": "Apache-2.0",
"dependencies": {
"dependency-graph": "^0.11.0",
"emittery": "^0.10.0",
"ow": "^0.27.0",
"p-queue": "^6.6.2"
},
"engines": {
"node": ">=v14.17.6"
}
},
"node_modules/@sindresorhus/is": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz",
"integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/dependency-graph": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz",
"integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/dot-prop": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
"integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
"dependencies": {
"is-obj": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/emittery": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.0.tgz",
"integrity": "sha512-AGvFfs+d0JKCJQ4o01ASQLGPmSCxgfU9RFXvzPvZdjKK8oscynksuJhWrSTSw7j7Ep/sZct5b5ZhYCi8S/t0HQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sindresorhus/emittery?sponsor=1"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
"node_modules/is-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
"engines": {
"node": ">=8"
}
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"node_modules/ow": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/ow/-/ow-0.27.0.tgz",
"integrity": "sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==",
"dependencies": {
"@sindresorhus/is": "^4.0.1",
"callsites": "^3.1.0",
"dot-prop": "^6.0.1",
"lodash.isequal": "^4.5.0",
"type-fest": "^1.2.1",
"vali-date": "^1.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
"engines": {
"node": ">=4"
}
},
"node_modules/p-queue": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
"integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==",
"dependencies": {
"eventemitter3": "^4.0.4",
"p-timeout": "^3.2.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-timeout": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
"integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
"dependencies": {
"p-finally": "^1.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/type-fest": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.2.2.tgz",
"integrity": "sha512-pfkPYCcuV0TJoo/jlsUeWNV8rk7uMU6ocnYNvca1Vu+pyKi8Rl8Zo2scPt9O72gCsXIm+dMxOOWuA3VFDSdzWA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/vali-date": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz",
"integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=",
"engines": {
"node": ">=0.10.0"
}
}
},
"dependencies": {
"@sindresorhus/is": {
"version": "4.0.1",

View File

@ -1,23 +1,23 @@
const taskRunner = require('../../index');
describe('@jsdoc/task-runner', () => {
it('is an object', () => {
expect(taskRunner).toBeObject();
it('is an object', () => {
expect(taskRunner).toBeObject();
});
describe('Task', () => {
it('is lib/task', () => {
const Task = require('../../lib/task');
expect(taskRunner.Task).toBe(Task);
});
});
describe('Task', () => {
it('is lib/task', () => {
const Task = require('../../lib/task');
describe('TaskRunner', () => {
it('is lib/task-runner', () => {
const TaskRunner = require('../../lib/task-runner');
expect(taskRunner.Task).toBe(Task);
});
});
describe('TaskRunner', () => {
it('is lib/task-runner', () => {
const TaskRunner = require('../../lib/task-runner');
expect(taskRunner.TaskRunner).toBe(TaskRunner);
});
expect(taskRunner.TaskRunner).toBe(TaskRunner);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -4,201 +4,200 @@ const Task = require('../../../lib/task');
const ARGUMENT_ERROR = 'ArgumentError';
describe('@jsdoc/task-runner/lib/task', () => {
it('is a function', () => {
expect(Task).toBeFunction();
it('is a function', () => {
expect(Task).toBeFunction();
});
it('inherits from emittery', () => {
expect(new Task() instanceof Emittery).toBeTrue();
});
it('can be constructed with no arguments', () => {
function factory() {
return new Task();
}
expect(factory).not.toThrow();
});
it('uses the provided name', () => {
const task = new Task({ name: 'foo' });
expect(task.name).toBe('foo');
});
it('uses the provided function', () => {
const func = () => Promise.resolve();
const task = new Task({ func });
expect(task.func).toBe(func);
});
describe('dependsOn', () => {
it('accepts an array of task names as dependencies', () => {
const dependsOn = ['bar', 'baz'];
const task = new Task({
name: 'foo',
func: () => Promise.resolve(),
dependsOn,
});
expect(task.dependsOn).toEqual(dependsOn);
});
it('inherits from emittery', () => {
expect(new Task() instanceof Emittery).toBeTrue();
it('accepts a single task name as a dependency', () => {
const task = new Task({
name: 'foo',
func: () => Promise.resolve(),
dependsOn: 'bar',
});
expect(task.dependsOn).toEqual(['bar']);
});
it('can be constructed with no arguments', () => {
function factory() {
return new Task();
it('fails with non-string, non-array dependencies', () => {
function factory() {
return new Task({
name: 'foo',
func: () => Promise.resolve(),
dependsOn: 7,
});
}
expect(factory).toThrowErrorOfType(ARGUMENT_ERROR);
});
it('fails with non-string arrays of dependencies', () => {
function factory() {
return new Task({
name: 'foo',
func: () => Promise.resolve(),
dependsOn: [7],
});
}
expect(factory).toThrowErrorOfType(ARGUMENT_ERROR);
});
});
it('uses the provided dependencies', () => {
const dependsOn = ['foo', 'bar'];
const task = new Task({ dependsOn });
expect(task.dependsOn).toEqual(dependsOn);
});
describe('run', () => {
it('requires a name', async () => {
let error;
async function start() {
const task = new Task({
func: () => Promise.resolve(),
});
await task.run();
}
try {
await start();
} catch (e) {
error = e;
}
expect(error).toBeDefined();
});
it('requires a function', async () => {
let error;
async function run() {
const task = new Task({
name: 'foo',
});
await task.run();
}
try {
await run();
} catch (e) {
error = e;
}
expect(error).toBeDefined();
});
it('accepts a context object', async () => {
const context = {};
const task = new Task({
name: 'foo',
func: (c) => {
c.foo = 'bar';
return Promise.resolve();
},
});
await task.run(context);
expect(context.foo).toBe('bar');
});
describe('events', () => {
it('emits a `start` event', async () => {
let event;
const task = new Task({
name: 'foo',
func: () => Promise.resolve(),
});
task.on('start', (e) => {
event = e;
});
await task.run();
expect(event).toBe(task);
});
it('emits an `end` event', async () => {
let event;
const task = new Task({
name: 'foo',
func: () => Promise.resolve(),
});
task.on('end', (e) => {
event = e;
});
await task.run();
expect(event).toBe(task);
});
it('emits an `error` event if necessary', async () => {
let error = new Error('oh no!');
let event;
const task = new Task({
name: 'foo',
func: () => Promise.reject(error),
});
task.on('error', (e) => {
event = e;
});
try {
await task.run();
} catch (e) {
// Expected behavior.
}
expect(factory).not.toThrow();
});
it('uses the provided name', () => {
const task = new Task({ name: 'foo' });
expect(task.name).toBe('foo');
});
it('uses the provided function', () => {
const func = () => Promise.resolve();
const task = new Task({ func });
expect(task.func).toBe(func);
});
describe('dependsOn', () => {
it('accepts an array of task names as dependencies', () => {
const dependsOn = ['bar', 'baz'];
const task = new Task({
name: 'foo',
func: () => Promise.resolve(),
dependsOn
});
expect(task.dependsOn).toEqual(dependsOn);
});
it('accepts a single task name as a dependency', () => {
const task = new Task({
name: 'foo',
func: () => Promise.resolve(),
dependsOn: 'bar'
});
expect(task.dependsOn).toEqual(['bar']);
});
it('fails with non-string, non-array dependencies', () => {
function factory() {
return new Task({
name: 'foo',
func: () => Promise.resolve(),
dependsOn: 7
});
}
expect(factory).toThrowErrorOfType(ARGUMENT_ERROR);
});
it('fails with non-string arrays of dependencies', () => {
function factory() {
return new Task({
name: 'foo',
func: () => Promise.resolve(),
dependsOn: [7]
});
}
expect(factory).toThrowErrorOfType(ARGUMENT_ERROR);
});
});
it('uses the provided dependencies', () => {
const dependsOn = ['foo', 'bar'];
const task = new Task({ dependsOn });
expect(task.dependsOn).toEqual(dependsOn);
});
describe('run', () => {
it('requires a name', async () => {
let error;
async function start() {
const task = new Task({
func: () => Promise.resolve()
});
await task.run();
}
try {
await start();
} catch (e) {
error = e;
}
expect(error).toBeDefined();
});
it('requires a function', async () => {
let error;
async function run() {
const task = new Task({
name: 'foo'
});
await task.run();
}
try {
await run();
} catch (e) {
error = e;
}
expect(error).toBeDefined();
});
it('accepts a context object', async () => {
const context = {};
const task = new Task({
name: 'foo',
func: c => {
c.foo = 'bar';
return Promise.resolve();
}
});
await task.run(context);
expect(context.foo).toBe('bar');
});
describe('events', () => {
it('emits a `start` event', async () => {
let event;
const task = new Task({
name: 'foo',
func: () => Promise.resolve()
});
task.on('start', e => {
event = e;
});
await task.run();
expect(event).toBe(task);
});
it('emits an `end` event', async () => {
let event;
const task = new Task({
name: 'foo',
func: () => Promise.resolve()
});
task.on('end', e => {
event = e;
});
await task.run();
expect(event).toBe(task);
});
it('emits an `error` event if necessary', async () => {
let error = new Error('oh no!');
let event;
const task = new Task({
name: 'foo',
func: () => Promise.reject(error)
});
task.on('error', e => {
event = e;
});
try {
await task.run();
} catch (e) {
// Expected behavior.
}
expect(event.error).toBe(error);
expect(event.task).toBe(task);
});
});
expect(event.error).toBe(error);
expect(event.task).toBe(task);
});
});
});
});

View File

@ -3,32 +3,32 @@ const { addMatchers } = require('add-matchers');
require('jasmine-expect');
addMatchers({
toBeError(value) {
return value instanceof Error;
},
toBeErrorOfType(other, value) {
return value instanceof Error && value.name === other;
},
toBeInstanceOf(other, value) {
let otherName;
let valueName;
toBeError(value) {
return value instanceof Error;
},
toBeErrorOfType(other, value) {
return value instanceof Error && value.name === other;
},
toBeInstanceOf(other, value) {
let otherName;
let valueName;
if (typeof value !== 'object') {
throw new TypeError(`Expected object value, got ${typeof value}`);
}
valueName = value.constructor.name;
// Class name.
if (typeof other === 'string') {
otherName = other;
// Class constructor.
} else if (typeof other === 'function') {
otherName = other.name;
} else {
otherName = other.constructor.name;
}
return valueName === otherName;
if (typeof value !== 'object') {
throw new TypeError(`Expected object value, got ${typeof value}`);
}
valueName = value.constructor.name;
// Class name.
if (typeof other === 'string') {
otherName = other;
// Class constructor.
} else if (typeof other === 'function') {
otherName = other.name;
} else {
otherName = other.constructor.name;
}
return valueName === otherName;
},
});

View File

@ -1,8 +1,35 @@
{
"name": "@jsdoc/test-matchers",
"version": "0.1.6",
"lockfileVersion": 1,
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@jsdoc/test-matchers",
"version": "0.1.6",
"license": "Apache-2.0",
"dependencies": {
"add-matchers": "^0.6.2",
"jasmine-expect": "^5.0.0"
},
"engines": {
"node": ">=v14.17.6"
}
},
"node_modules/add-matchers": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/add-matchers/-/add-matchers-0.6.2.tgz",
"integrity": "sha512-hVO2wodMei9RF00qe+506MoeJ/NEOdCMEkSJ12+fC3hx/5Z4zmhNiP92nJEF6XhmXokeB0hOtuQrjHCx2vmXrQ=="
},
"node_modules/jasmine-expect": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/jasmine-expect/-/jasmine-expect-5.0.0.tgz",
"integrity": "sha512-byn1zq0EQBA9UKs5A+H6gk5TRcanV+TqQMRxrjurGuqKkclaqgjw/vV6aT/jtf5tabXGonTH6VDZJ33Z1pxSxw==",
"dependencies": {
"add-matchers": "0.6.2"
}
}
},
"dependencies": {
"add-matchers": {
"version": "0.6.2",

View File

@ -10,8 +10,8 @@ const fs = require('./lib/fs');
const log = require('./lib/log');
module.exports = {
cast,
EventBus,
fs,
log
cast,
EventBus,
fs,
log,
};

View File

@ -1,6 +1,6 @@
const _ = require('lodash');
const EventEmitter = require('events').EventEmitter;
const {default: ow} = require('ow');
const { default: ow } = require('ow');
let cache = {};
const hasOwnProp = Object.prototype.hasOwnProperty;
@ -22,31 +22,31 @@ const hasOwnProp = Object.prototype.hasOwnProperty;
* @extends module:events.EventEmitter
*/
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();
/**
* 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));
ow(id, ow.any(ow.string, ow.symbol));
const shouldCache = _.isBoolean(opts.cache) ? opts.cache : true;
const shouldCache = _.isBoolean(opts.cache) ? opts.cache : true;
if (hasOwnProp.call(cache, id) && shouldCache) {
return cache[id];
}
this._id = id;
if (shouldCache) {
cache[id] = this;
}
if (hasOwnProp.call(cache, id) && shouldCache) {
return cache[id];
}
this._id = id;
if (shouldCache) {
cache[id] = this;
}
}
}
module.exports = EventBus;

View File

@ -13,49 +13,47 @@
* @return {(string|number|boolean)} The converted value.
*/
function castString(str) {
let number;
let result;
let number;
let result;
switch (str) {
case 'true':
result = true;
break;
switch (str) {
case 'true':
result = true;
break;
case 'false':
result = false;
break;
case 'false':
result = false;
break;
case 'NaN':
result = NaN;
break;
case 'NaN':
result = NaN;
break;
case 'null':
result = null;
break;
case 'null':
result = null;
break;
case 'undefined':
result = undefined;
break;
case 'undefined':
result = undefined;
break;
default:
if (typeof str === 'string') {
if (str.includes('.')) {
number = parseFloat(str);
}
else {
number = parseInt(str, 10);
}
default:
if (typeof str === 'string') {
if (str.includes('.')) {
number = parseFloat(str);
} else {
number = parseInt(str, 10);
}
if (String(number) === str && !isNaN(number)) {
result = number;
}
else {
result = str;
}
}
}
if (String(number) === str && !isNaN(number)) {
result = number;
} else {
result = str;
}
}
}
return result;
return result;
}
/**
@ -69,27 +67,24 @@ function castString(str) {
* @param {(string|Object|Array)} item - The item whose type or types will be converted.
* @return {*?} The converted value.
*/
const cast = module.exports = item => {
let result;
const cast = (module.exports = (item) => {
let result;
if (Array.isArray(item)) {
result = [];
for (let i = 0, l = item.length; i < l; i++) {
result[i] = cast(item[i]);
}
}
else if (typeof item === 'object' && item !== null) {
result = {};
Object.keys(item).forEach(prop => {
result[prop] = cast(item[prop]);
});
}
else if (typeof item === 'string') {
result = castString(item);
}
else {
result = item;
if (Array.isArray(item)) {
result = [];
for (let i = 0, l = item.length; i < l; i++) {
result[i] = cast(item[i]);
}
} else if (typeof item === 'object' && item !== null) {
result = {};
Object.keys(item).forEach((prop) => {
result[prop] = cast(item[prop]);
});
} else if (typeof item === 'string') {
result = castString(item);
} else {
result = item;
}
return result;
};
return result;
});

View File

@ -6,14 +6,14 @@ const _ = require('lodash');
const klawSync = require('klaw-sync');
const path = require('path');
exports.lsSync = ((dir, opts = {}) => {
const depth = _.has(opts, 'depth') ? opts.depth : -1;
exports.lsSync = (dir, opts = {}) => {
const depth = _.has(opts, 'depth') ? opts.depth : -1;
const files = klawSync(dir, {
depthLimit: depth,
filter: (f => !path.basename(f.path).startsWith('.')),
nodir: true
});
const files = klawSync(dir, {
depthLimit: depth,
filter: (f) => !path.basename(f.path).startsWith('.'),
nodir: true,
});
return files.map(f => f.path);
});
return files.map((f) => f.path);
};

View File

@ -3,8 +3,8 @@ const EventBus = require('./bus');
const bus = new EventBus('jsdoc');
const loggerFuncs = {};
['debug', 'error', 'info', 'fatal', 'verbose', 'warn'].forEach(fn => {
loggerFuncs[fn] = (...args) => bus.emit(`logger:${fn}`, ...args);
['debug', 'error', 'info', 'fatal', 'verbose', 'warn'].forEach((fn) => {
loggerFuncs[fn] = (...args) => bus.emit(`logger:${fn}`, ...args);
});
module.exports = loggerFuncs;

View File

@ -1,8 +1,125 @@
{
"name": "@jsdoc/util",
"version": "0.2.4",
"lockfileVersion": 1,
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@jsdoc/util",
"version": "0.2.4",
"license": "Apache-2.0",
"dependencies": {
"klaw-sync": "^6.0.0",
"lodash": "^4.17.21",
"ow": "^0.27.0"
},
"engines": {
"node": ">=v14.17.6"
}
},
"node_modules/@sindresorhus/is": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz",
"integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/dot-prop": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
"integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
"dependencies": {
"is-obj": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/graceful-fs": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ=="
},
"node_modules/is-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
"engines": {
"node": ">=8"
}
},
"node_modules/klaw-sync": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
"integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
"dependencies": {
"graceful-fs": "^4.1.11"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"node_modules/ow": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/ow/-/ow-0.27.0.tgz",
"integrity": "sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==",
"dependencies": {
"@sindresorhus/is": "^4.0.1",
"callsites": "^3.1.0",
"dot-prop": "^6.0.1",
"lodash.isequal": "^4.5.0",
"type-fest": "^1.2.1",
"vali-date": "^1.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/type-fest": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.2.2.tgz",
"integrity": "sha512-pfkPYCcuV0TJoo/jlsUeWNV8rk7uMU6ocnYNvca1Vu+pyKi8Rl8Zo2scPt9O72gCsXIm+dMxOOWuA3VFDSdzWA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/vali-date": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz",
"integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=",
"engines": {
"node": ">=0.10.0"
}
}
},
"dependencies": {
"@sindresorhus/is": {
"version": "4.0.1",

View File

@ -1,31 +1,31 @@
const util = require('../../index');
describe('@jsdoc/util', () => {
it('is an object', () => {
expect(util).toBeObject();
it('is an object', () => {
expect(util).toBeObject();
});
describe('cast', () => {
it('is lib/cast', () => {
const cast = require('../../lib/cast');
expect(util.cast).toBe(cast);
});
});
describe('cast', () => {
it('is lib/cast', () => {
const cast = require('../../lib/cast');
describe('EventBus', () => {
it('is lib/bus', () => {
const bus = require('../../lib/bus');
expect(util.cast).toBe(cast);
});
expect(util.EventBus).toBe(bus);
});
});
describe('EventBus', () => {
it('is lib/bus', () => {
const bus = require('../../lib/bus');
describe('fs', () => {
it('is lib/fs', () => {
const fs = require('../../lib/fs');
expect(util.EventBus).toBe(bus);
});
});
describe('fs', () => {
it('is lib/fs', () => {
const fs = require('../../lib/fs');
expect(util.fs).toBe(fs);
});
expect(util.fs).toBe(fs);
});
});
});

View File

@ -1,66 +1,66 @@
describe('@jsdoc/util/lib/bus', () => {
const EventBus = require('../../../lib/bus');
const EventEmitter = require('events').EventEmitter;
const EventBus = require('../../../lib/bus');
const EventEmitter = require('events').EventEmitter;
const ignoreCache = { cache: false };
const ignoreCache = { cache: false };
it('inherits from EventEmitter', () => {
expect(new EventBus('foo', ignoreCache) instanceof EventEmitter).toBeTrue();
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;
});
it('accepts a string for the ID', () => {
function makeBus() {
return new EventBus('foo', ignoreCache);
}
bus2.emit('foo');
expect(makeBus).not.toThrow();
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;
});
it('accepts a Symbol for the ID', () => {
function makeBus() {
return new EventBus(Symbol('foo'), ignoreCache);
}
bus2.emit('foo');
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();
});
expect(bus1).not.toBe(bus2);
expect(fired).toBeFalse();
});
});

View File

@ -1,66 +1,66 @@
describe('@jsdoc/util/lib/cast', () => {
const cast = require('../../../lib/cast');
const cast = require('../../../lib/cast');
it('is a function', () => {
expect(cast).toBeFunction();
it('is a function', () => {
expect(cast).toBeFunction();
});
it('does not modify values that are not strings, objects, or arrays', () => {
expect(cast(8)).toBe(8);
});
it('does not modify strings that are neither boolean-ish nor number-ish', () => {
expect(cast('hello world')).toBe('hello world');
});
it('casts "true" and "false" to booleans', () => {
expect(cast('true')).toBeTrue();
expect(cast('false')).toBeFalse();
});
it('casts "null" to null', () => {
expect(cast('null')).toBeNull();
});
it('casts "undefined" to undefined', () => {
expect(cast('undefined')).toBeUndefined();
});
it('casts positive number-ish strings to numbers', () => {
expect(cast('17.35')).toBe(17.35);
});
it('casts negative number-ish strings to numbers', () => {
expect(cast('-17.35')).toBe(-17.35);
});
it('casts "NaN" to NaN', () => {
expect(cast('NaN')).toBeNaN();
});
it('casts values of object properties', () => {
expect(cast({ foo: 'true' })).toEqual({ foo: true });
});
it('casts values of properties in nested objects', () => {
const result = cast({
foo: {
bar: 'true',
},
});
it('does not modify values that are not strings, objects, or arrays', () => {
expect(cast(8)).toBe(8);
expect(result).toEqual({
foo: {
bar: true,
},
});
});
it('does not modify strings that are neither boolean-ish nor number-ish', () => {
expect(cast('hello world')).toBe('hello world');
});
it('casts values in an array', () => {
expect(cast(['true', '17.35'])).toEqual([true, 17.35]);
});
it('casts "true" and "false" to booleans', () => {
expect(cast('true')).toBeTrue();
expect(cast('false')).toBeFalse();
});
it('casts "null" to null', () => {
expect(cast('null')).toBeNull();
});
it('casts "undefined" to undefined', () => {
expect(cast('undefined')).toBeUndefined();
});
it('casts positive number-ish strings to numbers', () => {
expect(cast('17.35')).toBe(17.35);
});
it('casts negative number-ish strings to numbers', () => {
expect(cast('-17.35')).toBe(-17.35);
});
it('casts "NaN" to NaN', () => {
expect(cast('NaN')).toBeNaN();
});
it('casts values of object properties', () => {
expect(cast({ foo: 'true' })).toEqual({ foo: true });
});
it('casts values of properties in nested objects', () => {
const result = cast({
foo: {
bar: 'true'
}
});
expect(result).toEqual({
foo: {
bar: true
}
});
});
it('casts values in an array', () => {
expect(cast(['true', '17.35'])).toEqual([true, 17.35]);
});
it('casts values in a nested array', () => {
expect(cast(['true', ['17.35']])).toEqual([true, [17.35]]);
});
it('casts values in a nested array', () => {
expect(cast(['true', ['17.35']])).toEqual([true, [17.35]]);
});
});

View File

@ -1,71 +1,66 @@
describe('@jsdoc/util/lib/fs', () => {
const mockFs = require('mock-fs');
const fsUtil = require('../../../lib/fs');
const path = require('path');
const mockFs = require('mock-fs');
const fsUtil = require('../../../lib/fs');
const path = require('path');
afterEach(() => mockFs.restore());
afterEach(() => mockFs.restore());
it('has an lsSync method', () => {
expect(fsUtil.lsSync).toBeFunction();
it('has an lsSync method', () => {
expect(fsUtil.lsSync).toBeFunction();
});
describe('lsSync', () => {
beforeEach(() => {
mockFs({
head: {
eyes: '',
ears: '',
mouth: '',
nose: '',
shoulders: {
knees: {
meniscus: '',
toes: {
phalanx: '',
'.big-toe-phalanx': '',
},
},
},
},
});
});
describe('lsSync', () => {
beforeEach(() => {
mockFs({
head: {
eyes: '',
ears: '',
mouth: '',
nose: '',
shoulders: {
knees: {
meniscus: '',
toes: {
phalanx: '',
'.big-toe-phalanx': ''
}
}
}
}
});
});
const cwd = process.cwd();
const cwd = process.cwd();
function resolvePaths(files) {
return files.map((f) => path.join(cwd, f)).sort();
}
function resolvePaths(files) {
return files.map(f => path.join(cwd, f)).sort();
}
const allFiles = resolvePaths([
'head/eyes',
'head/ears',
'head/mouth',
'head/nose',
'head/shoulders/knees/meniscus',
'head/shoulders/knees/toes/phalanx',
]);
const allFiles = resolvePaths([
'head/eyes',
'head/ears',
'head/mouth',
'head/nose',
'head/shoulders/knees/meniscus',
'head/shoulders/knees/toes/phalanx'
]);
it('gets all non-hidden files from all levels by default', () => {
const files = fsUtil.lsSync(cwd).sort();
it('gets all non-hidden files from all levels by default', () => {
const files = fsUtil.lsSync(cwd).sort();
expect(files).toEqual(allFiles);
});
it('limits recursion depth when asked', () => {
const files = fsUtil.lsSync(cwd, { depth: 1 }).sort();
expect(files).toEqual(resolvePaths([
'head/eyes',
'head/ears',
'head/mouth',
'head/nose'
]));
});
it('treats a depth of -1 as infinite', () => {
const files = fsUtil.lsSync('head', { depth: -1 }).sort();
expect(files).toEqual(allFiles);
});
expect(files).toEqual(allFiles);
});
it('limits recursion depth when asked', () => {
const files = fsUtil.lsSync(cwd, { depth: 1 }).sort();
expect(files).toEqual(resolvePaths(['head/eyes', 'head/ears', 'head/mouth', 'head/nose']));
});
it('treats a depth of -1 as infinite', () => {
const files = fsUtil.lsSync('head', { depth: -1 }).sort();
expect(files).toEqual(allFiles);
});
});
});

View File

@ -1,33 +1,33 @@
describe('@jsdoc/util/lib/log', () => {
const EventBus = require('../../../lib/bus');
const log = require('../../../lib/log');
const EventBus = require('../../../lib/bus');
const log = require('../../../lib/log');
const fns = ['debug', 'error', 'info', 'fatal', 'verbose', 'warn'];
const fns = ['debug', 'error', 'info', 'fatal', 'verbose', 'warn'];
it('is an object', () => {
expect(log).toBeObject();
it('is an object', () => {
expect(log).toBeObject();
});
it('provides the expected functions', () => {
fns.forEach((fn) => {
expect(log[fn]).toBeFunction();
});
});
it('provides the expected functions', () => {
fns.forEach(fn => {
expect(log[fn]).toBeFunction();
});
});
describe('functions', () => {
const bus = new EventBus('jsdoc');
it('sends events to the event bus', () => {
fns.forEach(fn => {
let event;
bus.once(`logger:${fn}`, e => {
event = e;
});
log[fn]('testing');
expect(event).toBe('testing');
});
describe('functions', () => {
const bus = new EventBus('jsdoc');
it('sends events to the event bus', () => {
fns.forEach((fn) => {
let event;
bus.once(`logger:${fn}`, (e) => {
event = e;
});
log[fn]('testing');
expect(event).toBe('testing');
});
});
});
});

View File

@ -15,398 +15,383 @@ const Promise = require('bluebird');
* @private
*/
module.exports = (() => {
const props = {
docs: [],
packageJson: null,
shouldExitWithError: false,
shouldPrintHelp: false,
tmpdir: null
const props = {
docs: [],
packageJson: null,
shouldExitWithError: false,
shouldPrintHelp: false,
tmpdir: null,
};
const bus = new EventBus('jsdoc');
const cli = {};
const engine = new Engine();
const FATAL_ERROR_MESSAGE =
'Exiting JSDoc because an error occurred. See the previous log ' + 'messages for details.';
const LOG_LEVELS = Engine.LOG_LEVELS;
// TODO: docs
cli.setVersionInfo = () => {
const fs = require('fs');
// allow this to throw--something is really wrong if we can't read our own package file
const info = JSON.parse(
stripBom(fs.readFileSync(path.join(env.dirname, 'package.json'), 'utf8'))
);
const revision = new Date(parseInt(info.revision, 10));
env.version = {
number: info.version,
revision: revision.toUTCString(),
};
const bus = new EventBus('jsdoc');
const cli = {};
const engine = new Engine();
const FATAL_ERROR_MESSAGE = 'Exiting JSDoc because an error occurred. See the previous log ' +
'messages for details.';
const LOG_LEVELS = Engine.LOG_LEVELS;
// TODO: docs
cli.setVersionInfo = () => {
const fs = require('fs');
// allow this to throw--something is really wrong if we can't read our own package file
const info = JSON.parse(stripBom(fs.readFileSync(path.join(env.dirname, 'package.json'),
'utf8')));
const revision = new Date(parseInt(info.revision, 10));
env.version = {
number: info.version,
revision: revision.toUTCString()
};
engine.version = env.version.number;
engine.revision = revision;
return cli;
};
// TODO: docs
cli.loadConfig = () => {
const _ = require('lodash');
let conf;
try {
env.opts = engine.parseFlags(env.args);
}
catch (e) {
props.shouldPrintHelp = true;
cli.exit(
1,
`${e.message}\n`
);
return cli;
}
try {
conf = config.loadSync(env.opts.configure);
env.conf = conf.config;
}
catch (e) {
cli.exit(
1,
`Cannot parse the config file ${conf.filepath}: ${e}\n${FATAL_ERROR_MESSAGE}`
);
return cli;
}
// look for options on the command line, then in the config
env.opts = _.defaults(env.opts, env.conf.opts);
return cli;
};
// TODO: docs
cli.configureLogger = () => {
function recoverableError() {
props.shouldExitWithError = true;
}
function fatalError() {
cli.exit(1);
}
if (env.opts.test) {
engine.logLevel = LOG_LEVELS.SILENT;
} else {
if (env.opts.debug) {
engine.logLevel = LOG_LEVELS.DEBUG;
}
else if (env.opts.verbose) {
engine.logLevel = LOG_LEVELS.INFO;
}
if (env.opts.pedantic) {
bus.once('logger:warn', recoverableError);
bus.once('logger:error', fatalError);
}
else {
bus.once('logger:error', recoverableError);
}
bus.once('logger:fatal', fatalError);
}
return cli;
};
// TODO: docs
cli.logStart = () => {
log.debug(engine.versionDetails);
log.debug('Environment info: %j', {
env: {
conf: env.conf,
opts: env.opts
}
});
};
// TODO: docs
cli.logFinish = () => {
let delta;
let deltaSeconds;
if (env.run.finish && env.run.start) {
delta = env.run.finish.getTime() - env.run.start.getTime();
}
if (delta !== undefined) {
deltaSeconds = (delta / 1000).toFixed(2);
log.info(`Finished running in ${deltaSeconds} seconds.`);
}
};
// TODO: docs
cli.runCommand = () => {
let cmd;
const opts = env.opts;
// If we already need to exit with an error, don't do any more work.
if (props.shouldExitWithError) {
cmd = () => Promise.resolve(0);
}
else if (opts.help) {
cmd = cli.printHelp;
}
else if (opts.test) {
cmd = cli.runTests;
}
else if (opts.version) {
cmd = cli.printVersion;
}
else {
cmd = cli.main;
}
return cmd().then(errorCode => {
if (!errorCode && props.shouldExitWithError) {
errorCode = 1;
}
cli.logFinish();
cli.exit(errorCode || 0);
});
};
// TODO: docs
cli.printHelp = () => {
cli.printVersion();
console.log(engine.help({ maxLength: process.stdout.columns }));
return Promise.resolve(0);
};
// TODO: docs
cli.runTests = () => require('./test')();
// TODO: docs
cli.printVersion = () => {
console.log(engine.versionDetails);
return Promise.resolve(0);
};
// TODO: docs
cli.main = () => {
cli.scanFiles();
if (env.sourceFiles.length === 0) {
console.log('There are no input files to process.');
return Promise.resolve(0);
} else {
return cli.createParser()
.parseFiles()
.processParseResults()
.then(() => {
env.run.finish = new Date();
return 0;
});
}
};
function readPackageJson(filepath) {
const fs = require('fs');
try {
return stripJsonComments( fs.readFileSync(filepath, 'utf8') );
}
catch (e) {
log.error(`Unable to read the package file ${filepath}`);
return null;
}
}
function buildSourceList() {
let packageJson;
let sourceFile;
let sourceFiles = env.opts._ ? env.opts._.slice(0) : [];
if (env.conf.source && env.conf.source.include) {
sourceFiles = sourceFiles.concat(env.conf.source.include);
}
// load the user-specified package file, if any
if (env.opts.package) {
packageJson = readPackageJson(env.opts.package);
}
// source files named `package.json` or `README.md` get special treatment, unless the user
// explicitly specified a package and/or README file
for (let i = 0, l = sourceFiles.length; i < l; i++) {
sourceFile = sourceFiles[i];
if ( !env.opts.package && /\bpackage\.json$/i.test(sourceFile) ) {
packageJson = readPackageJson(sourceFile);
sourceFiles.splice(i--, 1);
}
if ( !env.opts.readme && /(\bREADME|\.md)$/i.test(sourceFile) ) {
env.opts.readme = sourceFile;
sourceFiles.splice(i--, 1);
}
}
// Resolve the path to the README.
if (env.opts.readme) {
env.opts.readme = path.resolve(env.opts.readme);
}
props.packageJson = packageJson;
return sourceFiles;
}
// TODO: docs
cli.scanFiles = () => {
const { Filter } = require('jsdoc/src/filter');
const { Scanner } = require('jsdoc/src/scanner');
let filter;
let scanner;
env.opts._ = buildSourceList();
// are there any files to scan and parse?
if (env.conf.source && env.opts._.length) {
filter = new Filter(env.conf.source);
scanner = new Scanner();
env.sourceFiles = scanner.scan(env.opts._,
(env.opts.recurse ? env.conf.recurseDepth : undefined), filter);
}
return cli;
};
cli.createParser = () => {
const handlers = require('jsdoc/src/handlers');
const parser = require('jsdoc/src/parser');
const plugins = require('jsdoc/plugins');
props.parser = parser.createParser(env.conf.parser, env.conf);
if (env.conf.plugins) {
plugins.installPlugins(env.conf.plugins, props.parser);
}
handlers.attachTo(props.parser);
return cli;
};
cli.parseFiles = () => {
const augment = require('jsdoc/augment');
const borrow = require('jsdoc/borrow');
const Package = require('jsdoc/package').Package;
let docs;
let packageDocs;
props.docs = docs = props.parser.parse(env.sourceFiles, env.opts.encoding);
// If there is no package.json, just create an empty package
packageDocs = new Package(props.packageJson);
packageDocs.files = env.sourceFiles || [];
docs.push(packageDocs);
log.debug('Adding inherited symbols, mixins, and interface implementations...');
augment.augmentAll(docs);
log.debug('Adding borrowed doclets...');
borrow.resolveBorrows(docs);
log.debug('Post-processing complete.');
props.parser.fireProcessingComplete(docs);
return cli;
};
cli.processParseResults = () => {
if (env.opts.explain) {
cli.dumpParseResults();
return Promise.resolve();
}
else {
return cli.generateDocs();
}
};
cli.dumpParseResults = () => {
console.log(JSON.stringify(props.docs, null, 4));
return cli;
};
cli.generateDocs = () => {
let message;
const taffy = require('taffydb').taffy;
let template;
env.opts.template = env.opts.template || path.join(__dirname, 'templates', 'default');
try {
// TODO: Just look for a `publish` function in the specified module, not a `publish.js`
// file _and_ a `publish` function.
template = require(`${env.opts.template}/publish`);
}
catch (e) {
log.fatal(`Unable to load template: ${e.message}` || e);
}
// templates should include a publish.js file that exports a "publish" function
if (template.publish && typeof template.publish === 'function') {
let publishPromise;
log.info('Generating output files...');
publishPromise = template.publish(
taffy(props.docs),
env.opts
);
return Promise.resolve(publishPromise);
}
else {
message = `${env.opts.template} does not export a "publish" function. ` +
'Global "publish" functions are no longer supported.';
log.fatal(message);
return Promise.reject(new Error(message));
}
};
// TODO: docs
cli.exit = (exitCode, message) => {
if (exitCode > 0) {
props.shouldExitWithError = true;
if (message) {
console.error(message);
}
}
process.on('exit', () => {
if (props.shouldPrintHelp) {
cli.printHelp();
}
process.exit(exitCode);
});
};
engine.version = env.version.number;
engine.revision = revision;
return cli;
};
// TODO: docs
cli.loadConfig = () => {
const _ = require('lodash');
let conf;
try {
env.opts = engine.parseFlags(env.args);
} catch (e) {
props.shouldPrintHelp = true;
cli.exit(1, `${e.message}\n`);
return cli;
}
try {
conf = config.loadSync(env.opts.configure);
env.conf = conf.config;
} catch (e) {
cli.exit(1, `Cannot parse the config file ${conf.filepath}: ${e}\n${FATAL_ERROR_MESSAGE}`);
return cli;
}
// look for options on the command line, then in the config
env.opts = _.defaults(env.opts, env.conf.opts);
return cli;
};
// TODO: docs
cli.configureLogger = () => {
function recoverableError() {
props.shouldExitWithError = true;
}
function fatalError() {
cli.exit(1);
}
if (env.opts.test) {
engine.logLevel = LOG_LEVELS.SILENT;
} else {
if (env.opts.debug) {
engine.logLevel = LOG_LEVELS.DEBUG;
} else if (env.opts.verbose) {
engine.logLevel = LOG_LEVELS.INFO;
}
if (env.opts.pedantic) {
bus.once('logger:warn', recoverableError);
bus.once('logger:error', fatalError);
} else {
bus.once('logger:error', recoverableError);
}
bus.once('logger:fatal', fatalError);
}
return cli;
};
// TODO: docs
cli.logStart = () => {
log.debug(engine.versionDetails);
log.debug('Environment info: %j', {
env: {
conf: env.conf,
opts: env.opts,
},
});
};
// TODO: docs
cli.logFinish = () => {
let delta;
let deltaSeconds;
if (env.run.finish && env.run.start) {
delta = env.run.finish.getTime() - env.run.start.getTime();
}
if (delta !== undefined) {
deltaSeconds = (delta / 1000).toFixed(2);
log.info(`Finished running in ${deltaSeconds} seconds.`);
}
};
// TODO: docs
cli.runCommand = () => {
let cmd;
const opts = env.opts;
// If we already need to exit with an error, don't do any more work.
if (props.shouldExitWithError) {
cmd = () => Promise.resolve(0);
} else if (opts.help) {
cmd = cli.printHelp;
} else if (opts.test) {
cmd = cli.runTests;
} else if (opts.version) {
cmd = cli.printVersion;
} else {
cmd = cli.main;
}
return cmd().then((errorCode) => {
if (!errorCode && props.shouldExitWithError) {
errorCode = 1;
}
cli.logFinish();
cli.exit(errorCode || 0);
});
};
// TODO: docs
cli.printHelp = () => {
cli.printVersion();
console.log(engine.help({ maxLength: process.stdout.columns }));
return Promise.resolve(0);
};
// TODO: docs
cli.runTests = () => require('./test')();
// TODO: docs
cli.printVersion = () => {
console.log(engine.versionDetails);
return Promise.resolve(0);
};
// TODO: docs
cli.main = () => {
cli.scanFiles();
if (env.sourceFiles.length === 0) {
console.log('There are no input files to process.');
return Promise.resolve(0);
} else {
return cli
.createParser()
.parseFiles()
.processParseResults()
.then(() => {
env.run.finish = new Date();
return 0;
});
}
};
function readPackageJson(filepath) {
const fs = require('fs');
try {
return stripJsonComments(fs.readFileSync(filepath, 'utf8'));
} catch (e) {
log.error(`Unable to read the package file ${filepath}`);
return null;
}
}
function buildSourceList() {
let packageJson;
let sourceFile;
let sourceFiles = env.opts._ ? env.opts._.slice(0) : [];
if (env.conf.source && env.conf.source.include) {
sourceFiles = sourceFiles.concat(env.conf.source.include);
}
// load the user-specified package file, if any
if (env.opts.package) {
packageJson = readPackageJson(env.opts.package);
}
// source files named `package.json` or `README.md` get special treatment, unless the user
// explicitly specified a package and/or README file
for (let i = 0, l = sourceFiles.length; i < l; i++) {
sourceFile = sourceFiles[i];
if (!env.opts.package && /\bpackage\.json$/i.test(sourceFile)) {
packageJson = readPackageJson(sourceFile);
sourceFiles.splice(i--, 1);
}
if (!env.opts.readme && /(\bREADME|\.md)$/i.test(sourceFile)) {
env.opts.readme = sourceFile;
sourceFiles.splice(i--, 1);
}
}
// Resolve the path to the README.
if (env.opts.readme) {
env.opts.readme = path.resolve(env.opts.readme);
}
props.packageJson = packageJson;
return sourceFiles;
}
// TODO: docs
cli.scanFiles = () => {
const { Filter } = require('jsdoc/src/filter');
const { Scanner } = require('jsdoc/src/scanner');
let filter;
let scanner;
env.opts._ = buildSourceList();
// are there any files to scan and parse?
if (env.conf.source && env.opts._.length) {
filter = new Filter(env.conf.source);
scanner = new Scanner();
env.sourceFiles = scanner.scan(
env.opts._,
env.opts.recurse ? env.conf.recurseDepth : undefined,
filter
);
}
return cli;
};
cli.createParser = () => {
const handlers = require('jsdoc/src/handlers');
const parser = require('jsdoc/src/parser');
const plugins = require('jsdoc/plugins');
props.parser = parser.createParser(env.conf.parser, env.conf);
if (env.conf.plugins) {
plugins.installPlugins(env.conf.plugins, props.parser);
}
handlers.attachTo(props.parser);
return cli;
};
cli.parseFiles = () => {
const augment = require('jsdoc/augment');
const borrow = require('jsdoc/borrow');
const Package = require('jsdoc/package').Package;
let docs;
let packageDocs;
props.docs = docs = props.parser.parse(env.sourceFiles, env.opts.encoding);
// If there is no package.json, just create an empty package
packageDocs = new Package(props.packageJson);
packageDocs.files = env.sourceFiles || [];
docs.push(packageDocs);
log.debug('Adding inherited symbols, mixins, and interface implementations...');
augment.augmentAll(docs);
log.debug('Adding borrowed doclets...');
borrow.resolveBorrows(docs);
log.debug('Post-processing complete.');
props.parser.fireProcessingComplete(docs);
return cli;
};
cli.processParseResults = () => {
if (env.opts.explain) {
cli.dumpParseResults();
return Promise.resolve();
} else {
return cli.generateDocs();
}
};
cli.dumpParseResults = () => {
console.log(JSON.stringify(props.docs, null, 4));
return cli;
};
cli.generateDocs = () => {
let message;
const taffy = require('taffydb').taffy;
let template;
env.opts.template = env.opts.template || path.join(__dirname, 'templates', 'default');
try {
// TODO: Just look for a `publish` function in the specified module, not a `publish.js`
// file _and_ a `publish` function.
template = require(`${env.opts.template}/publish`);
} catch (e) {
log.fatal(`Unable to load template: ${e.message}` || e);
}
// templates should include a publish.js file that exports a "publish" function
if (template.publish && typeof template.publish === 'function') {
let publishPromise;
log.info('Generating output files...');
publishPromise = template.publish(taffy(props.docs), env.opts);
return Promise.resolve(publishPromise);
} else {
message =
`${env.opts.template} does not export a "publish" function. ` +
'Global "publish" functions are no longer supported.';
log.fatal(message);
return Promise.reject(new Error(message));
}
};
// TODO: docs
cli.exit = (exitCode, message) => {
if (exitCode > 0) {
props.shouldExitWithError = true;
if (message) {
console.error(message);
}
}
process.on('exit', () => {
if (props.shouldPrintHelp) {
cli.printHelp();
}
process.exit(exitCode);
});
};
return cli;
})();

View File

@ -2,34 +2,34 @@
// initialize the environment for Node.js
(() => {
const fs = require('fs');
const path = require('path');
const fs = require('fs');
const path = require('path');
let env;
let jsdocPath = __dirname;
let env;
let jsdocPath = __dirname;
// Create a custom require method that adds `lib/jsdoc` and `node_modules` to the module
// lookup path. This makes it possible to `require('jsdoc/foo')` from external templates and
// plugins, and within JSDoc itself. It also allows external templates and plugins to
// require JSDoc's module dependencies without installing them locally.
/* eslint-disable no-global-assign, no-redeclare */
require = require('requizzle')({
requirePaths: {
before: [path.join(__dirname, 'lib')],
after: [path.join(__dirname, 'node_modules')]
},
infect: true
});
/* eslint-enable no-global-assign, no-redeclare */
// Create a custom require method that adds `lib/jsdoc` and `node_modules` to the module
// lookup path. This makes it possible to `require('jsdoc/foo')` from external templates and
// plugins, and within JSDoc itself. It also allows external templates and plugins to
// require JSDoc's module dependencies without installing them locally.
/* eslint-disable no-global-assign, no-redeclare */
require = require('requizzle')({
requirePaths: {
before: [path.join(__dirname, 'lib')],
after: [path.join(__dirname, 'node_modules')],
},
infect: true,
});
/* eslint-enable no-global-assign, no-redeclare */
// resolve the path if it's a symlink
if ( fs.statSync(jsdocPath).isSymbolicLink() ) {
jsdocPath = path.resolve( path.dirname(jsdocPath), fs.readlinkSync(jsdocPath) );
}
// resolve the path if it's a symlink
if (fs.statSync(jsdocPath).isSymbolicLink()) {
jsdocPath = path.resolve(path.dirname(jsdocPath), fs.readlinkSync(jsdocPath));
}
env = require('./lib/jsdoc/env');
env.dirname = jsdocPath;
env.args = process.argv.slice(2);
env = require('./lib/jsdoc/env');
env.dirname = jsdocPath;
env.args = process.argv.slice(2);
})();
/**
@ -44,12 +44,9 @@
global.env = (() => require('./lib/jsdoc/env'))();
(async () => {
const cli = require('./cli');
const cli = require('./cli');
cli.setVersionInfo()
.loadConfig()
.configureLogger()
.logStart();
cli.setVersionInfo().loadConfig().configureLogger().logStart();
await cli.runCommand();
await cli.runCommand();
})();

View File

@ -6,116 +6,116 @@
const _ = require('lodash');
const { fromParts, SCOPE, toParts } = require('@jsdoc/core').name;
const jsdoc = {
doclet: require('jsdoc/doclet')
doclet: require('jsdoc/doclet'),
};
const hasOwnProp = Object.prototype.hasOwnProperty;
function mapDependencies(index, propertyName) {
const dependencies = {};
let doc;
let doclets;
const kinds = ['class', 'external', 'interface', 'mixin'];
let len = 0;
const dependencies = {};
let doc;
let doclets;
const kinds = ['class', 'external', 'interface', 'mixin'];
let len = 0;
Object.keys(index).forEach(indexName => {
doclets = index[indexName];
for (let i = 0, ii = doclets.length; i < ii; i++) {
doc = doclets[i];
if (kinds.includes(doc.kind)) {
dependencies[indexName] = {};
if (hasOwnProp.call(doc, propertyName)) {
len = doc[propertyName].length;
for (let j = 0; j < len; j++) {
dependencies[indexName][doc[propertyName][j]] = true;
}
}
}
Object.keys(index).forEach((indexName) => {
doclets = index[indexName];
for (let i = 0, ii = doclets.length; i < ii; i++) {
doc = doclets[i];
if (kinds.includes(doc.kind)) {
dependencies[indexName] = {};
if (hasOwnProp.call(doc, propertyName)) {
len = doc[propertyName].length;
for (let j = 0; j < len; j++) {
dependencies[indexName][doc[propertyName][j]] = true;
}
}
});
}
}
});
return dependencies;
return dependencies;
}
class Sorter {
constructor(dependencies) {
this.dependencies = dependencies;
this.visited = {};
this.sorted = [];
}
constructor(dependencies) {
this.dependencies = dependencies;
this.visited = {};
this.sorted = [];
}
visit(key) {
if (!(key in this.visited)) {
this.visited[key] = true;
visit(key) {
if (!(key in this.visited)) {
this.visited[key] = true;
if (this.dependencies[key]) {
Object.keys(this.dependencies[key]).forEach(path => {
this.visit(path);
});
}
this.sorted.push(key);
}
}
sort() {
Object.keys(this.dependencies).forEach(key => {
this.visit(key);
if (this.dependencies[key]) {
Object.keys(this.dependencies[key]).forEach((path) => {
this.visit(path);
});
}
return this.sorted;
this.sorted.push(key);
}
}
sort() {
Object.keys(this.dependencies).forEach((key) => {
this.visit(key);
});
return this.sorted;
}
}
function sort(dependencies) {
const sorter = new Sorter(dependencies);
const sorter = new Sorter(dependencies);
return sorter.sort();
return sorter.sort();
}
function getMembers(longname, {index}, scopes) {
const memberof = index.memberof[longname] || [];
const members = [];
function getMembers(longname, { index }, scopes) {
const memberof = index.memberof[longname] || [];
const members = [];
memberof.forEach(candidate => {
if (scopes.includes(candidate.scope)) {
members.push(candidate);
}
});
memberof.forEach((candidate) => {
if (scopes.includes(candidate.scope)) {
members.push(candidate);
}
});
return members;
return members;
}
function getDocumentedLongname(longname, {index}) {
const doclets = index.documented[longname] || [];
function getDocumentedLongname(longname, { index }) {
const doclets = index.documented[longname] || [];
return doclets[doclets.length - 1];
return doclets[doclets.length - 1];
}
function addDocletProperty(doclets, propName, value) {
for (let i = 0, l = doclets.length; i < l; i++) {
doclets[i][propName] = value;
}
for (let i = 0, l = doclets.length; i < l; i++) {
doclets[i][propName] = value;
}
}
function reparentDoclet({longname}, child) {
const parts = toParts(child.longname);
function reparentDoclet({ longname }, child) {
const parts = toParts(child.longname);
parts.memberof = longname;
child.memberof = longname;
child.longname = fromParts(parts);
parts.memberof = longname;
child.memberof = longname;
child.longname = fromParts(parts);
}
function parentIsClass({kind}) {
return kind === 'class';
function parentIsClass({ kind }) {
return kind === 'class';
}
function staticToInstance(doclet) {
const parts = toParts(doclet.longname);
const parts = toParts(doclet.longname);
parts.scope = SCOPE.PUNC.INSTANCE;
doclet.longname = fromParts(parts);
doclet.scope = SCOPE.NAMES.INSTANCE;
parts.scope = SCOPE.PUNC.INSTANCE;
doclet.longname = fromParts(parts);
doclet.scope = SCOPE.NAMES.INSTANCE;
}
/**
@ -137,15 +137,14 @@ function staticToInstance(doclet) {
* @return {void}
*/
function updateAddedDoclets(doclet, additions, indexes) {
if (typeof indexes[doclet.longname] !== 'undefined') {
// replace the existing doclet
additions[indexes[doclet.longname]] = doclet;
}
else {
// add the doclet to the array, and track its index
additions.push(doclet);
indexes[doclet.longname] = additions.length - 1;
}
if (typeof indexes[doclet.longname] !== 'undefined') {
// replace the existing doclet
additions[indexes[doclet.longname]] = doclet;
} else {
// add the doclet to the array, and track its index
additions.push(doclet);
indexes[doclet.longname] = additions.length - 1;
}
}
/**
@ -158,11 +157,11 @@ function updateAddedDoclets(doclet, additions, indexes) {
* @return {void}
*/
function updateDocumentedDoclets(doclet, documented) {
if ( !hasOwnProp.call(documented, doclet.longname) ) {
documented[doclet.longname] = [];
}
if (!hasOwnProp.call(documented, doclet.longname)) {
documented[doclet.longname] = [];
}
documented[doclet.longname].push(doclet);
documented[doclet.longname].push(doclet);
}
/**
@ -175,363 +174,360 @@ function updateDocumentedDoclets(doclet, documented) {
* @return {void}
*/
function updateMemberofDoclets(doclet, memberof) {
if (doclet.memberof) {
if ( !hasOwnProp.call(memberof, doclet.memberof) ) {
memberof[doclet.memberof] = [];
}
memberof[doclet.memberof].push(doclet);
if (doclet.memberof) {
if (!hasOwnProp.call(memberof, doclet.memberof)) {
memberof[doclet.memberof] = [];
}
memberof[doclet.memberof].push(doclet);
}
}
function explicitlyInherits(doclets) {
let doclet;
let inherits = false;
let doclet;
let inherits = false;
for (let i = 0, l = doclets.length; i < l; i++) {
doclet = doclets[i];
if (typeof doclet.inheritdoc !== 'undefined' || typeof doclet.override !== 'undefined') {
inherits = true;
break;
}
for (let i = 0, l = doclets.length; i < l; i++) {
doclet = doclets[i];
if (typeof doclet.inheritdoc !== 'undefined' || typeof doclet.override !== 'undefined') {
inherits = true;
break;
}
}
return inherits;
return inherits;
}
function changeMemberof(longname, newMemberof) {
const atoms = toParts(longname);
const atoms = toParts(longname);
atoms.memberof = newMemberof;
atoms.memberof = newMemberof;
return fromParts(atoms);
return fromParts(atoms);
}
// TODO: try to reduce overlap with similar methods
function getInheritedAdditions(doclets, docs, {documented, memberof}) {
let additionIndexes;
const additions = [];
let childDoclet;
let childLongname;
let doc;
let parentDoclet;
let parentMembers;
let parents;
let member;
let parts;
function getInheritedAdditions(doclets, docs, { documented, memberof }) {
let additionIndexes;
const additions = [];
let childDoclet;
let childLongname;
let doc;
let parentDoclet;
let parentMembers;
let parents;
let member;
let parts;
// doclets will be undefined if the inherited symbol isn't documented
doclets = doclets || [];
// doclets will be undefined if the inherited symbol isn't documented
doclets = doclets || [];
for (let i = 0, ii = doclets.length; i < ii; i++) {
doc = doclets[i];
parents = doc.augments;
for (let i = 0, ii = doclets.length; i < ii; i++) {
doc = doclets[i];
parents = doc.augments;
if ( parents && (doc.kind === 'class' || doc.kind === 'interface') ) {
// reset the lookup table of added doclet indexes by longname
additionIndexes = {};
if (parents && (doc.kind === 'class' || doc.kind === 'interface')) {
// reset the lookup table of added doclet indexes by longname
additionIndexes = {};
for (let j = 0, jj = parents.length; j < jj; j++) {
parentMembers = getMembers(parents[j], docs, ['instance']);
for (let j = 0, jj = parents.length; j < jj; j++) {
parentMembers = getMembers(parents[j], docs, ['instance']);
for (let k = 0, kk = parentMembers.length; k < kk; k++) {
parentDoclet = parentMembers[k];
for (let k = 0, kk = parentMembers.length; k < kk; k++) {
parentDoclet = parentMembers[k];
// We only care about symbols that are documented.
if (parentDoclet.undocumented) {
continue;
}
// We only care about symbols that are documented.
if (parentDoclet.undocumented) {
continue;
}
childLongname = changeMemberof(parentDoclet.longname, doc.longname);
childDoclet = getDocumentedLongname(childLongname, docs) || {};
childLongname = changeMemberof(parentDoclet.longname, doc.longname);
childDoclet = getDocumentedLongname(childLongname, docs) || {};
// We don't want to fold in properties from the child doclet if it had an
// `@inheritdoc` tag.
if (hasOwnProp.call(childDoclet, 'inheritdoc')) {
childDoclet = {};
}
// We don't want to fold in properties from the child doclet if it had an
// `@inheritdoc` tag.
if (hasOwnProp.call(childDoclet, 'inheritdoc')) {
childDoclet = {};
}
member = jsdoc.doclet.combine(childDoclet, parentDoclet);
member = jsdoc.doclet.combine(childDoclet, parentDoclet);
if (!member.inherited) {
member.inherits = member.longname;
}
member.inherited = true;
if (!member.inherited) {
member.inherits = member.longname;
}
member.inherited = true;
member.memberof = doc.longname;
parts = toParts(member.longname);
parts.memberof = doc.longname;
member.longname = fromParts(parts);
member.memberof = doc.longname;
parts = toParts(member.longname);
parts.memberof = doc.longname;
member.longname = fromParts(parts);
// Indicate what the descendant is overriding. (We only care about the closest
// ancestor. For classes A > B > C, if B#a overrides A#a, and C#a inherits B#a,
// we don't want the doclet for C#a to say that it overrides A#a.)
if ( hasOwnProp.call(docs.index.longname, member.longname) ) {
member.overrides = parentDoclet.longname;
}
else {
delete member.overrides;
}
// Indicate what the descendant is overriding. (We only care about the closest
// ancestor. For classes A > B > C, if B#a overrides A#a, and C#a inherits B#a,
// we don't want the doclet for C#a to say that it overrides A#a.)
if (hasOwnProp.call(docs.index.longname, member.longname)) {
member.overrides = parentDoclet.longname;
} else {
delete member.overrides;
}
// Add the ancestor's docs unless the descendant overrides the ancestor AND
// documents the override.
if ( !hasOwnProp.call(documented, member.longname) ) {
updateAddedDoclets(member, additions, additionIndexes);
updateDocumentedDoclets(member, documented);
updateMemberofDoclets(member, memberof);
}
// If the descendant used an @inheritdoc or @override tag, add the ancestor's
// docs, and ignore the existing doclets.
else if ( explicitlyInherits(documented[member.longname]) ) {
// Ignore any existing doclets. (This is safe because we only get here if
// `member.longname` is an own property of `documented`.)
addDocletProperty(documented[member.longname], 'ignore', true);
// Add the ancestor's docs unless the descendant overrides the ancestor AND
// documents the override.
if (!hasOwnProp.call(documented, member.longname)) {
updateAddedDoclets(member, additions, additionIndexes);
updateDocumentedDoclets(member, documented);
updateMemberofDoclets(member, memberof);
}
// If the descendant used an @inheritdoc or @override tag, add the ancestor's
// docs, and ignore the existing doclets.
else if (explicitlyInherits(documented[member.longname])) {
// Ignore any existing doclets. (This is safe because we only get here if
// `member.longname` is an own property of `documented`.)
addDocletProperty(documented[member.longname], 'ignore', true);
updateAddedDoclets(member, additions, additionIndexes);
updateDocumentedDoclets(member, documented);
updateMemberofDoclets(member, memberof);
updateAddedDoclets(member, additions, additionIndexes);
updateDocumentedDoclets(member, documented);
updateMemberofDoclets(member, memberof);
// Remove property that's no longer accurate.
if (member.virtual) {
delete member.virtual;
}
// Remove properties that we no longer need.
if (member.inheritdoc) {
delete member.inheritdoc;
}
if (member.override) {
delete member.override;
}
}
// If the descendant overrides the ancestor and documents the override,
// update the doclets to indicate what the descendant is overriding.
else {
addDocletProperty(documented[member.longname], 'overrides',
parentDoclet.longname);
}
}
// Remove property that's no longer accurate.
if (member.virtual) {
delete member.virtual;
}
// Remove properties that we no longer need.
if (member.inheritdoc) {
delete member.inheritdoc;
}
if (member.override) {
delete member.override;
}
}
// If the descendant overrides the ancestor and documents the override,
// update the doclets to indicate what the descendant is overriding.
else {
addDocletProperty(documented[member.longname], 'overrides', parentDoclet.longname);
}
}
}
}
}
return additions;
return additions;
}
function updateMixes(mixedDoclet, mixedLongname) {
let idx;
let mixedName;
let names;
let idx;
let mixedName;
let names;
// take the fast path if there's no array of mixed-in longnames
if (!mixedDoclet.mixes) {
mixedDoclet.mixes = [mixedLongname];
// take the fast path if there's no array of mixed-in longnames
if (!mixedDoclet.mixes) {
mixedDoclet.mixes = [mixedLongname];
} else {
// find the short name of the longname we're mixing in
mixedName = toParts(mixedLongname).name;
// find the short name of each previously mixed-in symbol
// TODO: why do we run a map if we always shorten the same value? this looks like a bug...
names = mixedDoclet.mixes.map(() => toParts(mixedDoclet.longname).name);
// if we're mixing `myMethod` into `MixinC` from `MixinB`, and `MixinB` had the method mixed
// in from `MixinA`, don't show `MixinA.myMethod` in the `mixes` list
idx = names.indexOf(mixedName);
if (idx !== -1) {
mixedDoclet.mixes.splice(idx, 1);
}
else {
// find the short name of the longname we're mixing in
mixedName = toParts(mixedLongname).name;
// find the short name of each previously mixed-in symbol
// TODO: why do we run a map if we always shorten the same value? this looks like a bug...
names = mixedDoclet.mixes.map(() => toParts(mixedDoclet.longname).name);
// if we're mixing `myMethod` into `MixinC` from `MixinB`, and `MixinB` had the method mixed
// in from `MixinA`, don't show `MixinA.myMethod` in the `mixes` list
idx = names.indexOf(mixedName);
if (idx !== -1) {
mixedDoclet.mixes.splice(idx, 1);
}
mixedDoclet.mixes.push(mixedLongname);
}
mixedDoclet.mixes.push(mixedLongname);
}
}
// TODO: try to reduce overlap with similar methods
function getMixedInAdditions(mixinDoclets, allDoclets, {documented, memberof}) {
let additionIndexes;
const additions = [];
const commentedDoclets = documented;
let doclet;
let mixedDoclet;
let mixedDoclets;
let mixes;
function getMixedInAdditions(mixinDoclets, allDoclets, { documented, memberof }) {
let additionIndexes;
const additions = [];
const commentedDoclets = documented;
let doclet;
let mixedDoclet;
let mixedDoclets;
let mixes;
// mixinDoclets will be undefined if the mixed-in symbol isn't documented
mixinDoclets = mixinDoclets || [];
// mixinDoclets will be undefined if the mixed-in symbol isn't documented
mixinDoclets = mixinDoclets || [];
for (let i = 0, ii = mixinDoclets.length; i < ii; i++) {
doclet = mixinDoclets[i];
mixes = doclet.mixes;
for (let i = 0, ii = mixinDoclets.length; i < ii; i++) {
doclet = mixinDoclets[i];
mixes = doclet.mixes;
if (mixes) {
// reset the lookup table of added doclet indexes by longname
additionIndexes = {};
if (mixes) {
// reset the lookup table of added doclet indexes by longname
additionIndexes = {};
for (let j = 0, jj = mixes.length; j < jj; j++) {
mixedDoclets = getMembers(mixes[j], allDoclets, ['static']);
for (let j = 0, jj = mixes.length; j < jj; j++) {
mixedDoclets = getMembers(mixes[j], allDoclets, ['static']);
for (let k = 0, kk = mixedDoclets.length; k < kk; k++) {
// We only care about symbols that are documented.
if (mixedDoclets[k].undocumented) {
continue;
}
for (let k = 0, kk = mixedDoclets.length; k < kk; k++) {
// We only care about symbols that are documented.
if (mixedDoclets[k].undocumented) {
continue;
}
mixedDoclet = _.cloneDeep(mixedDoclets[k]);
mixedDoclet = _.cloneDeep(mixedDoclets[k]);
updateMixes(mixedDoclet, mixedDoclet.longname);
mixedDoclet.mixed = true;
updateMixes(mixedDoclet, mixedDoclet.longname);
mixedDoclet.mixed = true;
reparentDoclet(doclet, mixedDoclet);
reparentDoclet(doclet, mixedDoclet);
// if we're mixing into a class, treat the mixed-in symbol as an instance member
if (parentIsClass(doclet)) {
staticToInstance(mixedDoclet);
}
// if we're mixing into a class, treat the mixed-in symbol as an instance member
if (parentIsClass(doclet)) {
staticToInstance(mixedDoclet);
}
updateAddedDoclets(mixedDoclet, additions, additionIndexes);
updateDocumentedDoclets(mixedDoclet, commentedDoclets);
updateMemberofDoclets(mixedDoclet, memberof);
}
}
updateAddedDoclets(mixedDoclet, additions, additionIndexes);
updateDocumentedDoclets(mixedDoclet, commentedDoclets);
updateMemberofDoclets(mixedDoclet, memberof);
}
}
}
}
return additions;
return additions;
}
function updateImplements(implDoclets, implementedLongname) {
if ( !Array.isArray(implDoclets) ) {
implDoclets = [implDoclets];
if (!Array.isArray(implDoclets)) {
implDoclets = [implDoclets];
}
implDoclets.forEach((implDoclet) => {
if (!hasOwnProp.call(implDoclet, 'implements')) {
implDoclet.implements = [];
}
implDoclets.forEach(implDoclet => {
if ( !hasOwnProp.call(implDoclet, 'implements') ) {
implDoclet.implements = [];
}
if (!implDoclet.implements.includes(implementedLongname)) {
implDoclet.implements.push(implementedLongname);
}
});
if (!implDoclet.implements.includes(implementedLongname)) {
implDoclet.implements.push(implementedLongname);
}
});
}
// TODO: try to reduce overlap with similar methods
function getImplementedAdditions(implDoclets, allDoclets, {documented, memberof}) {
let additionIndexes;
const additions = [];
let childDoclet;
let childLongname;
const commentedDoclets = documented;
let doclet;
let implementations;
let implExists;
let implementationDoclet;
let interfaceDoclets;
let parentDoclet;
function getImplementedAdditions(implDoclets, allDoclets, { documented, memberof }) {
let additionIndexes;
const additions = [];
let childDoclet;
let childLongname;
const commentedDoclets = documented;
let doclet;
let implementations;
let implExists;
let implementationDoclet;
let interfaceDoclets;
let parentDoclet;
// interfaceDoclets will be undefined if the implemented symbol isn't documented
implDoclets = implDoclets || [];
// interfaceDoclets will be undefined if the implemented symbol isn't documented
implDoclets = implDoclets || [];
for (let i = 0, ii = implDoclets.length; i < ii; i++) {
doclet = implDoclets[i];
implementations = doclet.implements;
for (let i = 0, ii = implDoclets.length; i < ii; i++) {
doclet = implDoclets[i];
implementations = doclet.implements;
if (implementations) {
// reset the lookup table of added doclet indexes by longname
additionIndexes = {};
if (implementations) {
// reset the lookup table of added doclet indexes by longname
additionIndexes = {};
for (let j = 0, jj = implementations.length; j < jj; j++) {
interfaceDoclets = getMembers(implementations[j], allDoclets, ['instance']);
for (let j = 0, jj = implementations.length; j < jj; j++) {
interfaceDoclets = getMembers(implementations[j], allDoclets, ['instance']);
for (let k = 0, kk = interfaceDoclets.length; k < kk; k++) {
parentDoclet = interfaceDoclets[k];
for (let k = 0, kk = interfaceDoclets.length; k < kk; k++) {
parentDoclet = interfaceDoclets[k];
// We only care about symbols that are documented.
if (parentDoclet.undocumented) {
continue;
}
// We only care about symbols that are documented.
if (parentDoclet.undocumented) {
continue;
}
childLongname = changeMemberof(parentDoclet.longname, doclet.longname);
childDoclet = getDocumentedLongname(childLongname, allDoclets) || {};
childLongname = changeMemberof(parentDoclet.longname, doclet.longname);
childDoclet = getDocumentedLongname(childLongname, allDoclets) || {};
// We don't want to fold in properties from the child doclet if it had an
// `@inheritdoc` tag.
if (hasOwnProp.call(childDoclet, 'inheritdoc')) {
childDoclet = {};
}
// We don't want to fold in properties from the child doclet if it had an
// `@inheritdoc` tag.
if (hasOwnProp.call(childDoclet, 'inheritdoc')) {
childDoclet = {};
}
implementationDoclet = jsdoc.doclet.combine(childDoclet, parentDoclet);
implementationDoclet = jsdoc.doclet.combine(childDoclet, parentDoclet);
reparentDoclet(doclet, implementationDoclet);
updateImplements(implementationDoclet, parentDoclet.longname);
reparentDoclet(doclet, implementationDoclet);
updateImplements(implementationDoclet, parentDoclet.longname);
// If there's no implementation, move along.
implExists = hasOwnProp.call(allDoclets.index.longname,
implementationDoclet.longname);
if (!implExists) {
continue;
}
// If there's no implementation, move along.
implExists = hasOwnProp.call(allDoclets.index.longname, implementationDoclet.longname);
if (!implExists) {
continue;
}
// Add the interface's docs unless the implementation is already documented.
if ( !hasOwnProp.call(commentedDoclets, implementationDoclet.longname) ) {
updateAddedDoclets(implementationDoclet, additions, additionIndexes);
updateDocumentedDoclets(implementationDoclet, commentedDoclets);
updateMemberofDoclets(implementationDoclet, memberof);
}
// If the implementation used an @inheritdoc or @override tag, add the
// interface's docs, and ignore the existing doclets.
else if ( explicitlyInherits(commentedDoclets[implementationDoclet.longname]) ) {
// Ignore any existing doclets. (This is safe because we only get here if
// `implementationDoclet.longname` is an own property of
// `commentedDoclets`.)
addDocletProperty(commentedDoclets[implementationDoclet.longname], 'ignore',
true);
// Add the interface's docs unless the implementation is already documented.
if (!hasOwnProp.call(commentedDoclets, implementationDoclet.longname)) {
updateAddedDoclets(implementationDoclet, additions, additionIndexes);
updateDocumentedDoclets(implementationDoclet, commentedDoclets);
updateMemberofDoclets(implementationDoclet, memberof);
}
// If the implementation used an @inheritdoc or @override tag, add the
// interface's docs, and ignore the existing doclets.
else if (explicitlyInherits(commentedDoclets[implementationDoclet.longname])) {
// Ignore any existing doclets. (This is safe because we only get here if
// `implementationDoclet.longname` is an own property of
// `commentedDoclets`.)
addDocletProperty(commentedDoclets[implementationDoclet.longname], 'ignore', true);
updateAddedDoclets(implementationDoclet, additions, additionIndexes);
updateDocumentedDoclets(implementationDoclet, commentedDoclets);
updateMemberofDoclets(implementationDoclet, memberof);
updateAddedDoclets(implementationDoclet, additions, additionIndexes);
updateDocumentedDoclets(implementationDoclet, commentedDoclets);
updateMemberofDoclets(implementationDoclet, memberof);
// Remove property that's no longer accurate.
if (implementationDoclet.virtual) {
delete implementationDoclet.virtual;
}
// Remove properties that we no longer need.
if (implementationDoclet.inheritdoc) {
delete implementationDoclet.inheritdoc;
}
if (implementationDoclet.override) {
delete implementationDoclet.override;
}
}
// If there's an implementation, and it's documented, update the doclets to
// indicate what the implementation is implementing.
else {
updateImplements(commentedDoclets[implementationDoclet.longname],
parentDoclet.longname);
}
}
// Remove property that's no longer accurate.
if (implementationDoclet.virtual) {
delete implementationDoclet.virtual;
}
// Remove properties that we no longer need.
if (implementationDoclet.inheritdoc) {
delete implementationDoclet.inheritdoc;
}
if (implementationDoclet.override) {
delete implementationDoclet.override;
}
}
// If there's an implementation, and it's documented, update the doclets to
// indicate what the implementation is implementing.
else {
updateImplements(
commentedDoclets[implementationDoclet.longname],
parentDoclet.longname
);
}
}
}
}
}
return additions;
return additions;
}
function augment(doclets, propertyName, docletFinder) {
const index = doclets.index.longname;
const dependencies = sort( mapDependencies(index, propertyName) );
const index = doclets.index.longname;
const dependencies = sort(mapDependencies(index, propertyName));
dependencies.forEach(depName => {
const additions = docletFinder(index[depName], doclets, doclets.index);
dependencies.forEach((depName) => {
const additions = docletFinder(index[depName], doclets, doclets.index);
additions.forEach(addition => {
const longname = addition.longname;
additions.forEach((addition) => {
const longname = addition.longname;
if ( !hasOwnProp.call(index, longname) ) {
index[longname] = [];
}
index[longname].push(addition);
doclets.push(addition);
});
if (!hasOwnProp.call(index, longname)) {
index[longname] = [];
}
index[longname].push(addition);
doclets.push(addition);
});
});
}
/**
@ -544,8 +540,8 @@ function augment(doclets, propertyName, docletFinder) {
* @param {!Object} doclets.index - The doclet index.
* @return {void}
*/
exports.addInherited = doclets => {
augment(doclets, 'augments', getInheritedAdditions);
exports.addInherited = (doclets) => {
augment(doclets, 'augments', getInheritedAdditions);
};
/**
@ -563,8 +559,8 @@ exports.addInherited = doclets => {
* @param {!Object} doclets.index - The doclet index.
* @return {void}
*/
exports.addMixedIn = doclets => {
augment(doclets, 'mixes', getMixedInAdditions);
exports.addMixedIn = (doclets) => {
augment(doclets, 'mixes', getMixedInAdditions);
};
/**
@ -584,8 +580,8 @@ exports.addMixedIn = doclets => {
* @param {!Object} doclets.index - The doclet index.
* @return {void}
*/
exports.addImplemented = doclets => {
augment(doclets, 'implements', getImplementedAdditions);
exports.addImplemented = (doclets) => {
augment(doclets, 'implements', getImplementedAdditions);
};
/**
@ -599,10 +595,10 @@ exports.addImplemented = doclets => {
*
* @return {void}
*/
exports.augmentAll = doclets => {
exports.addMixedIn(doclets);
exports.addImplemented(doclets);
exports.addInherited(doclets);
// look for implemented doclets again, in case we inherited an interface
exports.addImplemented(doclets);
exports.augmentAll = (doclets) => {
exports.addMixedIn(doclets);
exports.addImplemented(doclets);
exports.addInherited(doclets);
// look for implemented doclets again, in case we inherited an interface
exports.addImplemented(doclets);
};

View File

@ -5,35 +5,34 @@
const _ = require('lodash');
const { SCOPE } = require('@jsdoc/core').name;
function cloneBorrowedDoclets({borrowed, longname}, doclets) {
borrowed.forEach(({from, as}) => {
const borrowedDoclets = doclets.index.longname[from];
let borrowedAs = as || from;
let parts;
let scopePunc;
function cloneBorrowedDoclets({ borrowed, longname }, doclets) {
borrowed.forEach(({ from, as }) => {
const borrowedDoclets = doclets.index.longname[from];
let borrowedAs = as || from;
let parts;
let scopePunc;
if (borrowedDoclets) {
borrowedAs = borrowedAs.replace(/^prototype\./, SCOPE.PUNC.INSTANCE);
_.cloneDeep(borrowedDoclets).forEach(clone => {
// TODO: this will fail on longnames like '"Foo#bar".baz'
parts = borrowedAs.split(SCOPE.PUNC.INSTANCE);
if (borrowedDoclets) {
borrowedAs = borrowedAs.replace(/^prototype\./, SCOPE.PUNC.INSTANCE);
_.cloneDeep(borrowedDoclets).forEach((clone) => {
// TODO: this will fail on longnames like '"Foo#bar".baz'
parts = borrowedAs.split(SCOPE.PUNC.INSTANCE);
if (parts.length === 2) {
clone.scope = SCOPE.NAMES.INSTANCE;
scopePunc = SCOPE.PUNC.INSTANCE;
}
else {
clone.scope = SCOPE.NAMES.STATIC;
scopePunc = SCOPE.PUNC.STATIC;
}
clone.name = parts.pop();
clone.memberof = longname;
clone.longname = clone.memberof + scopePunc + clone.name;
doclets.push(clone);
});
if (parts.length === 2) {
clone.scope = SCOPE.NAMES.INSTANCE;
scopePunc = SCOPE.PUNC.INSTANCE;
} else {
clone.scope = SCOPE.NAMES.STATIC;
scopePunc = SCOPE.PUNC.STATIC;
}
});
clone.name = parts.pop();
clone.memberof = longname;
clone.longname = clone.memberof + scopePunc + clone.name;
doclets.push(clone);
});
}
});
}
/**
@ -42,11 +41,11 @@ function cloneBorrowedDoclets({borrowed, longname}, doclets) {
moving docs from the "borrowed" array and into the general docs, then
deleting the "borrowed" array.
*/
exports.resolveBorrows = doclets => {
for (let doclet of doclets.index.borrowed) {
cloneBorrowedDoclets(doclet, doclets);
delete doclet.borrowed;
}
exports.resolveBorrows = (doclets) => {
for (let doclet of doclets.index.borrowed) {
cloneBorrowedDoclets(doclet, doclets);
delete doclet.borrowed;
}
doclets.index.borrowed = [];
doclets.index.borrowed = [];
};

File diff suppressed because it is too large Load Diff

View File

@ -5,73 +5,73 @@
* @module jsdoc/env
*/
module.exports = {
/**
* The times at which JSDoc started and finished.
*
* @type {Object}
* @property {Date} start - The time at which JSDoc started running.
* @property {Date} finish - The time at which JSDoc finished running.
*/
run: {
start: new Date(),
finish: null
},
/**
* The times at which JSDoc started and finished.
*
* @type {Object}
* @property {Date} start - The time at which JSDoc started running.
* @property {Date} finish - The time at which JSDoc finished running.
*/
run: {
start: new Date(),
finish: null,
},
/**
* The command-line arguments passed to JSDoc.
*
* @type {Array<*>}
*/
args: [],
/**
* The command-line arguments passed to JSDoc.
*
* @type {Array<*>}
*/
args: [],
/**
* The data parsed from JSDoc's configuration file.
*
* @type Object<string, *>
*/
conf: {},
/**
* The data parsed from JSDoc's configuration file.
*
* @type Object<string, *>
*/
conf: {},
/**
* The absolute path to the base directory in which JSDoc is located. Set at startup.
*
* @private
* @type {string}
*/
dirname: null,
/**
* The absolute path to the base directory in which JSDoc is located. Set at startup.
*
* @private
* @type {string}
*/
dirname: null,
/**
* The user's working directory at the time when JSDoc started running.
*
* @private
* @type {string}
*/
pwd: null,
/**
* The user's working directory at the time when JSDoc started running.
*
* @private
* @type {string}
*/
pwd: null,
/**
* The command-line arguments, parsed into a key/value hash.
*
* @type {Object}
* @example if (global.env.opts.help) { console.log('Helpful message.'); }
*/
opts: {},
/**
* The command-line arguments, parsed into a key/value hash.
*
* @type {Object}
* @example if (global.env.opts.help) { console.log('Helpful message.'); }
*/
opts: {},
/**
* The source files that JSDoc will parse.
*
* @type {Array<string>}
* @memberof env
*/
sourceFiles: [],
/**
* The source files that JSDoc will parse.
*
* @type {Array<string>}
* @memberof env
*/
sourceFiles: [],
/**
* The JSDoc version number and revision date.
*
* @type {Object<string, string>}
* @property {string} number - The JSDoc version number.
* @property {string} revision - The JSDoc revision number, expressed as a UTC date string.
*/
version: {
number: null,
revision: null
}
/**
* The JSDoc version number and revision date.
*
* @type {Object<string, string>}
* @property {string} number - The JSDoc version number.
* @property {string} revision - The JSDoc revision number, expressed as a UTC date string.
*/
version: {
number: null,
revision: null,
},
};

View File

@ -10,13 +10,13 @@ const stripBom = require('strip-bom');
// Collect all of the license information from a `package.json` file.
function getLicenses(packageInfo) {
const licenses = packageInfo.licenses ? packageInfo.licenses.slice(0) : [];
const licenses = packageInfo.licenses ? packageInfo.licenses.slice(0) : [];
if (packageInfo.license) {
licenses.push({ type: packageInfo.license });
}
if (packageInfo.license) {
licenses.push({ type: packageInfo.license });
}
return licenses;
return licenses;
}
/**
@ -63,195 +63,194 @@ function getLicenses(packageInfo) {
* object may not use the format documented here.
*/
class Package {
/**
* @param {string} json - The contents of the `package.json` file.
*/
constructor(json) {
let packageInfo;
/**
* @param {string} json - The contents of the `package.json` file.
* The string identifier that is shared by all `Package` objects.
*
* @readonly
* @default
* @type {string}
*/
constructor(json) {
let packageInfo;
this.kind = 'package';
/**
* The string identifier that is shared by all `Package` objects.
*
* @readonly
* @default
* @type {string}
*/
this.kind = 'package';
try {
packageInfo = JSON.parse(json ? stripBom(json) : '{}');
}
catch (e) {
log.error(`Unable to parse the package file: ${e.message}`);
packageInfo = {};
}
if (packageInfo.name) {
/**
* The package name.
*
* @type {string}
*/
this.name = packageInfo.name;
}
/**
* The unique longname for this `Package` object.
*
* @type {string}
*/
this.longname = `${this.kind}:${this.name}`;
if (packageInfo.author) {
/**
* The author of this package. Contains either a
* {@link module:jsdoc/package.Package~PersonInfo PersonInfo} object or a string with
* information about the author.
*
* @type {(module:jsdoc/package.Package~PersonInfo|string)}
* @since 3.3.0
*/
this.author = packageInfo.author;
}
if (packageInfo.bugs) {
/**
* Information about where to report bugs in the project. May contain a URL, a string, or an
* object with more detailed information.
*
* @type {(string|module:jsdoc/package.Package~BugInfo)}
* @since 3.3.0
*/
this.bugs = packageInfo.bugs;
}
if (packageInfo.contributors) {
/**
* The contributors to this package.
*
* @type {Array.<(module:jsdoc/package.Package~PersonInfo|string)>}
* @since 3.3.0
*/
this.contributors = packageInfo.contributors;
}
if (packageInfo.dependencies) {
/**
* The dependencies for this package.
*
* @type {Object}
* @since 3.3.0
*/
this.dependencies = packageInfo.dependencies;
}
if (packageInfo.description) {
/**
* A brief description of the package.
*
* @type {string}
*/
this.description = packageInfo.description;
}
if (packageInfo.devDependencies) {
/**
* The development dependencies for this package.
*
* @type {Object}
* @since 3.3.0
*/
this.devDependencies = packageInfo.devDependencies;
}
if (packageInfo.engines) {
/**
* The JavaScript engines that this package supports. Each key is a string that identifies
* the engine (for example, `node`). Each value is a
* [semver](https://www.npmjs.org/doc/misc/semver.html)-compliant version number for the
* engine.
*
* @type {Object}
* @since 3.3.0
*/
this.engines = packageInfo.engines;
}
/**
* The source files associated with the package.
*
* New `Package` objects always contain an empty array, regardless of whether the `package.json`
* file includes a `files` property.
*
* After JSDoc parses your input files, it sets this property to a list of paths to your input
* files.
*
* @type {Array.<string>}
*/
this.files = [];
if (packageInfo.homepage) {
/**
* The URL for the package's homepage.
*
* @type {string}
* @since 3.3.0
*/
this.homepage = packageInfo.homepage;
}
if (packageInfo.keywords) {
/**
* Keywords to help users find the package.
*
* @type {Array.<string>}
* @since 3.3.0
*/
this.keywords = packageInfo.keywords;
}
if (packageInfo.license || packageInfo.licenses) {
/**
* 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/package.Package~LicenseInfo>}
*/
this.licenses = getLicenses(packageInfo);
}
if (packageInfo.main) {
/**
* The module ID that provides the primary entry point to the package. For example, if your
* package is a CommonJS module, and the value of this property is `foo`, users should be
* able to load your module with `require('foo')`.
*
* @type {string}
* @since 3.3.0
*/
this.main = packageInfo.main;
}
if (packageInfo.repository) {
/**
* The version-control repository for the package.
*
* @type {module:jsdoc/package.Package~RepositoryInfo}
* @since 3.3.0
*/
this.repository = packageInfo.repository;
}
if (packageInfo.version) {
/**
* The [semver](https://www.npmjs.org/doc/misc/semver.html)-compliant version number of the
* package.
*
* @type {string}
* @since 3.2.0
*/
this.version = packageInfo.version;
}
try {
packageInfo = JSON.parse(json ? stripBom(json) : '{}');
} catch (e) {
log.error(`Unable to parse the package file: ${e.message}`);
packageInfo = {};
}
if (packageInfo.name) {
/**
* The package name.
*
* @type {string}
*/
this.name = packageInfo.name;
}
/**
* The unique longname for this `Package` object.
*
* @type {string}
*/
this.longname = `${this.kind}:${this.name}`;
if (packageInfo.author) {
/**
* The author of this package. Contains either a
* {@link module:jsdoc/package.Package~PersonInfo PersonInfo} object or a string with
* information about the author.
*
* @type {(module:jsdoc/package.Package~PersonInfo|string)}
* @since 3.3.0
*/
this.author = packageInfo.author;
}
if (packageInfo.bugs) {
/**
* Information about where to report bugs in the project. May contain a URL, a string, or an
* object with more detailed information.
*
* @type {(string|module:jsdoc/package.Package~BugInfo)}
* @since 3.3.0
*/
this.bugs = packageInfo.bugs;
}
if (packageInfo.contributors) {
/**
* The contributors to this package.
*
* @type {Array.<(module:jsdoc/package.Package~PersonInfo|string)>}
* @since 3.3.0
*/
this.contributors = packageInfo.contributors;
}
if (packageInfo.dependencies) {
/**
* The dependencies for this package.
*
* @type {Object}
* @since 3.3.0
*/
this.dependencies = packageInfo.dependencies;
}
if (packageInfo.description) {
/**
* A brief description of the package.
*
* @type {string}
*/
this.description = packageInfo.description;
}
if (packageInfo.devDependencies) {
/**
* The development dependencies for this package.
*
* @type {Object}
* @since 3.3.0
*/
this.devDependencies = packageInfo.devDependencies;
}
if (packageInfo.engines) {
/**
* The JavaScript engines that this package supports. Each key is a string that identifies
* the engine (for example, `node`). Each value is a
* [semver](https://www.npmjs.org/doc/misc/semver.html)-compliant version number for the
* engine.
*
* @type {Object}
* @since 3.3.0
*/
this.engines = packageInfo.engines;
}
/**
* The source files associated with the package.
*
* New `Package` objects always contain an empty array, regardless of whether the `package.json`
* file includes a `files` property.
*
* After JSDoc parses your input files, it sets this property to a list of paths to your input
* files.
*
* @type {Array.<string>}
*/
this.files = [];
if (packageInfo.homepage) {
/**
* The URL for the package's homepage.
*
* @type {string}
* @since 3.3.0
*/
this.homepage = packageInfo.homepage;
}
if (packageInfo.keywords) {
/**
* Keywords to help users find the package.
*
* @type {Array.<string>}
* @since 3.3.0
*/
this.keywords = packageInfo.keywords;
}
if (packageInfo.license || packageInfo.licenses) {
/**
* 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/package.Package~LicenseInfo>}
*/
this.licenses = getLicenses(packageInfo);
}
if (packageInfo.main) {
/**
* The module ID that provides the primary entry point to the package. For example, if your
* package is a CommonJS module, and the value of this property is `foo`, users should be
* able to load your module with `require('foo')`.
*
* @type {string}
* @since 3.3.0
*/
this.main = packageInfo.main;
}
if (packageInfo.repository) {
/**
* The version-control repository for the package.
*
* @type {module:jsdoc/package.Package~RepositoryInfo}
* @since 3.3.0
*/
this.repository = packageInfo.repository;
}
if (packageInfo.version) {
/**
* The [semver](https://www.npmjs.org/doc/misc/semver.html)-compliant version number of the
* package.
*
* @type {string}
* @since 3.2.0
*/
this.version = packageInfo.version;
}
}
}
exports.Package = Package;

View File

@ -5,31 +5,31 @@
const dictionary = require('jsdoc/tag/dictionary');
function addHandlers(handlers, parser) {
Object.keys(handlers).forEach(eventName => {
parser.on(eventName, handlers[eventName]);
});
Object.keys(handlers).forEach((eventName) => {
parser.on(eventName, handlers[eventName]);
});
}
exports.installPlugins = (plugins, parser) => {
let plugin;
let plugin;
for (let pluginModule of plugins) {
plugin = require(pluginModule);
for (let pluginModule of plugins) {
plugin = require(pluginModule);
// allow user-defined plugins to...
// ...register event handlers
if (plugin.handlers) {
addHandlers(plugin.handlers, parser);
}
// ...define tags
if (plugin.defineTags) {
plugin.defineTags(dictionary);
}
// ...add an ESTree node visitor
if (plugin.astNodeVisitor) {
parser.addAstNodeVisitor(plugin.astNodeVisitor);
}
// allow user-defined plugins to...
// ...register event handlers
if (plugin.handlers) {
addHandlers(plugin.handlers, parser);
}
// ...define tags
if (plugin.defineTags) {
plugin.defineTags(dictionary);
}
// ...add an ESTree node visitor
if (plugin.astNodeVisitor) {
parser.addAstNodeVisitor(plugin.astNodeVisitor);
}
}
};

File diff suppressed because it is too large Load Diff

View File

@ -4,60 +4,59 @@
const path = require('path');
function makeRegExp(config) {
let regExp = null;
let regExp = null;
if (config) {
regExp = (typeof config === 'string') ? new RegExp(config) : config;
}
if (config) {
regExp = typeof config === 'string' ? new RegExp(config) : config;
}
return regExp;
return regExp;
}
/**
* @alias module:jsdoc/src/filter.Filter
*/
class Filter {
/**
* @param {Object} opts
* @param {string[]} opts.exclude - Specific files to exclude.
* @param {(string|RegExp)} opts.includePattern
* @param {(string|RegExp)} opts.excludePattern
*/
constructor({exclude, includePattern, excludePattern}) {
this._cwd = process.cwd();
this.exclude = exclude && Array.isArray(exclude) ?
exclude.map($ => path.resolve(this._cwd, $)) :
null;
this.includePattern = makeRegExp(includePattern);
this.excludePattern = makeRegExp(excludePattern);
/**
* @param {Object} opts
* @param {string[]} opts.exclude - Specific files to exclude.
* @param {(string|RegExp)} opts.includePattern
* @param {(string|RegExp)} opts.excludePattern
*/
constructor({ exclude, includePattern, excludePattern }) {
this._cwd = process.cwd();
this.exclude =
exclude && Array.isArray(exclude) ? exclude.map(($) => path.resolve(this._cwd, $)) : null;
this.includePattern = makeRegExp(includePattern);
this.excludePattern = makeRegExp(excludePattern);
}
/**
* @param {string} filepath - The filepath to check.
* @returns {boolean} Should the given file be included?
*/
isIncluded(filepath) {
let included = true;
filepath = path.resolve(this._cwd, filepath);
if (this.includePattern && !this.includePattern.test(filepath)) {
included = false;
}
/**
* @param {string} filepath - The filepath to check.
* @returns {boolean} Should the given file be included?
*/
isIncluded(filepath) {
let included = true;
filepath = path.resolve(this._cwd, filepath);
if ( this.includePattern && !this.includePattern.test(filepath) ) {
included = false;
}
if ( this.excludePattern && this.excludePattern.test(filepath) ) {
included = false;
}
if (this.exclude) {
this.exclude.forEach(exclude => {
if ( filepath.indexOf(exclude) === 0 ) {
included = false;
}
});
}
return included;
if (this.excludePattern && this.excludePattern.test(filepath)) {
included = false;
}
if (this.exclude) {
this.exclude.forEach((exclude) => {
if (filepath.indexOf(exclude) === 0) {
included = false;
}
});
}
return included;
}
}
exports.Filter = Filter;

View File

@ -10,37 +10,36 @@ const { Syntax } = require('@jsdoc/parse');
let currentModule = null;
class CurrentModule {
constructor(doclet) {
this.doclet = doclet;
this.longname = doclet.longname;
this.originalName = doclet.meta.code.name || '';
}
constructor(doclet) {
this.doclet = doclet;
this.longname = doclet.longname;
this.originalName = doclet.meta.code.name || '';
}
}
function filterByLongname({longname}) {
// you can't document prototypes
if ( /#$/.test(longname) ) {
return true;
}
function filterByLongname({ longname }) {
// you can't document prototypes
if (/#$/.test(longname)) {
return true;
}
return false;
return false;
}
function createDoclet(comment, e) {
let doclet;
let flatComment;
let msg;
let doclet;
let flatComment;
let msg;
try {
doclet = new Doclet(comment, e);
}
catch (error) {
flatComment = comment.replace(/[\r\n]/g, '');
msg = `cannot create a doclet for the comment "${flatComment}": ${error.message}`;
log.error(msg);
doclet = new Doclet('', e);
}
try {
doclet = new Doclet(comment, e);
} catch (error) {
flatComment = comment.replace(/[\r\n]/g, '');
msg = `cannot create a doclet for the comment "${flatComment}": ${error.message}`;
log.error(msg);
doclet = new Doclet('', e);
}
return doclet;
return doclet;
}
/**
@ -63,301 +62,301 @@ function createDoclet(comment, e) {
* @private
*/
function createSymbolDoclet(comment, e) {
let doclet = createDoclet(comment, e);
let doclet = createDoclet(comment, e);
if (doclet.name) {
// try again, without the comment
e.comment = '@undocumented';
doclet = createDoclet(e.comment, e);
}
if (doclet.name) {
// try again, without the comment
e.comment = '@undocumented';
doclet = createDoclet(e.comment, e);
}
return doclet;
return doclet;
}
function setCurrentModule(doclet) {
if (doclet.kind === 'module') {
currentModule = new CurrentModule(doclet);
}
if (doclet.kind === 'module') {
currentModule = new CurrentModule(doclet);
}
}
function setModuleScopeMemberOf(parser, doclet) {
let parentDoclet;
let skipMemberof;
let parentDoclet;
let skipMemberof;
// handle module symbols that are _not_ assigned to module.exports
if (currentModule && currentModule.longname !== doclet.name) {
if (!doclet.scope) {
// is this a method definition? if so, we usually get the scope from the node directly
if (doclet.meta && doclet.meta.code && doclet.meta.code.node &&
doclet.meta.code.node.type === Syntax.MethodDefinition) {
// special case for constructors of classes that have @alias tags
if (doclet.meta.code.node.kind === 'constructor') {
parentDoclet = parser._getDocletById(
doclet.meta.code.node.parent.parent.nodeId
);
// handle module symbols that are _not_ assigned to module.exports
if (currentModule && currentModule.longname !== doclet.name) {
if (!doclet.scope) {
// is this a method definition? if so, we usually get the scope from the node directly
if (
doclet.meta &&
doclet.meta.code &&
doclet.meta.code.node &&
doclet.meta.code.node.type === Syntax.MethodDefinition
) {
// special case for constructors of classes that have @alias tags
if (doclet.meta.code.node.kind === 'constructor') {
parentDoclet = parser._getDocletById(doclet.meta.code.node.parent.parent.nodeId);
if (parentDoclet && parentDoclet.alias) {
// the constructor should use the same name as the class
doclet.addTag('alias', parentDoclet.alias);
doclet.addTag('name', parentDoclet.alias);
if (parentDoclet && parentDoclet.alias) {
// the constructor should use the same name as the class
doclet.addTag('alias', parentDoclet.alias);
doclet.addTag('name', parentDoclet.alias);
// and we shouldn't try to set a memberof value
skipMemberof = true;
}
}
else if (doclet.meta.code.node.static) {
doclet.addTag('static');
}
else {
doclet.addTag('instance');
}
}
// is this something that the module exports? if so, it's a static member
else if (doclet.meta && doclet.meta.code && doclet.meta.code.node &&
doclet.meta.code.node.parent &&
doclet.meta.code.node.parent.type === Syntax.ExportNamedDeclaration) {
doclet.addTag('static');
}
// otherwise, it must be an inner member
else {
doclet.addTag('inner');
}
}
// if the doclet isn't a memberof anything yet, and it's not a global, it must be a memberof
// the current module (unless we were told to skip adding memberof)
if (!doclet.memberof && doclet.scope !== SCOPE.NAMES.GLOBAL && !skipMemberof) {
doclet.addTag('memberof', currentModule.longname);
// and we shouldn't try to set a memberof value
skipMemberof = true;
}
} else if (doclet.meta.code.node.static) {
doclet.addTag('static');
} else {
doclet.addTag('instance');
}
}
// is this something that the module exports? if so, it's a static member
else if (
doclet.meta &&
doclet.meta.code &&
doclet.meta.code.node &&
doclet.meta.code.node.parent &&
doclet.meta.code.node.parent.type === Syntax.ExportNamedDeclaration
) {
doclet.addTag('static');
}
// otherwise, it must be an inner member
else {
doclet.addTag('inner');
}
}
// if the doclet isn't a memberof anything yet, and it's not a global, it must be a memberof
// the current module (unless we were told to skip adding memberof)
if (!doclet.memberof && doclet.scope !== SCOPE.NAMES.GLOBAL && !skipMemberof) {
doclet.addTag('memberof', currentModule.longname);
}
}
}
function setDefaultScope(doclet) {
// module doclets don't get a default scope
if (!doclet.scope && doclet.kind !== 'module') {
doclet.setScope(SCOPE.NAMES.GLOBAL);
}
// module doclets don't get a default scope
if (!doclet.scope && doclet.kind !== 'module') {
doclet.setScope(SCOPE.NAMES.GLOBAL);
}
}
function addDoclet(parser, newDoclet) {
let e;
let e;
if (newDoclet) {
setCurrentModule(newDoclet);
e = { doclet: newDoclet };
parser.emit('newDoclet', e);
if (newDoclet) {
setCurrentModule(newDoclet);
e = { doclet: newDoclet };
parser.emit('newDoclet', e);
if ( !e.defaultPrevented && !filterByLongname(e.doclet) ) {
parser.addResult(e.doclet);
}
if (!e.defaultPrevented && !filterByLongname(e.doclet)) {
parser.addResult(e.doclet);
}
}
}
function processAlias(parser, doclet, astNode) {
let memberofName;
let memberofName;
if (doclet.alias === '{@thisClass}') {
memberofName = parser.resolveThis(astNode);
if (doclet.alias === '{@thisClass}') {
memberofName = parser.resolveThis(astNode);
// "class" refers to the owner of the prototype, not the prototype itself
if ( /^(.+?)(\.prototype|#)$/.test(memberofName) ) {
memberofName = RegExp.$1;
}
doclet.alias = memberofName;
// "class" refers to the owner of the prototype, not the prototype itself
if (/^(.+?)(\.prototype|#)$/.test(memberofName)) {
memberofName = RegExp.$1;
}
doclet.alias = memberofName;
}
doclet.addTag('name', doclet.alias);
doclet.postProcess();
doclet.addTag('name', doclet.alias);
doclet.postProcess();
}
// TODO: separate code that resolves `this` from code that resolves the module object
function findSymbolMemberof(parser, doclet, astNode, nameStartsWith, trailingPunc) {
let memberof = '';
let nameAndPunc;
let scopePunc = '';
let memberof = '';
let nameAndPunc;
let scopePunc = '';
// handle computed properties like foo['bar']
if (trailingPunc === '[') {
// we don't know yet whether the symbol is a static or instance member
trailingPunc = null;
// handle computed properties like foo['bar']
if (trailingPunc === '[') {
// we don't know yet whether the symbol is a static or instance member
trailingPunc = null;
}
nameAndPunc = nameStartsWith + (trailingPunc || '');
// remove stuff that indicates module membership (but don't touch the name `module.exports`,
// which identifies the module object itself)
if (doclet.name !== 'module.exports') {
doclet.name = doclet.name.replace(nameAndPunc, '');
}
// like `bar` in:
// exports.bar = 1;
// module.exports.bar = 1;
// module.exports = MyModuleObject; MyModuleObject.bar = 1;
if (nameStartsWith !== 'this' && currentModule && doclet.name !== 'module.exports') {
memberof = currentModule.longname;
scopePunc = SCOPE.PUNC.STATIC;
}
// like: module.exports = 1;
else if (doclet.name === 'module.exports' && currentModule) {
doclet.addTag('name', currentModule.longname);
doclet.postProcess();
} else {
memberof = parser.resolveThis(astNode);
// like the following at the top level of a module:
// this.foo = 1;
if (nameStartsWith === 'this' && currentModule && !memberof) {
memberof = currentModule.longname;
scopePunc = SCOPE.PUNC.STATIC;
} else {
scopePunc = SCOPE.PUNC.INSTANCE;
}
}
nameAndPunc = nameStartsWith + (trailingPunc || '');
// remove stuff that indicates module membership (but don't touch the name `module.exports`,
// which identifies the module object itself)
if (doclet.name !== 'module.exports') {
doclet.name = doclet.name.replace(nameAndPunc, '');
}
// like `bar` in:
// exports.bar = 1;
// module.exports.bar = 1;
// module.exports = MyModuleObject; MyModuleObject.bar = 1;
if (nameStartsWith !== 'this' && currentModule && doclet.name !== 'module.exports') {
memberof = currentModule.longname;
scopePunc = SCOPE.PUNC.STATIC;
}
// like: module.exports = 1;
else if (doclet.name === 'module.exports' && currentModule) {
doclet.addTag('name', currentModule.longname);
doclet.postProcess();
}
else {
memberof = parser.resolveThis(astNode);
// like the following at the top level of a module:
// this.foo = 1;
if (nameStartsWith === 'this' && currentModule && !memberof) {
memberof = currentModule.longname;
scopePunc = SCOPE.PUNC.STATIC;
}
else {
scopePunc = SCOPE.PUNC.INSTANCE;
}
}
return {
memberof: memberof,
scopePunc: scopePunc
};
return {
memberof: memberof,
scopePunc: scopePunc,
};
}
function addSymbolMemberof(parser, doclet, astNode) {
let basename;
let memberof;
let memberofInfo;
let moduleOriginalName = '';
let resolveTargetRegExp;
let scopePunc;
let unresolved;
let basename;
let memberof;
let memberofInfo;
let moduleOriginalName = '';
let resolveTargetRegExp;
let scopePunc;
let unresolved;
if (!astNode) {
return;
}
if (!astNode) {
return;
}
// check to see if the doclet name is an unresolved reference to the module object, or to `this`
// TODO: handle cases where the module object is shadowed in the current scope
if (currentModule) {
moduleOriginalName = `|${currentModule.originalName}`;
}
resolveTargetRegExp = new RegExp(`^((?:module.)?exports|this${moduleOriginalName})(\\.|\\[|$)`);
unresolved = resolveTargetRegExp.exec(doclet.name);
// check to see if the doclet name is an unresolved reference to the module object, or to `this`
// TODO: handle cases where the module object is shadowed in the current scope
if (currentModule) {
moduleOriginalName = `|${currentModule.originalName}`;
}
resolveTargetRegExp = new RegExp(`^((?:module.)?exports|this${moduleOriginalName})(\\.|\\[|$)`);
unresolved = resolveTargetRegExp.exec(doclet.name);
if (unresolved) {
memberofInfo = findSymbolMemberof(parser, doclet, astNode, unresolved[1], unresolved[2]);
memberof = memberofInfo.memberof;
scopePunc = memberofInfo.scopePunc;
if (unresolved) {
memberofInfo = findSymbolMemberof(parser, doclet, astNode, unresolved[1], unresolved[2]);
memberof = memberofInfo.memberof;
scopePunc = memberofInfo.scopePunc;
if (memberof) {
doclet.name = doclet.name ?
memberof + scopePunc + doclet.name :
memberof;
}
}
else {
memberofInfo = parser.astnodeToMemberof(astNode);
basename = memberofInfo.basename;
memberof = memberofInfo.memberof;
}
// if we found a memberof name, apply it to the doclet
if (memberof) {
doclet.addTag('memberof', memberof);
if (basename) {
doclet.name = (doclet.name || '')
.replace(new RegExp(`^${escape(basename)}.`), '');
}
doclet.name = doclet.name ? memberof + scopePunc + doclet.name : memberof;
}
// otherwise, add the defaults for a module (if we're currently in a module)
else {
setModuleScopeMemberOf(parser, doclet);
} else {
memberofInfo = parser.astnodeToMemberof(astNode);
basename = memberofInfo.basename;
memberof = memberofInfo.memberof;
}
// if we found a memberof name, apply it to the doclet
if (memberof) {
doclet.addTag('memberof', memberof);
if (basename) {
doclet.name = (doclet.name || '').replace(new RegExp(`^${escape(basename)}.`), '');
}
}
// otherwise, add the defaults for a module (if we're currently in a module)
else {
setModuleScopeMemberOf(parser, doclet);
}
}
function newSymbolDoclet(parser, docletSrc, e) {
const newDoclet = createSymbolDoclet(docletSrc, e);
const newDoclet = createSymbolDoclet(docletSrc, e);
// if there's an alias, use that as the symbol name
if (newDoclet.alias) {
processAlias(parser, newDoclet, e.astnode);
}
// otherwise, get the symbol name from the code
else if (e.code && typeof e.code.name !== 'undefined' && e.code.name !== '') {
newDoclet.addTag('name', e.code.name);
if (!newDoclet.memberof) {
addSymbolMemberof(parser, newDoclet, e.astnode);
}
newDoclet.postProcess();
}
else {
return false;
// if there's an alias, use that as the symbol name
if (newDoclet.alias) {
processAlias(parser, newDoclet, e.astnode);
}
// otherwise, get the symbol name from the code
else if (e.code && typeof e.code.name !== 'undefined' && e.code.name !== '') {
newDoclet.addTag('name', e.code.name);
if (!newDoclet.memberof) {
addSymbolMemberof(parser, newDoclet, e.astnode);
}
// set the scope to global unless any of the following are true:
// a) the doclet is a memberof something
// b) the doclet represents a module
// c) we're in a module that exports only this symbol
if ( !newDoclet.memberof && newDoclet.kind !== 'module' &&
(!currentModule || currentModule.longname !== newDoclet.name) ) {
newDoclet.scope = SCOPE.NAMES.GLOBAL;
}
newDoclet.postProcess();
} else {
return false;
}
// handle cases where the doclet kind is auto-detected from the node type
if (e.code.kind && newDoclet.kind === 'member') {
newDoclet.kind = e.code.kind;
}
// set the scope to global unless any of the following are true:
// a) the doclet is a memberof something
// b) the doclet represents a module
// c) we're in a module that exports only this symbol
if (
!newDoclet.memberof &&
newDoclet.kind !== 'module' &&
(!currentModule || currentModule.longname !== newDoclet.name)
) {
newDoclet.scope = SCOPE.NAMES.GLOBAL;
}
addDoclet(parser, newDoclet);
e.doclet = newDoclet;
// handle cases where the doclet kind is auto-detected from the node type
if (e.code.kind && newDoclet.kind === 'member') {
newDoclet.kind = e.code.kind;
}
return true;
addDoclet(parser, newDoclet);
e.doclet = newDoclet;
return true;
}
/**
* Attach these event handlers to a particular instance of a parser.
* @param parser
*/
exports.attachTo = parser => {
// Handle JSDoc "virtual comments" that include one of the following:
// + A `@name` tag
// + Another tag that accepts a name, such as `@function`
parser.on('jsdocCommentFound', e => {
const comments = e.comment.split(/@also\b/g);
let newDoclet;
exports.attachTo = (parser) => {
// Handle JSDoc "virtual comments" that include one of the following:
// + A `@name` tag
// + Another tag that accepts a name, such as `@function`
parser.on('jsdocCommentFound', (e) => {
const comments = e.comment.split(/@also\b/g);
let newDoclet;
for (let i = 0, l = comments.length; i < l; i++) {
newDoclet = createDoclet(comments[i], e);
for (let i = 0, l = comments.length; i < l; i++) {
newDoclet = createDoclet(comments[i], e);
// we're only interested in virtual comments here
if (!newDoclet.name) {
continue;
}
// we're only interested in virtual comments here
if (!newDoclet.name) {
continue;
}
// add the default scope/memberof for a module (if we're in a module)
setModuleScopeMemberOf(parser, newDoclet);
newDoclet.postProcess();
// add the default scope/memberof for a module (if we're in a module)
setModuleScopeMemberOf(parser, newDoclet);
newDoclet.postProcess();
// if we _still_ don't have a scope, use the default
setDefaultScope(newDoclet);
// if we _still_ don't have a scope, use the default
setDefaultScope(newDoclet);
addDoclet(parser, newDoclet);
addDoclet(parser, newDoclet);
e.doclet = newDoclet;
}
});
e.doclet = newDoclet;
}
});
// Handle named symbols in the code. May or may not have a JSDoc comment attached.
parser.on('symbolFound', e => {
const comments = e.comment.split(/@also\b/g);
// Handle named symbols in the code. May or may not have a JSDoc comment attached.
parser.on('symbolFound', (e) => {
const comments = e.comment.split(/@also\b/g);
for (let i = 0, l = comments.length; i < l; i++) {
newSymbolDoclet(parser, comments[i], e);
}
});
for (let i = 0, l = comments.length; i < l; i++) {
newSymbolDoclet(parser, comments[i], e);
}
});
parser.on('fileComplete', () => {
currentModule = null;
});
parser.on('fileComplete', () => {
currentModule = null;
});
};

File diff suppressed because it is too large Load Diff

View File

@ -11,54 +11,52 @@ const { statSync } = require('fs');
* @extends module:events.EventEmitter
*/
class Scanner extends EventEmitter {
constructor() {
super();
}
constructor() {
super();
}
/**
* Recursively searches the given searchPaths for js files.
* @param {Array.<string>} searchPaths
* @param {number} [depth]
* @fires sourceFileFound
*/
scan(searchPaths, depth, filter) {
let currentFile;
let filePaths = [];
/**
* Recursively searches the given searchPaths for js files.
* @param {Array.<string>} searchPaths
* @param {number} [depth]
* @fires sourceFileFound
*/
scan(searchPaths, depth, filter) {
let currentFile;
let filePaths = [];
searchPaths = searchPaths || [];
depth = depth || 1;
searchPaths = searchPaths || [];
depth = depth || 1;
searchPaths.forEach($ => {
const filepath = path.resolve(process.cwd(), decodeURIComponent($));
searchPaths.forEach(($) => {
const filepath = path.resolve(process.cwd(), decodeURIComponent($));
try {
currentFile = statSync(filepath);
}
catch (e) {
log.error(`Unable to find the source file or directory ${filepath}`);
try {
currentFile = statSync(filepath);
} catch (e) {
log.error(`Unable to find the source file or directory ${filepath}`);
return;
}
return;
}
if ( currentFile.isFile() ) {
filePaths.push(filepath);
}
else {
filePaths = filePaths.concat(lsSync(filepath, depth));
}
});
if (currentFile.isFile()) {
filePaths.push(filepath);
} else {
filePaths = filePaths.concat(lsSync(filepath, depth));
}
});
filePaths = filePaths.filter($ => filter.isIncluded($));
filePaths = filePaths.filter(($) => filter.isIncluded($));
filePaths = filePaths.filter($ => {
const e = { fileName: $ };
filePaths = filePaths.filter(($) => {
const e = { fileName: $ };
this.emit('sourceFileFound', e);
this.emit('sourceFileFound', e);
return !e.defaultPrevented;
});
return !e.defaultPrevented;
});
return filePaths;
}
return filePaths;
}
}
exports.Scanner = Scanner;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6,115 +6,115 @@ const env = require('jsdoc/env');
const { log } = require('@jsdoc/util');
const path = require('path');
const tag = {
dictionary: require('jsdoc/tag/dictionary'),
validator: require('jsdoc/tag/validator'),
type: require('@jsdoc/tag').type
dictionary: require('jsdoc/tag/dictionary'),
validator: require('jsdoc/tag/validator'),
type: require('@jsdoc/tag').type,
};
// Check whether the text is the same as a symbol name with leading or trailing whitespace. If so,
// the whitespace must be preserved, and the text cannot be trimmed.
function mustPreserveWhitespace(text, meta) {
return meta && meta.code && meta.code.name === text && text.match(/(?:^\s+)|(?:\s+$)/);
return meta && meta.code && meta.code.name === text && text.match(/(?:^\s+)|(?:\s+$)/);
}
function trim(text, opts, meta) {
let indentMatcher;
let match;
let indentMatcher;
let match;
opts = opts || {};
text = String(typeof text === 'undefined' ? '' : text);
opts = opts || {};
text = String(typeof text === 'undefined' ? '' : text);
if ( mustPreserveWhitespace(text, meta) ) {
text = `"${text}"`;
}
else if (opts.keepsWhitespace) {
text = text.replace(/^[\n\r\f]+|[\n\r\f]+$/g, '');
if (opts.removesIndent) {
match = text.match(/^([ \t]+)/);
if (match && match[1]) {
indentMatcher = new RegExp(`^${match[1]}`, 'gm');
text = text.replace(indentMatcher, '');
}
}
}
else {
text = text.replace(/^\s+|\s+$/g, '');
if (mustPreserveWhitespace(text, meta)) {
text = `"${text}"`;
} else if (opts.keepsWhitespace) {
text = text.replace(/^[\n\r\f]+|[\n\r\f]+$/g, '');
if (opts.removesIndent) {
match = text.match(/^([ \t]+)/);
if (match && match[1]) {
indentMatcher = new RegExp(`^${match[1]}`, 'gm');
text = text.replace(indentMatcher, '');
}
}
} else {
text = text.replace(/^\s+|\s+$/g, '');
}
return text;
return text;
}
function addHiddenProperty(obj, propName, propValue) {
Object.defineProperty(obj, propName, {
value: propValue,
writable: true,
enumerable: Boolean(env.opts.debug),
configurable: true
});
Object.defineProperty(obj, propName, {
value: propValue,
writable: true,
enumerable: Boolean(env.opts.debug),
configurable: true,
});
}
function parseType({text, originalTitle}, {canHaveName, canHaveType}, meta) {
try {
return tag.type.parse(text, canHaveName, canHaveType);
}
catch (e) {
log.error(
'Unable to parse a tag\'s type expression%s with tag title "%s" and text "%s": %s',
meta.filename ? ( ` for source file ${path.join(meta.path, meta.filename)}${meta.lineno ? (` in line ${meta.lineno}`) : ''}` ) : '',
originalTitle,
text,
e.message
);
function parseType({ text, originalTitle }, { canHaveName, canHaveType }, meta) {
try {
return tag.type.parse(text, canHaveName, canHaveType);
} catch (e) {
log.error(
'Unable to parse a tag\'s type expression%s with tag title "%s" and text "%s": %s',
meta.filename
? ` for source file ${path.join(meta.path, meta.filename)}${
meta.lineno ? ` in line ${meta.lineno}` : ''
}`
: '',
originalTitle,
text,
e.message
);
return {};
}
return {};
}
}
function processTagText(tagInstance, tagDef, meta) {
let tagType;
let tagType;
if (tagDef.onTagText) {
tagInstance.text = tagDef.onTagText(tagInstance.text);
if (tagDef.onTagText) {
tagInstance.text = tagDef.onTagText(tagInstance.text);
}
if (tagDef.canHaveType || tagDef.canHaveName) {
/** The value property represents the result of parsing the tag text. */
tagInstance.value = {};
tagType = parseType(tagInstance, tagDef, meta);
// It is possible for a tag to *not* have a type but still have
// optional or defaultvalue, e.g. '@param [foo]'.
// Although tagType.type.length == 0 we should still copy the other properties.
if (tagType.type) {
if (tagType.type.length) {
tagInstance.value.type = {
names: tagType.type,
};
addHiddenProperty(tagInstance.value.type, 'parsedType', tagType.parsedType);
}
['optional', 'nullable', 'variable', 'defaultvalue'].forEach((prop) => {
if (typeof tagType[prop] !== 'undefined') {
tagInstance.value[prop] = tagType[prop];
}
});
}
if (tagDef.canHaveType || tagDef.canHaveName) {
/** The value property represents the result of parsing the tag text. */
tagInstance.value = {};
tagType = parseType(tagInstance, tagDef, meta);
// It is possible for a tag to *not* have a type but still have
// optional or defaultvalue, e.g. '@param [foo]'.
// Although tagType.type.length == 0 we should still copy the other properties.
if (tagType.type) {
if (tagType.type.length) {
tagInstance.value.type = {
names: tagType.type
};
addHiddenProperty(tagInstance.value.type, 'parsedType', tagType.parsedType);
}
['optional', 'nullable', 'variable', 'defaultvalue'].forEach(prop => {
if (typeof tagType[prop] !== 'undefined') {
tagInstance.value[prop] = tagType[prop];
}
});
}
if (tagType.text && tagType.text.length) {
tagInstance.value.description = tagType.text;
}
if (tagDef.canHaveName) {
// note the dash is a special case: as a param name it means "no name"
if (tagType.name && tagType.name !== '-') {
tagInstance.value.name = tagType.name;
}
}
if (tagType.text && tagType.text.length) {
tagInstance.value.description = tagType.text;
}
else {
tagInstance.value = tagInstance.text;
if (tagDef.canHaveName) {
// note the dash is a special case: as a param name it means "no name"
if (tagType.name && tagType.name !== '-') {
tagInstance.value.name = tagType.name;
}
}
} else {
tagInstance.value = tagInstance.text;
}
}
/**
@ -127,62 +127,62 @@ function processTagText(tagInstance, tagDef, meta) {
* @param {module:jsdoc/tag/dictionary.Dictionary} dict - The new tag dictionary.
*/
exports._replaceDictionary = function _replaceDictionary(dict) {
tag.dictionary = dict;
tag.dictionary = dict;
};
/**
* Represents a single doclet tag.
*/
class Tag {
/**
* Constructs a new tag object. Calls the tag validator.
*
* @param {string} tagTitle
* @param {string=} tagBody
* @param {object=} meta
*/
constructor(tagTitle, tagBody, meta) {
let tagDef;
let trimOpts;
meta = meta || {};
this.originalTitle = trim(tagTitle);
/** The title of the tag (for example, `title` in `@title text`). */
this.title = tag.dictionary.normalize(this.originalTitle);
tagDef = tag.dictionary.lookUp(this.title);
trimOpts = {
keepsWhitespace: tagDef.keepsWhitespace,
removesIndent: tagDef.removesIndent,
};
/**
* Constructs a new tag object. Calls the tag validator.
* The text following the tag (for example, `text` in `@title text`).
*
* @param {string} tagTitle
* @param {string=} tagBody
* @param {object=} meta
* Whitespace is trimmed from the tag text as follows:
*
* + If the tag's `keepsWhitespace` option is falsy, all leading and trailing whitespace are
* removed.
* + If the tag's `keepsWhitespace` option is set to `true`, leading and trailing whitespace are
* not trimmed, unless the `removesIndent` option is also enabled.
* + If the tag's `removesIndent` option is set to `true`, any indentation that is shared by
* every line in the string is removed. This option is ignored unless `keepsWhitespace` is set
* to `true`.
*
* **Note**: If the tag text is the name of a symbol, and the symbol's name includes leading or
* trailing whitespace (for example, the property names in `{ ' ': true, ' foo ': false }`),
* the tag text is not trimmed. Instead, the tag text is wrapped in double quotes to prevent the
* whitespace from being trimmed.
*/
constructor(tagTitle, tagBody, meta) {
let tagDef;
let trimOpts;
this.text = trim(tagBody, trimOpts, meta);
meta = meta || {};
this.originalTitle = trim(tagTitle);
/** The title of the tag (for example, `title` in `@title text`). */
this.title = tag.dictionary.normalize(this.originalTitle);
tagDef = tag.dictionary.lookUp(this.title);
trimOpts = {
keepsWhitespace: tagDef.keepsWhitespace,
removesIndent: tagDef.removesIndent
};
/**
* The text following the tag (for example, `text` in `@title text`).
*
* Whitespace is trimmed from the tag text as follows:
*
* + If the tag's `keepsWhitespace` option is falsy, all leading and trailing whitespace are
* removed.
* + If the tag's `keepsWhitespace` option is set to `true`, leading and trailing whitespace are
* not trimmed, unless the `removesIndent` option is also enabled.
* + If the tag's `removesIndent` option is set to `true`, any indentation that is shared by
* every line in the string is removed. This option is ignored unless `keepsWhitespace` is set
* to `true`.
*
* **Note**: If the tag text is the name of a symbol, and the symbol's name includes leading or
* trailing whitespace (for example, the property names in `{ ' ': true, ' foo ': false }`),
* the tag text is not trimmed. Instead, the tag text is wrapped in double quotes to prevent the
* whitespace from being trimmed.
*/
this.text = trim(tagBody, trimOpts, meta);
if (this.text) {
processTagText(this, tagDef, meta);
}
tag.validator.validate(this, tagDef, meta);
if (this.text) {
processTagText(this, tagDef, meta);
}
tag.validator.validate(this, tagDef, meta);
}
}
exports.Tag = Tag;

View File

@ -6,168 +6,171 @@ const { log } = require('@jsdoc/util');
const hasOwnProp = Object.prototype.hasOwnProperty;
const DEFINITIONS = {
closure: 'closureTags',
jsdoc: 'jsdocTags'
closure: 'closureTags',
jsdoc: 'jsdocTags',
};
let dictionary;
/** @private */
class TagDefinition {
constructor(dict, title, etc) {
const self = this;
constructor(dict, title, etc) {
const self = this;
etc = etc || {};
etc = etc || {};
this.title = dict.normalize(title);
this.title = dict.normalize(title);
Object.defineProperty(this, '_dictionary', {
value: dict
});
Object.defineProperty(this, '_dictionary', {
value: dict,
});
Object.keys(etc).forEach(p => {
self[p] = etc[p];
});
}
Object.keys(etc).forEach((p) => {
self[p] = etc[p];
});
}
/** @private */
synonym(synonymName) {
this._dictionary.defineSynonym(this.title, synonymName);
/** @private */
synonym(synonymName) {
this._dictionary.defineSynonym(this.title, synonymName);
return this;
}
return this;
}
}
/**
* @alias module:jsdoc/tag/dictionary.Dictionary
*/
class Dictionary {
constructor() {
// TODO: Consider adding internal tags in the constructor, ideally as fallbacks that aren't
// used to confirm whether a tag is defined/valid, rather than requiring every set of tag
// definitions to contain the internal tags.
this._tags = {};
this._tagSynonyms = {};
// The longnames for `Package` objects include a `package` namespace. There's no `package`
// tag, though, so we declare the namespace here.
// TODO: Consider making this a fallback as suggested above for internal tags.
this._namespaces = ['package'];
constructor() {
// TODO: Consider adding internal tags in the constructor, ideally as fallbacks that aren't
// used to confirm whether a tag is defined/valid, rather than requiring every set of tag
// definitions to contain the internal tags.
this._tags = {};
this._tagSynonyms = {};
// The longnames for `Package` objects include a `package` namespace. There's no `package`
// tag, though, so we declare the namespace here.
// TODO: Consider making this a fallback as suggested above for internal tags.
this._namespaces = ['package'];
}
_defineNamespace(title) {
title = this.normalize(title || '');
if (title && !this._namespaces.includes(title)) {
this._namespaces.push(title);
}
_defineNamespace(title) {
title = this.normalize(title || '');
return this;
}
if (title && !this._namespaces.includes(title)) {
this._namespaces.push(title);
}
defineTag(title, opts) {
const tagDef = new TagDefinition(this, title, opts);
return this;
this._tags[tagDef.title] = tagDef;
if (tagDef.isNamespace) {
this._defineNamespace(tagDef.title);
}
if (tagDef.synonyms) {
tagDef.synonyms.forEach((synonym) => {
this.defineSynonym(title, synonym);
});
}
defineTag(title, opts) {
const tagDef = new TagDefinition(this, title, opts);
return this._tags[tagDef.title];
}
this._tags[tagDef.title] = tagDef;
defineTags(tagDefs) {
const tags = {};
if (tagDef.isNamespace) {
this._defineNamespace(tagDef.title);
}
if (tagDef.synonyms) {
tagDef.synonyms.forEach(synonym => {
this.defineSynonym(title, synonym);
});
}
return this._tags[tagDef.title];
for (const title of Object.keys(tagDefs)) {
tags[title] = this.defineTag(title, tagDefs[title]);
}
defineTags(tagDefs) {
const tags = {};
return tags;
}
for (const title of Object.keys(tagDefs)) {
tags[title] = this.defineTag(title, tagDefs[title]);
}
defineSynonym(title, synonym) {
this._tagSynonyms[synonym.toLowerCase()] = this.normalize(title);
}
return tags;
}
static fromConfig(env) {
let dictionaries = env.conf.tags.dictionaries;
const dict = new Dictionary();
defineSynonym(title, synonym) {
this._tagSynonyms[synonym.toLowerCase()] = this.normalize(title);
}
if (!dictionaries) {
log.error(
'The configuration setting "tags.dictionaries" is undefined. ' +
'Unable to load tag definitions.'
);
} else {
dictionaries
.slice()
.reverse()
.forEach((dictName) => {
const tagDefs = definitions[DEFINITIONS[dictName]];
static fromConfig(env) {
let dictionaries = env.conf.tags.dictionaries;
const dict = new Dictionary();
if (!dictionaries) {
if (!tagDefs) {
log.error(
'The configuration setting "tags.dictionaries" is undefined. ' +
'Unable to load tag definitions.'
'The configuration setting "tags.dictionaries" contains ' +
`the unknown dictionary name ${dictName}. Ignoring the dictionary.`
);
} else {
dictionaries.slice().reverse().forEach(dictName => {
const tagDefs = definitions[DEFINITIONS[dictName]];
if (!tagDefs) {
log.error(
'The configuration setting "tags.dictionaries" contains ' +
`the unknown dictionary name ${dictName}. Ignoring the dictionary.`
);
return;
}
return;
}
dict.defineTags(tagDefs);
});
dict.defineTags(tagDefs);
});
dict.defineTags(definitions.internalTags);
}
return dict;
dict.defineTags(definitions.internalTags);
}
getNamespaces() {
return this._namespaces.slice();
return dict;
}
getNamespaces() {
return this._namespaces.slice();
}
isNamespace(kind) {
if (kind) {
kind = this.normalize(kind);
if (this._namespaces.includes(kind)) {
return true;
}
}
isNamespace(kind) {
if (kind) {
kind = this.normalize(kind);
if (this._namespaces.includes(kind)) {
return true;
}
}
return false;
}
return false;
lookup(title) {
title = this.normalize(title);
if (hasOwnProp.call(this._tags, title)) {
return this._tags[title];
}
lookup(title) {
title = this.normalize(title);
return false;
}
if ( hasOwnProp.call(this._tags, title) ) {
return this._tags[title];
}
lookUp(title) {
return this.lookup(title);
}
return false;
normalise(title) {
return this.normalize(title);
}
normalize(title) {
const canonicalName = title.toLowerCase();
if (hasOwnProp.call(this._tagSynonyms, canonicalName)) {
return this._tagSynonyms[canonicalName];
}
lookUp(title) {
return this.lookup(title);
}
normalise(title) {
return this.normalize(title);
}
normalize(title) {
const canonicalName = title.toLowerCase();
if ( hasOwnProp.call(this._tagSynonyms, canonicalName) ) {
return this._tagSynonyms[canonicalName];
}
return canonicalName;
}
return canonicalName;
}
}
// initialize the default dictionary

File diff suppressed because it is too large Load Diff

View File

@ -5,47 +5,47 @@
const env = require('jsdoc/env');
const { log } = require('@jsdoc/util');
function buildMessage(tagName, {filename, lineno, comment}, desc) {
let result = `The @${tagName} tag ${desc}. File: ${filename}, line: ${lineno}`;
function buildMessage(tagName, { filename, lineno, comment }, desc) {
let result = `The @${tagName} tag ${desc}. File: ${filename}, line: ${lineno}`;
if (comment) {
result += `\n${comment}`;
}
if (comment) {
result += `\n${comment}`;
}
return result;
return result;
}
/**
* Validate the given tag.
*/
exports.validate = ({title, text, value}, tagDef, meta) => {
const allowUnknownTags = env.conf.tags.allowUnknownTags;
exports.validate = ({ title, text, value }, tagDef, meta) => {
const allowUnknownTags = env.conf.tags.allowUnknownTags;
// handle cases where the tag definition does not exist
if (!tagDef) {
// log an error if unknown tags are not allowed
if (!allowUnknownTags ||
(Array.isArray(allowUnknownTags) &&
!allowUnknownTags.includes(title))) {
log.error(buildMessage(title, meta, 'is not a known tag'));
}
// stop validation, since there's nothing to validate against
return;
// handle cases where the tag definition does not exist
if (!tagDef) {
// log an error if unknown tags are not allowed
if (
!allowUnknownTags ||
(Array.isArray(allowUnknownTags) && !allowUnknownTags.includes(title))
) {
log.error(buildMessage(title, meta, 'is not a known tag'));
}
// check for errors that make the tag useless
if (!text && tagDef.mustHaveValue) {
log.error(buildMessage(title, meta, 'requires a value'));
}
// stop validation, since there's nothing to validate against
return;
}
// check for minor issues that are usually harmless
else if (text && tagDef.mustNotHaveValue) {
log.warn(buildMessage(title, meta,
'does not permit a value; the value will be ignored'));
}
else if (value && value.description && tagDef.mustNotHaveDescription) {
log.warn(buildMessage(title, meta,
'does not permit a description; the description will be ignored'));
}
// check for errors that make the tag useless
if (!text && tagDef.mustHaveValue) {
log.error(buildMessage(title, meta, 'requires a value'));
}
// check for minor issues that are usually harmless
else if (text && tagDef.mustNotHaveValue) {
log.warn(buildMessage(title, meta, 'does not permit a value; the value will be ignored'));
} else if (value && value.description && tagDef.mustNotHaveDescription) {
log.warn(
buildMessage(title, meta, 'does not permit a description; the description will be ignored')
);
}
};

View File

@ -10,71 +10,71 @@ const path = require('path');
* Template helper.
*/
class Template {
/**
* @param {string} filepath - Templates directory.
*/
constructor(filepath) {
this.path = filepath;
this.layout = null;
this.cache = {};
// override default template tag settings
this.settings = {
evaluate: /<\?js([\s\S]+?)\?>/g,
interpolate: /<\?js=([\s\S]+?)\?>/g,
escape: /<\?js~([\s\S]+?)\?>/g
};
/**
* @param {string} filepath - Templates directory.
*/
constructor(filepath) {
this.path = filepath;
this.layout = null;
this.cache = {};
// override default template tag settings
this.settings = {
evaluate: /<\?js([\s\S]+?)\?>/g,
interpolate: /<\?js=([\s\S]+?)\?>/g,
escape: /<\?js~([\s\S]+?)\?>/g,
};
}
/**
* Loads template from given file.
* @param {string} file - Template filename.
* @return {function} Returns template closure.
*/
load(file) {
return _.template(fs.readFileSync(file, 'utf8'), this.settings);
}
/**
* Renders template using given data.
*
* This is low-level function, for rendering full templates use {@link Template.render()}.
*
* @param {string} file - Template filename.
* @param {object} data - Template variables (doesn't have to be object, but passing variables dictionary is best way and most common use).
* @return {string} Rendered template.
*/
partial(file, data) {
file = path.resolve(this.path, file);
// load template into cache
if (!(file in this.cache)) {
this.cache[file] = this.load(file);
}
/**
* Loads template from given file.
* @param {string} file - Template filename.
* @return {function} Returns template closure.
*/
load(file) {
return _.template(fs.readFileSync(file, 'utf8'), this.settings);
// keep template helper context
return this.cache[file].call(this, data);
}
/**
* Renders template with given data.
*
* This method automaticaly applies layout if set.
*
* @param {string} file - Template filename.
* @param {object} data - Template variables (doesn't have to be object, but passing variables dictionary is best way and most common use).
* @return {string} Rendered template.
*/
render(file, data) {
// main content
let content = this.partial(file, data);
// apply layout
if (this.layout) {
data.content = content;
content = this.partial(this.layout, data);
}
/**
* Renders template using given data.
*
* This is low-level function, for rendering full templates use {@link Template.render()}.
*
* @param {string} file - Template filename.
* @param {object} data - Template variables (doesn't have to be object, but passing variables dictionary is best way and most common use).
* @return {string} Rendered template.
*/
partial(file, data) {
file = path.resolve(this.path, file);
// load template into cache
if (!(file in this.cache)) {
this.cache[file] = this.load(file);
}
// keep template helper context
return this.cache[file].call(this, data);
}
/**
* Renders template with given data.
*
* This method automaticaly applies layout if set.
*
* @param {string} file - Template filename.
* @param {object} data - Template variables (doesn't have to be object, but passing variables dictionary is best way and most common use).
* @return {string} Rendered template.
*/
render(file, data) {
// main content
let content = this.partial(file, data);
// apply layout
if (this.layout) {
data.content = content;
content = this.partial(this.layout, data);
}
return content;
}
return content;
}
}
exports.Template = Template;

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,134 @@
{
"name": "jsdoc",
"version": "4.0.0-dev.16",
"lockfileVersion": 1,
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "jsdoc",
"version": "4.0.0-dev.16",
"license": "Apache-2.0",
"dependencies": {
"@babel/parser": "^7.15.7",
"bluebird": "^3.7.2",
"catharsis": "^0.9.0",
"code-prettify": "^0.1.0",
"color-themes-for-google-code-prettify": "^2.0.4",
"common-path-prefix": "^3.0.0",
"escape-string-regexp": "^4.0.0",
"lodash": "^4.17.21",
"open-sans-fonts": "^1.6.2",
"requizzle": "^0.2.3",
"strip-bom": "^4.0.0",
"strip-json-comments": "^3.1.1",
"taffydb": "2.6.2"
},
"bin": {
"jsdoc": "jsdoc.js"
},
"engines": {
"node": ">=v14.17.6"
}
},
"node_modules/@babel/parser": {
"version": "7.15.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.7.tgz",
"integrity": "sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==",
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
},
"node_modules/catharsis": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz",
"integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==",
"dependencies": {
"lodash": "^4.17.15"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/code-prettify": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/code-prettify/-/code-prettify-0.1.0.tgz",
"integrity": "sha1-RocMyMGlDQm61TmzOpg9vUqjSx4="
},
"node_modules/color-themes-for-google-code-prettify": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/color-themes-for-google-code-prettify/-/color-themes-for-google-code-prettify-2.0.4.tgz",
"integrity": "sha1-3urPZX/WhXaGR1TU5IbXjf2x54Q=",
"engines": {
"node": ">=5.9.0"
}
},
"node_modules/common-path-prefix": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz",
"integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w=="
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/open-sans-fonts": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/open-sans-fonts/-/open-sans-fonts-1.6.2.tgz",
"integrity": "sha512-vsJ6/Mm0TdUKQJqxfkXJy+0K2X0QeRuTmxQq9YE1ycziw6CbDPolDsHhQ6+ImoV/7OTh8K8ZTGklY1Z5nUAwug=="
},
"node_modules/requizzle": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz",
"integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==",
"dependencies": {
"lodash": "^4.17.14"
}
},
"node_modules/strip-bom": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
"integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
"engines": {
"node": ">=8"
}
},
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/taffydb": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz",
"integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg="
}
},
"dependencies": {
"@babel/parser": {
"version": "7.15.7",

View File

@ -5,17 +5,17 @@
* @module plugins/commentConvert
*/
exports.handlers = {
///
/// Convert ///-style comments into jsdoc comments.
/// @param e
/// @param e.filename
/// @param e.source
///
beforeParse(e) {
e.source = e.source.replace(/(\n[ \t]*\/\/\/[^\n]*)+/g, $ => {
const replacement = `\n/**${$.replace(/^[ \t]*\/\/\//mg, '').replace(/(\n$|$)/, '*/$1')}`;
///
/// Convert ///-style comments into jsdoc comments.
/// @param e
/// @param e.filename
/// @param e.source
///
beforeParse(e) {
e.source = e.source.replace(/(\n[ \t]*\/\/\/[^\n]*)+/g, ($) => {
const replacement = `\n/**${$.replace(/^[ \t]*\/\/\//gm, '').replace(/(\n$|$)/, '*/$1')}`;
return replacement;
});
}
return replacement;
});
},
};

View File

@ -4,14 +4,14 @@
* @module plugins/commentsOnly
*/
exports.handlers = {
beforeParse(e) {
// a JSDoc comment looks like: /**[one or more chars]*/
const comments = e.source.match(/\/\*\*[\s\S]+?\*\//g);
beforeParse(e) {
// a JSDoc comment looks like: /**[one or more chars]*/
const comments = e.source.match(/\/\*\*[\s\S]+?\*\//g);
if (comments) {
e.source = comments.join('\n\n');
} else {
e.source = ''; // If file has no comments, parser should still receive no code
}
if (comments) {
e.source = comments.join('\n\n');
} else {
e.source = ''; // If file has no comments, parser should still receive no code
}
},
};

View File

@ -4,15 +4,15 @@
* @module plugins/escapeHtml
*/
exports.handlers = {
/**
* Translate HTML tags in descriptions into safe entities. Replaces <, & and newlines
*/
newDoclet({doclet}) {
if (doclet.description) {
doclet.description = doclet.description
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/\r\n|\n|\r/g, '<br>');
}
/**
* Translate HTML tags in descriptions into safe entities. Replaces <, & and newlines
*/
newDoclet({ doclet }) {
if (doclet.description) {
doclet.description = doclet.description
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/\r\n|\n|\r/g, '<br>');
}
},
};

View File

@ -10,20 +10,20 @@ const conf = env.conf.eventDumper || {};
// Dump the included parser events (defaults to all events)
let events = conf.include || [
'parseBegin',
'fileBegin',
'beforeParse',
'jsdocCommentFound',
'symbolFound',
'newDoclet',
'fileComplete',
'parseComplete',
'processingComplete'
'parseBegin',
'fileBegin',
'beforeParse',
'jsdocCommentFound',
'symbolFound',
'newDoclet',
'fileComplete',
'parseComplete',
'processingComplete',
];
// Don't dump the excluded parser events
if (conf.exclude) {
events = _.difference(events, conf.exclude);
events = _.difference(events, conf.exclude);
}
/**
@ -33,25 +33,25 @@ if (conf.exclude) {
* @return {Object} The modified object.
*/
function replaceNodeObjects(o) {
const OBJECT_PLACEHOLDER = '<Object>';
const OBJECT_PLACEHOLDER = '<Object>';
if (o.code && o.code.node) {
// don't break the original object!
o.code = _.cloneDeep(o.code);
o.code.node = OBJECT_PLACEHOLDER;
}
if (o.code && o.code.node) {
// don't break the original object!
o.code = _.cloneDeep(o.code);
o.code.node = OBJECT_PLACEHOLDER;
}
if (o.doclet && o.doclet.meta && o.doclet.meta.code && o.doclet.meta.code.node) {
// don't break the original object!
o.doclet.meta.code = _.cloneDeep(o.doclet.meta.code);
o.doclet.meta.code.node = OBJECT_PLACEHOLDER;
}
if (o.doclet && o.doclet.meta && o.doclet.meta.code && o.doclet.meta.code.node) {
// don't break the original object!
o.doclet.meta.code = _.cloneDeep(o.doclet.meta.code);
o.doclet.meta.code.node = OBJECT_PLACEHOLDER;
}
if (o.astnode) {
o.astnode = OBJECT_PLACEHOLDER;
}
if (o.astnode) {
o.astnode = OBJECT_PLACEHOLDER;
}
return o;
return o;
}
/**
@ -61,35 +61,43 @@ function replaceNodeObjects(o) {
* @return {object} The fixed-up object.
*/
function cleanse(e) {
let result = {};
let result = {};
Object.keys(e).forEach(prop => {
// by default, don't stringify properties that contain an array of functions
if (!conf.includeFunctions && Array.isArray(e[prop]) && e[prop][0] &&
String(typeof e[prop][0]) === 'function') {
result[prop] = `function[${e[prop].length}]`;
}
// never include functions that belong to the object
else if (typeof e[prop] !== 'function') {
result[prop] = e[prop];
}
});
// allow users to omit node objects, which can be enormous
if (conf.omitNodes) {
result = replaceNodeObjects(result);
Object.keys(e).forEach((prop) => {
// by default, don't stringify properties that contain an array of functions
if (
!conf.includeFunctions &&
Array.isArray(e[prop]) &&
e[prop][0] &&
String(typeof e[prop][0]) === 'function'
) {
result[prop] = `function[${e[prop].length}]`;
}
// never include functions that belong to the object
else if (typeof e[prop] !== 'function') {
result[prop] = e[prop];
}
});
return result;
// allow users to omit node objects, which can be enormous
if (conf.omitNodes) {
result = replaceNodeObjects(result);
}
return result;
}
exports.handlers = {};
events.forEach(eventType => {
exports.handlers[eventType] = e => {
console.log(JSON.stringify({
type: eventType,
content: cleanse(e)
}), null, 4);
};
events.forEach((eventType) => {
exports.handlers[eventType] = (e) => {
console.log(
JSON.stringify({
type: eventType,
content: cleanse(e),
}),
null,
4
);
};
});

View File

@ -38,144 +38,143 @@
let functionDoclets;
function hasUniqueValues(obj) {
let isUnique = true;
const seen = [];
let isUnique = true;
const seen = [];
Object.keys(obj).forEach(key => {
if (seen.includes(obj[key])) {
isUnique = false;
}
Object.keys(obj).forEach((key) => {
if (seen.includes(obj[key])) {
isUnique = false;
}
seen.push(obj[key]);
});
seen.push(obj[key]);
});
return isUnique;
return isUnique;
}
function getParamNames(params) {
const names = [];
const names = [];
params.forEach(param => {
let name = param.name || '';
params.forEach((param) => {
let name = param.name || '';
if (param.variable) {
name = `...${name}`;
}
if (name !== '') {
names.push(name);
}
});
if (param.variable) {
name = `...${name}`;
}
if (name !== '') {
names.push(name);
}
});
return names.length ? names.join(', ') : '';
return names.length ? names.join(', ') : '';
}
function getParamVariation({params}) {
return getParamNames(params || []);
function getParamVariation({ params }) {
return getParamNames(params || []);
}
function getUniqueVariations(doclets) {
let counter = 0;
const variations = {};
const docletKeys = Object.keys(doclets);
let counter = 0;
const variations = {};
const docletKeys = Object.keys(doclets);
function getUniqueNumbers() {
docletKeys.forEach(doclet => {
let newLongname;
function getUniqueNumbers() {
docletKeys.forEach((doclet) => {
let newLongname;
while (true) {
counter++;
variations[doclet] = String(counter);
while (true) {
counter++;
variations[doclet] = String(counter);
// is this longname + variation unique?
newLongname = `${doclets[doclet].longname}(${variations[doclet]})`;
if ( !functionDoclets[newLongname] ) {
break;
}
}
});
}
function getUniqueNames() {
// start by trying to preserve existing variations
docletKeys.forEach(doclet => {
variations[doclet] = doclets[doclet].variation || getParamVariation(doclets[doclet]);
});
// if they're identical, try again, without preserving existing variations
if ( !hasUniqueValues(variations) ) {
docletKeys.forEach(doclet => {
variations[doclet] = getParamVariation(doclets[doclet]);
});
// if they're STILL identical, switch to numeric variations
if ( !hasUniqueValues(variations) ) {
getUniqueNumbers();
}
// is this longname + variation unique?
newLongname = `${doclets[doclet].longname}(${variations[doclet]})`;
if (!functionDoclets[newLongname]) {
break;
}
}
}
});
}
// are we already using numeric variations? if so, keep doing that
if (functionDoclets[`${doclets.newDoclet.longname}(1)`]) {
function getUniqueNames() {
// start by trying to preserve existing variations
docletKeys.forEach((doclet) => {
variations[doclet] = doclets[doclet].variation || getParamVariation(doclets[doclet]);
});
// if they're identical, try again, without preserving existing variations
if (!hasUniqueValues(variations)) {
docletKeys.forEach((doclet) => {
variations[doclet] = getParamVariation(doclets[doclet]);
});
// if they're STILL identical, switch to numeric variations
if (!hasUniqueValues(variations)) {
getUniqueNumbers();
}
}
else {
getUniqueNames();
}
}
return variations;
// are we already using numeric variations? if so, keep doing that
if (functionDoclets[`${doclets.newDoclet.longname}(1)`]) {
getUniqueNumbers();
} else {
getUniqueNames();
}
return variations;
}
function ensureUniqueLongname(newDoclet) {
const doclets = {
oldDoclet: functionDoclets[newDoclet.longname],
newDoclet: newDoclet
};
const docletKeys = Object.keys(doclets);
let oldDocletLongname;
let variations = {};
const doclets = {
oldDoclet: functionDoclets[newDoclet.longname],
newDoclet: newDoclet,
};
const docletKeys = Object.keys(doclets);
let oldDocletLongname;
let variations = {};
if (doclets.oldDoclet) {
oldDocletLongname = doclets.oldDoclet.longname;
// if the shared longname has a variation, like MyClass#myLongname(variation),
// remove the variation
if (doclets.oldDoclet.variation || doclets.oldDoclet.variation === '') {
docletKeys.forEach(doclet => {
doclets[doclet].longname = doclets[doclet].longname.replace(/\([\s\S]*\)$/, '');
doclets[doclet].variation = null;
});
}
variations = getUniqueVariations(doclets);
// update the longnames/variations
docletKeys.forEach(doclet => {
doclets[doclet].longname += `(${variations[doclet]})`;
doclets[doclet].variation = variations[doclet];
});
// update the old doclet in the lookup table
functionDoclets[oldDocletLongname] = null;
functionDoclets[doclets.oldDoclet.longname] = doclets.oldDoclet;
if (doclets.oldDoclet) {
oldDocletLongname = doclets.oldDoclet.longname;
// if the shared longname has a variation, like MyClass#myLongname(variation),
// remove the variation
if (doclets.oldDoclet.variation || doclets.oldDoclet.variation === '') {
docletKeys.forEach((doclet) => {
doclets[doclet].longname = doclets[doclet].longname.replace(/\([\s\S]*\)$/, '');
doclets[doclet].variation = null;
});
}
// always store the new doclet in the lookup table
functionDoclets[doclets.newDoclet.longname] = doclets.newDoclet;
variations = getUniqueVariations(doclets);
return doclets.newDoclet;
// update the longnames/variations
docletKeys.forEach((doclet) => {
doclets[doclet].longname += `(${variations[doclet]})`;
doclets[doclet].variation = variations[doclet];
});
// update the old doclet in the lookup table
functionDoclets[oldDocletLongname] = null;
functionDoclets[doclets.oldDoclet.longname] = doclets.oldDoclet;
}
// always store the new doclet in the lookup table
functionDoclets[doclets.newDoclet.longname] = doclets.newDoclet;
return doclets.newDoclet;
}
exports.handlers = {
parseBegin() {
functionDoclets = {};
},
parseBegin() {
functionDoclets = {};
},
newDoclet(e) {
if (e.doclet.kind === 'function') {
e.doclet = ensureUniqueLongname(e.doclet);
}
},
parseComplete() {
functionDoclets = null;
newDoclet(e) {
if (e.doclet.kind === 'function') {
e.doclet = ensureUniqueLongname(e.doclet);
}
},
parseComplete() {
functionDoclets = null;
},
};

Some files were not shown because too many files have changed in this diff Show More