├── .eslintignore ├── .eslintrc.json ├── .github ├── renovate.json └── workflows │ └── nodejs.yml ├── .gitignore ├── .prettierignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── babel.config.js ├── examples └── simple │ ├── .eslintignore │ ├── package.json │ └── src │ ├── file1.js │ ├── file2.js │ ├── file3.js │ └── file4.js ├── integrationTests ├── __fixtures__ │ ├── custom-parser │ │ ├── .eslintrc.json │ │ ├── __eslint__ │ │ │ └── file.js │ │ └── jest.config.js │ ├── failing │ │ ├── __eslint__ │ │ │ └── file.js │ │ └── jest.config.js │ ├── flat-config │ │ ├── __eslint__ │ │ │ └── file.js │ │ ├── eslint.config.js │ │ └── jest.config.js │ ├── format │ │ ├── __eslint__ │ │ │ └── file.js │ │ ├── jest-runner-eslint.config.js │ │ └── jest.config.js │ ├── github-actions │ │ ├── .eslintignore │ │ ├── __eslint__ │ │ │ ├── failing.js │ │ │ ├── passing.js │ │ │ └── skipped.js │ │ └── jest.config.js │ ├── legacy-config-at-eslint.config.js │ │ ├── __eslint__ │ │ │ └── file.js │ │ ├── eslint.config.js │ │ ├── jest-runner-eslint.config.js │ │ └── jest.config.js │ ├── loud │ │ ├── .eslintrc.json │ │ ├── __eslint__ │ │ │ └── file.js │ │ ├── jest-runner-eslint.config.js │ │ └── jest.config.js │ ├── max-warnings │ │ ├── .eslintrc.json │ │ ├── __eslint__ │ │ │ └── file.js │ │ ├── jest-runner-eslint.config.js │ │ └── jest.config.js │ ├── passing │ │ ├── __eslint__ │ │ │ └── file.js │ │ ├── jest-runner-eslint.config.js │ │ └── jest.config.js │ ├── quiet-mode │ │ ├── .eslintrc.json │ │ ├── __eslint__ │ │ │ └── file.js │ │ ├── jest-runner-eslint.config.js │ │ └── jest.config.js │ └── skipped │ │ ├── .eslintignore │ │ ├── __eslint__ │ │ └── file.js │ │ └── jest.config.js ├── __snapshots__ │ ├── custom-parser.test.js.snap │ ├── failing.test.js.snap │ ├── flat-config.test.js.snap │ ├── format.test.js.snap │ ├── github-actions.test.js.snap │ ├── legacy-config-at-eslint.config.js.test.js.snap │ ├── loud.test.js.snap │ ├── max-warnings.test.js.snap │ ├── passing.test.js.snap │ ├── quiet-mode.test.js.snap │ └── skipped.test.js.snap ├── custom-parser.test.js ├── failing.test.js ├── flat-config.test.js ├── format.test.js ├── github-actions.test.js ├── legacy-config-at-eslint.config.js.test.js ├── loud.test.js ├── max-warnings.test.js ├── passing.test.js ├── quiet-mode.test.js ├── runJest.js └── skipped.test.js ├── jest.config.js ├── package.json ├── src ├── runner │ ├── __tests__ │ │ └── runESLint.test.js │ ├── index.js │ └── runESLint.js ├── utils │ ├── __tests__ │ │ └── normalizeConfig.test.js │ ├── configOverrides.js │ ├── getESLintOptions.js │ └── normalizeConfig.js └── watchFixPlugin │ ├── __tests__ │ └── index.test.js │ └── index.js ├── watch-fix.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | **/coverage/ 3 | **/node_modules/** 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb-base", "plugin:prettier/recommended"], 3 | "plugins": ["jest"], 4 | "parserOptions": { 5 | "ecmaVersion": 11 6 | }, 7 | "rules": { 8 | "no-underscore-dangle": 0 9 | }, 10 | "env": { 11 | "jest/globals": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "lockFileMaintenance": { "enabled": true, "automerge": true }, 4 | "rangeStrategy": "replace", 5 | "postUpdateOptions": ["yarnDedupeHighest"] 6 | } 7 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Unit tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - next 8 | pull_request: 9 | branches: 10 | - main 11 | - next 12 | 13 | jobs: 14 | test-node: 15 | name: 16 | # prettier-ignore 17 | Test on Node.js v${{ matrix.node-version }}, eslint v${{ matrix.eslint-version }}, jest v${{ matrix.jest-version }}, and jest-watch-typeahead v${{ matrix.jest-watch-typeahead-version }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | node-version: [12.x, 14.x, 16.x, 18.x, 19.x, 20.x] 22 | eslint-version: [7, "8.40", "8.55", 8] 23 | jest-version: [27, 28, 29] 24 | jest-watch-typeahead-version: [1, 2] 25 | exclude: 26 | # jest@29 doesn't support node@12 27 | - node-version: 12.x 28 | jest-version: 29 29 | # jest-watch-typeahead@2 doesn't support node@12 30 | - node-version: 12 31 | jest-watch-typeahead-version: 2 32 | # Modern timers need Jest 29.2.1 on Node 19+: https://github.com/facebook/jest/pull/13467 33 | - node-version: 18.x 34 | jest-version: 28 35 | - node-version: 19.x 36 | jest-version: 28 37 | - node-version: 20.x 38 | jest-version: 28 39 | runs-on: ubuntu-latest 40 | 41 | steps: 42 | - uses: actions/checkout@v4 43 | - name: Use Node.js ${{ matrix.node-version }} 44 | uses: actions/setup-node@v4 45 | with: 46 | node-version: ${{ matrix.node-version }} 47 | cache: yarn 48 | - name: install with eslint v${{ matrix.eslint-version }}, jest@${{ matrix.jest-version }}, and jest-watch-typeahead@${{ matrix.jest-watch-typeahead-version }} 49 | run: yarn add --dev eslint@${{ matrix.eslint-version }} jest@${{ matrix.jest-version }} babel-jest@${{ matrix.jest-version }} jest-watch-typeahead@${{ matrix.jest-watch-typeahead-version }} --ignore-engines 50 | - name: run tests 51 | run: yarn test 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | projects 3 | yarn-error.log 4 | build 5 | coverage 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.rulers": [80], 3 | "files.exclude": { 4 | "**/.git": true, 5 | "**/node_modules": true, 6 | "**/build": true 7 | }, 8 | "editor.formatOnSave": true, 9 | "prettier.eslintIntegration": true 10 | } 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## main 2 | 3 | ## 0.7.0 4 | 5 | ### Features 6 | 7 | - Watch plugin for toggling --fix ([#53](https://github.com/jest-community/jest-runner-eslint/pull/53)) 8 | - Add more options ([#45](https://github.com/jest-community/jest-runner-eslint/pull/45)) 9 | 10 | ### Other 11 | 12 | - Move Jest and ESLint to peerDependencies ([#55](https://github.com/jest-community/jest-runner-eslint/pull/55)) 13 | - Upgrade all dependencies and remove transpilation step ([#49](https://github.com/jest-community/jest-runner-eslint/pull/49)) 14 | - Small clean up in runESLint and improve test setup ([#51](https://github.com/jest-community/jest-runner-eslint/pull/51)) 15 | - Upgrade Jest and drop Node 4 ([#48](https://github.com/jest-community/jest-runner-eslint/pull/48)) 16 | 17 | ## 0.6.0 18 | 19 | ### Fixes 20 | 21 | - cli options rules should be passed as an object instead of array of strings ([#39](https://github.com/jest-community/jest-runner-eslint/pull/39)) 22 | 23 | ## 0.5.0 24 | 25 | ### Other 26 | 27 | - refactor: use create-jest-runner ([#37](https://github.com/jest-community/jest-runner-eslint/pull/37)) 28 | - Document usage with other Jest runners ([#34](https://github.com/jest-community/jest-runner-eslint/pull/34)) 29 | 30 | ## 0.4.0 31 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Setup up your dev environment 2 | 3 | 1. Fork the repo and create your branch from `main`. A guide on how to fork a repository: https://help.github.com/articles/fork-a-repo/ 4 | 5 | Open terminal (e.g. Terminal, iTerm, Git Bash or Git Shell) and type: 6 | 7 | ```sh 8 | git clone https://github.com//jest-runner-eslint 9 | cd jest 10 | git checkout -b my_branch 11 | ``` 12 | 13 | Note: Replace `` with your GitHub username 14 | 15 | 2. jest-runner-eslint uses [Yarn](https://code.facebook.com/posts/1840075619545360) for running development scripts. If you haven't already done so, please [install yarn](https://yarnpkg.com/en/docs/install). 16 | 17 | 3. Run `yarn`. 18 | 19 | ```sh 20 | yarn 21 | ``` 22 | 23 | 4. If you've changed APIs, update the documentation. 24 | 25 | 5. Ensure the test suite passes via `yarn test`. To run the test suite you 26 | 27 | ```sh 28 | yarn test 29 | ``` 30 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG NODE_VERSION=20 2 | FROM node:${NODE_VERSION} 3 | 4 | WORKDIR /app 5 | 6 | COPY package.json yarn.lock /app/ 7 | 8 | RUN yarn --ignore-scripts 9 | 10 | COPY . . 11 | 12 | RUN yarn build 13 | 14 | CMD ["yarn", "test"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Actions Status](https://github.com/jest-community/jest-runner-eslint/actions/workflows/nodejs.yml/badge.svg?branch=main)](https://github.com/jest-community/jest-runner-eslint/actions) [![npm version](https://badge.fury.io/js/jest-runner-eslint.svg)](https://badge.fury.io/js/jest-runner-eslint) 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 |

