diff --git a/.eslintignore b/.eslintignore index ee93496..b413a02 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,5 @@ **/node_modules build +**/coverage/ integrationTests/__fixtures__/skipped/__eslint__/file.js \ No newline at end of file diff --git a/README.md b/README.md index dc27133..e66b3bd 100644 --- a/README.md +++ b/README.md @@ -44,3 +44,55 @@ module.exports = { ```bash yarn jest ``` + + +## Options + +This project uses [cosmiconfig](https://github.com/davidtheclark/cosmiconfig), so you can provide config via: +* a `jest-runner-eslint` property in your `package.json` +* a `jest-runner-eslint.config.js` JS file +* a `.jest-runner-eslintrc` JSON file + + +In `package.json` +```json +{ + "jest-runner-eslint": { + "cliOptions": { + // Options here + } + } +} +``` + +or in `jest-runner-eslint.config.js` +```js +module.exports = { + cliOptions: { + // Options here + } +} +``` + + +### cliOptions + +jest-runner-eslint maps a lot of ESLint CLI arguments to config options. For example `--fix` is `cliOptions.fix` + +|option|default|example +|-----|-----|-----| +|cacheLocation|`.eslintcache`|`"cacheLocation": "/path/to/cache"` +|config|`null`|`"config": "/path/to/config"` +|env|`null`|`"env": "mocha"` or `"env": ["mocha", "other"]` +|ext|`[".js"]`|`"ext": ".jsx"` or `"ext": [".jsx", ".ts"]` +|fix|`false`|`"fix": true` +|global|`[]`|`"global": "it"` or `"global": ["it", "describe"]` +|ignorePath|`null`|`"ignorePath": "/path/to/ignore"` +|noEslintrc|`false`|`"noEslintrc": true` +|noIgnore|`false`|`"noIgnore": true` +|noInlineConfig|`false`|`"noInlineConfig": true` +|parser|`espree`|`"parser": "flow"` +|parserOptions|`{}`|`"parserOptions": { "myOption": true }` +|plugin|`[]`|`"plugin": "prettier"` or `"plugin": ["pettier", "other"]` +|rule|`null`|`"rule": "'quotes: [2, double]'"` or `"rule": ["quotes: [2, double]", "no-console: 2"]` +|rulesdir|`[]`|`"rulesdir": "/path/to/rules/dir"` or `"env": ["/path/to/rules/dir", "/path/to/other"]` diff --git a/integrationTests/__fixtures__/passing/__eslint__/file.js b/integrationTests/__fixtures__/passing/__eslint__/file.js index 937eb2f..8782c1b 100644 --- a/integrationTests/__fixtures__/passing/__eslint__/file.js +++ b/integrationTests/__fixtures__/passing/__eslint__/file.js @@ -1,4 +1,3 @@ -/* global hello */ const a = 1; if (a === 2) { diff --git a/integrationTests/__fixtures__/passing/jest-runner-eslint.config.js b/integrationTests/__fixtures__/passing/jest-runner-eslint.config.js new file mode 100644 index 0000000..8c71237 --- /dev/null +++ b/integrationTests/__fixtures__/passing/jest-runner-eslint.config.js @@ -0,0 +1,5 @@ +module.exports = { + cliOptions: { + global: ['hello'], + }, +}; diff --git a/package.json b/package.json index 70d93a6..cf14c53 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "format": "prettier --single-quote --trailing-comma all --write \"!(build)/**/*.js\"" }, "dependencies": { + "cosmiconfig": "^3.0.1", "eslint": "4.5.0", "find-up": "^2.1.0", "pify": "3.0.0", diff --git a/src/runESLint.js b/src/runESLint.js index 86635d5..8a589dc 100644 --- a/src/runESLint.js +++ b/src/runESLint.js @@ -1,4 +1,5 @@ const getLocalESLint = require('./utils/getLocalESLint'); +const getESLintOptions = require('./utils/getESLintOptions'); const toTestResult = require('./utils/toTestResult'); const skip = ({ start, end, testPath }) => @@ -62,12 +63,18 @@ const runESLint = ({ testPath, config }, workerCallback) => { try { const start = +new Date(); const { CLIEngine } = getLocalESLint(config); - const cli = new CLIEngine({}); + const options = getESLintOptions(config); + const cli = new CLIEngine(options.cliOptions); if (cli.isPathIgnored(testPath)) { const end = +new Date(); workerCallback(null, skip({ start, end, testPath })); } else { const report = cli.executeOnFiles([testPath]); + + if (options.cliOptions && options.cliOptions.fix) { + CLIEngine.outputFixes(report); + } + const end = +new Date(); if (report.errorCount > 0) { diff --git a/src/utils/__tests__/normalizeConfig.test.js b/src/utils/__tests__/normalizeConfig.test.js new file mode 100644 index 0000000..75e93d1 --- /dev/null +++ b/src/utils/__tests__/normalizeConfig.test.js @@ -0,0 +1,194 @@ +const normalizeConfig = require('../normalizeConfig'); + +const normalizeCLIOptions = cliOptions => + normalizeConfig({ cliOptions }).cliOptions; + +it('ignores unkown options', () => { + expect(normalizeCLIOptions({ other: true })).not.toMatchObject({ + other: true, + }); +}); + +it('normalizes noInlineConfig', () => { + expect(normalizeCLIOptions({})).toMatchObject({ + allowInlineConfig: true, + }); + + expect(normalizeCLIOptions({ noInlineConfig: true })).toMatchObject({ + allowInlineConfig: false, + }); + + expect(normalizeCLIOptions({ noInlineConfig: false })).toMatchObject({ + allowInlineConfig: true, + }); +}); + +it('normalizes cacheLocation', () => { + expect(normalizeCLIOptions({})).toMatchObject({ + cacheLocation: '.eslintcache', + }); + + expect( + normalizeCLIOptions({ cacheLocation: '/path/to/cache' }), + ).toMatchObject({ + cacheLocation: '/path/to/cache', + }); +}); + +it('normalizes config', () => { + expect(normalizeCLIOptions({})).toMatchObject({ + configFile: null, + }); + + expect(normalizeCLIOptions({ config: '/path/to/config' })).toMatchObject({ + configFile: '/path/to/config', + }); +}); + +it('normalizes env', () => { + expect(normalizeCLIOptions({})).toMatchObject({ + envs: [], + }); + + expect(normalizeCLIOptions({ env: 'mocha' })).toMatchObject({ + envs: ['mocha'], + }); + + expect(normalizeCLIOptions({ env: ['mocha', 'browser'] })).toMatchObject({ + envs: ['mocha', 'browser'], + }); +}); + +it('normalizes ext', () => { + expect(normalizeCLIOptions({})).toMatchObject({ + extensions: ['.js'], + }); + + expect(normalizeCLIOptions({ ext: '.ts' })).toMatchObject({ + extensions: ['.ts'], + }); + + expect(normalizeCLIOptions({ ext: ['.js', '.jsx', '.ts'] })).toMatchObject({ + extensions: ['.js', '.jsx', '.ts'], + }); +}); + +it('normalizes fix', () => { + expect(normalizeCLIOptions({})).toMatchObject({ + fix: false, + }); + + expect(normalizeCLIOptions({ fix: true })).toMatchObject({ + fix: true, + }); +}); + +it('normalizes global', () => { + expect(normalizeCLIOptions({})).toMatchObject({ + globals: [], + }); + + expect(normalizeCLIOptions({ global: 'it' })).toMatchObject({ + globals: ['it'], + }); + + expect(normalizeCLIOptions({ global: ['it', 'describe'] })).toMatchObject({ + globals: ['it', 'describe'], + }); +}); + +it('normalizes noIgnore', () => { + expect(normalizeCLIOptions({})).toMatchObject({ + ignore: true, + }); + + expect(normalizeCLIOptions({ noIgnore: true })).toMatchObject({ + ignore: false, + }); +}); + +it('normalizes ignorePath', () => { + expect(normalizeCLIOptions({})).toMatchObject({ + ignorePath: null, + }); + + expect(normalizeCLIOptions({ ignorePath: '/path/to/ignore' })).toMatchObject({ + ignorePath: '/path/to/ignore', + }); +}); + +it('normalizes parser', () => { + expect(normalizeCLIOptions({})).toMatchObject({ + parser: 'espree', + }); + + expect(normalizeCLIOptions({ parser: 'flow' })).toMatchObject({ + parser: 'flow', + }); +}); + +it('normalizes parserOptions', () => { + expect(normalizeCLIOptions({})).toMatchObject({ + parserOptions: {}, + }); + + expect( + normalizeCLIOptions({ parserOptions: { ecmaVersion: 2015 } }), + ).toMatchObject({ + parserOptions: { ecmaVersion: 2015 }, + }); +}); + +it('normalizes plugin', () => { + expect(normalizeCLIOptions({})).toMatchObject({ + plugins: [], + }); + + expect(normalizeCLIOptions({ plugin: 'prettier' })).toMatchObject({ + plugins: ['prettier'], + }); + + expect(normalizeCLIOptions({ plugin: ['prettier'] })).toMatchObject({ + plugins: ['prettier'], + }); +}); + +it('normalizes rulesdir', () => { + expect(normalizeCLIOptions({})).toMatchObject({ + rulePaths: [], + }); + + expect(normalizeCLIOptions({ rulesdir: '/path/to/rules' })).toMatchObject({ + rulePaths: ['/path/to/rules'], + }); + + expect( + normalizeCLIOptions({ rulesdir: ['/path/to/rules', '/other/path'] }), + ).toMatchObject({ + rulePaths: ['/path/to/rules', '/other/path'], + }); +}); + +it('normalizes rule', () => { + expect(normalizeCLIOptions({})).toMatchObject({ + rules: null, + }); + + expect(normalizeCLIOptions({ rule: ['quotes: [2, double]'] })).toMatchObject({ + rules: ['quotes: [2, double]'], + }); + + expect(normalizeCLIOptions({ rule: 'quotes: [2, double]' })).toMatchObject({ + rules: ['quotes: [2, double]'], + }); +}); + +it('normalizes noEslintrc', () => { + expect(normalizeCLIOptions({})).toMatchObject({ + useEslintrc: true, + }); + + expect(normalizeCLIOptions({ noEslintrc: true })).toMatchObject({ + useEslintrc: false, + }); +}); diff --git a/src/utils/getESLintOptions.js b/src/utils/getESLintOptions.js new file mode 100644 index 0000000..92dc4e7 --- /dev/null +++ b/src/utils/getESLintOptions.js @@ -0,0 +1,16 @@ +const normalizeConfig = require('./normalizeConfig'); +const cosmiconfig = require('cosmiconfig'); + +const explorer = cosmiconfig('jest-runner-eslint', { sync: true }); + +const getESLintOptions = config => { + const result = explorer.load(config.rootDir); + + if (result) { + return normalizeConfig(result.config); + } + + return normalizeConfig({}); +}; + +module.exports = getESLintOptions; diff --git a/src/utils/normalizeConfig.js b/src/utils/normalizeConfig.js new file mode 100644 index 0000000..31b89c0 --- /dev/null +++ b/src/utils/normalizeConfig.js @@ -0,0 +1,95 @@ +const identity = v => v; +const negate = v => !v; +const asArray = v => (typeof v === 'string' ? [v] : v); + +const BASE_CONFIG = { + cacheLocation: { + default: '.eslintcache', + }, + config: { + name: 'configFile', + default: null, + }, + env: { + name: 'envs', + default: [], + transform: asArray, + }, + ext: { + name: 'extensions', + default: ['.js'], + transform: asArray, + }, + fix: { + default: false, + }, + global: { + name: 'globals', + default: [], + transform: asArray, + }, + ignorePath: { + default: null, + }, + noEslintrc: { + name: 'useEslintrc', + default: false, + transform: negate, + }, + noIgnore: { + name: 'ignore', + default: false, + transform: negate, + }, + noInlineConfig: { + name: 'allowInlineConfig', + default: false, + transform: negate, + }, + parser: { + default: 'espree', + }, + parserOptions: { + default: {}, + }, + plugin: { + name: 'plugins', + default: [], + transform: asArray, + }, + rule: { + name: 'rules', + default: null, + transform: asArray, + }, + rulesdir: { + name: 'rulePaths', + default: [], + transform: asArray, + }, +}; + +/* eslint-disable no-param-reassign */ +const normalizeCliOptions = rawConfig => + Object.keys(BASE_CONFIG).reduce((config, key) => { + const { + name = key, + transform = identity, + default: defaultValue, + } = BASE_CONFIG[key]; + + const value = rawConfig[key] !== undefined ? rawConfig[key] : defaultValue; + + return Object.assign({}, config, { + [name]: transform(value), + }); + }, {}); +/* eslint-enable no-param-reassign */ + +const normalizeConfig = config => { + return Object.assign({}, config, { + cliOptions: normalizeCliOptions(config.cliOptions || {}), + }); +}; + +module.exports = normalizeConfig; diff --git a/yarn.lock b/yarn.lock index 47e9455..dd910b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -644,7 +644,7 @@ babel-preset-jest@^21.0.0: dependencies: babel-plugin-jest-hoist "^21.0.0" -babel-register@6.26.0, babel-register@^6.26.0: +babel-register@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" dependencies: @@ -932,6 +932,15 @@ core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" +cosmiconfig@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-3.0.1.tgz#d290e2b657a7f3a335257a0d306587836650fdc0" + dependencies: + is-directory "^0.3.1" + js-yaml "^3.9.0" + parse-json "^3.0.0" + require-from-string "^2.0.1" + cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -1046,7 +1055,7 @@ errno@^0.1.4: dependencies: prr "~0.0.0" -error-ex@^1.2.0: +error-ex@^1.2.0, error-ex@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" dependencies: @@ -1658,6 +1667,10 @@ is-ci@^1.0.10: dependencies: ci-info "^1.0.0" +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + is-dotfile@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" @@ -2070,7 +2083,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -js-yaml@^3.7.0, js-yaml@^3.9.1: +js-yaml@^3.7.0, js-yaml@^3.9.0, js-yaml@^3.9.1: version "3.9.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.1.tgz#08775cebdfdd359209f0d2acd383c8f86a6904a0" dependencies: @@ -2215,7 +2228,7 @@ lodash.cond@^4.3.0: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" -lodash@4.17.4, lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.3.0: +lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -2278,7 +2291,7 @@ mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" -minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: +minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -2493,6 +2506,12 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" +parse-json@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-3.0.0.tgz#fa6f47b18e23826ead32f263e744d0e1e847fb13" + dependencies: + error-ex "^1.3.1" + parse5@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" @@ -2777,6 +2796,10 @@ require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" +require-from-string@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.1.tgz#c545233e9d7da6616e9d59adfb39fc9f588676ff" + require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"