From 1120b9b7b97f10f059d8b7ede19de2572f892366 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 14 Feb 2024 01:49:05 -0700 Subject: [PATCH] feat: Add loadESLint() API method for v8 (#18098) * feat: Add loadESLint() API method for v8 refs #18075 * Update docs * Add tests for loadESLint() to return ESLint * Move static property out of constructor for older Node.js versions * Add more tests * Update tests/lib/api.js Co-authored-by: Milos Djermanovic * Update tests/lib/api.js Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- docs/src/integrate/nodejs-api.md | 43 ++++++++++++++++++++++++++ lib/api.js | 30 +++++++++++++++++- lib/eslint/eslint.js | 7 +++++ lib/eslint/flat-eslint.js | 13 ++++++-- tests/lib/api.js | 53 +++++++++++++++++++++++++++++++- tests/lib/eslint/eslint.js | 5 +++ tests/lib/eslint/flat-eslint.js | 4 +++ 7 files changed, 151 insertions(+), 4 deletions(-) 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): 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..7085d5a4de2 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -682,6 +682,13 @@ class ESLint { } } +/** + * The type of configuration used by this class. + * @type {string} + * @static + */ +ESLint.configType = "eslintrc"; + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ 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..289886ae9c4 100644 --- a/tests/lib/api.js +++ b/tests/lib/api.js @@ -10,7 +10,9 @@ //----------------------------------------------------------------------------- const assert = require("chai").assert, - api = require("../../lib/api"); + api = require("../../lib/api"), + { FlatESLint } = require("../../lib/eslint"), + os = require("os"); //----------------------------------------------------------------------------- // Tests @@ -18,6 +20,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 +43,49 @@ describe("api", () => { it("should have SourceCode exposed", () => { assert.isFunction(api.SourceCode); }); + + describe("loadESLint", () => { + + afterEach(() => { + delete process.env.ESLINT_USE_FLAT_CONFIG; + }); + + 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); + }); + + it("should return ESLint when useFlatConfig is not provided and there is no eslint.config.js", async () => { + assert.strictEqual(await api.loadESLint({ + cwd: os.tmpdir() + }), api.ESLint); + }); + + 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); + }); + + }); + }); 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 {