From 78beb564ae6154881c591482e2e98acfbe18b164 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 8 Feb 2024 19:24:16 -0500 Subject: [PATCH 1/7] feat: Add loadESLint() API method for v8 refs #18075 --- lib/api.js | 30 +++++++++++++++++++++++++++++- lib/eslint/eslint.js | 6 ++++++ lib/eslint/flat-eslint.js | 13 +++++++++++-- tests/lib/api.js | 30 +++++++++++++++++++++++++++++- tests/lib/eslint/eslint.js | 5 +++++ tests/lib/eslint/flat-eslint.js | 4 ++++ 6 files changed, 84 insertions(+), 4 deletions(-) diff --git a/lib/api.js b/lib/api.js index 3dde0985505..cbaac8fef1b 100644 --- a/lib/api.js +++ b/lib/api.js @@ -9,17 +9,45 @@ // Requirements //----------------------------------------------------------------------------- -const { ESLint } = require("./eslint"); +const { ESLint, FlatESLint } = require("./eslint"); +const { shouldUseFlatConfig } = require("./eslint/flat-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 + * @param {string} [options.cwd] The current working directory + * @returns {Promise} The ESLint constructor + */ +async function loadESLint({ useFlatConfig, cwd = process.cwd() } = {}) { + + /* + * Note: The v9.x version of this function doesn't have a cwd option + * because it's not used. It's only used in the v8.x version of this + * function. + */ + + const shouldESLintUseFlatConfig = typeof useFlatConfig === "boolean" + ? useFlatConfig + : await shouldUseFlatConfig({ cwd }); + + return shouldESLintUseFlatConfig ? FlatESLint : ESLint; +} + //----------------------------------------------------------------------------- // Exports //----------------------------------------------------------------------------- module.exports = { Linter, + loadESLint, ESLint, RuleTester, SourceCode diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index 15e6b3dee41..24cec11a6a8 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -421,6 +421,12 @@ function compareResultsByFilePath(a, b) { */ class ESLint { + /** + * The type of configuration used by this class. + * @type {string} + */ + static configType = "eslintrc"; + /** * Creates a new instance of the main ESLint API. * @param {ESLintOptions} options The options for this instance. diff --git a/lib/eslint/flat-eslint.js b/lib/eslint/flat-eslint.js index 06b41c726c8..ca961aafb64 100644 --- a/lib/eslint/flat-eslint.js +++ b/lib/eslint/flat-eslint.js @@ -1116,11 +1116,20 @@ class FlatESLint { } } +/** + * The type of configuration used by this class. + * @type {string} + * @static + */ +FlatESLint.configType = "flat"; + /** * Returns whether flat config should be used. + * @param {Object} [options] The options for this function. + * @param {string} [options.cwd] The current working directory. * @returns {Promise} Whether flat config should be used. */ -async function shouldUseFlatConfig() { +async function shouldUseFlatConfig({ cwd = process.cwd() } = {}) { switch (process.env.ESLINT_USE_FLAT_CONFIG) { case "true": return true; @@ -1132,7 +1141,7 @@ async function shouldUseFlatConfig() { * If neither explicitly enabled nor disabled, then use the presence * of a flat config file to determine enablement. */ - return !!(await findFlatConfigFile(process.cwd())); + return !!(await findFlatConfigFile(cwd)); } } diff --git a/tests/lib/api.js b/tests/lib/api.js index 074d206e52e..d6627e3ad05 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"), + { FlatESLint } = require("../../lib/eslint"); //----------------------------------------------------------------------------- // Tests @@ -18,6 +19,10 @@ const assert = require("chai").assert, describe("api", () => { + it("should have ESLint exposed", () => { + assert.isFunction(api.ESLint); + }); + it("should have RuleTester exposed", () => { assert.isFunction(api.RuleTester); }); @@ -37,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 FlatESLint when useFlatConfig is true", async () => { + assert.strictEqual(await api.loadESLint({ useFlatConfig: true }), FlatESLint); + }); + + it("should return ESLint when useFlatConfig is false", async () => { + assert.strictEqual(await api.loadESLint({ useFlatConfig: false }), api.ESLint); + }); + + it("should return FlatESLint when useFlatConfig is not provided because we have eslint.config.js", async () => { + assert.strictEqual(await api.loadESLint(), FlatESLint); + }); + }); + }); diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 040722fcf64..fdcfcb81482 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -114,6 +114,11 @@ describe("ESLint", () => { }); describe("ESLint constructor function", () => { + + it("should have a static property indicating the configType being used", () => { + assert.strictEqual(ESLint.configType, "eslintrc"); + }); + it("the default value of 'options.cwd' should be the current working directory.", async () => { process.chdir(__dirname); try { diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index 9ec7305eb98..9bfdff3e305 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -128,6 +128,10 @@ describe("FlatESLint", () => { }); describe("ESLint constructor function", () => { + it("should have a static property indicating the configType being used", () => { + assert.strictEqual(FlatESLint.configType, "flat"); + }); + it("the default value of 'options.cwd' should be the current working directory.", async () => { process.chdir(__dirname); try { From 2d8e8ad8c3971a2527c22b401cd13b457b25d290 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 9 Feb 2024 12:29:30 -0500 Subject: [PATCH 2/7] Update docs --- docs/src/integrate/nodejs-api.md | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/src/integrate/nodejs-api.md b/docs/src/integrate/nodejs-api.md index 744f98295f4..d489cb8b911 100644 --- a/docs/src/integrate/nodejs-api.md +++ b/docs/src/integrate/nodejs-api.md @@ -457,6 +457,49 @@ 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(); + +if (DefaultESLint.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): From bc94c2f33e881c9413bee11fb6c3dc8c6da3d1d1 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 9 Feb 2024 13:10:04 -0500 Subject: [PATCH 3/7] Add tests for loadESLint() to return ESLint --- tests/lib/api.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/lib/api.js b/tests/lib/api.js index d6627e3ad05..6b121e1dbbc 100644 --- a/tests/lib/api.js +++ b/tests/lib/api.js @@ -63,6 +63,12 @@ describe("api", () => { it("should return FlatESLint when useFlatConfig is not provided because we have eslint.config.js", async () => { assert.strictEqual(await api.loadESLint(), FlatESLint); }); + + it("should return ESLint when useFlatConfig is not provided because we have .eslintrc.cjs", async () => { + assert.strictEqual(await api.loadESLint({ + cwd: "./tests/fixtures/config-file/cjs" + }), FlatESLint); + }); }); }); From 92aa85788dd99fdf64b6934111ed8e757bbd0c38 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 9 Feb 2024 13:36:04 -0500 Subject: [PATCH 4/7] Move static property out of constructor for older Node.js versions --- lib/eslint/eslint.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index 24cec11a6a8..7085d5a4de2 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -421,12 +421,6 @@ function compareResultsByFilePath(a, b) { */ class ESLint { - /** - * The type of configuration used by this class. - * @type {string} - */ - static configType = "eslintrc"; - /** * Creates a new instance of the main ESLint API. * @param {ESLintOptions} options The options for this instance. @@ -688,6 +682,13 @@ class ESLint { } } +/** + * The type of configuration used by this class. + * @type {string} + * @static + */ +ESLint.configType = "eslintrc"; + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ From 27c88e0712767d1771db4c2830672b4fa05a52c4 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 9 Feb 2024 13:39:46 -0500 Subject: [PATCH 5/7] Add more tests --- tests/lib/api.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/lib/api.js b/tests/lib/api.js index 6b121e1dbbc..967d6a3ea26 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); }); @@ -69,6 +74,17 @@ describe("api", () => { cwd: "./tests/fixtures/config-file/cjs" }), FlatESLint); }); + + it("should return ESLint 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(), api.ESLint); + }); + + it("should return FlatESLint 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(), FlatESLint); + }); + }); }); From 71ef76f40f12b7aa4b3e58c28541f37471726df6 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 12 Feb 2024 15:48:29 -0500 Subject: [PATCH 6/7] Update tests/lib/api.js Co-authored-by: Milos Djermanovic --- tests/lib/api.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/lib/api.js b/tests/lib/api.js index 967d6a3ea26..b83650735f7 100644 --- a/tests/lib/api.js +++ b/tests/lib/api.js @@ -69,10 +69,10 @@ describe("api", () => { assert.strictEqual(await api.loadESLint(), FlatESLint); }); - it("should return ESLint when useFlatConfig is not provided because we have .eslintrc.cjs", async () => { + it("should return ESLint when useFlatConfig is not provided and there is no eslint.config.js", async () => { assert.strictEqual(await api.loadESLint({ - cwd: "./tests/fixtures/config-file/cjs" - }), FlatESLint); + cwd: os.tmpdir() + }), api.ESLint); }); it("should return ESLint when useFlatConfig is not provided and ESLINT_USE_FLAT_CONFIG is false", async () => { From 96dedf22c9810a6611aa33016d793d82ba455a10 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 13 Feb 2024 12:43:12 -0500 Subject: [PATCH 7/7] Update tests/lib/api.js Co-authored-by: Milos Djermanovic --- tests/lib/api.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/lib/api.js b/tests/lib/api.js index b83650735f7..289886ae9c4 100644 --- a/tests/lib/api.js +++ b/tests/lib/api.js @@ -11,7 +11,8 @@ const assert = require("chai").assert, api = require("../../lib/api"), - { FlatESLint } = require("../../lib/eslint"); + { FlatESLint } = require("../../lib/eslint"), + os = require("os"); //----------------------------------------------------------------------------- // Tests