From 25c37411d9bae8aa8d0c246d98aead9d7c2b1070 Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Sat, 26 Jan 2019 12:02:55 -0800 Subject: [PATCH] add `@jsdoc/cli` and `@jsdoc/util` modules `@jsdoc/cli` deals with command-line arguments, and `@jsdoc/util` casts values to appropriate types. --- cli.js | 4 +- lib/jsdoc/opts/argparser.js | 301 --------------- lib/jsdoc/opts/args.js | 82 ----- lib/jsdoc/src/astnode.js | 2 +- lib/jsdoc/tag/type.js | 6 +- package-lock.json | 193 ++++++---- package.json | 2 + packages/jsdoc-cli/README.md | 11 + packages/jsdoc-cli/index.js | 9 + packages/jsdoc-cli/lib/args.js | 176 +++++++++ packages/jsdoc-cli/lib/help.js | 115 ++++++ packages/jsdoc-cli/package-lock.json | 35 ++ packages/jsdoc-cli/package.json | 27 ++ packages/jsdoc-cli/test/specs/index.js | 31 ++ packages/jsdoc-cli/test/specs/lib/args.js | 99 +++++ packages/jsdoc-cli/test/specs/lib/help.js | 14 + packages/jsdoc-config/index.js | 4 +- packages/jsdoc-config/{ => lib}/defaults.js | 3 +- packages/jsdoc-config/test/specs/index.js | 12 +- .../test/specs/{ => lib}/defaults.js | 4 +- packages/jsdoc-util/README.md | 11 + packages/jsdoc-util/index.js | 7 + .../util => packages/jsdoc-util/lib}/cast.js | 13 +- packages/jsdoc-util/package-lock.json | 5 + packages/jsdoc-util/package.json | 22 ++ packages/jsdoc-util/test/specs/index.js | 19 + packages/jsdoc-util/test/specs/lib/cast.js | 89 +++++ test/specs/jsdoc/opts/argparser.js | 168 --------- test/specs/jsdoc/opts/args.js | 344 ------------------ test/specs/jsdoc/util/cast.js | 96 ----- 30 files changed, 821 insertions(+), 1083 deletions(-) delete mode 100644 lib/jsdoc/opts/argparser.js delete mode 100644 lib/jsdoc/opts/args.js create mode 100644 packages/jsdoc-cli/README.md create mode 100644 packages/jsdoc-cli/index.js create mode 100644 packages/jsdoc-cli/lib/args.js create mode 100644 packages/jsdoc-cli/lib/help.js create mode 100644 packages/jsdoc-cli/package-lock.json create mode 100644 packages/jsdoc-cli/package.json create mode 100644 packages/jsdoc-cli/test/specs/index.js create mode 100644 packages/jsdoc-cli/test/specs/lib/args.js create mode 100644 packages/jsdoc-cli/test/specs/lib/help.js rename packages/jsdoc-config/{ => lib}/defaults.js (95%) rename packages/jsdoc-config/test/specs/{ => lib}/defaults.js (95%) create mode 100644 packages/jsdoc-util/README.md create mode 100644 packages/jsdoc-util/index.js rename {lib/jsdoc/util => packages/jsdoc-util/lib}/cast.js (92%) create mode 100644 packages/jsdoc-util/package-lock.json create mode 100644 packages/jsdoc-util/package.json create mode 100644 packages/jsdoc-util/test/specs/index.js create mode 100644 packages/jsdoc-util/test/specs/lib/cast.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 31c4647b..6fa698c1 100644 --- a/cli.js +++ b/cli.js @@ -47,7 +47,7 @@ module.exports = (() => { // TODO: docs cli.loadConfig = () => { const _ = require('lodash'); - const args = require('jsdoc/opts/args'); + const args = require('@jsdoc/cli').args; let conf; const config = require('@jsdoc/config'); @@ -166,7 +166,7 @@ module.exports = (() => { // TODO: docs cli.printHelp = () => { cli.printVersion(); - console.log( `\n${require('jsdoc/opts/args').help()}\n` ); + console.log( `\n${require('@jsdoc/cli').help}\n` ); console.log('Visit http://usejsdoc.org for more information.'); return Promise.resolve(0); diff --git a/lib/jsdoc/opts/argparser.js b/lib/jsdoc/opts/argparser.js deleted file mode 100644 index a48eca8f..00000000 --- a/lib/jsdoc/opts/argparser.js +++ /dev/null @@ -1,301 +0,0 @@ -/** - * Parse the command line arguments. - * @module jsdoc/opts/argparser - */ -const _ = require('lodash'); - -const hasOwnProp = Object.prototype.hasOwnProperty; - -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}) => { - 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 = padLeft('', 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 4dbe5e5e..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').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 b597a460..140d8793 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').cast; +const cast = require('@jsdoc/util').cast; const env = require('jsdoc/env'); const name = require('jsdoc/name'); const Syntax = require('@jsdoc/syntax'); diff --git a/lib/jsdoc/tag/type.js b/lib/jsdoc/tag/type.js index f7e3843e..7eddffc8 100644 --- a/lib/jsdoc/tag/type.js +++ b/lib/jsdoc/tag/type.js @@ -1,14 +1,12 @@ /** * @module jsdoc/tag/type */ +const cast = require('@jsdoc/util').cast; const catharsis = require('catharsis'); const jsdoc = { name: require('jsdoc/name'), tag: { inline: require('jsdoc/tag/inline') - }, - util: { - cast: require('jsdoc/util/cast') } }; const util = require('util'); @@ -161,7 +159,7 @@ function parseName(tagInfo) { // like 'foo=bar' or 'foo = bar' if ( /^(.+?)\s*=\s*(.+)$/.test(tagInfo.name) ) { tagInfo.name = RegExp.$1; - tagInfo.defaultvalue = jsdoc.util.cast.cast(RegExp.$2); + tagInfo.defaultvalue = cast(RegExp.$2); } } diff --git a/package-lock.json b/package-lock.json index 2de6f6ec..edf2eaea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -127,6 +127,97 @@ "to-fast-properties": "^2.0.0" } }, + "@jsdoc/cli": { + "version": "file:packages/jsdoc-cli", + "requires": { + "@jsdoc/util": "file:packages/jsdoc-util", + "lodash": "^4.17.11", + "yargs-parser": "^11.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "camelcase": { + "version": "5.0.0", + "bundled": true + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "bundled": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "bundled": true + }, + "lcid": { + "version": "2.0.0", + "bundled": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "os-locale": { + "version": "3.1.0", + "bundled": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true + }, + "yargs": { + "version": "12.0.5", + "bundled": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "bundled": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "@jsdoc/config": { "version": "file:packages/jsdoc-config", "requires": { @@ -260,6 +351,9 @@ "@jsdoc/template-silent": { "version": "file:packages/jsdoc-template-silent" }, + "@jsdoc/util": { + "version": "file:packages/jsdoc-util" + }, "@lerna/add": { "version": "3.10.6", "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.10.6.tgz", @@ -1338,8 +1432,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "3.2.1", @@ -2186,8 +2279,7 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "collection-map": { "version": "1.0.0", @@ -2653,7 +2745,6 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, "requires": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -2728,8 +2819,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", @@ -3037,7 +3127,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, "requires": { "once": "^1.4.0" }, @@ -3046,7 +3135,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -3287,7 +3375,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, "requires": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", @@ -4387,8 +4474,7 @@ "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, "get-pkg-repo": { "version": "1.4.0", @@ -4501,7 +4587,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, "requires": { "pump": "^3.0.0" }, @@ -4510,7 +4595,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -5346,8 +5430,7 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "is-glob": { "version": "4.0.0", @@ -5423,8 +5506,7 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-subset": { "version": "0.1.1", @@ -5483,8 +5565,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", @@ -6002,7 +6083,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -6011,8 +6091,7 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" } } }, @@ -6143,7 +6222,6 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, "requires": { "p-defer": "^1.0.0" } @@ -6212,7 +6290,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", - "dev": true, "requires": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^1.0.0", @@ -6402,8 +6479,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" }, "minimatch": { "version": "3.0.4", @@ -6618,8 +6694,7 @@ "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node-fetch-npm": { "version": "2.0.2", @@ -6807,7 +6882,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, "requires": { "path-key": "^2.0.0" } @@ -6827,8 +6901,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "nyc": { "version": "13.1.0", @@ -8084,7 +8157,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", - "dev": true, "requires": { "wrappy": "1" } @@ -8211,26 +8283,22 @@ "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-is-promise": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", - "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", - "dev": true + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=" }, "p-limit": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -8239,7 +8307,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, "requires": { "p-limit": "^2.0.0" } @@ -8274,8 +8341,7 @@ "p-try": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", - "dev": true + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" }, "p-waterfall": { "version": "1.0.0", @@ -8459,8 +8525,7 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "path-parse": { "version": "1.0.6", @@ -9008,14 +9073,12 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, "requizzle": { "version": "0.2.1", @@ -9171,8 +9234,7 @@ "semver": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", - "dev": true + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" }, "semver-greatest-satisfied-range": { "version": "1.1.0", @@ -9186,8 +9248,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-value": { "version": "2.0.0", @@ -9216,7 +9277,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -9224,8 +9284,7 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "sigmund": { "version": "1.0.1", @@ -9236,8 +9295,7 @@ "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "slash": { "version": "1.0.0", @@ -9578,7 +9636,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -9587,14 +9644,12 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -9620,7 +9675,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -9637,8 +9691,7 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-indent": { "version": "2.0.0", @@ -10439,7 +10492,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -10469,7 +10521,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" @@ -10479,7 +10530,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -10488,7 +10538,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -10500,8 +10549,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "0.2.1", @@ -10569,8 +10617,7 @@ "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" }, "yallist": { "version": "2.1.2", diff --git a/package.json b/package.json index 85ff09e2..6ff361b5 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,13 @@ }, "dependencies": { "@babel/parser": "~7.2.3", + "@jsdoc/cli": "file:packages/jsdoc-cli", "@jsdoc/config": "file:packages/jsdoc-config", "@jsdoc/logger": "file:packages/jsdoc-logger", "@jsdoc/syntax": "file:packages/jsdoc-syntax", "@jsdoc/template-original": "file:packages/jsdoc-template-original", "@jsdoc/template-silent": "file:packages/jsdoc-template-silent", + "@jsdoc/util": "file:packages/jsdoc-util", "bluebird": "~3.5.0", "catharsis": "~0.8.9", "escape-string-regexp": "~1.0.5", diff --git a/packages/jsdoc-cli/README.md b/packages/jsdoc-cli/README.md new file mode 100644 index 00000000..b3ac3ebe --- /dev/null +++ b/packages/jsdoc-cli/README.md @@ -0,0 +1,11 @@ +# @jsdoc/cli + +The code that powers JSDoc's command-line interface. + +## Installing the package + +Using npm: + +```shell +npm install --save @jsdoc/cli +``` diff --git a/packages/jsdoc-cli/index.js b/packages/jsdoc-cli/index.js new file mode 100644 index 00000000..313ed072 --- /dev/null +++ b/packages/jsdoc-cli/index.js @@ -0,0 +1,9 @@ +/** + * @module @jsdoc/cli + */ + +const args = require('./lib/args'); +const help = require('./lib/help'); + +exports.args = args; +exports.help = help; diff --git a/packages/jsdoc-cli/lib/args.js b/packages/jsdoc-cli/lib/args.js new file mode 100644 index 00000000..19c15fed --- /dev/null +++ b/packages/jsdoc-cli/lib/args.js @@ -0,0 +1,176 @@ +/** + * @module @jsdoc/cli/lib/args + */ + +const _ = require('lodash'); +const cast = require('@jsdoc/util').cast; +const querystring = require('querystring'); +const yargs = require('yargs-parser'); + +const ARGS = exports.ARGS = { + 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.' + } +}; + +function validateValue(arg, choices, values) { + for (let value of values) { + if (!choices.includes(value)) { + throw new Error(`The argument --${arg} accepts only these values: ${choices}`); + } + } +} + +const YARGS_OPTS = (() => { + const opts = { + alias: {}, + array: [], + boolean: [], + coerce: {}, + narg: {}, + normalize: [] + }; + + Object.keys(ARGS).forEach(arg => { + const value = ARGS[arg]; + + if (value.alias) { + opts.alias[arg] = [value.alias]; + } + + if (value.array) { + opts.array.push(arg); + } + + if (value.boolean) { + opts.boolean.push(arg); + } + + if (value.coerce) { + opts.coerce[arg] = value.coerce; + } + + if (value.normalize) { + opts.normalize.push(arg); + } + + if (value.requiresArg) { + opts.narg[arg] = 1; + } + }); + + return opts; +})(); + +exports.parse = (args => { + const knownArgs = Object.keys(ARGS); + const parsed = yargs(args, YARGS_OPTS); + + knownArgs.forEach(arg => { + if (parsed[arg] && ARGS[arg].choices) { + validateValue(arg, ARGS[arg].choices, parsed[arg]); + } + }); + + return _.pick(parsed, knownArgs.concat(['_'])); +}); diff --git a/packages/jsdoc-cli/lib/help.js b/packages/jsdoc-cli/lib/help.js new file mode 100644 index 00000000..0a1a63cc --- /dev/null +++ b/packages/jsdoc-cli/lib/help.js @@ -0,0 +1,115 @@ +/** + * @module @jsdoc/cli/lib/help + */ + +const ARGS = require('./args').ARGS; + +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; +} + +// 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 = padLeft('', maxNameLength + (MARGIN_LENGTH * 2)); + partialDescription += concatWithMaxLength(words, wrapDescriptionAt); + result += `\n${partialDescription}`; + } + + results.push(result); + }); + + return results; +} + +module.exports = (() => { + const options = { + names: [], + descriptions: [] + }; + + Object.keys(ARGS) + .sort() + .forEach(arg => { + const opts = ARGS[arg]; + let description = ''; + let name = ''; + + if (opts.alias) { + name += `-${opts.alias}, `; + } + + name += `--${arg}`; + + if (opts.requiresArg) { + name += ' '; + } + + description += opts.description; + + if (opts.array) { + description += ' Can be specified more than once.'; + } + + if (opts.choices) { + description += ` Accepts these values: ${opts.choices.join(', ')}`; + } + + options.names.push(name); + options.descriptions.push(description); + }); + + return `Options:\n${formatHelpInfo(options).join('\n')}`; +})(); diff --git a/packages/jsdoc-cli/package-lock.json b/packages/jsdoc-cli/package-lock.json new file mode 100644 index 00000000..cfcc448a --- /dev/null +++ b/packages/jsdoc-cli/package-lock.json @@ -0,0 +1,35 @@ +{ + "name": "@jsdoc/cli", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@jsdoc/util": { + "version": "file:../jsdoc-util" + }, + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==" + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/packages/jsdoc-cli/package.json b/packages/jsdoc-cli/package.json new file mode 100644 index 00000000..af0e3992 --- /dev/null +++ b/packages/jsdoc-cli/package.json @@ -0,0 +1,27 @@ +{ + "name": "@jsdoc/cli", + "version": "1.0.0", + "description": "The code that powers JSDoc's command-line interface.", + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/jsdoc3/jsdoc" + }, + "keywords": [ + "jsdoc" + ], + "author": { + "name": "Jeff Williams", + "email": "jeffrey.l.williams@gmail.com" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/jsdoc3/jsdoc/issues" + }, + "homepage": "https://github.com/jsdoc3/jsdoc#readme", + "dependencies": { + "@jsdoc/util": "file:../jsdoc-util", + "lodash": "^4.17.11", + "yargs-parser": "^11.1.1" + } +} diff --git a/packages/jsdoc-cli/test/specs/index.js b/packages/jsdoc-cli/test/specs/index.js new file mode 100644 index 00000000..27cc87f7 --- /dev/null +++ b/packages/jsdoc-cli/test/specs/index.js @@ -0,0 +1,31 @@ +const cli = require('../../index'); + +describe('@jsdoc/cli', () => { + it('is an object', () => { + expect(cli).toBeObject(); + }); + + it('has an args object', () => { + expect(cli.args).toBeObject(); + }); + + it('has a help string', () => { + expect(cli.help).toBeString(); + }); + + describe('args', () => { + it('is ./lib/args', () => { + const args = require('../../lib/args'); + + expect(cli.args).toBe(args); + }); + }); + + describe('help', () => { + it('is ./lib/help', () => { + const help = require('../../lib/help'); + + expect(cli.help).toBe(help); + }); + }); +}); diff --git a/packages/jsdoc-cli/test/specs/lib/args.js b/packages/jsdoc-cli/test/specs/lib/args.js new file mode 100644 index 00000000..8a53d12e --- /dev/null +++ b/packages/jsdoc-cli/test/specs/lib/args.js @@ -0,0 +1,99 @@ +const args = require('../../../lib/args'); +const ARGS = args.ARGS; + +describe('@jsdoc/cli/lib/args', () => { + it('exists', () => { + expect(args).toBeObject(); + }); + + it('has an `ARGS` object', () => { + expect(args.ARGS).toBeObject(); + }); + + it('has a `parse` function', () => { + expect(args.parse).toBeFunction(); + }); + + describe('ARGS', () => { + // No need to test this object directly. + }); + + describe('parse', () => { + it('accepts an array and returns an object', () => { + const parsed = args.parse([]); + + expect(parsed).toBeObject(); + }); + + it('parses all known options', () => { + const argv = []; + + Object.keys(ARGS).forEach(arg => { + argv.push(`--${arg}`); + + if (ARGS[arg].requiresArg) { + argv.push(ARGS[arg].choices ? ARGS[arg].choices[0] : `value-for-${arg}`); + } + }); + + const parsed = args.parse(argv); + + expect(Object.keys(parsed).sort()) + .toEqual(Object.keys(ARGS).concat(['_']).sort()); + }); + + it('puts extra arguments in the `_` array', () => { + const parsed = args.parse(['foo.js']); + + expect(parsed._).toEqual(['foo.js']); + }); + + it('ignores unknown options', () => { + const parsed = args.parse(['--foo']); + + expect(parsed.foo).toBeUndefined(); + }); + + describe('--access', () => { + it('is returned as an array even when specified only once', () => { + const parsed = args.parse(['--access', 'public']); + + expect(parsed.access).toEqual(['public']); + }); + + it('accepts the argument more than once', () => { + const parsed = args.parse([ + '--access', + 'protected', + '--access', + 'public' + ]); + + expect(parsed.access.sort()).toEqual(['protected', 'public']); + }); + + it('throws on unknown values', () => { + function badArg() { + return args.parse(['--access', 'ibility']); + } + + expect(badArg).toThrowError(); + }); + }); + + describe('--query', () => { + it('is returned as an object', () => { + const parsed = args.parse(['--query', 'foo=bar']); + + expect(parsed.query).toEqual({foo: 'bar'}); + }); + + it('coerces values to the appropriate types', () => { + const parsed = args.parse(['--query', 'foo=true&bar=17']); + + expect(parsed.query.foo).toBeTrue(); + expect(parsed.query.bar).toBe(17); + }); + }); + }); +}); diff --git a/packages/jsdoc-cli/test/specs/lib/help.js b/packages/jsdoc-cli/test/specs/lib/help.js new file mode 100644 index 00000000..617fdbfc --- /dev/null +++ b/packages/jsdoc-cli/test/specs/lib/help.js @@ -0,0 +1,14 @@ +const ARGS = require('../../../lib/args').ARGS; +const help = require('../../../lib/help'); + +describe('@jsdoc/cli/lib/help', () => { + it('is a string', () => { + expect(help).toBeString(); + }); + + it('covers all known command-line arguments', () => { + Object.keys(ARGS).forEach(arg => { + expect(help.includes(arg)).toBeTrue(); + }); + }); +}); diff --git a/packages/jsdoc-config/index.js b/packages/jsdoc-config/index.js index 1bf55104..424fd729 100644 --- a/packages/jsdoc-config/index.js +++ b/packages/jsdoc-config/index.js @@ -4,7 +4,7 @@ const _ = require('lodash'); const cosmiconfig = require('cosmiconfig'); -const defaults = require('./defaults'); +const defaults = require('./lib/defaults'); const stripBom = require('strip-bom'); const stripJsonComments = require('strip-json-comments'); @@ -44,6 +44,8 @@ const explorer = cosmiconfig(MODULE_NAME, { ] }); +exports.defaults = defaults; + exports.loadSync = (filepath) => { let loaded; diff --git a/packages/jsdoc-config/defaults.js b/packages/jsdoc-config/lib/defaults.js similarity index 95% rename from packages/jsdoc-config/defaults.js rename to packages/jsdoc-config/lib/defaults.js index 2157c268..57c10fdf 100644 --- a/packages/jsdoc-config/defaults.js +++ b/packages/jsdoc-config/lib/defaults.js @@ -1,12 +1,13 @@ /** * The default configuration settings for JSDoc. * - * @module @jsdoc/config/defaults + * @module @jsdoc/config/lib/defaults */ module.exports = { // TODO(hegemonic): integrate CLI options with other options opts: { + access: ['package', 'protected', 'public', 'undefined'], destination: './out', encoding: 'utf8' }, diff --git a/packages/jsdoc-config/test/specs/index.js b/packages/jsdoc-config/test/specs/index.js index f8ada3b8..1f936e01 100644 --- a/packages/jsdoc-config/test/specs/index.js +++ b/packages/jsdoc-config/test/specs/index.js @@ -1,7 +1,7 @@ describe('@jsdoc/config', () => { const mockFs = require('mock-fs'); const config = require('../../index'); - const defaults = require('../../defaults'); + const defaults = require('../../lib/defaults'); afterEach(() => mockFs.restore()); @@ -9,10 +9,20 @@ describe('@jsdoc/config', () => { expect(config).toBeObject(); }); + it('has a defaults object', () => { + expect(config.defaults).toBeObject(); + }); + it('has a loadSync method', () => { expect(config.loadSync).toBeFunction(); }); + describe('defaults', () => { + it('is ./lib/defaults', () => { + expect(config.defaults).toBe(defaults); + }); + }); + describe('loadSync', () => { it('returns an object with `config` and `filepath` properties', () => { mockFs({ diff --git a/packages/jsdoc-config/test/specs/defaults.js b/packages/jsdoc-config/test/specs/lib/defaults.js similarity index 95% rename from packages/jsdoc-config/test/specs/defaults.js rename to packages/jsdoc-config/test/specs/lib/defaults.js index ee15f69c..74f91b5a 100644 --- a/packages/jsdoc-config/test/specs/defaults.js +++ b/packages/jsdoc-config/test/specs/lib/defaults.js @@ -1,6 +1,6 @@ -const defaults = require('../../defaults'); +const defaults = require('../../../lib/defaults'); -describe('@jsdoc/config/defaults', () => { +describe('@jsdoc/config/lib/defaults', () => { it('exists', () => { expect(defaults).toBeObject(); }); diff --git a/packages/jsdoc-util/README.md b/packages/jsdoc-util/README.md new file mode 100644 index 00000000..c3716ab7 --- /dev/null +++ b/packages/jsdoc-util/README.md @@ -0,0 +1,11 @@ +# @jsdoc/util + +Utility modules for JSDoc. + +## Installing the package + +Using npm: + +```shell +npm install --save @jsdoc/util +``` diff --git a/packages/jsdoc-util/index.js b/packages/jsdoc-util/index.js new file mode 100644 index 00000000..bc09cc39 --- /dev/null +++ b/packages/jsdoc-util/index.js @@ -0,0 +1,7 @@ +/** + * @module @jsdoc/util + */ + +const cast = require('./lib/cast'); + +exports.cast = cast; diff --git a/lib/jsdoc/util/cast.js b/packages/jsdoc-util/lib/cast.js similarity index 92% rename from lib/jsdoc/util/cast.js rename to packages/jsdoc-util/lib/cast.js index cbbe644b..63580591 100644 --- a/lib/jsdoc/util/cast.js +++ b/packages/jsdoc-util/lib/cast.js @@ -1,7 +1,7 @@ /** * Module to convert values between various JavaScript types. - * @module - * @private + * + * @module @jsdoc/util/lib/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 { @@ -65,14 +65,13 @@ function castString(str) { * If an object or array is passed to this method, the object or array's values will be 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. */ -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]); @@ -92,4 +91,4 @@ exports.cast = function cast(item) { } return result; -}; +}); diff --git a/packages/jsdoc-util/package-lock.json b/packages/jsdoc-util/package-lock.json new file mode 100644 index 00000000..47467c1b --- /dev/null +++ b/packages/jsdoc-util/package-lock.json @@ -0,0 +1,5 @@ +{ + "name": "@jsdoc/util", + "version": "1.0.0", + "lockfileVersion": 1 +} diff --git a/packages/jsdoc-util/package.json b/packages/jsdoc-util/package.json new file mode 100644 index 00000000..0adc3aeb --- /dev/null +++ b/packages/jsdoc-util/package.json @@ -0,0 +1,22 @@ +{ + "name": "@jsdoc/util", + "version": "1.0.0", + "description": "Utility modules for JSDoc.", + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/jsdoc3/jsdoc" + }, + "keywords": [ + "jsdoc" + ], + "author": { + "name": "Jeff Williams", + "email": "jeffrey.l.williams@gmail.com" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/jsdoc3/jsdoc/issues" + }, + "homepage": "https://github.com/jsdoc3/jsdoc#readme" +} diff --git a/packages/jsdoc-util/test/specs/index.js b/packages/jsdoc-util/test/specs/index.js new file mode 100644 index 00000000..d7e48a96 --- /dev/null +++ b/packages/jsdoc-util/test/specs/index.js @@ -0,0 +1,19 @@ +const util = require('../../index'); + +describe('@jsdoc/util', () => { + it('is an object', () => { + expect(util).toBeObject(); + }); + + it('has a cast method', () => { + expect(util.cast).toBeFunction(); + }); + + describe('cast', () => { + it('is ./lib/cast', () => { + const cast = require('../../lib/cast'); + + expect(util.cast).toBe(cast); + }); + }); +}); diff --git a/packages/jsdoc-util/test/specs/lib/cast.js b/packages/jsdoc-util/test/specs/lib/cast.js new file mode 100644 index 00000000..852e0037 --- /dev/null +++ b/packages/jsdoc-util/test/specs/lib/cast.js @@ -0,0 +1,89 @@ +const cast = require('../../../lib/cast'); + +describe('@jsdoc/util/lib/cast', () => { + it('exists', () => { + expect(cast).toBeFunction(); + }); + + it('does not modify values that are not strings, objects, or arrays', () => { + const result = cast(8); + + expect(result).toBe(8); + }); + + it('does not modify strings that are not boolean-ish or number-ish', () => { + const result = cast('hello world'); + + expect(result).toBe('hello world'); + }); + + it('casts boolean-ish values to booleans', () => { + const truthish = cast('true'); + const falsish = cast('false'); + + expect(truthish).toBeTrue(); + expect(falsish).toBeFalse(); + }); + + it('casts null-ish values to null', () => { + const nullish = cast('null'); + + expect(nullish).toBeNull(); + }); + + it('casts undefined-ish values to undefined', () => { + const undefinedish = cast('undefined'); + + expect(undefinedish).toBeUndefined(); + }); + + it('casts positive number-ish values to numbers', () => { + const positive = cast('17.35'); + + expect(positive).toBe(17.35); + }); + + it('casts negative number-ish values to numbers', () => { + const negative = cast('-17.35'); + + expect(negative).toBe(-17.35); + }); + + it('casts NaN-ish values to NaN', () => { + const nan = cast('NaN'); + + expect(nan).toBeNaN(); + }); + + it('converts values in an object', () => { + const result = cast({ foo: 'true' }); + + expect(result).toEqual({ foo: true }); + }); + + it('converts values in nested objects', () => { + const result = cast({ + foo: { + bar: 'true' + } + }); + + expect(result).toEqual({ + foo: { + bar: true + } + }); + }); + + it('converts values in an array', () => { + const result = cast(['true', '17.35']); + + expect(result).toEqual([true, 17.35]); + }); + + it('converts values in a nested array', () => { + const result = cast(['true', ['17.35']]); + + expect(result).toEqual([true, [17.35]]); + }); +}); diff --git a/test/specs/jsdoc/opts/argparser.js b/test/specs/jsdoc/opts/argparser.js deleted file mode 100644 index cf795b2d..00000000 --- a/test/specs/jsdoc/opts/argparser.js +++ /dev/null @@ -1,168 +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 exist', () => { - expect(ArgParser).toBeDefined(); - }); - - it('should be a constructor', () => { - expect(typeof ArgParser).toBe('function'); - expect(new ArgParser() instanceof ArgParser).toBe(true); - }); - - describe('ArgParser', () => { - it('should provide an "addIgnoredOption" method', () => { - expect(argParser.addIgnoredOption).toBeDefined(); - expect(typeof argParser.addIgnoredOption).toBe('function'); - }); - - it('should provide an "addOption" method', () => { - expect(argParser.addOption).toBeDefined(); - expect(typeof argParser.addOption).toBe('function'); - }); - - it('should provide a "help" method', () => { - expect(argParser.help).toBeDefined(); - expect(typeof argParser.help).toBe('function'); - }); - - it('should provide a "parse" method', () => { - expect(argParser.parse).toBeDefined(); - expect(typeof argParser.parse).toBe('function'); - }); - - 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(typeof ourOptions).toBe('object'); - expect(ourOptions.strict).toBe(true); - 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).toBe(true); - 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(Array.isArray(ourOptions.multi)).toBe(true); - expect(ourOptions.multi.length).toBe(3); - expect(ourOptions.multi[0]).toBe('value1'); - expect(ourOptions.multi[1]).toBe('value2'); - expect(ourOptions.multi[2]).toBe('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).toBeDefined(); - expect(ourOptions.strict).toBe(true); - }); - - it('should coerce a string value if no coercer is provided', () => { - expect(ourOptions.name).toBeDefined(); - 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 14d16f01..00000000 --- a/test/specs/jsdoc/opts/args.js +++ /dev/null @@ -1,344 +0,0 @@ -describe('jsdoc/opts/args', () => { - const args = require('jsdoc/opts/args'); - const querystring = require('querystring'); - - it('should exist', () => { - expect(args).toBeDefined(); - expect(typeof args).toBe('object'); - }); - - it('should export a "parse" function', () => { - expect(args.parse).toBeDefined(); - expect(typeof args.parse).toBe('function'); - }); - - it('should export a "help" function', () => { - expect(args.help).toBeDefined(); - expect(typeof args.help).toBe('function'); - }); - - it('should export a "get" function', () => { - expect(args.get).toBeDefined(); - expect(typeof args.get).toBe('function'); - }); - - describe('parse', () => { - it('should accept a "-t" option and return an object with a "template" property', () => { - args.parse(['-t', 'mytemplate']); - const r = args.get(); - - expect(r.template).toBe('mytemplate'); - }); - - it('should accept a "--template" option and return an object with a "template" property', () => { - args.parse(['--template', 'mytemplate']); - const r = args.get(); - - expect(r.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']); - const r = args.get(); - - expect(r.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']); - const r = args.get(); - - expect(r.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']); - const r = args.get(); - - expect(r.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']); - const r = args.get(); - - expect(r.configure).toBe('myconf.js'); - }); - - it('should accept an "-e" option and return an object with a "encoding" property', () => { - args.parse(['-e', 'ascii']); - const r = args.get(); - - expect(r.encoding).toBe('ascii'); - }); - - it('should accept an "--encoding" option and return an object with an "encoding" property', () => { - args.parse(['--encoding', 'ascii']); - const r = args.get(); - - expect(r.encoding).toBe('ascii'); - }); - - it('should accept a "-T" option and return an object with a "test" property', () => { - args.parse(['-T']); - const r = args.get(); - - expect(r.test).toBe(true); - }); - - it('should accept a "--test" option and return an object with a "test" property', () => { - args.parse(['--test']); - const r = args.get(); - - expect(r.test).toBe(true); - }); - - it('should accept a "-d" option and return an object with a "destination" property', () => { - args.parse(['-d', 'mydestination']); - const r = args.get(); - - expect(r.destination).toBe('mydestination'); - }); - - it('should accept a "--destination" option and return an object with a "destination" property', () => { - args.parse(['--destination', 'mydestination']); - const r = args.get(); - - expect(r.destination).toBe('mydestination'); - }); - - it('should accept a "-p" option and return an object with a "private" property', () => { - args.parse(['-p']); - const r = args.get(); - - expect(r.private).toBe(true); - }); - - it('should accept a "--private" option and return an object with a "private" property', () => { - args.parse(['--private']); - const r = args.get(); - - expect(r.private).toBe(true); - }); - - it('should accept a "-a" option and return an object with an "access" property', () => { - args.parse(['-a', 'public']); - const r = args.get(); - - expect(r.access).toBe('public'); - }); - - it('should accept a "--access" option and return an object with an "access" property', () => { - args.parse(['--access', 'public']); - const r = args.get(); - - expect(r.access).toBe('public'); - }); - - it('should accept multiple "--access" options and return an object with an "access" property', () => { - args.parse(['--access', 'public', '--access', 'protected']); - const 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']); - const r = args.get(); - - expect(r.recurse).toBe(true); - }); - - it('should accept a "--recurse" option and return an object with a "recurse" property', () => { - args.parse(['--recurse']); - const r = args.get(); - - expect(r.recurse).toBe(true); - }); - - it('should accept a "-l" option and ignore it', () => { - args.parse(['-l']); - const r = args.get(); - - expect(r.lenient).not.toBeDefined(); - }); - - it('should accept a "--lenient" option and ignore it', () => { - args.parse(['--lenient']); - const r = args.get(); - - expect(r.lenient).not.toBeDefined(); - }); - - it('should accept a "-h" option and return an object with a "help" property', () => { - args.parse(['-h']); - const r = args.get(); - - expect(r.help).toBe(true); - }); - - it('should accept a "--help" option and return an object with a "help" property', () => { - args.parse(['--help']); - const r = args.get(); - - expect(r.help).toBe(true); - }); - - it('should accept an "-X" option and return an object with an "explain" property', () => { - args.parse(['-X']); - const r = args.get(); - - expect(r.explain).toBe(true); - }); - - it('should accept an "--explain" option and return an object with an "explain" property', () => { - args.parse(['--explain']); - const r = args.get(); - - expect(r.explain).toBe(true); - }); - - it('should accept a "-q" option and return an object with a "query" property', () => { - args.parse(['-q', 'foo=bar&fab=baz']); - const r = args.get(); - - expect(r.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']); - const r = args.get(); - - expect(r.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] - }; - let r; - - args.parse(['-q', querystring.stringify(obj)]); - r = args.get(); - - expect(r.query).toEqual(obj); - }); - - it('should accept a "-u" option and return an object with a "tutorials" property', () => { - args.parse(['-u', 'mytutorials']); - const r = args.get(); - - expect(r.tutorials).toBe('mytutorials'); - }); - - it('should accept a "--tutorials" option and return an object with a "tutorials" property', () => { - args.parse(['--tutorials', 'mytutorials']); - const r = args.get(); - - expect(r.tutorials).toBe('mytutorials'); - }); - - it('should accept a "--debug" option and return an object with a "debug" property', () => { - args.parse(['--debug']); - const r = args.get(); - - expect(r.debug).toBe(true); - }); - - 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']); - const r = args.get(); - - expect(r.pedantic).toBe(true); - }); - - it('should accept a "--match" option and return an object with a "match" property', () => { - args.parse(['--match', '.*tag']); - const r = args.get(); - - expect(r.match).toBe('.*tag'); - }); - - it('should accept multiple "--match" options and return an object with a "match" property', () => { - args.parse(['--match', '.*tag', '--match', 'parser']); - const r = args.get(); - - expect(r.match).toEqual(['.*tag', 'parser']); - }); - - it('should accept a "--nocolor" option and return an object with a "nocolor" property', () => { - args.parse(['--nocolor']); - const r = args.get(); - - expect(r.nocolor).toBe(true); - }); - - it('should accept a "-P" option and return an object with a "package" property', () => { - args.parse(['-P', 'path/to/package/file.json']); - const r = args.get(); - - expect(r.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']); - const r = args.get(); - - expect(r.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']); - const r = args.get(); - - expect(r.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']); - const r = args.get(); - - expect(r.readme).toBe('path/to/readme/file.md'); - }); - - it('should accept a "-v" option and return an object with a "version" property', () => { - args.parse(['-v']); - const r = args.get(); - - expect(r.version).toBe(true); - }); - - it('should accept a "--version" option and return an object with a "version" property', () => { - args.parse(['--version']); - const r = args.get(); - - expect(r.version).toBe(true); - }); - - it('should accept a naked option (with no "-") and return an object with a "_" property', () => { - args.parse(['myfile1', 'myfile2']); - const r = args.get(); - - expect(r._).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 164e5ef3..00000000 --- a/test/specs/jsdoc/util/cast.js +++ /dev/null @@ -1,96 +0,0 @@ -describe('jsdoc/util/cast', () => { - const cast = require('jsdoc/util/cast'); - - it('should exist', () => { - expect(typeof cast).toBe('object'); - }); - - it('should export a "cast" method', () => { - expect(typeof cast.cast).toBe('function'); - }); - - 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).toBe(true); - expect(falsish).toBe(false); - }); - - it('should cast null-ish values to null', () => { - const nullish = cast.cast('null'); - - expect(nullish).toBe(null); - }); - - 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(typeof nan).toBe('number'); - expect(isNaN(nan)).toBe(true); - }); - - 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]]); - }); - }); -});