From afae8b0d8cabdaa0cb1dc6d79d038785056a501d Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 22 Jan 2019 10:14:04 -0800 Subject: [PATCH 01/78] New: Config File Simplification --- designs/2019-config-simplification/README.md | 541 +++++++++++++++++++ 1 file changed, 541 insertions(+) create mode 100644 designs/2019-config-simplification/README.md diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md new file mode 100644 index 00000000..b436c662 --- /dev/null +++ b/designs/2019-config-simplification/README.md @@ -0,0 +1,541 @@ +- Start Date: 2019-01-20 +- RFC PR: TBD +- Authors: Nicholas C. Zakas (@nzakas) + +# Config File Simplification + +## Summary + +This proposal provides a way to simplify configuration of ESLint through a new configuration file format. The configuration file format is written in JavaScript and removes several existing configuration keys in favor of allowing the user to manually create them. + +## Motivation + +The ESLint configuration file format (`.eslintrc`) hasn't changed much since ESLint was first released. As we have continued to add new features, the configuration file format has continued to be extended and evolve into a state where it is the most complicated part of ESLint. The `.eslintrc` way of doing things means a variety of different merging strategies (cascading, extending, overriding), loading strategies (for plugins, processors, parsers), and naming conventions (`eslint-config-*`, `eslint-plugin-*`). + +Although `.eslintrc` has proven to be a very robust format, it is also limited and unable to easily accommodate feature requests that the community favors, such as: + +1. [bundling plugins with configs](https://github.com/eslint/eslint/issues/3458) +1. [specifying ignore patterns in the config](https://github.com/eslint/eslint/issues/10891) +1. [specifying `--ext` information in the config](https://github.com/eslint/eslint/issues/11223) +1. [using `extends` in `overrides`](https://github.com/eslint/eslint/issues/8813) + +The only reason that these requests are difficult to implement in ESLint is because of how complex the `.eslintrc` configuration format is. Any changes made to any part of `.eslintrc` processing end up affecting millions of ESLint installations, so we have ended up stuck. The complicated parts of `.eslintrc` include: + +1. Resolution behavior of modules (`plugins`, `parser`, `extends`) +1. Merging of cascading config files in a directory structure +1. Merging of config files via `extends` +1. Overriding of configuration via `overrides` + +All of these complexities arise from `.eslintrc` format of describing what should happen rather than how it should happen. We keep trying to anticipate how users want things to happen and guessing wrong has led to increased complexity. + +This RFC proposes a fresh start for configuration in ESLint that takes into account all of the requests we've received over the years. By simplifying and stripping configuration we can actually make a configuration system that is flexible enough to accomodate changes in the future and easy enough to implement that we won't be afraid to do so. + +## Detailed Design + +Design Summary: + +1. Introduce a new `.eslint.js` configuration file format +1. Searching for `.eslint.js` starts from the current working directory and continues up +1. All `.eslint.js` files are treated as if they have `root: true` +1. There is no automatic merging of config files +1. The `.eslint.js` configuration is not serializable and objects such as functions and plugins may be added directly into configuration +1. Remove the concept of environments (`env`) +1. Remove `.eslintignore` and `--ignore-file` (no longer necessary) +1. Remove `--rulesdir` +1. Create a `@eslint/config` package to help users merge configs + +### The `.eslint.js` File + +The `.eslint.js` file is a JavaScript file (there is no JSON or YAML equivalent) that exports a `config` object: + +```js +exports.config = { + files: "*.js", + ignore: "*.test.js", + globals: {}, + processor: object, + parser: object, + ruledefs: {}, + rules: {} +}; +``` + +The following keys are new to the `.eslint.js` format: + +* `files` - **Required.** Determines the global pattern that this configuration applies to and also is used as the default pattern to search for when ESLint is executed on the command line. +* `ignore` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this glob pattern are subtracted from the files specified in `files`. +* `ruledefs` - Contains definitions for rules grouped by a specific name. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option. + +The following keys are specified the same as in `.eslintrc` files: + +* `globals` +* `rules` +* `parserOptions` + +The following keys are specified differently than in `.eslintrc` files: + +* `parser` - an object in `.eslint.js` files (a string in `.eslintrc`) +* `processor` - an object in `.eslint.js` files (a string in `.eslintrc`) + +Each of these keys used to require one or more strings specifying module(s) to load in `.eslintrc`. In `.eslint.js`, these are all objects, requiring users to manually specify the objects to use. + +The following keys are invalid in `.eslint.js`: + +* `env` - responsibility of the user +* `extends` - responsibility of the user +* `overrides` - responsibility of the user +* `plugins` - replaced by `ruledefs` +* `root` - always considered `true` + +Each of these keys represent different ways of augmenting how configuration is calculated and all of that responsibility now falls on the user. + +#### Referencing Plugin Rules + +The `plugins` key in `.eslintrc` was an array of strings indicating the plugins to load, allowing you to specify processors, rules, etc., by referencing the name of the plugin. It's no longer necessary to indicate the plugins to load because that is done directly in the `.eslint.js` file. For example, consider this `.eslintrc` file: + +```yaml +plugins: + - react + +rules: + react/jsx-uses-react: error +``` + +This file tells ESLint to load `eslint-plugin-react` and then configure a rule from that plugin. The `react/` is automatically preprended to the rule by ESLint for easy reference. + +In `.eslint.js`, the same configuration is achieved using a `ruledefs` key: + +```js +const reactPlugin = require("eslint-plugin-react"); + +exports.config = { + ruledefs: { + react: reactPlugin.rules + }, + rules: { + "react/jsx-uses-react": "error" + } +}; +``` + +Here, it is the `ruledefs` that assigns the name `react` to the rules from `eslint-plugin-react`. The reference to `react/` in a rule will always look up that value in the `ruledefs` key. + +#### Referencing Parsers and Processors + +In `.eslintrc`, the `parser` and `processor` keys required strings to be specified, such as: + +```yaml +plugins: ["markdown"] +parser: "babel-eslint" +processor: "markdown/markdown" +``` + +In `.eslint.js`, you would need to pass the references directly, such as: + +```js +exports.config = { + parser: require("babel-eslint"), + processor: require("eslint-plugin-markdown).processors.markdown +}; +``` + +In both cases, users now must pass a direct object reference. This has the benefit of using the builtin Node.js module resolution system or allowing users to specify their own. There is never of question of where the modules will be resolved from. + +#### Applying an Environment + +Unlike with `.eslintrc` files, there is no `env` key in `.eslint.js`. Users can mimmick the behavior of `env` by assigning directly to the `globals` key: + +```js +const globals = require("globals"); + +exports.config = { + globals: { + MyGlobal: true, + ...globals.browser + } +}; +``` + +This effectively duplicates the use of `env: { browser: true }` in ESLint. + +**Note:** This would allow us to stop environments in ESLint. We could just tell people to use `globals` in their config and allow them to specify which version of `globals` they want to use. + +#### Extending Another Config + +With the removal of `extends`, it's now up to the users to inherit from other configs. That can easily be done in the following manner: + +```js +const standardConfig = require("eslint-config-standard"); + +standardConfig.rules.semi = ["error", "always"]; + +exports.config = standardConfig; +``` + +This config extends `eslint-config-standard` by first importing and then making modifications before exporting the modified config. + +#### Overriding Configuration Based on File Patterns + +Whereas `.eslintrc` had an `overrides` key that made a hierarchical structure, the `.eslint.js` file does not have any such hierarchy. Instead, users can return an array of configs that should be used. For example, consider this `.eslintrc` config: + +```yaml +plugins: ["react"] +rules: + react/jsx-uses-react: error + semi: error + +overrides: + - files: "*.md" + plugins: ["markdown"], + processor: "markdown/markdown" +``` + +This can be written in `.eslint.js` as an array of two configs: + +```js +exports.config = [ + { + files: "*.js", + ruledefs: { + react: require("eslint-plugin-react").rules, + }, + rules: { + "react/jsx-uses-react": "error", + semi: "error" + } + }, + { + files: "*.md", + processor: require("eslint-plugin-markdown").processors.markdown + } +]; +``` + +When ESLint uses this config, it will check each `files` pattern to determine which config applies. In this way, returning an array acts exactly the same as the array in `overrides`, with the exception that no configuration options are inherited. + +#### Replacing `.eslintignore` + +Because there is only one `.eslint.js` file to consider, ESLint doesn't have to first search directories to determine its location. That allows `.eslint.js` to specify files to ignore directly instead of relying on `.eslintignore`. For backwards compatibility, users could create a config like this: + +```js +const fs = require("fs"); + +exports.config = { + files: "*.js", + ignore: fs.readFileSync(".eslintignore").replace(/\n/g, ";") +}; +``` + +### The `@eslint/config` Package + +Because some of the operations ESLint currently do are quite complicated, this design includes a utility package called `@eslint/config` that users can use for common tasks. This package contains the following utilities: + +1. `Config` - a class that both wraps a configuration object to provide helper methods for manipulating configs and a container for static methods to work with configs. +1. `RuleLoader` - a class that aids in loading rules from a directory. + +The `@eslint/config` package is intended to add back functionality that would be removed from ESLint with this design. + +**Note:** It is not required to use this package with `.eslint.js`; this is an optional tool that users may choose to use for convenience. + +#### `Config` Class + +The `Config` class has this form: + +```js +class Config { + + // pass in any object literal to wrap in Config + constructor(object) {} + + // convenience method for easily setting rule severity + setRuleSeverity(ruleId, severity) {} + + // determine if an object is a Config + static isConfig(object) {} + + // create a new config based on another config + static create(baseConfig, overrideConfig) {} + + // merge info from other configs into a base config + static assign(receiverConfig, ...supplierConfigs) {} +} +``` + +A new instance of `Config` is created like this: + +```js +const { Config } = require("@eslint/config"); + +const config = new Config({ + files: "*.js", + ruledefs: { + react: require("eslint-plugin-react").rules, + }, + rules: { + "react/jsx-uses-react": "error", + semi: ["error", "always"] + } +}); +``` + +After that point, you can easily change the severity of a rule like this: + +```js +config.setRuleSeverity("semi", "warn"); +``` + +In this way, users don't need to modify an array to change a rule's severity, which is a major downside of this design when using a vanilla object structure as a config. + +The static methods on `Config` make it easy to create and modify configs. For example, suppose you want to create a config based on `eslint-config-standard` but would prefer `semi` be a warning instead of an error, you can do so using `Config.create()`: + +```js +const standardConfig = require("eslint-config-standard"); +const { Config } = require("@eslint/config"); + +const myConfig = Config.create(standardConfig, { + rules: { + semi: "warn" + } +}); + +exports.config = myConfig; +``` + +Here, the `Config.create()` method creates a new `Config` object based on `standardConfig` and then merges in the overrides from the second argument. The overrides config just changed the severity of `semi` to be `"warn"` but does not affect the other `semi` options. (This implements the current config merging functionality in ESLint.) + +Similarly, the `Config.assign()` method lets users modify their config by merging in changes from other configs, such as: + +```js +const standardConfig = require("eslint-config-standard"); +const personalConfig = require("@personal/eslint-config"); +const { Config } = require("@eslint/config"); + +const myConfig = Config.create(standardConfig, { + rules: { + semi: "warn" + } +}); + +Config.assign(myConfig, personalConfig); + +exports.config = myConfig; +``` + +This code example merges `personalConfig` into `myConfig` using the same functionality that ESLint currently uses to merge configs. + +#### The `RuleLoader` Class + +The purpose of the `RuleLoader` class is aid in loading rules to replace `--rulesdir` functionality and has this form: + +```js +class RuleLoader { + + // create a new instance + constructor() {} + + // mimic `--rulesdir` loading + loadRulesFromDirectory(directory) {} +} +``` + +In order to recreate the functionality of `--rulesdir`, a user would need to create a new entry in `ruledefs` and then specify the rules from a directory, such as: + +```js +const { Config, RuleLoader } = require("@eslint/config"); +const ruleLoader = new RuleLoader(); + +exports.config = new Config({ + ruledefs: { + custom: ruleLoader.loadFromDirectory("./custom-rules") + }, + rules: { + "custom/my-rule": "error" + } +}); +``` + +The `RuleLoader#loadFromDirectory()` method returns an object where the keys are the rule IDs (based on the filenames found in the directory) and the values are the rule objects. Unlike today, rules loaded from a local directory must have a namespace just like plugin rules (`custom` in this example). + +### Advanced Configs + +Some users may need information from ESLint to determine the correct configuration to use. To allow for that, `exports.config` may also be a function that returns an object, such as: + +```js +exports.config = (context) => { + + // do something + + return { + rules: { + "semi": ["error", "always"] + } + }; + +}; +``` + +The `context` object has the following members: + +* `core` - information about the ESLint core that is using the config + * `version` - the version of ESLint being used + * `hasRule(ruleId)` - determine if the given rule is in the core + * `getConfig(configId)` - returns one of the predefined ESLint configs +* `cwd` - the current working directory for ESLint (might be different than `process.cwd()`, see https://github.com/eslint/eslint/issues/11218) + +This information allows users to make logical decisions about how the config should be constructed. + +#### Checking for Rule Existence + +One of the problems with shareable configs today is when a new rule is added to the ESLint core, shareable configs using that rule are not valid for older versions of ESLint (because ESLint validates that configured rules are present). With advanced configs, a shareable config could detect if a new rule is present before deciding to include it, for example: + +```js +const { Config } = require("@eslint/config"); + +exports.config = (context) => { + const myConfig = new Config({ + ruledefs: { + custom: ruleLoader.loadFromDirectory("./custom-rules") + }, + rules: { + "custom/my-rule": "error" + } + }; + + if (context.hasRule("some-new-rule")) { + myConfig.rules["some-new-rule"] = ["error"]; + } + + return myConfig; + +}; +``` + +#### Extending `eslint:recommended` + +Advanced configs must be used to extend builtin configs such as `eslint:recommended` and `eslint:all`: + +```js +const { Config } = require("@eslint/config"); + +exports.config = (context) => { + + const recommendedConfig = context.core.getConfig("eslint:recommended"); + + return Config.create(recommendedConfig, { + ruledefs: { + custom: ruleLoader.loadFromDirectory("./custom-rules") + }, + rules: { + "custom/my-rule": "error" + } + }); +}; +``` + +### Configuration Location Resolution + +When ESLint is executed, the following steps are taken to find the `.eslint.js` file to use: + +1. If the `-c` flag is used then the specified configuration file is used. There is no further search performed. +1. Otherwise: + a. Look for `.eslint.js` in the current working directory. If founnd, stop searching and use that file. + b. If not found, search up the directory hierarchy looking for `.eslint.js`. + c. If a `.eslint.js` file is found at any point, stop searching and use that file. + d. If `/` is reached without finding `.eslint.js`, then stop searching and output a "no configuration found" error. + +This approach will allow running ESLint from within a subdirectory of a project and get the same result as when ESLint is run from the project's root directory (the one where `.eslint.js` is found). + +Some of the key differences from the way ESLint's configuration resolution works today are: + +1. There is no automatic search for `.eslint.js` in the user's home directory. Users wanting this functionality can either pass a home directory file using `-c` or manually read in that file from their `.eslint.js` file. +1. Once a `.eslint.js` file is found, there is no more searching for any further config files. +1. There is no automatic merging of config files using either `extends` or `overrides`. +1. When `-c` is passed on the command line, there is no search performed. + +### File Pattern Resolution + +Because there are file patterns included in `.eslint.js`, this requires a change to how ESLint decides which files to lint. The process for determining which files to lint is: + +1. When a filename is passed directly (such as `eslint foo.js`): + 1. ESLint checks to see if there is a config where the `files` pattern matches the file that was passed in. If no config is found, then the file is ignored (with an appropriate warning). + 1. If a matching config is found, then the `ignore` pattern is tested against the filename. If it's a match, then the file is ignored. Otherwise, the file is linted. +1. When a glob pattern is passed directly (such as `eslint src/*.js`): + 1. ESLint expands the glob pattern to get a list of files. + 1. Each file is checked individually as in step 1. +1. When a directory is passed directly (such as `eslint src`): + 1. The directory is converted into a glob pattern (such as `src` becomes `src/**`). + 1. The glob pattern is checked as in step 2. + +## Documentation + +This will require extensive documentation changes and an introductory blog post. + +At a minimum, these pages will have to be updated (and rewritten): + +* https://eslint.org/docs/user-guide/getting-started#configuration +* https://eslint.org/docs/user-guide/configuring +* https://eslint.org/docs/developer-guide/working-with-plugins#configs-in-plugins +* https://eslint.org/docs/developer-guide/shareable-configs + +## Drawbacks + +As with any significant change, there are some significant drawbacks: + +1. We'd need to do a phased rollout (see Backwards Compatibility Analysis) to minimize impact on users. That means maintaining two configuration systems for a while, increasing maintenance overhead and complexity of the application. +1. Getting everyone to convert to `.eslint.js` format would be a significant stress on the community that could cause some resentment. +1. We can no longer enforce naming conventions for plugins and shareable configs. This may make it more difficult for users to find compatible npm packages. +1. Creating configuration files will be more complicated. +1. The `--print-config` option becomes less useful because we can't output objects in a meaningful way. +1. People depending on environments may find this change particularly painful. + +## Backwards Compatibility Analysis + +The intent of this proposal is to replace the current `.eslintrc` format, but can be implemented incrementally so as to cause as little disturbance to ESLint users as possible + +In the first phase, I envision this: + +1. `.eslint.js` can be implemented alongside `.eslintrc`. +1. If `.eslint.js` is found, then: + 1. All `.eslintrc` files are ignored. + 1. `.eslintignore` is ignored. + 1. `--ignore-file` is ignored. + 1. `--rulesdir` is ignored. + 1. `--env` is ignored. + 1. `eslint-env` config comments are ignored. +1. If `.eslint.js` is not found, then fall back to the current behavior. + +This keeps the current behavior for the majority of users while allowing some users to test out the new functionality. Also, `-c` could not be used with `.eslint.js` in this phase. + +In the second phase (and in a major release), ESLint will emit deprecation warnings whenever the original functionality is used but will still honor them so long as `.eslint.js` is not found. + +In the third phase (and in another major release), the original functionality is removed. + +So while this is intended to be a breaking change, it will be introduced over the course of three major releases in order to give users ample time to transition. + +## Alternatives + +While there are no alternatives that cover all of the functionality in this RFC, there are alternatives designed to address various parts. + +* Both https://github.com/eslint/rfcs/pull/7 and https://github.com/eslint/rfcs/pull/5 specify an alternative method for resolving plugin locations in configs. This attempts to solve the problem with bundling plugins with configs (https://github.com/eslint/eslint/issues/3458). These proposals have the benefit of working within the current configuration system and requiring very few changes from users. +* It is possible to come up with a solution to https://github.com/eslint/eslint/issues/8813 that makes use of `extends`, though this would get a bit complicated if an extended config in an `overrides` section also has `overrides`. +* We could switch the current configuration system over so that all config files are considered to implicitly have `root:true`. This could dramatically simplify configuration searching and merging. + +## Open Questions + +1. Should `ignore` allow line breaks so it can emulate `.eslintignore`? +1. Is `ruledefs` a create enough key name? +1. Do we need a command line flag to opt-in to `.eslint.js` instead of trying to do it alongside the existing configuration system? +1. Does the file pattern system actually remove the need for `--ext`? + +## Related Discussions + +* https://github.com/eslint/rfcs/pull/7 +* https://github.com/eslint/rfcs/pull/5 +* https://github.com/eslint/eslint/issues/3458 +* https://github.com/eslint/eslint/issues/6732 +* https://github.com/eslint/eslint/issues/8813 +* https://github.com/eslint/eslint/issues/9897 +* https://github.com/eslint/eslint/issues/10125 +* https://github.com/eslint/eslint/issues/10643 +* https://github.com/eslint/eslint/issues/10891 +* https://github.com/eslint/eslint/issues/11223 \ No newline at end of file From 8f5f07b1799bcb0256e55b605a3de66680c1213a Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 22 Jan 2019 10:16:27 -0800 Subject: [PATCH 02/78] Added missing key --- designs/2019-config-simplification/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index b436c662..cec56592 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -53,8 +53,10 @@ exports.config = { files: "*.js", ignore: "*.test.js", globals: {}, + settings: {}, processor: object, parser: object, + parserOptions: {}, ruledefs: {}, rules: {} }; @@ -69,6 +71,7 @@ The following keys are new to the `.eslint.js` format: The following keys are specified the same as in `.eslintrc` files: * `globals` +* `settings` * `rules` * `parserOptions` From 6dec12ff593f249aab6024ae518972b0224924f0 Mon Sep 17 00:00:00 2001 From: Kevin Partington Date: Wed, 23 Jan 2019 07:42:47 -0700 Subject: [PATCH 03/78] Fix typo Co-Authored-By: nzakas --- designs/2019-config-simplification/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index cec56592..0f3b712f 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -138,7 +138,7 @@ In `.eslint.js`, you would need to pass the references directly, such as: ```js exports.config = { parser: require("babel-eslint"), - processor: require("eslint-plugin-markdown).processors.markdown + processor: require("eslint-plugin-markdown").processors.markdown }; ``` @@ -541,4 +541,4 @@ While there are no alternatives that cover all of the functionality in this RFC, * https://github.com/eslint/eslint/issues/10125 * https://github.com/eslint/eslint/issues/10643 * https://github.com/eslint/eslint/issues/10891 -* https://github.com/eslint/eslint/issues/11223 \ No newline at end of file +* https://github.com/eslint/eslint/issues/11223 From 3e97c91f46f4c5416a45b5d608e6f45aada5fb58 Mon Sep 17 00:00:00 2001 From: Kevin Partington Date: Wed, 23 Jan 2019 07:43:11 -0700 Subject: [PATCH 04/78] Fix typo Co-Authored-By: nzakas --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 0f3b712f..4e42af68 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -146,7 +146,7 @@ In both cases, users now must pass a direct object reference. This has the benef #### Applying an Environment -Unlike with `.eslintrc` files, there is no `env` key in `.eslint.js`. Users can mimmick the behavior of `env` by assigning directly to the `globals` key: +Unlike with `.eslintrc` files, there is no `env` key in `.eslint.js`. Users can mimic the behavior of `env` by assigning directly to the `globals` key: ```js const globals = require("globals"); From b3545b3113de1d11d46d5e9b5787d9351e1a6c64 Mon Sep 17 00:00:00 2001 From: Kevin Partington Date: Wed, 23 Jan 2019 07:43:29 -0700 Subject: [PATCH 05/78] Fix typo Co-Authored-By: nzakas --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 4e42af68..28a258d8 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -328,7 +328,7 @@ This code example merges `personalConfig` into `myConfig` using the same functio #### The `RuleLoader` Class -The purpose of the `RuleLoader` class is aid in loading rules to replace `--rulesdir` functionality and has this form: +The purpose of the `RuleLoader` class is to aid in loading rules to replace `--rulesdir` functionality and has this form: ```js class RuleLoader { From e5e790f986595ab73638962809f847fa811908a3 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 23 Jan 2019 07:32:07 -0800 Subject: [PATCH 06/78] Incorporating first round of feedback --- designs/2019-config-simplification/README.md | 136 ++++++++++--------- 1 file changed, 71 insertions(+), 65 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 28a258d8..76892450 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -34,19 +34,19 @@ This RFC proposes a fresh start for configuration in ESLint that takes into acco Design Summary: -1. Introduce a new `.eslint.js` configuration file format -1. Searching for `.eslint.js` starts from the current working directory and continues up -1. All `.eslint.js` files are treated as if they have `root: true` +1. Introduce a new `.eslint.config.js` configuration file format +1. Searching for `.eslint.config.js` starts from the current working directory and continues up +1. All `.eslint.config.js` files are treated as if they have `root: true` 1. There is no automatic merging of config files -1. The `.eslint.js` configuration is not serializable and objects such as functions and plugins may be added directly into configuration +1. The `.eslint.config.js` configuration is not serializable and objects such as functions and plugins may be added directly into configuration 1. Remove the concept of environments (`env`) 1. Remove `.eslintignore` and `--ignore-file` (no longer necessary) 1. Remove `--rulesdir` 1. Create a `@eslint/config` package to help users merge configs -### The `.eslint.js` File +### The `.eslint.config.js` File -The `.eslint.js` file is a JavaScript file (there is no JSON or YAML equivalent) that exports a `config` object: +The `.eslint.config.js` file is a JavaScript file (there is no JSON or YAML equivalent) that exports a `config` object: ```js exports.config = { @@ -62,9 +62,9 @@ exports.config = { }; ``` -The following keys are new to the `.eslint.js` format: +The following keys are new to the `.eslint.config.js` format: -* `files` - **Required.** Determines the global pattern that this configuration applies to and also is used as the default pattern to search for when ESLint is executed on the command line. +* `files` - **Required.** Determines the glob file patterns that this configuration applies to. * `ignore` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this glob pattern are subtracted from the files specified in `files`. * `ruledefs` - Contains definitions for rules grouped by a specific name. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option. @@ -77,12 +77,12 @@ The following keys are specified the same as in `.eslintrc` files: The following keys are specified differently than in `.eslintrc` files: -* `parser` - an object in `.eslint.js` files (a string in `.eslintrc`) -* `processor` - an object in `.eslint.js` files (a string in `.eslintrc`) +* `parser` - an object in `.eslint.config.js` files (a string in `.eslintrc`) +* `processor` - an object in `.eslint.config.js` files (a string in `.eslintrc`) -Each of these keys used to require one or more strings specifying module(s) to load in `.eslintrc`. In `.eslint.js`, these are all objects, requiring users to manually specify the objects to use. +Each of these keys used to require one or more strings specifying module(s) to load in `.eslintrc`. In `.eslint.config.js`, these are all objects, requiring users to manually specify the objects to use. -The following keys are invalid in `.eslint.js`: +The following keys are invalid in `.eslint.config.js`: * `env` - responsibility of the user * `extends` - responsibility of the user @@ -94,7 +94,7 @@ Each of these keys represent different ways of augmenting how configuration is c #### Referencing Plugin Rules -The `plugins` key in `.eslintrc` was an array of strings indicating the plugins to load, allowing you to specify processors, rules, etc., by referencing the name of the plugin. It's no longer necessary to indicate the plugins to load because that is done directly in the `.eslint.js` file. For example, consider this `.eslintrc` file: +The `plugins` key in `.eslintrc` was an array of strings indicating the plugins to load, allowing you to specify processors, rules, etc., by referencing the name of the plugin. It's no longer necessary to indicate the plugins to load because that is done directly in the `.eslint.config.js` file. For example, consider this `.eslintrc` file: ```yaml plugins: @@ -106,7 +106,7 @@ rules: This file tells ESLint to load `eslint-plugin-react` and then configure a rule from that plugin. The `react/` is automatically preprended to the rule by ESLint for easy reference. -In `.eslint.js`, the same configuration is achieved using a `ruledefs` key: +In `.eslint.config.js`, the same configuration is achieved using a `ruledefs` key: ```js const reactPlugin = require("eslint-plugin-react"); @@ -133,7 +133,7 @@ parser: "babel-eslint" processor: "markdown/markdown" ``` -In `.eslint.js`, you would need to pass the references directly, such as: +In `.eslint.config.js`, you would need to pass the references directly, such as: ```js exports.config = { @@ -142,11 +142,11 @@ exports.config = { }; ``` -In both cases, users now must pass a direct object reference. This has the benefit of using the builtin Node.js module resolution system or allowing users to specify their own. There is never of question of where the modules will be resolved from. +In both cases, users now must pass a direct object reference. This has the benefit of using the builtin Node.js module resolution system or allowing users to specify their own. There is never a question of where the modules will be resolved from. #### Applying an Environment -Unlike with `.eslintrc` files, there is no `env` key in `.eslint.js`. Users can mimic the behavior of `env` by assigning directly to the `globals` key: +Unlike with `.eslintrc` files, there is no `env` key in `.eslint.config.js`. Users can mimic the behavior of `env` by assigning directly to the `globals` key: ```js const globals = require("globals"); @@ -179,7 +179,7 @@ This config extends `eslint-config-standard` by first importing and then making #### Overriding Configuration Based on File Patterns -Whereas `.eslintrc` had an `overrides` key that made a hierarchical structure, the `.eslint.js` file does not have any such hierarchy. Instead, users can return an array of configs that should be used. For example, consider this `.eslintrc` config: +Whereas `.eslintrc` had an `overrides` key that made a hierarchical structure, the `.eslint.config.js` file does not have any such hierarchy. Instead, users can return an array of configs that should be used. For example, consider this `.eslintrc` config: ```yaml plugins: ["react"] @@ -193,7 +193,7 @@ overrides: processor: "markdown/markdown" ``` -This can be written in `.eslint.js` as an array of two configs: +This can be written in `.eslint.config.js` as an array of two configs: ```js exports.config = [ @@ -218,14 +218,14 @@ When ESLint uses this config, it will check each `files` pattern to determine wh #### Replacing `.eslintignore` -Because there is only one `.eslint.js` file to consider, ESLint doesn't have to first search directories to determine its location. That allows `.eslint.js` to specify files to ignore directly instead of relying on `.eslintignore`. For backwards compatibility, users could create a config like this: +Because there is only one `.eslint.config.js` file to consider, ESLint doesn't have to first search directories to determine its location. That allows `.eslint.config.js` to specify files to ignore directly instead of relying on `.eslintignore`. For backwards compatibility, users could create a config like this: ```js const fs = require("fs"); exports.config = { files: "*.js", - ignore: fs.readFileSync(".eslintignore").replace(/\n/g, ";") + ignore: fs.readFileSync(".eslintignore", "utf8").replace(/\n/g, ";") }; ``` @@ -238,7 +238,7 @@ Because some of the operations ESLint currently do are quite complicated, this d The `@eslint/config` package is intended to add back functionality that would be removed from ESLint with this design. -**Note:** It is not required to use this package with `.eslint.js`; this is an optional tool that users may choose to use for convenience. +**Note:** It is not required to use this package with `.eslint.config.js`; this is an optional tool that users may choose to use for convenience. #### `Config` Class @@ -257,7 +257,7 @@ class Config { static isConfig(object) {} // create a new config based on another config - static create(baseConfig, overrideConfig) {} + static create(baseConfig, ...overrideConfigs) {} // merge info from other configs into a base config static assign(receiverConfig, ...supplierConfigs) {} @@ -326,6 +326,27 @@ exports.config = myConfig; This code example merges `personalConfig` into `myConfig` using the same functionality that ESLint currently uses to merge configs. +##### Extending Builtin Configs + +The `Config` class has two static properties representing ESLint's builtin configs: + +* `Config.recommended` - represents the `eslint:recommended` config. +* `Config.all` - represents the `eslint:all` config. + +You can then create a config based on these special configs like this: + +```js +const { Config } = require("@eslint/config"); + +const myConfig = Config.create(Config.recommended, { + rules: { + semi: ["warn", "always"] + } +}); + +exports.config = myConfig; +``` + #### The `RuleLoader` Class The purpose of the `RuleLoader` class is to aid in loading rules to replace `--rulesdir` functionality and has this form: @@ -382,7 +403,6 @@ The `context` object has the following members: * `core` - information about the ESLint core that is using the config * `version` - the version of ESLint being used * `hasRule(ruleId)` - determine if the given rule is in the core - * `getConfig(configId)` - returns one of the predefined ESLint configs * `cwd` - the current working directory for ESLint (might be different than `process.cwd()`, see https://github.com/eslint/eslint/issues/11218) This information allows users to make logical decisions about how the config should be constructed. @@ -402,7 +422,7 @@ exports.config = (context) => { rules: { "custom/my-rule": "error" } - }; + }); if (context.hasRule("some-new-rule")) { myConfig.rules["some-new-rule"] = ["error"]; @@ -413,51 +433,29 @@ exports.config = (context) => { }; ``` -#### Extending `eslint:recommended` - -Advanced configs must be used to extend builtin configs such as `eslint:recommended` and `eslint:all`: - -```js -const { Config } = require("@eslint/config"); - -exports.config = (context) => { - - const recommendedConfig = context.core.getConfig("eslint:recommended"); - - return Config.create(recommendedConfig, { - ruledefs: { - custom: ruleLoader.loadFromDirectory("./custom-rules") - }, - rules: { - "custom/my-rule": "error" - } - }); -}; -``` - ### Configuration Location Resolution -When ESLint is executed, the following steps are taken to find the `.eslint.js` file to use: +When ESLint is executed, the following steps are taken to find the `.eslint.config.js` file to use: 1. If the `-c` flag is used then the specified configuration file is used. There is no further search performed. 1. Otherwise: - a. Look for `.eslint.js` in the current working directory. If founnd, stop searching and use that file. - b. If not found, search up the directory hierarchy looking for `.eslint.js`. - c. If a `.eslint.js` file is found at any point, stop searching and use that file. - d. If `/` is reached without finding `.eslint.js`, then stop searching and output a "no configuration found" error. + a. Look for `.eslint.config.js` in the current working directory. If founnd, stop searching and use that file. + b. If not found, search up the directory hierarchy looking for `.eslint.config.js`. + c. If a `.eslint.config.js` file is found at any point, stop searching and use that file. + d. If `/` is reached without finding `.eslint.config.js`, then stop searching and output a "no configuration found" error. -This approach will allow running ESLint from within a subdirectory of a project and get the same result as when ESLint is run from the project's root directory (the one where `.eslint.js` is found). +This approach will allow running ESLint from within a subdirectory of a project and get the same result as when ESLint is run from the project's root directory (the one where `.eslint.config.js` is found). Some of the key differences from the way ESLint's configuration resolution works today are: -1. There is no automatic search for `.eslint.js` in the user's home directory. Users wanting this functionality can either pass a home directory file using `-c` or manually read in that file from their `.eslint.js` file. -1. Once a `.eslint.js` file is found, there is no more searching for any further config files. +1. There is no automatic search for `.eslint.config.js` in the user's home directory. Users wanting this functionality can either pass a home directory file using `-c` or manually read in that file from their `.eslint.config.js` file. +1. Once a `.eslint.config.js` file is found, there is no more searching for any further config files. 1. There is no automatic merging of config files using either `extends` or `overrides`. 1. When `-c` is passed on the command line, there is no search performed. ### File Pattern Resolution -Because there are file patterns included in `.eslint.js`, this requires a change to how ESLint decides which files to lint. The process for determining which files to lint is: +Because there are file patterns included in `.eslint.config.js`, this requires a change to how ESLint decides which files to lint. The process for determining which files to lint is: 1. When a filename is passed directly (such as `eslint foo.js`): 1. ESLint checks to see if there is a config where the `files` pattern matches the file that was passed in. If no config is found, then the file is ignored (with an appropriate warning). @@ -485,7 +483,7 @@ At a minimum, these pages will have to be updated (and rewritten): As with any significant change, there are some significant drawbacks: 1. We'd need to do a phased rollout (see Backwards Compatibility Analysis) to minimize impact on users. That means maintaining two configuration systems for a while, increasing maintenance overhead and complexity of the application. -1. Getting everyone to convert to `.eslint.js` format would be a significant stress on the community that could cause some resentment. +1. Getting everyone to convert to `.eslint.config.js` format would be a significant stress on the community that could cause some resentment. 1. We can no longer enforce naming conventions for plugins and shareable configs. This may make it more difficult for users to find compatible npm packages. 1. Creating configuration files will be more complicated. 1. The `--print-config` option becomes less useful because we can't output objects in a meaningful way. @@ -497,21 +495,21 @@ The intent of this proposal is to replace the current `.eslintrc` format, but ca In the first phase, I envision this: -1. `.eslint.js` can be implemented alongside `.eslintrc`. -1. If `.eslint.js` is found, then: +1. `.eslint.config.js` can be implemented alongside `.eslintrc`. +1. If `.eslint.config.js` is found, then: 1. All `.eslintrc` files are ignored. 1. `.eslintignore` is ignored. 1. `--ignore-file` is ignored. 1. `--rulesdir` is ignored. 1. `--env` is ignored. 1. `eslint-env` config comments are ignored. -1. If `.eslint.js` is not found, then fall back to the current behavior. +1. If `.eslint.config.js` is not found, then fall back to the current behavior. -This keeps the current behavior for the majority of users while allowing some users to test out the new functionality. Also, `-c` could not be used with `.eslint.js` in this phase. +This keeps the current behavior for the majority of users while allowing some users to test out the new functionality. Also, `-c` could not be used with `.eslint.config.js` in this phase. -In the second phase (and in a major release), ESLint will emit deprecation warnings whenever the original functionality is used but will still honor them so long as `.eslint.js` is not found. +In the second phase (and in a major release), ESLint will emit deprecation warnings whenever the original functionality is used but will still honor them so long as `.eslint.config.js` is not found. -In the third phase (and in another major release), the original functionality is removed. +In the third phase (and in another major release), `.eslint.config.js` becomes the official way to configure ESLint. If no `.eslint.config.js` file is found, ESLint will still search for a `.eslintrc` file, and if found, print an error message information the user that the configuration file format has changed. So while this is intended to be a breaking change, it will be introduced over the course of three major releases in order to give users ample time to transition. @@ -526,10 +524,18 @@ While there are no alternatives that cover all of the functionality in this RFC, ## Open Questions 1. Should `ignore` allow line breaks so it can emulate `.eslintignore`? -1. Is `ruledefs` a create enough key name? -1. Do we need a command line flag to opt-in to `.eslint.js` instead of trying to do it alongside the existing configuration system? +1. Is `ruledefs` a clear enough key name? +1. Do we need a command line flag to opt-in to `.eslint.config.js` instead of trying to do it alongside the existing configuration system? 1. Does the file pattern system actually remove the need for `--ext`? +## Frequently Asked Questions + +### Can a config be an async function? + +No. Right now it won't be possible to implement a config with an async function because the rest of ESLint is fully synchronous. Once we look at how to make ESLint more asynchronous, we can revisit and allow configs to be created with async functions. + + + ## Related Discussions * https://github.com/eslint/rfcs/pull/7 From e0402a3b08acc2bd955d292212a472522daeae8e Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 24 Jan 2019 07:06:48 -0800 Subject: [PATCH 07/78] Add Teddy's extends proposal --- designs/2019-config-simplification/README.md | 74 ++++++++++++-------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 76892450..f6e8028d 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -18,6 +18,8 @@ Although `.eslintrc` has proven to be a very robust format, it is also limited a 1. [specifying ignore patterns in the config](https://github.com/eslint/eslint/issues/10891) 1. [specifying `--ext` information in the config](https://github.com/eslint/eslint/issues/11223) 1. [using `extends` in `overrides`](https://github.com/eslint/eslint/issues/8813) +1. [Customize merging of config options](https://github.com/eslint/eslint/issues/9192) + The only reason that these requests are difficult to implement in ESLint is because of how complex the `.eslintrc` configuration format is. Any changes made to any part of `.eslintrc` processing end up affecting millions of ESLint installations, so we have ended up stuck. The complicated parts of `.eslintrc` include: @@ -52,6 +54,7 @@ The `.eslint.config.js` file is a JavaScript file (there is no JSON or YAML equi exports.config = { files: "*.js", ignore: "*.test.js", + extends: [], globals: {}, settings: {}, processor: object, @@ -77,6 +80,7 @@ The following keys are specified the same as in `.eslintrc` files: The following keys are specified differently than in `.eslintrc` files: +* `extends` - an object or array in `.eslint.config.js` files (a string or string array in `.eslintrc`) * `parser` - an object in `.eslint.config.js` files (a string in `.eslintrc`) * `processor` - an object in `.eslint.config.js` files (a string in `.eslintrc`) @@ -85,7 +89,6 @@ Each of these keys used to require one or more strings specifying module(s) to l The following keys are invalid in `.eslint.config.js`: * `env` - responsibility of the user -* `extends` - responsibility of the user * `overrides` - responsibility of the user * `plugins` - replaced by `ruledefs` * `root` - always considered `true` @@ -165,17 +168,49 @@ This effectively duplicates the use of `env: { browser: true }` in ESLint. #### Extending Another Config -With the removal of `extends`, it's now up to the users to inherit from other configs. That can easily be done in the following manner: +Extending another config works the same as in `.eslintrc` except users pass entire config objects rather than the name of a config. That can easily be done in the following manner: ```js -const standardConfig = require("eslint-config-standard"); +exports.config = { + extends: require("eslint-config-standard"), + rules: { + semi: ["error", "always"] + } +}; +``` -standardConfig.rules.semi = ["error", "always"]; +This config extends `eslint-config-standard` by assigning it to `extends`. You can also use an array for `extends` to extend from multiple configs: -exports.config = standardConfig; +```js +exports.config = { + extends: [ + require("eslint-config-standard"), + require("@me/eslint-config") + ], + rules: { + semi: ["error", "always"] + } +}; +``` + +#### Extending From `eslint:recommended` and `eslint:all` + +Both `eslint:recommended` and `eslint:all` can be represented as strings in the `extends` key. For example: + +```js +exports.config = { + extends: [ + "eslint:recommended", + require("eslint-config-standard"), + require("@me/eslint-config") + ], + rules: { + semi: ["error", "always"] + } +}; ``` -This config extends `eslint-config-standard` by first importing and then making modifications before exporting the modified config. +This config first extends `eslint:recommended` and then continues on to extend other configs. #### Overriding Configuration Based on File Patterns @@ -326,27 +361,6 @@ exports.config = myConfig; This code example merges `personalConfig` into `myConfig` using the same functionality that ESLint currently uses to merge configs. -##### Extending Builtin Configs - -The `Config` class has two static properties representing ESLint's builtin configs: - -* `Config.recommended` - represents the `eslint:recommended` config. -* `Config.all` - represents the `eslint:all` config. - -You can then create a config based on these special configs like this: - -```js -const { Config } = require("@eslint/config"); - -const myConfig = Config.create(Config.recommended, { - rules: { - semi: ["warn", "always"] - } -}); - -exports.config = myConfig; -``` - #### The `RuleLoader` Class The purpose of the `RuleLoader` class is to aid in loading rules to replace `--rulesdir` functionality and has this form: @@ -458,7 +472,7 @@ Some of the key differences from the way ESLint's configuration resolution works Because there are file patterns included in `.eslint.config.js`, this requires a change to how ESLint decides which files to lint. The process for determining which files to lint is: 1. When a filename is passed directly (such as `eslint foo.js`): - 1. ESLint checks to see if there is a config where the `files` pattern matches the file that was passed in. If no config is found, then the file is ignored (with an appropriate warning). + 1. ESLint checks to see if there is a config where the `files` pattern matches the file that was passed in. The pattern is evaluated by prepending the directory in which `.eslint.config.js` was found to each pattern. If no config is found, then the file is ignored (with an appropriate warning). 1. If a matching config is found, then the `ignore` pattern is tested against the filename. If it's a match, then the file is ignored. Otherwise, the file is linted. 1. When a glob pattern is passed directly (such as `eslint src/*.js`): 1. ESLint expands the glob pattern to get a list of files. @@ -502,6 +516,7 @@ In the first phase, I envision this: 1. `--ignore-file` is ignored. 1. `--rulesdir` is ignored. 1. `--env` is ignored. + 1. `--ext` is ignored. 1. `eslint-env` config comments are ignored. 1. If `.eslint.config.js` is not found, then fall back to the current behavior. @@ -534,7 +549,9 @@ While there are no alternatives that cover all of the functionality in this RFC, No. Right now it won't be possible to implement a config with an async function because the rest of ESLint is fully synchronous. Once we look at how to make ESLint more asynchronous, we can revisit and allow configs to be created with async functions. +### Why use `exports.config` instead of `module.exports`? +Using an exported key gives us more flexibility for the future if we decide that config files should be able to output more than one thing. For example, I've been thinking of a `--config-key` option that would allow users to specify which exported key should be used as their config. Users could then export multiple different keys (`config1`, `config2`, etc.) and easily switch between configs on the command line. That option is not part of this proposal because it isn't solving an existing problem and I'd rather focus on existing problems first (this proposal is already big enough). ## Related Discussions @@ -543,6 +560,7 @@ No. Right now it won't be possible to implement a config with an async function * https://github.com/eslint/eslint/issues/3458 * https://github.com/eslint/eslint/issues/6732 * https://github.com/eslint/eslint/issues/8813 +* https://github.com/eslint/eslint/issues/9192 * https://github.com/eslint/eslint/issues/9897 * https://github.com/eslint/eslint/issues/10125 * https://github.com/eslint/eslint/issues/10643 From 9f705b8fc158c6a7f20cdcfc319bed0612c47c88 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 24 Jan 2019 07:24:48 -0800 Subject: [PATCH 08/78] Change files and ignore to arrays --- designs/2019-config-simplification/README.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index f6e8028d..5cc3a3ec 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -52,8 +52,8 @@ The `.eslint.config.js` file is a JavaScript file (there is no JSON or YAML equi ```js exports.config = { - files: "*.js", - ignore: "*.test.js", + files: ["*.js"], + ignore: ["*.test.js"], extends: [], globals: {}, settings: {}, @@ -68,7 +68,7 @@ exports.config = { The following keys are new to the `.eslint.config.js` format: * `files` - **Required.** Determines the glob file patterns that this configuration applies to. -* `ignore` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this glob pattern are subtracted from the files specified in `files`. +* `ignore` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`. * `ruledefs` - Contains definitions for rules grouped by a specific name. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option. The following keys are specified the same as in `.eslintrc` files: @@ -115,6 +115,7 @@ In `.eslint.config.js`, the same configuration is achieved using a `ruledefs` ke const reactPlugin = require("eslint-plugin-react"); exports.config = { + files: ["*.js"], ruledefs: { react: reactPlugin.rules }, @@ -140,6 +141,7 @@ In `.eslint.config.js`, you would need to pass the references directly, such as: ```js exports.config = { + files: ["*.js"], parser: require("babel-eslint"), processor: require("eslint-plugin-markdown").processors.markdown }; @@ -155,6 +157,7 @@ Unlike with `.eslintrc` files, there is no `env` key in `.eslint.config.js`. Use const globals = require("globals"); exports.config = { + files: ["*.js"], globals: { MyGlobal: true, ...globals.browser @@ -172,6 +175,7 @@ Extending another config works the same as in `.eslintrc` except users pass enti ```js exports.config = { + files: ["*.js"], extends: require("eslint-config-standard"), rules: { semi: ["error", "always"] @@ -183,6 +187,7 @@ This config extends `eslint-config-standard` by assigning it to `extends`. You c ```js exports.config = { + files: ["*.js"], extends: [ require("eslint-config-standard"), require("@me/eslint-config") @@ -199,6 +204,7 @@ Both `eslint:recommended` and `eslint:all` can be represented as strings in the ```js exports.config = { + files: ["*.js"], extends: [ "eslint:recommended", require("eslint-config-standard"), @@ -259,8 +265,8 @@ Because there is only one `.eslint.config.js` file to consider, ESLint doesn't h const fs = require("fs"); exports.config = { - files: "*.js", - ignore: fs.readFileSync(".eslintignore", "utf8").replace(/\n/g, ";") + files: ["*.js"], + ignore: fs.readFileSync(".eslintignore", "utf8").split("\n") }; ``` @@ -305,7 +311,7 @@ A new instance of `Config` is created like this: const { Config } = require("@eslint/config"); const config = new Config({ - files: "*.js", + files: ["*.js"], ruledefs: { react: require("eslint-plugin-react").rules, }, @@ -404,6 +410,7 @@ exports.config = (context) => { // do something return { + files: ["*.js"], rules: { "semi": ["error", "always"] } @@ -430,6 +437,7 @@ const { Config } = require("@eslint/config"); exports.config = (context) => { const myConfig = new Config({ + files: ["*.js"], ruledefs: { custom: ruleLoader.loadFromDirectory("./custom-rules") }, From a51c188313b14c82a3e4d56f7bd2fb47aa177ecf Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 24 Jan 2019 07:51:08 -0800 Subject: [PATCH 09/78] .eslint.config.js -> eslint.config.js --- designs/2019-config-simplification/README.md | 89 +++++++++++--------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 5cc3a3ec..1219a560 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -36,19 +36,19 @@ This RFC proposes a fresh start for configuration in ESLint that takes into acco Design Summary: -1. Introduce a new `.eslint.config.js` configuration file format -1. Searching for `.eslint.config.js` starts from the current working directory and continues up -1. All `.eslint.config.js` files are treated as if they have `root: true` +1. Introduce a new `eslint.config.js` configuration file format +1. Searching for `eslint.config.js` starts from the current working directory and continues up +1. All `eslint.config.js` files are treated as if they have `root: true` 1. There is no automatic merging of config files -1. The `.eslint.config.js` configuration is not serializable and objects such as functions and plugins may be added directly into configuration +1. The `eslint.config.js` configuration is not serializable and objects such as functions and plugins may be added directly into configuration 1. Remove the concept of environments (`env`) 1. Remove `.eslintignore` and `--ignore-file` (no longer necessary) 1. Remove `--rulesdir` 1. Create a `@eslint/config` package to help users merge configs -### The `.eslint.config.js` File +### The `eslint.config.js` File -The `.eslint.config.js` file is a JavaScript file (there is no JSON or YAML equivalent) that exports a `config` object: +The `eslint.config.js` file is a JavaScript file (there is no JSON or YAML equivalent) that exports a `config` object: ```js exports.config = { @@ -65,7 +65,7 @@ exports.config = { }; ``` -The following keys are new to the `.eslint.config.js` format: +The following keys are new to the `eslint.config.js` format: * `files` - **Required.** Determines the glob file patterns that this configuration applies to. * `ignore` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`. @@ -80,13 +80,13 @@ The following keys are specified the same as in `.eslintrc` files: The following keys are specified differently than in `.eslintrc` files: -* `extends` - an object or array in `.eslint.config.js` files (a string or string array in `.eslintrc`) -* `parser` - an object in `.eslint.config.js` files (a string in `.eslintrc`) -* `processor` - an object in `.eslint.config.js` files (a string in `.eslintrc`) +* `extends` - an object or array in `eslint.config.js` files (a string or string array in `.eslintrc`) +* `parser` - an object in `eslint.config.js` files (a string in `.eslintrc`) +* `processor` - an object in `eslint.config.js` files (a string in `.eslintrc`) -Each of these keys used to require one or more strings specifying module(s) to load in `.eslintrc`. In `.eslint.config.js`, these are all objects, requiring users to manually specify the objects to use. +Each of these keys used to require one or more strings specifying module(s) to load in `.eslintrc`. In `eslint.config.js`, these are all objects, requiring users to manually specify the objects to use. -The following keys are invalid in `.eslint.config.js`: +The following keys are invalid in `eslint.config.js`: * `env` - responsibility of the user * `overrides` - responsibility of the user @@ -97,7 +97,7 @@ Each of these keys represent different ways of augmenting how configuration is c #### Referencing Plugin Rules -The `plugins` key in `.eslintrc` was an array of strings indicating the plugins to load, allowing you to specify processors, rules, etc., by referencing the name of the plugin. It's no longer necessary to indicate the plugins to load because that is done directly in the `.eslint.config.js` file. For example, consider this `.eslintrc` file: +The `plugins` key in `.eslintrc` was an array of strings indicating the plugins to load, allowing you to specify processors, rules, etc., by referencing the name of the plugin. It's no longer necessary to indicate the plugins to load because that is done directly in the `eslint.config.js` file. For example, consider this `.eslintrc` file: ```yaml plugins: @@ -109,7 +109,7 @@ rules: This file tells ESLint to load `eslint-plugin-react` and then configure a rule from that plugin. The `react/` is automatically preprended to the rule by ESLint for easy reference. -In `.eslint.config.js`, the same configuration is achieved using a `ruledefs` key: +In `eslint.config.js`, the same configuration is achieved using a `ruledefs` key: ```js const reactPlugin = require("eslint-plugin-react"); @@ -137,7 +137,7 @@ parser: "babel-eslint" processor: "markdown/markdown" ``` -In `.eslint.config.js`, you would need to pass the references directly, such as: +In `eslint.config.js`, you would need to pass the references directly, such as: ```js exports.config = { @@ -151,7 +151,7 @@ In both cases, users now must pass a direct object reference. This has the benef #### Applying an Environment -Unlike with `.eslintrc` files, there is no `env` key in `.eslint.config.js`. Users can mimic the behavior of `env` by assigning directly to the `globals` key: +Unlike with `.eslintrc` files, there is no `env` key in `eslint.config.js`. Users can mimic the behavior of `env` by assigning directly to the `globals` key: ```js const globals = require("globals"); @@ -220,7 +220,7 @@ This config first extends `eslint:recommended` and then continues on to extend o #### Overriding Configuration Based on File Patterns -Whereas `.eslintrc` had an `overrides` key that made a hierarchical structure, the `.eslint.config.js` file does not have any such hierarchy. Instead, users can return an array of configs that should be used. For example, consider this `.eslintrc` config: +Whereas `.eslintrc` had an `overrides` key that made a hierarchical structure, the `eslint.config.js` file does not have any such hierarchy. Instead, users can return an array of configs that should be used. For example, consider this `.eslintrc` config: ```yaml plugins: ["react"] @@ -234,7 +234,7 @@ overrides: processor: "markdown/markdown" ``` -This can be written in `.eslint.config.js` as an array of two configs: +This can be written in `eslint.config.js` as an array of two configs: ```js exports.config = [ @@ -259,7 +259,7 @@ When ESLint uses this config, it will check each `files` pattern to determine wh #### Replacing `.eslintignore` -Because there is only one `.eslint.config.js` file to consider, ESLint doesn't have to first search directories to determine its location. That allows `.eslint.config.js` to specify files to ignore directly instead of relying on `.eslintignore`. For backwards compatibility, users could create a config like this: +Because there is only one `eslint.config.js` file to consider, ESLint doesn't have to first search directories to determine its location. That allows `eslint.config.js` to specify files to ignore directly instead of relying on `.eslintignore`. For backwards compatibility, users could create a config like this: ```js const fs = require("fs"); @@ -279,7 +279,7 @@ Because some of the operations ESLint currently do are quite complicated, this d The `@eslint/config` package is intended to add back functionality that would be removed from ESLint with this design. -**Note:** It is not required to use this package with `.eslint.config.js`; this is an optional tool that users may choose to use for convenience. +**Note:** It is not required to use this package with `eslint.config.js`; this is an optional tool that users may choose to use for convenience. #### `Config` Class @@ -424,7 +424,7 @@ The `context` object has the following members: * `core` - information about the ESLint core that is using the config * `version` - the version of ESLint being used * `hasRule(ruleId)` - determine if the given rule is in the core -* `cwd` - the current working directory for ESLint (might be different than `process.cwd()`, see https://github.com/eslint/eslint/issues/11218) +* `cwd` - the current working directory for ESLint (might be different than `process.cwd()` but always matches `CLIEngine.options.cwd`, see https://github.com/eslint/eslint/issues/11218) This information allows users to make logical decisions about how the config should be constructed. @@ -457,30 +457,30 @@ exports.config = (context) => { ### Configuration Location Resolution -When ESLint is executed, the following steps are taken to find the `.eslint.config.js` file to use: +When ESLint is executed, the following steps are taken to find the `eslint.config.js` file to use: 1. If the `-c` flag is used then the specified configuration file is used. There is no further search performed. 1. Otherwise: - a. Look for `.eslint.config.js` in the current working directory. If founnd, stop searching and use that file. - b. If not found, search up the directory hierarchy looking for `.eslint.config.js`. - c. If a `.eslint.config.js` file is found at any point, stop searching and use that file. - d. If `/` is reached without finding `.eslint.config.js`, then stop searching and output a "no configuration found" error. + a. Look for `eslint.config.js` in the current working directory. If found, stop searching and use that file. + b. If not found, search up the directory hierarchy looking for `eslint.config.js`. + c. If a `eslint.config.js` file is found at any point, stop searching and use that file. + d. If `/` is reached without finding `eslint.config.js`, then stop searching and output a "no configuration found" error. -This approach will allow running ESLint from within a subdirectory of a project and get the same result as when ESLint is run from the project's root directory (the one where `.eslint.config.js` is found). +This approach will allow running ESLint from within a subdirectory of a project and get the same result as when ESLint is run from the project's root directory (the one where `eslint.config.js` is found). Some of the key differences from the way ESLint's configuration resolution works today are: -1. There is no automatic search for `.eslint.config.js` in the user's home directory. Users wanting this functionality can either pass a home directory file using `-c` or manually read in that file from their `.eslint.config.js` file. -1. Once a `.eslint.config.js` file is found, there is no more searching for any further config files. +1. There is no automatic search for `eslint.config.js` in the user's home directory. Users wanting this functionality can either pass a home directory file using `-c` or manually read in that file from their `eslint.config.js` file. +1. Once a `eslint.config.js` file is found, there is no more searching for any further config files. 1. There is no automatic merging of config files using either `extends` or `overrides`. 1. When `-c` is passed on the command line, there is no search performed. ### File Pattern Resolution -Because there are file patterns included in `.eslint.config.js`, this requires a change to how ESLint decides which files to lint. The process for determining which files to lint is: +Because there are file patterns included in `eslint.config.js`, this requires a change to how ESLint decides which files to lint. The process for determining which files to lint is: 1. When a filename is passed directly (such as `eslint foo.js`): - 1. ESLint checks to see if there is a config where the `files` pattern matches the file that was passed in. The pattern is evaluated by prepending the directory in which `.eslint.config.js` was found to each pattern. If no config is found, then the file is ignored (with an appropriate warning). + 1. ESLint checks to see if there is a config where the `files` pattern matches the file that was passed in. The pattern is evaluated by prepending the directory in which `eslint.config.js` was found to each pattern. The first config with a `files` pattern that matches wins. If no config is found, then the file is ignored (with an appropriate warning). 1. If a matching config is found, then the `ignore` pattern is tested against the filename. If it's a match, then the file is ignored. Otherwise, the file is linted. 1. When a glob pattern is passed directly (such as `eslint src/*.js`): 1. ESLint expands the glob pattern to get a list of files. @@ -489,6 +489,10 @@ Because there are file patterns included in `.eslint.config.js`, this requires a 1. The directory is converted into a glob pattern (such as `src` becomes `src/**`). 1. The glob pattern is checked as in step 2. +### Rename `--use-eslintrc` to `--use-config-file` + +Because the config filename has changed, it makes sense to change the `--use-eslintrc` flag to a more generic name, `--use-config-file`. In the short term, to avoid a breaking change, these two names can be aliased to each other. + ## Documentation This will require extensive documentation changes and an introductory blog post. @@ -505,7 +509,7 @@ At a minimum, these pages will have to be updated (and rewritten): As with any significant change, there are some significant drawbacks: 1. We'd need to do a phased rollout (see Backwards Compatibility Analysis) to minimize impact on users. That means maintaining two configuration systems for a while, increasing maintenance overhead and complexity of the application. -1. Getting everyone to convert to `.eslint.config.js` format would be a significant stress on the community that could cause some resentment. +1. Getting everyone to convert to `eslint.config.js` format would be a significant stress on the community that could cause some resentment. 1. We can no longer enforce naming conventions for plugins and shareable configs. This may make it more difficult for users to find compatible npm packages. 1. Creating configuration files will be more complicated. 1. The `--print-config` option becomes less useful because we can't output objects in a meaningful way. @@ -517,22 +521,23 @@ The intent of this proposal is to replace the current `.eslintrc` format, but ca In the first phase, I envision this: -1. `.eslint.config.js` can be implemented alongside `.eslintrc`. -1. If `.eslint.config.js` is found, then: +1. `eslint.config.js` can be implemented alongside `.eslintrc`. +1. If `eslint.config.js` is found, then: 1. All `.eslintrc` files are ignored. 1. `.eslintignore` is ignored. 1. `--ignore-file` is ignored. 1. `--rulesdir` is ignored. 1. `--env` is ignored. 1. `--ext` is ignored. + 1. `--use-eslintrc` is ignored. 1. `eslint-env` config comments are ignored. -1. If `.eslint.config.js` is not found, then fall back to the current behavior. +1. If `eslint.config.js` is not found, then fall back to the current behavior. -This keeps the current behavior for the majority of users while allowing some users to test out the new functionality. Also, `-c` could not be used with `.eslint.config.js` in this phase. +This keeps the current behavior for the majority of users while allowing some users to test out the new functionality. Also, `-c` could not be used with `eslint.config.js` in this phase. -In the second phase (and in a major release), ESLint will emit deprecation warnings whenever the original functionality is used but will still honor them so long as `.eslint.config.js` is not found. +In the second phase (and in a major release), ESLint will emit deprecation warnings whenever the original functionality is used but will still honor them so long as `eslint.config.js` is not found. -In the third phase (and in another major release), `.eslint.config.js` becomes the official way to configure ESLint. If no `.eslint.config.js` file is found, ESLint will still search for a `.eslintrc` file, and if found, print an error message information the user that the configuration file format has changed. +In the third phase (and in another major release), `eslint.config.js` becomes the official way to configure ESLint. If no `eslint.config.js` file is found, ESLint will still search for a `.eslintrc` file, and if found, print an error message information the user that the configuration file format has changed. So while this is intended to be a breaking change, it will be introduced over the course of three major releases in order to give users ample time to transition. @@ -546,10 +551,10 @@ While there are no alternatives that cover all of the functionality in this RFC, ## Open Questions -1. Should `ignore` allow line breaks so it can emulate `.eslintignore`? 1. Is `ruledefs` a clear enough key name? -1. Do we need a command line flag to opt-in to `.eslint.config.js` instead of trying to do it alongside the existing configuration system? +1. Do we need a command line flag to opt-in to `eslint.config.js` instead of trying to do it alongside the existing configuration system? 1. Does the file pattern system actually remove the need for `--ext`? +1. How should `files` and `ignore` be merged when a shareable config has then? Should they be overwritten or merged? ## Frequently Asked Questions @@ -561,6 +566,10 @@ No. Right now it won't be possible to implement a config with an async function Using an exported key gives us more flexibility for the future if we decide that config files should be able to output more than one thing. For example, I've been thinking of a `--config-key` option that would allow users to specify which exported key should be used as their config. Users could then export multiple different keys (`config1`, `config2`, etc.) and easily switch between configs on the command line. That option is not part of this proposal because it isn't solving an existing problem and I'd rather focus on existing problems first (this proposal is already big enough). +### How does this affect configuration via `package.json`? + +The `eslintConfig` and `eslintIgnore` keys in `package.json` will not be honored when `eslint.config.js` is found. Users could still pull that information into their `eslint.config.js` file manually if they want to. + ## Related Discussions * https://github.com/eslint/rfcs/pull/7 From e6a55677578762548a1b2084c24e5e51c5881c2e Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 25 Jan 2019 09:42:10 -0800 Subject: [PATCH 10/78] Add --ext example, remove Config.create --- designs/2019-config-simplification/README.md | 60 +++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 1219a560..e9fd6965 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -270,6 +270,34 @@ exports.config = { }; ``` +### Replacing `--ext` + +The `--ext` flag is currently used to pass in one or more file extensions that ESLint should search for when a directory without a glob pattern is passed on the command line, such as: + +```bash +eslint src/ --ext .js,.jsx +``` + +This curently searches all subdirectories of `src/` for files with extensions matching `.js` or `.jsx`. + +This proposal removes `--ext` by allowing the same information to be passed in a config. For example, the following config achieves the same result: + +```js +const fs = require("fs"); + +exports.config = { + files: ["*.js", "*.jsx"], +}; +``` + +ESLint could then be run with this command: + +```bash +eslint src/ +``` + +When evaluating the `files` array in the config, ESLint will end up searching for `src/**/*.js` and `src/**/*.jsx`. (More information about file resolution is included later this proposal.) + ### The `@eslint/config` Package Because some of the operations ESLint currently do are quite complicated, this design includes a utility package called `@eslint/config` that users can use for common tasks. This package contains the following utilities: @@ -294,12 +322,6 @@ class Config { // convenience method for easily setting rule severity setRuleSeverity(ruleId, severity) {} - // determine if an object is a Config - static isConfig(object) {} - - // create a new config based on another config - static create(baseConfig, ...overrideConfigs) {} - // merge info from other configs into a base config static assign(receiverConfig, ...supplierConfigs) {} } @@ -330,31 +352,15 @@ config.setRuleSeverity("semi", "warn"); In this way, users don't need to modify an array to change a rule's severity, which is a major downside of this design when using a vanilla object structure as a config. -The static methods on `Config` make it easy to create and modify configs. For example, suppose you want to create a config based on `eslint-config-standard` but would prefer `semi` be a warning instead of an error, you can do so using `Config.create()`: - -```js -const standardConfig = require("eslint-config-standard"); -const { Config } = require("@eslint/config"); - -const myConfig = Config.create(standardConfig, { - rules: { - semi: "warn" - } -}); - -exports.config = myConfig; -``` - -Here, the `Config.create()` method creates a new `Config` object based on `standardConfig` and then merges in the overrides from the second argument. The overrides config just changed the severity of `semi` to be `"warn"` but does not affect the other `semi` options. (This implements the current config merging functionality in ESLint.) - -Similarly, the `Config.assign()` method lets users modify their config by merging in changes from other configs, such as: +The `Config.assign()` method lets users modify their config by merging in changes from other configs, such as: ```js const standardConfig = require("eslint-config-standard"); const personalConfig = require("@personal/eslint-config"); const { Config } = require("@eslint/config"); -const myConfig = Config.create(standardConfig, { +const myConfig = { + extends: standardConfig, rules: { semi: "warn" } @@ -486,7 +492,7 @@ Because there are file patterns included in `eslint.config.js`, this requires a 1. ESLint expands the glob pattern to get a list of files. 1. Each file is checked individually as in step 1. 1. When a directory is passed directly (such as `eslint src`): - 1. The directory is converted into a glob pattern (such as `src` becomes `src/**`). + 1. The directory is converted into a glob pattern by appending the contents of the `files` array (such as `src` becomes `src/**/*.js`). 1. The glob pattern is checked as in step 2. ### Rename `--use-eslintrc` to `--use-config-file` @@ -554,7 +560,7 @@ While there are no alternatives that cover all of the functionality in this RFC, 1. Is `ruledefs` a clear enough key name? 1. Do we need a command line flag to opt-in to `eslint.config.js` instead of trying to do it alongside the existing configuration system? 1. Does the file pattern system actually remove the need for `--ext`? -1. How should `files` and `ignore` be merged when a shareable config has then? Should they be overwritten or merged? +1. How should `files` and `ignore` be merged when a shareable config has them? Should they be overwritten or merged? ## Frequently Asked Questions From 123c00c2d5750e7b535e1c36cbaccfad10381fd0 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 28 Jan 2019 08:03:11 -0800 Subject: [PATCH 11/78] Added two more FAQs --- designs/2019-config-simplification/README.md | 48 ++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index e9fd6965..aca89b2f 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -576,6 +576,54 @@ Using an exported key gives us more flexibility for the future if we decide that The `eslintConfig` and `eslintIgnore` keys in `package.json` will not be honored when `eslint.config.js` is found. Users could still pull that information into their `eslint.config.js` file manually if they want to. +### Do shareable configs still export an object on `module.exports`? + +That is completely up to the shareable config. For simplicity sake, I think we still want to encourage people to do so. However, there is no longer and formal contract between ESLint and shareable configs, so developers could potentially export configs from any npm package using any exported key. They would just need to inform users about how to extend their config properly. + +### Is there a way to allow plugins to specify the namespace of their rules? + +Maybe, but even if we could do that, the namespaces aren't guaranteed to be unique. + +The reason we could enforce naming of both plugin packages and rule namespaces is because ESLint controlled how the plugins were loaded: users passed ESLint a string and then ESLint could both inspect the string to pull out details it needed (the plugin name without the `eslint-plugin-` prefix) and then modify the rule names to be prepended with the plugin name. + +In this design, the user is responsible for loading npm packages. Because we are only ever passed an object, we no longer have access to the plugin name. + +An earlier iteration of this design had plugins specified like this: + +```js +exports.config = { + plugins: [ + require("eslint-plugin-react"), + require("eslint-plugin-mardkown") + ] +}; +``` + +This didn't work because ESLint didn't know how to name the plugin rules (we weren't getting the plugin name, just the object). So the next iteration went to this: + +```js +exports.config = { + plugins: { + react: require("eslint-plugin-react"), + markdown: require("eslint-plugin-mardkown") + } +}; +``` + +This iteration gave the plugin rules names, but then I realized the only things that need names in plugins with this design are rules. Users can get processors, parsers, etc., directly without ESLint being involved in picking them out of plugins. So I renamed `plugins` to `ruledefs` to make this explicit: + +```js +exports.config = { + ruledefs: { + react: require("eslint-plugin-react").rules, + markdown: require("eslint-plugin-mardkown").rules + } +}; +``` + +This is the iteration I first submitted. + + ## Related Discussions * https://github.com/eslint/rfcs/pull/7 From 103bfcc219f5b841103428b24288794cd3914881 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 29 Jan 2019 09:18:39 -0800 Subject: [PATCH 12/78] Update config array behavior --- designs/2019-config-simplification/README.md | 74 +++++++++++++++++--- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index aca89b2f..d00f5203 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -1,6 +1,7 @@ - Start Date: 2019-01-20 -- RFC PR: TBD +- RFC PR: https://github.com/eslint/rfcs/pull/9 - Authors: Nicholas C. Zakas (@nzakas) +- Contributors: Teddy Katz (@not-an-ardvaark), Toru Nagashima (@mysticatea) # Config File Simplification @@ -53,7 +54,7 @@ The `eslint.config.js` file is a JavaScript file (there is no JSON or YAML equiv ```js exports.config = { files: ["*.js"], - ignore: ["*.test.js"], + ignores: ["*.test.js"], extends: [], globals: {}, settings: {}, @@ -68,7 +69,7 @@ exports.config = { The following keys are new to the `eslint.config.js` format: * `files` - **Required.** Determines the glob file patterns that this configuration applies to. -* `ignore` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`. +* `ignores` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`. * `ruledefs` - Contains definitions for rules grouped by a specific name. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option. The following keys are specified the same as in `.eslintrc` files: @@ -167,7 +168,7 @@ exports.config = { This effectively duplicates the use of `env: { browser: true }` in ESLint. -**Note:** This would allow us to stop environments in ESLint. We could just tell people to use `globals` in their config and allow them to specify which version of `globals` they want to use. +**Note:** This would allow us to stop shipping environments in ESLint. We could just tell people to use `globals` in their config and allow them to specify which version of `globals` they want to use. #### Extending Another Config @@ -255,7 +256,62 @@ exports.config = [ ]; ``` -When ESLint uses this config, it will check each `files` pattern to determine which config applies. In this way, returning an array acts exactly the same as the array in `overrides`, with the exception that no configuration options are inherited. +When ESLint uses this config, it will check each `files` pattern to determine which configs apply. Any config with a `files` pattern matching the file to lint will be extracted and used (if multiple configs match, then those configs are merged to determine the final config to use). In this way, returning an array acts exactly the same as the array in `overrides`. + +If a config in the config array does not contain `files` or `ignores`, then that config applies to all files. For example: + +```js +exports.config = [ + { + globals: { + Foo: true + } + }, + { + files: "*.js", + ruledefs: { + react: require("eslint-plugin-react").rules, + }, + rules: { + "react/jsx-uses-react": "error", + semi: "error" + } + }, + { + files: "*.md", + processor: require("eslint-plugin-markdown").processors.markdown + } +]; +``` + +In this example, the first config in the array defines a global variable of `Foo`. That global variable is merged into the other two configs in the array automatically because there is no `files` or `ignores` specifying when it should be used. + +Each item in a config array can be a config array. For example, this is a valid config array: + +```js +exports.config = [ + [ + require("eslint-config-vue"), + require("eslint-config-import") + ] + { + files: "*.js", + ruledefs: { + react: require("eslint-plugin-react").rules, + }, + rules: { + "react/jsx-uses-react": "error", + semi: "error" + } + }, + { + files: "*.md", + processor: require("eslint-plugin-markdown").processors.markdown + } +]; +``` + +A config array is always flattened before being evaluating, so either though this example is a two-dimensional config array, it will be evaluated as if it were a one-dimensional config array. #### Replacing `.eslintignore` @@ -266,7 +322,7 @@ const fs = require("fs"); exports.config = { files: ["*.js"], - ignore: fs.readFileSync(".eslintignore", "utf8").split("\n") + ignores: fs.readFileSync(".eslintignore", "utf8").split("\n") }; ``` @@ -486,8 +542,8 @@ Some of the key differences from the way ESLint's configuration resolution works Because there are file patterns included in `eslint.config.js`, this requires a change to how ESLint decides which files to lint. The process for determining which files to lint is: 1. When a filename is passed directly (such as `eslint foo.js`): - 1. ESLint checks to see if there is a config where the `files` pattern matches the file that was passed in. The pattern is evaluated by prepending the directory in which `eslint.config.js` was found to each pattern. The first config with a `files` pattern that matches wins. If no config is found, then the file is ignored (with an appropriate warning). - 1. If a matching config is found, then the `ignore` pattern is tested against the filename. If it's a match, then the file is ignored. Otherwise, the file is linted. + 1. ESLint checks to see if there is one or more configs where the `files` pattern matches the file that was passed in. The pattern is evaluated by prepending the directory in which `eslint.config.js` was found to each pattern. All configs with a matching `files` pattern are merged (with the last matching config taking precedence over others). If no config is found, then the file is ignored with an appropriate warning. + 1. If a matching config is found, then the `ignores` pattern is tested against the filename. If it's a match, then the file is ignored. Otherwise, the file is linted. 1. When a glob pattern is passed directly (such as `eslint src/*.js`): 1. ESLint expands the glob pattern to get a list of files. 1. Each file is checked individually as in step 1. @@ -560,7 +616,7 @@ While there are no alternatives that cover all of the functionality in this RFC, 1. Is `ruledefs` a clear enough key name? 1. Do we need a command line flag to opt-in to `eslint.config.js` instead of trying to do it alongside the existing configuration system? 1. Does the file pattern system actually remove the need for `--ext`? -1. How should `files` and `ignore` be merged when a shareable config has them? Should they be overwritten or merged? +1. How should `files` and `ignores` be merged when a shareable config has them? Should they be overwritten or merged? ## Frequently Asked Questions From c2975e60a3b9fe6b498107c6263fd0c129081f09 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 29 Jan 2019 09:21:18 -0800 Subject: [PATCH 13/78] Remove class - no longer needed --- designs/2019-config-simplification/README.md | 80 ++------------------ 1 file changed, 6 insertions(+), 74 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index d00f5203..49ffd8db 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -45,7 +45,7 @@ Design Summary: 1. Remove the concept of environments (`env`) 1. Remove `.eslintignore` and `--ignore-file` (no longer necessary) 1. Remove `--rulesdir` -1. Create a `@eslint/config` package to help users merge configs +1. Create a `@eslint/config` package to help users manager configs ### The `eslint.config.js` File @@ -358,77 +358,12 @@ When evaluating the `files` array in the config, ESLint will end up searching fo Because some of the operations ESLint currently do are quite complicated, this design includes a utility package called `@eslint/config` that users can use for common tasks. This package contains the following utilities: -1. `Config` - a class that both wraps a configuration object to provide helper methods for manipulating configs and a container for static methods to work with configs. 1. `RuleLoader` - a class that aids in loading rules from a directory. The `@eslint/config` package is intended to add back functionality that would be removed from ESLint with this design. **Note:** It is not required to use this package with `eslint.config.js`; this is an optional tool that users may choose to use for convenience. -#### `Config` Class - -The `Config` class has this form: - -```js -class Config { - - // pass in any object literal to wrap in Config - constructor(object) {} - - // convenience method for easily setting rule severity - setRuleSeverity(ruleId, severity) {} - - // merge info from other configs into a base config - static assign(receiverConfig, ...supplierConfigs) {} -} -``` - -A new instance of `Config` is created like this: - -```js -const { Config } = require("@eslint/config"); - -const config = new Config({ - files: ["*.js"], - ruledefs: { - react: require("eslint-plugin-react").rules, - }, - rules: { - "react/jsx-uses-react": "error", - semi: ["error", "always"] - } -}); -``` - -After that point, you can easily change the severity of a rule like this: - -```js -config.setRuleSeverity("semi", "warn"); -``` - -In this way, users don't need to modify an array to change a rule's severity, which is a major downside of this design when using a vanilla object structure as a config. - -The `Config.assign()` method lets users modify their config by merging in changes from other configs, such as: - -```js -const standardConfig = require("eslint-config-standard"); -const personalConfig = require("@personal/eslint-config"); -const { Config } = require("@eslint/config"); - -const myConfig = { - extends: standardConfig, - rules: { - semi: "warn" - } -}); - -Config.assign(myConfig, personalConfig); - -exports.config = myConfig; -``` - -This code example merges `personalConfig` into `myConfig` using the same functionality that ESLint currently uses to merge configs. - #### The `RuleLoader` Class The purpose of the `RuleLoader` class is to aid in loading rules to replace `--rulesdir` functionality and has this form: @@ -447,17 +382,17 @@ class RuleLoader { In order to recreate the functionality of `--rulesdir`, a user would need to create a new entry in `ruledefs` and then specify the rules from a directory, such as: ```js -const { Config, RuleLoader } = require("@eslint/config"); +const { RuleLoader } = require("@eslint/config"); const ruleLoader = new RuleLoader(); -exports.config = new Config({ +exports.config = { ruledefs: { custom: ruleLoader.loadFromDirectory("./custom-rules") }, rules: { "custom/my-rule": "error" } -}); +}; ``` The `RuleLoader#loadFromDirectory()` method returns an object where the keys are the rule IDs (based on the filenames found in the directory) and the values are the rule objects. Unlike today, rules loaded from a local directory must have a namespace just like plugin rules (`custom` in this example). @@ -495,10 +430,8 @@ This information allows users to make logical decisions about how the config sho One of the problems with shareable configs today is when a new rule is added to the ESLint core, shareable configs using that rule are not valid for older versions of ESLint (because ESLint validates that configured rules are present). With advanced configs, a shareable config could detect if a new rule is present before deciding to include it, for example: ```js -const { Config } = require("@eslint/config"); - exports.config = (context) => { - const myConfig = new Config({ + const myConfig = { files: ["*.js"], ruledefs: { custom: ruleLoader.loadFromDirectory("./custom-rules") @@ -506,14 +439,13 @@ exports.config = (context) => { rules: { "custom/my-rule": "error" } - }); + }; if (context.hasRule("some-new-rule")) { myConfig.rules["some-new-rule"] = ["error"]; } return myConfig; - }; ``` From 849ec2788464ba331fc6e72e1d4f302b93a98309 Mon Sep 17 00:00:00 2001 From: Brandon Mills Date: Wed, 30 Jan 2019 08:11:54 -0700 Subject: [PATCH 14/78] Clarify command line changes Co-Authored-By: nzakas --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 49ffd8db..22dc193e 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -485,7 +485,7 @@ Because there are file patterns included in `eslint.config.js`, this requires a ### Rename `--use-eslintrc` to `--use-config-file` -Because the config filename has changed, it makes sense to change the `--use-eslintrc` flag to a more generic name, `--use-config-file`. In the short term, to avoid a breaking change, these two names can be aliased to each other. +Because the config filename has changed, it makes sense to change the command line `--no-eslintrc` flag to a more generic name, `--no-config-file` and change `CLIEngine`'s `useEslintrc` option to `useConfigFile`. In the short term, to avoid a breaking change, these pairs of names can be aliased to each other. ## Documentation From ede3d317315468f6a98c54769f4ebe57b9e3a26c Mon Sep 17 00:00:00 2001 From: Brandon Mills Date: Wed, 30 Jan 2019 08:12:29 -0700 Subject: [PATCH 15/78] Update config name options Co-Authored-By: nzakas --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 22dc193e..d82119d7 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -483,7 +483,7 @@ Because there are file patterns included in `eslint.config.js`, this requires a 1. The directory is converted into a glob pattern by appending the contents of the `files` array (such as `src` becomes `src/**/*.js`). 1. The glob pattern is checked as in step 2. -### Rename `--use-eslintrc` to `--use-config-file` +### Rename `--no-eslintrc` to `--no-config-file` and `useEslintrc` to `useConfigFile` Because the config filename has changed, it makes sense to change the command line `--no-eslintrc` flag to a more generic name, `--no-config-file` and change `CLIEngine`'s `useEslintrc` option to `useConfigFile`. In the short term, to avoid a breaking change, these pairs of names can be aliased to each other. From e6094b14b4127482848f9a61f331793b1b404b0e Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 30 Jan 2019 07:15:43 -0800 Subject: [PATCH 16/78] Clarify files/ignores matching order --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index d82119d7..edc2b008 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -474,7 +474,7 @@ Some of the key differences from the way ESLint's configuration resolution works Because there are file patterns included in `eslint.config.js`, this requires a change to how ESLint decides which files to lint. The process for determining which files to lint is: 1. When a filename is passed directly (such as `eslint foo.js`): - 1. ESLint checks to see if there is one or more configs where the `files` pattern matches the file that was passed in. The pattern is evaluated by prepending the directory in which `eslint.config.js` was found to each pattern. All configs with a matching `files` pattern are merged (with the last matching config taking precedence over others). If no config is found, then the file is ignored with an appropriate warning. + 1. ESLint checks to see if there is one or more configs where the `files` pattern matches the file that was passed in and does not match the `ignores` pattern. The pattern is evaluated by prepending the directory in which `eslint.config.js` was found to each pattern in `files`. All configs that match `files` and not `ignores` are merged (with the last matching config taking precedence over others). If no config is found, then the file is ignored with an appropriate warning. 1. If a matching config is found, then the `ignores` pattern is tested against the filename. If it's a match, then the file is ignored. Otherwise, the file is linted. 1. When a glob pattern is passed directly (such as `eslint src/*.js`): 1. ESLint expands the glob pattern to get a list of files. From dfa090960f7f52d5b30cd446af25e1433581e919 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 30 Jan 2019 07:19:46 -0800 Subject: [PATCH 17/78] Updated directory processing on command line --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index edc2b008..0b3108db 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -480,7 +480,7 @@ Because there are file patterns included in `eslint.config.js`, this requires a 1. ESLint expands the glob pattern to get a list of files. 1. Each file is checked individually as in step 1. 1. When a directory is passed directly (such as `eslint src`): - 1. The directory is converted into a glob pattern by appending the contents of the `files` array (such as `src` becomes `src/**/*.js`). + 1. The directory is converted into a glob pattern by appending `**/*` to the directory (such as `src` becomes `src/**/*`). 1. The glob pattern is checked as in step 2. ### Rename `--no-eslintrc` to `--no-config-file` and `useEslintrc` to `useConfigFile` From 889096ec5ca52ac357e50ab6a7e49b7bb93675db Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 30 Jan 2019 07:21:27 -0800 Subject: [PATCH 18/78] Fix typo --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 0b3108db..fbf5d9f0 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -311,7 +311,7 @@ exports.config = [ ]; ``` -A config array is always flattened before being evaluating, so either though this example is a two-dimensional config array, it will be evaluated as if it were a one-dimensional config array. +A config array is always flattened before being evaluating, so even though this example is a two-dimensional config array, it will be evaluated as if it were a one-dimensional config array. #### Replacing `.eslintignore` From c1f41b68710466f3488b145a49f51e0296a78945 Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Fri, 1 Feb 2019 10:03:56 -0700 Subject: [PATCH 19/78] Fix typo Co-Authored-By: nzakas --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index fbf5d9f0..9a525e16 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -45,7 +45,7 @@ Design Summary: 1. Remove the concept of environments (`env`) 1. Remove `.eslintignore` and `--ignore-file` (no longer necessary) 1. Remove `--rulesdir` -1. Create a `@eslint/config` package to help users manager configs +1. Create a `@eslint/config` package to help users manage configs ### The `eslint.config.js` File From e274a01659260df18f227bff4289a62d0348d620 Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Fri, 1 Feb 2019 10:18:20 -0700 Subject: [PATCH 20/78] Fix typo Co-Authored-By: nzakas --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 9a525e16..b28f409a 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -226,7 +226,7 @@ Whereas `.eslintrc` had an `overrides` key that made a hierarchical structure, t ```yaml plugins: ["react"] rules: - react/jsx-uses-react: error + react/jsx-uses-react: error semi: error overrides: From d9c3d8d5ca83c21236c3bb7a30954917577fd1fd Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Fri, 1 Feb 2019 10:22:21 -0700 Subject: [PATCH 21/78] Fix typo Co-Authored-By: nzakas --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index b28f409a..ce9761ec 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -311,7 +311,7 @@ exports.config = [ ]; ``` -A config array is always flattened before being evaluating, so even though this example is a two-dimensional config array, it will be evaluated as if it were a one-dimensional config array. +A config array is always flattened before being evaluated, so even though this example is a two-dimensional config array, it will be evaluated as if it were a one-dimensional config array. #### Replacing `.eslintignore` From 6cff5778a91842805015266415b62657f9b42a0e Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 1 Feb 2019 08:41:03 -0800 Subject: [PATCH 22/78] ruledefs namespaces must be unique; plugins specifying namespaces --- designs/2019-config-simplification/README.md | 60 ++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index ce9761ec..e9f980af 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -128,6 +128,66 @@ exports.config = { Here, it is the `ruledefs` that assigns the name `react` to the rules from `eslint-plugin-react`. The reference to `react/` in a rule will always look up that value in the `ruledefs` key. +**Note:** If the config `extends` another config that already has a `ruledefs` namespace defined, then an error is thrown. In this case, if a config already has a `react` namespace, then attempting to combine with another config that has a `react` namespace will throw an error. This is to ensure the meaning of `namespace/rule` remains consistent. + +#### Suggested Plugin Improvements + +Rules imported from a plugin must be assigned a namespace using `ruledefs`, which puts the responsibility for that namespace on the config file user. Plugins can define their own namespace for rules in two ways. + +First, a plugin can export a recommended configuration to place in the `extends` key. For example, a plugin called `eslint-plugin-example`, might define a config that looks like this: + +```js +exports.configs = { + recommended: { + ruledefs: { + example: { + rule1: require("./rules/rule1") + } + } + } +}; +``` + +Then, inside of a user config, the plugin's recommended config can be loaded: + +```js +exports.config = { + extends: [ + require("eslint-plugin-example").configs.recommended + ], + rules: { + "example/rule1": "error" + } +}; +``` + +The user config in this example now inherits the `ruledefs` from the plugin's recommended config, automatically adding in the rules with their preferred namespace. (Note that the user config can't have another `ruledefs` namespace called `example` without an error being thrown.) + +The second way for plugins to specify their preferred namespace is to export a `ruledefs` key directly that users can include their own config. This is what it would look like in the plugin: + +```js +exports.ruledefs = { + example: { + rule1: require("./rules/rule1") + } +}; +``` + +Then, inside of a user config, the plugin's `ruledefs` can be included directly`: + +```js +exports.config = { + ruledefs: { + ...require("eslint-plugin-example").ruledefs + }, + rules: { + "example/rule1": "error" + } +}; +``` + +This example imports the `ruledefs` from a plugin directly into the same section in the user config. + #### Referencing Parsers and Processors In `.eslintrc`, the `parser` and `processor` keys required strings to be specified, such as: From f5383269fd883a431d9a969e53e85cfee844e7c5 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 1 Feb 2019 09:03:29 -0800 Subject: [PATCH 23/78] Clarify what happens when 'eslint .' is run --- designs/2019-config-simplification/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index e9f980af..8e5fa030 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -542,6 +542,9 @@ Because there are file patterns included in `eslint.config.js`, this requires a 1. When a directory is passed directly (such as `eslint src`): 1. The directory is converted into a glob pattern by appending `**/*` to the directory (such as `src` becomes `src/**/*`). 1. The glob pattern is checked as in step 2. +1. When a relative directory is passed directly (such as `eslint .`): + 1. The relative directory pattern is resolved to a full directory name. + 1. The glob pattern is checked as in step 3. ### Rename `--no-eslintrc` to `--no-config-file` and `useEslintrc` to `useConfigFile` From 77878dd105b074caf604eb084b5825f89bc2d2ff Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 1 Feb 2019 09:11:28 -0800 Subject: [PATCH 24/78] Clarify what a config function can return --- designs/2019-config-simplification/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 8e5fa030..eca1f3a8 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -485,6 +485,8 @@ The `context` object has the following members: This information allows users to make logical decisions about how the config should be constructed. +A configuration function may return an object or an array of objects. An error is thrown if any other type of value is returned. + #### Checking for Rule Existence One of the problems with shareable configs today is when a new rule is added to the ESLint core, shareable configs using that rule are not valid for older versions of ESLint (because ESLint validates that configured rules are present). With advanced configs, a shareable config could detect if a new rule is present before deciding to include it, for example: From d28bb5b0a9f8d55ae37b4f4308fb1caba73fcfba Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 1 Feb 2019 09:14:02 -0800 Subject: [PATCH 25/78] Clarify when 'files' is required in a config array --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index eca1f3a8..3572c0c0 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -318,7 +318,7 @@ exports.config = [ When ESLint uses this config, it will check each `files` pattern to determine which configs apply. Any config with a `files` pattern matching the file to lint will be extracted and used (if multiple configs match, then those configs are merged to determine the final config to use). In this way, returning an array acts exactly the same as the array in `overrides`. -If a config in the config array does not contain `files` or `ignores`, then that config applies to all files. For example: +When using a config array, only one config object must have a `files` key (config arrays where no objects contain `files` will result in an error). If a config in the config array does not contain `files` or `ignores`, then that config applies to all files. For example: ```js exports.config = [ From 2e710143f9172a425f6740ce3c31e2c236e30906 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 1 Feb 2019 09:22:07 -0800 Subject: [PATCH 26/78] Clarify what a config with no 'files' does --- designs/2019-config-simplification/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 3572c0c0..4e79b5ab 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -318,7 +318,7 @@ exports.config = [ When ESLint uses this config, it will check each `files` pattern to determine which configs apply. Any config with a `files` pattern matching the file to lint will be extracted and used (if multiple configs match, then those configs are merged to determine the final config to use). In this way, returning an array acts exactly the same as the array in `overrides`. -When using a config array, only one config object must have a `files` key (config arrays where no objects contain `files` will result in an error). If a config in the config array does not contain `files` or `ignores`, then that config applies to all files. For example: +When using a config array, only one config object must have a `files` key (config arrays where no objects contain `files` will result in an error). If a config in the config array does not contain `files` or `ignores`, then that config is merged into every config with a `files` pattern. For example: ```js exports.config = [ @@ -344,7 +344,7 @@ exports.config = [ ]; ``` -In this example, the first config in the array defines a global variable of `Foo`. That global variable is merged into the other two configs in the array automatically because there is no `files` or `ignores` specifying when it should be used. +In this example, the first config in the array defines a global variable of `Foo`. That global variable is merged into the other two configs in the array automatically because there is no `files` or `ignores` specifying when it should be used. The first config matches zero files on its own and would be invalid if it was the only config in the config array. Each item in a config array can be a config array. For example, this is a valid config array: From 558b4a29b268e74f3d1ec2456ee354e66f1c5562 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 1 Feb 2019 09:27:21 -0800 Subject: [PATCH 27/78] Add note about node_modules --- designs/2019-config-simplification/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 4e79b5ab..de3f9d0b 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -548,6 +548,8 @@ Because there are file patterns included in `eslint.config.js`, this requires a 1. The relative directory pattern is resolved to a full directory name. 1. The glob pattern is checked as in step 3. +**Note:** ESLint will continue to ignore `node_modules` by default. + ### Rename `--no-eslintrc` to `--no-config-file` and `useEslintrc` to `useConfigFile` Because the config filename has changed, it makes sense to change the command line `--no-eslintrc` flag to a more generic name, `--no-config-file` and change `CLIEngine`'s `useEslintrc` option to `useConfigFile`. In the short term, to avoid a breaking change, these pairs of names can be aliased to each other. From 25c9051ba34c55c485169e6bff5243d9f0828e1a Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 4 Feb 2019 07:00:32 -0800 Subject: [PATCH 28/78] Remove @eslint/config package --- designs/2019-config-simplification/README.md | 35 +++----------------- 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index de3f9d0b..1e4daac3 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -45,7 +45,6 @@ Design Summary: 1. Remove the concept of environments (`env`) 1. Remove `.eslintignore` and `--ignore-file` (no longer necessary) 1. Remove `--rulesdir` -1. Create a `@eslint/config` package to help users manage configs ### The `eslint.config.js` File @@ -414,40 +413,16 @@ eslint src/ When evaluating the `files` array in the config, ESLint will end up searching for `src/**/*.js` and `src/**/*.jsx`. (More information about file resolution is included later this proposal.) -### The `@eslint/config` Package +### Replacing `--rulesdir` -Because some of the operations ESLint currently do are quite complicated, this design includes a utility package called `@eslint/config` that users can use for common tasks. This package contains the following utilities: - -1. `RuleLoader` - a class that aids in loading rules from a directory. - -The `@eslint/config` package is intended to add back functionality that would be removed from ESLint with this design. - -**Note:** It is not required to use this package with `eslint.config.js`; this is an optional tool that users may choose to use for convenience. - -#### The `RuleLoader` Class - -The purpose of the `RuleLoader` class is to aid in loading rules to replace `--rulesdir` functionality and has this form: - -```js -class RuleLoader { - - // create a new instance - constructor() {} - - // mimic `--rulesdir` loading - loadRulesFromDirectory(directory) {} -} -``` - -In order to recreate the functionality of `--rulesdir`, a user would need to create a new entry in `ruledefs` and then specify the rules from a directory, such as: +In order to recreate the functionality of `--rulesdir`, a user would need to create a new entry in `ruledefs` and then specify the rules from a directory. This can be accomplished using the [`requireindex`](https://npmjs.com/package/requireindex) npm package: ```js -const { RuleLoader } = require("@eslint/config"); -const ruleLoader = new RuleLoader(); +const requireIndex = require("requireindex"); exports.config = { ruledefs: { - custom: ruleLoader.loadFromDirectory("./custom-rules") + custom: requireIndex("./custom-rules") }, rules: { "custom/my-rule": "error" @@ -455,7 +430,7 @@ exports.config = { }; ``` -The `RuleLoader#loadFromDirectory()` method returns an object where the keys are the rule IDs (based on the filenames found in the directory) and the values are the rule objects. Unlike today, rules loaded from a local directory must have a namespace just like plugin rules (`custom` in this example). +The `requireIndex()` method returns an object where the keys are the rule IDs (based on the filenames found in the directory) and the values are the rule objects. Unlike today, rules loaded from a local directory must have a namespace just like plugin rules (`custom` in this example). ### Advanced Configs From 7d25988e2541459dab6549db79f027e7fa729755 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 4 Feb 2019 07:02:56 -0800 Subject: [PATCH 29/78] Add 'name' key for configs --- designs/2019-config-simplification/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 1e4daac3..f1220b96 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -70,6 +70,7 @@ The following keys are new to the `eslint.config.js` format: * `files` - **Required.** Determines the glob file patterns that this configuration applies to. * `ignores` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`. * `ruledefs` - Contains definitions for rules grouped by a specific name. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option. +* `name` - Specifies the name of the config object. This is helpful for printing out debugging information and, while not required, is recommended for that reason. The following keys are specified the same as in `.eslintrc` files: From ccf3e62c70473a4d78bb715d9ce03d7542d7612f Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 4 Feb 2019 07:34:04 -0800 Subject: [PATCH 30/78] Remove 'extends' --- designs/2019-config-simplification/README.md | 238 ++++++++++--------- 1 file changed, 120 insertions(+), 118 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index f1220b96..6ef9759b 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -45,6 +45,7 @@ Design Summary: 1. Remove the concept of environments (`env`) 1. Remove `.eslintignore` and `--ignore-file` (no longer necessary) 1. Remove `--rulesdir` +1. Remove `--ext` ### The `eslint.config.js` File @@ -52,9 +53,9 @@ The `eslint.config.js` file is a JavaScript file (there is no JSON or YAML equiv ```js exports.config = { + name: "name", files: ["*.js"], ignores: ["*.test.js"], - extends: [], globals: {}, settings: {}, processor: object, @@ -67,10 +68,10 @@ exports.config = { The following keys are new to the `eslint.config.js` format: +* `name` - Specifies the name of the config object. This is helpful for printing out debugging information and, while not required, is recommended for that reason. * `files` - **Required.** Determines the glob file patterns that this configuration applies to. * `ignores` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`. * `ruledefs` - Contains definitions for rules grouped by a specific name. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option. -* `name` - Specifies the name of the config object. This is helpful for printing out debugging information and, while not required, is recommended for that reason. The following keys are specified the same as in `.eslintrc` files: @@ -81,7 +82,6 @@ The following keys are specified the same as in `.eslintrc` files: The following keys are specified differently than in `.eslintrc` files: -* `extends` - an object or array in `eslint.config.js` files (a string or string array in `.eslintrc`) * `parser` - an object in `eslint.config.js` files (a string in `.eslintrc`) * `processor` - an object in `eslint.config.js` files (a string in `.eslintrc`) @@ -89,6 +89,7 @@ Each of these keys used to require one or more strings specifying module(s) to l The following keys are invalid in `eslint.config.js`: +* `extends` - replaced by config arrays * `env` - responsibility of the user * `overrides` - responsibility of the user * `plugins` - replaced by `ruledefs` @@ -96,6 +97,102 @@ The following keys are invalid in `eslint.config.js`: Each of these keys represent different ways of augmenting how configuration is calculated and all of that responsibility now falls on the user. +#### Extending Another Config + +Extending another config is accomplished by returning an array as the value of `exports.config`. Configs that come later in the array are merged with configs that come earlier in the array. For example: + +```js +exports.config = [ + require("eslint-config-standard"), + { + files: ["*.js"], + rules: { + semi: ["error", "always"] + } + } +]; +``` + +This config extends `eslint-config-standard` because that package is included first in the array. You can add multiple configs into the array to extend from multiple configs, such as: + +```js +exports.config = [ + require("eslint-config-standard"), + require("@me/eslint-config"), + { + files: ["*.js"], + rules: { + semi: ["error", "always"] + } + } +]; +``` + +Each item in a config array can be a config array. For example, this is a valid config array and equivalent to the previous example: + +```js +exports.config = [ + [ + require("eslint-config-standard"), + require("@me/eslint-config") + ], + { + files: ["*.js"], + rules: { + semi: ["error", "always"] + } + } +]; +``` + +A config array is always flattened before being evaluated, so even though this example is a two-dimensional config array, it will be evaluated as if it were a one-dimensional config array. + +When using a config array, only one config object must have a `files` key (config arrays where no objects contain `files` will result in an error). If a config in the config array does not contain `files` or `ignores`, then that config is merged into every config with a `files` pattern. For example: + +```js +exports.config = [ + { + globals: { + Foo: true + } + }, + { + files: ["*.js"], + rules: { + semi: ["error", "always"] + } + }, + { + files: ["*.mjs"], + rules: { + semi: ["error", "never"] + } + } +]; +``` + +In this example, the first config in the array defines a global variable of `Foo`. That global variable is merged into the other two configs in the array automatically because there is no `files` or `ignores` specifying when it should be used. The first config matches zero files on its own and would be invalid if it was the only config in the config array. + +#### Extending From `eslint:recommended` and `eslint:all` + +Both `eslint:recommended` and `eslint:all` can be represented as strings in a config array. For example: + +```js +exports.config = [ + "eslint:recommended", + require("eslint-config-standard"), + require("@me/eslint-config"), + { + files: ["*.js"], + rules: { + semi: ["error", "always"] + } + } +]; +``` + +This config first extends `eslint:recommended` and then continues on to extend other configs. + #### Referencing Plugin Rules The `plugins` key in `.eslintrc` was an array of strings indicating the plugins to load, allowing you to specify processors, rules, etc., by referencing the name of the plugin. It's no longer necessary to indicate the plugins to load because that is done directly in the `eslint.config.js` file. For example, consider this `.eslintrc` file: @@ -128,13 +225,13 @@ exports.config = { Here, it is the `ruledefs` that assigns the name `react` to the rules from `eslint-plugin-react`. The reference to `react/` in a rule will always look up that value in the `ruledefs` key. -**Note:** If the config `extends` another config that already has a `ruledefs` namespace defined, then an error is thrown. In this case, if a config already has a `react` namespace, then attempting to combine with another config that has a `react` namespace will throw an error. This is to ensure the meaning of `namespace/rule` remains consistent. +**Note:** If a config is merged with another config that already has a `ruledefs` namespace defined, then an error is thrown. In this case, if a config already has a `react` namespace, then attempting to combine with another config that has a `react` namespace will throw an error. This is to ensure the meaning of `namespace/rule` remains consistent. -#### Suggested Plugin Improvements +#### Plugins Specifying Their Own Namespaces Rules imported from a plugin must be assigned a namespace using `ruledefs`, which puts the responsibility for that namespace on the config file user. Plugins can define their own namespace for rules in two ways. -First, a plugin can export a recommended configuration to place in the `extends` key. For example, a plugin called `eslint-plugin-example`, might define a config that looks like this: +First, a plugin can export a recommended configuration to place in a config array. For example, a plugin called `eslint-plugin-example`, might define a config that looks like this: ```js exports.configs = { @@ -151,14 +248,14 @@ exports.configs = { Then, inside of a user config, the plugin's recommended config can be loaded: ```js -exports.config = { - extends: [ - require("eslint-plugin-example").configs.recommended - ], - rules: { - "example/rule1": "error" +exports.config = [ + require("eslint-plugin-example").configs.recommended, + { + rules: { + "example/rule1": "error" + } } -}; +]; ``` The user config in this example now inherits the `ruledefs` from the plugin's recommended config, automatically adding in the rules with their preferred namespace. (Note that the user config can't have another `ruledefs` namespace called `example` without an error being thrown.) @@ -230,54 +327,6 @@ This effectively duplicates the use of `env: { browser: true }` in ESLint. **Note:** This would allow us to stop shipping environments in ESLint. We could just tell people to use `globals` in their config and allow them to specify which version of `globals` they want to use. -#### Extending Another Config - -Extending another config works the same as in `.eslintrc` except users pass entire config objects rather than the name of a config. That can easily be done in the following manner: - -```js -exports.config = { - files: ["*.js"], - extends: require("eslint-config-standard"), - rules: { - semi: ["error", "always"] - } -}; -``` - -This config extends `eslint-config-standard` by assigning it to `extends`. You can also use an array for `extends` to extend from multiple configs: - -```js -exports.config = { - files: ["*.js"], - extends: [ - require("eslint-config-standard"), - require("@me/eslint-config") - ], - rules: { - semi: ["error", "always"] - } -}; -``` - -#### Extending From `eslint:recommended` and `eslint:all` - -Both `eslint:recommended` and `eslint:all` can be represented as strings in the `extends` key. For example: - -```js -exports.config = { - files: ["*.js"], - extends: [ - "eslint:recommended", - require("eslint-config-standard"), - require("@me/eslint-config") - ], - rules: { - semi: ["error", "always"] - } -}; -``` - -This config first extends `eslint:recommended` and then continues on to extend other configs. #### Overriding Configuration Based on File Patterns @@ -318,61 +367,6 @@ exports.config = [ When ESLint uses this config, it will check each `files` pattern to determine which configs apply. Any config with a `files` pattern matching the file to lint will be extracted and used (if multiple configs match, then those configs are merged to determine the final config to use). In this way, returning an array acts exactly the same as the array in `overrides`. -When using a config array, only one config object must have a `files` key (config arrays where no objects contain `files` will result in an error). If a config in the config array does not contain `files` or `ignores`, then that config is merged into every config with a `files` pattern. For example: - -```js -exports.config = [ - { - globals: { - Foo: true - } - }, - { - files: "*.js", - ruledefs: { - react: require("eslint-plugin-react").rules, - }, - rules: { - "react/jsx-uses-react": "error", - semi: "error" - } - }, - { - files: "*.md", - processor: require("eslint-plugin-markdown").processors.markdown - } -]; -``` - -In this example, the first config in the array defines a global variable of `Foo`. That global variable is merged into the other two configs in the array automatically because there is no `files` or `ignores` specifying when it should be used. The first config matches zero files on its own and would be invalid if it was the only config in the config array. - -Each item in a config array can be a config array. For example, this is a valid config array: - -```js -exports.config = [ - [ - require("eslint-config-vue"), - require("eslint-config-import") - ] - { - files: "*.js", - ruledefs: { - react: require("eslint-plugin-react").rules, - }, - rules: { - "react/jsx-uses-react": "error", - semi: "error" - } - }, - { - files: "*.md", - processor: require("eslint-plugin-markdown").processors.markdown - } -]; -``` - -A config array is always flattened before being evaluated, so even though this example is a two-dimensional config array, it will be evaluated as if it were a one-dimensional config array. - #### Replacing `.eslintignore` Because there is only one `eslint.config.js` file to consider, ESLint doesn't have to first search directories to determine its location. That allows `eslint.config.js` to specify files to ignore directly instead of relying on `.eslintignore`. For backwards compatibility, users could create a config like this: @@ -611,9 +605,17 @@ The `eslintConfig` and `eslintIgnore` keys in `package.json` will not be honored That is completely up to the shareable config. For simplicity sake, I think we still want to encourage people to do so. However, there is no longer and formal contract between ESLint and shareable configs, so developers could potentially export configs from any npm package using any exported key. They would just need to inform users about how to extend their config properly. -### Is there a way to allow plugins to specify the namespace of their rules? +### Why are "eslint:recommended" and "eslint:all" strings instead of objects? + +In order to use objects for these two configs, we'd need to somehow pass those objects to the config file. That would mean either exposing something on the ESLint package itself (i.e., `require("eslint").configs.recommended`) or publishing a separate package to do the same (i.e., `require("@eslint/configs).recommended). + +In the first case, you'd end up with a situation where the config needs to specify a particular version of ESLint as its dependency, and that could mean a config could force an ESLint upgrade unnecessarily (especially when shareable configs depend on `"eslint:recommended"`). + +In the second case, we'd be stuck trying to keep the core ESLint configs in sync with another package, which is maintenance overhead. + +By using strings as placeholders, we allow the core to fill in the values for those configs without adding more restrictions onto the config files. -Maybe, but even if we could do that, the namespaces aren't guaranteed to be unique. +### Why is `ruledefs` the way to load plugin rules instead of `plugins`? The reason we could enforce naming of both plugin packages and rule namespaces is because ESLint controlled how the plugins were loaded: users passed ESLint a string and then ESLint could both inspect the string to pull out details it needed (the plugin name without the `eslint-plugin-` prefix) and then modify the rule names to be prepended with the plugin name. From 7598a19e45ac6074505b82eb24b95131669ee770 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 4 Feb 2019 07:36:51 -0800 Subject: [PATCH 31/78] Add section on shareable config names --- designs/2019-config-simplification/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 6ef9759b..c11434b9 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -193,6 +193,20 @@ exports.config = [ This config first extends `eslint:recommended` and then continues on to extend other configs. +#### Setting the Name of Shareable Configs + +For shareable configs, specifying a `name` property for each config they export helps ESLint to output more useful error messages if there is a problem. The `name` property is a string that will be used to identify configs to help users resolve problems. For example, if you are creating `eslint-config-example`, then you can specify a `name` property to reflect that: + +```js +module.exports = { + name: "eslint-config-example", + + // other info here +}; +``` + +It's recommended that the shareable config provide a unique name for each config that is exported. + #### Referencing Plugin Rules The `plugins` key in `.eslintrc` was an array of strings indicating the plugins to load, allowing you to specify processors, rules, etc., by referencing the name of the plugin. It's no longer necessary to indicate the plugins to load because that is done directly in the `eslint.config.js` file. For example, consider this `.eslintrc` file: From 78898459a1cade4e654f64eaaf0f3ee6635a7333 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 4 Feb 2019 07:54:31 -0800 Subject: [PATCH 32/78] Add more info about shareable configs --- designs/2019-config-simplification/README.md | 53 +++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index c11434b9..06da34c1 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -207,6 +207,54 @@ module.exports = { It's recommended that the shareable config provide a unique name for each config that is exported. +#### Disambiguating Shareable Configs With Common Dependencies + +Today, shareable configs that depend on plugin rules must specify the plugin as a peer dependency and then either provide a script to install those dependencies or ask the user to install them manually. + +With this design, shareable configs can specify plugins as direct dependencies that will be automatically installed with the shareable config, improving the user experience of complex shareable configs. This means its possible for multiple shareable configs to depend on the same plugin and, in theory, depend on different versions of the same plugin. In general, npm will handle this directly, installing the correct plugin version at the correct level for each shareable config to `require()`. For example, suppose there are two shareable configs, `eslint-config-a` and `eslint-config-b` that both rely on the `eslint-plugin-example` plugin, but the former relies on 1.0.0 and the latter relies on 2.0.0. npm will install those plugins like this: + +``` +your-project +├── eslint.config.js +└── node_modules + ├── eslint + ├── eslint-config-a + | └── eslint-plugin-example@1.0.0 + └── eslint-config-b + └── eslint-plugin-example@2.0.0 +``` + +The problem comes when the shareable configs try to use the default namespace of `eslint-plugin-example` for its rules, such as: + +```js +module.exports = { + ruledefs: { + ...require("eslint-plugin-example").ruledefs + } +}; +``` + +If both shareable configs do this, and the user tries to use both shareable configs, an error will be thrown because the `ruledefs` namespace "example" can only be assigned once. + +To work around this problem, shareable configs that rely on plugins should also export a separate config that assigns a unique namespace to all plugin rules. To continue with the example in this section, that might be under `eslint-config-example/compat` and would look like this: + +```js +const originalRuleDefs = require("eslint-plugin-example").ruledefs; +const compatRuleDefs = {}; + +for (const key in originalRuleDefs) { + compatRuleDefs["config-a::" + key] = originalRuleDefs; +} + +module.exports = { + ruledefs: { + ...compatRuleDefs + } +}; +``` + +Here, the shareable config is exporting the same rules from the plugins as before, but using the namespace `config-a::example` instead of the default `example` that `eslint-plugin-example` defines. In this way, shareable config authors can provide an option for users who may be extending multiple configs that depend on the same plugin. + #### Referencing Plugin Rules The `plugins` key in `.eslintrc` was an array of strings indicating the plugins to load, allowing you to specify processors, rules, etc., by referencing the name of the plugin. It's no longer necessary to indicate the plugins to load because that is done directly in the `eslint.config.js` file. For example, consider this `.eslintrc` file: @@ -341,7 +389,6 @@ This effectively duplicates the use of `env: { browser: true }` in ESLint. **Note:** This would allow us to stop shipping environments in ESLint. We could just tell people to use `globals` in their config and allow them to specify which version of `globals` they want to use. - #### Overriding Configuration Based on File Patterns Whereas `.eslintrc` had an `overrides` key that made a hierarchical structure, the `eslint.config.js` file does not have any such hierarchy. Instead, users can return an array of configs that should be used. For example, consider this `.eslintrc` config: @@ -629,6 +676,10 @@ In the second case, we'd be stuck trying to keep the core ESLint configs in sync By using strings as placeholders, we allow the core to fill in the values for those configs without adding more restrictions onto the config files. +### Should shareable configs also use `exports.config`? + +It's really up to the shareable configs. With this design, there is no required format for shareable configs, so we can no longer enforce any such conventions. For simplicity, I think that most shareable configs will use `module.exports`, but it's really up to the shareable config author. + ### Why is `ruledefs` the way to load plugin rules instead of `plugins`? The reason we could enforce naming of both plugin packages and rule namespaces is because ESLint controlled how the plugins were loaded: users passed ESLint a string and then ESLint could both inspect the string to pull out details it needed (the plugin name without the `eslint-plugin-` prefix) and then modify the rule names to be prepended with the plugin name. From f36956597bd5e955f752bd0a4ed66d7b143a68e3 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 5 Feb 2019 08:40:12 -0800 Subject: [PATCH 33/78] Clarify merging for same ruledefs namespace --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 06da34c1..80b556e6 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -287,7 +287,7 @@ exports.config = { Here, it is the `ruledefs` that assigns the name `react` to the rules from `eslint-plugin-react`. The reference to `react/` in a rule will always look up that value in the `ruledefs` key. -**Note:** If a config is merged with another config that already has a `ruledefs` namespace defined, then an error is thrown. In this case, if a config already has a `react` namespace, then attempting to combine with another config that has a `react` namespace will throw an error. This is to ensure the meaning of `namespace/rule` remains consistent. +**Note:** If a config is merged with another config that already has the same `ruledefs` namespace defined, then an error is thrown. In this case, if a config already has a `react` namespace, then attempting to combine with another config that has a `react` namespace will throw an error. This is to ensure the meaning of `namespace/rule` remains consistent. #### Plugins Specifying Their Own Namespaces From 6845a1f29dc3b0f70acfc41854633487b0a3630f Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 5 Feb 2019 08:48:24 -0800 Subject: [PATCH 34/78] Add open question about running eslint alone --- designs/2019-config-simplification/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 80b556e6..a726742d 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -647,6 +647,7 @@ While there are no alternatives that cover all of the functionality in this RFC, 1. Do we need a command line flag to opt-in to `eslint.config.js` instead of trying to do it alongside the existing configuration system? 1. Does the file pattern system actually remove the need for `--ext`? 1. How should `files` and `ignores` be merged when a shareable config has them? Should they be overwritten or merged? +1. Should `eslint` run without any directories, globs, or filenames, fall back to using the globs in the config to find files to run? ## Frequently Asked Questions From b8b91b781a27a2a24a0049ad420fa4ef64e8f2db Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 5 Feb 2019 08:52:07 -0800 Subject: [PATCH 35/78] Clarify shareable config file structure --- designs/2019-config-simplification/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index a726742d..668dea72 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -219,9 +219,11 @@ your-project └── node_modules ├── eslint ├── eslint-config-a - | └── eslint-plugin-example@1.0.0 + | └── node_modules + | └── eslint-plugin-example@1.0.0 └── eslint-config-b - └── eslint-plugin-example@2.0.0 + └── node_modules + └── eslint-plugin-example@2.0.0 ``` The problem comes when the shareable configs try to use the default namespace of `eslint-plugin-example` for its rules, such as: From 4057f7bb8a47c2ccb24ce767dafba4df02930af1 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 5 Feb 2019 09:00:16 -0800 Subject: [PATCH 36/78] Cleanup shareable configs compat section --- designs/2019-config-simplification/README.md | 33 ++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 668dea72..0f3f7c4e 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -241,13 +241,17 @@ If both shareable configs do this, and the user tries to use both shareable conf To work around this problem, shareable configs that rely on plugins should also export a separate config that assigns a unique namespace to all plugin rules. To continue with the example in this section, that might be under `eslint-config-example/compat` and would look like this: ```js -const originalRuleDefs = require("eslint-plugin-example").ruledefs; +// pull in ruldefs from the regular config +const originalRuleDefs = require("./index").ruledefs; + +// namespace each original ruledef const compatRuleDefs = {}; for (const key in originalRuleDefs) { - compatRuleDefs["config-a::" + key] = originalRuleDefs; + compatRuleDefs["config-a::" + key] = originalRuleDefs[key]; } +// include in a config module.exports = { ruledefs: { ...compatRuleDefs @@ -257,6 +261,31 @@ module.exports = { Here, the shareable config is exporting the same rules from the plugins as before, but using the namespace `config-a::example` instead of the default `example` that `eslint-plugin-example` defines. In this way, shareable config authors can provide an option for users who may be extending multiple configs that depend on the same plugin. +If a shareable config does not provide a compat version, then the end user still can create one on their own by manually creating a new config from the shareable config. For example: + +```js +// get the config you want to extend +const configToExtend = require("eslint-plugin-example"); + +// create a new copy +const compatConfig = Object.create(configToExtend); +compatConfig.ruledefs = {}; + +// namespace each original +for (const key in configToExtend.ruledefs) { + compatConfig.ruledefs["compat::" + key] = configToExtend.ruledefs[key]; +} + +// include in config +exports.config = [ + compatConfig, + { + // overrides here + } +]; +``` + + #### Referencing Plugin Rules The `plugins` key in `.eslintrc` was an array of strings indicating the plugins to load, allowing you to specify processors, rules, etc., by referencing the name of the plugin. It's no longer necessary to indicate the plugins to load because that is done directly in the `eslint.config.js` file. For example, consider this `.eslintrc` file: From b8aa166a00c8bdd383757b905e18fbf98e667d92 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 5 Feb 2019 09:24:11 -0800 Subject: [PATCH 37/78] Allow functions in config arrays --- designs/2019-config-simplification/README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 0f3f7c4e..cd13ef89 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -519,7 +519,7 @@ exports.config = { The `requireIndex()` method returns an object where the keys are the rule IDs (based on the filenames found in the directory) and the values are the rule objects. Unlike today, rules loaded from a local directory must have a namespace just like plugin rules (`custom` in this example). -### Advanced Configs +### Function Configs Some users may need information from ESLint to determine the correct configuration to use. To allow for that, `exports.config` may also be a function that returns an object, such as: @@ -573,6 +573,21 @@ exports.config = (context) => { }; ``` +#### Including Function Configs in an Array + +A function config can be used anywhere a config object or a config array is valid. That means you can insert a function config as a config array member: + +```js +exports.config = [ + (context) => someObject, + require("eslint-config-myconfig") +]; +``` + +Each function config in an array will be executed with a `context` object when ESLint evaluates the configuration file. This also means that shareable configs can export a function instead of an object or array. + +**Note:** If a function config inside of a config array happens to return an array, then those config array items are flattened as with any array-in-array situation. + ### Configuration Location Resolution When ESLint is executed, the following steps are taken to find the `eslint.config.js` file to use: From 9e46fba62fd702abaf191bc4ce131aa15899ab7a Mon Sep 17 00:00:00 2001 From: Kevin Partington Date: Wed, 6 Feb 2019 08:11:33 -0700 Subject: [PATCH 38/78] Fix typo Co-Authored-By: nzakas --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index cd13ef89..43cbcfee 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -711,7 +711,7 @@ The `eslintConfig` and `eslintIgnore` keys in `package.json` will not be honored ### Do shareable configs still export an object on `module.exports`? -That is completely up to the shareable config. For simplicity sake, I think we still want to encourage people to do so. However, there is no longer and formal contract between ESLint and shareable configs, so developers could potentially export configs from any npm package using any exported key. They would just need to inform users about how to extend their config properly. +That is completely up to the shareable config. For simplicity sake, I think we still want to encourage people to do so. However, there is no longer a formal contract between ESLint and shareable configs, so developers could potentially export configs from any npm package using any exported key. They would just need to inform users about how to extend their config properly. ### Why are "eslint:recommended" and "eslint:all" strings instead of objects? From c3af1dae0c7d38806a68e5ccd990915d0ed1e740 Mon Sep 17 00:00:00 2001 From: Kevin Partington Date: Wed, 6 Feb 2019 08:11:59 -0700 Subject: [PATCH 39/78] Fix typo Co-Authored-By: nzakas --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 43cbcfee..20b06d88 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -715,7 +715,7 @@ That is completely up to the shareable config. For simplicity sake, I think we s ### Why are "eslint:recommended" and "eslint:all" strings instead of objects? -In order to use objects for these two configs, we'd need to somehow pass those objects to the config file. That would mean either exposing something on the ESLint package itself (i.e., `require("eslint").configs.recommended`) or publishing a separate package to do the same (i.e., `require("@eslint/configs).recommended). +In order to use objects for these two configs, we'd need to somehow pass those objects to the config file. That would mean either exposing something on the ESLint package itself (i.e., `require("eslint").configs.recommended`) or publishing a separate package to do the same (i.e., `require("@eslint/configs).recommended`). In the first case, you'd end up with a situation where the config needs to specify a particular version of ESLint as its dependency, and that could mean a config could force an ESLint upgrade unnecessarily (especially when shareable configs depend on `"eslint:recommended"`). From c98f516ad39156f033e9ea70989726fda333cdc5 Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Fri, 8 Feb 2019 11:44:59 -0700 Subject: [PATCH 40/78] Add missing period Co-Authored-By: nzakas --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 20b06d88..eea53d7d 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -655,7 +655,7 @@ As with any significant change, there are some significant drawbacks: ## Backwards Compatibility Analysis -The intent of this proposal is to replace the current `.eslintrc` format, but can be implemented incrementally so as to cause as little disturbance to ESLint users as possible +The intent of this proposal is to replace the current `.eslintrc` format, but can be implemented incrementally so as to cause as little disturbance to ESLint users as possible. In the first phase, I envision this: From bb3a2b945b38251f82233999188b9c24830ef700 Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Fri, 8 Feb 2019 11:59:33 -0700 Subject: [PATCH 41/78] Fix typo Co-Authored-By: nzakas --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index eea53d7d..8cecc956 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -211,7 +211,7 @@ It's recommended that the shareable config provide a unique name for each config Today, shareable configs that depend on plugin rules must specify the plugin as a peer dependency and then either provide a script to install those dependencies or ask the user to install them manually. -With this design, shareable configs can specify plugins as direct dependencies that will be automatically installed with the shareable config, improving the user experience of complex shareable configs. This means its possible for multiple shareable configs to depend on the same plugin and, in theory, depend on different versions of the same plugin. In general, npm will handle this directly, installing the correct plugin version at the correct level for each shareable config to `require()`. For example, suppose there are two shareable configs, `eslint-config-a` and `eslint-config-b` that both rely on the `eslint-plugin-example` plugin, but the former relies on 1.0.0 and the latter relies on 2.0.0. npm will install those plugins like this: +With this design, shareable configs can specify plugins as direct dependencies that will be automatically installed with the shareable config, improving the user experience of complex shareable configs. This means it's possible for multiple shareable configs to depend on the same plugin and, in theory, depend on different versions of the same plugin. In general, npm will handle this directly, installing the correct plugin version at the correct level for each shareable config to `require()`. For example, suppose there are two shareable configs, `eslint-config-a` and `eslint-config-b` that both rely on the `eslint-plugin-example` plugin, but the former relies on 1.0.0 and the latter relies on 2.0.0. npm will install those plugins like this: ``` your-project From 81717b667d4e0d43d2bfe5258821f918d41a6119 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 6 Feb 2019 07:47:47 -0800 Subject: [PATCH 42/78] Add implementation details --- designs/2019-config-simplification/README.md | 34 ++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 8cecc956..874faf35 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -631,6 +631,40 @@ Because there are file patterns included in `eslint.config.js`, this requires a Because the config filename has changed, it makes sense to change the command line `--no-eslintrc` flag to a more generic name, `--no-config-file` and change `CLIEngine`'s `useEslintrc` option to `useConfigFile`. In the short term, to avoid a breaking change, these pairs of names can be aliased to each other. +### Implementation Details + +The implementation of this feature requires the following changes: + +1. Create a new `ConfigArray` class to manage configs. +1. Create a `--no-config-file` CLI option and alias it to `--no-eslintrc` for backwards compatibility. +1. Create a `useConfigFile` option for `CLIEngine`. Alias `useESLintRC` to this option for backwards compatibility. +1. In `CLIEngine#executeOnFiles()`: + 1. Check for existence of `eslint.config.js`, and if found, opt-in to new behavior. + 1. Create a `ConfigArray` to hold the configuration information and to determine which files to lint (in conjuction with already-existing `globUtils`) + 1. Rename the private functions `processText()` and `processFiles()` to `legacyProcessText()` and `legacyProcessFiles()`; create new versions with the new functionality named `processText()` and `processFiles()`. Use the appropriate functions based on whether or not the user has opted-in. + 1. Update `Linter#verify()` to check for objects on keys that now support objects instead of strings (like `parser`). +1. At a later point, we will be able to remove a lot of the existing configuration utilities. + +#### The `ConfigArray` Class + +The `ConfigArray` class is the primary new class for handling the configuration change defined in this proposal. + +```js +class ConfigArray extends Array { + + // create a normalized ConfigArray from an iterable + static normalize(iterable, context) {} + + // normalize the current ConfigArray + normalize(context) {} + + // get a single config for the given filename + getConfigFor(filename, configFileDir) {} +} +``` + +In this class, "normalize" means that all functions are called and replaced with their results, the array has been flattened, and configs without `files` keys have been merged into configs that do have `files` keys for easier calculation. + ## Documentation This will require extensive documentation changes and an introductory blog post. From 86071b216296e94f315b1e4071ab0b2554c30e3a Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 8 Feb 2019 10:50:02 -0800 Subject: [PATCH 43/78] Remove --use-eslintrc typo --- designs/2019-config-simplification/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 874faf35..7afce713 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -701,7 +701,6 @@ In the first phase, I envision this: 1. `--rulesdir` is ignored. 1. `--env` is ignored. 1. `--ext` is ignored. - 1. `--use-eslintrc` is ignored. 1. `eslint-env` config comments are ignored. 1. If `eslint.config.js` is not found, then fall back to the current behavior. From 6fdb989d145f4333e3185a849950965461b3d7ed Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 8 Feb 2019 10:53:08 -0800 Subject: [PATCH 44/78] Emit warnings when unnecessary options are ignored --- designs/2019-config-simplification/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 7afce713..e1e1465b 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -706,6 +706,8 @@ In the first phase, I envision this: This keeps the current behavior for the majority of users while allowing some users to test out the new functionality. Also, `-c` could not be used with `eslint.config.js` in this phase. +For every option that is provided and ignored, ESLint will emit a warning. (For example, if `.eslintignore` is found and not used then a warning will be output.) + In the second phase (and in a major release), ESLint will emit deprecation warnings whenever the original functionality is used but will still honor them so long as `eslint.config.js` is not found. In the third phase (and in another major release), `eslint.config.js` becomes the official way to configure ESLint. If no `eslint.config.js` file is found, ESLint will still search for a `.eslintrc` file, and if found, print an error message information the user that the configuration file format has changed. From 26caebc07471e92a25424a550281f351e4895be8 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 8 Feb 2019 11:00:55 -0800 Subject: [PATCH 45/78] Clarify when error is thrown for dupe ruledefs --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index e1e1465b..11eeb898 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -236,7 +236,7 @@ module.exports = { }; ``` -If both shareable configs do this, and the user tries to use both shareable configs, an error will be thrown because the `ruledefs` namespace "example" can only be assigned once. +If both shareable configs do this, and the user tries to use both shareable configs, an error will be thrown when the configs are merged because the `ruledefs` namespace "example" can only be assigned once. To work around this problem, shareable configs that rely on plugins should also export a separate config that assigns a unique namespace to all plugin rules. To continue with the example in this section, that might be under `eslint-config-example/compat` and would look like this: From df2e654ba2b820f3cc380320dd1a12aec00d58f3 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 15 Feb 2019 08:33:42 -0700 Subject: [PATCH 46/78] should -> may --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 11eeb898..67134a36 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -238,7 +238,7 @@ module.exports = { If both shareable configs do this, and the user tries to use both shareable configs, an error will be thrown when the configs are merged because the `ruledefs` namespace "example" can only be assigned once. -To work around this problem, shareable configs that rely on plugins should also export a separate config that assigns a unique namespace to all plugin rules. To continue with the example in this section, that might be under `eslint-config-example/compat` and would look like this: +To work around this problem, shareable configs that rely on plugins may also export a separate config that assigns a unique namespace to all plugin rules. To continue with the example in this section, that might be under `eslint-config-example/compat` and would look like this: ```js // pull in ruldefs from the regular config From 9e1a42ef400b891d37a0adc7f223581fdd669de2 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 15 Feb 2019 07:36:39 -0800 Subject: [PATCH 47/78] Validate config when normalized --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 67134a36..5a382ad0 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -236,7 +236,7 @@ module.exports = { }; ``` -If both shareable configs do this, and the user tries to use both shareable configs, an error will be thrown when the configs are merged because the `ruledefs` namespace "example" can only be assigned once. +If both shareable configs do this, and the user tries to use both shareable configs, an error will be thrown when the configs are normalized because the `ruledefs` namespace "example" can only be assigned once. To work around this problem, shareable configs that rely on plugins may also export a separate config that assigns a unique namespace to all plugin rules. To continue with the example in this section, that might be under `eslint-config-example/compat` and would look like this: From 3b75b91d0a3ef024c31306c1abb5fd872e00dd10 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 15 Feb 2019 07:37:17 -0800 Subject: [PATCH 48/78] Updated open questions --- designs/2019-config-simplification/README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 5a382ad0..c30f9d98 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -724,10 +724,7 @@ While there are no alternatives that cover all of the functionality in this RFC, ## Open Questions -1. Is `ruledefs` a clear enough key name? 1. Do we need a command line flag to opt-in to `eslint.config.js` instead of trying to do it alongside the existing configuration system? -1. Does the file pattern system actually remove the need for `--ext`? -1. How should `files` and `ignores` be merged when a shareable config has them? Should they be overwritten or merged? 1. Should `eslint` run without any directories, globs, or filenames, fall back to using the globs in the config to find files to run? ## Frequently Asked Questions From f62b0c5cfffe7be7bdaaf7275271e8ffc7fbab93 Mon Sep 17 00:00:00 2001 From: Kevin Partington Date: Wed, 20 Feb 2019 07:39:54 -0700 Subject: [PATCH 49/78] Fix typo Co-Authored-By: nzakas --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index c30f9d98..38d44f61 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -637,7 +637,7 @@ The implementation of this feature requires the following changes: 1. Create a new `ConfigArray` class to manage configs. 1. Create a `--no-config-file` CLI option and alias it to `--no-eslintrc` for backwards compatibility. -1. Create a `useConfigFile` option for `CLIEngine`. Alias `useESLintRC` to this option for backwards compatibility. +1. Create a `useConfigFile` option for `CLIEngine`. Alias `useEslintrc` to this option for backwards compatibility. 1. In `CLIEngine#executeOnFiles()`: 1. Check for existence of `eslint.config.js`, and if found, opt-in to new behavior. 1. Create a `ConfigArray` to hold the configuration information and to determine which files to lint (in conjuction with already-existing `globUtils`) From 5dd52f1ca4752ce60512903e94e4c1ca94544e64 Mon Sep 17 00:00:00 2001 From: Kevin Partington Date: Wed, 20 Feb 2019 07:40:15 -0700 Subject: [PATCH 50/78] Fix typo Co-Authored-By: nzakas --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 38d44f61..6aebf7e5 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -640,7 +640,7 @@ The implementation of this feature requires the following changes: 1. Create a `useConfigFile` option for `CLIEngine`. Alias `useEslintrc` to this option for backwards compatibility. 1. In `CLIEngine#executeOnFiles()`: 1. Check for existence of `eslint.config.js`, and if found, opt-in to new behavior. - 1. Create a `ConfigArray` to hold the configuration information and to determine which files to lint (in conjuction with already-existing `globUtils`) + 1. Create a `ConfigArray` to hold the configuration information and to determine which files to lint (in conjunction with already-existing `globUtils`) 1. Rename the private functions `processText()` and `processFiles()` to `legacyProcessText()` and `legacyProcessFiles()`; create new versions with the new functionality named `processText()` and `processFiles()`. Use the appropriate functions based on whether or not the user has opted-in. 1. Update `Linter#verify()` to check for objects on keys that now support objects instead of strings (like `parser`). 1. At a later point, we will be able to remove a lot of the existing configuration utilities. From ea369d753d571128ecb9afb3c459b8ce936fba24 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 22 Mar 2019 09:11:37 -0700 Subject: [PATCH 51/78] ruledefs -> defs.ruleNamespaces --- designs/2019-config-simplification/README.md | 126 +++++++++---------- 1 file changed, 59 insertions(+), 67 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 6aebf7e5..691dd009 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -61,7 +61,9 @@ exports.config = { processor: object, parser: object, parserOptions: {}, - ruledefs: {}, + defs: { + ruleNamespaces: {} + }, rules: {} }; ``` @@ -71,7 +73,8 @@ The following keys are new to the `eslint.config.js` format: * `name` - Specifies the name of the config object. This is helpful for printing out debugging information and, while not required, is recommended for that reason. * `files` - **Required.** Determines the glob file patterns that this configuration applies to. * `ignores` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`. -* `ruledefs` - Contains definitions for rules grouped by a specific name. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option. +* `defs` - Contains definitions that are used in the config. + * `ruleNamespaces` - Contains definitions for rule namespaces grouped by a specific name. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option. The following keys are specified the same as in `.eslintrc` files: @@ -230,54 +233,32 @@ The problem comes when the shareable configs try to use the default namespace of ```js module.exports = { - ruledefs: { - ...require("eslint-plugin-example").ruledefs - } -}; -``` - -If both shareable configs do this, and the user tries to use both shareable configs, an error will be thrown when the configs are normalized because the `ruledefs` namespace "example" can only be assigned once. - -To work around this problem, shareable configs that rely on plugins may also export a separate config that assigns a unique namespace to all plugin rules. To continue with the example in this section, that might be under `eslint-config-example/compat` and would look like this: - -```js -// pull in ruldefs from the regular config -const originalRuleDefs = require("./index").ruledefs; - -// namespace each original ruledef -const compatRuleDefs = {}; - -for (const key in originalRuleDefs) { - compatRuleDefs["config-a::" + key] = originalRuleDefs[key]; -} - -// include in a config -module.exports = { - ruledefs: { - ...compatRuleDefs + defs: { + ruleNamespaces: { + example: require("eslint-plugin-example").rules + } } }; ``` -Here, the shareable config is exporting the same rules from the plugins as before, but using the namespace `config-a::example` instead of the default `example` that `eslint-plugin-example` defines. In this way, shareable config authors can provide an option for users who may be extending multiple configs that depend on the same plugin. +If both shareable configs do this, and the user tries to use both shareable configs, an error will be thrown when the configs are normalized because the rule namespace `example` can only be assigned once. -If a shareable config does not provide a compat version, then the end user still can create one on their own by manually creating a new config from the shareable config. For example: +To work around this problem, the end user can create a separate namespace for the same plugin so that it doesn't conflict with an existing rule namespace from a shareable config. For example, suppose you want to use `eslint-config-first`, and that has an `example` rule namespace defined. You'd also like to use `eslint-config-second`, which also has an `example` rule namespace defined. Trying to use both shareable configs will throw an error because a rule namespace cannot be defined twice. You can still use both shareable configs by creating a new config from `eslint-config-second` that uses a different namespace. For example: ```js // get the config you want to extend -const configToExtend = require("eslint-plugin-example"); +const configToExtend = require("eslint-config-second"); -// create a new copy +// create a new copy (NOTE: probably best to do this with @eslint/config somehow) const compatConfig = Object.create(configToExtend); -compatConfig.ruledefs = {}; - -// namespace each original -for (const key in configToExtend.ruledefs) { - compatConfig.ruledefs["compat::" + key] = configToExtend.ruledefs[key]; -} +compatConfig.defs.ruleNamespaces = Object.assign({}, configToExtend.defs.ruleNamespaces); +compatConfig.defs.ruleNamespaces["compat::example"] = require("eslint-plugin-example").rules; +delete compatConfig.defs.ruleNamespaces.example; // include in config exports.config = [ + + require("eslint-config-first"); compatConfig, { // overrides here @@ -307,8 +288,10 @@ const reactPlugin = require("eslint-plugin-react"); exports.config = { files: ["*.js"], - ruledefs: { - react: reactPlugin.rules + defs: { + ruleNamespaces: { + react: reactPlugin.rules + } }, rules: { "react/jsx-uses-react": "error" @@ -316,22 +299,24 @@ exports.config = { }; ``` -Here, it is the `ruledefs` that assigns the name `react` to the rules from `eslint-plugin-react`. The reference to `react/` in a rule will always look up that value in the `ruledefs` key. +Here, it is the `defs.ruleNamespaces` that assigns the name `react` to the rules from `eslint-plugin-react`. The reference to `react/` in a rule will always look up that value in the `defs.ruleNamespaces` key. -**Note:** If a config is merged with another config that already has the same `ruledefs` namespace defined, then an error is thrown. In this case, if a config already has a `react` namespace, then attempting to combine with another config that has a `react` namespace will throw an error. This is to ensure the meaning of `namespace/rule` remains consistent. +**Note:** If a config is merged with another config that already has the same `defs.ruleNamespaces` namespace defined, then an error is thrown. In this case, if a config already has a `react` namespace, then attempting to combine with another config that has a `react` namespace will throw an error. This is to ensure the meaning of `namespace/rule` remains consistent. #### Plugins Specifying Their Own Namespaces -Rules imported from a plugin must be assigned a namespace using `ruledefs`, which puts the responsibility for that namespace on the config file user. Plugins can define their own namespace for rules in two ways. +Rules imported from a plugin must be assigned a namespace using `defs.ruleNamespaces`, which puts the responsibility for that namespace on the config file user. Plugins can define their own namespace for rules in two ways. First, a plugin can export a recommended configuration to place in a config array. For example, a plugin called `eslint-plugin-example`, might define a config that looks like this: ```js exports.configs = { recommended: { - ruledefs: { - example: { - rule1: require("./rules/rule1") + defs: { + ruleNamespaces: { + example: { + rule1: require("./rules/rule1") + } } } } @@ -351,32 +336,34 @@ exports.config = [ ]; ``` -The user config in this example now inherits the `ruledefs` from the plugin's recommended config, automatically adding in the rules with their preferred namespace. (Note that the user config can't have another `ruledefs` namespace called `example` without an error being thrown.) +The user config in this example now inherits the `defs.ruleNamespaces` from the plugin's recommended config, automatically adding in the rules with their preferred namespace. (Note that the user config can't have another `defs.ruleNamespaces` namespace called `example` without an error being thrown.) -The second way for plugins to specify their preferred namespace is to export a `ruledefs` key directly that users can include their own config. This is what it would look like in the plugin: +The second way for plugins to specify their preferred namespace is to export a `ruleNamespaces` key directly that users can include their own config. This is what it would look like in the plugin: ```js -exports.ruledefs = { +exports.ruleNamespaces = { example: { rule1: require("./rules/rule1") } }; ``` -Then, inside of a user config, the plugin's `ruledefs` can be included directly`: +Then, inside of a user config, the plugin's `defs.ruleNamespaces` can be included directly`: ```js exports.config = { - ruledefs: { - ...require("eslint-plugin-example").ruledefs - }, + defs: { + ruleNamespaces: { + ...require("eslint-plugin-example").ruleNamespaces + }, + } rules: { "example/rule1": "error" } }; ``` -This example imports the `ruledefs` from a plugin directly into the same section in the user config. +This example imports the `ruleNamespaces` from a plugin directly into the same section in the user config. #### Referencing Parsers and Processors @@ -442,9 +429,11 @@ This can be written in `eslint.config.js` as an array of two configs: exports.config = [ { files: "*.js", - ruledefs: { - react: require("eslint-plugin-react").rules, - }, + defs: { + ruleNamespaces: { + react: require("eslint-plugin-react").rules, + }, + } rules: { "react/jsx-uses-react": "error", semi: "error" @@ -502,15 +491,17 @@ When evaluating the `files` array in the config, ESLint will end up searching fo ### Replacing `--rulesdir` -In order to recreate the functionality of `--rulesdir`, a user would need to create a new entry in `ruledefs` and then specify the rules from a directory. This can be accomplished using the [`requireindex`](https://npmjs.com/package/requireindex) npm package: +In order to recreate the functionality of `--rulesdir`, a user would need to create a new entry in `defs.ruleNamespaces` and then specify the rules from a directory. This can be accomplished using the [`requireindex`](https://npmjs.com/package/requireindex) npm package: ```js const requireIndex = require("requireindex"); exports.config = { - ruledefs: { - custom: requireIndex("./custom-rules") - }, + defs: { + ruleNamespaces: { + custom: requireIndex("./custom-rules") + }, + } rules: { "custom/my-rule": "error" } @@ -554,12 +545,16 @@ A configuration function may return an object or an array of objects. An error i One of the problems with shareable configs today is when a new rule is added to the ESLint core, shareable configs using that rule are not valid for older versions of ESLint (because ESLint validates that configured rules are present). With advanced configs, a shareable config could detect if a new rule is present before deciding to include it, for example: ```js +const requireIndex = require("requireindex"); + exports.config = (context) => { const myConfig = { files: ["*.js"], - ruledefs: { - custom: ruleLoader.loadFromDirectory("./custom-rules") - }, + defs: { + ruleNamespaces: { + custom: requireIndex("./custom-rules") + }, + } rules: { "custom/my-rule": "error" } @@ -652,14 +647,11 @@ The `ConfigArray` class is the primary new class for handling the configuration ```js class ConfigArray extends Array { - // create a normalized ConfigArray from an iterable - static normalize(iterable, context) {} - // normalize the current ConfigArray normalize(context) {} // get a single config for the given filename - getConfigFor(filename, configFileDir) {} + getConfig(filename) {} } ``` From 579d466532a1e13550ffa947d6eeba802907ce2f Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 28 Mar 2019 14:30:48 -0700 Subject: [PATCH 52/78] Add back @eslint/config, explain back compat features --- designs/2019-config-simplification/README.md | 154 +++++++++++++++---- 1 file changed, 121 insertions(+), 33 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 691dd009..3856bc15 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -42,6 +42,7 @@ Design Summary: 1. All `eslint.config.js` files are treated as if they have `root: true` 1. There is no automatic merging of config files 1. The `eslint.config.js` configuration is not serializable and objects such as functions and plugins may be added directly into configuration +1. An `@eslint/config` utility is provided to aid with backwards compatibility 1. Remove the concept of environments (`env`) 1. Remove `.eslintignore` and `--ignore-file` (no longer necessary) 1. Remove `--rulesdir` @@ -52,7 +53,7 @@ Design Summary: The `eslint.config.js` file is a JavaScript file (there is no JSON or YAML equivalent) that exports a `config` object: ```js -exports.config = { +module.exports = { name: "name", files: ["*.js"], ignores: ["*.test.js"], @@ -102,10 +103,10 @@ Each of these keys represent different ways of augmenting how configuration is c #### Extending Another Config -Extending another config is accomplished by returning an array as the value of `exports.config`. Configs that come later in the array are merged with configs that come earlier in the array. For example: +Extending another config is accomplished by returning an array as the value of `module.exports`. Configs that come later in the array are merged with configs that come earlier in the array. For example: ```js -exports.config = [ +module.exports = [ require("eslint-config-standard"), { files: ["*.js"], @@ -119,7 +120,7 @@ exports.config = [ This config extends `eslint-config-standard` because that package is included first in the array. You can add multiple configs into the array to extend from multiple configs, such as: ```js -exports.config = [ +module.exports = [ require("eslint-config-standard"), require("@me/eslint-config"), { @@ -134,7 +135,7 @@ exports.config = [ Each item in a config array can be a config array. For example, this is a valid config array and equivalent to the previous example: ```js -exports.config = [ +module.exports = [ [ require("eslint-config-standard"), require("@me/eslint-config") @@ -153,7 +154,7 @@ A config array is always flattened before being evaluated, so even though this e When using a config array, only one config object must have a `files` key (config arrays where no objects contain `files` will result in an error). If a config in the config array does not contain `files` or `ignores`, then that config is merged into every config with a `files` pattern. For example: ```js -exports.config = [ +module.exports = [ { globals: { Foo: true @@ -181,7 +182,7 @@ In this example, the first config in the array defines a global variable of `Foo Both `eslint:recommended` and `eslint:all` can be represented as strings in a config array. For example: ```js -exports.config = [ +module.exports = [ "eslint:recommended", require("eslint-config-standard"), require("@me/eslint-config"), @@ -256,7 +257,7 @@ compatConfig.defs.ruleNamespaces["compat::example"] = require("eslint-plugin-exa delete compatConfig.defs.ruleNamespaces.example; // include in config -exports.config = [ +module.exports = [ require("eslint-config-first"); compatConfig, @@ -286,7 +287,7 @@ In `eslint.config.js`, the same configuration is achieved using a `ruledefs` key ```js const reactPlugin = require("eslint-plugin-react"); -exports.config = { +module.exports = { files: ["*.js"], defs: { ruleNamespaces: { @@ -301,7 +302,7 @@ exports.config = { Here, it is the `defs.ruleNamespaces` that assigns the name `react` to the rules from `eslint-plugin-react`. The reference to `react/` in a rule will always look up that value in the `defs.ruleNamespaces` key. -**Note:** If a config is merged with another config that already has the same `defs.ruleNamespaces` namespace defined, then an error is thrown. In this case, if a config already has a `react` namespace, then attempting to combine with another config that has a `react` namespace will throw an error. This is to ensure the meaning of `namespace/rule` remains consistent. +**Note:** If a config is merged with another config that already has the same `defs.ruleNamespaces` namespace defined and the namespace doesn't refer to the same rules object, then an error is thrown. In this case, if a config already has a `react` namespace, then attempting to combine with another config that has a `react` namespace that contains different rules will throw an error. This is to ensure the meaning of `namespace/rule` remains consistent. #### Plugins Specifying Their Own Namespaces @@ -310,7 +311,7 @@ Rules imported from a plugin must be assigned a namespace using `defs.ruleNamesp First, a plugin can export a recommended configuration to place in a config array. For example, a plugin called `eslint-plugin-example`, might define a config that looks like this: ```js -exports.configs = { +module.exportss = { recommended: { defs: { ruleNamespaces: { @@ -326,7 +327,7 @@ exports.configs = { Then, inside of a user config, the plugin's recommended config can be loaded: ```js -exports.config = [ +module.exports = [ require("eslint-plugin-example").configs.recommended, { rules: { @@ -351,7 +352,7 @@ exports.ruleNamespaces = { Then, inside of a user config, the plugin's `defs.ruleNamespaces` can be included directly`: ```js -exports.config = { +module.exports = { defs: { ruleNamespaces: { ...require("eslint-plugin-example").ruleNamespaces @@ -378,7 +379,7 @@ processor: "markdown/markdown" In `eslint.config.js`, you would need to pass the references directly, such as: ```js -exports.config = { +module.exports = { files: ["*.js"], parser: require("babel-eslint"), processor: require("eslint-plugin-markdown").processors.markdown @@ -394,7 +395,7 @@ Unlike with `.eslintrc` files, there is no `env` key in `eslint.config.js`. User ```js const globals = require("globals"); -exports.config = { +module.exports = { files: ["*.js"], globals: { MyGlobal: true, @@ -426,7 +427,7 @@ overrides: This can be written in `eslint.config.js` as an array of two configs: ```js -exports.config = [ +module.exports = [ { files: "*.js", defs: { @@ -450,15 +451,28 @@ When ESLint uses this config, it will check each `files` pattern to determine wh #### Replacing `.eslintignore` -Because there is only one `eslint.config.js` file to consider, ESLint doesn't have to first search directories to determine its location. That allows `eslint.config.js` to specify files to ignore directly instead of relying on `.eslintignore`. For backwards compatibility, users could create a config like this: +Because there is only one `eslint.config.js` file to consider, ESLint doesn't have to first search directories to determine its location. That allows `eslint.config.js` to specify files to ignore directly instead of relying on `.eslintignore`. + +Anytime `ignores` appears in a config object without `files`, then the `ignores` patterns acts like the current `.eslintignore` file in that the patterns are excluded from all searches before any other matching is done. For example: + +```js +module.exports = [{ + ignores: "web_modules" +}]; +``` + +Here, the directory `web_modules` will be ignored as if it were defined in an `.eslintignore` file. The `web_modules` directory will be excluded from the glob pattern used to determine which files ESLint will run against. + +For backwards compatibility, users could create a config like this: ```js const fs = require("fs"); -exports.config = { - files: ["*.js"], - ignores: fs.readFileSync(".eslintignore", "utf8").split("\n") -}; +module.exports = [ + { + ignores: fs.readFileSync(".eslintignore", "utf8").split("\n") + } +]; ``` ### Replacing `--ext` @@ -476,7 +490,7 @@ This proposal removes `--ext` by allowing the same information to be passed in a ```js const fs = require("fs"); -exports.config = { +module.exports = { files: ["*.js", "*.jsx"], }; ``` @@ -496,7 +510,7 @@ In order to recreate the functionality of `--rulesdir`, a user would need to cre ```js const requireIndex = require("requireindex"); -exports.config = { +module.exports = { defs: { ruleNamespaces: { custom: requireIndex("./custom-rules") @@ -512,10 +526,10 @@ The `requireIndex()` method returns an object where the keys are the rule IDs (b ### Function Configs -Some users may need information from ESLint to determine the correct configuration to use. To allow for that, `exports.config` may also be a function that returns an object, such as: +Some users may need information from ESLint to determine the correct configuration to use. To allow for that, `module.exports` may also be a function that returns an object, such as: ```js -exports.config = (context) => { +module.exports = (context) => { // do something @@ -531,7 +545,8 @@ exports.config = (context) => { The `context` object has the following members: -* `core` - information about the ESLint core that is using the config +* `application` - information about the ESLint core that is using the config + * `name` - the name of the application being used * `version` - the version of ESLint being used * `hasRule(ruleId)` - determine if the given rule is in the core * `cwd` - the current working directory for ESLint (might be different than `process.cwd()` but always matches `CLIEngine.options.cwd`, see https://github.com/eslint/eslint/issues/11218) @@ -547,7 +562,7 @@ One of the problems with shareable configs today is when a new rule is added to ```js const requireIndex = require("requireindex"); -exports.config = (context) => { +module.exports = (context) => { const myConfig = { files: ["*.js"], defs: { @@ -573,7 +588,7 @@ exports.config = (context) => { A function config can be used anywhere a config object or a config array is valid. That means you can insert a function config as a config array member: ```js -exports.config = [ +module.exports = [ (context) => someObject, require("eslint-config-myconfig") ]; @@ -583,6 +598,73 @@ Each function config in an array will be executed with a `context` object when E **Note:** If a function config inside of a config array happens to return an array, then those config array items are flattened as with any array-in-array situation. +### The `@eslint/config` Utility + +To allow for backwards compatibility with existing configs and plugins, an `@eslint/config` utility is provided. The package exports the following functions: + +* `importESLintRC(eslintrcName)` - allows using `.eslintrc`-style configs +* `translateESLintRC(config)` - translates an `.eslintrc`-style config object into the correct format +* `importPlugin(pluginName)` - automatically loads and merges in plugin information likes rules and processors +* `importEnvGlobals(envName)` - imports globals from an environment +* `importEnvConfig(envName)` - imports an environment with globals and things like `parserOptions` (for `es6` and `node` environments) + +#### The `importESLintRC()` function + +The `importESLintRC()` function allows users to specify an existing `.eslintrc` config location in the same format that used in the `.eslintrc` `extends` key. Users can pass in a filename, a shareable config name, or a plugin config name and have it converted automatically into the correct format. For example: + +```js +module.exports = [ + "eslint:recommended", + + // load a file + importESLintRC("./.eslintrc.yml", __dirname), + + // load eslint-config-standard + importESLintRC("standard", __dirname), + + // load eslint-plugin-vue/recommended + importESLintRC("plugin:vue/recommended", __dirname) + +]; +``` + +#### The `translateESLintRC()` function + +The `translateESLintRC()` function allows users to pass in a `.eslintrc`-style config and get back a config object that works with `eslint.config.js`. For example: + +```js +const config = { + env: { + node: true + }, + root: true +}; + +module.exports = [ + "eslint:recommended", + + translateESLintRC(config) +]; +``` + +#### The `importPlugin()` function + +The `importPlugin()` function allows users to automatically load a plugin's rules and processors without separately assigning a namespace. For example: + +```js +module.exports = [ + "eslint:recommended", + + // add in eslint-plugin-vue + importPlugin("vue"), + + // add in eslint-plugin-example + importPlugin("example") +]; +``` + +This example includes both `eslint-plugin-vue` and `eslint-plugin-example` so that all of the rules are available with the correct namespace and processors are automatically hooked up to the correct `files` pattern. + ### Configuration Location Resolution When ESLint is executed, the following steps are taken to find the `eslint.config.js` file to use: @@ -652,6 +734,12 @@ class ConfigArray extends Array { // get a single config for the given filename getConfig(filename) {} + + // get the file patterns to search for + getFilePatterns() {} + + // get the "ignore file" values + getIgnorePatterns() {} } ``` @@ -725,7 +813,7 @@ While there are no alternatives that cover all of the functionality in this RFC, No. Right now it won't be possible to implement a config with an async function because the rest of ESLint is fully synchronous. Once we look at how to make ESLint more asynchronous, we can revisit and allow configs to be created with async functions. -### Why use `exports.config` instead of `module.exports`? +### Why use `module.exports` instead of `module.exports`? Using an exported key gives us more flexibility for the future if we decide that config files should be able to output more than one thing. For example, I've been thinking of a `--config-key` option that would allow users to specify which exported key should be used as their config. Users could then export multiple different keys (`config1`, `config2`, etc.) and easily switch between configs on the command line. That option is not part of this proposal because it isn't solving an existing problem and I'd rather focus on existing problems first (this proposal is already big enough). @@ -747,7 +835,7 @@ In the second case, we'd be stuck trying to keep the core ESLint configs in sync By using strings as placeholders, we allow the core to fill in the values for those configs without adding more restrictions onto the config files. -### Should shareable configs also use `exports.config`? +### Should shareable configs also use `module.exports`? It's really up to the shareable configs. With this design, there is no required format for shareable configs, so we can no longer enforce any such conventions. For simplicity, I think that most shareable configs will use `module.exports`, but it's really up to the shareable config author. @@ -760,7 +848,7 @@ In this design, the user is responsible for loading npm packages. Because we are An earlier iteration of this design had plugins specified like this: ```js -exports.config = { +module.exports = { plugins: [ require("eslint-plugin-react"), require("eslint-plugin-mardkown") @@ -771,7 +859,7 @@ exports.config = { This didn't work because ESLint didn't know how to name the plugin rules (we weren't getting the plugin name, just the object). So the next iteration went to this: ```js -exports.config = { +module.exports = { plugins: { react: require("eslint-plugin-react"), markdown: require("eslint-plugin-mardkown") @@ -782,7 +870,7 @@ exports.config = { This iteration gave the plugin rules names, but then I realized the only things that need names in plugins with this design are rules. Users can get processors, parsers, etc., directly without ESLint being involved in picking them out of plugins. So I renamed `plugins` to `ruledefs` to make this explicit: ```js -exports.config = { +module.exports = { ruledefs: { react: require("eslint-plugin-react").rules, markdown: require("eslint-plugin-mardkown").rules From 919a43d0b43333305b8e90c070088e6269aa05a1 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 28 Nov 2019 13:33:21 -0800 Subject: [PATCH 53/78] ruleNamespaces -> plugins --- designs/2019-config-simplification/README.md | 118 +++++++------------ 1 file changed, 43 insertions(+), 75 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 3856bc15..08fc5bfc 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -63,7 +63,7 @@ module.exports = { parser: object, parserOptions: {}, defs: { - ruleNamespaces: {} + plugins: {} }, rules: {} }; @@ -75,7 +75,7 @@ The following keys are new to the `eslint.config.js` format: * `files` - **Required.** Determines the glob file patterns that this configuration applies to. * `ignores` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`. * `defs` - Contains definitions that are used in the config. - * `ruleNamespaces` - Contains definitions for rule namespaces grouped by a specific name. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option. + * `plugins` - Contains definitions for plugins. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option. The following keys are specified the same as in `.eslintrc` files: @@ -233,18 +233,20 @@ your-project The problem comes when the shareable configs try to use the default namespace of `eslint-plugin-example` for its rules, such as: ```js +const example = require("eslint-plugin-example"); + module.exports = { defs: { - ruleNamespaces: { - example: require("eslint-plugin-example").rules + plugins: { + example } } }; ``` -If both shareable configs do this, and the user tries to use both shareable configs, an error will be thrown when the configs are normalized because the rule namespace `example` can only be assigned once. +If both shareable configs do this, and the user tries to use both shareable configs, an error will be thrown when the configs are normalized because the plugin namespace `example` can only be assigned once. -To work around this problem, the end user can create a separate namespace for the same plugin so that it doesn't conflict with an existing rule namespace from a shareable config. For example, suppose you want to use `eslint-config-first`, and that has an `example` rule namespace defined. You'd also like to use `eslint-config-second`, which also has an `example` rule namespace defined. Trying to use both shareable configs will throw an error because a rule namespace cannot be defined twice. You can still use both shareable configs by creating a new config from `eslint-config-second` that uses a different namespace. For example: +To work around this problem, the end user can create a separate namespace for the same plugin so that it doesn't conflict with an existing plugin namespace from a shareable config. For example, suppose you want to use `eslint-config-first`, and that has an `example` plugin namespace defined. You'd also like to use `eslint-config-second`, which also has an `example` plugin namespace defined. Trying to use both shareable configs will throw an error because a plugin namespace cannot be defined twice. You can still use both shareable configs by creating a new config from `eslint-config-second` that uses a different namespace. For example: ```js // get the config you want to extend @@ -252,9 +254,8 @@ const configToExtend = require("eslint-config-second"); // create a new copy (NOTE: probably best to do this with @eslint/config somehow) const compatConfig = Object.create(configToExtend); -compatConfig.defs.ruleNamespaces = Object.assign({}, configToExtend.defs.ruleNamespaces); -compatConfig.defs.ruleNamespaces["compat::example"] = require("eslint-plugin-example").rules; -delete compatConfig.defs.ruleNamespaces.example; +compatConfig.defs.plugins["compat::example"] = require("eslint-plugin-example"); +delete compatConfig.defs.plugins.example; // include in config module.exports = [ @@ -282,16 +283,16 @@ rules: This file tells ESLint to load `eslint-plugin-react` and then configure a rule from that plugin. The `react/` is automatically preprended to the rule by ESLint for easy reference. -In `eslint.config.js`, the same configuration is achieved using a `ruledefs` key: +In `eslint.config.js`, the same configuration is achieved using a `plugins` key: ```js -const reactPlugin = require("eslint-plugin-react"); +const react = require("eslint-plugin-react"); module.exports = { files: ["*.js"], defs: { - ruleNamespaces: { - react: reactPlugin.rules + plugins: { + react } }, rules: { @@ -300,13 +301,13 @@ module.exports = { }; ``` -Here, it is the `defs.ruleNamespaces` that assigns the name `react` to the rules from `eslint-plugin-react`. The reference to `react/` in a rule will always look up that value in the `defs.ruleNamespaces` key. +Here, it is the `defs.plugins` that assigns the name `react` to the rules from `eslint-plugin-react`. The reference to `react/` in a rule will always look up that value in the `defs.plugins` key. -**Note:** If a config is merged with another config that already has the same `defs.ruleNamespaces` namespace defined and the namespace doesn't refer to the same rules object, then an error is thrown. In this case, if a config already has a `react` namespace, then attempting to combine with another config that has a `react` namespace that contains different rules will throw an error. This is to ensure the meaning of `namespace/rule` remains consistent. +**Note:** If a config is merged with another config that already has the same `defs.plugins` namespace defined and the namespace doesn't refer to the same rules object, then an error is thrown. In this case, if a config already has a `react` namespace, then attempting to combine with another config that has a `react` namespace that contains different rules will throw an error. This is to ensure the meaning of `namespace/rule` remains consistent. #### Plugins Specifying Their Own Namespaces -Rules imported from a plugin must be assigned a namespace using `defs.ruleNamespaces`, which puts the responsibility for that namespace on the config file user. Plugins can define their own namespace for rules in two ways. +Rules imported from a plugin must be assigned a namespace using `defs.plugins`, which puts the responsibility for that namespace on the config file user. Plugins can define their own namespace for rules in two ways. (Note that plugins will not be required to define their own namespaces.) First, a plugin can export a recommended configuration to place in a config array. For example, a plugin called `eslint-plugin-example`, might define a config that looks like this: @@ -314,9 +315,12 @@ First, a plugin can export a recommended configuration to place in a config arra module.exportss = { recommended: { defs: { - ruleNamespaces: { + plugins: { example: { - rule1: require("./rules/rule1") + rules: { + rule1: require("./rules/rule1") + + } } } } @@ -337,25 +341,27 @@ module.exports = [ ]; ``` -The user config in this example now inherits the `defs.ruleNamespaces` from the plugin's recommended config, automatically adding in the rules with their preferred namespace. (Note that the user config can't have another `defs.ruleNamespaces` namespace called `example` without an error being thrown.) +The user config in this example now inherits the `defs.plugins` from the plugin's recommended config, automatically adding in the rules with their preferred namespace. (Note that the user config can't have another `defs.plugins` namespace called `example` without an error being thrown.) -The second way for plugins to specify their preferred namespace is to export a `ruleNamespaces` key directly that users can include their own config. This is what it would look like in the plugin: +The second way for plugins to specify their preferred namespace is to export a `plugin` key directly that users can include their own config. This is what it would look like in the plugin: ```js -exports.ruleNamespaces = { +exports.plugin = { example: { - rule1: require("./rules/rule1") + rules: { + rule1: require("./rules/rule1") + } } }; ``` -Then, inside of a user config, the plugin's `defs.ruleNamespaces` can be included directly`: +Then, inside of a user config, the plugin's `defs.plugins` can be included directly`: ```js module.exports = { defs: { - ruleNamespaces: { - ...require("eslint-plugin-example").ruleNamespaces + plugins: { + ...require("eslint-plugin-example").plugin }, } rules: { @@ -364,7 +370,7 @@ module.exports = { }; ``` -This example imports the `ruleNamespaces` from a plugin directly into the same section in the user config. +This example imports the `plugin` from a plugin directly into the same section in the user config. #### Referencing Parsers and Processors @@ -431,8 +437,8 @@ module.exports = [ { files: "*.js", defs: { - ruleNamespaces: { - react: require("eslint-plugin-react").rules, + plugins: { + react: require("eslint-plugin-react"), }, } rules: { @@ -505,15 +511,17 @@ When evaluating the `files` array in the config, ESLint will end up searching fo ### Replacing `--rulesdir` -In order to recreate the functionality of `--rulesdir`, a user would need to create a new entry in `defs.ruleNamespaces` and then specify the rules from a directory. This can be accomplished using the [`requireindex`](https://npmjs.com/package/requireindex) npm package: +In order to recreate the functionality of `--rulesdir`, a user would need to create a new entry in `defs.plugins` and then specify the rules from a directory. This can be accomplished using the [`requireindex`](https://npmjs.com/package/requireindex) npm package: ```js const requireIndex = require("requireindex"); module.exports = { defs: { - ruleNamespaces: { - custom: requireIndex("./custom-rules") + plugins: { + custom: { + rules: requireIndex("./custom-rules") + } }, } rules: { @@ -566,8 +574,10 @@ module.exports = (context) => { const myConfig = { files: ["*.js"], defs: { - ruleNamespaces: { - custom: requireIndex("./custom-rules") + plugins: { + custom: { + rules: requireIndex("./custom-rules") + } }, } rules: { @@ -839,48 +849,6 @@ By using strings as placeholders, we allow the core to fill in the values for th It's really up to the shareable configs. With this design, there is no required format for shareable configs, so we can no longer enforce any such conventions. For simplicity, I think that most shareable configs will use `module.exports`, but it's really up to the shareable config author. -### Why is `ruledefs` the way to load plugin rules instead of `plugins`? - -The reason we could enforce naming of both plugin packages and rule namespaces is because ESLint controlled how the plugins were loaded: users passed ESLint a string and then ESLint could both inspect the string to pull out details it needed (the plugin name without the `eslint-plugin-` prefix) and then modify the rule names to be prepended with the plugin name. - -In this design, the user is responsible for loading npm packages. Because we are only ever passed an object, we no longer have access to the plugin name. - -An earlier iteration of this design had plugins specified like this: - -```js -module.exports = { - plugins: [ - require("eslint-plugin-react"), - require("eslint-plugin-mardkown") - ] -}; -``` - -This didn't work because ESLint didn't know how to name the plugin rules (we weren't getting the plugin name, just the object). So the next iteration went to this: - -```js -module.exports = { - plugins: { - react: require("eslint-plugin-react"), - markdown: require("eslint-plugin-mardkown") - } -}; -``` - -This iteration gave the plugin rules names, but then I realized the only things that need names in plugins with this design are rules. Users can get processors, parsers, etc., directly without ESLint being involved in picking them out of plugins. So I renamed `plugins` to `ruledefs` to make this explicit: - -```js -module.exports = { - ruledefs: { - react: require("eslint-plugin-react").rules, - markdown: require("eslint-plugin-mardkown").rules - } -}; -``` - -This is the iteration I first submitted. - - ## Related Discussions * https://github.com/eslint/rfcs/pull/7 From ba647ed8076344b1605e9e7b33b0550c0159400f Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 28 Nov 2019 13:45:14 -0800 Subject: [PATCH 54/78] Cleanup description of parsers and processors --- designs/2019-config-simplification/README.md | 36 +++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 08fc5bfc..7b7f05a5 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -59,8 +59,8 @@ module.exports = { ignores: ["*.test.js"], globals: {}, settings: {}, - processor: object, - parser: object, + processor: object || "string", + parser: object || "string", parserOptions: {}, defs: { plugins: {} @@ -72,7 +72,7 @@ module.exports = { The following keys are new to the `eslint.config.js` format: * `name` - Specifies the name of the config object. This is helpful for printing out debugging information and, while not required, is recommended for that reason. -* `files` - **Required.** Determines the glob file patterns that this configuration applies to. +* `files` - Determines the glob file patterns that this configuration applies to. * `ignores` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`. * `defs` - Contains definitions that are used in the config. * `plugins` - Contains definitions for plugins. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option. @@ -86,8 +86,8 @@ The following keys are specified the same as in `.eslintrc` files: The following keys are specified differently than in `.eslintrc` files: -* `parser` - an object in `eslint.config.js` files (a string in `.eslintrc`) -* `processor` - an object in `eslint.config.js` files (a string in `.eslintrc`) +* `parser` - an object or string in `eslint.config.js` files (a string in `.eslintrc`) +* `processor` - an object or string in `eslint.config.js` files (a string in `.eslintrc`) Each of these keys used to require one or more strings specifying module(s) to load in `.eslintrc`. In `eslint.config.js`, these are all objects, requiring users to manually specify the objects to use. @@ -96,7 +96,6 @@ The following keys are invalid in `eslint.config.js`: * `extends` - replaced by config arrays * `env` - responsibility of the user * `overrides` - responsibility of the user -* `plugins` - replaced by `ruledefs` * `root` - always considered `true` Each of these keys represent different ways of augmenting how configuration is calculated and all of that responsibility now falls on the user. @@ -382,7 +381,7 @@ parser: "babel-eslint" processor: "markdown/markdown" ``` -In `eslint.config.js`, you would need to pass the references directly, such as: +In `eslint.config.js`, there are two options. First, you can pass references directly into these keys: ```js module.exports = { @@ -392,7 +391,26 @@ module.exports = { }; ``` -In both cases, users now must pass a direct object reference. This has the benefit of using the builtin Node.js module resolution system or allowing users to specify their own. There is never a question of where the modules will be resolved from. +Second, you can use a string to specify an object to load from a plugin, such as: + +```js +module.exports = { + defs: { + plugins: { + markdown: require("eslint-plugin-markdown"), + babel: require("eslint-plugin-babel") + } + } + files: ["*.js"], + parser: "babel/eslint-parser", + processor: "markdown/markdown" +}; +``` + +In this example, `"babel/eslint-parser"` loads the parser defined in the `eslint-plugin-babel` plugin and `"markdown/markdown"` loads the processor from the `eslint-plugin-markdown` plugin. Note that the behavior for `parser` is different than with `.eslintrc` in that the string **must** represent a parser defined in a plugin. + + +The benefit to this approach of specifying parsers and processors is that it uses the builtin Node.js module resolution system or allows users to specify their own. There is never a question of where the modules will be resolved from. #### Applying an Environment @@ -614,7 +632,7 @@ To allow for backwards compatibility with existing configs and plugins, an `@esl * `importESLintRC(eslintrcName)` - allows using `.eslintrc`-style configs * `translateESLintRC(config)` - translates an `.eslintrc`-style config object into the correct format -* `importPlugin(pluginName)` - automatically loads and merges in plugin information likes rules and processors +* `importPlugin(pluginName)` - automatically loads and merges in plugin information like rules and processors * `importEnvGlobals(envName)` - imports globals from an environment * `importEnvConfig(envName)` - imports an environment with globals and things like `parserOptions` (for `es6` and `node` environments) From 34150b57f06784b3f7274bad4f1b3a3f4b8ed7a6 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 2 Mar 2020 10:53:08 -0800 Subject: [PATCH 55/78] Update designs/2019-config-simplification/README.md Co-Authored-By: Alex Zherdev --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 7b7f05a5..92738045 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -1,7 +1,7 @@ - Start Date: 2019-01-20 - RFC PR: https://github.com/eslint/rfcs/pull/9 - Authors: Nicholas C. Zakas (@nzakas) -- Contributors: Teddy Katz (@not-an-ardvaark), Toru Nagashima (@mysticatea) +- Contributors: Teddy Katz (@not-an-aardvark), Toru Nagashima (@mysticatea) # Config File Simplification From b68280ee769f1f7a3a2dedff58ef585d60ce7130 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 29 Nov 2019 10:02:54 -0800 Subject: [PATCH 56/78] defs.plugins -> plugins --- designs/2019-config-simplification/README.md | 98 ++++++++------------ 1 file changed, 39 insertions(+), 59 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 92738045..bea0a7ff 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -62,9 +62,7 @@ module.exports = { processor: object || "string", parser: object || "string", parserOptions: {}, - defs: { - plugins: {} - }, + plugins: {} rules: {} }; ``` @@ -73,9 +71,7 @@ The following keys are new to the `eslint.config.js` format: * `name` - Specifies the name of the config object. This is helpful for printing out debugging information and, while not required, is recommended for that reason. * `files` - Determines the glob file patterns that this configuration applies to. -* `ignores` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`. -* `defs` - Contains definitions that are used in the config. - * `plugins` - Contains definitions for plugins. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option. +* `ignores` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`. If there is no `files` key, then `ignores` acts the same as `ignorePatterns` in `.eslintrc` files. The following keys are specified the same as in `.eslintrc` files: @@ -88,6 +84,7 @@ The following keys are specified differently than in `.eslintrc` files: * `parser` - an object or string in `eslint.config.js` files (a string in `.eslintrc`) * `processor` - an object or string in `eslint.config.js` files (a string in `.eslintrc`) +* `plugins` - an object mapping plugin names to implementations. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option. Each of these keys used to require one or more strings specifying module(s) to load in `.eslintrc`. In `eslint.config.js`, these are all objects, requiring users to manually specify the objects to use. @@ -235,10 +232,8 @@ The problem comes when the shareable configs try to use the default namespace of const example = require("eslint-plugin-example"); module.exports = { - defs: { - plugins: { - example - } + plugins: { + example } }; ``` @@ -253,8 +248,8 @@ const configToExtend = require("eslint-config-second"); // create a new copy (NOTE: probably best to do this with @eslint/config somehow) const compatConfig = Object.create(configToExtend); -compatConfig.defs.plugins["compat::example"] = require("eslint-plugin-example"); -delete compatConfig.defs.plugins.example; +compatConfig.plugins["compat::example"] = require("eslint-plugin-example"); +delete compatConfig.plugins.example; // include in config module.exports = [ @@ -289,10 +284,8 @@ const react = require("eslint-plugin-react"); module.exports = { files: ["*.js"], - defs: { - plugins: { - react - } + plugins: { + react }, rules: { "react/jsx-uses-react": "error" @@ -300,26 +293,23 @@ module.exports = { }; ``` -Here, it is the `defs.plugins` that assigns the name `react` to the rules from `eslint-plugin-react`. The reference to `react/` in a rule will always look up that value in the `defs.plugins` key. +Here, it is the `plugins` that assigns the name `react` to the rules from `eslint-plugin-react`. The reference to `react/` in a rule will always look up that value in the `plugins` key. -**Note:** If a config is merged with another config that already has the same `defs.plugins` namespace defined and the namespace doesn't refer to the same rules object, then an error is thrown. In this case, if a config already has a `react` namespace, then attempting to combine with another config that has a `react` namespace that contains different rules will throw an error. This is to ensure the meaning of `namespace/rule` remains consistent. +**Note:** If a config is merged with another config that already has the same `plugins` namespace defined and the namespace doesn't refer to the same rules object, then an error is thrown. In this case, if a config already has a `react` namespace, then attempting to combine with another config that has a `react` namespace that contains different rules will throw an error. This is to ensure the meaning of `namespace/rule` remains consistent. #### Plugins Specifying Their Own Namespaces -Rules imported from a plugin must be assigned a namespace using `defs.plugins`, which puts the responsibility for that namespace on the config file user. Plugins can define their own namespace for rules in two ways. (Note that plugins will not be required to define their own namespaces.) +Rules imported from a plugin must be assigned a namespace using `plugins`, which puts the responsibility for that namespace on the config file user. Plugins can define their own namespace for rules in two ways. (Note that plugins will not be required to define their own namespaces.) First, a plugin can export a recommended configuration to place in a config array. For example, a plugin called `eslint-plugin-example`, might define a config that looks like this: ```js module.exportss = { recommended: { - defs: { - plugins: { - example: { - rules: { - rule1: require("./rules/rule1") - - } + plugins: { + example: { + rules: { + rule1: require("./rules/rule1") } } } @@ -340,7 +330,7 @@ module.exports = [ ]; ``` -The user config in this example now inherits the `defs.plugins` from the plugin's recommended config, automatically adding in the rules with their preferred namespace. (Note that the user config can't have another `defs.plugins` namespace called `example` without an error being thrown.) +The user config in this example now inherits the `plugins` from the plugin's recommended config, automatically adding in the rules with their preferred namespace. (Note that the user config can't have another `plugins` namespace called `example` without an error being thrown.) The second way for plugins to specify their preferred namespace is to export a `plugin` key directly that users can include their own config. This is what it would look like in the plugin: @@ -354,15 +344,13 @@ exports.plugin = { }; ``` -Then, inside of a user config, the plugin's `defs.plugins` can be included directly`: +Then, inside of a user config, the plugin's `plugin` can be included directly`: ```js module.exports = { - defs: { - plugins: { - ...require("eslint-plugin-example").plugin - }, - } + plugins: { + ...require("eslint-plugin-example").plugin + }, rules: { "example/rule1": "error" } @@ -395,12 +383,10 @@ Second, you can use a string to specify an object to load from a plugin, such as ```js module.exports = { - defs: { - plugins: { - markdown: require("eslint-plugin-markdown"), - babel: require("eslint-plugin-babel") - } - } + plugins: { + markdown: require("eslint-plugin-markdown"), + babel: require("eslint-plugin-babel") + }, files: ["*.js"], parser: "babel/eslint-parser", processor: "markdown/markdown" @@ -454,11 +440,9 @@ This can be written in `eslint.config.js` as an array of two configs: module.exports = [ { files: "*.js", - defs: { - plugins: { - react: require("eslint-plugin-react"), - }, - } + plugins: { + react: require("eslint-plugin-react"), + }, rules: { "react/jsx-uses-react": "error", semi: "error" @@ -529,19 +513,17 @@ When evaluating the `files` array in the config, ESLint will end up searching fo ### Replacing `--rulesdir` -In order to recreate the functionality of `--rulesdir`, a user would need to create a new entry in `defs.plugins` and then specify the rules from a directory. This can be accomplished using the [`requireindex`](https://npmjs.com/package/requireindex) npm package: +In order to recreate the functionality of `--rulesdir`, a user would need to create a new entry in `plugins` and then specify the rules from a directory. This can be accomplished using the [`requireindex`](https://npmjs.com/package/requireindex) npm package: ```js const requireIndex = require("requireindex"); module.exports = { - defs: { - plugins: { - custom: { - rules: requireIndex("./custom-rules") - } - }, - } + plugins: { + custom: { + rules: requireIndex("./custom-rules") + } + }, rules: { "custom/my-rule": "error" } @@ -591,13 +573,11 @@ const requireIndex = require("requireindex"); module.exports = (context) => { const myConfig = { files: ["*.js"], - defs: { - plugins: { - custom: { - rules: requireIndex("./custom-rules") - } - }, - } + plugins: { + custom: { + rules: requireIndex("./custom-rules") + } + }, rules: { "custom/my-rule": "error" } From c2867d038d067e730eec1d7fd998842934bd4f5b Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 2 Mar 2020 11:22:50 -0800 Subject: [PATCH 57/78] Update: languageOptions and environments --- designs/2019-config-simplification/README.md | 87 ++++++++++++++------ 1 file changed, 63 insertions(+), 24 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index bea0a7ff..ba75a7c7 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -50,18 +50,22 @@ Design Summary: ### The `eslint.config.js` File -The `eslint.config.js` file is a JavaScript file (there is no JSON or YAML equivalent) that exports a `config` object: +The `eslint.config.js` file is a JavaScript file (there is no JSON or YAML equivalent) that exports an object: ```js module.exports = { name: "name", files: ["*.js"], ignores: ["*.test.js"], - globals: {}, settings: {}, + languageOptions: { + ecmaVersion: 2020, + sourceType: "module", + globals: {}, + parser: object || "string", + parserOptions: {}, + } processor: object || "string", - parser: object || "string", - parserOptions: {}, plugins: {} rules: {} }; @@ -75,18 +79,21 @@ The following keys are new to the `eslint.config.js` format: The following keys are specified the same as in `.eslintrc` files: -* `globals` * `settings` * `rules` -* `parserOptions` The following keys are specified differently than in `.eslintrc` files: -* `parser` - an object or string in `eslint.config.js` files (a string in `.eslintrc`) -* `processor` - an object or string in `eslint.config.js` files (a string in `.eslintrc`) * `plugins` - an object mapping plugin names to implementations. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option. +* `processor` - an object or string in `eslint.config.js` files (a string in `.eslintrc`) +* `languageOptions` - top-level grouping for all options that affect how JavaScript is interpreted by ESLint. + * `ecmaVersion` - sets the JavaScript version ESLint should use for these files. This value is copied into `parserOptions` if not already present. + * `sourceType` - sets the source type of the JavaScript code to parse. One of `module`, `script`, or `commonjs`. + * `parser` - an object or string in `eslint.config.js` files (a string in `.eslintrc`) + * `parserOptions` - an object specifying any additional parameters to be passed to the parser. + * `globals` - any additional global variables to add. -Each of these keys used to require one or more strings specifying module(s) to load in `.eslintrc`. In `eslint.config.js`, these are all objects, requiring users to manually specify the objects to use. +Each of these keys used to require one or more strings specifying module(s) to load in `.eslintrc`. In `eslint.config.js`, these are all objects or strings (referencing an object in a plugin), requiring users to manually specify the objects to use. The following keys are invalid in `eslint.config.js`: @@ -152,8 +159,10 @@ When using a config array, only one config object must have a `files` key (confi ```js module.exports = [ { - globals: { - Foo: true + languageOptions: { + globals: { + Foo: true + } } }, { @@ -304,7 +313,7 @@ Rules imported from a plugin must be assigned a namespace using `plugins`, which First, a plugin can export a recommended configuration to place in a config array. For example, a plugin called `eslint-plugin-example`, might define a config that looks like this: ```js -module.exportss = { +module.exports = { recommended: { plugins: { example: { @@ -374,7 +383,9 @@ In `eslint.config.js`, there are two options. First, you can pass references dir ```js module.exports = { files: ["*.js"], - parser: require("babel-eslint"), + languageOptions: { + parser: require("babel-eslint"), + }, processor: require("eslint-plugin-markdown").processors.markdown }; ``` @@ -388,28 +399,60 @@ module.exports = { babel: require("eslint-plugin-babel") }, files: ["*.js"], - parser: "babel/eslint-parser", + languageOptions: { + parser: "babel/eslint-parser", + }, processor: "markdown/markdown" }; ``` In this example, `"babel/eslint-parser"` loads the parser defined in the `eslint-plugin-babel` plugin and `"markdown/markdown"` loads the processor from the `eslint-plugin-markdown` plugin. Note that the behavior for `parser` is different than with `.eslintrc` in that the string **must** represent a parser defined in a plugin. - The benefit to this approach of specifying parsers and processors is that it uses the builtin Node.js module resolution system or allows users to specify their own. There is never a question of where the modules will be resolved from. +**Note:** This example requires that `eslint-plugin-babel` publishes a `parsers` property, such as: + +```js +module.exports = { + parsers: { + "eslint-parser": require("./some-file.js") + } +} +``` + +This is a new feature of plugins introduced with this RFC. + #### Applying an Environment -Unlike with `.eslintrc` files, there is no `env` key in `eslint.config.js`. Users can mimic the behavior of `env` by assigning directly to the `globals` key: +Unlike with `.eslintrc` files, there is no `env` key in `eslint.config.js`. For different ECMA versions, ESLint will automatically add in the required globals. For example: ```js const globals = require("globals"); module.exports = { files: ["*.js"], - globals: { - MyGlobal: true, - ...globals.browser + languageOptions: { + ecmaVersion: 2020 + } +}; +``` + +Because the `languageOptions.ecmaVersion` property is now a linter-level option instead of a parser-level option, ESLint will automatically add in all of the globals for ES2020 without requiring the user to do anything else. + +Similarly, when `sourceType` is `"commonjs"`, ESLint will automatically add the `require`, `exports`, and `module` global variables (as well as set `parserOptions.ecmaFeatures.globalReturn` to `true`). In this case, ESLint will pass a `sourceType` of `"script"` as part of `parserOptions` because parsers don't support `"commonjs"` for `sourceType`. + +For other globals, ssers can mimic the behavior of `env` by assigning directly to the `globals` key: + +```js +const globals = require("globals"); + +module.exports = { + files: ["*.js"], + languageOptions: { + globals: { + MyGlobal: true, + ...globals.browser + } } }; ``` @@ -819,11 +862,7 @@ While there are no alternatives that cover all of the functionality in this RFC, ### Can a config be an async function? -No. Right now it won't be possible to implement a config with an async function because the rest of ESLint is fully synchronous. Once we look at how to make ESLint more asynchronous, we can revisit and allow configs to be created with async functions. - -### Why use `module.exports` instead of `module.exports`? - -Using an exported key gives us more flexibility for the future if we decide that config files should be able to output more than one thing. For example, I've been thinking of a `--config-key` option that would allow users to specify which exported key should be used as their config. Users could then export multiple different keys (`config1`, `config2`, etc.) and easily switch between configs on the command line. That option is not part of this proposal because it isn't solving an existing problem and I'd rather focus on existing problems first (this proposal is already big enough). +Yes. We will be able to implement async config functions once the new async ESLint API has been implemented. ### How does this affect configuration via `package.json`? From be75bbb14b1f5df015b995b16f68af65b9c72e33 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 2 Mar 2020 11:25:53 -0800 Subject: [PATCH 58/78] Update: Grepping patterns --- designs/2019-config-simplification/README.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index ba75a7c7..551f44b6 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -554,6 +554,14 @@ eslint src/ When evaluating the `files` array in the config, ESLint will end up searching for `src/**/*.js` and `src/**/*.jsx`. (More information about file resolution is included later this proposal.) +Additionally, ESLint can be run without specifying anything on the command line, relying just on what's in `eslint.config.js` to determine what to lint: + +```bash +eslint +``` + +This will go into the `eslint.config.js` and use all of the `files` glob patterns to determine which files to lint. + ### Replacing `--rulesdir` In order to recreate the functionality of `--rulesdir`, a user would need to create a new entry in `plugins` and then specify the rules from a directory. This can be accomplished using the [`requireindex`](https://npmjs.com/package/requireindex) npm package: @@ -596,10 +604,9 @@ module.exports = (context) => { The `context` object has the following members: -* `application` - information about the ESLint core that is using the config - * `name` - the name of the application being used - * `version` - the version of ESLint being used - * `hasRule(ruleId)` - determine if the given rule is in the core +* `name` - the name of the application being used +* `version` - the version of ESLint being used +* `hasRule(ruleId)` - determine if the given rule is in the core * `cwd` - the current working directory for ESLint (might be different than `process.cwd()` but always matches `CLIEngine.options.cwd`, see https://github.com/eslint/eslint/issues/11218) This information allows users to make logical decisions about how the config should be constructed. From b156e53c803fbd95ddb9e3c54f16b207b1cb09a8 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 5 Mar 2020 13:46:30 -0800 Subject: [PATCH 59/78] New: languageOptions, node env compat, @eslint/config update --- designs/2019-config-simplification/README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 551f44b6..0da1e31c 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -656,9 +656,9 @@ Each function config in an array will be executed with a `context` object when E **Note:** If a function config inside of a config array happens to return an array, then those config array items are flattened as with any array-in-array situation. -### The `@eslint/config` Utility +### The `@eslint/eslintrc` Utility -To allow for backwards compatibility with existing configs and plugins, an `@eslint/config` utility is provided. The package exports the following functions: +To allow for backwards compatibility with existing configs and plugins, an `@eslint/eslintrc` utility is provided. The package exports the following functions: * `importESLintRC(eslintrcName)` - allows using `.eslintrc`-style configs * `translateESLintRC(config)` - translates an `.eslintrc`-style config object into the correct format @@ -671,6 +671,8 @@ To allow for backwards compatibility with existing configs and plugins, an `@esl The `importESLintRC()` function allows users to specify an existing `.eslintrc` config location in the same format that used in the `.eslintrc` `extends` key. Users can pass in a filename, a shareable config name, or a plugin config name and have it converted automatically into the correct format. For example: ```js +const { importESLintRC } = require("@eslint/eslintrc"); + module.exports = [ "eslint:recommended", @@ -691,6 +693,8 @@ module.exports = [ The `translateESLintRC()` function allows users to pass in a `.eslintrc`-style config and get back a config object that works with `eslint.config.js`. For example: ```js +const { translateESLintRC } = require("@eslint/eslintrc"); + const config = { env: { node: true @@ -710,6 +714,8 @@ module.exports = [ The `importPlugin()` function allows users to automatically load a plugin's rules and processors without separately assigning a namespace. For example: ```js +const { importPlugin } = require("@eslint/eslintrc"); + module.exports = [ "eslint:recommended", From 0f3dba9115718dd3e628b7f4b34c6c924e143895 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 6 Mar 2020 10:46:59 -0800 Subject: [PATCH 60/78] Update: @eslint/eslintrc utility --- designs/2019-config-simplification/README.md | 93 +++++++++++++------- 1 file changed, 63 insertions(+), 30 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 0da1e31c..d6ddf30b 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -658,77 +658,110 @@ Each function config in an array will be executed with a `context` object when E ### The `@eslint/eslintrc` Utility -To allow for backwards compatibility with existing configs and plugins, an `@eslint/eslintrc` utility is provided. The package exports the following functions: +To allow for backwards compatibility with existing configs and plugins, an `@eslint/eslintrc` utility is provided. The package exports the following classes: -* `importESLintRC(eslintrcName)` - allows using `.eslintrc`-style configs -* `translateESLintRC(config)` - translates an `.eslintrc`-style config object into the correct format -* `importPlugin(pluginName)` - automatically loads and merges in plugin information like rules and processors -* `importEnvGlobals(envName)` - imports globals from an environment -* `importEnvConfig(envName)` - imports an environment with globals and things like `parserOptions` (for `es6` and `node` environments) +* `ESLintRCCompat` - a class to help convert `.eslintrc`-style configs into the correct format. -#### The `importESLintRC()` function -The `importESLintRC()` function allows users to specify an existing `.eslintrc` config location in the same format that used in the `.eslintrc` `extends` key. Users can pass in a filename, a shareable config name, or a plugin config name and have it converted automatically into the correct format. For example: +```js +class ESLintRCCompat { + + constructor(baseDir) {} + + config(configObjct) {} + plugins(...pluginNames) {} + extends(...sharedConfigNames) {} + env(envObject) {} + +} +``` + +#### Importing existing configs + +The `ESLintRCCompat#extends()` function allows users to specify an existing `.eslintrc` config location in the same format that used in the `.eslintrc` `extends` key. Users can pass in a filename, a shareable config name, or a plugin config name and have it converted automatically into the correct format. For example: ```js -const { importESLintRC } = require("@eslint/eslintrc"); +const { ESLintRCCompat } = require("@eslint/eslintrc"); + +const eslintrc = new ESLintRCCompat(__dirname); module.exports = [ "eslint:recommended", // load a file - importESLintRC("./.eslintrc.yml", __dirname), + eslintrc.extends("./.eslintrc.yml"), // load eslint-config-standard - importESLintRC("standard", __dirname), + eslintrc.extends("standard"), // load eslint-plugin-vue/recommended - importESLintRC("plugin:vue/recommended", __dirname) + eslintrc.extends("plugin:vue/recommended"), + + // or multiple at once + eslintrc.extends("./.eslintrc.yml", "standard", "plugin:vue/recommended") ]; ``` -#### The `translateESLintRC()` function +#### Translating config objects -The `translateESLintRC()` function allows users to pass in a `.eslintrc`-style config and get back a config object that works with `eslint.config.js`. For example: +The `ESLintRCCompat#config()` methods allows users to pass in a `.eslintrc`-style config and get back a config object that works with `eslint.config.js`. For example: ```js -const { translateESLintRC } = require("@eslint/eslintrc"); +const { ESLintRCCompat } = require("@eslint/eslintrc"); -const config = { - env: { - node: true - }, - root: true -}; +const eslintrc = new ESLintRCCompat(__dirname); module.exports = [ "eslint:recommended", - translateESLintRC(config) + eslintrc.config({ + env: { + node: true + }, + root: true + }); ]; ``` -#### The `importPlugin()` function +#### Including plugins -The `importPlugin()` function allows users to automatically load a plugin's rules and processors without separately assigning a namespace. For example: +The `ESLintRCCompat#plugins()` method allows users to automatically load a plugin's rules and processors without separately assigning a namespace. For example: ```js -const { importPlugin } = require("@eslint/eslintrc"); +const { ESLintRCCompat } = require("@eslint/eslintrc"); + +const eslintrc = new ESLintRCCompat(__dirname); module.exports = [ "eslint:recommended", - // add in eslint-plugin-vue - importPlugin("vue"), - - // add in eslint-plugin-example - importPlugin("example") + // add in eslint-plugin-vue and eslint-plugin-example + eslintrc.plugins("vue", "example") ]; ``` This example includes both `eslint-plugin-vue` and `eslint-plugin-example` so that all of the rules are available with the correct namespace and processors are automatically hooked up to the correct `files` pattern. +#### Applying environments + +The `ESLintRCCompat#env()` method allows users to specify an `env` settings as they would in an `.eslintrc`-style config and have globals automatically added. For example: + +```js +const { ESLintRCCompat } = require("@eslint/eslintrc"); + +const eslintrc = new ESLintRCCompat(__dirname); + +module.exports = [ + "eslint:recommended", + + // load node environment + eslintrc.env({ + node: true + }) +]; +``` + ### Configuration Location Resolution When ESLint is executed, the following steps are taken to find the `eslint.config.js` file to use: From 1272e570082d35731ab8f4cdde1a5746d44f4ddc Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 6 Mar 2020 10:57:51 -0800 Subject: [PATCH 61/78] Update: Misc updates --- designs/2019-config-simplification/README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index d6ddf30b..d872863e 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -42,11 +42,12 @@ Design Summary: 1. All `eslint.config.js` files are treated as if they have `root: true` 1. There is no automatic merging of config files 1. The `eslint.config.js` configuration is not serializable and objects such as functions and plugins may be added directly into configuration -1. An `@eslint/config` utility is provided to aid with backwards compatibility +1. An `@eslint/eslintrc` utility is provided to aid with backwards compatibility 1. Remove the concept of environments (`env`) 1. Remove `.eslintignore` and `--ignore-file` (no longer necessary) 1. Remove `--rulesdir` 1. Remove `--ext` +1. Replace `context.parserPath` with `context.parser` (the path to the parser is no longer valid) ### The `eslint.config.js` File @@ -827,16 +828,16 @@ The `ConfigArray` class is the primary new class for handling the configuration class ConfigArray extends Array { // normalize the current ConfigArray - normalize(context) {} + async normalize(context) {} // get a single config for the given filename getConfig(filename) {} // get the file patterns to search for - getFilePatterns() {} + get files() {} // get the "ignore file" values - getIgnorePatterns() {} + get ignores() {} } ``` @@ -870,6 +871,7 @@ The intent of this proposal is to replace the current `.eslintrc` format, but ca In the first phase, I envision this: +1. Extract all current `.eslintrc` functionality into `@eslint/eslintrc` package. Change ESLint to depend on `@eslint/eslintrc` package. 1. `eslint.config.js` can be implemented alongside `.eslintrc`. 1. If `eslint.config.js` is found, then: 1. All `.eslintrc` files are ignored. @@ -902,10 +904,16 @@ While there are no alternatives that cover all of the functionality in this RFC, ## Open Questions 1. Do we need a command line flag to opt-in to `eslint.config.js` instead of trying to do it alongside the existing configuration system? -1. Should `eslint` run without any directories, globs, or filenames, fall back to using the globs in the config to find files to run? ## Frequently Asked Questions +### Will the inability to serialize these configs affect any features? + +There are two features that non-serializable configs affect directly: + +1. **Parallel linting** - we can no longer pass the configs between the main thread and a worker thread. However, we can work around this by passing the config filename to each worker thread to load independently. This may have some overhead but given the speed gains of parallel linting they should be negligible. +1. **Caching** - we can no longer use the serialized version of the configs as part of the cache key. We can instead read the config file in as a string and use that as part of the cache key. + ### Can a config be an async function? Yes. We will be able to implement async config functions once the new async ESLint API has been implemented. @@ -932,6 +940,7 @@ By using strings as placeholders, we allow the core to fill in the values for th It's really up to the shareable configs. With this design, there is no required format for shareable configs, so we can no longer enforce any such conventions. For simplicity, I think that most shareable configs will use `module.exports`, but it's really up to the shareable config author. + ## Related Discussions * https://github.com/eslint/rfcs/pull/7 From 4787306baa4efa95e020eb9e857d3ff44a3f2aac Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 6 Mar 2020 10:59:53 -0800 Subject: [PATCH 62/78] Update: Add reportUnusedDisableDirectives option --- designs/2019-config-simplification/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index d872863e..f6717086 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -65,6 +65,9 @@ module.exports = { globals: {}, parser: object || "string", parserOptions: {}, + linterOptions: { + reportUnusedDisableDirectives: "string" + } } processor: object || "string", plugins: {} @@ -92,6 +95,8 @@ The following keys are specified differently than in `.eslintrc` files: * `sourceType` - sets the source type of the JavaScript code to parse. One of `module`, `script`, or `commonjs`. * `parser` - an object or string in `eslint.config.js` files (a string in `.eslintrc`) * `parserOptions` - an object specifying any additional parameters to be passed to the parser. + * `linterOptions` - an object for linter-specific settings + * `reportUnusedDisableDirectives` - new location for the same option name. * `globals` - any additional global variables to add. Each of these keys used to require one or more strings specifying module(s) to load in `.eslintrc`. In `eslint.config.js`, these are all objects or strings (referencing an object in a plugin), requiring users to manually specify the objects to use. From dad73e7cd96bc3e6b9828bcc3f1a0bbad79bdc13 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 6 Mar 2020 11:03:53 -0800 Subject: [PATCH 63/78] Update: Clarifications and finishing touches --- designs/2019-config-simplification/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index f6717086..be8a9265 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -43,10 +43,11 @@ Design Summary: 1. There is no automatic merging of config files 1. The `eslint.config.js` configuration is not serializable and objects such as functions and plugins may be added directly into configuration 1. An `@eslint/eslintrc` utility is provided to aid with backwards compatibility -1. Remove the concept of environments (`env`) +1. Remove the concept of environments (`env`) (including `--env`, `/*eslint-env*/`, and `env` in config files) 1. Remove `.eslintignore` and `--ignore-file` (no longer necessary) 1. Remove `--rulesdir` 1. Remove `--ext` +1. Remove `--resolve-plugins-relative-to` 1. Replace `context.parserPath` with `context.parser` (the path to the parser is no longer valid) ### The `eslint.config.js` File @@ -914,10 +915,11 @@ While there are no alternatives that cover all of the functionality in this RFC, ### Will the inability to serialize these configs affect any features? -There are two features that non-serializable configs affect directly: +There are three features that non-serializable configs affect directly: 1. **Parallel linting** - we can no longer pass the configs between the main thread and a worker thread. However, we can work around this by passing the config filename to each worker thread to load independently. This may have some overhead but given the speed gains of parallel linting they should be negligible. 1. **Caching** - we can no longer use the serialized version of the configs as part of the cache key. We can instead read the config file in as a string and use that as part of the cache key. +1. **--print-config** - printing the config to the console will be a bit more confusing because there will be non-serializable objects included. We can look into ways of addressing this issue as we receive feedback on use cases. ### Can a config be an async function? From fd145fc04aac8fee32d8bb3c7c024a72df37b1bf Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 6 Mar 2020 11:09:28 -0800 Subject: [PATCH 64/78] Update: Add in plugin parsers support --- designs/2019-config-simplification/README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index be8a9265..06aafd26 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -49,6 +49,7 @@ Design Summary: 1. Remove `--ext` 1. Remove `--resolve-plugins-relative-to` 1. Replace `context.parserPath` with `context.parser` (the path to the parser is no longer valid) +1. Allow plugins to specify a `parsers` key so parsers can be referenced the same as rules and processors in plugins. ### The `eslint.config.js` File @@ -943,11 +944,6 @@ In the second case, we'd be stuck trying to keep the core ESLint configs in sync By using strings as placeholders, we allow the core to fill in the values for those configs without adding more restrictions onto the config files. -### Should shareable configs also use `module.exports`? - -It's really up to the shareable configs. With this design, there is no required format for shareable configs, so we can no longer enforce any such conventions. For simplicity, I think that most shareable configs will use `module.exports`, but it's really up to the shareable config author. - - ## Related Discussions * https://github.com/eslint/rfcs/pull/7 From fccc89855e113b0f15501e5b994b071cf2d1cef3 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 9 Mar 2020 10:55:38 -0700 Subject: [PATCH 65/78] Update: Clarify plugin namespaces --- designs/2019-config-simplification/README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 06aafd26..72be0567 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -318,15 +318,17 @@ Here, it is the `plugins` that assigns the name `react` to the rules from `eslin Rules imported from a plugin must be assigned a namespace using `plugins`, which puts the responsibility for that namespace on the config file user. Plugins can define their own namespace for rules in two ways. (Note that plugins will not be required to define their own namespaces.) -First, a plugin can export a recommended configuration to place in a config array. For example, a plugin called `eslint-plugin-example`, might define a config that looks like this: +First, a plugin can export a recommended configuration to place in a config array. For example, a plugin called `eslint-plugin-example`, might define a config like this: ```js module.exports = { - recommended: { - plugins: { - example: { - rules: { - rule1: require("./rules/rule1") + configs: { + recommended: { + plugins: { + example: { + rules: { + rule1: require("./rules/rule1") + } } } } From f79dc88aaf9eefa1d571579360a6810550c09c07 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 9 Mar 2020 10:56:21 -0700 Subject: [PATCH 66/78] Update: Remove context#hasRule() --- designs/2019-config-simplification/README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 72be0567..7a80fccb 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -616,7 +616,6 @@ The `context` object has the following members: * `name` - the name of the application being used * `version` - the version of ESLint being used -* `hasRule(ruleId)` - determine if the given rule is in the core * `cwd` - the current working directory for ESLint (might be different than `process.cwd()` but always matches `CLIEngine.options.cwd`, see https://github.com/eslint/eslint/issues/11218) This information allows users to make logical decisions about how the config should be constructed. @@ -643,10 +642,6 @@ module.exports = (context) => { } }; - if (context.hasRule("some-new-rule")) { - myConfig.rules["some-new-rule"] = ["error"]; - } - return myConfig; }; ``` From b3692c537d844a8dec35ed04528aa452d556106b Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 10 Mar 2020 20:16:57 -0700 Subject: [PATCH 67/78] Update: Mention features that were added after the RFC was written --- designs/2019-config-simplification/README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 7a80fccb..c9bb1352 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -16,11 +16,10 @@ The ESLint configuration file format (`.eslintrc`) hasn't changed much since ESL Although `.eslintrc` has proven to be a very robust format, it is also limited and unable to easily accommodate feature requests that the community favors, such as: 1. [bundling plugins with configs](https://github.com/eslint/eslint/issues/3458) -1. [specifying ignore patterns in the config](https://github.com/eslint/eslint/issues/10891) -1. [specifying `--ext` information in the config](https://github.com/eslint/eslint/issues/11223) -1. [using `extends` in `overrides`](https://github.com/eslint/eslint/issues/8813) 1. [Customize merging of config options](https://github.com/eslint/eslint/issues/9192) - +1. [specifying ignore patterns in the config](https://github.com/eslint/eslint/issues/10891) (implemented in `.eslintrc` after this RFC was written) +1. [specifying `--ext` information in the config](https://github.com/eslint/eslint/issues/11223) (implemented in `.eslintrc` after this RFC was written) +1. [using `extends` in `overrides`](https://github.com/eslint/eslint/issues/8813) (implemented in `.eslintrc` after this RFC was written) The only reason that these requests are difficult to implement in ESLint is because of how complex the `.eslintrc` configuration format is. Any changes made to any part of `.eslintrc` processing end up affecting millions of ESLint installations, so we have ended up stuck. The complicated parts of `.eslintrc` include: From adddff2adbc2d11c9c7d3a8d77d22bafaf5cfd66 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 25 May 2020 10:37:28 -0700 Subject: [PATCH 68/78] Add AND operator for files key --- .../2018-processors-improvements/README.md | 1 + designs/2019-config-simplification/README.md | 23 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/designs/2018-processors-improvements/README.md b/designs/2018-processors-improvements/README.md index bf75c9d9..68d4fb2e 100644 --- a/designs/2018-processors-improvements/README.md +++ b/designs/2018-processors-improvements/README.md @@ -6,6 +6,7 @@ ## Summary + This proposal provides a way to explicitly define which processor(s) to use for different files inside of configuration. It also allows the chaining of multiple processors to fully process a file. ## Motivation diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index c9bb1352..503490ab 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -1,7 +1,8 @@ - Start Date: 2019-01-20 - RFC PR: https://github.com/eslint/rfcs/pull/9 - Authors: Nicholas C. Zakas (@nzakas) -- Contributors: Teddy Katz (@not-an-aardvark), Toru Nagashima (@mysticatea) +- repo: eslint/eslint +- Contributors: Teddy Katz (@not-an-aardvark), Toru Nagashima (@mysticatea), Kai Cataldo (@kaicataldo) # Config File Simplification @@ -278,7 +279,6 @@ module.exports = [ ]; ``` - #### Referencing Plugin Rules The `plugins` key in `.eslintrc` was an array of strings indicating the plugins to load, allowing you to specify processors, rules, etc., by referencing the name of the plugin. It's no longer necessary to indicate the plugins to load because that is done directly in the `eslint.config.js` file. For example, consider this `.eslintrc` file: @@ -509,6 +509,25 @@ module.exports = [ When ESLint uses this config, it will check each `files` pattern to determine which configs apply. Any config with a `files` pattern matching the file to lint will be extracted and used (if multiple configs match, then those configs are merged to determine the final config to use). In this way, returning an array acts exactly the same as the array in `overrides`. +#### Using AND patterns for files + +If any entry in the `files` key is an array, then all of the patterns must match in order for a filename to be considered a match. For example: + +```js +module.exports = [ + { + files: [ ["*.test.*", "*.js"] ], + rules: { + semi: ["error", "always"] + } + } +]; +``` + +Here, the `files` key specifies two glob patterns. The filename `foo.test.js` would match because it matches both patterns whereas the filename `foo.js` would not match because it only matches one of the glob patterns. + +**Note:** This feature is primarily intended for backwards compatibility with eslintrc's ability to specify `extends` in an `overrides` block. + #### Replacing `.eslintignore` Because there is only one `eslint.config.js` file to consider, ESLint doesn't have to first search directories to determine its location. That allows `eslint.config.js` to specify files to ignore directly instead of relying on `.eslintignore`. From c6602968c2d0319eadce169596cf7b7a4a564dbd Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 25 May 2020 10:41:22 -0700 Subject: [PATCH 69/78] Some cleanup --- designs/2019-config-simplification/README.md | 24 -------------------- 1 file changed, 24 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 503490ab..e0df2242 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -640,30 +640,6 @@ This information allows users to make logical decisions about how the config sho A configuration function may return an object or an array of objects. An error is thrown if any other type of value is returned. -#### Checking for Rule Existence - -One of the problems with shareable configs today is when a new rule is added to the ESLint core, shareable configs using that rule are not valid for older versions of ESLint (because ESLint validates that configured rules are present). With advanced configs, a shareable config could detect if a new rule is present before deciding to include it, for example: - -```js -const requireIndex = require("requireindex"); - -module.exports = (context) => { - const myConfig = { - files: ["*.js"], - plugins: { - custom: { - rules: requireIndex("./custom-rules") - } - }, - rules: { - "custom/my-rule": "error" - } - }; - - return myConfig; -}; -``` - #### Including Function Configs in an Array A function config can be used anywhere a config object or a config array is valid. That means you can insert a function config as a config array member: From ecee4ccf45667ee4933ffd2e1926a4db7c785419 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 25 May 2020 10:51:38 -0700 Subject: [PATCH 70/78] Move linterOptions to top level of config --- designs/2019-config-simplification/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index e0df2242..f79934ba 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -67,10 +67,10 @@ module.exports = { globals: {}, parser: object || "string", parserOptions: {}, - linterOptions: { - reportUnusedDisableDirectives: "string" - } } + linterOptions: { + reportUnusedDisableDirectives: "string" + }, processor: object || "string", plugins: {} rules: {} @@ -97,9 +97,9 @@ The following keys are specified differently than in `.eslintrc` files: * `sourceType` - sets the source type of the JavaScript code to parse. One of `module`, `script`, or `commonjs`. * `parser` - an object or string in `eslint.config.js` files (a string in `.eslintrc`) * `parserOptions` - an object specifying any additional parameters to be passed to the parser. - * `linterOptions` - an object for linter-specific settings - * `reportUnusedDisableDirectives` - new location for the same option name. * `globals` - any additional global variables to add. +* `linterOptions` - an object for linter-specific settings + * `reportUnusedDisableDirectives` - new location for the same option name. Each of these keys used to require one or more strings specifying module(s) to load in `.eslintrc`. In `eslint.config.js`, these are all objects or strings (referencing an object in a plugin), requiring users to manually specify the objects to use. From 541c6f1dddb1abf1f5aad53ddefc2de17307c384 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 28 May 2020 11:10:51 -0700 Subject: [PATCH 71/78] Update ignore patterns details --- designs/2019-config-simplification/README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index f79934ba..18d2c8db 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -2,7 +2,7 @@ - RFC PR: https://github.com/eslint/rfcs/pull/9 - Authors: Nicholas C. Zakas (@nzakas) - repo: eslint/eslint -- Contributors: Teddy Katz (@not-an-aardvark), Toru Nagashima (@mysticatea), Kai Cataldo (@kaicataldo) +- Contributors: Teddy Katz (@not-an-aardvark), Toru Nagashima (@mysticatea), Kai Cataldo (@kaicataldo), Shahar Dawn (@mightyiam) # Config File Simplification @@ -530,7 +530,13 @@ Here, the `files` key specifies two glob patterns. The filename `foo.test.js` wo #### Replacing `.eslintignore` -Because there is only one `eslint.config.js` file to consider, ESLint doesn't have to first search directories to determine its location. That allows `eslint.config.js` to specify files to ignore directly instead of relying on `.eslintignore`. +Because there is only one `eslint.config.js` file to consider, ESLint doesn't have to first search directories to determine its location. That allows `eslint.config.js` to specify files to ignore directly instead of relying on `.eslintignore`. + +With `eslint.config.js`, there are three ways that files and directories can be ignored: + +1. **Defaults** - by default, ESLint will ignore `node_modules` and `.git` directories only. This is different from the current behavior where ESLint ignores `node_modules` and all files/directories beginning with a dot (`.`). +2. **.gitignore** - any files or directories specified in `.gitignore` will also be ignored. This feature has been requested before ([eslint/eslint#12199](https://github.com/eslint/eslint/issues/12199), [eslint/eslint#9329](https://github.com/eslint/eslint/issues/9329), [eslint/eslint#5848](https://github.com/eslint/eslint/issues/5848)), and would be a nice way to make up for the loss of `.eslintignore`. +3. **eslint.config.js** - patterns specified in `ignores` keys when `files` is not specified. (See details below.) Anytime `ignores` appears in a config object without `files`, then the `ignores` patterns acts like the current `.eslintignore` file in that the patterns are excluded from all searches before any other matching is done. For example: @@ -554,6 +560,10 @@ module.exports = [ ]; ``` +The `--no-ignore` flag will disable both `.gitignore` and `eslint.config.js` ignore patterns while leaving the default ignore patterns in place. + +The `--ignore-path`, when specified, will prevent `.gitignore` from being read in favor of the path specified. + ### Replacing `--ext` The `--ext` flag is currently used to pass in one or more file extensions that ESLint should search for when a directory without a glob pattern is passed on the command line, such as: @@ -948,3 +958,4 @@ By using strings as placeholders, we allow the core to fill in the values for th * https://github.com/eslint/eslint/issues/10643 * https://github.com/eslint/eslint/issues/10891 * https://github.com/eslint/eslint/issues/11223 +* https://github.com/eslint/rfcs/pull/55 From c8ca71f535e55bf560d13334c8e7111c9af3dee3 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 28 May 2020 11:13:47 -0700 Subject: [PATCH 72/78] Specify option to Linter#verify() for disabling environments --- designs/2019-config-simplification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 18d2c8db..eccb3d1a 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -825,7 +825,7 @@ The implementation of this feature requires the following changes: 1. Check for existence of `eslint.config.js`, and if found, opt-in to new behavior. 1. Create a `ConfigArray` to hold the configuration information and to determine which files to lint (in conjunction with already-existing `globUtils`) 1. Rename the private functions `processText()` and `processFiles()` to `legacyProcessText()` and `legacyProcessFiles()`; create new versions with the new functionality named `processText()` and `processFiles()`. Use the appropriate functions based on whether or not the user has opted-in. - 1. Update `Linter#verify()` to check for objects on keys that now support objects instead of strings (like `parser`). + 1. Update `Linter#verify()` to check for objects on keys that now support objects instead of strings (like `parser`) and add a `disableEnv` property to the options to indicate that environments should not be honored. 1. At a later point, we will be able to remove a lot of the existing configuration utilities. #### The `ConfigArray` Class From 449f405ab983f307b3e93095ec7ebd80c00e4a05 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 28 May 2020 11:16:29 -0700 Subject: [PATCH 73/78] Rename ConfigArray -> ESLintConfigArray --- designs/2019-config-simplification/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index eccb3d1a..213d96d8 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -818,22 +818,22 @@ Because the config filename has changed, it makes sense to change the command li The implementation of this feature requires the following changes: -1. Create a new `ConfigArray` class to manage configs. +1. Create a new `ESLintConfigArray` class to manage configs. 1. Create a `--no-config-file` CLI option and alias it to `--no-eslintrc` for backwards compatibility. 1. Create a `useConfigFile` option for `CLIEngine`. Alias `useEslintrc` to this option for backwards compatibility. 1. In `CLIEngine#executeOnFiles()`: 1. Check for existence of `eslint.config.js`, and if found, opt-in to new behavior. - 1. Create a `ConfigArray` to hold the configuration information and to determine which files to lint (in conjunction with already-existing `globUtils`) + 1. Create a `ESLintConfigArray` to hold the configuration information and to determine which files to lint (in conjunction with already-existing `globUtils`) 1. Rename the private functions `processText()` and `processFiles()` to `legacyProcessText()` and `legacyProcessFiles()`; create new versions with the new functionality named `processText()` and `processFiles()`. Use the appropriate functions based on whether or not the user has opted-in. 1. Update `Linter#verify()` to check for objects on keys that now support objects instead of strings (like `parser`) and add a `disableEnv` property to the options to indicate that environments should not be honored. 1. At a later point, we will be able to remove a lot of the existing configuration utilities. -#### The `ConfigArray` Class +#### The `ESLintConfigArray` Class -The `ConfigArray` class is the primary new class for handling the configuration change defined in this proposal. +The `ESLintConfigArray` class is the primary new class for handling the configuration change defined in this proposal. ```js -class ConfigArray extends Array { +class ESLintConfigArray extends Array { // normalize the current ConfigArray async normalize(context) {} From 69caf04c6f3b0422dd8df3472292ad557dd04da1 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 1 Jun 2020 09:13:07 -0700 Subject: [PATCH 74/78] Remove default .gitignore --- designs/2019-config-simplification/README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 213d96d8..15ad9b02 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -535,7 +535,6 @@ Because there is only one `eslint.config.js` file to consider, ESLint doesn't ha With `eslint.config.js`, there are three ways that files and directories can be ignored: 1. **Defaults** - by default, ESLint will ignore `node_modules` and `.git` directories only. This is different from the current behavior where ESLint ignores `node_modules` and all files/directories beginning with a dot (`.`). -2. **.gitignore** - any files or directories specified in `.gitignore` will also be ignored. This feature has been requested before ([eslint/eslint#12199](https://github.com/eslint/eslint/issues/12199), [eslint/eslint#9329](https://github.com/eslint/eslint/issues/9329), [eslint/eslint#5848](https://github.com/eslint/eslint/issues/5848)), and would be a nice way to make up for the loss of `.eslintignore`. 3. **eslint.config.js** - patterns specified in `ignores` keys when `files` is not specified. (See details below.) Anytime `ignores` appears in a config object without `files`, then the `ignores` patterns acts like the current `.eslintignore` file in that the patterns are excluded from all searches before any other matching is done. For example: @@ -560,9 +559,7 @@ module.exports = [ ]; ``` -The `--no-ignore` flag will disable both `.gitignore` and `eslint.config.js` ignore patterns while leaving the default ignore patterns in place. - -The `--ignore-path`, when specified, will prevent `.gitignore` from being read in favor of the path specified. +The `--no-ignore` flag will disable `eslint.config.js` ignore patterns while leaving the default ignore patterns in place. ### Replacing `--ext` From 4c8eac546bf22dae6af828bd6500b8472d25cf6b Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 8 Jun 2020 10:09:28 -0700 Subject: [PATCH 75/78] Clarify behavior of files/ignores --- designs/2019-config-simplification/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 15ad9b02..dc0c92de 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -80,8 +80,8 @@ module.exports = { The following keys are new to the `eslint.config.js` format: * `name` - Specifies the name of the config object. This is helpful for printing out debugging information and, while not required, is recommended for that reason. -* `files` - Determines the glob file patterns that this configuration applies to. -* `ignores` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`. If there is no `files` key, then `ignores` acts the same as `ignorePatterns` in `.eslintrc` files. +* `files` - Determines the glob file patterns that this configuration applies to. These patterns can be negated by prefixing them with `!`, which effectively mimics the behavior of `excludedFiles` in `.eslintrc`. +* `ignores` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`. If there is no `files` key, then `ignores` acts the same as `ignorePatterns` in `.eslintrc` files; if there is a `files` key then `ignores` acts like `excludedFiles` in `.eslintrc`. The following keys are specified the same as in `.eslintrc` files: From e68e6a016c52df91fd601a8692774704bc7ca870 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 8 Jun 2020 10:12:06 -0700 Subject: [PATCH 76/78] Add back in .eslintignore --- designs/2019-config-simplification/README.md | 28 ++++---------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index dc0c92de..3dc56e2e 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -44,7 +44,6 @@ Design Summary: 1. The `eslint.config.js` configuration is not serializable and objects such as functions and plugins may be added directly into configuration 1. An `@eslint/eslintrc` utility is provided to aid with backwards compatibility 1. Remove the concept of environments (`env`) (including `--env`, `/*eslint-env*/`, and `env` in config files) -1. Remove `.eslintignore` and `--ignore-file` (no longer necessary) 1. Remove `--rulesdir` 1. Remove `--ext` 1. Remove `--resolve-plugins-relative-to` @@ -81,7 +80,7 @@ The following keys are new to the `eslint.config.js` format: * `name` - Specifies the name of the config object. This is helpful for printing out debugging information and, while not required, is recommended for that reason. * `files` - Determines the glob file patterns that this configuration applies to. These patterns can be negated by prefixing them with `!`, which effectively mimics the behavior of `excludedFiles` in `.eslintrc`. -* `ignores` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`. If there is no `files` key, then `ignores` acts the same as `ignorePatterns` in `.eslintrc` files; if there is a `files` key then `ignores` acts like `excludedFiles` in `.eslintrc`. +* `ignores` - Determines the files that should not be linted using ESLint. The files specified by this array of glob patterns are subtracted from the files specified in `files`. If there is no `files` key, then `ignores` acts the same as `ignorePatterns` in `.eslintrc` files; if there is a `files` key then `ignores` acts like `excludedFiles` in `.eslintrc`. The following keys are specified the same as in `.eslintrc` files: @@ -528,16 +527,15 @@ Here, the `files` key specifies two glob patterns. The filename `foo.test.js` wo **Note:** This feature is primarily intended for backwards compatibility with eslintrc's ability to specify `extends` in an `overrides` block. -#### Replacing `.eslintignore` - -Because there is only one `eslint.config.js` file to consider, ESLint doesn't have to first search directories to determine its location. That allows `eslint.config.js` to specify files to ignore directly instead of relying on `.eslintignore`. +#### Ignoring files With `eslint.config.js`, there are three ways that files and directories can be ignored: 1. **Defaults** - by default, ESLint will ignore `node_modules` and `.git` directories only. This is different from the current behavior where ESLint ignores `node_modules` and all files/directories beginning with a dot (`.`). +2. **.eslintignore** - the regular ESLint ignore file. 3. **eslint.config.js** - patterns specified in `ignores` keys when `files` is not specified. (See details below.) -Anytime `ignores` appears in a config object without `files`, then the `ignores` patterns acts like the current `.eslintignore` file in that the patterns are excluded from all searches before any other matching is done. For example: +Anytime `ignores` appears in a config object without `files`, then the `ignores` patterns acts like the `ignorePatterns` key in `.eslintrc` in that the patterns are excluded from all searches before any other matching is done. For example: ```js module.exports = [{ @@ -547,19 +545,7 @@ module.exports = [{ Here, the directory `web_modules` will be ignored as if it were defined in an `.eslintignore` file. The `web_modules` directory will be excluded from the glob pattern used to determine which files ESLint will run against. -For backwards compatibility, users could create a config like this: - -```js -const fs = require("fs"); - -module.exports = [ - { - ignores: fs.readFileSync(".eslintignore", "utf8").split("\n") - } -]; -``` - -The `--no-ignore` flag will disable `eslint.config.js` ignore patterns while leaving the default ignore patterns in place. +The `--no-ignore` flag will disable `eslint.config.js` and `.eslintignore` ignore patterns while leaving the default ignore patterns in place. ### Replacing `--ext` @@ -880,8 +866,6 @@ In the first phase, I envision this: 1. `eslint.config.js` can be implemented alongside `.eslintrc`. 1. If `eslint.config.js` is found, then: 1. All `.eslintrc` files are ignored. - 1. `.eslintignore` is ignored. - 1. `--ignore-file` is ignored. 1. `--rulesdir` is ignored. 1. `--env` is ignored. 1. `--ext` is ignored. @@ -890,8 +874,6 @@ In the first phase, I envision this: This keeps the current behavior for the majority of users while allowing some users to test out the new functionality. Also, `-c` could not be used with `eslint.config.js` in this phase. -For every option that is provided and ignored, ESLint will emit a warning. (For example, if `.eslintignore` is found and not used then a warning will be output.) - In the second phase (and in a major release), ESLint will emit deprecation warnings whenever the original functionality is used but will still honor them so long as `eslint.config.js` is not found. In the third phase (and in another major release), `eslint.config.js` becomes the official way to configure ESLint. If no `eslint.config.js` file is found, ESLint will still search for a `.eslintrc` file, and if found, print an error message information the user that the configuration file format has changed. From 75535da0cbb46c7ec563e0614c9b9ef1c4fb083a Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 19 Jun 2020 09:44:20 -0700 Subject: [PATCH 77/78] Update development plan --- designs/2019-config-simplification/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index 3dc56e2e..d8c0a329 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -871,10 +871,11 @@ In the first phase, I envision this: 1. `--ext` is ignored. 1. `eslint-env` config comments are ignored. 1. If `eslint.config.js` is not found, then fall back to the current behavior. +1. Switch ESLint itself to use `eslint.config.js` as a way to test and ensure compatibility with existing shareable configs in `.eslintrc` format. This keeps the current behavior for the majority of users while allowing some users to test out the new functionality. Also, `-c` could not be used with `eslint.config.js` in this phase. -In the second phase (and in a major release), ESLint will emit deprecation warnings whenever the original functionality is used but will still honor them so long as `eslint.config.js` is not found. +In the second phase (and in a major release), ESLint will emit deprecation warnings whenever the original functionality is used but will still honor them so long as `eslint.config.js` is not found. In this phase, we will work with several high-profile plugins and shareable configs to convert their packages into the new format. We will use this to find the remaining compatibility issues. In the third phase (and in another major release), `eslint.config.js` becomes the official way to configure ESLint. If no `eslint.config.js` file is found, ESLint will still search for a `.eslintrc` file, and if found, print an error message information the user that the configuration file format has changed. From 3d1f1d8dcd675a85dfc0542be9dac8c384a1369d Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 2 Jul 2020 13:41:01 -0700 Subject: [PATCH 78/78] Update designs/2019-config-simplification/README.md Co-authored-by: Brandon Mills --- designs/2019-config-simplification/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/designs/2019-config-simplification/README.md b/designs/2019-config-simplification/README.md index d8c0a329..a6bd18a0 100644 --- a/designs/2019-config-simplification/README.md +++ b/designs/2019-config-simplification/README.md @@ -760,10 +760,10 @@ When ESLint is executed, the following steps are taken to find the `eslint.confi 1. If the `-c` flag is used then the specified configuration file is used. There is no further search performed. 1. Otherwise: - a. Look for `eslint.config.js` in the current working directory. If found, stop searching and use that file. - b. If not found, search up the directory hierarchy looking for `eslint.config.js`. - c. If a `eslint.config.js` file is found at any point, stop searching and use that file. - d. If `/` is reached without finding `eslint.config.js`, then stop searching and output a "no configuration found" error. + 1. Look for `eslint.config.js` in the current working directory. If found, stop searching and use that file. + 1. If not found, search up the directory hierarchy looking for `eslint.config.js`. + 1. If a `eslint.config.js` file is found at any point, stop searching and use that file. + 1. If `/` is reached without finding `eslint.config.js`, then stop searching and output a "no configuration found" error. This approach will allow running ESLint from within a subdirectory of a project and get the same result as when ESLint is run from the project's root directory (the one where `eslint.config.js` is found).