Skip to content
Open
Changes from all 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
245 changes: 245 additions & 0 deletions designs/2026-custom-base-path/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
- Repo: `eslint/eslint`, possibly `eslint/rewrite`
- Start Date: 2026-02-03
- RFC PR: eslint/rfcs#145
- Authors: Francesco Trotta

# Support `--base-path` Option

## Summary

This document proposes a mechanism for configuring and linting files relative to a specified directory, regardless of the current working directory (CWD) or where the configuration file is located. This is achieved by introducing a new CLI option `--base-path`, and a corresponding `basePath` option on the `ESLint` constructor.

## Motivation

In the legacy eslintrc configuration system, users could lint files in arbitrary locations by explicitly specifying a config file (for example, via `--config`).
ESLint would resolve the target file paths relative to the current working directory and lint them, even though it wasn't possible to configure those files with the provided config.

Flat config, on the other hand, enforces that only files under the current base path can be configured and linted. When a config file is explicitly provided, the base path is the current working directory. This has caused problems for users migrating from legacy config to flat config, where linting files outside the current working directory previously worked. See [related discussions](#related-discussions) below for some examples.

To support the legacy use case while allowing users to configure files independently, we propose introducing a new option that will allow users to specify a custom base path.

## Detailed Design

### Current Behavior
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would also be helpful to provide some context as to why ESLint has this behavior today. Going back to first principles may help in this discussion.


The base path is the directory that defines which files a `ConfigArray` can lint.
Files outside that base path cannot be linted, even if they are matched by a config object.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find a reference for this specific design in flat config. If someone can point me to a relevant discussion, I could elaborate.


Today, ESLint determines the base path as follows:
- the current working directory, if one of `--config` or `--no-config-lookup` is passed
- otherwise, the directory where the config file is located. If a project contains different config files, each config file has its own base path.

The main reason for using CWD as the base path when a config file is explicitly specified is to support configs files located in a different folder (e.g. with shared resources), from where they can be used to lint different directories in the codebase.

Internally, the base path is stored as a `basePath` property on `ConfigArray` (not to be confused with [`basePath`](https://eslint.org/docs/latest/use/configure/configuration-files#specify-base-path) in config objects). It serves two purposes:
- it limits which files can be linted
- it provides the default `basePath` for config objects that don't have one explicitly set. The config object's base path is then used to resolve that object's `files` and `ignores`

Note that this is different from other config systems where the two functions may be separate (for example, TypeScript separates concepts like `rootDir` from the location used to resolve relative paths).

### Proposed Change

This proposal adds a way to set a custom base path that overrides the `basePath` of each config array.

For users and integration developers, this introduces:
- a new CLI option, `--base-path`
- a corresponding `basePath` option on the `ESLint` constructor

The base path must be a non-empty string and a valid absolute or relative path. If relative, it will be resolved against the current working directory, or against the `cwd` option when provided.

When `basePath` is specified and no file patterns are passed on the command line, ESLint will enumerate files in that base path. For example:

```shell
npx eslint --config=eslint.config.js --base-path=../src
```

will behave the same as

```shell
npx eslint --config=eslint.config.js --base-path=../src ../src
```

### Example Usage

The use case proposed in eslint/eslint#19118 involves linting files in a sibling directory of the current directory.

```text
(root)
├─ app (cwd)
│ ├─ eslint.config.mjs
│ └─ ... (files to lint)
└─ tmp
└─ ... (more files to lint)
```

For this case, `--base-path` should point to a parent of both directories, i.e. `..`:

```shell
npx eslint --config=eslint.config.mjs --base-path=.. . ../tmp
```

Relative paths in the config file will now be resolved relative to `..`. This includes `files` and `ignores` settings in config objects that don't include a `basePath`, or the config object's `basePath` itself. So, a config object that specifies different settings for the current directory and its sibling would look like this:

<a name="example-1"></a>

```js
export default defineConfig([
{
name: "app-config",
files: ["app/**/*.{js,cjs,mjs}"],
...myAppConfig
},
{
name: "tmp-config",
files: ["tmp/**/*.{js,cjs,mjs}"],
...myTmpConfig
},
]);
```

or similarly, using a `basePath`:

<a name="example-2"></a>

```js
export default defineConfig([
{
name: "app-config",
basePath: "app",
files: ["**/*.{js,cjs,mjs}"],
...myAppConfig
},
{
name: "tmp-config",
basePath: "tmp",
files: ["**/*.{js,cjs,mjs}"],
...myTmpConfig
},
]);
```

### Alternative: Maintain Reference Location

The above solution is fully backward-compatible but it's hard to extend to existing configurations because changing the base path also means that all relative paths and patterns in existing configs (in `files`, `ignores` and `basePath`) must be modified to be relative to the new base path.

For example, in the example in the previous section, a project may already have a config that lints files in the current directory (`app`) like this:

```js
export default defineConfig([
{
name: "app-config",
files: ["**/*.{js,cjs,mjs}"],
...myAppConfig
},
]);
```
Comment on lines +123 to +135
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the other hand, if a config object with files: ["**/*.{js,cjs,mjs}"] is intended to apply to all .js, .cjs, and .mjs files regardless of directories, it would need to be updated to e.g., files: ["../**/*.{js,cjs,mjs}"]?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, most of the default config objects (https://github.com/eslint/eslint/blob/main/lib/config/default-config.js) would only apply in the directory where the config file is?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the other hand, if a config object with files: ["**/*.{js,cjs,mjs}"] is intended to apply to all .js, .cjs, and .mjs files regardless of directories, it would need to be updated to e.g., files: ["../**/*.{js,cjs,mjs}"]?

I just tested that. A pattern like "**/*.{js,cjs,mjs}" would already match files in the parent directory, probably because universal patterns apply to all directories regardless of base path.

Also, most of the default config objects (https://github.com/eslint/eslint/blob/main/lib/config/default-config.js) would only apply in the directory where the config file is?

The ".git/" ignore pattern would apply to the directory that contains the config file, which seems suboptimal for the scenario in the example. Otherwise, the default config objects would behave the same.


To extend this config to also lint files in `../tmp`, you must pass `--base-path=..`. If the existing config object(s) in the config should still apply only to the current directory, they must also be changed either by modifying the `files` patterns as in [the first example](#example-1), or by adding an explicit `basePath` as in [the second example](#example-2). Note that in both cases, the name of the current working directory (`app`) must be explicitly added, because there is no longer an implicit way to reference it from the config.

To avoid this inconvenience, we could add a new internal property to config arrays, tentatively named `referenceLocation`. This property is set once by ESLint, at the time a `ConfigArray` instance is created, and it holds the directory that is currently used as a `basePath` for the config array (CWD or config file location). When computing the effective `basePath` for each config object, ESLint would:

1. First check whether the config object specifies an explicit `basePath`.
- If that `basePath` is absolute, it is used as-is.
- If that `basePath` is relative, it is resolved against `referenceLocation`.
2. If the config object does not specify `basePath`, `referenceLocation` is used as its `basePath`.

This solution will ensure that existing config arrays can be extended to lint additional files with a new common directory root without changing existing config objects. For example, with this alternative, the example above can be extended to lint `../tmp` without changing the other config object(s), like this:

```js
export default defineConfig([
{
name: "app-config",
files: ["**/*.{js,cjs,mjs}"],
...myAppConfig
},
{
name: "tmp-config",
files: ["../tmp/**/*.{js,cjs,mjs}"],
...myTmpConfig
},
]);
```

With this alternative, `--base-path` would define which files are in scope for file enumeration and linting, but resolution of `basePath` values in config objects would remain unchanged.

While still maintaining the changes non-breaking and backward-compatible, this alternative solution requires changing `@eslint/config-array` to introduce a new property on `ConfigArray` instances. It will also add complexity to the implementation.

### Testing Impact

The implementation should include coverage for:
- CLI `--base-path` validation (empty string, invalid values, relative and absolute paths)
- file enumeration behavior when `--base-path` is provided and no file patterns are passed on the command line
- interaction with `--config` and `--no-config-lookup`
- interaction with the ESLint constructor `cwd` option when resolving a relative `basePath`
- behavior when config lookup is enabled (depending on the decision for `--base-path` support in that mode)

### Preview

Prototype branches for the proposed implementations are available for testing and previewing:
- [issue-19118](https://github.com/eslint/eslint/tree/issue-19118): prototype implementation for the main proposal
- [issue-19118-alt](https://github.com/eslint/eslint/tree/issue-19118-alt): prototype implementation for the [alternative proposal](#alternative-maintain-reference-location)

## Documentation

- [CLI options documentation](https://eslint.org/docs/latest/use/command-line-interface#options) needs to include the `--base-path` option
- [`ESLint` constructor documentation](https://eslint.org/docs/latest/integrate/nodejs-api#-new-eslintoptions) needs to include `options.basePath`
- [Configuration Migration Guide](https://eslint.org/docs/latest/use/configure/migration-guide) should mention the use case of linting files outside the current directory
- if a new `ConfigArray` option is added, [`@eslint/config-array`](https://github.com/eslint/rewrite/tree/main/packages/config-array#readme) documentation should be updated

## Drawbacks

Besides the added maintenance burden, this change will add complexity to the configuration, in terms of logic, documentation, and testing. Users and maintainers will need to reason about an additional parameter that plays a role in the behavior of a config.

## Backwards Compatibility Analysis

This proposal is backward-compatible and does not introduce any breaking changes.

## Alternatives

### Change `**/` semantics to match outside the base path
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still feel like this approach is the cleanest option. I know there are some questions about ignores and some other edge cases, but I feel like most users would expect **/*.js to match all JavaScript files passed on the command line.

Adding one or more new properties to config objects and then also a command line option feels like building a house of cards.

We could try this under an unstable feature flag to let people give it a try without committing to any changes.

Another alternative, which may be a bit more dramatic, is to enable the matchBase option of minimatch. This would enable patterns such as foo.js to match all paths that end with foo.js (likewise, *.js would match all JS files). This might actually work better because it does not change the behavior of ** (I think...).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still feel like this approach is the cleanest option. I know there are some questions about ignores and some other edge cases, but I feel like most users would expect **/*.js to match all JavaScript files passed on the command line.

I can try to prepare a prototype for that so we can test how it works. This will need changes in @eslint/config-array because of the ability to configure external files.

Adding one or more new properties to config objects and then also a command line option feels like building a house of cards.

Just to clarify: no new config object properties are being proposed in this RFC.


One proposal was to make patterns prefixed with `**/` (such as `**/*.js`) match files regardless of base path.

This approach has some drawbacks:

- it creates edge cases for `ignores` (for example, `**/foo/` might unexpectedly match ancestor path segments)
- it does not provide a clear way to target only specific paths outside the base path
- it would require matching against absolute paths in some cases, which is harder to reason about
- it changes established glob semantics and is potentially breaking

To address the last point and avoid a breaking change, additional solutions have been proposed:

- adding a feature flag `v10_file_matching` for changed glob semantics
- adding a dedicated config key `matchAbsolute` for absolute matching

### Rely on config lookup improvements only

The `unstable_config_lookup_from_file` / `v10_config_lookup_from_file` flag was considered as a possible way to cover these scenarios.

User feedback pointed out that this does not solve the specific problem of linting files outside the effective base path when using a centralized config/tooling setup.

### Add only `basePath` on config objects

Adding `basePath` to config objects (discussed in related design work, see [RFC \#131](https://github.com/eslint/rfcs/tree/main/designs/2025-base-path-in-config-objects)) was also considered as a standalone solution. However, this change alone was not sufficient for this issue because files external to the base path were still being ignored as external.

## Open Questions

### Path-resolution model when `--base-path` is provided

- Main proposal: relative config paths (`files`, `ignores`, and config-object `basePath`) resolve from `--base-path`.
- Alternative proposal: `--base-path` defines linting scope, while relative config-object `basePath` values resolve from `referenceLocation` (CWD or config file location).

Which model should ESLint adopt?

### `--base-path` with config lookup?

Should `--base-path` be allowed when config lookup is enabled (i.e. neither `--config` nor `--no-config-lookup` is passed)? In that case, ESLint searches for a config file in a file's directory and its ancestors. This means that config files outside that line of search are not considered, so that each file is required to have its associated config in an expected location. In this setup, I'm not sure how `--base-path` would be useful.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it shouldn't be allowed as it would be effectively ignored so seems better to throw an error to avoid confusion.


## Help Needed

I can implement this RFC.

## Related Discussions

- eslint/eslint#19118
- eslint/eslint#18806