Skip to content

Commit f6c72e1

Browse files
committed
feat: use ESLint instead of CLIEngine when available
BREAKING CHANGE: Using `ESLint` might need different configuration options. See https://eslint.org/docs/developer-guide/nodejs-api#parameters
1 parent ae4d533 commit f6c72e1

File tree

9 files changed

+146
-42
lines changed

9 files changed

+146
-42
lines changed

.eslintrc.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{
22
"extends": ["airbnb-base", "prettier"],
33
"plugins": ["prettier", "jest"],
4+
"parserOptions": {
5+
"ecmaVersion": 11
6+
},
47
"rules": {
58
"no-underscore-dangle": 0,
69
"arrow-body-style": 0,
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
const { ESLint } = require('eslint');
2+
13
module.exports = {
24
cliOptions: {
3-
global: ['hello'],
5+
// `ESLint` requires this to be an object, not an array
6+
global: ESLint ? { hello: true } : ['hello'],
47
},
58
};

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"dependencies": {
2727
"chalk": "^3.0.0",
2828
"cosmiconfig": "^6.0.0",
29-
"create-jest-runner": "^0.6.0"
29+
"create-jest-runner": "^0.6.0",
30+
"dot-prop": "^5.3.0"
3031
},
3132
"devDependencies": {
3233
"@babel/cli": "^7.10.4",

src/runner/__tests__/runESLint.test.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ const runESLintRunnerWithMockedEngine = options => {
1111

1212
executeOnFiles() {
1313
return {
14+
results:
15+
options.cliEngine.errorCount > 0
16+
? [{ errorCount: options.cliEngine.errorCount, warningCount: 0 }]
17+
: [],
1418
errorCount: options.cliEngine.errorCount,
19+
warningCount: 0,
1520
};
1621
}
1722

@@ -25,7 +30,7 @@ const runESLintRunnerWithMockedEngine = options => {
2530
return runESLint({ extraOptions: {}, ...options.runESLint });
2631
};
2732

28-
it('Requires the config setupTestFrameworkScriptFile when specified', () => {
33+
it('Requires the config setupTestFrameworkScriptFile when specified', async () => {
2934
const setupFile = path.join(__dirname, './path/to/setupFile.js');
3035

3136
let setupFileWasLoaded = false;
@@ -37,7 +42,7 @@ it('Requires the config setupTestFrameworkScriptFile when specified', () => {
3742
{ virtual: true },
3843
);
3944

40-
runESLintRunnerWithMockedEngine({
45+
await runESLintRunnerWithMockedEngine({
4146
cliEngine: {
4247
ignoredFiles: ['/path/to/file.test.js'],
4348
errorCount: 0,
@@ -53,8 +58,8 @@ it('Requires the config setupTestFrameworkScriptFile when specified', () => {
5358
expect(setupFileWasLoaded).toBeTruthy();
5459
});
5560

56-
it('Returns "skipped" when the test path is ignored', () => {
57-
const result = runESLintRunnerWithMockedEngine({
61+
it('Returns "skipped" when the test path is ignored', async () => {
62+
const result = await runESLintRunnerWithMockedEngine({
5863
cliEngine: {
5964
ignoredFiles: ['/path/to/file.test.js'],
6065
errorCount: 0,
@@ -73,8 +78,8 @@ it('Returns "skipped" when the test path is ignored', () => {
7378
});
7479
});
7580

76-
it('Returns "passed" when the test passed', () => {
77-
const result = runESLintRunnerWithMockedEngine({
81+
it('Returns "passed" when the test passed', async () => {
82+
const result = await runESLintRunnerWithMockedEngine({
7883
cliEngine: {
7984
ignoredFiles: [],
8085
errorCount: 0,
@@ -92,8 +97,8 @@ it('Returns "passed" when the test passed', () => {
9297
});
9398
});
9499

95-
it('Returns "fail" when the test failed', () => {
96-
const result = runESLintRunnerWithMockedEngine({
100+
it('Returns "fail" when the test failed', async () => {
101+
const result = await runESLintRunnerWithMockedEngine({
97102
cliEngine: {
98103
ignoredFiles: [],
99104
errorCount: 1,

src/runner/runESLint.js

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const { pass, fail, skip } = require('create-jest-runner');
2-
const { CLIEngine } = require('eslint');
2+
const { CLIEngine, ESLint } = require('eslint');
33
const getESLintOptions = require('../utils/getESLintOptions');
44

55
const getComputedFixValue = ({ fix, quiet, fixDryRun }) => {
@@ -9,54 +9,89 @@ const getComputedFixValue = ({ fix, quiet, fixDryRun }) => {
99
return undefined;
1010
};
1111

12+
const ESLintEngine = ESLint || CLIEngine;
13+
1214
let cachedValues;
1315
const getCachedValues = (config, extraOptions) => {
1416
if (!cachedValues) {
15-
const { cliOptions: baseCliOptions } = getESLintOptions(config);
17+
const useEngine = ESLint == null;
18+
const { cliOptions: baseCliOptions } = getESLintOptions(config, !useEngine);
1619
const cliOptions = {
1720
...baseCliOptions,
1821
fix: getComputedFixValue(baseCliOptions),
1922
...extraOptions,
2023
};
21-
const cli = new CLIEngine(cliOptions);
22-
const formatter = cli.getFormatter(cliOptions.format);
2324

24-
cachedValues = { cli, formatter, cliOptions };
25+
// these are not constructor args, so remove them
26+
const { fixDryRun, format, maxWarnings, quiet } = cliOptions;
27+
delete cliOptions.fixDryRun;
28+
delete cliOptions.format;
29+
delete cliOptions.maxWarnings;
30+
delete cliOptions.quiet;
31+
32+
const cli = useEngine ? new CLIEngine(cliOptions) : new ESLint(cliOptions);
33+
34+
cachedValues = {
35+
isPathIgnored: cli.isPathIgnored.bind(cli),
36+
lintFiles: (...args) => {
37+
if (useEngine) {
38+
return cli.executeOnFiles(...args).results;
39+
}
40+
41+
return cli.lintFiles(...args);
42+
},
43+
formatter: async (...args) => {
44+
if (useEngine) {
45+
return cli.getFormatter(format)(...args);
46+
}
47+
48+
const formatter = await cli.loadFormatter(format);
49+
50+
return formatter.format(...args);
51+
},
52+
cliOptions: {
53+
...cliOptions,
54+
fixDryRun,
55+
maxWarnings,
56+
quiet,
57+
},
58+
};
2559
}
2660

2761
return cachedValues;
2862
};
2963

30-
const runESLint = ({ testPath, config, extraOptions }) => {
64+
const runESLint = async ({ testPath, config, extraOptions }) => {
3165
const start = Date.now();
3266

3367
if (config.setupTestFrameworkScriptFile) {
3468
// eslint-disable-next-line import/no-dynamic-require,global-require
3569
require(config.setupTestFrameworkScriptFile);
3670
}
3771

38-
const { cli, formatter, cliOptions } = getCachedValues(config, extraOptions);
72+
const { isPathIgnored, lintFiles, formatter, cliOptions } = getCachedValues(
73+
config,
74+
extraOptions,
75+
);
3976

40-
if (cli.isPathIgnored(testPath)) {
77+
if (await isPathIgnored(testPath)) {
4178
const end = Date.now();
4279
return skip({ start, end, test: { path: testPath, title: 'ESLint' } });
4380
}
4481

45-
const report = cli.executeOnFiles([testPath]);
82+
const report = await lintFiles([testPath]);
4683

4784
if (cliOptions.fix && !cliOptions.fixDryRun) {
48-
CLIEngine.outputFixes(report);
85+
await ESLintEngine.outputFixes(report);
4986
}
5087

5188
const end = Date.now();
5289

53-
const message = formatter(
54-
cliOptions.quiet
55-
? CLIEngine.getErrorResults(report.results)
56-
: report.results,
90+
const message = await formatter(
91+
cliOptions.quiet ? ESLintEngine.getErrorResults(report) : report,
5792
);
5893

59-
if (report.errorCount > 0) {
94+
if (report[0]?.errorCount > 0) {
6095
return fail({
6196
start,
6297
end,
@@ -65,7 +100,8 @@ const runESLint = ({ testPath, config, extraOptions }) => {
65100
}
66101

67102
const tooManyWarnings =
68-
cliOptions.maxWarnings >= 0 && report.warningCount > cliOptions.maxWarnings;
103+
cliOptions.maxWarnings >= 0 &&
104+
report[0]?.warningCount > cliOptions.maxWarnings;
69105
if (tooManyWarnings) {
70106
return fail({
71107
start,
@@ -84,7 +120,7 @@ const runESLint = ({ testPath, config, extraOptions }) => {
84120
test: { path: testPath, title: 'ESLint' },
85121
});
86122

87-
if (!cliOptions.quiet && report.warningCount > 0) {
123+
if (!cliOptions.quiet && report[0]?.warningCount > 0) {
88124
result.console = [{ message, origin: '', type: 'warn' }];
89125
}
90126

src/utils/__tests__/normalizeConfig.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const normalizeConfig = require('../normalizeConfig');
33
const normalizeCLIOptions = cliOptions =>
44
normalizeConfig({ cliOptions }).cliOptions;
55

6-
it('ignores unkown options', () => {
6+
it('ignores unknown options', () => {
77
expect(normalizeCLIOptions({ other: true })).not.toMatchObject({
88
other: true,
99
});

src/utils/getESLintOptions.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ const normalizeConfig = require('./normalizeConfig');
33

44
const explorer = cosmiconfigSync('jest-runner-eslint');
55

6-
const getESLintOptions = config => {
6+
const getESLintOptions = (config, newApi) => {
77
const result = explorer.search(config.rootDir);
88

99
if (result) {
10-
return normalizeConfig(result.config);
10+
return normalizeConfig(result.config, newApi);
1111
}
1212

13-
return normalizeConfig({});
13+
return normalizeConfig({}, newApi);
1414
};
1515

1616
module.exports = getESLintOptions;

src/utils/normalizeConfig.js

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const dotProp = require('dot-prop');
2+
13
const identity = v => v;
24
const negate = v => !v;
35
const asArray = v => (typeof v === 'string' ? [v] : v);
@@ -12,7 +14,7 @@ const asInt = v => {
1214
return int;
1315
};
1416

15-
const BASE_CONFIG = {
17+
const OLD_BASE_CONFIG = {
1618
cache: {
1719
default: false,
1820
},
@@ -40,7 +42,7 @@ const BASE_CONFIG = {
4042
default: false,
4143
},
4244
format: {
43-
default: null,
45+
default: undefined,
4446
},
4547
global: {
4648
name: 'globals',
@@ -103,28 +105,70 @@ const BASE_CONFIG = {
103105
},
104106
};
105107

108+
const BASE_CONFIG = {
109+
...OLD_BASE_CONFIG,
110+
config: {
111+
name: 'overrideConfigFile',
112+
default: null,
113+
},
114+
env: {
115+
name: 'overrideConfig.env',
116+
default: {},
117+
},
118+
global: {
119+
name: 'overrideConfig.globals',
120+
default: {},
121+
},
122+
ignorePattern: {
123+
name: 'overrideConfig.ignorePatterns',
124+
default: [],
125+
transform: asArray,
126+
},
127+
parser: {
128+
name: 'overrideConfig.parser',
129+
default: null,
130+
},
131+
parserOptions: {
132+
name: 'overrideConfig.parserOptions',
133+
default: {},
134+
},
135+
plugin: {
136+
name: 'overrideConfig.plugins',
137+
default: [],
138+
transform: asArray,
139+
},
140+
reportUnusedDisableDirectives: {
141+
default: null,
142+
},
143+
rules: {
144+
name: 'overrideConfig.rules',
145+
default: {},
146+
},
147+
};
148+
106149
/* eslint-disable no-param-reassign */
107-
const normalizeCliOptions = rawConfig =>
108-
Object.keys(BASE_CONFIG).reduce((config, key) => {
150+
const normalizeCliOptions = (rawConfig, newApi) => {
151+
const configToUse = newApi ? BASE_CONFIG : OLD_BASE_CONFIG;
152+
return Object.keys(configToUse).reduce((config, key) => {
109153
const {
110154
name = key,
111155
transform = identity,
112156
default: defaultValue,
113-
} = BASE_CONFIG[key];
157+
} = configToUse[key];
114158

115159
const value = rawConfig[key] !== undefined ? rawConfig[key] : defaultValue;
116160

117-
return {
118-
...config,
119-
[name]: transform(value),
120-
};
161+
dotProp.set(config, name, transform(value));
162+
163+
return config;
121164
}, {});
165+
};
122166
/* eslint-enable no-param-reassign */
123167

124-
const normalizeConfig = config => {
168+
const normalizeConfig = (config, newApi) => {
125169
return {
126170
...config,
127-
cliOptions: normalizeCliOptions(config.cliOptions || {}),
171+
cliOptions: normalizeCliOptions(config.cliOptions || {}, newApi),
128172
};
129173
};
130174

yarn.lock

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2036,6 +2036,13 @@ domexception@^2.0.1:
20362036
dependencies:
20372037
webidl-conversions "^5.0.0"
20382038

2039+
dot-prop@^5.3.0:
2040+
version "5.3.0"
2041+
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
2042+
integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==
2043+
dependencies:
2044+
is-obj "^2.0.0"
2045+
20392046
electron-to-chromium@^1.3.830:
20402047
version "1.3.842"
20412048
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.842.tgz#641e414012dded277468892c0156cb01984f4f6f"
@@ -2956,6 +2963,11 @@ is-number@^7.0.0:
29562963
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
29572964
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
29582965

2966+
is-obj@^2.0.0:
2967+
version "2.0.0"
2968+
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
2969+
integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
2970+
29592971
is-plain-object@^2.0.3, is-plain-object@^2.0.4:
29602972
version "2.0.4"
29612973
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"

0 commit comments

Comments
 (0)