Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 78 additions & 3 deletions docs/src/use/configure/configuration-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ module.exports = defineConfig([
Each configuration object contains all of the information ESLint needs to execute on a set of files. Each configuration object is made up of these properties:

- `name` - A name for the configuration object. This is used in error messages and [config inspector](https://github.com/eslint/config-inspector) to help identify which configuration object is being used. ([Naming Convention](#configuration-naming-conventions))
- `basePath` - A string specifying the path to a subdirectory to which the configuration object should apply to. It can be a relative or an absolute path.
- `files` - An array of glob patterns indicating the files that the configuration object should apply to. If not specified, the configuration object applies to all files matched by any other configuration object.
- `ignores` - An array of glob patterns indicating the files that the configuration object should not apply to. If not specified, the configuration object applies to all files matched by `files`. If `ignores` is used without any other keys in the configuration object, then the patterns act as [global ignores](#globally-ignoring-files-with-ignores) and it gets applied to every configuration object.
- `extends` - An array of strings, configuration objects, or configuration arrays that contain additional configuration to apply.
Expand All @@ -87,7 +88,7 @@ Each configuration object contains all of the information ESLint needs to execut
### Specifying `files` and `ignores`

::: tip
Patterns specified in `files` and `ignores` use [`minimatch`](https://www.npmjs.com/package/minimatch) syntax and are evaluated relative to the location of the `eslint.config.js` file. If using an alternate config file via the `--config` command line option, then all patterns are evaluated relative to the current working directory.
Patterns specified in `files` and `ignores` use [`minimatch`](https://www.npmjs.com/package/minimatch) syntax and are evaluated relative to the location of the `eslint.config.js` file. If using an alternate config file via the `--config` command line option, then all patterns are evaluated relative to the current working directory. In case the configuration object has the `basePath` property with a relative path, the subdirectory it specifies is evaluated relative to the location of the `eslint.config.js` file (or relative to the current working directory if using an alternate config file via the `--config` command line option). In configuration objects with the `basePath` property, patterns specified in `files` and `ignores` are evaluated relative to the subdirectory represented by the `basePath`.
:::

You can use a combination of `files` and `ignores` to determine which files the configuration object should apply to and which not. Here's an example:
Expand Down Expand Up @@ -346,6 +347,80 @@ export default defineConfig([

For more information and examples on configuring rules regarding `ignores`, see [Ignore Files](ignore).

#### Specifying base path

You can optionally specify `basePath` to apply the configuration object to a specific subdirectory (including its subdirectories).

```js
// eslint.config.js
import { defineConfig } from "eslint/config";

export default defineConfig([
// matches all files in tests and its subdirectories
{
basePath: "tests",
rules: {
"no-undef": "error",
},
},

// matches all files ending with spec.js in tests and its subdirectories
{
basePath: "tests",
files: ["**/*.spec.js"],
languageOptions: {
globals: {
it: "readonly",
describe: "readonly",
},
},
},

// globally ignores tests/fixtures directory
{
basePath: "tests",
ignores: ["fixtures/"],
},
]);
```

In combination with [`extends`](#extending-configurations), multiple configuration objects can be applied to the same subdirectory by specifying `basePath` only once, like this:

```js
// eslint.config.js
import { defineConfig } from "eslint/config";

export default defineConfig([
{
basePath: "tests",
extends: [
// matches all files in tests and its subdirectories
{
rules: {
"no-undef": "error",
},
},

// matches all files ending with spec.js in tests and its subdirectories
{
files: ["**/*.spec.js"],
languageOptions: {
globals: {
it: "readonly",
describe: "readonly",
},
},
},

// globally ignores tests/fixtures directory
{
ignores: ["fixtures/"],
},
],
},
]);
```

#### Cascading Configuration Objects

When more than one configuration object matches a given filename, the configuration objects are merged with later objects overriding previous objects when there is a conflict. For example:
Expand Down Expand Up @@ -698,12 +773,12 @@ In this case, ESLint does not search for `eslint.config.js` and instead uses `so
This feature is experimental and its details may change before being finalized. This behavior will be the new lookup behavior starting in v10.0.0, but you can try it today using a feature flag.
:::

You can use the `unstable_config_lookup_from_file` flag to change the way ESLint searches for configuration files. Instead of searching from the current working directory, ESLint will search for a configuration file by first starting in the directory of the file being linted and then searching up its ancestor directories until it finds a `eslint.config.js` file (or any other extension of configuration file). This behavior is better for monorepos, where each subdirectory may have its own configuration file.
You can use the `v10_config_lookup_from_file` flag to change the way ESLint searches for configuration files. Instead of searching from the current working directory, ESLint will search for a configuration file by first starting in the directory of the file being linted and then searching up its ancestor directories until it finds a `eslint.config.js` file (or any other extension of configuration file). This behavior is better for monorepos, where each subdirectory may have its own configuration file.

To use this feature on the command line, use the `--flag` flag:

```shell
npx eslint --flag unstable_config_lookup_from_file .
npx eslint --flag v10_config_lookup_from_file .
```

For more information about using feature flags, see [Feature Flags](../../flags/).
Expand Down
31 changes: 2 additions & 29 deletions lib/config/config-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -642,40 +642,13 @@ class ConfigLoader {

// append command line ignore patterns
if (ignorePatterns && ignorePatterns.length > 0) {
let relativeIgnorePatterns;

/*
* If the config file basePath is different than the cwd, then
* the ignore patterns won't work correctly. Here, we adjust the
* ignore pattern to include the correct relative path. Patterns
* passed as `ignorePatterns` are relative to the cwd, whereas
* the config file basePath can be an ancestor of the cwd.
*/
if (basePath === cwd) {
relativeIgnorePatterns = ignorePatterns;
} else {
// relative path must only have Unix-style separators
const relativeIgnorePath = path
.relative(basePath, cwd)
.replace(/\\/gu, "/");

relativeIgnorePatterns = ignorePatterns.map(pattern => {
const negated = pattern.startsWith("!");
const basePattern = negated ? pattern.slice(1) : pattern;

return (
(negated ? "!" : "") +
path.posix.join(relativeIgnorePath, basePattern)
);
});
}

/*
* Ignore patterns are added to the end of the config array
* so they can override default ignores.
*/
configs.push({
ignores: relativeIgnorePatterns,
basePath: cwd,
ignores: ignorePatterns,
});
}

Expand Down
2 changes: 1 addition & 1 deletion lib/config/flat-config-array.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const { Config } = require("./config");
/**
* Fields that are considered metadata and not part of the config object.
*/
const META_FIELDS = new Set(["name"]);
const META_FIELDS = new Set(["name", "basePath"]);

/**
* Wraps a config error with details about where the error occurred.
Expand Down
2 changes: 1 addition & 1 deletion lib/eslint/eslint.js
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ class ESLint {
warningService,
};

this.#configLoader = linter.hasFlag("unstable_config_lookup_from_file")
this.#configLoader = linter.hasFlag("v10_config_lookup_from_file")
? new ConfigLoader(configLoaderOptions)
: new LegacyConfigLoader(configLoaderOptions);

Expand Down
10 changes: 9 additions & 1 deletion lib/shared/flags.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const activeFlags = new Map([
["test_only", "Used only for testing."],
["test_only_2", "Used only for testing."],
[
"unstable_config_lookup_from_file",
"v10_config_lookup_from_file",
"Look up `eslint.config.js` from the file being linted.",
],
[
Expand Down Expand Up @@ -73,6 +73,14 @@ const inactiveFlags = new Map([
replacedBy: null,
},
],
[
"unstable_config_lookup_from_file",
{
description:
"Look up `eslint.config.js` from the file being linted.",
replacedBy: "v10_config_lookup_from_file",
},
],
]);

/**
Expand Down
7 changes: 7 additions & 0 deletions lib/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1776,6 +1776,13 @@ export namespace Linter {
*/
name?: string;

/**
* Path to the directory where the configuration object should apply.
* `files` and `ignores` patterns in the configuration object are
* interpreted as relative to this path.
*/
basePath?: string;

/**
* An array of glob patterns indicating the files that the configuration
* object should apply to. If not specified, the configuration object applies
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.20.1",
"@eslint/config-helpers": "^0.2.1",
"@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.14.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.29.0",
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/config-base-path/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let foo;
1 change: 1 addition & 0 deletions tests/fixtures/config-base-path/b.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let foo;
1 change: 1 addition & 0 deletions tests/fixtures/config-base-path/subdir/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let foo;
1 change: 1 addition & 0 deletions tests/fixtures/config-base-path/subdir/b.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let foo;
1 change: 1 addition & 0 deletions tests/fixtures/config-lookup-ignores-3/subdir/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let foo;
1 change: 1 addition & 0 deletions tests/fixtures/config-lookup-ignores-3/subdir/b.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let foo;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default [
{
rules: {
"no-unused-vars": "warn"
}
}
];
4 changes: 2 additions & 2 deletions tests/lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -3212,8 +3212,8 @@ describe("cli", () => {
});
});

describe("unstable_config_lookup_from_file", () => {
const flag = "unstable_config_lookup_from_file";
describe("v10_config_lookup_from_file", () => {
const flag = "v10_config_lookup_from_file";

it("should throw an error when text is passed and no config file is found", async () => {
await stdAssert.rejects(
Expand Down
Loading
Loading