Skip to content

Commit 52a5fca

Browse files
feat: Support basePath property in config objects (#19879)
* use `config.basePath` for `ignorePatterns` * add tests for user-specified relative `basePath` * fix `--no-ignore` * update types * update docs * update dependencies * mention absolute paths in the docs * update flag to `v10_config_lookup_from_file` * Update tests/lib/eslint/eslint.js Co-authored-by: Francesco Trotta <github@fasttime.org> --------- Co-authored-by: Francesco Trotta <github@fasttime.org>
1 parent 6a0f164 commit 52a5fca

File tree

17 files changed

+605
-42
lines changed

17 files changed

+605
-42
lines changed

docs/src/use/configure/configuration-files.md

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ module.exports = defineConfig([
6666
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:
6767

6868
- `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))
69+
- `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.
6970
- `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.
7071
- `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.
7172
- `extends` - An array of strings, configuration objects, or configuration arrays that contain additional configuration to apply.
@@ -87,7 +88,7 @@ Each configuration object contains all of the information ESLint needs to execut
8788
### Specifying `files` and `ignores`
8889

8990
::: tip
90-
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.
91+
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`.
9192
:::
9293

9394
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:
@@ -346,6 +347,80 @@ export default defineConfig([
346347

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

350+
#### Specifying base path
351+
352+
You can optionally specify `basePath` to apply the configuration object to a specific subdirectory (including its subdirectories).
353+
354+
```js
355+
// eslint.config.js
356+
import { defineConfig } from "eslint/config";
357+
358+
export default defineConfig([
359+
// matches all files in tests and its subdirectories
360+
{
361+
basePath: "tests",
362+
rules: {
363+
"no-undef": "error",
364+
},
365+
},
366+
367+
// matches all files ending with spec.js in tests and its subdirectories
368+
{
369+
basePath: "tests",
370+
files: ["**/*.spec.js"],
371+
languageOptions: {
372+
globals: {
373+
it: "readonly",
374+
describe: "readonly",
375+
},
376+
},
377+
},
378+
379+
// globally ignores tests/fixtures directory
380+
{
381+
basePath: "tests",
382+
ignores: ["fixtures/"],
383+
},
384+
]);
385+
```
386+
387+
In combination with [`extends`](#extending-configurations), multiple configuration objects can be applied to the same subdirectory by specifying `basePath` only once, like this:
388+
389+
```js
390+
// eslint.config.js
391+
import { defineConfig } from "eslint/config";
392+
393+
export default defineConfig([
394+
{
395+
basePath: "tests",
396+
extends: [
397+
// matches all files in tests and its subdirectories
398+
{
399+
rules: {
400+
"no-undef": "error",
401+
},
402+
},
403+
404+
// matches all files ending with spec.js in tests and its subdirectories
405+
{
406+
files: ["**/*.spec.js"],
407+
languageOptions: {
408+
globals: {
409+
it: "readonly",
410+
describe: "readonly",
411+
},
412+
},
413+
},
414+
415+
// globally ignores tests/fixtures directory
416+
{
417+
ignores: ["fixtures/"],
418+
},
419+
],
420+
},
421+
]);
422+
```
423+
349424
#### Cascading Configuration Objects
350425

351426
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:
@@ -698,12 +773,12 @@ In this case, ESLint does not search for `eslint.config.js` and instead uses `so
698773
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.
699774
:::
700775

701-
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.
776+
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.
702777

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

705780
```shell
706-
npx eslint --flag unstable_config_lookup_from_file .
781+
npx eslint --flag v10_config_lookup_from_file .
707782
```
708783

709784
For more information about using feature flags, see [Feature Flags](../../flags/).

lib/config/config-loader.js

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -642,40 +642,13 @@ class ConfigLoader {
642642

643643
// append command line ignore patterns
644644
if (ignorePatterns && ignorePatterns.length > 0) {
645-
let relativeIgnorePatterns;
646-
647-
/*
648-
* If the config file basePath is different than the cwd, then
649-
* the ignore patterns won't work correctly. Here, we adjust the
650-
* ignore pattern to include the correct relative path. Patterns
651-
* passed as `ignorePatterns` are relative to the cwd, whereas
652-
* the config file basePath can be an ancestor of the cwd.
653-
*/
654-
if (basePath === cwd) {
655-
relativeIgnorePatterns = ignorePatterns;
656-
} else {
657-
// relative path must only have Unix-style separators
658-
const relativeIgnorePath = path
659-
.relative(basePath, cwd)
660-
.replace(/\\/gu, "/");
661-
662-
relativeIgnorePatterns = ignorePatterns.map(pattern => {
663-
const negated = pattern.startsWith("!");
664-
const basePattern = negated ? pattern.slice(1) : pattern;
665-
666-
return (
667-
(negated ? "!" : "") +
668-
path.posix.join(relativeIgnorePath, basePattern)
669-
);
670-
});
671-
}
672-
673645
/*
674646
* Ignore patterns are added to the end of the config array
675647
* so they can override default ignores.
676648
*/
677649
configs.push({
678-
ignores: relativeIgnorePatterns,
650+
basePath: cwd,
651+
ignores: ignorePatterns,
679652
});
680653
}
681654

lib/config/flat-config-array.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const { Config } = require("./config");
2121
/**
2222
* Fields that are considered metadata and not part of the config object.
2323
*/
24-
const META_FIELDS = new Set(["name"]);
24+
const META_FIELDS = new Set(["name", "basePath"]);
2525

2626
/**
2727
* Wraps a config error with details about where the error occurred.

lib/eslint/eslint.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ class ESLint {
463463
warningService,
464464
};
465465

466-
this.#configLoader = linter.hasFlag("unstable_config_lookup_from_file")
466+
this.#configLoader = linter.hasFlag("v10_config_lookup_from_file")
467467
? new ConfigLoader(configLoaderOptions)
468468
: new LegacyConfigLoader(configLoaderOptions);
469469

lib/shared/flags.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const activeFlags = new Map([
2929
["test_only", "Used only for testing."],
3030
["test_only_2", "Used only for testing."],
3131
[
32-
"unstable_config_lookup_from_file",
32+
"v10_config_lookup_from_file",
3333
"Look up `eslint.config.js` from the file being linted.",
3434
],
3535
[
@@ -73,6 +73,14 @@ const inactiveFlags = new Map([
7373
replacedBy: null,
7474
},
7575
],
76+
[
77+
"unstable_config_lookup_from_file",
78+
{
79+
description:
80+
"Look up `eslint.config.js` from the file being linted.",
81+
replacedBy: "v10_config_lookup_from_file",
82+
},
83+
],
7684
]);
7785

7886
/**

lib/types/index.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,6 +1776,13 @@ export namespace Linter {
17761776
*/
17771777
name?: string;
17781778

1779+
/**
1780+
* Path to the directory where the configuration object should apply.
1781+
* `files` and `ignores` patterns in the configuration object are
1782+
* interpreted as relative to this path.
1783+
*/
1784+
basePath?: string;
1785+
17791786
/**
17801787
* An array of glob patterns indicating the files that the configuration
17811788
* object should apply to. If not specified, the configuration object applies

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@
106106
"dependencies": {
107107
"@eslint-community/eslint-utils": "^4.2.0",
108108
"@eslint-community/regexpp": "^4.12.1",
109-
"@eslint/config-array": "^0.20.1",
110-
"@eslint/config-helpers": "^0.2.1",
109+
"@eslint/config-array": "^0.21.0",
110+
"@eslint/config-helpers": "^0.3.0",
111111
"@eslint/core": "^0.14.0",
112112
"@eslint/eslintrc": "^3.3.1",
113113
"@eslint/js": "9.29.0",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
let foo;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
let foo;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
let foo;

0 commit comments

Comments
 (0)