Skip to content

Commit 948078f

Browse files
committed
feat: add support for eslint 8
Closes: #664
1 parent 34ebcd8 commit 948078f

13 files changed

+14243
-4281
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: 38 additions & 14 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,28 @@ 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, ignored },
31+
{ async: true, webpack: '^4.0.0', eslint: '^7.0.0', absolute: true, ignored },
32+
{ async: false, webpack: '^5.0.0', eslint: '^7.0.0', absolute: true, ignored },
33+
{
34+
async: true,
35+
webpack: '^5.0.0',
36+
eslint: '^8.0.0',
37+
absolute: false,
38+
ignored,
39+
},
40+
])('reports lint error for %p', async ({ async, webpack, eslint, absolute, ignored }) => {
41+
if (ignored) {
42+
console.warn('Ignoring test - incompatible node version');
43+
return;
44+
}
3245
await sandbox.load([
3346
await readFixture(join(__dirname, 'fixtures/environment/eslint-basic.fixture'), {
3447
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION: JSON.stringify(
3548
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION
3649
),
3750
TS_LOADER_VERSION: JSON.stringify('^5.0.0'),
51+
ESLINT_VERSION: JSON.stringify(eslint),
3852
TYPESCRIPT_VERSION: JSON.stringify('~3.8.0'),
3953
WEBPACK_VERSION: JSON.stringify(webpack),
4054
WEBPACK_CLI_VERSION: JSON.stringify(WEBPACK_CLI_VERSION),
@@ -61,7 +75,7 @@ describe('EsLint', () => {
6175
'WARNING in src/authenticate.ts:14:34',
6276
'@typescript-eslint/no-explicit-any: Unexpected any. Specify a different type.',
6377
' 12 | }',
64-
' 13 | ',
78+
' 13 |',
6579
' > 14 | async function logout(): Promise<any> {',
6680
' | ^^^',
6781
' 15 | const response = await fetch(',
@@ -76,7 +90,7 @@ describe('EsLint', () => {
7690
" > 31 | loginForm.addEventListener('submit', async event => {",
7791
' | ^^^^^',
7892
' 32 | const user = await login(email, password);',
79-
' 33 | ',
93+
' 33 |',
8094
" 34 | if (user.role === 'admin') {",
8195
].join('\n'),
8296
]);
@@ -127,22 +141,22 @@ describe('EsLint', () => {
127141
'WARNING in src/model/User.ts:11:5',
128142
"@typescript-eslint/no-unused-vars: 'temporary' is defined but never used.",
129143
' 9 | }',
130-
' 10 | ',
144+
' 10 |',
131145
' > 11 | let temporary: any;',
132146
' | ^^^^^^^^^^^^^^',
133-
' 12 | ',
134-
' 13 | ',
147+
' 12 |',
148+
' 13 |',
135149
' 14 | function getUserName(user: User): string {',
136150
].join('\n'),
137151
[
138152
'WARNING in src/model/User.ts:11:16',
139153
'@typescript-eslint/no-explicit-any: Unexpected any. Specify a different type.',
140154
' 9 | }',
141-
' 10 | ',
155+
' 10 |',
142156
' > 11 | let temporary: any;',
143157
' | ^^^',
144-
' 12 | ',
145-
' 13 | ',
158+
' 12 |',
159+
' 13 |',
146160
' 14 | function getUserName(user: User): string {',
147161
].join('\n'),
148162
]);
@@ -155,6 +169,7 @@ describe('EsLint', () => {
155169
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION
156170
),
157171
TS_LOADER_VERSION: JSON.stringify('^5.0.0'),
172+
ESLINT_VERSION: JSON.stringify('~6.8.0'),
158173
TYPESCRIPT_VERSION: JSON.stringify('~3.8.0'),
159174
WEBPACK_VERSION: JSON.stringify('^4.0.0'),
160175
WEBPACK_CLI_VERSION: JSON.stringify(WEBPACK_CLI_VERSION),
@@ -210,13 +225,22 @@ describe('EsLint', () => {
210225
await driver.waitForNoErrors();
211226
});
212227

213-
it('fixes errors with `fix: true` option', async () => {
228+
it.each([
229+
{ eslint: '^6.0.0', ignored },
230+
{ eslint: '^7.0.0', ignored },
231+
{ eslint: '^8.0.0', ignored },
232+
])('fixes errors with `fix: true` option for %p', async ({ eslint, ignored }) => {
233+
if (ignored) {
234+
console.warn('Ignoring test - incompatible node version');
235+
return;
236+
}
214237
await sandbox.load([
215238
await readFixture(join(__dirname, 'fixtures/environment/eslint-basic.fixture'), {
216239
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION: JSON.stringify(
217240
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION
218241
),
219242
TS_LOADER_VERSION: JSON.stringify('^5.0.0'),
243+
ESLINT_VERSION: JSON.stringify(eslint),
220244
TYPESCRIPT_VERSION: JSON.stringify('~3.8.0'),
221245
WEBPACK_VERSION: JSON.stringify('^4.0.0'),
222246
WEBPACK_CLI_VERSION: JSON.stringify(WEBPACK_CLI_VERSION),

test/e2e/fixtures/environment/eslint-basic.fixture

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
},
1010
"devDependencies": {
1111
"@types/eslint": "^6.8.0",
12-
"@typescript-eslint/eslint-plugin": "^2.27.0",
13-
"@typescript-eslint/parser": "^2.27.0",
12+
"@typescript-eslint/eslint-plugin": "^5.0.0",
13+
"@typescript-eslint/parser": "^5.0.0",
1414
"css-loader": "^3.5.0",
15-
"eslint": "^6.8.0",
15+
"eslint": ${ESLINT_VERSION},
1616
"fork-ts-checker-webpack-plugin": ${FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION},
1717
"style-loader": "^1.2.0",
1818
"ts-loader": ${TS_LOADER_VERSION},
@@ -44,11 +44,11 @@
4444
/// .eslintrc.js
4545
module.exports = {
4646
parser: '@typescript-eslint/parser',
47-
parserOptions: {
48-
ecmaVersion: 2018,
49-
sourceType: 'module'
50-
},
51-
extends: ['plugin:@typescript-eslint/recommended']
47+
plugins: ["@typescript-eslint"],
48+
extends: ["plugin:@typescript-eslint/recommended"],
49+
rules: {
50+
'@typescript-eslint/no-loss-of-precision': 'off'
51+
}
5252
};
5353

5454
/// webpack.config.js

test/e2e/jest.setup.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
jest.retryTimes(5);
1+
// jest.retryTimes(5);

0 commit comments

Comments
 (0)