Skip to content

Commit 4b8d4de

Browse files
fix: #43, #44, #45 (#47)
* test: add failing test * fix: #44 keep all results across runs * fix: #45 - include eslint options in types * fix: 43 Co-authored-by: Ricardo Gobbo de Souza <[email protected]>
1 parent 46fc07a commit 4b8d4de

File tree

11 files changed

+141
-112
lines changed

11 files changed

+141
-112
lines changed

declarations/getESLint.d.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,8 @@ export function loadESLintThreaded(poolSize: number, options: Options): Linter;
2323
export default function getESLint({ threads, ...options }: Options): Linter;
2424
export type ESLint = import('eslint').ESLint;
2525
export type LintResult = import('eslint').ESLint.LintResult;
26-
export type Options = {
27-
context?: string | undefined;
28-
emitError?: boolean | undefined;
29-
emitWarning?: boolean | undefined;
30-
eslintPath?: string | undefined;
31-
exclude?: string | string[] | undefined;
32-
extensions?: string | string[] | undefined;
33-
failOnError?: boolean | undefined;
34-
failOnWarning?: boolean | undefined;
35-
files?: string | string[] | undefined;
36-
fix?: boolean | undefined;
37-
formatter?: string | import('./options').FormatterFunction | undefined;
38-
lintDirtyModulesOnly?: boolean | undefined;
39-
quiet?: boolean | undefined;
40-
outputReport?: import('./options').OutputReport | undefined;
41-
threads?: number | boolean | undefined;
42-
};
26+
export type Options = import('./options').PluginOptions &
27+
import('eslint').ESLint.Options;
4328
export type AsyncTask = () => Promise<void>;
4429
export type LintTask = (files: string | string[]) => Promise<LintResult[]>;
4530
export type Worker = JestWorker & {

declarations/index.d.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export class ESLintWebpackPlugin {
33
* @param {Options} options
44
*/
55
constructor(options?: Options);
6-
options: import('./options').Options;
6+
options: import('./options').PluginOptions;
77
/**
88
* @param {Compiler} compiler
99
*/
@@ -22,20 +22,5 @@ export class ESLintWebpackPlugin {
2222
}
2323
export default ESLintWebpackPlugin;
2424
export type Compiler = import('webpack').Compiler;
25-
export type Options = {
26-
context?: string | undefined;
27-
emitError?: boolean | undefined;
28-
emitWarning?: boolean | undefined;
29-
eslintPath?: string | undefined;
30-
exclude?: string | string[] | undefined;
31-
extensions?: string | string[] | undefined;
32-
failOnError?: boolean | undefined;
33-
failOnWarning?: boolean | undefined;
34-
files?: string | string[] | undefined;
35-
fix?: boolean | undefined;
36-
formatter?: string | import('./options').FormatterFunction | undefined;
37-
lintDirtyModulesOnly?: boolean | undefined;
38-
quiet?: boolean | undefined;
39-
outputReport?: import('./options').OutputReport | undefined;
40-
threads?: number | boolean | undefined;
41-
};
25+
export type Options = import('./options').PluginOptions &
26+
import('eslint').ESLint.Options;

declarations/linter.d.ts

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
/** @typedef {import('eslint').ESLint} ESLint */
2-
/** @typedef {import('eslint').ESLint.Formatter} Formatter */
3-
/** @typedef {import('eslint').ESLint.LintResult} LintResult */
4-
/** @typedef {import('webpack').Compiler} Compiler */
5-
/** @typedef {import('webpack').Compilation} Compilation */
6-
/** @typedef {import('webpack-sources').Source} Source */
7-
/** @typedef {import('./options').Options} Options */
8-
/** @typedef {import('./options').FormatterFunction} FormatterFunction */
9-
/** @typedef {(compilation: Compilation) => Promise<void>} GenerateReport */
10-
/** @typedef {{errors?: ESLintError, warnings?: ESLintError, generateReportAsset?: GenerateReport}} Report */
11-
/** @typedef {() => Promise<Report>} Reporter */
12-
/** @typedef {(files: string|string[]) => void} Linter */
131
/**
142
* @param {Options} options
153
* @param {Compilation} compilation
@@ -28,23 +16,8 @@ export type LintResult = import('eslint').ESLint.LintResult;
2816
export type Compiler = import('webpack').Compiler;
2917
export type Compilation = import('webpack').Compilation;
3018
export type Source = import('webpack-sources/lib/Source');
31-
export type Options = {
32-
context?: string | undefined;
33-
emitError?: boolean | undefined;
34-
emitWarning?: boolean | undefined;
35-
eslintPath?: string | undefined;
36-
exclude?: string | string[] | undefined;
37-
extensions?: string | string[] | undefined;
38-
failOnError?: boolean | undefined;
39-
failOnWarning?: boolean | undefined;
40-
files?: string | string[] | undefined;
41-
fix?: boolean | undefined;
42-
formatter?: string | import('./options').FormatterFunction | undefined;
43-
lintDirtyModulesOnly?: boolean | undefined;
44-
quiet?: boolean | undefined;
45-
outputReport?: import('./options').OutputReport | undefined;
46-
threads?: number | boolean | undefined;
47-
};
19+
export type Options = import('./options').PluginOptions &
20+
import('eslint').ESLint.Options;
4821
export type FormatterFunction = (
4922
results: import('eslint').ESLint.LintResult[],
5023
data?: import('eslint').ESLint.LintResultData | undefined
@@ -59,4 +32,7 @@ export type Report = {
5932
};
6033
export type Reporter = () => Promise<Report>;
6134
export type Linter = (files: string | string[]) => void;
35+
export type LintResultMap = {
36+
[files: string]: import('eslint').ESLint.LintResult;
37+
};
6238
import ESLintError from './ESLintError';

declarations/options.d.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* @property {string|FormatterFunction=} formatter
1414
*/
1515
/**
16-
* @typedef {Object} Options
16+
* @typedef {Object} PluginOptions
1717
* @property {string=} context
1818
* @property {boolean=} emitError
1919
* @property {boolean=} emitWarning
@@ -30,11 +30,12 @@
3030
* @property {OutputReport=} outputReport
3131
* @property {number|boolean=} threads
3232
*/
33+
/** @typedef {PluginOptions & ESLintOptions} Options */
3334
/**
3435
* @param {Options} pluginOptions
35-
* @returns {Options}
36+
* @returns {PluginOptions}
3637
*/
37-
export function getOptions(pluginOptions: Options): Options;
38+
export function getOptions(pluginOptions: Options): PluginOptions;
3839
/**
3940
* @param {Options} loaderOptions
4041
* @returns {ESLintOptions}
@@ -51,7 +52,7 @@ export type OutputReport = {
5152
filePath?: string | undefined;
5253
formatter?: (string | FormatterFunction) | undefined;
5354
};
54-
export type Options = {
55+
export type PluginOptions = {
5556
context?: string | undefined;
5657
emitError?: boolean | undefined;
5758
emitWarning?: boolean | undefined;
@@ -68,3 +69,4 @@ export type Options = {
6869
outputReport?: OutputReport | undefined;
6970
threads?: (number | boolean) | undefined;
7071
};
72+
export type Options = PluginOptions & import('eslint').ESLint.Options;

src/getESLint.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,19 @@ export function loadESLint(options) {
2323

2424
// Filter out loader options before passing the options to ESLint.
2525
const eslint = new ESLint(getESLintOptions(options));
26-
const lintFiles = eslint.lintFiles.bind(eslint);
2726

2827
return {
2928
threads: 1,
3029
ESLint,
3130
eslint,
32-
lintFiles,
31+
lintFiles: async (files) => {
32+
const results = await eslint.lintFiles(files);
33+
// istanbul ignore else
34+
if (options.fix) {
35+
await ESLint.outputFixes(results);
36+
}
37+
return results;
38+
},
3339
// no-op for non-threaded
3440
cleanup: async () => {},
3541
};

src/linter.js

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@ import getESLint from './getESLint';
1515
/** @typedef {{errors?: ESLintError, warnings?: ESLintError, generateReportAsset?: GenerateReport}} Report */
1616
/** @typedef {() => Promise<Report>} Reporter */
1717
/** @typedef {(files: string|string[]) => void} Linter */
18+
/** @typedef {{[files: string]: LintResult}} LintResultMap */
19+
20+
/** @type {WeakMap<Compiler, LintResultMap>} */
21+
const resultStorage = new WeakMap();
22+
1823
/**
1924
* @param {Options} options
2025
* @param {Compilation} compilation
2126
* @returns {{lint: Linter, report: Reporter}}
2227
*/
2328
export default function linter(options, compilation) {
24-
/** @type {ESLint} */
25-
let ESLint;
26-
2729
/** @type {ESLint} */
2830
let eslint;
2931

@@ -36,8 +38,10 @@ export default function linter(options, compilation) {
3638
/** @type {Promise<LintResult[]>[]} */
3739
const rawResults = [];
3840

41+
const crossRunResultStorage = getResultStorage(compilation);
42+
3943
try {
40-
({ ESLint, eslint, lintFiles, cleanup } = getESLint(options));
44+
({ eslint, lintFiles, cleanup } = getESLint(options));
4145
} catch (e) {
4246
throw new ESLintError(e.message);
4347
}
@@ -51,6 +55,9 @@ export default function linter(options, compilation) {
5155
* @param {string | string[]} files
5256
*/
5357
function lint(files) {
58+
for (const file of asList(files)) {
59+
delete crossRunResultStorage[file];
60+
}
5461
rawResults.push(
5562
lintFiles(files).catch((e) => {
5663
compilation.errors.push(e);
@@ -61,7 +68,7 @@ export default function linter(options, compilation) {
6168

6269
async function report() {
6370
// Filter out ignored files.
64-
const results = await removeIgnoredWarnings(
71+
let results = await removeIgnoredWarnings(
6572
eslint,
6673
// Get the current results, resetting the rawResults to empty
6774
await flatten(rawResults.splice(0, rawResults.length))
@@ -74,12 +81,12 @@ export default function linter(options, compilation) {
7481
return {};
7582
}
7683

77-
// if enabled, use eslint autofixing where possible
78-
if (options.fix) {
79-
// @ts-ignore
80-
await ESLint.outputFixes(results);
84+
for (const result of results) {
85+
crossRunResultStorage[result.filePath] = result;
8186
}
8287

88+
results = Object.values(crossRunResultStorage);
89+
8390
const formatter = await loadFormatter(eslint, options.formatter);
8491
const { errors, warnings } = formatResults(
8592
formatter,
@@ -276,3 +283,23 @@ async function flatten(results) {
276283
const flat = (acc, list) => [...acc, ...list];
277284
return (await Promise.all(results)).reduce(flat, []);
278285
}
286+
287+
/**
288+
* @param {Compilation} compilation
289+
* @returns {LintResultMap}
290+
*/
291+
function getResultStorage({ compiler }) {
292+
let storage = resultStorage.get(compiler);
293+
if (!storage) {
294+
resultStorage.set(compiler, (storage = {}));
295+
}
296+
return storage;
297+
}
298+
299+
/**
300+
* @param {string | string[]} x
301+
*/
302+
function asList(x) {
303+
/* istanbul ignore next */
304+
return Array.isArray(x) ? x : [x];
305+
}

src/options.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import schema from './options.json';
2020
*/
2121

2222
/**
23-
* @typedef {Object} Options
23+
* @typedef {Object} PluginOptions
2424
* @property {string=} context
2525
* @property {boolean=} emitError
2626
* @property {boolean=} emitWarning
@@ -38,9 +38,11 @@ import schema from './options.json';
3838
* @property {number|boolean=} threads
3939
*/
4040

41+
/** @typedef {PluginOptions & ESLintOptions} Options */
42+
4143
/**
4244
* @param {Options} pluginOptions
43-
* @returns {Options}
45+
* @returns {PluginOptions}
4446
*/
4547
export function getOptions(pluginOptions) {
4648
const options = {

src/worker.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,36 @@ Object.assign(module.exports, {
66
setup,
77
});
88

9+
/** @type {{ new (arg0: import("eslint").ESLint.Options): import("eslint").ESLint; outputFixes: (arg0: import("eslint").ESLint.LintResult[]) => any; }} */
10+
let ESLint;
11+
912
/** @type {ESLint} */
1013
let eslint;
1114

15+
/** @type {boolean} */
16+
let fix;
17+
1218
/**
1319
* @typedef {object} setupOptions
1420
* @property {string=} eslintPath - import path of eslint
1521
* @property {ESLintOptions=} eslintOptions - linter options
1622
*
1723
* @param {setupOptions} arg0 - setup worker
1824
*/
19-
function setup({ eslintPath, eslintOptions }) {
20-
const { ESLint } = require(eslintPath || 'eslint');
25+
function setup({ eslintPath, eslintOptions = {} }) {
26+
fix = !!(eslintOptions && eslintOptions.fix);
27+
({ ESLint } = require(eslintPath || 'eslint'));
2128
eslint = new ESLint(eslintOptions);
2229
}
2330

2431
/**
2532
* @param {string | string[]} files
2633
*/
2734
async function lintFiles(files) {
28-
return eslint.lintFiles(files);
35+
const result = await eslint.lintFiles(files);
36+
// if enabled, use eslint autofixing where possible
37+
if (fix) {
38+
await ESLint.outputFixes(result);
39+
}
40+
return result;
2941
}

test/autofix.test.js

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { join } from 'path';
22

3-
import { copySync, removeSync } from 'fs-extra';
3+
import { copySync, removeSync, readFileSync } from 'fs-extra';
44

55
import pack from './utils/pack';
66

@@ -15,20 +15,32 @@ describe('autofix stop', () => {
1515
removeSync(entry);
1616
});
1717

18-
it('should not throw error if file ok after auto-fixing', (done) => {
19-
const compiler = pack('fixable-clone', {
20-
fix: true,
21-
extensions: ['js', 'cjs', 'mjs'],
22-
overrideConfig: {
23-
rules: { semi: ['error', 'always'] },
24-
},
25-
});
26-
27-
compiler.run((err, stats) => {
28-
expect(err).toBeNull();
29-
expect(stats.hasWarnings()).toBe(false);
30-
expect(stats.hasErrors()).toBe(false);
31-
done();
32-
});
33-
});
18+
test.each([[{}], [{ threads: false }]])(
19+
'should not throw error if file ok after auto-fixing',
20+
(cfg, done) => {
21+
const compiler = pack('fixable-clone', {
22+
...cfg,
23+
fix: true,
24+
extensions: ['js', 'cjs', 'mjs'],
25+
overrideConfig: {
26+
rules: { semi: ['error', 'always'] },
27+
},
28+
});
29+
30+
compiler.run((err, stats) => {
31+
expect(err).toBeNull();
32+
expect(stats.hasWarnings()).toBe(false);
33+
expect(stats.hasErrors()).toBe(false);
34+
expect(readFileSync(entry).toString('utf8')).toMatchInlineSnapshot(`
35+
"function foo() {
36+
return true;
37+
}
38+
39+
foo();
40+
"
41+
`);
42+
done();
43+
});
44+
}
45+
);
3446
});

0 commit comments

Comments
 (0)