From 4badaef5fd901add874ece58074287bcc6d0e5da Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Fri, 25 Jan 2019 19:33:18 -0800 Subject: [PATCH] better config loading (#1550) + Use `cosmiconfig` instead of rolling our own code (which gives us YAML support) + Look for the config in these locations, and in this order: + A `jsdoc` property in `package.json` + `.jsdocrc` (can be JSON or YAML; comments not allowed for JSON) + `.jsdocrc.json` (comments allowed) + `.jsdocrc.yaml` + `.jsdocrc.yml` + `.jsdocrc.js` + `jsdoc.config.js` --- cli.js | 51 ++------ lib/jsdoc/config.js | 74 ----------- lib/jsdoc/src/astbuilder.js | 2 +- package-lock.json | 126 ++++++++++++++++++ package.json | 4 +- packages/jsdoc-config/README.md | 11 ++ packages/jsdoc-config/defaults.js | 78 +++++++++++ packages/jsdoc-config/index.js | 60 +++++++++ packages/jsdoc-config/package-lock.json | 128 +++++++++++++++++++ packages/jsdoc-config/package.json | 29 +++++ packages/jsdoc-config/test/specs/defaults.js | 81 ++++++++++++ packages/jsdoc-config/test/specs/index.js | 105 +++++++++++++++ test/index.js | 3 + test/specs/jsdoc/config.js | 92 ------------- 14 files changed, 636 insertions(+), 208 deletions(-) delete mode 100644 lib/jsdoc/config.js create mode 100644 packages/jsdoc-config/README.md create mode 100644 packages/jsdoc-config/defaults.js create mode 100644 packages/jsdoc-config/index.js create mode 100644 packages/jsdoc-config/package-lock.json create mode 100644 packages/jsdoc-config/package.json create mode 100644 packages/jsdoc-config/test/specs/defaults.js create mode 100644 packages/jsdoc-config/test/specs/index.js delete mode 100644 test/specs/jsdoc/config.js diff --git a/cli.js b/cli.js index 365b10e3..31c4647b 100644 --- a/cli.js +++ b/cli.js @@ -48,18 +48,8 @@ module.exports = (() => { cli.loadConfig = () => { const _ = require('lodash'); const args = require('jsdoc/opts/args'); - const Config = require('jsdoc/config'); - let config; - const fs = require('jsdoc/fs'); - const path = require('jsdoc/path'); - - let confPath; - let isFile; - - const defaultOpts = { - destination: './out/', - encoding: 'utf8' - }; + let conf; + const config = require('@jsdoc/config'); try { env.opts = args.parse(env.args); @@ -71,37 +61,18 @@ module.exports = (() => { }); } - confPath = env.opts.configure || path.join(env.dirname, 'conf.json'); try { - isFile = fs.statSync(confPath).isFile(); - } - catch (e) { - isFile = false; + conf = config.loadSync(env.opts.configure); + env.conf = conf.config; + } catch (e) { + cli.exit( + 1, + `Cannot parse the config file ${conf.filepath}: ${e}\n${FATAL_ERROR_MESSAGE}` + ); } - if ( !isFile && !env.opts.configure ) { - confPath = path.join(env.dirname, 'conf.json.EXAMPLE'); - } - - try { - switch ( path.extname(confPath) ) { - case '.js': - config = require( path.resolve(confPath) ) || {}; - break; - case '.json': - case '.EXAMPLE': - default: - config = fs.readFileSync(confPath, 'utf8'); - break; - } - env.conf = new Config(config).get(); - } - catch (e) { - cli.exit(1, `Cannot parse the config file ${confPath}: ${e}\n${FATAL_ERROR_MESSAGE}`); - } - - // look for options on the command line, in the config file, and in the defaults, in that order - env.opts = _.defaults(env.opts, env.conf.opts, defaultOpts); + // look for options on the command line, then in the config + env.opts = _.defaults(env.opts, env.conf.opts); return cli; }; diff --git a/lib/jsdoc/config.js b/lib/jsdoc/config.js deleted file mode 100644 index 8e867498..00000000 --- a/lib/jsdoc/config.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @module jsdoc/config - */ -const stripBom = require('jsdoc/util/stripbom'); -const stripJsonComments = require('strip-json-comments'); - -function mergeRecurse(target, source) { - Object.keys(source).forEach(p => { - if ( source[p].constructor === Object ) { - if ( !target[p] ) { - target[p] = {}; - } - mergeRecurse(target[p], source[p]); - } - else { - target[p] = source[p]; - } - }); - - return target; -} - -// required config values, override these defaults in your config.json if necessary -const defaults = { - plugins: [], - recurseDepth: 10, - source: { - includePattern: '.+\\.js(doc|x)?$', - excludePattern: '' - }, - sourceType: 'module', - tags: { - allowUnknownTags: true, - dictionaries: ['jsdoc', 'closure'] - }, - templates: { - monospaceLinks: false, - cleverLinks: false - } -}; - -/** - * Represents a JSDoc application configuration. - */ -class Config { - /** - * @param {(string|object)} [jsonOrObject] - The contents of config.json, or a JavaScript object - * exported from a .js config file. - */ - constructor(jsonOrObject) { - if (typeof jsonOrObject === 'undefined') { - jsonOrObject = {}; - } - - if (typeof jsonOrObject === 'string') { - jsonOrObject = JSON.parse( (stripJsonComments(stripBom.strip(jsonOrObject)) || '{}') ); - } - - if (typeof jsonOrObject !== 'object') { - jsonOrObject = {}; - } - - this._config = mergeRecurse(defaults, jsonOrObject); - } - - /** - * Get the merged configuration values. - */ - get() { - return this._config; - } -} - -module.exports = Config; diff --git a/lib/jsdoc/src/astbuilder.js b/lib/jsdoc/src/astbuilder.js index 6af10577..cf53aa8f 100644 --- a/lib/jsdoc/src/astbuilder.js +++ b/lib/jsdoc/src/astbuilder.js @@ -38,7 +38,7 @@ const parserOptions = exports.parserOptions = { 'throwExpressions' ], ranges: true, - sourceType: env.conf.sourceType + sourceType: env.conf.source.type }; function parse(source, filename) { diff --git a/package-lock.json b/package-lock.json index 1a4f1293..2de6f6ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -127,6 +127,111 @@ "to-fast-properties": "^2.0.0" } }, + "@jsdoc/config": { + "version": "file:packages/jsdoc-config", + "requires": { + "cosmiconfig": "^5.0.7", + "lodash": "^4.17.11", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.1" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "bundled": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "caller-callsite": { + "version": "2.0.0", + "bundled": true, + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "2.0.0", + "bundled": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "2.0.0", + "bundled": true + }, + "cosmiconfig": { + "version": "5.0.7", + "bundled": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0" + } + }, + "error-ex": { + "version": "1.3.2", + "bundled": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "esprima": { + "version": "4.0.1", + "bundled": true + }, + "import-fresh": { + "version": "2.0.0", + "bundled": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true + }, + "is-directory": { + "version": "0.3.1", + "bundled": true + }, + "js-yaml": { + "version": "3.12.1", + "bundled": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true + }, + "parse-json": { + "version": "4.0.0", + "bundled": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "resolve-from": { + "version": "3.0.0", + "bundled": true + }, + "sprintf-js": { + "version": "1.0.3", + "bundled": true + }, + "strip-bom": { + "version": "3.0.0", + "bundled": true + } + } + }, "@jsdoc/logger": { "version": "file:packages/jsdoc-logger" }, @@ -1170,6 +1275,12 @@ "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", "dev": true }, + "add-matchers": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/add-matchers/-/add-matchers-0.6.2.tgz", + "integrity": "sha512-hVO2wodMei9RF00qe+506MoeJ/NEOdCMEkSJ12+fC3hx/5Z4zmhNiP92nJEF6XhmXokeB0hOtuQrjHCx2vmXrQ==", + "dev": true + }, "agent-base": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", @@ -5424,6 +5535,15 @@ "integrity": "sha512-3/xSmG/d35hf80BEN66Y6g9Ca5l/Isdeg/j6zvbTYlTzeKinzmaTM4p9am5kYqOmE05D7s1t8FGjzdSnbUbceA==", "dev": true }, + "jasmine-expect": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jasmine-expect/-/jasmine-expect-4.0.1.tgz", + "integrity": "sha512-tn+sdVx04MRHpW6ltIkEKANRO29C7AUdmkeupFrwbAawd8ICA/IZT9YsdIljGuxU+wLYoOJsaics6swnw2lO2g==", + "dev": true, + "requires": { + "add-matchers": "0.6.2" + } + }, "js-beautify": { "version": "1.8.9", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.8.9.tgz", @@ -6401,6 +6521,12 @@ "minimist": "0.0.8" } }, + "mock-fs": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.7.0.tgz", + "integrity": "sha512-WlQNtUlzMRpvLHf8dqeUmNqfdPjGY29KrJF50Ldb4AcL+vQeR8QH3wQcFMgrhTwb1gHjZn9xggho+84tBskLgA==", + "dev": true + }, "modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", diff --git a/package.json b/package.json index 74c1122d..85ff09e2 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@babel/parser": "~7.2.3", + "@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", @@ -28,7 +29,6 @@ "marked": "~0.6.0", "mkdirp": "~0.5.1", "requizzle": "~0.2.1", - "strip-json-comments": "~2.0.1", "taffydb": "2.6.2" }, "devDependencies": { @@ -37,8 +37,10 @@ "gulp-eslint": "~5.0.0", "gulp-json-editor": "~2.5.0", "jasmine": "^3.3.1", + "jasmine-expect": "^4.0.1", "klaw-sync": "^6.0.0", "lerna": "^3.10.6", + "mock-fs": "^4.7.0", "nyc": "~13.1.0" }, "greenkeeper": { diff --git a/packages/jsdoc-config/README.md b/packages/jsdoc-config/README.md new file mode 100644 index 00000000..e7baf0bb --- /dev/null +++ b/packages/jsdoc-config/README.md @@ -0,0 +1,11 @@ +# @jsdoc/config + +Loads configuration settings for JSDoc. + +## Installing the package + +Using npm: + +```shell +npm install --save @jsdoc/config +``` diff --git a/packages/jsdoc-config/defaults.js b/packages/jsdoc-config/defaults.js new file mode 100644 index 00000000..2157c268 --- /dev/null +++ b/packages/jsdoc-config/defaults.js @@ -0,0 +1,78 @@ +/** + * The default configuration settings for JSDoc. + * + * @module @jsdoc/config/defaults + */ + +module.exports = { + // TODO(hegemonic): integrate CLI options with other options + opts: { + destination: './out', + encoding: 'utf8' + }, + /** + * The JSDoc plugins to load. + */ + plugins: [], + // TODO(hegemonic): move to `source` or remove + recurseDepth: 10, + // TODO(hegemonic): switch to glob patterns + /** + * Settings for loading and parsing source files. + */ + source: { + /** + * A regular expression that matches source files to exclude from processing. + * + * To exclude files if any portion of their path begins with an underscore, use the value + * `(^|\\/|\\\\)_`. + */ + excludePattern: '', + /** + * A regular expression that matches source files that JSDoc should process. + * + * By default, all source files with the extensions `.js`, `.jsdoc`, and `.jsx` are + * processed. + */ + includePattern: '.+\\.js(doc|x)?$', + /** + * The type of source file. In general, you should use the value `module`. If none of your + * source files use ECMAScript >=2015 syntax, you can use the value `script`. + */ + type: 'module' + }, + /** + * Settings for interpreting JSDoc tags. + */ + tags: { + /** + * Set to `true` to allow tags that JSDoc does not recognize. + */ + allowUnknownTags: true, + // TODO(hegemonic): use module paths, not magic strings + /** + * The JSDoc tag dictionaries to load. + * + * If you specify two or more tag dictionaries, and a tag is defined in multiple + * dictionaries, JSDoc uses the definition from the first dictionary that includes that tag. + */ + dictionaries: [ + 'jsdoc', + 'closure' + ] + }, + /** + * Settings for generating output with JSDoc templates. + */ + templates: { + /** + * Set to `true` to use a monospaced font for links to other code symbols, but not links to + * websites. + */ + cleverLinks: false, + /** + * Set to `true` to use a monospaced font for all links. + */ + monospaceLinks: false + } +}; diff --git a/packages/jsdoc-config/index.js b/packages/jsdoc-config/index.js new file mode 100644 index 00000000..1bf55104 --- /dev/null +++ b/packages/jsdoc-config/index.js @@ -0,0 +1,60 @@ +/** + * @module @jsdoc/config + */ + +const _ = require('lodash'); +const cosmiconfig = require('cosmiconfig'); +const defaults = require('./defaults'); +const stripBom = require('strip-bom'); +const stripJsonComments = require('strip-json-comments'); + +const MODULE_NAME = 'jsdoc'; + +class Config { + constructor(filepath, config) { + this.config = config; + this.filepath = filepath; + } +} + +function loadJson(filepath, content) { + return cosmiconfig.loadJson(filepath, stripBom(stripJsonComments(content))); +} + +function loadYaml(filepath, content) { + return cosmiconfig.loadYaml(filepath, stripBom(content)); +} + +const explorer = cosmiconfig(MODULE_NAME, { + cache: false, + loaders: { + '.json': loadJson, + '.yaml': loadYaml, + '.yml': loadYaml, + noExt: loadYaml + }, + searchPlaces: [ + 'package.json', + `.${MODULE_NAME}rc`, + `.${MODULE_NAME}rc.json`, + `.${MODULE_NAME}rc.yaml`, + `.${MODULE_NAME}rc.yml`, + `.${MODULE_NAME}rc.js`, + `${MODULE_NAME}.config.js` + ] +}); + +exports.loadSync = (filepath) => { + let loaded; + + if (filepath) { + loaded = explorer.loadSync(filepath); + } else { + loaded = explorer.searchSync() || {}; + } + + return new Config( + loaded.filepath, + _.defaultsDeep({}, loaded.config, defaults) + ); +}; diff --git a/packages/jsdoc-config/package-lock.json b/packages/jsdoc-config/package-lock.json new file mode 100644 index 00000000..8b53bd30 --- /dev/null +++ b/packages/jsdoc-config/package-lock.json @@ -0,0 +1,128 @@ +{ + "name": "@jsdoc/config", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "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.0.7", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.7.tgz", + "integrity": "sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "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.12.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz", + "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==", + "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.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "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": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, + "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=" + } + } +} diff --git a/packages/jsdoc-config/package.json b/packages/jsdoc-config/package.json new file mode 100644 index 00000000..585c1e60 --- /dev/null +++ b/packages/jsdoc-config/package.json @@ -0,0 +1,29 @@ +{ + "name": "@jsdoc/config", + "version": "1.0.0", + "description": "Loads configuration settings 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", + "dependencies": { + "cosmiconfig": "^5.0.7", + "lodash": "^4.17.11", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.1" + }, + "devDependencies": {} +} diff --git a/packages/jsdoc-config/test/specs/defaults.js b/packages/jsdoc-config/test/specs/defaults.js new file mode 100644 index 00000000..ee15f69c --- /dev/null +++ b/packages/jsdoc-config/test/specs/defaults.js @@ -0,0 +1,81 @@ +const defaults = require('../../defaults'); + +describe('@jsdoc/config/defaults', () => { + it('exists', () => { + expect(defaults).toBeObject(); + }); + + describe('plugins', () => { + it('is an array', () => { + expect(defaults.plugins).toBeArray(); + }); + }); + + describe('source', () => { + it('is an object', () => { + expect(defaults.source).toBeObject(); + }); + + describe('excludePattern', () => { + it('is a string', () => { + expect(defaults.source.excludePattern).toBeString(); + }); + + it('represents a valid regexp', () => { + expect(() => new RegExp(defaults.source.excludePattern)).not.toThrow(); + }); + }); + + describe('includePattern', () => { + it('is a string', () => { + expect(defaults.source.includePattern).toBeString(); + }); + + it('represents a valid regexp', () => { + expect(() => new RegExp(defaults.source.includePattern)).not.toThrow(); + }); + }); + + describe('type', () => { + it('is a string', () => { + expect(defaults.source.type).toBeString(); + }); + }); + + describe('tags', () => { + it('is an object', () => { + expect(defaults.tags).toBeObject(); + }); + + describe('allowUnknownTags', () => { + it('is a boolean', () => { + expect(defaults.tags.allowUnknownTags).toBeBoolean(); + }); + }); + + describe('dictionaries', () => { + it('is an array of strings', () => { + expect(defaults.tags.dictionaries).toBeArrayOfStrings(); + }); + }); + }); + + describe('templates', () => { + it('is an object', () => { + expect(defaults.templates).toBeObject(); + }); + + describe('cleverLinks', () => { + it('is a boolean', () => { + expect(defaults.templates.cleverLinks).toBeBoolean(); + }); + }); + + describe('monospaceLinks', () => { + it('is a boolean', () => { + expect(defaults.templates.monospaceLinks).toBeBoolean(); + }); + }); + }); + }); +}); diff --git a/packages/jsdoc-config/test/specs/index.js b/packages/jsdoc-config/test/specs/index.js new file mode 100644 index 00000000..f8ada3b8 --- /dev/null +++ b/packages/jsdoc-config/test/specs/index.js @@ -0,0 +1,105 @@ +describe('@jsdoc/config', () => { + const mockFs = require('mock-fs'); + const config = require('../../index'); + const defaults = require('../../defaults'); + + afterEach(() => mockFs.restore()); + + it('exists', () => { + expect(config).toBeObject(); + }); + + it('has a loadSync method', () => { + expect(config.loadSync).toBeFunction(); + }); + + describe('loadSync', () => { + it('returns an object with `config` and `filepath` properties', () => { + mockFs({ + 'conf.json': '{}' + }); + + const conf = config.loadSync('conf.json'); + + expect(conf.config).toBeObject(); + expect(conf.filepath).toEndWith('conf.json'); + }); + + it('loads settings from the specified filepath if there is one', () => { + mockFs({ + 'conf.json': '{"foo":"bar"}' + }); + + const conf = config.loadSync('conf.json'); + + expect(conf.config.foo).toBe('bar'); + }); + + it('finds the config file when no filepath is specified', () => { + mockFs({ + 'package.json': '{"jsdoc":{"foo":"bar"}}' + }); + + const conf = config.loadSync(); + + expect(conf.config.foo).toBe('bar'); + }); + + it('parses JSON config files that have an extension and contain comments', () => { + mockFs({ + '.jsdocrc.json': '// comment\n{"foo":"bar"}' + }); + + const conf = config.loadSync(); + + expect(conf.config.foo).toBe('bar'); + }); + + it('parses JSON files that start with a BOM', () => { + mockFs({ + '.jsdocrc.json': '\uFEFF{"foo":"bar"}' + }); + + const conf = config.loadSync(); + + expect(conf.config.foo).toBe('bar'); + }); + + it('parses YAML files that start with a BOM', () => { + mockFs({ + '.jsdocrc.yaml': '\uFEFF{"foo":"bar"}' + }); + + const conf = config.loadSync(); + + expect(conf.config.foo).toBe('bar'); + }); + + it('provides the default config if the user config is an empty object', () => { + mockFs({ + '.jsdocrc.json': '{}' + }); + + const conf = config.loadSync(); + + expect(conf.config).toEqual(defaults); + }); + + it('provides the default config if there is no user config', () => { + const conf = config.loadSync(); + + expect(conf.config).toEqual(defaults); + }); + + it('merges nested defaults with nested user settings as expected', () => { + mockFs({ + '.jsdocrc.json': '{"tags":{"foo":"bar"}}' + }); + + const conf = config.loadSync(); + + expect(conf.config.tags.allowUnknownTags).toBe(defaults.tags.allowUnknownTags); + expect(conf.config.tags.foo).toBe('bar'); + }); + }); +}); diff --git a/test/index.js b/test/index.js index 27878d7b..7870ad74 100644 --- a/test/index.js +++ b/test/index.js @@ -23,10 +23,13 @@ const SPEC_FILES = (() => { module.exports = () => { const jasmine = new Jasmine(); const matcher = env.opts.matcher; + /* eslint-disable no-empty-function */ const promise = new Promise(() => {}); + /* eslint-enable no-empty-function */ jasmine.loadConfig({ helpers: [ + 'node_modules/jasmine-expect/index.js', 'test/helpers/**/*.js' ], random: false diff --git a/test/specs/jsdoc/config.js b/test/specs/jsdoc/config.js deleted file mode 100644 index 279b7dee..00000000 --- a/test/specs/jsdoc/config.js +++ /dev/null @@ -1,92 +0,0 @@ -describe('jsdoc/config', () => { - const Config = require('jsdoc/config'); - - it('should exist', () => { - expect(Config).toBeDefined(); - expect(typeof Config).toBe('function'); - }); - - it('should provide a "get" instance function', () => { - const conf = new Config(); - - expect(conf.get).toBeDefined(); - expect(typeof conf.get).toBe('function'); - }); - - describe('constructor with empty', () => { - it('should be possible to construct a Config with an empty arguments', () => { - const conf = new Config().get(); - - expect(Array.isArray(conf.plugins)).toBe(true); - expect(conf.plugins.length).toBe(0); - }); - }); - - describe('constructor with {}', () => { - it('should be possible to construct a Config with JSON of an object literal that is empty', () => { - const conf = new Config('{}').get(); - - expect(Array.isArray(conf.plugins)).toBe(true); - expect(conf.plugins.length).toBe(0); - }); - - it('should be possible to construct a Config with an empty JavaScript object', () => { - const conf = new Config({}).get(); - - expect(Array.isArray(conf.plugins)).toBe(true); - expect(conf.plugins.length).toBe(0); - }); - }); - - describe('constructor with leading BOM', () => { - it('should be possible to construct a Config with JSON that has a leading BOM', () => { - function getConfig() { - return new Config('\uFEFF{}').get(); - } - - expect(getConfig).not.toThrow(); - }); - }); - - describe('constructor with comments', () => { - it('should be possible to construct a Config with JSON that includes comments', () => { - function getConfig() { - return new Config('{\n// comment\n}').get(); - } - - expect(getConfig).not.toThrow(); - }); - }); - - describe('constructor with plugins value', () => { - it('should be possible to construct a Config with JSON of an object literal that has a plugin value', () => { - const conf = new Config('{"plugins":[42]}').get(); - - expect(Array.isArray(conf.plugins)).toBe(true); - expect(conf.plugins.length).toBe(1); - expect(conf.plugins[0]).toBe(42); - }); - - it('should be possible to construct a Config with a JavaScript object that has a plugin value', () => { - const conf = new Config({'plugins': [42]}).get(); - - expect(Array.isArray(conf.plugins)).toBe(true); - expect(conf.plugins.length).toBe(1); - expect(conf.plugins[0]).toBe(42); - }); - }); - - describe('constructor with source value', () => { - it('should be possible to construct a Config with JSON of an object literal that has a source value', () => { - const conf = new Config('{"source":{"includePattern":"hello"}}').get(); - - expect(conf.source.includePattern).toBe('hello'); - }); - - it('should be possible to construct a Config with a JavaScript object that has a source value', () => { - const conf = new Config({source: {includePattern: 'hello'}}).get(); - - expect(conf.source.includePattern).toBe('hello'); - }); - }); -});