From 163055a994051102139ed29b3136b929dbf05d3b Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 8 Feb 2024 18:59:11 -0500 Subject: [PATCH 1/3] feat: Add loadESLint() API method for v9 refs #18075 --- docs/src/integrate/nodejs-api.md | 44 +++++++++++++++++++++++++++++++ lib/api.js | 26 +++++++++++++++++- lib/eslint/eslint.js | 6 +++++ lib/eslint/legacy-eslint.js | 6 +++++ tests/lib/api.js | 26 +++++++++++++++++- tests/lib/eslint/eslint.js | 5 ++++ tests/lib/eslint/legacy-eslint.js | 5 ++++ 7 files changed, 116 insertions(+), 2 deletions(-) diff --git a/docs/src/integrate/nodejs-api.md b/docs/src/integrate/nodejs-api.md index ce0890c79b6..b8f10db13b8 100644 --- a/docs/src/integrate/nodejs-api.md +++ b/docs/src/integrate/nodejs-api.md @@ -442,6 +442,50 @@ The `LoadedFormatter` value is the object to convert the [LintResult] objects to --- +## loadESLint() + +The `loadESLint()` function is used for integrations that wish to support both the current configuration system (flat config) and the old configuration system (eslintrc). This function returns the correct `ESLint` class implementation based on the arguments provided: + +```js +const { loadESLint } = require("eslint"); + +// loads the default ESLint that the CLI would use based on process.cwd() +const DefaultESLint = await loadESLint(); + +// loads the default ESLint that the CLI would use based on the provided cwd +const CwdDefaultESLint = await loadESLint({ cwd: "/foo/bar" }); + +// loads the flat config version specifically +const FlatESLint = await loadESLint({ useFlatConfig: true }); + +// loads the legacy version specifically +const LegacyESLint = await loadESLint({ useFlatConfig: false }); +``` + +You can then use the returned constructor to instantiate a new `ESLint` instance, like this: + +```js +// loads the default ESLint that the CLI would use based on process.cwd() +const DefaultESLint = await loadESLint(); +const eslint = new DefaultESLint(); +``` + +If you're ever unsure which config system the returned constructor uses, check the `configType` property, which is either `"flat"` or `"eslintrc"`: + +```js +// loads the default ESLint that the CLI would use based on process.cwd() +const DefaultESLint = await loadESLint(); +const eslint = new DefaultESLint(); + +if (eslint.configType === "flat") { + // do something specific to flat config +} +``` + +If you don't need to support both the old and new configuration systems, then it's recommended to just use the `ESLint` constructor directly. + +--- + ## SourceCode The `SourceCode` type represents the parsed source code that ESLint executes on. It's used internally in ESLint and is also available so that already-parsed code can be used. You can create a new instance of `SourceCode` by passing in the text string representing the code and an abstract syntax tree (AST) in [ESTree](https://github.com/estree/estree) format (including location information, range information, comments, and tokens): diff --git a/lib/api.js b/lib/api.js index 4a689250af7..ab0ec2fcd31 100644 --- a/lib/api.js +++ b/lib/api.js @@ -9,17 +9,41 @@ // Requirements //----------------------------------------------------------------------------- -const { ESLint } = require("./eslint/eslint"); +const { ESLint, shouldUseFlatConfig } = require("./eslint/eslint"); +const { LegacyESLint } = require("./eslint/legacy-eslint"); const { Linter } = require("./linter"); const { RuleTester } = require("./rule-tester"); const { SourceCode } = require("./source-code"); +//----------------------------------------------------------------------------- +// Functions +//----------------------------------------------------------------------------- + +/** + * Loads the correct ESLint constructor given the options. + * @param {Object} [options] The options object + * @param {boolean} [options.useFlatConfig] Whether or not to use a flat config + * @returns {Promise} The ESLint constructor + */ +async function loadESLint({ useFlatConfig } = {}) { + + /* + * Note: The v8.x version of this function also accepted a `cwd` option, but + * it is not used in this implementation so we silently ignore it. + */ + + const shouldESLintUseFlatConfig = useFlatConfig ?? (await shouldUseFlatConfig()); + + return shouldESLintUseFlatConfig ? ESLint : LegacyESLint; +} + //----------------------------------------------------------------------------- // Exports //----------------------------------------------------------------------------- module.exports = { Linter, + loadESLint, ESLint, RuleTester, SourceCode diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index 49bc0e7579a..97102d3fe0e 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -565,6 +565,12 @@ function createExtraneousResultsError() { */ class ESLint { + /** + * The type of configuration used by this class. + * @type {string} + */ + static configType = "flat"; + /** * Creates a new instance of the main ESLint API. * @param {ESLintOptions} options The options for this instance. diff --git a/lib/eslint/legacy-eslint.js b/lib/eslint/legacy-eslint.js index 251a3890db8..9c86163ef63 100644 --- a/lib/eslint/legacy-eslint.js +++ b/lib/eslint/legacy-eslint.js @@ -438,6 +438,12 @@ function compareResultsByFilePath(a, b) { */ class LegacyESLint { + /** + * The type of configuration used by this class. + * @type {string} + */ + static configType = "eslintrc"; + /** * Creates a new instance of the main ESLint API. * @param {LegacyESLintOptions} options The options for this instance. diff --git a/tests/lib/api.js b/tests/lib/api.js index abcbea5aef1..80443e042db 100644 --- a/tests/lib/api.js +++ b/tests/lib/api.js @@ -10,7 +10,8 @@ //----------------------------------------------------------------------------- const assert = require("chai").assert, - api = require("../../lib/api"); + api = require("../../lib/api"), + { LegacyESLint } = require("../../lib/eslint/legacy-eslint"); //----------------------------------------------------------------------------- // Tests @@ -41,4 +42,27 @@ describe("api", () => { it("should have SourceCode exposed", () => { assert.isFunction(api.SourceCode); }); + + describe("loadESLint", () => { + it("should be a function", () => { + assert.isFunction(api.loadESLint); + }); + + it("should return a Promise", () => { + assert.instanceOf(api.loadESLint(), Promise); + }); + + it("should return ESLint when useFlatConfig is true", async () => { + assert.strictEqual(await api.loadESLint({ useFlatConfig: true }), api.ESLint); + }); + + it("should return LegacyESLint when useFlatConfig is false", async () => { + assert.strictEqual(await api.loadESLint({ useFlatConfig: false }), LegacyESLint); + }); + + it("should return ESLint when useFlatConfig is not provided", async () => { + assert.strictEqual(await api.loadESLint(), api.ESLint); + }); + }); + }); diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 3bec83d441e..9360d39449d 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -134,6 +134,11 @@ describe("ESLint", () => { }); describe("ESLint constructor function", () => { + + it("should have a static property indicating the configType being used", () => { + assert.strictEqual(ESLint.configType, "flat"); + }); + it("the default value of 'options.cwd' should be the current working directory.", async () => { process.chdir(__dirname); try { diff --git a/tests/lib/eslint/legacy-eslint.js b/tests/lib/eslint/legacy-eslint.js index 7bc70d4be93..60b40cb5cd6 100644 --- a/tests/lib/eslint/legacy-eslint.js +++ b/tests/lib/eslint/legacy-eslint.js @@ -114,6 +114,11 @@ describe("LegacyESLint", () => { }); describe("ESLint constructor function", () => { + + it("should have a static property indicating the configType being used", () => { + assert.strictEqual(LegacyESLint.configType, "eslintrc"); + }); + it("the default value of 'options.cwd' should be the current working directory.", async () => { process.chdir(__dirname); try { From 9605f91fd6853b5d4b944dce52b8c076ada9fdb7 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 9 Feb 2024 12:28:32 -0500 Subject: [PATCH 2/3] Fix docs --- docs/src/integrate/nodejs-api.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/src/integrate/nodejs-api.md b/docs/src/integrate/nodejs-api.md index b8f10db13b8..5d081df3d3b 100644 --- a/docs/src/integrate/nodejs-api.md +++ b/docs/src/integrate/nodejs-api.md @@ -475,9 +475,8 @@ If you're ever unsure which config system the returned constructor uses, check t ```js // loads the default ESLint that the CLI would use based on process.cwd() const DefaultESLint = await loadESLint(); -const eslint = new DefaultESLint(); -if (eslint.configType === "flat") { +if (DefaultESLint.configType === "flat") { // do something specific to flat config } ``` From 046892e4ca064c3a1104cb9fed5010d882ca7289 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 9 Feb 2024 13:38:29 -0500 Subject: [PATCH 3/3] Add more tests using environment variables --- tests/lib/api.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/lib/api.js b/tests/lib/api.js index 80443e042db..71a5f42930a 100644 --- a/tests/lib/api.js +++ b/tests/lib/api.js @@ -44,6 +44,11 @@ describe("api", () => { }); describe("loadESLint", () => { + + afterEach(() => { + delete process.env.ESLINT_USE_FLAT_CONFIG; + }); + it("should be a function", () => { assert.isFunction(api.loadESLint); }); @@ -63,6 +68,16 @@ describe("api", () => { it("should return ESLint when useFlatConfig is not provided", async () => { assert.strictEqual(await api.loadESLint(), api.ESLint); }); + + it("should return LegacyESLint when useFlatConfig is not provided and ESLINT_USE_FLAT_CONFIG is false", async () => { + process.env.ESLINT_USE_FLAT_CONFIG = "false"; + assert.strictEqual(await api.loadESLint(), LegacyESLint); + }); + + it("should return ESLint when useFlatConfig is not provided and ESLINT_USE_FLAT_CONFIG is true", async () => { + process.env.ESLINT_USE_FLAT_CONFIG = "true"; + assert.strictEqual(await api.loadESLint(), api.ESLint); + }); }); });