Skip to content

Commit a53360e

Browse files
refactor!: remove threads option (#299)
* refactor!: remove threads option * test: add multithread tests for worker concurrency behavior
1 parent a4400b2 commit a53360e

22 files changed

+79
-376
lines changed

README.md

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -239,18 +239,6 @@ type lintDirtyModulesOnly = boolean;
239239

240240
Lint only changed files, skipping initial lint on build start.
241241

242-
### `threads`
243-
244-
- Type:
245-
246-
```ts
247-
type threads = boolean | number;
248-
```
249-
250-
- Default: `false`
251-
252-
Will run lint tasks across a thread pool. The pool size is automatic unless you specify a number.
253-
254242
### Errors and Warning
255243

256244
**By default the plugin will auto adjust error reporting depending on eslint errors/warnings counts.**

package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@
5151
},
5252
"dependencies": {
5353
"@types/eslint": "^9.6.1",
54-
"flatted": "^3.3.3",
55-
"jest-worker": "^30.2.0",
5654
"micromatch": "^4.0.8",
5755
"normalize-path": "^3.0.0",
5856
"schema-utils": "^4.3.3"

src/getESLint.js

Lines changed: 29 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,51 @@
1-
const { cpus } = require("node:os");
2-
3-
const { stringify } = require("flatted");
4-
const { Worker: JestWorker } = require("jest-worker");
5-
61
const { getESLintOptions } = require("./options");
7-
const { jsonStringifyReplacerSortKeys } = require("./utils");
8-
const { lintFiles, setup } = require("./worker");
9-
10-
/** @type {{ [key: string]: Linter }} */
11-
const cache = {};
122

133
/** @typedef {import("eslint").ESLint} ESLint */
144
/** @typedef {import("eslint").ESLint.LintResult} LintResult */
155
/** @typedef {import("./options").Options} Options */
16-
/** @typedef {() => Promise<void>} AsyncTask */
176
/** @typedef {(files: string | string[]) => Promise<LintResult[]>} LintTask */
18-
/** @typedef {{ threads: number, eslint: ESLint, lintFiles: LintTask, cleanup: AsyncTask }} Linter */
19-
/** @typedef {JestWorker & { lintFiles: LintTask }} Worker */
7+
/** @typedef {{ eslint: ESLint, lintFiles: LintTask }} Linter */
8+
/** @typedef {import("eslint").ESLint.Options} ESLintOptions */
9+
/** @typedef {{ new (arg0: ESLintOptions): ESLint, outputFixes: (arg0: LintResult[]) => Promise<void> }} ESLintClass */
2010

2111
/**
2212
* @param {Options} options options
2313
* @returns {Promise<Linter>} linter
2414
*/
25-
async function loadESLint(options) {
26-
const { eslintPath } = options;
27-
const eslint = await setup({
28-
eslintPath,
29-
configType: options.configType,
30-
eslintOptions: getESLintOptions(options),
15+
async function getESLint(options) {
16+
const eslintOptions = getESLintOptions(options);
17+
const fix = Boolean(eslintOptions && eslintOptions.fix);
18+
19+
const eslintModule = require(options.eslintPath || "eslint");
20+
21+
/** @type {ESLintClass} */
22+
const ESLint = await eslintModule.loadESLint({
23+
useFlatConfig: options.configType === "flat",
3124
});
3225

26+
/** @type {ESLint} */
27+
const eslint = new ESLint(eslintOptions);
28+
29+
/**
30+
* @param {string | string[]} files files
31+
* @returns {Promise<LintResult[]>} lint results
32+
*/
33+
async function lintFiles(files) {
34+
/** @type {LintResult[]} */
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;
41+
}
42+
3343
return {
34-
threads: 1,
3544
lintFiles,
3645
eslint,
37-
// no-op for non-threaded
38-
cleanup: async () => {},
39-
};
40-
}
41-
42-
/**
43-
* @param {string | undefined} key a cache key
44-
* @param {Options} options options
45-
* @returns {string} a stringified cache key
46-
*/
47-
function getCacheKey(key, options) {
48-
return stringify({ key, options }, jsonStringifyReplacerSortKeys);
49-
}
50-
51-
/**
52-
* @param {string | undefined} key a cache key
53-
* @param {number} poolSize number of workers
54-
* @param {Options} options options
55-
* @returns {Promise<Linter>} linter
56-
*/
57-
async function loadESLintThreaded(key, poolSize, options) {
58-
const cacheKey = getCacheKey(key, options);
59-
const { eslintPath = "eslint" } = options;
60-
const source = require.resolve("./worker");
61-
const workerOptions = {
62-
enableWorkerThreads: true,
63-
numWorkers: poolSize,
64-
setupArgs: [
65-
{
66-
eslintPath,
67-
configType: options.configType,
68-
eslintOptions: getESLintOptions(options),
69-
},
70-
],
71-
};
72-
73-
const local = await loadESLint(options);
74-
75-
let worker =
76-
/** @type {Worker | null} */
77-
(new JestWorker(source, workerOptions));
78-
79-
/** @type {Linter} */
80-
const context = {
81-
...local,
82-
threads: poolSize,
83-
lintFiles: async (files) =>
84-
(worker && (await worker.lintFiles(files))) ||
85-
/* istanbul ignore next */ [],
86-
cleanup: async () => {
87-
cache[cacheKey] = local;
88-
context.lintFiles = (files) => local.lintFiles(files);
89-
if (worker) {
90-
worker.end();
91-
worker = null;
92-
}
93-
},
9446
};
95-
96-
return context;
97-
}
98-
99-
/**
100-
* @param {string | undefined} key a cache key
101-
* @param {Options} options options
102-
* @returns {Promise<Linter>} linter
103-
*/
104-
async function getESLint(key, { threads, ...options }) {
105-
const max =
106-
typeof threads !== "number"
107-
? threads
108-
? cpus().length - 1
109-
: 1
110-
: /* istanbul ignore next */
111-
threads;
112-
113-
const cacheKey = getCacheKey(key, { threads, ...options });
114-
if (!cache[cacheKey]) {
115-
cache[cacheKey] =
116-
max > 1
117-
? await loadESLintThreaded(key, max, options)
118-
: await loadESLint(options);
119-
}
120-
return cache[cacheKey];
12147
}
12248

12349
module.exports = {
12450
getESLint,
125-
loadESLint,
126-
loadESLintThreaded,
12751
};

src/index.js

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,9 @@ class ESLintWebpackPlugin {
9999
let lint;
100100
/** @type {import("./linter").Reporter} */
101101
let report;
102-
/** @type number */
103-
let threads;
104102

105103
try {
106-
({ lint, report, threads } = await linter(
107-
this.key,
108-
options,
109-
compilation,
110-
));
104+
({ lint, report } = await linter(options, compilation));
111105
} catch (err) {
112106
compilation.errors.push(err);
113107
return;
@@ -135,8 +129,6 @@ class ESLintWebpackPlugin {
135129

136130
if (isFileNotListed && isFileWanted && isQueryNotExclude) {
137131
files.push(file);
138-
139-
if (threads > 1) lint(file);
140132
}
141133
}
142134

@@ -149,7 +141,7 @@ class ESLintWebpackPlugin {
149141

150142
// Lint all files added
151143
compilation.hooks.finishModules.tap(this.key, () => {
152-
if (files.length > 0 && threads <= 1) lint(files);
144+
if (files.length > 0) lint(files);
153145
});
154146

155147
// await and interpret results

src/linter.js

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -159,29 +159,22 @@ function parseResults(options, results) {
159159
}
160160

161161
/**
162-
* @param {string | undefined} key a cache key
163162
* @param {Options} options options
164163
* @param {Compilation} compilation compilation
165-
* @returns {Promise<{ lint: Linter, report: Reporter, threads: number }>} linter with additional functions
164+
* @returns {Promise<{ lint: Linter, report: Reporter }>} linter with additional functions
166165
*/
167-
async function linter(key, options, compilation) {
166+
async function linter(options, compilation) {
168167
/** @type {ESLint} */
169168
let eslint;
170169

171170
/** @type {(files: string | string[]) => Promise<LintResult[]>} */
172171
let lintFiles;
173172

174-
/** @type {() => Promise<void>} */
175-
let cleanup;
176-
177-
/** @type number */
178-
let threads;
179-
180173
/** @type {Promise<LintResult[]>[]} */
181174
const rawResults = [];
182175

183176
try {
184-
({ eslint, lintFiles, cleanup, threads } = await getESLint(key, options));
177+
({ eslint, lintFiles } = await getESLint(options));
185178
} catch (err) {
186179
throw new ESLintError(err.message);
187180
}
@@ -209,8 +202,6 @@ async function linter(key, options, compilation) {
209202
await flatten(rawResults.splice(0)),
210203
);
211204

212-
await cleanup();
213-
214205
// do not analyze if there are no results or eslint config
215206
if (!results || results.length < 1) {
216207
return {};
@@ -282,7 +273,6 @@ async function linter(key, options, compilation) {
282273
return {
283274
lint,
284275
report,
285-
threads,
286276
};
287277
}
288278

src/options.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ const schema = require("./options.json");
3333
* @property {boolean=} quiet will process and report errors only and ignore warnings
3434
* @property {string=} eslintPath path to `eslint` instance that will be used for linting
3535
* @property {OutputReport=} outputReport writes the output of the errors to a file - for example, a `json` file for use for reporting
36-
* @property {number | boolean=} threads number of worker threads
3736
* @property {RegExp | RegExp[]=} resourceQueryExclude Specify the resource query to exclude
3837
* @property {string=} configType config type
3938
*/

src/options.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,6 @@
8383
}
8484
}
8585
]
86-
},
87-
"threads": {
88-
"description": "Default is false. Set to true for an auto-selected pool size based on number of cpus. Set to a number greater than 1 to set an explicit pool size. Set to false, 1, or less to disable and only run in main process.",
89-
"anyOf": [{ "type": "number" }, { "type": "boolean" }]
9086
}
9187
}
9288
}

src/utils.js

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -90,34 +90,8 @@ function parseFoldersToGlobs(patterns, extensions = []) {
9090
});
9191
}
9292

93-
/**
94-
* @param {string} _ key, but unused
95-
* @param {EXPECTED_ANY} value value
96-
* @returns {{ [x: string]: EXPECTED_ANY }} result
97-
*/
98-
const jsonStringifyReplacerSortKeys = (_, value) => {
99-
/**
100-
* @param {{ [x: string]: EXPECTED_ANY }} sorted sorted
101-
* @param {string | number} key key
102-
* @returns {{ [x: string]: EXPECTED_ANY }} result
103-
*/
104-
const insert = (sorted, key) => {
105-
sorted[key] = value[key];
106-
return sorted;
107-
};
108-
109-
if (value instanceof Object && !Array.isArray(value)) {
110-
const sorted = Object.keys(value).toSorted().reduce(insert, {});
111-
for (const key of Object.keys(value)) delete value[key];
112-
Object.assign(value, sorted);
113-
}
114-
115-
return value;
116-
};
117-
11893
module.exports = {
11994
arrify,
120-
jsonStringifyReplacerSortKeys,
12195
parseFiles,
12296
parseFoldersToGlobs,
12397
};

src/worker.js

Lines changed: 0 additions & 51 deletions
This file was deleted.

test/autofix.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ describe("autofix stop", () => {
1515
removeSync(entry);
1616
});
1717

18-
it.each([[{}], [{ threads: false }]])(
18+
it.each([[{}]])(
1919
"should not throw error if file ok after auto-fixing",
2020
async (cfg) => {
2121
const compiler = pack("fixable-clone", {

0 commit comments

Comments
 (0)