Skip to content

Commit 551f2fb

Browse files
authored
feat: add support for eslint 8 (#666)
Closes: #664
1 parent e9a4e8d commit 551f2fb

13 files changed

+14292
-4325
lines changed

src/eslint-reporter/reporter/EsLintReporter.ts

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,36 @@
11
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2-
import { CLIEngine, LintReport, LintResult } from '../types/eslint';
2+
import { CLIEngine, ESLintOrCLIEngine, LintReport, LintResult } from '../types/eslint';
33
import { createIssuesFromEsLintResults } from '../issue/EsLintIssueFactory';
44
import { EsLintReporterConfiguration } from '../EsLintReporterConfiguration';
55
import { Reporter } from '../../reporter';
6-
import { normalize } from 'path';
6+
import path from 'path';
7+
import fs from 'fs-extra';
78
import minimatch from 'minimatch';
89
import glob from 'glob';
910

11+
const isOldCLIEngine = (eslint: ESLintOrCLIEngine): eslint is CLIEngine =>
12+
(eslint as CLIEngine).resolveFileGlobPatterns !== undefined;
13+
1014
function createEsLintReporter(configuration: EsLintReporterConfiguration): Reporter {
1115
// eslint-disable-next-line @typescript-eslint/no-var-requires
12-
const { CLIEngine } = require('eslint');
13-
const engine: CLIEngine = new CLIEngine(configuration.options);
16+
const { CLIEngine, ESLint } = require('eslint');
17+
18+
const eslint: ESLintOrCLIEngine = ESLint
19+
? new ESLint(configuration.options)
20+
: new CLIEngine(configuration.options);
1421

1522
let isInitialRun = true;
1623
let isInitialGetFiles = true;
1724

1825
const lintResults = new Map<string, LintResult>();
19-
const includedGlobPatterns = engine.resolveFileGlobPatterns(configuration.files);
26+
const includedGlobPatterns = resolveFileGlobPatterns(configuration.files);
2027
const includedFiles = new Set<string>();
2128

22-
function isFileIncluded(path: string) {
29+
async function isFileIncluded(path: string): Promise<boolean> {
2330
return (
2431
!path.includes('node_modules') &&
2532
includedGlobPatterns.some((pattern) => minimatch(path, pattern)) &&
26-
!engine.isPathIgnored(path)
33+
!(await eslint.isPathIgnored(path))
2734
);
2835
}
2936

@@ -49,7 +56,7 @@ function createEsLintReporter(configuration: EsLintReporterConfiguration): Repor
4956

5057
for (const resolvedGlob of resolvedGlobs) {
5158
for (const resolvedFile of resolvedGlob) {
52-
if (isFileIncluded(resolvedFile)) {
59+
if (await isFileIncluded(resolvedFile)) {
5360
includedFiles.add(resolvedFile);
5461
}
5562
}
@@ -67,12 +74,43 @@ function createEsLintReporter(configuration: EsLintReporterConfiguration): Repor
6774
return configuration.options.extensions || [];
6875
}
6976

77+
// Copied from the eslint 6 implementation, as it's not available in eslint 8
78+
function resolveFileGlobPatterns(globPatterns: string[]) {
79+
if (configuration.options.globInputPaths === false) {
80+
return globPatterns.filter(Boolean);
81+
}
82+
83+
const extensions = getExtensions().map((ext) => ext.replace(/^\./u, ''));
84+
const dirSuffix = `/**/*.{${extensions.join(',')}}`;
85+
86+
return globPatterns.filter(Boolean).map((globPattern) => {
87+
const resolvedPath = path.resolve(configuration.options.cwd || '', globPattern);
88+
const newPath = directoryExists(resolvedPath)
89+
? globPattern.replace(/[/\\]$/u, '') + dirSuffix
90+
: globPattern;
91+
92+
return path.normalize(newPath).replace(/\\/gu, '/');
93+
});
94+
}
95+
96+
// Copied from the eslint 6 implementation, as it's not available in eslint 8
97+
function directoryExists(resolvedPath: string) {
98+
try {
99+
return fs.statSync(resolvedPath).isDirectory();
100+
} catch (error) {
101+
if (error && error.code === 'ENOENT') {
102+
return false;
103+
}
104+
throw error;
105+
}
106+
}
107+
70108
return {
71109
getReport: async ({ changedFiles = [], deletedFiles = [] }) => {
72110
return {
73111
async getDependencies() {
74112
for (const changedFile of changedFiles) {
75-
if (isFileIncluded(changedFile)) {
113+
if (await isFileIncluded(changedFile)) {
76114
includedFiles.add(changedFile);
77115
}
78116
}
@@ -81,8 +119,8 @@ function createEsLintReporter(configuration: EsLintReporterConfiguration): Repor
81119
}
82120

83121
return {
84-
files: (await getFiles()).map((file) => normalize(file)),
85-
dirs: getDirs().map((dir) => normalize(dir)),
122+
files: (await getFiles()).map((file) => path.normalize(file)),
123+
dirs: getDirs().map((dir) => path.normalize(dir)),
86124
excluded: [],
87125
extensions: getExtensions(),
88126
};
@@ -100,23 +138,38 @@ function createEsLintReporter(configuration: EsLintReporterConfiguration): Repor
100138
const lintReports: LintReport[] = [];
101139

102140
if (isInitialRun) {
103-
lintReports.push(engine.executeOnFiles(includedGlobPatterns));
141+
const lintReport: LintReport = await (isOldCLIEngine(eslint)
142+
? Promise.resolve(eslint.executeOnFiles(includedGlobPatterns))
143+
: eslint.lintFiles(includedGlobPatterns).then((results) => ({ results })));
144+
lintReports.push(lintReport);
104145
isInitialRun = false;
105146
} else {
106147
// we need to take care to not lint files that are not included by the configuration.
107148
// the eslint engine will not exclude them automatically
108-
const changedAndIncludedFiles = changedFiles.filter((changedFile) =>
109-
isFileIncluded(changedFile)
110-
);
149+
const changedAndIncludedFiles: string[] = [];
150+
for (const changedFile of changedFiles) {
151+
if (await isFileIncluded(changedFile)) {
152+
changedAndIncludedFiles.push(changedFile);
153+
}
154+
}
111155

112156
if (changedAndIncludedFiles.length) {
113-
lintReports.push(engine.executeOnFiles(changedAndIncludedFiles));
157+
const lintReport: LintReport = await (isOldCLIEngine(eslint)
158+
? Promise.resolve(eslint.executeOnFiles(changedAndIncludedFiles))
159+
: eslint.lintFiles(changedAndIncludedFiles).then((results) => ({ results })));
160+
lintReports.push(lintReport);
114161
}
115162
}
116163

117164
// output fixes if `fix` option is provided
118165
if (configuration.options.fix) {
119-
await Promise.all(lintReports.map((lintReport) => CLIEngine.outputFixes(lintReport)));
166+
await Promise.all(
167+
lintReports.map((lintReport) =>
168+
isOldCLIEngine(eslint)
169+
? CLIEngine.outputFixes(lintReport)
170+
: ESLint.outputFixes(lintReport.results)
171+
)
172+
);
120173
}
121174

122175
// store results

src/eslint-reporter/types/eslint.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ export interface CLIEngine {
3030
resolveFileGlobPatterns(filesPatterns: string[]): string[];
3131
isPathIgnored(filePath: string): boolean;
3232
}
33+
export interface ESLint {
34+
version: string;
35+
lintFiles(filesPatterns: string[]): Promise<LintResult[]>;
36+
isPathIgnored(filePath: string): Promise<boolean>;
37+
}
38+
39+
export type ESLintOrCLIEngine = CLIEngine | ESLint;
3340

3441
export interface CLIEngineOptions {
3542
cwd?: string;

test/e2e/EsLint.spec.ts

Lines changed: 87 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { join } from 'path';
2+
import process from 'process';
23
import { readFixture } from './sandbox/Fixture';
34
import { Sandbox, createSandbox } from './sandbox/Sandbox';
45
import {
@@ -8,6 +9,8 @@ import {
89
} from './sandbox/WebpackDevServerDriver';
910
import { FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION } from './sandbox/Plugin';
1011

12+
const ignored = process.version.startsWith('v10');
13+
1114
describe('EsLint', () => {
1215
let sandbox: Sandbox;
1316

@@ -24,17 +27,27 @@ describe('EsLint', () => {
2427
});
2528

2629
it.each([
27-
{ async: false, webpack: '4.0.0', absolute: false },
28-
{ async: true, webpack: '^4.0.0', absolute: true },
29-
{ async: false, webpack: '^5.0.0', absolute: true },
30-
{ async: true, webpack: '^5.0.0', absolute: false },
31-
])('reports lint error for %p', async ({ async, webpack, absolute }) => {
30+
{ async: false, webpack: '4.0.0', eslint: '^6.0.0', absolute: false },
31+
{ async: true, webpack: '^4.0.0', eslint: '^7.0.0', absolute: true },
32+
{ async: false, webpack: '^5.0.0', eslint: '^7.0.0', absolute: true },
33+
{
34+
async: true,
35+
webpack: '^5.0.0',
36+
eslint: '^8.0.0',
37+
absolute: false,
38+
},
39+
])('reports lint error for %p', async ({ async, webpack, eslint, absolute }) => {
40+
if (ignored) {
41+
console.warn('Ignoring test - incompatible node version');
42+
return;
43+
}
3244
await sandbox.load([
3345
await readFixture(join(__dirname, 'fixtures/environment/eslint-basic.fixture'), {
3446
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION: JSON.stringify(
3547
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION
3648
),
3749
TS_LOADER_VERSION: JSON.stringify('^5.0.0'),
50+
ESLINT_VERSION: JSON.stringify(eslint),
3851
TYPESCRIPT_VERSION: JSON.stringify('~3.8.0'),
3952
WEBPACK_VERSION: JSON.stringify(webpack),
4053
WEBPACK_CLI_VERSION: JSON.stringify(WEBPACK_CLI_VERSION),
@@ -61,7 +74,7 @@ describe('EsLint', () => {
6174
'WARNING in src/authenticate.ts:14:34',
6275
'@typescript-eslint/no-explicit-any: Unexpected any. Specify a different type.',
6376
' 12 | }',
64-
' 13 | ',
77+
' 13 |',
6578
' > 14 | async function logout(): Promise<any> {',
6679
' | ^^^',
6780
' 15 | const response = await fetch(',
@@ -76,7 +89,7 @@ describe('EsLint', () => {
7689
" > 31 | loginForm.addEventListener('submit', async event => {",
7790
' | ^^^^^',
7891
' 32 | const user = await login(email, password);',
79-
' 33 | ',
92+
' 33 |',
8093
" 34 | if (user.role === 'admin') {",
8194
].join('\n'),
8295
]);
@@ -127,34 +140,39 @@ describe('EsLint', () => {
127140
'WARNING in src/model/User.ts:11:5',
128141
"@typescript-eslint/no-unused-vars: 'temporary' is defined but never used.",
129142
' 9 | }',
130-
' 10 | ',
143+
' 10 |',
131144
' > 11 | let temporary: any;',
132145
' | ^^^^^^^^^^^^^^',
133-
' 12 | ',
134-
' 13 | ',
146+
' 12 |',
147+
' 13 |',
135148
' 14 | function getUserName(user: User): string {',
136149
].join('\n'),
137150
[
138151
'WARNING in src/model/User.ts:11:16',
139152
'@typescript-eslint/no-explicit-any: Unexpected any. Specify a different type.',
140153
' 9 | }',
141-
' 10 | ',
154+
' 10 |',
142155
' > 11 | let temporary: any;',
143156
' | ^^^',
144-
' 12 | ',
145-
' 13 | ',
157+
' 12 |',
158+
' 13 |',
146159
' 14 | function getUserName(user: User): string {',
147160
].join('\n'),
148161
]);
149162
});
150163

151164
it('adds files dependencies to webpack', async () => {
165+
if (ignored) {
166+
console.warn('Ignoring test - incompatible node version');
167+
return;
168+
}
152169
await sandbox.load([
153170
await readFixture(join(__dirname, 'fixtures/environment/eslint-basic.fixture'), {
154171
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION: JSON.stringify(
155172
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION
156173
),
157174
TS_LOADER_VERSION: JSON.stringify('^5.0.0'),
175+
ESLINT_VERSION: JSON.stringify('~6.8.0'),
158176
TYPESCRIPT_VERSION: JSON.stringify('~3.8.0'),
159177
WEBPACK_VERSION: JSON.stringify('^4.0.0'),
160178
WEBPACK_CLI_VERSION: JSON.stringify(WEBPACK_CLI_VERSION),
@@ -210,54 +228,65 @@ describe('EsLint', () => {
210228
await driver.waitForNoErrors();
211229
});
212230

213-
it('fixes errors with `fix: true` option', async () => {
214-
await sandbox.load([
215-
await readFixture(join(__dirname, 'fixtures/environment/eslint-basic.fixture'), {
216-
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION: JSON.stringify(
217-
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION
218-
),
219-
TS_LOADER_VERSION: JSON.stringify('^5.0.0'),
220-
TYPESCRIPT_VERSION: JSON.stringify('~3.8.0'),
221-
WEBPACK_VERSION: JSON.stringify('^4.0.0'),
222-
WEBPACK_CLI_VERSION: JSON.stringify(WEBPACK_CLI_VERSION),
223-
WEBPACK_DEV_SERVER_VERSION: JSON.stringify(WEBPACK_DEV_SERVER_VERSION),
224-
ASYNC: JSON.stringify(false),
225-
}),
226-
await readFixture(join(__dirname, 'fixtures/implementation/typescript-basic.fixture')),
227-
]);
228-
229-
// fix initial issues
230-
await sandbox.patch(
231-
'src/authenticate.ts',
232-
'async function logout(): Promise<any> {',
233-
'async function logout(): Promise<unknown> {'
234-
);
235-
await sandbox.patch(
236-
'src/index.ts',
237-
"loginForm.addEventListener('submit', async event => {",
238-
"loginForm.addEventListener('submit', async () => {"
239-
);
231+
it.each([{ eslint: '^6.0.0' }, { eslint: '^7.0.0' }, { eslint: '^8.0.0' }])(
232+
'fixes errors with `fix: true` option for %p',
233+
async ({ eslint }) => {
234+
if (ignored) {
235+
console.warn('Ignoring test - incompatible node version');
236+
return;
237+
}
238+
await sandbox.load([
239+
await readFixture(join(__dirname, 'fixtures/environment/eslint-basic.fixture'), {
240+
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION: JSON.stringify(
241+
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION
242+
),
243+
TS_LOADER_VERSION: JSON.stringify('^5.0.0'),
244+
ESLINT_VERSION: JSON.stringify(eslint),
245+
TYPESCRIPT_VERSION: JSON.stringify('~3.8.0'),
246+
WEBPACK_VERSION: JSON.stringify('^4.0.0'),
247+
WEBPACK_CLI_VERSION: JSON.stringify(WEBPACK_CLI_VERSION),
248+
WEBPACK_DEV_SERVER_VERSION: JSON.stringify(WEBPACK_DEV_SERVER_VERSION),
249+
ASYNC: JSON.stringify(false),
250+
}),
251+
await readFixture(join(__dirname, 'fixtures/implementation/typescript-basic.fixture')),
252+
]);
253+
254+
// fix initial issues
255+
await sandbox.patch(
256+
'src/authenticate.ts',
257+
'async function logout(): Promise<any> {',
258+
'async function logout(): Promise<unknown> {'
259+
);
260+
await sandbox.patch(
261+
'src/index.ts',
262+
"loginForm.addEventListener('submit', async event => {",
263+
"loginForm.addEventListener('submit', async () => {"
264+
);
240265

241-
// set fix option for the eslint
242-
await sandbox.write(
243-
'fork-ts-checker.config.js',
244-
'module.exports = { eslint: { enabled: true, options: { fix: true } } };'
245-
);
266+
// set fix option for the eslint
267+
await sandbox.write(
268+
'fork-ts-checker.config.js',
269+
'module.exports = { eslint: { enabled: true, options: { fix: true } } };'
270+
);
246271

247-
// add fixable issue
248-
await sandbox.patch(
249-
'src/authenticate.ts',
250-
'const response = await fetch(',
251-
'let response = await fetch('
252-
);
272+
// add fixable issue
273+
await sandbox.patch(
274+
'src/authenticate.ts',
275+
'const response = await fetch(',
276+
'let response = await fetch('
277+
);
253278

254-
const driver = createWebpackDevServerDriver(sandbox.spawn('npm run webpack-dev-server'), false);
279+
const driver = createWebpackDevServerDriver(
280+
sandbox.spawn('npm run webpack-dev-server'),
281+
false
282+
);
255283

256-
// it should be automatically fixed
257-
await driver.waitForNoErrors();
284+
// it should be automatically fixed
285+
await driver.waitForNoErrors();
258286

259-
// check if issue has been fixed
260-
const content = await sandbox.read('src/authenticate.ts');
261-
expect(content).not.toContain('let response = await fetch(');
262-
});
287+
// check if issue has been fixed
288+
const content = await sandbox.read('src/authenticate.ts');
289+
expect(content).not.toContain('let response = await fetch(');
290+
}
291+
);
263292
});

0 commit comments

Comments
 (0)