Skip to content

Commit 58ef473

Browse files
authored
fix: resolve eslint dependencies for proper watching (#602)
Closes: #580
1 parent 13c276f commit 58ef473

File tree

5 files changed

+4812
-31
lines changed

5 files changed

+4812
-31
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"cosmiconfig": "^6.0.0",
6868
"deepmerge": "^4.2.2",
6969
"fs-extra": "^9.0.0",
70+
"glob": "^7.1.6",
7071
"memfs": "^3.1.2",
7172
"minimatch": "^3.0.4",
7273
"schema-utils": "2.7.0",

src/eslint-reporter/reporter/EsLintReporter.ts

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,54 +3,108 @@ import { CLIEngine, 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';
67
import minimatch from 'minimatch';
8+
import glob from 'glob';
79

810
function createEsLintReporter(configuration: EsLintReporterConfiguration): Reporter {
911
// eslint-disable-next-line @typescript-eslint/no-var-requires
1012
const { CLIEngine } = require('eslint');
1113
const engine: CLIEngine = new CLIEngine(configuration.options);
1214

1315
let isInitialRun = true;
16+
let isInitialGetFiles = true;
17+
1418
const lintResults = new Map<string, LintResult>();
15-
const includedFilesPatterns = engine.resolveFileGlobPatterns(configuration.files);
19+
const includedGlobPatterns = engine.resolveFileGlobPatterns(configuration.files);
20+
const includedFiles = new Set<string>();
21+
22+
function isFileIncluded(path: string) {
23+
return (
24+
includedGlobPatterns.some((pattern) => minimatch(path, pattern)) &&
25+
!engine.isPathIgnored(path)
26+
);
27+
}
28+
29+
async function getFiles() {
30+
if (isInitialGetFiles) {
31+
isInitialGetFiles = false;
32+
33+
const resolvedGlobs = await Promise.all(
34+
includedGlobPatterns.map(
35+
(globPattern) =>
36+
new Promise<string[]>((resolve) => {
37+
glob(globPattern, (error, resolvedFiles) => {
38+
if (error) {
39+
// fail silently
40+
resolve([]);
41+
} else {
42+
resolve(resolvedFiles || []);
43+
}
44+
});
45+
})
46+
)
47+
);
48+
49+
for (const resolvedGlob of resolvedGlobs) {
50+
for (const resolvedFile of resolvedGlob) {
51+
if (isFileIncluded(resolvedFile)) {
52+
includedFiles.add(resolvedFile);
53+
}
54+
}
55+
}
56+
}
57+
58+
return Array.from(includedFiles);
59+
}
60+
61+
function getDirs() {
62+
return includedGlobPatterns || [];
63+
}
64+
65+
function getExtensions() {
66+
return configuration.options.extensions || [];
67+
}
1668

1769
return {
1870
getReport: async ({ changedFiles = [], deletedFiles = [] }) => {
1971
return {
2072
async getDependencies() {
73+
for (const changedFile of changedFiles) {
74+
if (isFileIncluded(changedFile)) {
75+
includedFiles.add(changedFile);
76+
}
77+
}
78+
for (const deletedFile of deletedFiles) {
79+
includedFiles.delete(deletedFile);
80+
}
81+
2182
return {
22-
files: [],
23-
dirs: [],
24-
extensions: [],
83+
files: (await getFiles()).map((file) => normalize(file)),
84+
dirs: getDirs().map((dir) => normalize(dir)),
85+
extensions: getExtensions(),
2586
};
2687
},
2788
async getIssues() {
2889
// cleanup old results
29-
changedFiles.forEach((changedFile) => {
90+
for (const changedFile of changedFiles) {
3091
lintResults.delete(changedFile);
31-
});
32-
deletedFiles.forEach((removedFile) => {
33-
lintResults.delete(removedFile);
34-
});
92+
}
93+
for (const deletedFile of deletedFiles) {
94+
lintResults.delete(deletedFile);
95+
}
3596

3697
// get reports
3798
const lintReports: LintReport[] = [];
3899

39100
if (isInitialRun) {
40-
lintReports.push(engine.executeOnFiles(includedFilesPatterns));
101+
lintReports.push(engine.executeOnFiles(includedGlobPatterns));
41102
isInitialRun = false;
42103
} else {
43104
// we need to take care to not lint files that are not included by the configuration.
44105
// the eslint engine will not exclude them automatically
45-
const changedAndIncludedFiles = changedFiles.filter(
46-
(changedFile) =>
47-
includedFilesPatterns.some((includedFilesPattern) =>
48-
minimatch(changedFile, includedFilesPattern)
49-
) &&
50-
(configuration.options.extensions || []).some((extension) =>
51-
changedFile.endsWith(extension)
52-
) &&
53-
!engine.isPathIgnored(changedFile)
106+
const changedAndIncludedFiles = changedFiles.filter((changedFile) =>
107+
isFileIncluded(changedFile)
54108
);
55109

56110
if (changedAndIncludedFiles.length) {
@@ -64,11 +118,11 @@ function createEsLintReporter(configuration: EsLintReporterConfiguration): Repor
64118
}
65119

66120
// store results
67-
lintReports.forEach((lintReport) => {
68-
lintReport.results.forEach((lintResult) => {
121+
for (const lintReport of lintReports) {
122+
for (const lintResult of lintReport.results) {
69123
lintResults.set(lintResult.filePath, lintResult);
70-
});
71-
});
124+
}
125+
}
72126

73127
// get actual list of previous and current reports
74128
const results = Array.from(lintResults.values());

test/e2e/EsLint.spec.ts

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,15 @@ describe('EsLint', () => {
4848
// test case for providing absolute path to files
4949
await sandbox.patch(
5050
'webpack.config.js',
51-
"files: './src/**/*'",
52-
"files: path.resolve(__dirname, './src/**/*')"
51+
"files: './src/**/*.{ts,tsx,js,jsx}'",
52+
"files: path.resolve(__dirname, './src/**/*.{ts,tsx,js,jsx}')"
5353
);
5454
}
5555

5656
const driver = createWebpackDevServerDriver(sandbox.spawn('npm run webpack-dev-server'), async);
57-
let errors: string[];
5857

5958
// first compilation contains 2 warnings
60-
errors = await driver.waitForErrors();
61-
expect(errors).toEqual([
59+
expect(await driver.waitForErrors()).toEqual([
6260
[
6361
'WARNING in src/authenticate.ts:14:34',
6462
'@typescript-eslint/no-explicit-any: Unexpected any. Specify a different type.',
@@ -124,8 +122,7 @@ describe('EsLint', () => {
124122
[' lastName?: string;', '}', '', 'let temporary: any;', ''].join('\n')
125123
);
126124

127-
errors = await driver.waitForErrors();
128-
expect(errors).toEqual([
125+
expect(await driver.waitForErrors()).toEqual([
129126
[
130127
'WARNING in src/model/User.ts:11:5',
131128
"@typescript-eslint/no-unused-vars: 'temporary' is defined but never used.",
@@ -151,6 +148,68 @@ describe('EsLint', () => {
151148
]);
152149
});
153150

151+
it('adds files dependencies to webpack', async () => {
152+
await sandbox.load([
153+
await readFixture(join(__dirname, 'fixtures/environment/eslint-basic.fixture'), {
154+
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION: JSON.stringify(
155+
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION
156+
),
157+
TS_LOADER_VERSION: JSON.stringify('^5.0.0'),
158+
TYPESCRIPT_VERSION: JSON.stringify('~3.8.0'),
159+
WEBPACK_VERSION: JSON.stringify('^4.0.0'),
160+
WEBPACK_CLI_VERSION: JSON.stringify(WEBPACK_CLI_VERSION),
161+
WEBPACK_DEV_SERVER_VERSION: JSON.stringify(WEBPACK_DEV_SERVER_VERSION),
162+
ASYNC: JSON.stringify(false),
163+
}),
164+
await readFixture(join(__dirname, 'fixtures/implementation/typescript-basic.fixture')),
165+
]);
166+
167+
// update configuration
168+
await sandbox.patch(
169+
'webpack.config.js',
170+
"files: './src/**/*.{ts,tsx,js,jsx}'",
171+
"files: './outside/**/*.{ts,tsx,js,jsx}'"
172+
);
173+
174+
// create a file with lint error
175+
await sandbox.write('./outside/test.ts', 'const x = 4;');
176+
177+
const driver = createWebpackDevServerDriver(sandbox.spawn('npm run webpack-dev-server'), false);
178+
179+
// initially we should have 1 error
180+
expect(await driver.waitForErrors()).toEqual([
181+
[
182+
'WARNING in outside/test.ts:1:7',
183+
"@typescript-eslint/no-unused-vars: 'x' is assigned a value but never used.",
184+
' > 1 | const x = 4;',
185+
' | ^',
186+
].join('\n'),
187+
]);
188+
189+
// let's fix that error
190+
await sandbox.write('./outside/test.ts', 'export const x = 4;');
191+
await driver.waitForNoErrors();
192+
193+
// add a new file in this directory
194+
await sandbox.write('./outside/another.ts', '');
195+
await driver.waitForNoErrors();
196+
197+
// update another.ts with a code that has 1 lint error
198+
await sandbox.write('./outside/another.ts', 'const y = 5;');
199+
expect(await driver.waitForErrors()).toEqual([
200+
[
201+
'WARNING in outside/another.ts:1:7',
202+
"@typescript-eslint/no-unused-vars: 'y' is assigned a value but never used.",
203+
' > 1 | const y = 5;',
204+
' | ^',
205+
].join('\n'),
206+
]);
207+
208+
// let's remove this file - this will check if we handle remove events
209+
await sandbox.remove('./outside/another.ts');
210+
await driver.waitForNoErrors();
211+
});
212+
154213
it('fixes errors with `fix: true` option', async () => {
155214
await sandbox.load([
156215
await readFixture(join(__dirname, 'fixtures/environment/eslint-basic.fixture'), {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ module.exports = {
8585
async: ${ASYNC},
8686
eslint: {
8787
enabled: true,
88-
files: './src/**/*'
88+
files: './src/**/*.{ts,tsx,js,jsx}'
8989
},
9090
logger: {
9191
infrastructure: "console"

0 commit comments

Comments
 (0)