jest-runner-eslint

10 |

ESLint runner for Jest

11 |
12 | 13 |
14 | 15 |
16 | 17 | ## Usage 18 | 19 | ### Install 20 | 21 | Install `jest`_(it needs Jest 21+)_ and `jest-runner-eslint` 22 | 23 | ```bash 24 | yarn add --dev jest jest-runner-eslint 25 | 26 | # or with NPM 27 | 28 | npm install --save-dev jest jest-runner-eslint 29 | ``` 30 | 31 | ### Add it to your Jest config 32 | 33 | #### Standalone 34 | 35 | In your `package.json` 36 | 37 | ```json 38 | { 39 | "jest": { 40 | "runner": "jest-runner-eslint", 41 | "displayName": "lint", 42 | "testMatch": ["/src/**/*.js"] 43 | } 44 | } 45 | ``` 46 | 47 | Or in `jest.config.js` 48 | 49 | ```js 50 | module.exports = { 51 | runner: 'jest-runner-eslint', 52 | displayName: 'lint', 53 | testMatch: ['/src/**/*.js'], 54 | }; 55 | ``` 56 | 57 | Please update `testMatch` to match your project folder structure 58 | 59 | #### Alongside other runners 60 | 61 | It is recommended to use the [`projects`](https://jestjs.io/docs/configuration#projects-arraystring--projectconfig) configuration option to run multiple Jest runners simultaneously. 62 | 63 | If you are using Jest <22.0.5, you can use multiple Jest configuration files and supply the paths to those files in the `projects` option. For example: 64 | 65 | ```js 66 | // jest-test.config.js 67 | module.exports = { 68 | // your Jest test options 69 | displayName: 'test', 70 | }; 71 | 72 | // jest-eslint.config.js 73 | module.exports = { 74 | // your jest-runner-eslint options 75 | runner: 'jest-runner-eslint', 76 | displayName: 'lint', 77 | testMatch: ['/src/**/*.js'], 78 | }; 79 | ``` 80 | 81 | In your `package.json`: 82 | 83 | ```json 84 | { 85 | "jest": { 86 | "projects": [ 87 | "/jest-test.config.js", 88 | "/jest-eslint.config.js" 89 | ] 90 | } 91 | } 92 | ``` 93 | 94 | Or in `jest.config.js`: 95 | 96 | ```js 97 | module.exports = { 98 | projects: [ 99 | '/jest-test.config.js', 100 | '/jest-eslint.config.js', 101 | ], 102 | }; 103 | ``` 104 | 105 | If you are using Jest >=22.0.5, you can supply an array of project configuration objects instead. In your `package.json`: 106 | 107 | ```json 108 | { 109 | "jest": { 110 | "projects": [ 111 | { 112 | "displayName": "test" 113 | }, 114 | { 115 | "runner": "jest-runner-eslint", 116 | "displayName": "lint", 117 | "testMatch": ["/src/**/*.js"] 118 | } 119 | ] 120 | } 121 | } 122 | ``` 123 | 124 | Or in `jest.config.js`: 125 | 126 | ```js 127 | module.exports = { 128 | projects: [ 129 | { 130 | displayName: 'test', 131 | }, 132 | { 133 | runner: 'jest-runner-eslint', 134 | displayName: 'lint', 135 | testMatch: ['/src/**/*.js'], 136 | }, 137 | ], 138 | }; 139 | ``` 140 | 141 | ### Run Jest 142 | 143 | ```bash 144 | yarn jest 145 | ``` 146 | 147 | ## Toggle `--fix` in watch mode 148 | 149 | `jest-runner-eslint` comes with a watch plugin that allows you to toggle the `--fix` value while in watch mode without having to update your configuration. 150 | 151 | ![fix](https://user-images.githubusercontent.com/574806/46181271-93205080-c279-11e8-8d73-b4c5e11086c4.gif) 152 | 153 | To use this watch plugin simply add this to your Jest configuration. 154 | 155 | ```js 156 | { 157 | watchPlugins: ['jest-runner-eslint/watch-fix'], 158 | } 159 | ``` 160 | 161 | After this run Jest in watch mode and you will see the following line in your watch usage menu 162 | 163 | ``` 164 | › Press F to override ESLint --fix. 165 | ``` 166 | 167 | ## Options 168 | 169 | This project uses [cosmiconfig](https://github.com/davidtheclark/cosmiconfig), so you can provide config via: 170 | 171 | - a `jest-runner-eslint` property in your `package.json` 172 | - a `jest-runner-eslint.config.js` JS file 173 | - a `.jest-runner-eslintrc` JSON file 174 | 175 | In `package.json` 176 | 177 | ```json 178 | { 179 | "jest-runner-eslint": { 180 | "cliOptions": { 181 | // Options here 182 | } 183 | } 184 | } 185 | ``` 186 | 187 | or in `jest-runner-eslint.config.js` 188 | 189 | ```js 190 | module.exports = { 191 | cliOptions: { 192 | // Options here 193 | }, 194 | }; 195 | ``` 196 | 197 | ### cliOptions 198 | 199 | jest-runner-eslint maps a lot of ESLint CLI arguments to config options. For example `--fix` is `cliOptions.fix` 200 | 201 | | option | default | example | 202 | | ----------------------------- | -------------- | --------------------------------------------------------------------------------------------- | 203 | | cache | `false` | `"cache": true` | 204 | | cacheLocation | `.eslintcache` | `"cacheLocation": "/path/to/cache"` | 205 | | config | `null` | `"config": "/path/to/config"` | 206 | | env | `null` | `"env": "mocha"` or `"env": ["mocha", "other"]` | 207 | | ext | `[".js"]` | `"ext": ".jsx"` or `"ext": [".jsx", ".ts"]` | 208 | | fix | `false` | `"fix": true` | 209 | | fixDryRun | `false` | `"fixDryRun": true` | 210 | | format | `null` | `"format": "codeframe"` | 211 | | global | `[]` | `"global": "it"` or `"global": ["it", "describe"]` | 212 | | ignorePath | `null` | `"ignorePath": "/path/to/ignore"` | 213 | | ignorePattern | `[]` | `"ignorePattern": ["/path/to/ignore/*"]` | 214 | | maxWarnings | `-1` | `"maxWarnings": 0` | 215 | | noEslintrc | `false` | `"noEslintrc": true` | 216 | | noIgnore | `false` | `"noIgnore": true` | 217 | | noInlineConfig | `false` | `"noInlineConfig": true` | 218 | | parser | `espree` | `"parser": "flow"` | 219 | | parserOptions | `{}` | `"parserOptions": { "myOption": true }` | 220 | | plugin | `[]` | `"plugin": "prettier"` or `"plugin": ["prettier", "other"]` | 221 | | quiet | `false` | `"quiet": true` | 222 | | resolvePluginsRelativeTo | `undefined` | `"resolvePluginsRelativeTo": "./eslint-config"` | 223 | | reportUnusedDisableDirectives | `false` | `"reportUnusedDisableDirectives": true` | 224 | | rules | `{}` | `"rules": {"quotes": [2, "double"]}` or `"rules": {"quotes": [2, "double"], "no-console": 2}` | 225 | | rulesdir | `[]` | `"rulesdir": "/path/to/rules/dir"` or `"rulesdir": ["/path/to/rules/dir", "/path/to/other"]` | 226 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | const semver = require('semver'); 4 | const pkg = require('./package.json'); 5 | 6 | const supportedNodeVersion = semver.minVersion(pkg.engines.node).version; 7 | 8 | module.exports = { 9 | presets: [['@babel/preset-env', { targets: { node: supportedNodeVersion } }]], 10 | }; 11 | -------------------------------------------------------------------------------- /examples/simple/.eslintignore: -------------------------------------------------------------------------------- 1 | file1.js 2 | -------------------------------------------------------------------------------- /examples/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jest-runner-eslint-example-simple", 3 | "version": "0.0.1", 4 | "description": "A simple example of jest-runner-eslint", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "jest": "^25.1.0", 9 | "jest-runner-eslint": "file:../../" 10 | }, 11 | "jest": { 12 | "runner": "jest-runner-eslint", 13 | "displayName": "lint", 14 | "testMatch": [ 15 | "/src/**/*.js" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/simple/src/file1.js: -------------------------------------------------------------------------------- 1 | function b() {} 2 | const a = 1; 3 | 4 | if (a === 1) { 5 | b(); 6 | hello(); 7 | } 8 | -------------------------------------------------------------------------------- /examples/simple/src/file2.js: -------------------------------------------------------------------------------- 1 | function b() {} 2 | const a = 1; 3 | 4 | if (a === 1) { 5 | b(); 6 | } 7 | -------------------------------------------------------------------------------- /examples/simple/src/file3.js: -------------------------------------------------------------------------------- 1 | function b() {} 2 | const a = 1; 3 | 4 | if (a === 1) { 5 | b(); 6 | } 7 | -------------------------------------------------------------------------------- /examples/simple/src/file4.js: -------------------------------------------------------------------------------- 1 | function b() {} 2 | const a = 1; 3 | 4 | if (a === 1) { 5 | b(); 6 | } 7 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/custom-parser/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "this-parser-does-not-exist" 3 | } 4 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/custom-parser/__eslint__/file.js: -------------------------------------------------------------------------------- 1 | let foo; 2 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/custom-parser/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | runner: '../../../', 3 | testMatch: ['**/__eslint__/**/*.js'], 4 | }; 5 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/failing/__eslint__/file.js: -------------------------------------------------------------------------------- 1 | const a = 1; 2 | 3 | if (a === 2) { 4 | hello(); 5 | bye(); 6 | } 7 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/failing/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | runner: '../../../', 3 | testMatch: ['**/__eslint__/**/*.js'], 4 | }; 5 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/flat-config/__eslint__/file.js: -------------------------------------------------------------------------------- 1 | const a = 1; 2 | 3 | console.log('a', a); 4 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/flat-config/eslint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | rules: { 4 | 'no-console': 'error', 5 | }, 6 | }, 7 | ]; 8 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/flat-config/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | runner: '../../../', 3 | testMatch: ['**/__eslint__/**/*.js'], 4 | }; 5 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/format/__eslint__/file.js: -------------------------------------------------------------------------------- 1 | const a = 1; 2 | 3 | if (a === 2) { 4 | hello(); 5 | bye(); 6 | } 7 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/format/jest-runner-eslint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cliOptions: { 3 | format: 'codeframe', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/format/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | runner: '../../../', 3 | testMatch: ['**/__eslint__/**/*.js'], 4 | }; 5 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/github-actions/.eslintignore: -------------------------------------------------------------------------------- 1 | __eslint__/skipped.js 2 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/github-actions/__eslint__/failing.js: -------------------------------------------------------------------------------- 1 | const a = 1; 2 | 3 | if (a === 2) { 4 | hello(); 5 | bye(); 6 | } 7 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/github-actions/__eslint__/passing.js: -------------------------------------------------------------------------------- 1 | const hello = () => { 2 | /* no-op */ 3 | }; 4 | 5 | const a = 1; 6 | 7 | if (a === 2) { 8 | hello(); 9 | } 10 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/github-actions/__eslint__/skipped.js: -------------------------------------------------------------------------------- 1 | const a = 1; 2 | 3 | if (a === 2) { 4 | hello(); 5 | bye(); 6 | } 7 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/github-actions/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | runner: '../../../', 3 | testMatch: ['**/__eslint__/**/*.js'], 4 | reporters: ['github-actions'], 5 | }; 6 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/legacy-config-at-eslint.config.js/__eslint__/file.js: -------------------------------------------------------------------------------- 1 | const a = 1; 2 | 3 | console.log('a', a); 4 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/legacy-config-at-eslint.config.js/eslint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-console': 'error', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/legacy-config-at-eslint.config.js/jest-runner-eslint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | config: './eslint.config.js', 3 | }; 4 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/legacy-config-at-eslint.config.js/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | runner: '../../../', 3 | testMatch: ['**/__eslint__/**/*.js'], 4 | }; 5 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/loud/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "warn" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/loud/__eslint__/file.js: -------------------------------------------------------------------------------- 1 | const a = 1; 2 | 3 | console.log('a', a); 4 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/loud/jest-runner-eslint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cliOptions: { 3 | quiet: false, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/loud/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | runner: '../../../', 3 | testMatch: ['**/__eslint__/**/*.js'], 4 | }; 5 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/max-warnings/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "warn" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/max-warnings/__eslint__/file.js: -------------------------------------------------------------------------------- 1 | const a = 1; 2 | 3 | console.log('a', a); 4 | console.log('a', a); 5 | console.log('a', a); 6 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/max-warnings/jest-runner-eslint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cliOptions: { 3 | maxWarnings: 2, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/max-warnings/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | runner: '../../../', 3 | testMatch: ['**/__eslint__/**/*.js'], 4 | }; 5 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/passing/__eslint__/file.js: -------------------------------------------------------------------------------- 1 | const a = 1; 2 | 3 | if (a === 2) { 4 | hello(); 5 | } 6 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/passing/jest-runner-eslint.config.js: -------------------------------------------------------------------------------- 1 | const { ESLint } = require('eslint'); 2 | 3 | module.exports = { 4 | cliOptions: { 5 | // `ESLint` requires this to be an object, not an array 6 | global: ESLint ? { hello: true } : ['hello'], 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/passing/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | runner: '../../../', 3 | testMatch: ['**/__eslint__/**/*.js'], 4 | }; 5 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/quiet-mode/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "warn" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/quiet-mode/__eslint__/file.js: -------------------------------------------------------------------------------- 1 | const a = 1; 2 | 3 | console.log('a', a); 4 | if (a === 2) { 5 | hello(); 6 | } 7 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/quiet-mode/jest-runner-eslint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cliOptions: { 3 | quiet: true, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/quiet-mode/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | runner: '../../../', 3 | testMatch: ['**/__eslint__/**/*.js'], 4 | }; 5 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/skipped/.eslintignore: -------------------------------------------------------------------------------- 1 | __eslint__/file.js 2 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/skipped/__eslint__/file.js: -------------------------------------------------------------------------------- 1 | const a = 1; 2 | 3 | if (a === 2) { 4 | hello(); 5 | bye(); 6 | } 7 | -------------------------------------------------------------------------------- /integrationTests/__fixtures__/skipped/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | runner: '../../../', 3 | testMatch: ['**/__eslint__/**/*.js'], 4 | }; 5 | -------------------------------------------------------------------------------- /integrationTests/__snapshots__/custom-parser.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Doesn't override parser when not set [ESLint=7] 1`] = ` 4 | "FAIL __eslint__/file.js 5 | ● Test suite failed to run 6 | 7 | Failed to load parser 'this-parser-does-not-exist' declared in '.eslintrc.json': Cannot find module 'this-parser-does-not-exist' 8 | Require stack: 9 | - /mocked-path-to-jest-runner-mocha/integrationTests/__fixtures__/custom-parser/.eslintrc.json 10 | 11 | at Object.resolve (../../../node_modules/@eslint/eslintrc/lib/shared/relative-module-resolver.js:28:50) 12 | at _normalizeObjectConfigDataBody.next () 13 | at _normalizeObjectConfigData.next () 14 | 15 | Test Suites: 1 failed, 1 total 16 | Tests: 0 total 17 | Snapshots: 0 total 18 | Time: 19 | Ran all test suites. 20 | " 21 | `; 22 | 23 | exports[`Doesn't override parser when not set 1`] = ` 24 | "FAIL __eslint__/file.js 25 | ● Test suite failed to run 26 | 27 | Failed to load parser 'this-parser-does-not-exist' declared in '.eslintrc.json': Cannot find module 'this-parser-does-not-exist' 28 | Require stack: 29 | - /mocked-path-to-jest-runner-mocha/integrationTests/__fixtures__/custom-parser/.eslintrc.json 30 | 31 | at Object.resolve (../../../node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2346:46) 32 | at _normalizeObjectConfigDataBody.next () 33 | at _normalizeObjectConfigData.next () 34 | 35 | Test Suites: 1 failed, 1 total 36 | Tests: 0 total 37 | Snapshots: 0 total 38 | Time: 39 | Ran all test suites. 40 | " 41 | `; 42 | -------------------------------------------------------------------------------- /integrationTests/__snapshots__/failing.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Works when it has failing tests 1`] = ` 4 | "FAIL __eslint__/file.js 5 | ✕ no-undef 6 | ✕ no-undef 7 | 8 | 9 | /mocked-path-to-jest-runner-mocha/integrationTests/__fixtures__/failing/__eslint__/file.js 10 | 4:3 error 'hello' is not defined no-undef 11 | 5:3 error 'bye' is not defined no-undef 12 | 13 | ✖ 2 problems (2 errors, 0 warnings) 14 | 15 | Test Suites: 1 failed, 1 total 16 | Tests: 2 failed, 2 total 17 | Snapshots: 0 total 18 | Time: 19 | Ran all test suites. 20 | " 21 | `; 22 | -------------------------------------------------------------------------------- /integrationTests/__snapshots__/flat-config.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Works with the new flat config format 1`] = ` 4 | "FAIL __eslint__/file.js 5 | ✕ no-console 6 | 7 | 8 | /mocked-path-to-jest-runner-mocha/integrationTests/__fixtures__/flat-config/__eslint__/file.js 9 | 3:1 error Unexpected console statement no-console 10 | 11 | ✖ 1 problem (1 error, 0 warnings) 12 | 13 | Test Suites: 1 failed, 1 total 14 | Tests: 1 failed, 1 total 15 | Snapshots: 0 total 16 | Time: 17 | Ran all test suites. 18 | " 19 | `; 20 | -------------------------------------------------------------------------------- /integrationTests/__snapshots__/format.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Applies custom formatter 1`] = ` 4 | "FAIL __eslint__/file.js 5 | ✕ no-undef 6 | ✕ no-undef 7 | 8 | error: 'hello' is not defined (no-undef) at __eslint__/file.js:4:3: 9 | 2 | 10 | 3 | if (a === 2) { 11 | > 4 | hello(); 12 | | ^ 13 | 5 | bye(); 14 | 6 | } 15 | 7 | 16 | 17 | 18 | error: 'bye' is not defined (no-undef) at __eslint__/file.js:5:3: 19 | 3 | if (a === 2) { 20 | 4 | hello(); 21 | > 5 | bye(); 22 | | ^ 23 | 6 | } 24 | 7 | 25 | 26 | 27 | 2 errors found. 28 | Test Suites: 1 failed, 1 total 29 | Tests: 2 failed, 2 total 30 | Snapshots: 0 total 31 | Time: 32 | Ran all test suites. 33 | " 34 | `; 35 | -------------------------------------------------------------------------------- /integrationTests/__snapshots__/github-actions.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Reports with the github actions reporter 1`] = ` 4 | " 5 | ::error file=/mocked-path-to-jest-runner-mocha/integrationTests/__fixtures__/github-actions/__eslint__/failing.js,line=4,title=no-undef::'hello' is not defined.%0A%0A at __eslint__/failing.js:4:3 6 | 7 | ::error file=/mocked-path-to-jest-runner-mocha/integrationTests/__fixtures__/github-actions/__eslint__/failing.js,line=5,title=no-undef::'bye' is not defined.%0A%0A at __eslint__/failing.js:5:3 8 | " 9 | `; 10 | -------------------------------------------------------------------------------- /integrationTests/__snapshots__/legacy-config-at-eslint.config.js.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Does not try to use flat config on eslint versions that don't support it 1`] = ` 4 | "PASS __eslint__/file.js 5 | ● Console 6 | 7 | console.warn 8 | 9 | /mocked-path-to-jest-runner-mocha/integrationTests/__fixtures__/legacy-config-at-eslint.config.js/__eslint__/file.js 10 | 3:1 warning Unexpected console statement no-console 11 | 12 | ✖ 1 problem (0 errors, 1 warning) 13 | 14 | ✓ ESLint 15 | 16 | Test Suites: 1 passed, 1 total 17 | Tests: 1 passed, 1 total 18 | Snapshots: 0 total 19 | Time: 20 | Ran all test suites. 21 | " 22 | `; 23 | -------------------------------------------------------------------------------- /integrationTests/__snapshots__/loud.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Outputs warnings as console messages 1`] = ` 4 | "PASS __eslint__/file.js 5 | ● Console 6 | 7 | console.warn 8 | 9 | /mocked-path-to-jest-runner-mocha/integrationTests/__fixtures__/loud/__eslint__/file.js 10 | 3:1 warning Unexpected console statement no-console 11 | 12 | ✖ 1 problem (0 errors, 1 warning) 13 | 14 | ✓ ESLint 15 | 16 | Test Suites: 1 passed, 1 total 17 | Tests: 1 passed, 1 total 18 | Snapshots: 0 total 19 | Time: 20 | Ran all test suites. 21 | " 22 | `; 23 | -------------------------------------------------------------------------------- /integrationTests/__snapshots__/max-warnings.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Fails if more than max warnings 1`] = ` 4 | "FAIL __eslint__/file.js 5 | ✕ no-console 6 | ✕ no-console 7 | ✕ no-console 8 | 9 | 10 | /mocked-path-to-jest-runner-mocha/integrationTests/__fixtures__/max-warnings/__eslint__/file.js 11 | 3:1 warning Unexpected console statement no-console 12 | 4:1 warning Unexpected console statement no-console 13 | 5:1 warning Unexpected console statement no-console 14 | 15 | ✖ 3 problems (0 errors, 3 warnings) 16 | 17 | ESLint found too many warnings (maximum: 2). 18 | Test Suites: 1 failed, 1 total 19 | Tests: 1 failed, 1 total 20 | Snapshots: 0 total 21 | Time: 22 | Ran all test suites. 23 | " 24 | `; 25 | -------------------------------------------------------------------------------- /integrationTests/__snapshots__/passing.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Works when it has only passing tests 1`] = ` 4 | "PASS __eslint__/file.js 5 | ✓ ESLint 6 | 7 | Test Suites: 1 passed, 1 total 8 | Tests: 1 passed, 1 total 9 | Snapshots: 0 total 10 | Time: 11 | Ran all test suites. 12 | " 13 | `; 14 | -------------------------------------------------------------------------------- /integrationTests/__snapshots__/quiet-mode.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`does not log warnings as errors in quiet mode 1`] = ` 4 | "FAIL __eslint__/file.js 5 | ✕ no-undef 6 | 7 | 8 | /mocked-path-to-jest-runner-mocha/integrationTests/__fixtures__/quiet-mode/__eslint__/file.js 9 | 5:3 error 'hello' is not defined no-undef 10 | 11 | ✖ 1 problem (1 error, 0 warnings) 12 | 13 | Test Suites: 1 failed, 1 total 14 | Tests: 1 failed, 1 total 15 | Snapshots: 0 total 16 | Time: 17 | Ran all test suites. 18 | " 19 | `; 20 | -------------------------------------------------------------------------------- /integrationTests/__snapshots__/skipped.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Works when it has failing tests 1`] = ` 4 | "Test Suites: 1 skipped, 0 of 1 total 5 | Tests: 0 total 6 | Snapshots: 0 total 7 | Time: 8 | Ran all test suites. 9 | " 10 | `; 11 | -------------------------------------------------------------------------------- /integrationTests/custom-parser.test.js: -------------------------------------------------------------------------------- 1 | const { version } = require('eslint/package.json'); 2 | const semver = require('semver'); 3 | const runJest = require('./runJest'); 4 | 5 | // Note: ESLint versions 7 and 8 have a different error message or stack for 6 | // this test. The snapshot file contains all messages so we can test across 7 | // versions. Without the skipped tests for the other versions, the tests will 8 | // always fail with `1 snapshot obsolete`. 9 | if (semver.satisfies(version, '7')) { 10 | it.skip("Doesn't override parser when not set", () => {}); 11 | it("Doesn't override parser when not set [ESLint=7]", async () => { 12 | expect(await runJest('custom-parser')).toMatchSnapshot(); 13 | }); 14 | } else { 15 | it("Doesn't override parser when not set", async () => { 16 | expect(await runJest('custom-parser')).toMatchSnapshot(); 17 | }); 18 | it.skip("Doesn't override parser when not set [ESLint=7]", () => {}); 19 | } 20 | -------------------------------------------------------------------------------- /integrationTests/failing.test.js: -------------------------------------------------------------------------------- 1 | const runJest = require('./runJest'); 2 | 3 | it('Works when it has failing tests', async () => { 4 | expect(await runJest('failing')).toMatchSnapshot(); 5 | }); 6 | -------------------------------------------------------------------------------- /integrationTests/flat-config.test.js: -------------------------------------------------------------------------------- 1 | const { version } = require('eslint/package.json'); 2 | const semver = require('semver'); 3 | const runJest = require('./runJest'); 4 | 5 | (semver.satisfies(version, '>=8.41') ? it : it.skip)( 6 | 'Works with the new flat config format', 7 | async () => { 8 | expect(await runJest('flat-config')).toMatchSnapshot(); 9 | }, 10 | ); 11 | -------------------------------------------------------------------------------- /integrationTests/format.test.js: -------------------------------------------------------------------------------- 1 | const runJest = require('./runJest'); 2 | 3 | it('Applies custom formatter', async () => { 4 | expect(await runJest('format')).toMatchSnapshot(); 5 | }); 6 | -------------------------------------------------------------------------------- /integrationTests/github-actions.test.js: -------------------------------------------------------------------------------- 1 | const { version } = require('jest/package.json'); 2 | const semver = require('semver'); 3 | const runJest = require('./runJest'); 4 | 5 | // NOTE: Jest versions <28 don't have a github action reporter, so this test is 6 | // not valid. 7 | if (semver.satisfies(version, '<28')) { 8 | it.skip('Reports with the github actions reporter', () => {}); 9 | } else { 10 | it('Reports with the github actions reporter', async () => { 11 | const priorActionsConfig = process.env.GITHUB_ACTIONS; 12 | process.env.GITHUB_ACTIONS = true; 13 | expect(await runJest('github-actions')).toMatchSnapshot(); 14 | process.env.GITHUB_ACTIONS = priorActionsConfig; 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /integrationTests/legacy-config-at-eslint.config.js.test.js: -------------------------------------------------------------------------------- 1 | const { version } = require('eslint/package.json'); 2 | const semver = require('semver'); 3 | const runJest = require('./runJest'); 4 | 5 | (semver.satisfies(version, '<8.41') ? it : it.skip)( 6 | "Does not try to use flat config on eslint versions that don't support it", 7 | async () => { 8 | expect( 9 | await runJest('legacy-config-at-eslint.config.js'), 10 | ).toMatchSnapshot(); 11 | }, 12 | ); 13 | -------------------------------------------------------------------------------- /integrationTests/loud.test.js: -------------------------------------------------------------------------------- 1 | const runJest = require('./runJest'); 2 | 3 | it('Outputs warnings as console messages', async () => { 4 | expect(await runJest('loud')).toMatchSnapshot(); 5 | }); 6 | -------------------------------------------------------------------------------- /integrationTests/max-warnings.test.js: -------------------------------------------------------------------------------- 1 | const runJest = require('./runJest'); 2 | 3 | it('Fails if more than max warnings', async () => { 4 | expect(await runJest('max-warnings')).toMatchSnapshot(); 5 | }); 6 | -------------------------------------------------------------------------------- /integrationTests/passing.test.js: -------------------------------------------------------------------------------- 1 | const runJest = require('./runJest'); 2 | 3 | it('Works when it has only passing tests', async () => { 4 | expect(await runJest('passing')).toMatchSnapshot(); 5 | }); 6 | -------------------------------------------------------------------------------- /integrationTests/quiet-mode.test.js: -------------------------------------------------------------------------------- 1 | const runJest = require('./runJest'); 2 | 3 | it('does not log warnings as errors in quiet mode', async () => { 4 | expect(await runJest('quiet-mode')).toMatchSnapshot(); 5 | }); 6 | -------------------------------------------------------------------------------- /integrationTests/runJest.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | // eslint-disable-next-line import/no-extraneous-dependencies 3 | const execa = require('execa'); 4 | 5 | const rootDir = path.join(__dirname, '..'); 6 | 7 | const normalize = output => 8 | output 9 | .replace(/((✕|✓) .* )\(\d*(\.\d+)? ?m?s\)/g, '$1') 10 | .replace(new RegExp(rootDir, 'g'), '/mocked-path-to-jest-runner-mocha') 11 | .replace(/(Time: {8})\d*(\.\d+)? ?m?s/, '$1'); 12 | 13 | const runJest = (testDir, options = []) => { 14 | jest.setTimeout(30000); 15 | return execa( 16 | 'jest', 17 | ['--useStderr', '--no-watchman', '--no-cache'].concat(options), 18 | { 19 | cwd: path.join(__dirname, '__fixtures__', testDir), 20 | env: { ...process.env, FORCE_COLOR: 0 }, 21 | reject: false, 22 | }, 23 | ).then(({ stdout, stderr }) => `${normalize(stderr)}\n${normalize(stdout)}`); 24 | }; 25 | 26 | module.exports = runJest; 27 | -------------------------------------------------------------------------------- /integrationTests/skipped.test.js: -------------------------------------------------------------------------------- 1 | const runJest = require('./runJest'); 2 | 3 | it('Works when it has failing tests', async () => { 4 | expect(await runJest('skipped')).toMatchSnapshot(); 5 | }); 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | watchPlugins: [ 3 | 'jest-watch-typeahead/filename', 4 | 'jest-watch-typeahead/testname', 5 | 'jest-watch-select-projects', 6 | './watch-fix', 7 | ], 8 | projects: [ 9 | { 10 | displayName: 'e2e', 11 | testPathIgnorePatterns: [ 12 | '/examples/', 13 | '/node_modules/', 14 | '/__eslint__/', 15 | '/__fixtures__/', 16 | ], 17 | testMatch: [ 18 | '/integrationTests/*.test.js', 19 | '/integrationTests/**/*.test.js', 20 | ], 21 | }, 22 | { 23 | displayName: 'tests', 24 | testMatch: ['/src/**/__tests__/**/*.js'], 25 | }, 26 | { 27 | displayName: 'lint', 28 | runner: './', 29 | testPathIgnorePatterns: [ 30 | '/examples/', 31 | '/node_modules/', 32 | '/__eslint__/', 33 | '/__fixtures__/', 34 | ], 35 | testMatch: [ 36 | '/src/*.js', 37 | '/src/**/*.js', 38 | '/integrationTests/*.js', 39 | '/integrationTests/**/*.js', 40 | ], 41 | }, 42 | ], 43 | }; 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jest-runner-eslint", 3 | "version": "2.2.1", 4 | "main": "build/runner", 5 | "author": "Rogelio Guzman ", 6 | "description": "An ESLint runner for Jest", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/jest-community/jest-runner-eslint.git" 11 | }, 12 | "homepage": "https://github.com/jest-community/jest-runner-eslint", 13 | "files": [ 14 | "build/", 15 | "watch-fix.js" 16 | ], 17 | "scripts": { 18 | "test": "jest", 19 | "lint": "eslint . --config ./.eslintrc.json --no-eslintrc", 20 | "watch": "babel src -w --ignore **/*.test.js,integration -d build", 21 | "prebuild": "rimraf build/", 22 | "build": "babel src --ignore **/*.test.js,integration -d build", 23 | "prepare": "yarn build", 24 | "format": "prettier --write \"**/*.js\"" 25 | }, 26 | "dependencies": { 27 | "chalk": "^4.0.0", 28 | "cosmiconfig": "^7.0.0", 29 | "create-jest-runner": "^0.11.2", 30 | "dot-prop": "^6.0.1" 31 | }, 32 | "devDependencies": { 33 | "@babel/cli": "^7.10.4", 34 | "@babel/core": "^7.10.4", 35 | "@babel/preset-env": "^7.10.4", 36 | "babel-jest": "^27 || ^28 || ^29", 37 | "eslint": "^7 || ^8", 38 | "eslint-config-airbnb-base": "^15.0.0", 39 | "eslint-config-prettier": "^10.0.0", 40 | "eslint-formatter-codeframe": "^7.32.1", 41 | "eslint-plugin-import": "^2.22.0", 42 | "eslint-plugin-jest": "^26.9.0", 43 | "eslint-plugin-prettier": "^4.0.0", 44 | "execa": "^5.1.1", 45 | "jest": "^27 || ^28 || ^29", 46 | "jest-watch-select-projects": "^2.0.0", 47 | "jest-watch-typeahead": "^1.1.0 || ^2.1.1", 48 | "prettier": "^2.8.4", 49 | "rimraf": "^3.0.2", 50 | "semver": "^7.3.8" 51 | }, 52 | "peerDependencies": { 53 | "eslint": "^7 || ^8", 54 | "jest": "^27 || ^28 || ^29" 55 | }, 56 | "prettier": { 57 | "arrowParens": "avoid", 58 | "proseWrap": "never", 59 | "singleQuote": true, 60 | "trailingComma": "all" 61 | }, 62 | "engines": { 63 | "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=18.0.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/runner/__tests__/runESLint.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable class-methods-use-this, global-require */ 2 | const path = require('path'); 3 | 4 | const runESLintRunnerWithMockedEngine = ({ 5 | mockOptions, 6 | runESLintOptions, 7 | extraOptions, 8 | }) => { 9 | jest.resetModules(); 10 | jest.doMock('eslint', () => ({ 11 | ESLint: class { 12 | isPathIgnored(file) { 13 | return mockOptions.ignoredFiles.includes(file); 14 | } 15 | 16 | lintFiles() { 17 | return mockOptions.errorCount > 0 18 | ? [ 19 | { 20 | errorCount: mockOptions.errorCount, 21 | warningCount: 0, 22 | messages: [ 23 | { 24 | message: 'Test', 25 | ruleId: 'test-rule', 26 | line: 1, 27 | column: 2, 28 | }, 29 | ], 30 | }, 31 | ] 32 | : []; 33 | } 34 | 35 | loadFormatter() { 36 | return Promise.resolve({ format() {} }); 37 | } 38 | 39 | // eslint-disable-next-line no-unused-vars 40 | static outputFixes(report) {} 41 | }, 42 | })); 43 | const runESLint = require('../runESLint'); 44 | 45 | return runESLint({ extraOptions: extraOptions || {}, ...runESLintOptions }); 46 | }; 47 | 48 | it('Requires the config setupTestFrameworkScriptFile when specified', async () => { 49 | const setupFile = path.join(__dirname, './path/to/setupFile.js'); 50 | 51 | let setupFileWasLoaded = false; 52 | jest.doMock( 53 | setupFile, 54 | () => { 55 | setupFileWasLoaded = true; 56 | }, 57 | { virtual: true }, 58 | ); 59 | 60 | await runESLintRunnerWithMockedEngine({ 61 | mockOptions: { 62 | ignoredFiles: ['/path/to/file.test.js'], 63 | errorCount: 0, 64 | }, 65 | runESLintOptions: { 66 | testPath: 'path/to/file.test.js', 67 | config: { 68 | setupTestFrameworkScriptFile: setupFile, 69 | }, 70 | }, 71 | }); 72 | 73 | expect(setupFileWasLoaded).toBeTruthy(); 74 | }); 75 | 76 | it('Requires the config setupFilesAfterEnv when specified', async () => { 77 | const setupFiles = [ 78 | path.join(__dirname, './path/to/setupFileFoo.js'), 79 | path.join(__dirname, './path/to/setupFileBar.js'), 80 | ]; 81 | 82 | const setupFilesWereLoaded = setupFiles.map(() => false); 83 | 84 | setupFiles.forEach((setupFile, index) => { 85 | jest.doMock( 86 | setupFile, 87 | () => { 88 | setupFilesWereLoaded[index] = true; 89 | }, 90 | { virtual: true }, 91 | ); 92 | }); 93 | 94 | await runESLintRunnerWithMockedEngine({ 95 | mockOptions: { 96 | ignoredFiles: ['/path/to/file.test.js'], 97 | errorCount: 0, 98 | }, 99 | runESLintOptions: { 100 | testPath: 'path/to/file.test.js', 101 | config: { 102 | setupFilesAfterEnv: setupFiles, 103 | }, 104 | }, 105 | }); 106 | 107 | expect(setupFilesWereLoaded).toEqual([true, true]); 108 | }); 109 | 110 | it('Returns "skipped" when the test path is ignored', async () => { 111 | const result = await runESLintRunnerWithMockedEngine({ 112 | mockOptions: { 113 | ignoredFiles: ['/path/to/file.test.js'], 114 | errorCount: 0, 115 | }, 116 | runESLintOptions: { 117 | testPath: '/path/to/file.test.js', 118 | config: {}, 119 | }, 120 | }); 121 | 122 | expect(result).toMatchObject({ 123 | numFailingTests: 0, 124 | numPassingTests: 0, 125 | numPendingTests: 0, 126 | skipped: true, 127 | }); 128 | }); 129 | 130 | it('Returns "passed" when the test passed', async () => { 131 | const result = await runESLintRunnerWithMockedEngine({ 132 | mockOptions: { 133 | ignoredFiles: [], 134 | errorCount: 0, 135 | }, 136 | runESLintOptions: { 137 | testPath: '/path/to/file.test.js', 138 | config: {}, 139 | }, 140 | }); 141 | 142 | expect(result).toMatchObject({ 143 | numFailingTests: 0, 144 | numPassingTests: 1, 145 | numPendingTests: 0, 146 | }); 147 | }); 148 | 149 | it('Returns "fail" when the test failed', async () => { 150 | const result = await runESLintRunnerWithMockedEngine({ 151 | mockOptions: { 152 | ignoredFiles: [], 153 | errorCount: 1, 154 | }, 155 | runESLintOptions: { 156 | testPath: '/path/to/file.test.js', 157 | config: {}, 158 | }, 159 | }); 160 | 161 | expect(result).toMatchObject({ 162 | numFailingTests: 1, 163 | numPassingTests: 0, 164 | numPendingTests: 0, 165 | testResults: [ 166 | { 167 | title: 'test-rule', 168 | testFilePath: '/path/to/file.test.js', 169 | fullName: '1:2: Test [test-rule]', 170 | failureMessages: ['Test\n at /path/to/file.test.js:1:2'], 171 | location: { 172 | line: 1, 173 | column: 2, 174 | }, 175 | status: 'failed', 176 | }, 177 | ], 178 | }); 179 | }); 180 | 181 | it('Not to be override by undefined in extraOptions', async () => { 182 | const result = await runESLintRunnerWithMockedEngine({ 183 | mockOptions: { 184 | ignoredFiles: [], 185 | errorCount: 0, 186 | }, 187 | runESLintOptions: { 188 | testPath: '/path/to/file.test.js', 189 | config: {}, 190 | }, 191 | extraOptions: { 192 | fix: true, 193 | }, 194 | }); 195 | 196 | expect(result.cliOptions.fix).toBeTruthy(); 197 | }); 198 | -------------------------------------------------------------------------------- /src/runner/index.js: -------------------------------------------------------------------------------- 1 | const { createJestRunner } = require('create-jest-runner'); 2 | const configOverrides = require('../utils/configOverrides'); 3 | 4 | const runner = createJestRunner(require.resolve('./runESLint'), { 5 | getExtraOptions: () => ({ fix: configOverrides.getFix() }), 6 | }); 7 | 8 | module.exports = runner; 9 | -------------------------------------------------------------------------------- /src/runner/runESLint.js: -------------------------------------------------------------------------------- 1 | const eslint = require('eslint'); 2 | const getESLintOptions = require('../utils/getESLintOptions'); 3 | 4 | const { ESLint } = eslint; 5 | let { loadESLint } = eslint; 6 | 7 | // loadESLint and ESLint.configType were added in eslint v8.57.0. The block 8 | // below is some compat code to make this library work with flat config and 9 | // versions of eslint prior to 8.57.0. 10 | if (!loadESLint) { 11 | try { 12 | const { 13 | FlatESLint, 14 | shouldUseFlatConfig, 15 | // eslint-disable-next-line global-require, import/no-unresolved 16 | } = require('eslint/use-at-your-own-risk'); 17 | FlatESLint.configType = 'flat'; 18 | loadESLint = async () => 19 | (await shouldUseFlatConfig?.()) ? FlatESLint : ESLint; 20 | } catch { 21 | /* no-op */ 22 | } 23 | } 24 | 25 | /* 26 | * This function exists because there are issues with the `pass`, `skip`, and 27 | * `fail` functions from `create-jest-runner`: 28 | * 29 | * 1. The `pass` and `skip` functions have a bug in our version of 30 | * `create-jest-runner` where calling them will actually pass an `undefined` 31 | * item in `testResults`, which causes the GithubActionsReporter to report an 32 | * empty error message on every file. This has been resolved in later 33 | * versions of `create-jest-runner`, but upgrading is a breaking change that 34 | * is incompatible with some node versions we support. 35 | * 36 | * 2. The `fail` function in `create-jest-runner` does not support passing 37 | * multiple failure messages, which makes it impossible to annotate each 38 | * eslint failure. This has not been resolved, although presumably could be 39 | * worked around by using the underlying `toTestResult` function instead 40 | * (although that function isn't exposed in the public API of that library). 41 | * 42 | * TODO At some point, we should put a PR in to `create-jest-runner` to resolve 43 | * point 2 above, and then should upgrade and remove this function and go back 44 | * to using `pass`, `skip`, and `fail` from that library instead. 45 | */ 46 | const mkTestResults = ({ 47 | message, 48 | start, 49 | end, 50 | numFailingTests, 51 | numPassingTests, 52 | testPath, 53 | assertionResults, 54 | cliOptions, 55 | }) => { 56 | const startTime = new Date(start).getTime(); 57 | const endTime = new Date(end).getTime(); 58 | 59 | return { 60 | failureMessage: message, 61 | leaks: false, 62 | numFailingTests, 63 | numPassingTests, 64 | numPendingTests: 0, 65 | numTodoTests: 0, 66 | openHandles: [], 67 | perfStats: { 68 | start: startTime, 69 | end: endTime, 70 | duration: endTime - startTime, 71 | runtime: endTime - startTime, 72 | slow: false, 73 | }, 74 | skipped: numPassingTests === 0 && numFailingTests === 0, 75 | snapshot: { 76 | added: 0, 77 | fileDeleted: false, 78 | matched: 0, 79 | unchecked: 0, 80 | uncheckedKeys: [], 81 | unmatched: 0, 82 | updated: 0, 83 | }, 84 | testFilePath: testPath, 85 | testResults: assertionResults.map(result => ({ 86 | duration: endTime - startTime, 87 | ancestorTitles: [], 88 | failureDetails: [], 89 | failureMessages: result.message ? [result.message] : [], 90 | fullName: result.fullName, 91 | location: result.location, 92 | testFilePath: testPath, 93 | numPassingAsserts: numPassingTests, 94 | status: result.status, 95 | title: result.title, 96 | })), 97 | cliOptions, 98 | }; 99 | }; 100 | 101 | const mkAssertionResults = (testPath, report) => 102 | report[0].messages?.map(reportMessage => ({ 103 | message: [ 104 | reportMessage.message, 105 | ` at ${testPath}:${reportMessage.line}:${reportMessage.column}`, 106 | ].join('\n'), 107 | fullName: `${reportMessage.line}:${reportMessage.column}: ${reportMessage.message} [${reportMessage.ruleId}]`, 108 | location: { 109 | column: reportMessage.column, 110 | line: reportMessage.line, 111 | }, 112 | status: 'failed', 113 | title: reportMessage.ruleId, 114 | })) ?? []; 115 | 116 | const getComputedFixValue = ({ fix, quiet, fixDryRun }) => { 117 | if (fix || fixDryRun) { 118 | return quiet ? ({ severity }) => severity === 2 : true; 119 | } 120 | return undefined; 121 | }; 122 | 123 | function removeUndefinedFromObject(object) { 124 | return Object.fromEntries( 125 | Object.entries(object).filter(([, value]) => typeof value !== 'undefined'), 126 | ); 127 | } 128 | 129 | let cachedValues; 130 | const getCachedValues = async (config, extraOptions) => { 131 | if (!cachedValues) { 132 | const ESLintConstructor = (await loadESLint?.()) ?? ESLint; 133 | 134 | const { cliOptions: baseCliOptions } = getESLintOptions( 135 | ESLintConstructor.configType, 136 | config, 137 | ); 138 | const cliOptions = { 139 | ...baseCliOptions, 140 | fix: getComputedFixValue(baseCliOptions), 141 | ...removeUndefinedFromObject(extraOptions), 142 | }; 143 | 144 | // Remove options that are not constructor args. 145 | const { fixDryRun, format, maxWarnings, quiet, ...constructorArgs } = 146 | cliOptions; 147 | 148 | const cli = new ESLintConstructor(constructorArgs); 149 | 150 | cachedValues = { 151 | isPathIgnored: cli.isPathIgnored.bind(cli), 152 | lintFiles: (...args) => cli.lintFiles(...args), 153 | formatter: async (...args) => { 154 | const formatter = await cli.loadFormatter(cliOptions.format); 155 | return formatter.format(...args); 156 | }, 157 | cliOptions, 158 | ESLintConstructor, 159 | }; 160 | } 161 | 162 | return cachedValues; 163 | }; 164 | 165 | const runESLint = async ({ testPath, config, extraOptions }) => { 166 | const start = Date.now(); 167 | 168 | if (config.setupTestFrameworkScriptFile) { 169 | // eslint-disable-next-line import/no-dynamic-require,global-require 170 | require(config.setupTestFrameworkScriptFile); 171 | } 172 | 173 | if (config.setupFilesAfterEnv) { 174 | // eslint-disable-next-line import/no-dynamic-require,global-require 175 | config.setupFilesAfterEnv.forEach(require); 176 | } 177 | 178 | const { isPathIgnored, lintFiles, formatter, cliOptions, ESLintConstructor } = 179 | await getCachedValues(config, extraOptions); 180 | 181 | if (await isPathIgnored(testPath)) { 182 | return mkTestResults({ 183 | start, 184 | end: Date.now(), 185 | testPath, 186 | numFailingTests: 0, 187 | numPassingTests: 0, 188 | assertionResults: [ 189 | { 190 | title: 'ESLint', 191 | status: 'skipped', 192 | }, 193 | ], 194 | }); 195 | } 196 | 197 | const report = await lintFiles([testPath]); 198 | 199 | if (cliOptions.fix && !cliOptions.fixDryRun) { 200 | await ESLintConstructor.outputFixes(report); 201 | } 202 | 203 | const end = Date.now(); 204 | 205 | const eslintReport = cliOptions.quiet 206 | ? ESLintConstructor.getErrorResults(report) 207 | : report; 208 | const message = await formatter(eslintReport); 209 | 210 | if (eslintReport[0]?.errorCount > 0) { 211 | return mkTestResults({ 212 | message, 213 | start, 214 | end, 215 | testPath, 216 | numFailingTests: eslintReport[0].errorCount, 217 | numPassingTests: 0, 218 | assertionResults: mkAssertionResults(testPath, eslintReport), 219 | cliOptions, 220 | }); 221 | } 222 | 223 | const tooManyWarnings = 224 | cliOptions.maxWarnings >= 0 && 225 | report[0]?.warningCount > cliOptions.maxWarnings; 226 | if (tooManyWarnings) { 227 | return mkTestResults({ 228 | message: `${message}\nESLint found too many warnings (maximum: ${cliOptions.maxWarnings}).`, 229 | start, 230 | end, 231 | testPath, 232 | numFailingTests: 1, 233 | numPassingTests: 0, 234 | assertionResults: mkAssertionResults(testPath, report), 235 | cliOptions, 236 | }); 237 | } 238 | 239 | const result = mkTestResults({ 240 | start, 241 | end, 242 | testPath, 243 | numFailingTests: 0, 244 | numPassingTests: 1, 245 | assertionResults: [ 246 | { 247 | title: 'ESLint', 248 | status: 'passed', 249 | }, 250 | ], 251 | cliOptions, 252 | }); 253 | 254 | if (!cliOptions.quiet && report[0]?.warningCount > 0) { 255 | result.console = [{ message, origin: '', type: 'warn' }]; 256 | } 257 | 258 | return result; 259 | }; 260 | 261 | module.exports = runESLint; 262 | -------------------------------------------------------------------------------- /src/utils/__tests__/normalizeConfig.test.js: -------------------------------------------------------------------------------- 1 | const normalizeConfig = require('../normalizeConfig'); 2 | 3 | const normalizeCLIOptions = (cliOptions, configType) => 4 | normalizeConfig(configType, { cliOptions }).cliOptions; 5 | 6 | it('ignores unknown options', () => { 7 | expect(normalizeCLIOptions({ other: true })).not.toMatchObject({ 8 | other: true, 9 | }); 10 | }); 11 | 12 | it('normalizes noInlineConfig', () => { 13 | expect(normalizeCLIOptions({})).toMatchObject({ 14 | allowInlineConfig: true, 15 | }); 16 | 17 | expect(normalizeCLIOptions({ noInlineConfig: true })).toMatchObject({ 18 | allowInlineConfig: false, 19 | }); 20 | 21 | expect(normalizeCLIOptions({ noInlineConfig: false })).toMatchObject({ 22 | allowInlineConfig: true, 23 | }); 24 | }); 25 | 26 | it('normalizes cacheLocation', () => { 27 | expect(normalizeCLIOptions({})).toMatchObject({ 28 | cacheLocation: '.eslintcache', 29 | }); 30 | 31 | expect( 32 | normalizeCLIOptions({ cacheLocation: '/path/to/cache' }), 33 | ).toMatchObject({ 34 | cacheLocation: '/path/to/cache', 35 | }); 36 | }); 37 | 38 | it('normalizes config', () => { 39 | expect(normalizeCLIOptions({})).toMatchObject({ 40 | overrideConfigFile: null, 41 | }); 42 | 43 | expect(normalizeCLIOptions({ config: '/path/to/config' })).toMatchObject({ 44 | overrideConfigFile: '/path/to/config', 45 | }); 46 | }); 47 | 48 | it('normalizes env', () => { 49 | expect(normalizeCLIOptions({})).toMatchObject({ 50 | overrideConfig: { 51 | env: {}, 52 | }, 53 | }); 54 | 55 | expect(normalizeCLIOptions({ env: 'mocha' })).toMatchObject({ 56 | overrideConfig: { 57 | env: 'mocha', 58 | }, 59 | }); 60 | 61 | expect(normalizeCLIOptions({ env: ['mocha', 'browser'] })).toMatchObject({ 62 | overrideConfig: { 63 | env: ['mocha', 'browser'], 64 | }, 65 | }); 66 | }); 67 | 68 | it('normalizes ext', () => { 69 | expect(normalizeCLIOptions({})).toMatchObject({ 70 | extensions: ['.js'], 71 | }); 72 | 73 | expect(normalizeCLIOptions({ ext: '.ts' })).toMatchObject({ 74 | extensions: ['.ts'], 75 | }); 76 | 77 | expect(normalizeCLIOptions({ ext: ['.js', '.jsx', '.ts'] })).toMatchObject({ 78 | extensions: ['.js', '.jsx', '.ts'], 79 | }); 80 | }); 81 | 82 | it('normalizes fix', () => { 83 | expect(normalizeCLIOptions({})).toMatchObject({ 84 | fix: false, 85 | }); 86 | 87 | expect(normalizeCLIOptions({ fix: true })).toMatchObject({ 88 | fix: true, 89 | }); 90 | }); 91 | 92 | it('normalizes global', () => { 93 | expect(normalizeCLIOptions({})).toMatchObject({ 94 | overrideConfig: { 95 | globals: {}, 96 | }, 97 | }); 98 | 99 | expect(normalizeCLIOptions({ global: 'it' })).toMatchObject({ 100 | overrideConfig: { 101 | globals: 'it', 102 | }, 103 | }); 104 | 105 | expect(normalizeCLIOptions({ global: ['it', 'describe'] })).toMatchObject({ 106 | overrideConfig: { 107 | globals: ['it', 'describe'], 108 | }, 109 | }); 110 | }); 111 | 112 | it('normalizes maxWarnings', () => { 113 | expect(normalizeCLIOptions({})).toMatchObject({ 114 | maxWarnings: -1, 115 | }); 116 | 117 | expect(normalizeCLIOptions({ maxWarnings: '10' })).toMatchObject({ 118 | maxWarnings: 10, 119 | }); 120 | 121 | expect(() => normalizeCLIOptions({ maxWarnings: 'not-an-int' })).toThrowError( 122 | `'not-an-int' cannot be converted to a number`, 123 | ); 124 | }); 125 | 126 | it('normalizes noIgnore', () => { 127 | expect(normalizeCLIOptions({})).toMatchObject({ 128 | ignore: true, 129 | }); 130 | 131 | expect(normalizeCLIOptions({ noIgnore: true })).toMatchObject({ 132 | ignore: false, 133 | }); 134 | }); 135 | 136 | it('normalizes ignorePath', () => { 137 | expect(normalizeCLIOptions({})).toMatchObject({ 138 | ignorePath: null, 139 | }); 140 | 141 | expect(normalizeCLIOptions({ ignorePath: '/path/to/ignore' })).toMatchObject({ 142 | ignorePath: '/path/to/ignore', 143 | }); 144 | }); 145 | 146 | it('normalizes parser', () => { 147 | expect(normalizeCLIOptions({})).not.toMatchObject({ 148 | overrideConfig: { 149 | parser: 'espree', 150 | }, 151 | }); 152 | 153 | expect(normalizeCLIOptions({ parser: 'flow' })).toMatchObject({ 154 | overrideConfig: { 155 | parser: 'flow', 156 | }, 157 | }); 158 | }); 159 | 160 | it('normalizes parserOptions', () => { 161 | expect(normalizeCLIOptions({})).toMatchObject({ 162 | overrideConfig: { 163 | parserOptions: {}, 164 | }, 165 | }); 166 | 167 | expect( 168 | normalizeCLIOptions({ parserOptions: { ecmaVersion: 2015 } }), 169 | ).toMatchObject({ 170 | overrideConfig: { 171 | parserOptions: { ecmaVersion: 2015 }, 172 | }, 173 | }); 174 | }); 175 | 176 | it('normalizes plugin', () => { 177 | expect(normalizeCLIOptions({})).toMatchObject({ 178 | overrideConfig: { 179 | plugins: [], 180 | }, 181 | }); 182 | 183 | expect(normalizeCLIOptions({ plugin: 'prettier' })).toMatchObject({ 184 | overrideConfig: { 185 | plugins: ['prettier'], 186 | }, 187 | }); 188 | 189 | expect(normalizeCLIOptions({ plugin: ['prettier'] })).toMatchObject({ 190 | overrideConfig: { 191 | plugins: ['prettier'], 192 | }, 193 | }); 194 | }); 195 | 196 | it('normalizes rulesdir', () => { 197 | expect(normalizeCLIOptions({})).toMatchObject({ 198 | rulePaths: [], 199 | }); 200 | 201 | expect(normalizeCLIOptions({ rulesdir: '/path/to/rules' })).toMatchObject({ 202 | rulePaths: ['/path/to/rules'], 203 | }); 204 | 205 | expect( 206 | normalizeCLIOptions({ rulesdir: ['/path/to/rules', '/other/path'] }), 207 | ).toMatchObject({ 208 | rulePaths: ['/path/to/rules', '/other/path'], 209 | }); 210 | }); 211 | 212 | it('normalizes rules', () => { 213 | expect(normalizeCLIOptions({})).toMatchObject({ 214 | overrideConfig: { 215 | rules: {}, 216 | }, 217 | }); 218 | 219 | const ruleOptions = { quotes: [2, 'double'], 'no-console': 2 }; 220 | 221 | expect(normalizeCLIOptions({ rules: ruleOptions })).toMatchObject({ 222 | overrideConfig: { 223 | rules: ruleOptions, 224 | }, 225 | }); 226 | }); 227 | 228 | it('normalizes noEslintrc', () => { 229 | expect(normalizeCLIOptions({})).toMatchObject({ 230 | useEslintrc: true, 231 | }); 232 | 233 | expect(normalizeCLIOptions({ noEslintrc: true })).toMatchObject({ 234 | useEslintrc: false, 235 | }); 236 | }); 237 | -------------------------------------------------------------------------------- /src/utils/configOverrides.js: -------------------------------------------------------------------------------- 1 | class ConfigOverrides { 2 | setFix(fix) { 3 | this.fix = fix; 4 | } 5 | 6 | getFix() { 7 | return this.fix; 8 | } 9 | } 10 | 11 | const configOverrides = new ConfigOverrides(); 12 | 13 | module.exports = configOverrides; 14 | -------------------------------------------------------------------------------- /src/utils/getESLintOptions.js: -------------------------------------------------------------------------------- 1 | const { cosmiconfigSync } = require('cosmiconfig'); 2 | const normalizeConfig = require('./normalizeConfig'); 3 | 4 | const explorer = cosmiconfigSync('jest-runner-eslint'); 5 | 6 | const getESLintOptions = (configType, config) => { 7 | const result = explorer.search(config.rootDir); 8 | 9 | if (result) { 10 | return normalizeConfig(configType, result.config); 11 | } 12 | 13 | return normalizeConfig(configType, {}); 14 | }; 15 | 16 | module.exports = getESLintOptions; 17 | -------------------------------------------------------------------------------- /src/utils/normalizeConfig.js: -------------------------------------------------------------------------------- 1 | const dotProp = require('dot-prop'); 2 | 3 | const identity = v => v; 4 | const negate = v => !v; 5 | const asArray = v => (typeof v === 'string' ? [v] : v); 6 | const asInt = v => { 7 | if (typeof v === 'number') { 8 | return v; 9 | } 10 | const int = parseInt(v, 10); 11 | if (Number.isNaN(int)) { 12 | throw new Error(`'${v}' cannot be converted to a number`); 13 | } 14 | return int; 15 | }; 16 | 17 | const BASE_CONFIG = { 18 | cache: { 19 | default: false, 20 | }, 21 | cacheLocation: { 22 | default: '.eslintcache', 23 | }, 24 | fix: { 25 | default: false, 26 | }, 27 | fixDryRun: { 28 | default: false, 29 | }, 30 | format: { 31 | default: undefined, 32 | }, 33 | maxWarnings: { 34 | default: -1, 35 | transform: asInt, 36 | }, 37 | noIgnore: { 38 | name: 'ignore', 39 | default: false, 40 | transform: negate, 41 | }, 42 | noInlineConfig: { 43 | name: 'allowInlineConfig', 44 | default: false, 45 | transform: negate, 46 | }, 47 | quiet: { 48 | default: false, 49 | }, 50 | config: { 51 | name: 'overrideConfigFile', 52 | default: null, 53 | }, 54 | }; 55 | 56 | const LEGACY_CONFIG = { 57 | ...BASE_CONFIG, 58 | ext: { 59 | name: 'extensions', 60 | default: ['.js'], 61 | transform: asArray, 62 | }, 63 | ignorePath: { 64 | default: null, 65 | }, 66 | rulesdir: { 67 | name: 'rulePaths', 68 | default: [], 69 | transform: asArray, 70 | }, 71 | resolvePluginsRelativeTo: { 72 | default: undefined, 73 | }, 74 | noEslintrc: { 75 | name: 'useEslintrc', 76 | default: false, 77 | transform: negate, 78 | }, 79 | reportUnusedDisableDirectives: { 80 | default: null, 81 | }, 82 | env: { 83 | name: 'overrideConfig.env', 84 | default: {}, 85 | }, 86 | global: { 87 | name: 'overrideConfig.globals', 88 | default: {}, 89 | }, 90 | ignorePattern: { 91 | name: 'overrideConfig.ignorePatterns', 92 | default: [], 93 | transform: asArray, 94 | }, 95 | parser: { 96 | name: 'overrideConfig.parser', 97 | default: null, 98 | }, 99 | parserOptions: { 100 | name: 'overrideConfig.parserOptions', 101 | default: {}, 102 | }, 103 | plugin: { 104 | name: 'overrideConfig.plugins', 105 | default: [], 106 | transform: asArray, 107 | }, 108 | rules: { 109 | name: 'overrideConfig.rules', 110 | default: {}, 111 | }, 112 | }; 113 | 114 | const FLAT_CONFIG = { 115 | ...BASE_CONFIG, 116 | reportUnusedDisableDirectives: { 117 | name: 'overrideConfig.linterOptions.reportUnusedDisableDirectives', 118 | default: false, 119 | }, 120 | }; 121 | 122 | const normalizeCliOptions = (configType, rawConfig) => { 123 | const configConfig = configType === 'flat' ? FLAT_CONFIG : LEGACY_CONFIG; 124 | return Object.keys(configConfig).reduce((config, key) => { 125 | const { 126 | name = key, 127 | transform = identity, 128 | default: defaultValue, 129 | } = configConfig[key]; 130 | 131 | const value = rawConfig[key] !== undefined ? rawConfig[key] : defaultValue; 132 | 133 | dotProp.set(config, name, transform(value)); 134 | 135 | return config; 136 | }, {}); 137 | }; 138 | 139 | const normalizeConfig = (configType, config) => { 140 | return { 141 | ...config, 142 | cliOptions: normalizeCliOptions(configType, config.cliOptions || {}), 143 | }; 144 | }; 145 | 146 | module.exports = normalizeConfig; 147 | -------------------------------------------------------------------------------- /src/watchFixPlugin/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | const chalk = require('chalk'); 3 | 4 | jest.doMock('chalk', () => new chalk.Instance({ level: 0 })); 5 | 6 | jest.useFakeTimers(); 7 | 8 | let WatchFixPlugin; 9 | let configOverrides; 10 | 11 | describe('watchFixPlugin', () => { 12 | beforeEach(() => { 13 | jest.resetModules(); 14 | configOverrides = require('../../utils/configOverrides'); 15 | WatchFixPlugin = require('..'); 16 | }); 17 | 18 | it('shows the correct prompt', async () => { 19 | const stdout = { write: jest.fn() }; 20 | const config = {}; 21 | const plugin = new WatchFixPlugin({ stdout, config }); 22 | expect(plugin.getUsageInfo()).toEqual({ 23 | key: 'F', 24 | prompt: 'override ESLint --fix', 25 | }); 26 | 27 | await plugin.run(plugin); 28 | 29 | expect(plugin.getUsageInfo()).toEqual({ 30 | key: 'F', 31 | prompt: 'toggle ESLint --fix (enabled)', 32 | }); 33 | 34 | await plugin.run(plugin); 35 | 36 | expect(plugin.getUsageInfo()).toEqual({ 37 | key: 'F', 38 | prompt: 'toggle ESLint --fix (disabled)', 39 | }); 40 | }); 41 | 42 | it('overrides the setting in configOverrides after each invocation', async () => { 43 | const stdout = { write: jest.fn() }; 44 | const config = {}; 45 | const plugin = new WatchFixPlugin({ stdout, config }); 46 | expect(configOverrides.getFix()).toBeUndefined(); 47 | 48 | await plugin.run(plugin); 49 | 50 | expect(configOverrides.getFix()).toBe(true); 51 | 52 | await plugin.run(plugin); 53 | 54 | expect(configOverrides.getFix()).toBe(false); 55 | }); 56 | 57 | it('can customize the key', () => { 58 | const stdout = { write: jest.fn() }; 59 | const config = { key: 'z' }; 60 | const plugin = new WatchFixPlugin({ stdout, config }); 61 | expect(plugin.getUsageInfo()).toEqual({ 62 | key: 'z', 63 | prompt: 'override ESLint --fix', 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /src/watchFixPlugin/index.js: -------------------------------------------------------------------------------- 1 | const { getVersion: getJestVersion } = require('jest'); 2 | const chalk = require('chalk'); 3 | const configOverrides = require('../utils/configOverrides'); 4 | 5 | const majorJestVersion = parseInt(getJestVersion().split('.')[0], 10); 6 | 7 | if (majorJestVersion < 23) { 8 | // eslint-disable-next-line no-console 9 | throw new Error(`Insufficient Jest version for jest-runner-eslint watch plugin 10 | 11 | Watch plugins are only available in Jest 23.0.0 and above. 12 | Upgrade your version of Jest in order to use it. 13 | `); 14 | } 15 | 16 | class ESLintWatchFixPlugin { 17 | constructor({ stdout, config }) { 18 | this._stdout = stdout; 19 | this._key = config.key || 'F'; 20 | } 21 | 22 | // eslint-disable-next-line class-methods-use-this 23 | async run() { 24 | const fix = configOverrides.getFix(); 25 | configOverrides.setFix(!fix); 26 | return true; 27 | } 28 | 29 | getUsageInfo() { 30 | const getPrompt = () => { 31 | const fix = configOverrides.getFix(); 32 | if (fix === undefined) { 33 | return 'override ESLint --fix'; 34 | } 35 | if (!fix) { 36 | return `toggle ESLint --fix ${chalk.italic('(disabled)')}`; 37 | } 38 | return `toggle ESLint --fix ${chalk.italic('(enabled)')}`; 39 | }; 40 | 41 | return { 42 | key: this._key, 43 | prompt: getPrompt(), 44 | }; 45 | } 46 | } 47 | 48 | module.exports = ESLintWatchFixPlugin; 49 | -------------------------------------------------------------------------------- /watch-fix.js: -------------------------------------------------------------------------------- 1 | const ESLintWatchFixPlugin = require('./build/watchFixPlugin'); 2 | 3 | module.exports = ESLintWatchFixPlugin; 4 | --------------------------------------------------------------------------------