diff --git a/lib/config/flat-config-array.js b/lib/config/flat-config-array.js index cd439e55244..fbedf139d8b 100644 --- a/lib/config/flat-config-array.js +++ b/lib/config/flat-config-array.js @@ -14,7 +14,6 @@ const { flatConfigSchema } = require("./flat-config-schema"); const { RuleValidator } = require("./rule-validator"); const { defaultConfig } = require("./default-config"); const recommendedConfig = require("../../conf/eslint-recommended"); -const allConfig = require("../../conf/eslint-all"); //----------------------------------------------------------------------------- // Helpers @@ -79,7 +78,13 @@ class FlatConfigArray extends ConfigArray { } if (config === "eslint:all") { - return allConfig; + + /* + * Load `eslint-all.js` here instead of at the top level to avoid loading all rule modules + * when it isn't necessary. `eslint-all.js` reads `meta` of rule objects to filter out deprecated ones, + * so requiring `eslint-all.js` module loads all rule modules as a consequence. + */ + return require("../../conf/eslint-all"); } return config; diff --git a/package.json b/package.json index b9018f7f03f..b5dd764327e 100644 --- a/package.json +++ b/package.json @@ -122,6 +122,7 @@ "node-polyfill-webpack-plugin": "^1.0.3", "npm-license": "^0.3.3", "nyc": "^15.0.1", + "pirates": "^4.0.5", "progress": "^2.0.3", "proxyquire": "^2.0.1", "puppeteer": "^9.1.1", diff --git a/tests/_utils/test-lazy-loading-rules.js b/tests/_utils/test-lazy-loading-rules.js new file mode 100644 index 00000000000..bac4383242a --- /dev/null +++ b/tests/_utils/test-lazy-loading-rules.js @@ -0,0 +1,66 @@ +/** + * @fileoverview Tests lazy-loading of core rules + * @author Milos Djermanovic + */ + +/* + * This module should be run as a child process, with `fork()`, + * because it is important to run this test with a separate, clean Node process + * in order to add hooks before any of the ESLint modules is loaded. + */ + +"use strict"; + +const path = require("path"); +const assert = require("assert"); +const { addHook } = require("pirates"); + +const { + dir: rulesDirectoryPath, + name: rulesDirectoryIndexFilename +} = path.parse(require.resolve("../../lib/rules")); + +// Show full stack trace. The default 10 is usually not enough to find the root cause of this problem. +Error.stackTraceLimit = Infinity; + +const [cwd, pattern, usedRulesCommaSeparated] = process.argv.slice(2); + +assert.ok(cwd, "cwd argument isn't provided"); +assert.ok(pattern, "pattern argument isn't provided"); +assert.ok(usedRulesCommaSeparated, "used rules argument isn't provided"); + +const usedRules = usedRulesCommaSeparated.split(","); + +// `require()` hook +addHook( + (_code, filename) => { + throw new Error(`Unexpected attempt to load unused rule ${filename}`); + }, + { + + // returns `true` if the hook (the function passed in as the first argument) should be called for this filename + matcher(filename) { + const { dir, name } = path.parse(filename); + + if (dir === rulesDirectoryPath && ![rulesDirectoryIndexFilename, ...usedRules].includes(name)) { + return true; + } + + return false; + } + + } +); + +/* + * Everything related to loading any ESLint modules should be in this IIFE + */ +(async () => { + const { ESLint } = require("../.."); + const eslint = new ESLint({ cwd }); + + await eslint.lintFiles([pattern]); +})().catch(({ message, stack }) => { + process.send({ message, stack }); + process.exit(1); +}); diff --git a/tests/fixtures/lazy-loading-rules/.eslintrc.js b/tests/fixtures/lazy-loading-rules/.eslintrc.js new file mode 100644 index 00000000000..fc6500203b9 --- /dev/null +++ b/tests/fixtures/lazy-loading-rules/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + root: true, + rules: { + semi: 2 + } +}; diff --git a/tests/fixtures/lazy-loading-rules/foo.js b/tests/fixtures/lazy-loading-rules/foo.js new file mode 100644 index 00000000000..9b2b3016bd0 --- /dev/null +++ b/tests/fixtures/lazy-loading-rules/foo.js @@ -0,0 +1 @@ +/* content is not necessary */ diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 9c84e979d9b..cc150b34961 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -27,6 +27,7 @@ const { const hash = require("../../../lib/cli-engine/hash"); const { unIndent, createCustomTeardown } = require("../../_utils"); const coreRules = require("../../../lib/rules"); +const childProcess = require("child_process"); //------------------------------------------------------------------------------ // Tests @@ -6694,4 +6695,45 @@ describe("ESLint", () => { }); }); }); + + describe("loading rules", () => { + it("should not load unused core rules", done => { + let calledDone = false; + + const cwd = getFixturePath("lazy-loading-rules"); + const pattern = "foo.js"; + const usedRules = ["semi"]; + + const forkedProcess = childProcess.fork( + path.join(__dirname, "../../_utils/test-lazy-loading-rules.js"), + [cwd, pattern, String(usedRules)] + ); + + // this is an error message + forkedProcess.on("message", ({ message, stack }) => { + if (calledDone) { + return; + } + calledDone = true; + + const error = new Error(message); + + error.stack = stack; + done(error); + }); + + forkedProcess.on("exit", exitCode => { + if (calledDone) { + return; + } + calledDone = true; + + if (exitCode === 0) { + done(); + } else { + done(new Error("Forked process exited with a non-zero exit code")); + } + }); + }); + }); });