Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,18 +239,6 @@ type lintDirtyModulesOnly = boolean;

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

### `threads`

- Type:

```ts
type threads = boolean | number;
```

- Default: `false`

Will run lint tasks across a thread pool. The pool size is automatic unless you specify a number.

### Errors and Warning

**By default the plugin will auto adjust error reporting depending on eslint errors/warnings counts.**
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@
},
"dependencies": {
"@types/eslint": "^9.6.1",
"flatted": "^3.3.3",
"jest-worker": "^30.2.0",
"micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
"schema-utils": "^4.3.3"
Expand Down
134 changes: 29 additions & 105 deletions src/getESLint.js
Original file line number Diff line number Diff line change
@@ -1,127 +1,51 @@
const { cpus } = require("node:os");

const { stringify } = require("flatted");
const { Worker: JestWorker } = require("jest-worker");

const { getESLintOptions } = require("./options");
const { jsonStringifyReplacerSortKeys } = require("./utils");
const { lintFiles, setup } = require("./worker");

/** @type {{ [key: string]: Linter }} */
const cache = {};

/** @typedef {import("eslint").ESLint} ESLint */
/** @typedef {import("eslint").ESLint.LintResult} LintResult */
/** @typedef {import("./options").Options} Options */
/** @typedef {() => Promise<void>} AsyncTask */
/** @typedef {(files: string | string[]) => Promise<LintResult[]>} LintTask */
/** @typedef {{ threads: number, eslint: ESLint, lintFiles: LintTask, cleanup: AsyncTask }} Linter */
/** @typedef {JestWorker & { lintFiles: LintTask }} Worker */
/** @typedef {{ eslint: ESLint, lintFiles: LintTask }} Linter */
/** @typedef {import("eslint").ESLint.Options} ESLintOptions */
/** @typedef {{ new (arg0: ESLintOptions): ESLint, outputFixes: (arg0: LintResult[]) => Promise<void> }} ESLintClass */

/**
* @param {Options} options options
* @returns {Promise<Linter>} linter
*/
async function loadESLint(options) {
const { eslintPath } = options;
const eslint = await setup({
eslintPath,
configType: options.configType,
eslintOptions: getESLintOptions(options),
async function getESLint(options) {
const eslintOptions = getESLintOptions(options);
const fix = Boolean(eslintOptions && eslintOptions.fix);

const eslintModule = require(options.eslintPath || "eslint");

/** @type {ESLintClass} */
const ESLint = await eslintModule.loadESLint({
useFlatConfig: options.configType === "flat",
});

/** @type {ESLint} */
const eslint = new ESLint(eslintOptions);

/**
* @param {string | string[]} files files
* @returns {Promise<LintResult[]>} lint results
*/
async function lintFiles(files) {
/** @type {LintResult[]} */
const result = await eslint.lintFiles(files);
// if enabled, use eslint autofixing where possible
if (fix) {
await ESLint.outputFixes(result);
}
return result;
}

return {
threads: 1,
lintFiles,
eslint,
// no-op for non-threaded
cleanup: async () => {},
};
}

/**
* @param {string | undefined} key a cache key
* @param {Options} options options
* @returns {string} a stringified cache key
*/
function getCacheKey(key, options) {
return stringify({ key, options }, jsonStringifyReplacerSortKeys);
}

/**
* @param {string | undefined} key a cache key
* @param {number} poolSize number of workers
* @param {Options} options options
* @returns {Promise<Linter>} linter
*/
async function loadESLintThreaded(key, poolSize, options) {
const cacheKey = getCacheKey(key, options);
const { eslintPath = "eslint" } = options;
const source = require.resolve("./worker");
const workerOptions = {
enableWorkerThreads: true,
numWorkers: poolSize,
setupArgs: [
{
eslintPath,
configType: options.configType,
eslintOptions: getESLintOptions(options),
},
],
};

const local = await loadESLint(options);

let worker =
/** @type {Worker | null} */
(new JestWorker(source, workerOptions));

/** @type {Linter} */
const context = {
...local,
threads: poolSize,
lintFiles: async (files) =>
(worker && (await worker.lintFiles(files))) ||
/* istanbul ignore next */ [],
cleanup: async () => {
cache[cacheKey] = local;
context.lintFiles = (files) => local.lintFiles(files);
if (worker) {
worker.end();
worker = null;
}
},
};

return context;
}

/**
* @param {string | undefined} key a cache key
* @param {Options} options options
* @returns {Promise<Linter>} linter
*/
async function getESLint(key, { threads, ...options }) {
const max =
typeof threads !== "number"
? threads
? cpus().length - 1
: 1
: /* istanbul ignore next */
threads;

const cacheKey = getCacheKey(key, { threads, ...options });
if (!cache[cacheKey]) {
cache[cacheKey] =
max > 1
? await loadESLintThreaded(key, max, options)
: await loadESLint(options);
}
return cache[cacheKey];
}

module.exports = {
getESLint,
loadESLint,
loadESLintThreaded,
};
12 changes: 2 additions & 10 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,9 @@ class ESLintWebpackPlugin {
let lint;
/** @type {import("./linter").Reporter} */
let report;
/** @type number */
let threads;

try {
({ lint, report, threads } = await linter(
this.key,
options,
compilation,
));
({ lint, report } = await linter(options, compilation));
} catch (err) {
compilation.errors.push(err);
return;
Expand Down Expand Up @@ -135,8 +129,6 @@ class ESLintWebpackPlugin {

if (isFileNotListed && isFileWanted && isQueryNotExclude) {
files.push(file);

if (threads > 1) lint(file);
}
}

Expand All @@ -149,7 +141,7 @@ class ESLintWebpackPlugin {

// Lint all files added
compilation.hooks.finishModules.tap(this.key, () => {
if (files.length > 0 && threads <= 1) lint(files);
if (files.length > 0) lint(files);
});

// await and interpret results
Expand Down
16 changes: 3 additions & 13 deletions src/linter.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,29 +159,22 @@ function parseResults(options, results) {
}

/**
* @param {string | undefined} key a cache key
* @param {Options} options options
* @param {Compilation} compilation compilation
* @returns {Promise<{ lint: Linter, report: Reporter, threads: number }>} linter with additional functions
* @returns {Promise<{ lint: Linter, report: Reporter }>} linter with additional functions
*/
async function linter(key, options, compilation) {
async function linter(options, compilation) {
/** @type {ESLint} */
let eslint;

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

/** @type {() => Promise<void>} */
let cleanup;

/** @type number */
let threads;

/** @type {Promise<LintResult[]>[]} */
const rawResults = [];

try {
({ eslint, lintFiles, cleanup, threads } = await getESLint(key, options));
({ eslint, lintFiles } = await getESLint(options));
} catch (err) {
throw new ESLintError(err.message);
}
Expand Down Expand Up @@ -209,8 +202,6 @@ async function linter(key, options, compilation) {
await flatten(rawResults.splice(0)),
);

await cleanup();

// do not analyze if there are no results or eslint config
if (!results || results.length < 1) {
return {};
Expand Down Expand Up @@ -282,7 +273,6 @@ async function linter(key, options, compilation) {
return {
lint,
report,
threads,
};
}

Expand Down
1 change: 0 additions & 1 deletion src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ const schema = require("./options.json");
* @property {boolean=} quiet will process and report errors only and ignore warnings
* @property {string=} eslintPath path to `eslint` instance that will be used for linting
* @property {OutputReport=} outputReport writes the output of the errors to a file - for example, a `json` file for use for reporting
* @property {number | boolean=} threads number of worker threads
* @property {RegExp | RegExp[]=} resourceQueryExclude Specify the resource query to exclude
* @property {string=} configType config type
*/
Expand Down
4 changes: 0 additions & 4 deletions src/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,6 @@
}
}
]
},
"threads": {
"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.",
"anyOf": [{ "type": "number" }, { "type": "boolean" }]
}
}
}
26 changes: 0 additions & 26 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,34 +90,8 @@ function parseFoldersToGlobs(patterns, extensions = []) {
});
}

/**
* @param {string} _ key, but unused
* @param {EXPECTED_ANY} value value
* @returns {{ [x: string]: EXPECTED_ANY }} result
*/
const jsonStringifyReplacerSortKeys = (_, value) => {
/**
* @param {{ [x: string]: EXPECTED_ANY }} sorted sorted
* @param {string | number} key key
* @returns {{ [x: string]: EXPECTED_ANY }} result
*/
const insert = (sorted, key) => {
sorted[key] = value[key];
return sorted;
};

if (value instanceof Object && !Array.isArray(value)) {
const sorted = Object.keys(value).toSorted().reduce(insert, {});
for (const key of Object.keys(value)) delete value[key];
Object.assign(value, sorted);
}

return value;
};

module.exports = {
arrify,
jsonStringifyReplacerSortKeys,
parseFiles,
parseFoldersToGlobs,
};
51 changes: 0 additions & 51 deletions src/worker.js

This file was deleted.

2 changes: 1 addition & 1 deletion test/autofix.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe("autofix stop", () => {
removeSync(entry);
});

it.each([[{}], [{ threads: false }]])(
it.each([[{}]])(
"should not throw error if file ok after auto-fixing",
async (cfg) => {
const compiler = pack("fixable-clone", {
Expand Down
2 changes: 1 addition & 1 deletion test/child-compiler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ChildPlugin {

describe("child compiler", () => {
it("should have linting process", (done) => {
const config = conf("good", { threads: false });
const config = conf("good");
config.plugins.push(
new ChildPlugin({
entry: {
Expand Down
Loading
Loading