From 6d6cc2ff75ae3853ffe3e56fc51a79ee08023092 Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:41:39 -0500 Subject: [PATCH 01/12] feat: support additional metadata for rule deprecations --- .../2024-deprecated-rule-metadata/README.md | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 designs/2024-deprecated-rule-metadata/README.md diff --git a/designs/2024-deprecated-rule-metadata/README.md b/designs/2024-deprecated-rule-metadata/README.md new file mode 100644 index 00000000..c0fb7e73 --- /dev/null +++ b/designs/2024-deprecated-rule-metadata/README.md @@ -0,0 +1,246 @@ +- Repo: eslint/eslint +- Start Date: 2024-02-20 +- RFC PR: +- Authors: [bmish](https://github.com/bmish) + +# Support additional metadata for rule deprecations + +## Summary + + + +This RFC suggests a format for storing additional information in rule metadata about rule deprecations and replacement rules, allowing automated documentation and website tooling to generate more informative deprecation notices. + +## Motivation + + + +There are long-time rule [properties](https://eslint.org/docs/latest/extend/custom-rules#rule-structure) `meta.deprecated` and `meta.replacedBy` that have been intended to document when rules are deprecated and what their replacement rule(s) are. For the most part, usage would look something like this: + +```js +module.exports = { meta: { deprecated: true, replacedBy: ['replacement-rule-name'] } }; +``` + +These properties are often used for generating plugin/rule documentation websites and in documentation tooling like [eslint-doc-generator](https://github.com/bmish/eslint-doc-generator). + +But there are some limitations to this current format: + +- Simply providing the replacement rule name as a string doesn't yield much context/explanation of the replacement/deprecation. That means documentation tooling / websites and code editors can only generate limited information to present about the situation. +- Some rules provide the replacement rule name with the plugin prefix as in `prefix/rule-name` while others just provide it as `rule-name`, which can result in ambiguity about whether the replacement rule is in the same plugin, a different third-party plugin, or ESLint core. And for third-party plugins, there's no easy way to automatically determine where their documentation is located or how to link to them. + +## Detailed Design + + + +We propose extended `meta.deprecated` and `meta.replacedBy` rule property schemas to reduce ambiguity and allow additional key details to be represented, described below as a TypeScript type for clarity: + +```ts +type RuleMeta = { + deprecated: + | boolean // Existing boolean option, backwards compatible. + | string // Shorthand property for general deprecation message, such as why the deprecation occurred. Any truthy value implies deprecated. + | { + message?: string; // General deprecation message, such as why the deprecation occurred. + url?: string; // URL to more information about this deprecation in general. + }; + replacedBy: + | readonly string[] // Existing shorthand property for rule names, backwards compatible. It's recommended to omit the plugin prefix from rule names. + | readonly { + /** + * Plugin containing the replacement. + * Use "eslint" if the replacement is an ESLint core rule. + * Omit if the replacement is in the same plugin. + */ + plugin?: + | string // Shorthand property for the plugin name i.e. "eslint-plugin-example" that contains the replacement rule. + | { + name?: string; // Plugin name i.e. "eslint-plugin-example" that contains the replacement rule. + url?: string; // URL to plugin documentation. + }; + rule: + | string // Shorthand property for rule name (without plugin prefix). + | { + name?: string; // Replacement rule name (without plugin prefix). + url?: string; // URL to rule documentation. + }; + meta?: { + message?: string; // Message about this specific replacement, such as how to use/configure the replacement rule to achieve the same results as the rule being replaced. + url?: string; // URL to more information about this specific deprecation/replacement. + }; + }[] + | undefined; +}; +``` + +Real-world example of how this could be used based on the situation in : + +```js +// lib/rules/semi.js +module.exports = { + meta: { + deprecated: { + message: 'Stylistic rules are being moved out of ESLint core.', + url: 'https://eslint.org/blog/2023/10/deprecating-formatting-rules/', + }, + replacedBy: [ + { + plugin: { + name: '@stylistic/js', + url: 'https://eslint.style/', + }, + rule: { + name: 'semi', + url: 'https://eslint.style/rules/js/semi', + }, + }, + ], + }, +}; +``` + +This data could be used by documentation websites and tooling like [eslint-doc-generator](https://github.com/bmish/eslint-doc-generator) to generate notices and links like: + +> semi (deprecated) \ +> Replaced by [semi](https://eslint.style/rules/js/semi) from [@stylistic/js](https://eslint.style/). \ +> Stylistic rules are being moved out of ESLint core. [Read more](https://eslint.org/blog/2023/10/deprecating-formatting-rules/). + +We can also support the same `meta.deprecated` and `meta.replacedBy` properties on configurations and processors (the other kinds of objects exported by ESLint plugins), replacing `rule` with `config` or `processor` as needed. This would be part of the effort to standardize documentation properties in . + +In terms of actual changes inside ESLint needed for this: + +- Mention the new schema in the custom rule [documentation](https://eslint.org/docs/latest/extend/custom-rules#rule-structure) +- Ensure these properties are allowed on configurations and processors +- Add any additional information to these properties in core rules as desired (such as in , ) +- Update ESLint's website generator to take into account the additional information for rule doc deprecation notices + +External changes: + +- Update the [types](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/b77d83e019025017b06953907cb77f35e4231714/types/eslint/index.d.ts#L734) in @types/eslint +- Update the [types](https://github.com/typescript-eslint/typescript-eslint/blob/82cb9dd580f62644ed988fd2bf27f519177a60bd/packages/utils/src/ts-eslint/Rule.ts#L70) in @typescript-eslint/eslint +- Update eslint-doc-generator to handle the new information: +- Consider implementing an [eslint-plugin-eslint-plugin](https://github.com/eslint-community/eslint-plugin-eslint-plugin) rule to encourage more complete deprecation information to be stored in these properties + +## Documentation + + + +We don't necessarily need a formal announcement for this. The aforementioned changes to the rule documentation page and types should be sufficient. + +However, this update could be covered in a blog post about general rule metadata best practices, if anyone ever has an interest in writing something like that. + +## Drawbacks + + + +There are some limited [backwards compatibility](#backwards-compatibility-analysis) concerns for third-party tooling. + +## Backwards Compatibility Analysis + + + +Existing rules will continue to be backwards-compatible with the new format. + +Changing the format of these properties mainly affects third-party documentation tooling and websites that use this information, and not ESLint users nor ESLint plugins directly. + +For the most part, the new `meta.deprecated` format should be backwards-compatible, as code is often written to check simply for a truthy value inside of `meta.deprecated`, e.g. `if (rule.meta.deprecated) { /* ... */ }`, which will continue to work as expected. If code checks specifically for the boolean `true` value in `meta.deprecated`, or retrieves rule names from `meta.replacedBy`, then it would need to be updated. + +Overall, a limited number of third-party tools that might be affected, and these should be trivial to fix when impacts are discovered. + +We do not need to consider this to be a breaking change in terms of ESLint's [semantic versioning policy](https://github.com/eslint/eslint#semantic-versioning-policy). + +## Alternatives + + + +1. Do nothing. This would leave the current `meta.deprecated` and `meta.replacedBy` properties as they are, which would continue to be ambiguous and limited in the information they can provide. +2. Consolidate `meta.replacedBy` into `meta.deprecated.replacedBy`. This alternative was strongly considered. If we were to design things from scratch, we might choose this approach. However, there's not much tangible benefit to it besides organizational tidiness, and the downsides include: + - It creates yet another migration burden for the plugin ecosystem (potentially hundreds of plugins) to migrate from the old property to the new property + - There's overhead for us to go through the process of deprecating the old property and encouraging or helping plugins to move to the new one + - Tooling would need to support both properties, perhaps indefinitely due to the long tail of rules that may never be updated + +## Open Questions + + + +1. Is there additional deprecation information we'd like to represent? Note that additional information can always be added later, but it's good to consider any possible needs now. + +## Help Needed + + + +I should be able to handle the minimal changes needed in ESLint core, and can kick off some of the changes needed in community projects. + +## Frequently Asked Questions + + + +## Related Discussions + + + +The issue triggering this RFC: + +- + +This proposal is inspired by: + +- +- + +Related: + +- From 404c101f5154b8a6be5a857a60728596530b0564 Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Tue, 20 Feb 2024 22:11:50 -0500 Subject: [PATCH 02/12] tweak --- designs/2024-deprecated-rule-metadata/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/designs/2024-deprecated-rule-metadata/README.md b/designs/2024-deprecated-rule-metadata/README.md index c0fb7e73..3c255e8d 100644 --- a/designs/2024-deprecated-rule-metadata/README.md +++ b/designs/2024-deprecated-rule-metadata/README.md @@ -100,6 +100,10 @@ module.exports = { name: 'semi', url: 'https://eslint.style/rules/js/semi', }, + meta: { + message: 'Use the `foo` option on the new rule to achieve the same behavior as before.', + url: 'https://example.com/how-to-migrate-to-the-new-semi-rule', + } }, ], }, @@ -137,7 +141,7 @@ External changes: We don't necessarily need a formal announcement for this. The aforementioned changes to the rule documentation page and types should be sufficient. -However, this update could be covered in a blog post about general rule metadata best practices, if anyone ever has an interest in writing something like that. +However, this update could be covered in a blog post about general rule documentation best practices, if anyone ever has an interest in writing something like that. ## Drawbacks From edb2b76dc12e788d9c01dc64b349f16ea2fa9b38 Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:21:15 -0500 Subject: [PATCH 03/12] Update designs/2024-deprecated-rule-metadata/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Josh Goldberg ✨ --- designs/2024-deprecated-rule-metadata/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2024-deprecated-rule-metadata/README.md b/designs/2024-deprecated-rule-metadata/README.md index 3c255e8d..130c874e 100644 --- a/designs/2024-deprecated-rule-metadata/README.md +++ b/designs/2024-deprecated-rule-metadata/README.md @@ -51,7 +51,7 @@ type RuleMeta = { message?: string; // General deprecation message, such as why the deprecation occurred. url?: string; // URL to more information about this deprecation in general. }; - replacedBy: + replacedBy?: | readonly string[] // Existing shorthand property for rule names, backwards compatible. It's recommended to omit the plugin prefix from rule names. | readonly { /** From 800b3eb60265b3af5c3b25d538f85a0e4bc815b8 Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:21:21 -0500 Subject: [PATCH 04/12] Update designs/2024-deprecated-rule-metadata/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Josh Goldberg ✨ --- designs/2024-deprecated-rule-metadata/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2024-deprecated-rule-metadata/README.md b/designs/2024-deprecated-rule-metadata/README.md index 130c874e..27bb35bb 100644 --- a/designs/2024-deprecated-rule-metadata/README.md +++ b/designs/2024-deprecated-rule-metadata/README.md @@ -16,7 +16,7 @@ This RFC suggests a format for storing additional information in rule metadata a -There are long-time rule [properties](https://eslint.org/docs/latest/extend/custom-rules#rule-structure) `meta.deprecated` and `meta.replacedBy` that have been intended to document when rules are deprecated and what their replacement rule(s) are. For the most part, usage would look something like this: +There are long-time [rule properties](https://eslint.org/docs/latest/extend/custom-rules#rule-structure) `meta.deprecated` and `meta.replacedBy` that have been intended to document when rules are deprecated and what their replacement rule(s) are. For the most part, usage would look something like this: ```js module.exports = { meta: { deprecated: true, replacedBy: ['replacement-rule-name'] } }; From 5c57161e40ce5078551d694c9f4b6d84b3ae9bbe Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:24:19 -0500 Subject: [PATCH 05/12] tweaks --- designs/2024-deprecated-rule-metadata/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/designs/2024-deprecated-rule-metadata/README.md b/designs/2024-deprecated-rule-metadata/README.md index 27bb35bb..ab16f0d6 100644 --- a/designs/2024-deprecated-rule-metadata/README.md +++ b/designs/2024-deprecated-rule-metadata/README.md @@ -44,7 +44,7 @@ We propose extended `meta.deprecated` and `meta.replacedBy` rule property schema ```ts type RuleMeta = { - deprecated: + deprecated?: | boolean // Existing boolean option, backwards compatible. | string // Shorthand property for general deprecation message, such as why the deprecation occurred. Any truthy value implies deprecated. | { @@ -66,7 +66,7 @@ type RuleMeta = { url?: string; // URL to plugin documentation. }; rule: - | string // Shorthand property for rule name (without plugin prefix). + | string // Shorthand property for replacement rule name (without plugin prefix). | { name?: string; // Replacement rule name (without plugin prefix). url?: string; // URL to rule documentation. @@ -120,7 +120,7 @@ We can also support the same `meta.deprecated` and `meta.replacedBy` properties In terms of actual changes inside ESLint needed for this: -- Mention the new schema in the custom rule [documentation](https://eslint.org/docs/latest/extend/custom-rules#rule-structure) +- Mention the new schema in the [custom rule documentation](https://eslint.org/docs/latest/extend/custom-rules#rule-structure) - Ensure these properties are allowed on configurations and processors - Add any additional information to these properties in core rules as desired (such as in , ) - Update ESLint's website generator to take into account the additional information for rule doc deprecation notices @@ -174,7 +174,7 @@ For the most part, the new `meta.deprecated` format should be backwards-compatib Overall, a limited number of third-party tools that might be affected, and these should be trivial to fix when impacts are discovered. -We do not need to consider this to be a breaking change in terms of ESLint's [semantic versioning policy](https://github.com/eslint/eslint#semantic-versioning-policy). +We do not need to consider this to be a breaking change in terms of [ESLint's semantic versioning policy](https://github.com/eslint/eslint#semantic-versioning-policy). ## Alternatives From 5aaf39f282a025704ddc659146b0cf54a671d821 Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:26:27 -0500 Subject: [PATCH 06/12] tweak --- designs/2024-deprecated-rule-metadata/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2024-deprecated-rule-metadata/README.md b/designs/2024-deprecated-rule-metadata/README.md index ab16f0d6..8cdf5ce1 100644 --- a/designs/2024-deprecated-rule-metadata/README.md +++ b/designs/2024-deprecated-rule-metadata/README.md @@ -65,7 +65,7 @@ type RuleMeta = { name?: string; // Plugin name i.e. "eslint-plugin-example" that contains the replacement rule. url?: string; // URL to plugin documentation. }; - rule: + rule?: | string // Shorthand property for replacement rule name (without plugin prefix). | { name?: string; // Replacement rule name (without plugin prefix). From e18f1f4e95575b889894b3e7bcbbf711615cea3a Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:28:29 -0500 Subject: [PATCH 07/12] tweak --- designs/2024-deprecated-rule-metadata/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/designs/2024-deprecated-rule-metadata/README.md b/designs/2024-deprecated-rule-metadata/README.md index 8cdf5ce1..f038b62b 100644 --- a/designs/2024-deprecated-rule-metadata/README.md +++ b/designs/2024-deprecated-rule-metadata/README.md @@ -114,6 +114,7 @@ This data could be used by documentation websites and tooling like [eslint-doc-g > semi (deprecated) \ > Replaced by [semi](https://eslint.style/rules/js/semi) from [@stylistic/js](https://eslint.style/). \ +> Use the `foo` option on the new rule to achieve the same behavior as before. [Read more](https://example.com/how-to-migrate-to-the-new-semi-rule). \ > Stylistic rules are being moved out of ESLint core. [Read more](https://eslint.org/blog/2023/10/deprecating-formatting-rules/). We can also support the same `meta.deprecated` and `meta.replacedBy` properties on configurations and processors (the other kinds of objects exported by ESLint plugins), replacing `rule` with `config` or `processor` as needed. This would be part of the effort to standardize documentation properties in . From 4d46d08018a6bd43c81d0b2cd2c175f5b789d43a Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:30:15 -0500 Subject: [PATCH 08/12] tweak --- designs/2024-deprecated-rule-metadata/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/designs/2024-deprecated-rule-metadata/README.md b/designs/2024-deprecated-rule-metadata/README.md index f038b62b..a5fa3722 100644 --- a/designs/2024-deprecated-rule-metadata/README.md +++ b/designs/2024-deprecated-rule-metadata/README.md @@ -50,7 +50,8 @@ type RuleMeta = { | { message?: string; // General deprecation message, such as why the deprecation occurred. url?: string; // URL to more information about this deprecation in general. - }; + } + | undefined; replacedBy?: | readonly string[] // Existing shorthand property for rule names, backwards compatible. It's recommended to omit the plugin prefix from rule names. | readonly { From e8aa655b6e4fca920365fc1be056b8dc15548bfc Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Wed, 21 Feb 2024 14:08:56 -0500 Subject: [PATCH 09/12] out of scope options/conversion function --- designs/2024-deprecated-rule-metadata/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/designs/2024-deprecated-rule-metadata/README.md b/designs/2024-deprecated-rule-metadata/README.md index a5fa3722..0a30fdbd 100644 --- a/designs/2024-deprecated-rule-metadata/README.md +++ b/designs/2024-deprecated-rule-metadata/README.md @@ -207,6 +207,7 @@ We do not need to consider this to be a breaking change in terms of [ESLint's se --> 1. Is there additional deprecation information we'd like to represent? Note that additional information can always be added later, but it's good to consider any possible needs now. + - We considered including properties such as the options or conversion function needed to achieve the same behavior with a replacement rule, but deemed these out-of-scope for now. [More info in this comment](https://github.com/eslint/rfcs/pull/116#discussion_r1497468163). ## Help Needed From 9a00eee260b00c8597233059264e1b4c07d3f9d8 Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Wed, 21 Feb 2024 14:15:19 -0500 Subject: [PATCH 10/12] tweak --- designs/2024-deprecated-rule-metadata/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2024-deprecated-rule-metadata/README.md b/designs/2024-deprecated-rule-metadata/README.md index 0a30fdbd..21925d2b 100644 --- a/designs/2024-deprecated-rule-metadata/README.md +++ b/designs/2024-deprecated-rule-metadata/README.md @@ -207,7 +207,7 @@ We do not need to consider this to be a breaking change in terms of [ESLint's se --> 1. Is there additional deprecation information we'd like to represent? Note that additional information can always be added later, but it's good to consider any possible needs now. - - We considered including properties such as the options or conversion function needed to achieve the same behavior with a replacement rule, but deemed these out-of-scope for now. [More info in this comment](https://github.com/eslint/rfcs/pull/116#discussion_r1497468163). + - We considered including properties such as the exact options or conversion function needed to achieve the same behavior with a replacement rule, but decided to omit these for now in favor of just using `message`. We can consider adding these later if someone wants to champion them. [More info in this comment](https://github.com/eslint/rfcs/pull/116#discussion_r1497468163). ## Help Needed From 16cdc7674f27bb306882698195c58fde365b35cf Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Wed, 21 Feb 2024 14:21:51 -0500 Subject: [PATCH 11/12] alternatives flesh out --- .../2024-deprecated-rule-metadata/README.md | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/designs/2024-deprecated-rule-metadata/README.md b/designs/2024-deprecated-rule-metadata/README.md index 21925d2b..94d70ab8 100644 --- a/designs/2024-deprecated-rule-metadata/README.md +++ b/designs/2024-deprecated-rule-metadata/README.md @@ -187,11 +187,24 @@ We do not need to consider this to be a breaking change in terms of [ESLint's se projects have already implemented a similar feature. --> -1. Do nothing. This would leave the current `meta.deprecated` and `meta.replacedBy` properties as they are, which would continue to be ambiguous and limited in the information they can provide. -2. Consolidate `meta.replacedBy` into `meta.deprecated.replacedBy`. This alternative was strongly considered. If we were to design things from scratch, we might choose this approach. However, there's not much tangible benefit to it besides organizational tidiness, and the downsides include: - - It creates yet another migration burden for the plugin ecosystem (potentially hundreds of plugins) to migrate from the old property to the new property - - There's overhead for us to go through the process of deprecating the old property and encouraging or helping plugins to move to the new one - - Tooling would need to support both properties, perhaps indefinitely due to the long tail of rules that may never be updated +### Do nothing + +This would leave the current `meta.deprecated` and `meta.replacedBy` properties as they are, which would continue to be ambiguous and limited in the information they can provide. + +### Consolidate `meta.replacedBy` into `meta.deprecated.replacedBy` + +This alternative was strongly considered. If we were to design things from scratch, we might choose this approach. + +Pros: + +- Organizational tidiness +- Avoids the inconsistent situation where `meta.deprecated` is `false` or omitted but `meta.replacedBy` has a value, resulting in ambiguity about whether the rule is deprecated or not + +Cons: + +- It creates yet another migration burden for the plugin ecosystem (potentially hundreds of plugins) to migrate from the old property to the new property +- There's overhead for us to go through the process of deprecating the old property and encouraging or helping plugins to move to the new one +- Tooling would need to support both properties, perhaps indefinitely due to the long tail of rules that may never be updated ## Open Questions From 2bd065d22e9cbf81f2165f0edf95d0adfd1803c3 Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Wed, 21 Feb 2024 14:40:31 -0500 Subject: [PATCH 12/12] flesh out --- designs/2024-deprecated-rule-metadata/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/designs/2024-deprecated-rule-metadata/README.md b/designs/2024-deprecated-rule-metadata/README.md index 94d70ab8..56fe2560 100644 --- a/designs/2024-deprecated-rule-metadata/README.md +++ b/designs/2024-deprecated-rule-metadata/README.md @@ -199,6 +199,8 @@ Pros: - Organizational tidiness - Avoids the inconsistent situation where `meta.deprecated` is `false` or omitted but `meta.replacedBy` has a value, resulting in ambiguity about whether the rule is deprecated or not +- This RFC, where we're already making changes to these properties, is likely the best time to clean things up +- This would be a relatively lightweight and inconsequential migration, compared to more painful ESLint migrations or breaking changes we have performed in the past. Cons: