From 5d9bc0f6375bb46e6c8574064c70df13a1dc0c6d Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Sun, 8 Sep 2019 19:34:48 -0700 Subject: [PATCH] move code to `@jsdoc/core`; remove bespoke argument-parsing code --- cli.js | 88 +++-- lib/jsdoc/opts/argparser.js | 305 ----------------- lib/jsdoc/opts/args.js | 82 ----- lib/jsdoc/src/astnode.js | 2 +- lib/jsdoc/tag/type.js | 2 +- package-lock.json | 306 ++++++------------ package.json | 8 +- packages/jsdoc-core/index.js | 10 +- packages/jsdoc-core/lib/engine/flags.js | 114 +++++++ packages/jsdoc-core/lib/engine/help.js | 135 ++++++++ packages/jsdoc-core/lib/engine/index.js | 206 ++++++++++++ .../jsdoc-core/lib}/util/cast.js | 18 +- packages/jsdoc-core/lib/util/index.js | 5 + packages/jsdoc-core/package-lock.json | 36 ++- packages/jsdoc-core/package.json | 7 +- packages/jsdoc-core/test/specs/index.js | 13 +- packages/jsdoc-core/test/specs/lib/config.js | 10 +- .../jsdoc-core/test/specs/lib/engine/flags.js | 36 +++ .../jsdoc-core/test/specs/lib/engine/help.js | 3 + .../jsdoc-core/test/specs/lib/engine/index.js | 186 +++++++++++ .../jsdoc-core/test/specs/lib/util/cast.js | 66 ++++ .../jsdoc-core/test/specs/lib/util/index.js | 15 + test/specs/jsdoc/opts/argparser.js | 158 --------- test/specs/jsdoc/opts/args.js | 300 ----------------- test/specs/jsdoc/util/cast.js | 95 ------ 25 files changed, 993 insertions(+), 1213 deletions(-) delete mode 100644 lib/jsdoc/opts/argparser.js delete mode 100644 lib/jsdoc/opts/args.js create mode 100644 packages/jsdoc-core/lib/engine/flags.js create mode 100644 packages/jsdoc-core/lib/engine/help.js create mode 100644 packages/jsdoc-core/lib/engine/index.js rename {lib/jsdoc => packages/jsdoc-core/lib}/util/cast.js (81%) create mode 100644 packages/jsdoc-core/lib/util/index.js create mode 100644 packages/jsdoc-core/test/specs/lib/engine/flags.js create mode 100644 packages/jsdoc-core/test/specs/lib/engine/help.js create mode 100644 packages/jsdoc-core/test/specs/lib/engine/index.js create mode 100644 packages/jsdoc-core/test/specs/lib/util/cast.js create mode 100644 packages/jsdoc-core/test/specs/lib/util/index.js delete mode 100644 test/specs/jsdoc/opts/argparser.js delete mode 100644 test/specs/jsdoc/opts/args.js delete mode 100644 test/specs/jsdoc/util/cast.js diff --git a/cli.js b/cli.js index 52330bd2..63178b51 100644 --- a/cli.js +++ b/cli.js @@ -1,31 +1,30 @@ /* eslint-disable indent, no-process-exit */ + +const { config, Engine } = require('@jsdoc/core'); +const env = require('jsdoc/env'); +const logger = require('jsdoc/util/logger'); +const stripBom = require('strip-bom'); +const stripJsonComments = require('strip-json-comments'); +const Promise = require('bluebird'); + /** * Helper methods for running JSDoc on the command line. * - * A few critical notes for anyone who works on this module: - * - * + The module should really export an instance of `cli`, and `props` should be properties of a - * `cli` instance. - * * @private */ module.exports = (() => { - const env = require('jsdoc/env'); - const logger = require('jsdoc/util/logger'); - const stripBom = require('strip-bom'); - const stripJsonComments = require('strip-json-comments'); - const Promise = require('bluebird'); - const props = { docs: [], packageJson: null, shouldExitWithError: false, + shouldPrintHelp: false, tmpdir: null }; const FATAL_ERROR_MESSAGE = 'Exiting JSDoc because an error occurred. See the previous log ' + 'messages for details.'; const cli = {}; + const engine = new Engine(); // TODO: docs cli.setVersionInfo = () => { @@ -35,30 +34,35 @@ module.exports = (() => { // 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: new Date( parseInt(info.revision, 10) ).toUTCString() + revision: revision.toUTCString() }; + engine.version = env.version.number; + engine.revision = revision; + return cli; }; // TODO: docs cli.loadConfig = () => { const _ = require('lodash'); - const args = require('jsdoc/opts/args'); let conf; - const { config } = require('@jsdoc/core'); try { - env.opts = args.parse(env.args); + env.opts = engine.parseFlags(env.args); } catch (e) { - console.error(`${e.message}\n`); - cli.printHelp().then(() => { - cli.exit(1); - }); + props.shouldPrintHelp = true; + cli.exit( + 1, + `${e.message}\n` + ); + + return cli; } try { @@ -70,6 +74,8 @@ module.exports = (() => { 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 @@ -114,8 +120,7 @@ module.exports = (() => { // TODO: docs cli.logStart = () => { - logger.debug( cli.getVersion() ); - + logger.debug(engine.versionDetails); logger.debug('Environment info: %j', { env: { conf: env.conf, @@ -135,17 +140,20 @@ module.exports = (() => { if (delta !== undefined) { deltaSeconds = (delta / 1000).toFixed(2); - logger.info('Finished running in %s seconds.', deltaSeconds); + logger.info(`Finished running in ${deltaSeconds} seconds.`); } }; // TODO: docs cli.runCommand = cb => { let cmd; - const opts = env.opts; - if (opts.help) { + // 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) { @@ -169,8 +177,7 @@ module.exports = (() => { // TODO: docs cli.printHelp = () => { cli.printVersion(); - console.log( `\n${require('jsdoc/opts/args').help()}\n` ); - console.log('Visit https://jsdoc.app/ for more information.'); + console.log(engine.help({ maxLength: process.stdout.columns })); return Promise.resolve(0); }; @@ -178,12 +185,9 @@ module.exports = (() => { // TODO: docs cli.runTests = () => require('./test')(); - // TODO: docs - cli.getVersion = () => `JSDoc ${env.version.number} (${env.version.revision})`; - // TODO: docs cli.printVersion = () => { - console.log( cli.getVersion() ); + console.log(engine.versionDetails); return Promise.resolve(0); }; @@ -215,7 +219,7 @@ module.exports = (() => { return stripJsonComments( fs.readFileSync(filepath, 'utf8') ); } catch (e) { - logger.error('Unable to read the package file "%s"', filepath); + logger.error(`Unable to read the package file ${filepath}`); return null; } @@ -417,7 +421,10 @@ module.exports = (() => { return Promise.resolve(publishPromise); } else { - logger.fatal(`${env.opts.template} does not export a "publish" function. Global "publish" functions are no longer supported.`); + logger.fatal( + `${env.opts.template} does not export a "publish" function. ` + + 'Global "publish" functions are no longer supported.' + ); } return Promise.resolve(); @@ -425,10 +432,21 @@ module.exports = (() => { // TODO: docs cli.exit = (exitCode, message) => { - if (exitCode > 0 && message) { - console.error(message); + if (exitCode > 0) { + props.shouldExitWithError = true; + + if (message) { + console.error(message); + } } - process.on('exit', () => { process.exit(exitCode); }); + + process.on('exit', () => { + if (props.shouldPrintHelp) { + cli.printHelp(); + } + + process.exit(exitCode); + }); }; return cli; diff --git a/lib/jsdoc/opts/argparser.js b/lib/jsdoc/opts/argparser.js deleted file mode 100644 index ae23432f..00000000 --- a/lib/jsdoc/opts/argparser.js +++ /dev/null @@ -1,305 +0,0 @@ -/** - * Parse the command line arguments. - * @module jsdoc/opts/argparser - */ -const _ = require('lodash'); - -const hasOwnProp = Object.prototype.hasOwnProperty; - -function padding(length) { - return new Array(length + 1).join(' '); -} - -function padLeft(str, length) { - return padding(length) + str; -} - -function padRight(str, length) { - return str + padding(length); -} - -function findMaxLength(arr) { - let max = 0; - - arr.forEach(({length}) => { - if (length > max) { - max = length; - } - }); - - return max; -} - -function concatWithMaxLength(items, maxLength) { - let result = ''; - - // 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()}`; - } - - return result; -} - -// we want to format names and descriptions like this: -// | -f, --foo Very long description very long description very long | -// | description very long description. | -function formatHelpInfo({names, descriptions}) { - const MARGIN_LENGTH = 4; - const results = []; - - const maxLength = process.stdout.columns; - const maxNameLength = findMaxLength(names); - const wrapDescriptionAt = maxLength - (MARGIN_LENGTH * 3) - maxNameLength; - - // build the string for each option - names.forEach((name, i) => { - let result; - let partialDescription; - let words; - - // add a left margin to the name - result = padLeft(names[i], MARGIN_LENGTH); - // and a right margin, with extra padding so the descriptions line up with one another - result = padRight(result, maxNameLength - names[i].length + MARGIN_LENGTH); - - // 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 description - while (words.length) { - partialDescription = padding( maxNameLength + (MARGIN_LENGTH * 2) ); - partialDescription += concatWithMaxLength(words, wrapDescriptionAt); - result += `\n${partialDescription}`; - } - - results.push(result); - }); - - return results; -} - -/** - * A parser to interpret the key-value pairs entered on the command line. - * - * @alias module:jsdoc/opts/argparser - */ -class ArgParser { - /** - * Create an instance of the parser. - */ - constructor() { - this._options = []; - this._shortNameIndex = {}; - this._longNameIndex = {}; - } - - _getOptionByShortName(name) { - if (hasOwnProp.call(this._shortNameIndex, name)) { - return this._options[this._shortNameIndex[name]]; - } - - return null; - } - - _getOptionByLongName(name) { - if (hasOwnProp.call(this._longNameIndex, name)) { - return this._options[this._longNameIndex[name]]; - } - - return null; - } - - _addOption(option) { - let currentIndex; - - const longName = option.longName; - const shortName = option.shortName; - - this._options.push(option); - currentIndex = this._options.length - 1; - - if (shortName) { - this._shortNameIndex[shortName] = currentIndex; - } - if (longName) { - this._longNameIndex[longName] = currentIndex; - } - - return this; - } - - /** - * Provide information about a legal option. - * - * @param {character} shortName - The short name of the option, entered like: -T. - * @param {string} longName - The equivalent long name of the option, entered like: --test. - * @param {boolean} hasValue - Does this option require a value? Like: -t templatename - * @param {string} helpText - A brief description of the option. - * @param {boolean} [canHaveMultiple=false] - Set to `true` if the option can be provided more - * than once. - * @param {function} [coercer] - A function to coerce the given value to a specific type. - * @return {this} - * @example - * myParser.addOption('t', 'template', true, 'The path to the template.'); - * myParser.addOption('h', 'help', false, 'Show the help message.'); - */ - addOption(shortName, longName, hasValue, helpText, canHaveMultiple = false, coercer) { - return this._addOption({ - shortName: shortName, - longName: longName, - hasValue: hasValue, - helpText: helpText, - canHaveMultiple: canHaveMultiple, - coercer: coercer - }); - } - - // TODO: refactor addOption to accept objects, then get rid of this method - /** - * Provide information about an option that should not cause an error if present, but that is always - * ignored (for example, an option that was used in previous versions but is no longer supported). - * - * @private - * @param {string} shortName - The short name of the option with a leading hyphen (for example, - * `-v`). - * @param {string} longName - The long name of the option with two leading hyphens (for example, - * `--version`). - */ - addIgnoredOption(shortName, longName) { - return this._addOption({ - shortName: shortName, - longName: longName, - ignore: true - }); - } - - /** - * Generate a summary of all the options with corresponding help text. - * @returns {string} - */ - help() { - const options = { - names: [], - descriptions: [] - }; - - this._options.forEach(option => { - let name = ''; - - // don't show ignored options - if (option.ignore) { - return; - } - - if (option.shortName) { - name += `-${option.shortName}${option.longName ? ', ' : ''}`; - } - - if (option.longName) { - name += `--${option.longName}`; - } - - if (option.hasValue) { - name += ' '; - } - - options.names.push(name); - options.descriptions.push(option.helpText); - }); - - return `Options:\n${formatHelpInfo(options).join('\n')}`; - } - - /** - * Get the options. - * @param {Array.} args An array, like ['-x', 'hello'] - * @param {Object} [defaults={}] An optional collection of default values. - * @returns {Object} The keys will be the longNames, or the shortName if no longName is defined for - * that option. The values will be the values provided, or `true` if the option accepts no value. - */ - parse(args, defaults) { - let arg; - let next; - let option; - const result = ( defaults && _.defaults({}, defaults) ) || {}; - let shortName; - let longName; - let name; - let value; - - result._ = []; - for (let i = 0, l = args.length; i < l; i++) { - arg = String(args[i]); - next = (i < l - 1) ? String(args[i + 1]) : null; - shortName = null; - value = null; - - // like -t - if (arg.charAt(0) === '-') { - // like --template - if (arg.charAt(1) === '-') { - name = longName = arg.slice(2); - option = this._getOptionByLongName(longName); - } - else { - name = shortName = arg.slice(1); - option = this._getOptionByShortName(shortName); - } - - if (option === null) { - throw new Error(`Unknown command-line option "${name}".`); - } - - if (option.hasValue) { - value = next; - i++; - - if (value === null || value.charAt(0) === '-') { - throw new Error(`The command-line option "${name}" requires a value.`); - } - } - else { - value = true; - } - - // skip ignored options now that we've consumed the option text - if (option.ignore) { - continue; - } - - if (option.longName && shortName) { - name = option.longName; - } - - if (typeof option.coercer === 'function') { - value = option.coercer(value); - } - - // Allow for multiple options of the same type to be present - if (option.canHaveMultiple && hasOwnProp.call(result, name)) { - const val = result[name]; - - if (val instanceof Array) { - val.push(value); - } else { - result[name] = [val, value]; - } - } - else { - result[name] = value; - } - } - else { - result._.push(arg); - } - } - - return result; - } -} - -module.exports = ArgParser; diff --git a/lib/jsdoc/opts/args.js b/lib/jsdoc/opts/args.js deleted file mode 100644 index 458da94c..00000000 --- a/lib/jsdoc/opts/args.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * @module jsdoc/opts/args - * @requires jsdoc/opts/argparser - */ -const ArgParser = require('jsdoc/opts/argparser'); -const { cast } = require('jsdoc/util/cast'); -const querystring = require('querystring'); - -let ourOptions; - -const argParser = new ArgParser(); -const hasOwnProp = Object.prototype.hasOwnProperty; - -function parseQuery(str) { - return cast( querystring.parse(str) ); -} - -/* eslint-disable no-multi-spaces */ -argParser.addOption('a', 'access', true, 'Only display symbols with the given access: "package", public", "protected", "private" or "undefined", or "all" for all access levels. Default: all except "private"', true); -argParser.addOption('c', 'configure', true, 'The path to the configuration file. Default: path/to/jsdoc/conf.json'); -argParser.addOption('d', 'destination', true, 'The path to the output folder. Default: ./out/'); -argParser.addOption('', 'debug', false, 'Log information for debugging JSDoc.'); -argParser.addOption('e', 'encoding', true, 'Assume this encoding when reading all source files. Default: utf8'); -argParser.addOption('h', 'help', false, 'Print this message and quit.'); -argParser.addOption('', 'match', true, 'When running tests, only use specs whose names contain .', true); -argParser.addOption('', 'nocolor', false, 'When running tests, do not use color in console output.'); -argParser.addOption('p', 'private', false, 'Display symbols marked with the @private tag. Equivalent to "--access all". Default: false'); -argParser.addOption('P', 'package', true, 'The path to the project\'s package file. Default: path/to/sourcefiles/package.json'); -argParser.addOption('', 'pedantic', false, 'Treat errors as fatal errors, and treat warnings as errors. Default: false'); -argParser.addOption('q', 'query', true, 'A query string to parse and store in jsdoc.env.opts.query. Example: foo=bar&baz=true', false, parseQuery); -argParser.addOption('r', 'recurse', false, 'Recurse into subdirectories when scanning for source files and tutorials.'); -argParser.addOption('R', 'readme', true, 'The path to the project\'s README file. Default: path/to/sourcefiles/README.md'); -argParser.addOption('t', 'template', true, 'The path to the template to use. Default: path/to/jsdoc/templates/default'); -argParser.addOption('T', 'test', false, 'Run all tests and quit.'); -argParser.addOption('u', 'tutorials', true, 'Directory in which JSDoc should search for tutorials.'); -argParser.addOption('v', 'version', false, 'Display the version number and quit.'); -argParser.addOption('', 'verbose', false, 'Log detailed information to the console as JSDoc runs.'); -argParser.addOption('X', 'explain', false, 'Dump all found doclet internals to console and quit.'); -/* eslint-enable no-multi-spaces */ - -// Options that are no longer supported and should be ignored -argParser.addIgnoredOption('l', 'lenient'); // removed in JSDoc 3.3.0 - -/** - * Set the options for this app. - * @throws {Error} Illegal arguments will throw errors. - * @param {string|String[]} args The command line arguments for this app. - */ -exports.parse = (args = []) => { - if (typeof args === 'string' || args.constructor === String) { - args = String(args).split(/\s+/g); - } - - ourOptions = argParser.parse(args); - - return ourOptions; -}; - -/** - * Retrieve help message for options. - */ -exports.help = () => argParser.help(); - -/** - * Get a named option. - * @variation name - * @param {string} name The name of the option. - * @return {string} The value associated with the given name. - *//** -* Get all the options for this app. -* @return {Object} A collection of key/values representing all the options. -*/ -exports.get = name => { - if (typeof name === 'undefined') { - return ourOptions; - } - else if ( hasOwnProp.call(ourOptions, name) ) { - return ourOptions[name]; - } - - return undefined; -}; diff --git a/lib/jsdoc/src/astnode.js b/lib/jsdoc/src/astnode.js index 1e6854df..c76d4b22 100644 --- a/lib/jsdoc/src/astnode.js +++ b/lib/jsdoc/src/astnode.js @@ -1,6 +1,6 @@ // TODO: docs /** @module jsdoc/src/astnode */ -const { cast } = require('jsdoc/util/cast'); +const { cast } = require('@jsdoc/core').util; const env = require('jsdoc/env'); const name = require('jsdoc/name'); const { Syntax } = require('jsdoc/src/syntax'); diff --git a/lib/jsdoc/tag/type.js b/lib/jsdoc/tag/type.js index a26d0c25..470d7cc6 100644 --- a/lib/jsdoc/tag/type.js +++ b/lib/jsdoc/tag/type.js @@ -1,7 +1,7 @@ /** * @module jsdoc/tag/type */ -const { cast } = require('jsdoc/util/cast'); +const { cast } = require('@jsdoc/core').util; const catharsis = require('catharsis'); const { extractInlineTag } = require('jsdoc/tag/inline'); const { splitName } = require('jsdoc/name'); diff --git a/package-lock.json b/package-lock.json index 8e62b39b..99591dc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -284,147 +284,34 @@ } }, "@jsdoc/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jsdoc/core/-/core-0.1.1.tgz", - "integrity": "sha512-X+3T/z/POwRM5IMhbZ4BxnZKQ7YfFE4qsTBOIcd5AH+zA8Qw1gRn1gSTfAkOMO6VgtIMJHonv7Lry2dUW1pyqQ==", + "version": "file:packages/jsdoc-core", "requires": { "cosmiconfig": "^5.2.1", "lodash": "^4.17.15", + "ow": "^0.13.2", "strip-bom": "^4.0.0", - "strip-json-comments": "^3.0.1" + "strip-json-comments": "^3.0.1", + "yargs-parser": "^14.0.0" }, "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "yargs-parser": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-14.0.0.tgz", + "integrity": "sha512-zn/Mnx+tbFjkCFUodEpjXckNS65NfpB5oyqOkDDEG/8uxlfLZJu2IoBLQFjukUkn9rBbGkVYNzrDh6qy4NUd3g==", "requires": { - "sprintf-js": "~1.0.2" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } - }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "requires": { - "callsites": "^2.0.0" - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "requires": { - "caller-callsite": "^2.0.0" - } - }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" - }, - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - } - }, - "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==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "mock-fs": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.10.1.tgz", - "integrity": "sha512-w22rOL5ZYu6HbUehB5deurghGM0hS/xBVyHMGKOuQctkk93J9z9VEOhDsiWrXOprVNQpP9uzGKdl8v9mFspKuw==" - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "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==" - }, - "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==" } } }, "@jsdoc/eslint-config": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@jsdoc/eslint-config/-/eslint-config-1.0.1.tgz", - "integrity": "sha512-jssJlFRUvfU6C/S1BC11NCEnlNapmbrjpph8374nj+u6MTRNdYXn35F4dfD+xh6UH9QHlpCalnycf5SUCtUSnQ==", + "version": "file:packages/jsdoc-eslint-config", "dev": true }, "@lerna/add": { @@ -1737,9 +1624,9 @@ "dev": true }, "acorn": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.0.tgz", - "integrity": "sha512-8oe72N3WPMjA+2zVG71Ia0nXZ8DpQH+QyyHO+p06jT8eg8FGG3FbcUIi8KziHlAfheJQZeoqbvq1mQSQHXKYLw==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", + "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", "dev": true }, "acorn-jsx": { @@ -2414,7 +2301,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "dev": true, "requires": { "callsites": "^2.0.0" }, @@ -2422,8 +2308,7 @@ "callsites": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" } } }, @@ -2431,7 +2316,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "dev": true, "requires": { "caller-callsite": "^2.0.0" } @@ -2479,13 +2363,6 @@ "integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==", "requires": { "lodash": "^4.17.14" - }, - "dependencies": { - "lodash": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", - "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==" - } } }, "chalk": { @@ -3131,7 +3008,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "dev": true, "requires": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", @@ -3143,7 +3019,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "dev": true, "requires": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" @@ -3153,7 +3028,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, "requires": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -3162,8 +3036,7 @@ "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" } } }, @@ -3268,8 +3141,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decamelize-keys": { "version": "1.1.0", @@ -3610,7 +3482,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -3711,9 +3582,9 @@ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" }, "eslint": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.0.1.tgz", - "integrity": "sha512-DyQRaMmORQ+JsWShYsSg4OPTjY56u1nCjAmICrE8vLWqyLKxhFXOthwMj1SA8xwfrv0CofLNVnqbfyhwCkaO0w==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.1.0.tgz", + "integrity": "sha512-QhrbdRD7ofuV09IuE2ySWBz0FyXCq0rriLTZXZqaWSI79CVtHVRdkFuFTViiqzZhkCgfOh9USpriuGN2gIpZDQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -3722,7 +3593,7 @@ "cross-spawn": "^6.0.5", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^4.0.3", + "eslint-scope": "^5.0.0", "eslint-utils": "^1.3.1", "eslint-visitor-keys": "^1.0.0", "espree": "^6.0.0", @@ -3730,34 +3601,35 @@ "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^3.1.0", + "glob-parent": "^5.0.0", "globals": "^11.7.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^6.2.2", + "inquirer": "^6.4.1", "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", "optionator": "^0.8.2", "progress": "^2.0.0", "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", "table": "^5.2.3", - "text-table": "^0.2.0" + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, "debug": { @@ -3769,33 +3641,42 @@ "ms": "^2.1.1" } }, + "glob-parent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", + "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true } } }, "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -3812,9 +3693,9 @@ } }, "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", "dev": true }, "espree": { @@ -3831,8 +3712,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { "version": "1.0.1", @@ -3853,9 +3733,9 @@ } }, "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, "esutils": { @@ -5779,8 +5659,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-binary-path": { "version": "1.0.1", @@ -5868,8 +5747,7 @@ "is-directory": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "dev": true + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" }, "is-extendable": { "version": "0.1.1", @@ -6235,7 +6113,6 @@ "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -6256,8 +6133,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-schema": { "version": "0.2.3", @@ -7078,6 +6954,12 @@ "mkdirp": "*" } }, + "mock-fs": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.10.1.tgz", + "integrity": "sha512-w22rOL5ZYu6HbUehB5deurghGM0hS/xBVyHMGKOuQctkk93J9z9VEOhDsiWrXOprVNQpP9uzGKdl8v9mFspKuw==", + "dev": true + }, "modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -7833,6 +7715,21 @@ "os-tmpdir": "^1.0.0" } }, + "ow": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/ow/-/ow-0.13.2.tgz", + "integrity": "sha512-9wvr+q+ZTDRvXDjL6eDOdFe5WUl/wa5sntf9kAolxqSpkBqaIObwLgFCGXSJASFw+YciXnOVtDWpxXa9cqV94A==", + "requires": { + "type-fest": "^0.5.1" + }, + "dependencies": { + "type-fest": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz", + "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==" + } + } + }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -8555,13 +8452,6 @@ "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", "requires": { "lodash": "^4.17.14" - }, - "dependencies": { - "lodash": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", - "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==" - } } }, "resolve": { @@ -9237,13 +9127,13 @@ } }, "table": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.1.tgz", - "integrity": "sha512-E6CK1/pZe2N75rGZQotFOdmzWQ1AILtgYbMAbAjvms0S1l5IDB47zG3nCnFGB/w+7nB3vKofbLXCH7HPBo864w==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.5.tgz", + "integrity": "sha512-oGa2Hl7CQjfoaogtrOHEJroOcYILTx7BZWLGsJIlzoWmB2zmguhNfPJZsWPKYek/MgCxfco54gEi31d1uN2hFA==", "dev": true, "requires": { - "ajv": "^6.9.1", - "lodash": "^4.17.11", + "ajv": "^6.10.2", + "lodash": "^4.17.14", "slice-ansi": "^2.1.0", "string-width": "^3.0.0" }, @@ -9880,6 +9770,12 @@ "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", "dev": true }, + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "dev": true + }, "v8flags": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", diff --git a/package.json b/package.json index 08d3435f..fc107750 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jsdoc", "version": "4.0.0-dev", - "revision": "1563126982480", + "revision": "1567996328943", "description": "An API documentation generator for JavaScript.", "keywords": [ "documentation", @@ -14,7 +14,7 @@ }, "dependencies": { "@babel/parser": "^7.5.5", - "@jsdoc/core": "^0.1.1", + "@jsdoc/core": "file:packages/jsdoc-core", "bluebird": "^3.5.5", "catharsis": "^0.8.11", "code-prettify": "^0.1.0", @@ -32,8 +32,9 @@ "taffydb": "2.6.2" }, "devDependencies": { - "@jsdoc/eslint-config": "^1.0.1", + "@jsdoc/eslint-config": "^1.0.4", "ajv": "^6.10.2", + "eslint": "^6.3.0", "gulp": "^4.0.2", "gulp-eslint": "^6.0.0", "gulp-json-editor": "^2.5.3", @@ -42,6 +43,7 @@ "jasmine-expect": "^4.0.3", "klaw-sync": "^6.0.0", "lerna": "^3.16.4", + "mock-fs": "^4.10.1", "nyc": "^14.1.1" }, "engines": { diff --git a/packages/jsdoc-core/index.js b/packages/jsdoc-core/index.js index b85eb833..52defcbf 100644 --- a/packages/jsdoc-core/index.js +++ b/packages/jsdoc-core/index.js @@ -1,11 +1,15 @@ /** - * Provides core functionality for JSDoc. + * Core functionality for JSDoc. * - * @module @jsdoc/config + * @module @jsdoc/core */ const config = require('./lib/config'); +const Engine = require('./lib/engine'); +const util = require('./lib/util'); module.exports = { - config + config, + Engine, + util }; diff --git a/packages/jsdoc-core/lib/engine/flags.js b/packages/jsdoc-core/lib/engine/flags.js new file mode 100644 index 00000000..c673c080 --- /dev/null +++ b/packages/jsdoc-core/lib/engine/flags.js @@ -0,0 +1,114 @@ +const cast = require('../util/cast'); +const querystring = require('querystring'); + +// TODO: Document the format of this object, then update the docs for `Engine`. +/** + * Command-line flags recognized by JSDoc. + * + * @alias module:@jsdoc/core/lib/engine/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', + boolean: true, + 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.' + }, + tutorials: { + alias: 'u', + description: 'The directory to search for tutorials.', + normalize: true, + requiresArg: true + }, + verbose: { + boolean: true, + description: 'Log detailed information to the console.' + }, + version: { + alias: 'v', + boolean: true, + description: 'Display the version number and exit.' + } +}; diff --git a/packages/jsdoc-core/lib/engine/help.js b/packages/jsdoc-core/lib/engine/help.js new file mode 100644 index 00000000..0efd01f7 --- /dev/null +++ b/packages/jsdoc-core/lib/engine/help.js @@ -0,0 +1,135 @@ +const flags = require('./flags'); + +function padLeft(str, length) { + return str.padStart(str.length + length); +} + +function padRight(str, length) { + return str.padEnd(str.length + length); +} + +function findMaxLength(arr) { + let max = 0; + + arr.forEach(({length}) => { + max = Math.max(max, length); + }); + + return max; +} + +function concatWithMaxLength(items, maxLength) { + let result = ''; + + // 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()}`; + } + + return result; +} + +/** + * Format the name and description of each flag into columns, similar to the following example, + * where the pipe (`|`) characters represent the start and end of the line: + * + * ``` + * | -f, --foo Very long description very long description very long | + * | description very long description. | + * | --bar This description is not as long. | + * ``` + * + * @param {Object} flagInfo - Information about each known flag. + * @param {Array} flagInfo.names - An array of known flag names. + * @param {Array} flagInfo.descriptions - An array of descriptions for each known flag, in + * the same order as `flagInfo.names`. + * @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 = []; + + 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; + + // 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}`; + } + + results.push(result); + }); + + return results; +} + +/** + * Get a formatted version of the help text for JSDoc. + * + * @alias module:@jsdoc/core/lib/engine/help + * @param {Object} opts - Options for formatting the help text. + * @param {number} opts.maxLength - The maximum length of each line in the formatted text. + * @return {string} The formatted help text. + * @private + */ +module.exports = ({ maxLength }) => { + const flagInfo = { + names: [], + descriptions: [] + }; + + Object.keys(flags) + .sort() + .forEach(flagName => { + const flagDetail = flags[flagName]; + let description = ''; + let name = ''; + + if (flagDetail.alias) { + name += `-${flagDetail.alias}, `; + } + + name += `--${flagName}`; + + if (flagDetail.requiresArg) { + name += ' '; + } + + description += flagDetail.description; + + if (flagDetail.array) { + description += ' Can be specified more than once.'; + } + + if (flagDetail.choices) { + description += ` Accepts these values: ${flagDetail.choices.join(', ')}`; + } + + flagInfo.names.push(name); + flagInfo.descriptions.push(description); + }); + + return `${formatHelpInfo(flagInfo, {maxLength}).join('\n')}`; +}; diff --git a/packages/jsdoc-core/lib/engine/index.js b/packages/jsdoc-core/lib/engine/index.js new file mode 100644 index 00000000..79d0222f --- /dev/null +++ b/packages/jsdoc-core/lib/engine/index.js @@ -0,0 +1,206 @@ +const _ = require('lodash'); +const flags = require('./flags'); +const help = require('./help'); +const ow = require('ow'); +const yargs = require('yargs-parser'); + +function validateChoice(flagInfo, choices, values) { + let flagNames = flagInfo.alias ? `-${flagInfo.alias}/` : ''; + + flagNames += `--${flagInfo.name}`; + + for (let value of values) { + if (!choices.includes(value)) { + throw new TypeError( + `The flag ${flagNames} accepts only these values: ${choices.join(', ')}` + ); + } + } +} + +/** + * `KNOWN_FLAGS` is a set of all known flag names, including the long and short forms. + * + * `YARGS_FLAGS` is details about the known command-line flags, but in a form that `yargs-parser` + * understands. (That form is relatively hard to read, so we build this object from the more + * readable `flags` object.) + * + * @private + */ +const { KNOWN_FLAGS, YARGS_FLAGS } = (() => { + const names = new Set(); + const opts = { + alias: {}, + array: [], + boolean: [], + coerce: {}, + narg: {}, + normalize: [] + }; + + // `_` contains unparsed arguments. + names.add('_'); + + Object.keys(flags).forEach(flag => { + const value = flags[flag]; + + names.add(flag); + + if (value.alias) { + names.add(value.alias); + opts.alias[flag] = [value.alias]; + } + + if (value.array) { + opts.array.push(flag); + } + + if (value.boolean) { + opts.boolean.push(flag); + } + + if (value.coerce) { + opts.coerce[flag] = value.coerce; + } + + if (value.normalize) { + opts.normalize.push(flag); + } + + if (value.requiresArg) { + opts.narg[flag] = 1; + } + }); + + return { + KNOWN_FLAGS: names, + YARGS_FLAGS: opts + }; +})(); + +/** + * CLI engine for JSDoc. + * + * @alias module:@jsdoc/core.Engine + */ +class Engine { + /** + * Create an instance of the CLI engine. + * + * @param {Object} opts - Options for the CLI engine. + * @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); + ow(opts.revision, ow.optional.date); + ow(opts.version, ow.optional.string); + + this.flags = []; + this.revision = opts.revision; + this.version = opts.version; + } + + /** + * 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} 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)); + + // 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; + } + + /** + * 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(); + } +} + +module.exports = Engine; diff --git a/lib/jsdoc/util/cast.js b/packages/jsdoc-core/lib/util/cast.js similarity index 81% rename from lib/jsdoc/util/cast.js rename to packages/jsdoc-core/lib/util/cast.js index cbbe644b..a4d196d5 100644 --- a/lib/jsdoc/util/cast.js +++ b/packages/jsdoc-core/lib/util/cast.js @@ -1,7 +1,7 @@ /** * Module to convert values between various JavaScript types. - * @module - * @private + * + * @alias module:@jsdoc/core.util.cast */ /** @@ -46,7 +46,7 @@ function castString(str) { number = parseInt(str, 10); } - if ( String(number) === str && !isNaN(number) ) { + if (String(number) === str && !isNaN(number)) { result = number; } else { @@ -59,20 +59,20 @@ function castString(str) { } /** - * Check whether a string contains a boolean or numeric value, and convert the string to the + * Check whether a string represents another primitive type, and convert the string to the * appropriate type if necessary. * - * If an object or array is passed to this method, the object or array's values will be recursively + * If an object or array is passed to this method, the object or array's values are recursively * converted to the appropriate types. The original object or array is not modified. * * @private - * @param {(string|Object|Array)} item - The item whose type will be converted. - * @return {(string|number|boolean|Object|Array)} The converted value. + * @param {(string|Object|Array)} item - The item whose type or types will be converted. + * @return {*?} The converted value. */ -exports.cast = function cast(item) { +const cast = module.exports = item => { let result; - if ( Array.isArray(item) ) { + if (Array.isArray(item)) { result = []; for (let i = 0, l = item.length; i < l; i++) { result[i] = cast(item[i]); diff --git a/packages/jsdoc-core/lib/util/index.js b/packages/jsdoc-core/lib/util/index.js new file mode 100644 index 00000000..a9498a5f --- /dev/null +++ b/packages/jsdoc-core/lib/util/index.js @@ -0,0 +1,5 @@ +const cast = require('./cast'); + +module.exports = { + cast +}; diff --git a/packages/jsdoc-core/package-lock.json b/packages/jsdoc-core/package-lock.json index cfc92952..ec1362bc 100644 --- a/packages/jsdoc-core/package-lock.json +++ b/packages/jsdoc-core/package-lock.json @@ -33,6 +33,11 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, "cosmiconfig": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", @@ -44,6 +49,11 @@ "parse-json": "^4.0.0" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -95,11 +105,13 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, - "mock-fs": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.10.1.tgz", - "integrity": "sha512-w22rOL5ZYu6HbUehB5deurghGM0hS/xBVyHMGKOuQctkk93J9z9VEOhDsiWrXOprVNQpP9uzGKdl8v9mFspKuw==", - "dev": true + "ow": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/ow/-/ow-0.13.2.tgz", + "integrity": "sha512-9wvr+q+ZTDRvXDjL6eDOdFe5WUl/wa5sntf9kAolxqSpkBqaIObwLgFCGXSJASFw+YciXnOVtDWpxXa9cqV94A==", + "requires": { + "type-fest": "^0.5.1" + } }, "parse-json": { "version": "4.0.0", @@ -129,6 +141,20 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==" + }, + "type-fest": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz", + "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==" + }, + "yargs-parser": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-14.0.0.tgz", + "integrity": "sha512-zn/Mnx+tbFjkCFUodEpjXckNS65NfpB5oyqOkDDEG/8uxlfLZJu2IoBLQFjukUkn9rBbGkVYNzrDh6qy4NUd3g==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } } diff --git a/packages/jsdoc-core/package.json b/packages/jsdoc-core/package.json index a7723897..a1727004 100644 --- a/packages/jsdoc-core/package.json +++ b/packages/jsdoc-core/package.json @@ -24,10 +24,9 @@ "dependencies": { "cosmiconfig": "^5.2.1", "lodash": "^4.17.15", + "ow": "^0.13.2", "strip-bom": "^4.0.0", - "strip-json-comments": "^3.0.1" - }, - "devDependencies": { - "mock-fs": "^4.10.1" + "strip-json-comments": "^3.0.1", + "yargs-parser": "^14.0.0" } } diff --git a/packages/jsdoc-core/test/specs/index.js b/packages/jsdoc-core/test/specs/index.js index 287ed0ca..fb28b137 100644 --- a/packages/jsdoc-core/test/specs/index.js +++ b/packages/jsdoc-core/test/specs/index.js @@ -1,13 +1,22 @@ -const config = require('../../lib/config'); const core = require('../../index'); describe('@jsdoc/core', () => { - it('exists', () => { + it('is an object', () => { expect(core).toBeObject(); }); + describe('Engine', () => { + it('is lib/engine', () => { + const Engine = require('../../lib/engine'); + + expect(core.Engine).toBe(Engine); + }); + }); + describe('config', () => { it('is lib/config', () => { + const config = require('../../lib/config'); + expect(core.config).toBe(config); }); }); diff --git a/packages/jsdoc-core/test/specs/lib/config.js b/packages/jsdoc-core/test/specs/lib/config.js index 9ae2e05a..33ae789d 100644 --- a/packages/jsdoc-core/test/specs/lib/config.js +++ b/packages/jsdoc-core/test/specs/lib/config.js @@ -1,15 +1,15 @@ -const { config } = require('@jsdoc/core'); +const config = require('../../../lib/config'); const mockFs = require('mock-fs'); -describe('@jsdoc/config', () => { +describe('@jsdoc/core/lib/config', () => { afterEach(() => mockFs.restore()); - it('exists', () => { + it('is an object', () => { expect(config).toBeObject(); }); describe('loadSync', () => { - it('exists', () => { + it('is a function', () => { expect(config.loadSync).toBeFunction(); }); @@ -105,7 +105,7 @@ describe('@jsdoc/config', () => { describe('defaults', () => { const { defaults } = config; - it('exists', () => { + it('is an object', () => { expect(defaults).toBeObject(); }); diff --git a/packages/jsdoc-core/test/specs/lib/engine/flags.js b/packages/jsdoc-core/test/specs/lib/engine/flags.js new file mode 100644 index 00000000..e666e8d8 --- /dev/null +++ b/packages/jsdoc-core/test/specs/lib/engine/flags.js @@ -0,0 +1,36 @@ +const flags = require('../../../../lib/engine/flags'); +const ow = require('ow'); + +function validate(name, opts) { + name = `--${name}`; + + 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.`); + } + + 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.`); + } +} + +describe('@jsdoc/core/lib/engine/flags', () => { + 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(); + } + }); +}); diff --git a/packages/jsdoc-core/test/specs/lib/engine/help.js b/packages/jsdoc-core/test/specs/lib/engine/help.js new file mode 100644 index 00000000..f34a03d1 --- /dev/null +++ b/packages/jsdoc-core/test/specs/lib/engine/help.js @@ -0,0 +1,3 @@ +describe('@jsdoc/core/lib/engine/help', () => { + // Tested indirectly by the tests for `@jsdoc/core.Engine`. +}); diff --git a/packages/jsdoc-core/test/specs/lib/engine/index.js b/packages/jsdoc-core/test/specs/lib/engine/index.js new file mode 100644 index 00000000..cb1c8995 --- /dev/null +++ b/packages/jsdoc-core/test/specs/lib/engine/index.js @@ -0,0 +1,186 @@ +const Engine = require('../../../../lib/engine'); +const flags = require('../../../../lib/engine/flags'); + +describe('@jsdoc/core/lib/engine', () => { + it('exists', () => { + expect(Engine).toBeFunction(); + }); + + it('works with no input', () => { + expect(() => new Engine()).not.toThrow(); + }); + + 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 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 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(() => instance.help()).not.toThrow(); + }); + + it('throws on bad input', () => { + expect(() => instance.help('hi')).toThrow(); + }); + + 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(); + }); + }); + + describe('parseFlags', () => { + it('throws with no input', () => { + expect(() => new Engine().parseFlags()).toThrow(); + }); + + 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()})`); + }); + }); +}); diff --git a/packages/jsdoc-core/test/specs/lib/util/cast.js b/packages/jsdoc-core/test/specs/lib/util/cast.js new file mode 100644 index 00000000..1f2da6d5 --- /dev/null +++ b/packages/jsdoc-core/test/specs/lib/util/cast.js @@ -0,0 +1,66 @@ +describe('@jsdoc/core/lib/util/cast', () => { + const cast = require('../../../../lib/util/cast'); + + 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' + } + }); + + 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]]); + }); +}); diff --git a/packages/jsdoc-core/test/specs/lib/util/index.js b/packages/jsdoc-core/test/specs/lib/util/index.js new file mode 100644 index 00000000..f245dbf2 --- /dev/null +++ b/packages/jsdoc-core/test/specs/lib/util/index.js @@ -0,0 +1,15 @@ +describe('@jsdoc/core/lib/util', () => { + const util = require('../../../../lib/util'); + + it('is an object', () => { + expect(util).toBeObject(); + }); + + describe('cast', () => { + it('is lib/util/cast', () => { + const cast = require('../../../../lib/util/cast'); + + expect(util.cast).toBe(cast); + }); + }); +}); diff --git a/test/specs/jsdoc/opts/argparser.js b/test/specs/jsdoc/opts/argparser.js deleted file mode 100644 index c85b94d0..00000000 --- a/test/specs/jsdoc/opts/argparser.js +++ /dev/null @@ -1,158 +0,0 @@ -describe('jsdoc/opts/argparser', () => { - const ArgParser = require('jsdoc/opts/argparser'); - let argParser; - let ourOptions; - - function trueFalse(v) { - let r = false; - - if (v) { - if (v === 'true') { r = true; } - else if (v === 'false') { r = false; } - else { v = Boolean(r); } - } - - return r; - } - - beforeEach(() => { - argParser = new ArgParser() - .addOption('s', 'strict', true, 'Throw error on invalid input.', false, trueFalse) - .addOption('n', 'name', true, 'The name of the project.', false); - - ourOptions = argParser.parse(['-s', 'true', '-n', 'true']); - }); - - it('should be a constructor', () => { - expect(ArgParser).toBeFunction(); - expect(new ArgParser() instanceof ArgParser).toBe(true); - }); - - describe('ArgParser', () => { - it('should provide an "addIgnoredOption" method', () => { - expect(argParser.addIgnoredOption).toBeFunction(); - }); - - it('should provide an "addOption" method', () => { - expect(argParser.addOption).toBeFunction(); - }); - - it('should provide a "help" method', () => { - expect(argParser.help).toBeFunction(); - }); - - it('should provide a "parse" method', () => { - expect(argParser.parse).toBeFunction(); - }); - - describe('addIgnoredOption', () => { - it('should be chainable', () => { - expect(argParser.addIgnoredOption({})).toBe(argParser); - }); - }); - - describe('addOption', () => { - it('should be chainable', () => { - expect(argParser.addOption('a', null, false, 'Option')).toBe(argParser); - }); - }); - - describe('help', () => { - const columns = process.stdout.columns; - - beforeEach(() => { - process.stdout.columns = 80; - }); - - afterEach(() => { - process.stdout.columns = columns; - }); - - it('should format the help text correctly', () => { - const helpText = [ - 'Options:', - ' --noshortname Just a long name.', - ' -o Only a short name.', - ' -s, --supercalifragilisticexpialidocious If you say it loud enough,', - " you'll always sound", - ' precocious.' - ].join('\n'); - - argParser = new ArgParser() - .addIgnoredOption('m', 'meh', false, 'Ignore me.') - .addOption(null, 'noshortname', false, 'Just a long name.') - .addOption('o', null, true, 'Only a short name.') - .addOption('s', 'supercalifragilisticexpialidocious', false, - "If you say it loud enough, you'll always sound precocious."); - - expect(argParser.help()).toBe(helpText); - }); - }); - - describe('parse', () => { - it('should return an object with information about the options', () => { - expect(ourOptions).toBeObject(); - expect(ourOptions.strict).toBeTrue(); - expect(ourOptions.name).toBe('true'); - }); - - it('should merge the defaults into the command-line options', () => { - const defaults = { - strict: false, - name: 'Hello world!' - }; - - ourOptions = argParser.parse(['-s', true], defaults); - - expect(ourOptions.strict).toBeTrue(); - expect(ourOptions.name).toBe(defaults.name); - }); - - it('should recognize options that can be used more than once', () => { - argParser.addOption(null, 'multi', true, '', true); - - ourOptions = argParser.parse(['--multi', 'value1', '--multi', 'value2', - '--multi', 'value3']); - expect(ourOptions.multi).toEqual([ - 'value1', - 'value2', - 'value3' - ]); - }); - - it('should throw an error if an unrecognized short option is used', () => { - function badShortOption() { - argParser.parse(['-w']); - } - - expect(badShortOption).toThrow(); - }); - - it('should throw an error if an unrecognized long option is used', () => { - function badLongOption() { - argParser.parse(['--whatever']); - } - - expect(badLongOption).toThrow(); - }); - - it('should throw an error if a required value is missing', () => { - function missingValue() { - argParser.parse(['--requires-value']); - } - - argParser.addOption(null, 'requires-value', true, ''); - - expect(missingValue).toThrow(); - }); - - it('should coerce a true value if a coercer is provided', () => { - expect(ourOptions.strict).toBeTrue(); - }); - - it('should coerce a string value if no coercer is provided', () => { - expect(ourOptions.name).toBe('true'); - }); - }); - }); -}); diff --git a/test/specs/jsdoc/opts/args.js b/test/specs/jsdoc/opts/args.js deleted file mode 100644 index 9bf57bd9..00000000 --- a/test/specs/jsdoc/opts/args.js +++ /dev/null @@ -1,300 +0,0 @@ -describe('jsdoc/opts/args', () => { - const args = require('jsdoc/opts/args'); - const querystring = require('querystring'); - - it('should exist', () => { - expect(args).toBeObject(); - }); - - it('should export a "parse" function', () => { - expect(args.parse).toBeFunction(); - }); - - it('should export a "help" function', () => { - expect(args.help).toBeFunction(); - }); - - it('should export a "get" function', () => { - expect(args.get).toBeFunction(); - }); - - describe('parse', () => { - it('should accept a "-t" option and return an object with a "template" property', () => { - args.parse(['-t', 'mytemplate']); - - expect(args.get().template).toBe('mytemplate'); - }); - - it('should accept a "--template" option and return an object with a "template" property', () => { - args.parse(['--template', 'mytemplate']); - - expect(args.get().template).toBe('mytemplate'); - }); - - it('should accept a "-c" option with a JSON file and return an object with a "configure" property', () => { - args.parse(['-c', 'myconf.json']); - - expect(args.get().configure).toBe('myconf.json'); - }); - - it('should accept a "-c" option with a JS file and return an object with a "configure" property', () => { - args.parse(['-c', 'myconf.js']); - - expect(args.get().configure).toBe('myconf.js'); - }); - - it('should accept a "--configure" option with a JSON file and return an object with a "configure" property', () => { - args.parse(['--configure', 'myconf.json']); - - expect(args.get().configure).toBe('myconf.json'); - }); - - it('should accept a "--configure" option with a JS file and return an object with a "configure" property', () => { - args.parse(['--configure', 'myconf.js']); - - expect(args.get().configure).toBe('myconf.js'); - }); - - it('should accept an "-e" option and return an object with a "encoding" property', () => { - args.parse(['-e', 'ascii']); - - expect(args.get().encoding).toBe('ascii'); - }); - - it('should accept an "--encoding" option and return an object with an "encoding" property', () => { - args.parse(['--encoding', 'ascii']); - - expect(args.get().encoding).toBe('ascii'); - }); - - it('should accept a "-T" option and return an object with a "test" property', () => { - args.parse(['-T']); - - expect(args.get().test).toBe(true); - }); - - it('should accept a "--test" option and return an object with a "test" property', () => { - args.parse(['--test']); - - expect(args.get().test).toBe(true); - }); - - it('should accept a "-d" option and return an object with a "destination" property', () => { - args.parse(['-d', 'mydestination']); - - expect(args.get().destination).toBe('mydestination'); - }); - - it('should accept a "--destination" option and return an object with a "destination" property', () => { - args.parse(['--destination', 'mydestination']); - - expect(args.get().destination).toBe('mydestination'); - }); - - it('should accept a "-p" option and return an object with a "private" property', () => { - args.parse(['-p']); - - expect(args.get().private).toBe(true); - }); - - it('should accept a "--private" option and return an object with a "private" property', () => { - args.parse(['--private']); - - expect(args.get().private).toBe(true); - }); - - it('should accept a "-a" option and return an object with an "access" property', () => { - args.parse(['-a', 'public']); - - expect(args.get().access).toBe('public'); - }); - - it('should accept a "--access" option and return an object with an "access" property', () => { - args.parse(['--access', 'public']); - - expect(args.get().access).toBe('public'); - }); - - it('should accept multiple "--access" options and return an object with an "access" property', () => { - let r; - - args.parse(['--access', 'public', '--access', 'protected']); - r = args.get(); - - expect(r.access).toContain('public'); - expect(r.access).toContain('protected'); - }); - - it('should accept a "-r" option and return an object with a "recurse" property', () => { - args.parse(['-r']); - - expect(args.get().recurse).toBeTrue(); - }); - - it('should accept a "--recurse" option and return an object with a "recurse" property', () => { - args.parse(['--recurse']); - - expect(args.get().recurse).toBeTrue(); - }); - - it('should accept a "-l" option and ignore it', () => { - args.parse(['-l']); - - expect(args.get().lenient).toBeUndefined(); - }); - - it('should accept a "--lenient" option and ignore it', () => { - args.parse(['--lenient']); - - expect(args.get().lenient).toBeUndefined(); - }); - - it('should accept a "-h" option and return an object with a "help" property', () => { - args.parse(['-h']); - - expect(args.get().help).toBeTrue(); - }); - - it('should accept a "--help" option and return an object with a "help" property', () => { - args.parse(['--help']); - - expect(args.get().help).toBeTrue(); - }); - - it('should accept an "-X" option and return an object with an "explain" property', () => { - args.parse(['-X']); - - expect(args.get().explain).toBeTrue(); - }); - - it('should accept an "--explain" option and return an object with an "explain" property', () => { - args.parse(['--explain']); - - expect(args.get().explain).toBeTrue(); - }); - - it('should accept a "-q" option and return an object with a "query" property', () => { - args.parse(['-q', 'foo=bar&fab=baz']); - - expect(args.get().query).toEqual({ - foo: 'bar', - fab: 'baz' - }); - }); - - it('should accept a "--query" option and return an object with a "query" property', () => { - args.parse(['--query', 'foo=bar&fab=baz']); - - expect(args.get().query).toEqual({ - foo: 'bar', - fab: 'baz' - }); - }); - - it('should use type coercion on the "query" property so it has real booleans and numbers', () => { - const obj = { - foo: 'fab', - bar: true, - baz: false, - qux: [1, -97] - }; - - args.parse(['-q', querystring.stringify(obj)]); - - expect(args.get().query).toEqual(obj); - }); - - it('should accept a "-u" option and return an object with a "tutorials" property', () => { - args.parse(['-u', 'mytutorials']); - - expect(args.get().tutorials).toBe('mytutorials'); - }); - - it('should accept a "--tutorials" option and return an object with a "tutorials" property', () => { - args.parse(['--tutorials', 'mytutorials']); - - expect(args.get().tutorials).toBe('mytutorials'); - }); - - it('should accept a "--debug" option and return an object with a "debug" property', () => { - args.parse(['--debug']); - - expect(args.get().debug).toBeTrue(); - }); - - it('should accept a "--verbose" option and return an object with a "verbose" property', () => { - args.parse(['--verbose']); - const r = args.get(); - - expect(r.verbose).toBe(true); - }); - - it('should accept a "--pedantic" option and return an object with a "pedantic" property', () => { - args.parse(['--pedantic']); - - expect(args.get().pedantic).toBeTrue(); - }); - - it('should accept a "--match" option and return an object with a "match" property', () => { - args.parse(['--match', '.*tag']); - - expect(args.get().match).toBe('.*tag'); - }); - - it('should accept multiple "--match" options and return an object with a "match" property', () => { - args.parse(['--match', '.*tag', '--match', 'parser']); - - expect(args.get().match).toEqual(['.*tag', 'parser']); - }); - - it('should accept a "--nocolor" option and return an object with a "nocolor" property', () => { - args.parse(['--nocolor']); - - expect(args.get().nocolor).toBeTrue(); - }); - - it('should accept a "-P" option and return an object with a "package" property', () => { - args.parse(['-P', 'path/to/package/file.json']); - - expect(args.get().package).toBe('path/to/package/file.json'); - }); - - it('should accept a "--package" option and return an object with a "package" property', () => { - args.parse(['--package', 'path/to/package/file.json']); - - expect(args.get().package).toBe('path/to/package/file.json'); - }); - - it('should accept a "-R" option and return an object with a "readme" property', () => { - args.parse(['-R', 'path/to/readme/file.md']); - - expect(args.get().readme).toBe('path/to/readme/file.md'); - }); - - it('should accept a "--readme" option and return an object with a "readme" property', () => { - args.parse(['--readme', 'path/to/readme/file.md']); - - expect(args.get().readme).toBe('path/to/readme/file.md'); - }); - - it('should accept a "-v" option and return an object with a "version" property', () => { - args.parse(['-v']); - - expect(args.get().version).toBeTrue(); - }); - - it('should accept a "--version" option and return an object with a "version" property', () => { - args.parse(['--version']); - - expect(args.get().version).toBeTrue(); - }); - - it('should accept a naked option (with no "-") and return an object with a "_" property', () => { - args.parse(['myfile1', 'myfile2']); - - expect(args.get()._).toEqual(['myfile1', 'myfile2']); - }); - - // TODO: tests for args that must have values - }); -}); diff --git a/test/specs/jsdoc/util/cast.js b/test/specs/jsdoc/util/cast.js deleted file mode 100644 index 9c7c16f3..00000000 --- a/test/specs/jsdoc/util/cast.js +++ /dev/null @@ -1,95 +0,0 @@ -describe('jsdoc/util/cast', () => { - const cast = require('jsdoc/util/cast'); - - it('should exist', () => { - expect(cast).toBeObject(); - }); - - it('should export a "cast" method', () => { - expect(cast.cast).toBeFunction(); - }); - - describe('cast', () => { - it('should not modify values that are not strings, objects, or arrays', () => { - const result = cast.cast(8); - - expect(result).toBe(8); - }); - - it('should not modify strings that are not boolean-ish or number-ish', () => { - const result = cast.cast('hello world'); - - expect(result).toBe('hello world'); - }); - - it('should cast boolean-ish values to booleans', () => { - const truthish = cast.cast('true'); - const falsish = cast.cast('false'); - - expect(truthish).toBeTrue(); - expect(falsish).toBeFalse(); - }); - - it('should cast null-ish values to null', () => { - const nullish = cast.cast('null'); - - expect(nullish).toBeNull(); - }); - - it('should cast undefined-ish values to undefined', () => { - const undefinedish = cast.cast('undefined'); - - expect(undefinedish).toBeUndefined(); - }); - - it('should cast positive number-ish values to numbers', () => { - const positive = cast.cast('17.35'); - - expect(positive).toBe(17.35); - }); - - it('should cast negative number-ish values to numbers', () => { - const negative = cast.cast('-17.35'); - - expect(negative).toBe(-17.35); - }); - - it('should cast NaN-ish values to NaN', () => { - const nan = cast.cast('NaN'); - - expect(nan).toBeNaN(); - }); - - it('should convert values in an object', () => { - const result = cast.cast({ foo: 'true' }); - - expect(result).toEqual({ foo: true }); - }); - - it('should convert values in nested objects', () => { - const result = cast.cast({ - foo: { - bar: 'true' - } - }); - - expect(result).toEqual({ - foo: { - bar: true - } - }); - }); - - it('should convert values in an array', () => { - const result = cast.cast(['true', '17.35']); - - expect(result).toEqual([true, 17.35]); - }); - - it('should convert values in a nested array', () => { - const result = cast.cast(['true', ['17.35']]); - - expect(result).toEqual([true, [17.35]]); - }); - }); -});