Skip to content

Commit

Permalink
feat: Add loadESLint() API method for v8 (#18098)
Browse files Browse the repository at this point in the history
* 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 <milos.djermanovic@gmail.com>

* Update tests/lib/api.js

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

---------

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
  • Loading branch information
nzakas and mdjermanovic committed Feb 14, 2024
1 parent 5b8c363 commit 1120b9b
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 4 deletions.
43 changes: 43 additions & 0 deletions docs/src/integrate/nodejs-api.md
Expand Up @@ -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):
Expand Down
30 changes: 29 additions & 1 deletion lib/api.js
Expand Up @@ -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<ESLint|LegacyESLint>} 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
Expand Down
7 changes: 7 additions & 0 deletions lib/eslint/eslint.js
Expand Up @@ -682,6 +682,13 @@ class ESLint {
}
}

/**
* The type of configuration used by this class.
* @type {string}
* @static
*/
ESLint.configType = "eslintrc";

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
Expand Down
13 changes: 11 additions & 2 deletions lib/eslint/flat-eslint.js
Expand Up @@ -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<boolean>} 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;
Expand All @@ -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));
}
}

Expand Down
53 changes: 52 additions & 1 deletion tests/lib/api.js
Expand Up @@ -10,14 +10,20 @@
//-----------------------------------------------------------------------------

const assert = require("chai").assert,
api = require("../../lib/api");
api = require("../../lib/api"),
{ FlatESLint } = require("../../lib/eslint"),
os = require("os");

//-----------------------------------------------------------------------------
// Tests
//-----------------------------------------------------------------------------

describe("api", () => {

it("should have ESLint exposed", () => {
assert.isFunction(api.ESLint);
});

it("should have RuleTester exposed", () => {
assert.isFunction(api.RuleTester);
});
Expand All @@ -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);
});

});

});
5 changes: 5 additions & 0 deletions tests/lib/eslint/eslint.js
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions tests/lib/eslint/flat-eslint.js
Expand Up @@ -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 {
Expand Down

0 comments on commit 1120b9b

Please sign in to comment.