From c14b2221afe0f547f9316928198c8c550582e33c Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Mon, 18 Nov 2024 14:44:00 +0100 Subject: [PATCH 1/2] Add suggestions to `require-meta-docs-recommended` rule --- lib/rules/require-meta-docs-recommended.js | 48 +++++++ .../rules/require-meta-docs-recommended.js | 132 ++++++++++++++++-- 2 files changed, 172 insertions(+), 8 deletions(-) diff --git a/lib/rules/require-meta-docs-recommended.js b/lib/rules/require-meta-docs-recommended.js index f2cdeee8..c4b1afb7 100644 --- a/lib/rules/require-meta-docs-recommended.js +++ b/lib/rules/require-meta-docs-recommended.js @@ -3,6 +3,24 @@ const { getStaticValue } = require('@eslint-community/eslint-utils'); const utils = require('../utils'); +/** + * @param {import('eslint').Rule.RuleFixer} fixer + * @param {import('estree').ObjectExpression} objectNode + * @param {boolean} recommendedValue + */ +function insertRecommendedProperty(fixer, objectNode, recommendedValue) { + if (objectNode.properties.length === 0) { + return fixer.replaceText( + objectNode, + `{ recommended: ${recommendedValue} }`, + ); + } + return fixer.insertTextAfter( + objectNode.properties.at(-1), + `, recommended: ${recommendedValue}`, + ); +} + /** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { @@ -15,6 +33,7 @@ module.exports = { url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/require-meta-docs-recommended.md', }, fixable: null, + hasSuggestions: true, schema: [ { type: 'object', @@ -31,6 +50,8 @@ module.exports = { messages: { incorrect: '`meta.docs.recommended` is required to be a boolean.', missing: '`meta.docs.recommended` is required.', + setRecommendedTrue: 'Set `meta.docs.recommended` to `true`.', + setRecommendedFalse: 'Set `meta.docs.recommended` to `false`.', }, }, @@ -49,9 +70,26 @@ module.exports = { } = utils.getMetaDocsProperty('recommended', ruleInfo, scopeManager); if (!descriptionNode) { + const suggestions = + docsNode?.value?.type === 'ObjectExpression' + ? [ + { + messageId: 'setRecommendedTrue', + fix: (fixer) => + insertRecommendedProperty(fixer, docsNode.value, true), + }, + { + messageId: 'setRecommendedFalse', + fix: (fixer) => + insertRecommendedProperty(fixer, docsNode.value, false), + }, + ] + : []; + context.report({ node: docsNode || metaNode || ruleInfo.create, messageId: 'missing', + suggest: suggestions, }); return {}; } @@ -68,6 +106,16 @@ module.exports = { context.report({ node: descriptionNode.value, messageId: 'incorrect', + suggest: [ + { + messageId: 'setRecommendedTrue', + fix: (fixer) => fixer.replaceText(descriptionNode.value, 'true'), + }, + { + messageId: 'setRecommendedFalse', + fix: (fixer) => fixer.replaceText(descriptionNode.value, 'false'), + }, + ], }); return {}; } diff --git a/tests/lib/rules/require-meta-docs-recommended.js b/tests/lib/rules/require-meta-docs-recommended.js index 591a2c77..1a58bacc 100644 --- a/tests/lib/rules/require-meta-docs-recommended.js +++ b/tests/lib/rules/require-meta-docs-recommended.js @@ -90,7 +90,9 @@ ruleTester.run('require-meta-docs-recommended', rule, { { code: 'module.exports = { create(context) {} };', output: null, - errors: [{ messageId: 'missing', type: 'FunctionExpression' }], + errors: [ + { messageId: 'missing', type: 'FunctionExpression', suggestions: [] }, + ], }, { code: ` @@ -100,7 +102,13 @@ ruleTester.run('require-meta-docs-recommended', rule, { }; `, output: null, - errors: [{ messageId: 'missing', type: 'ObjectExpression' }], + errors: [ + { + messageId: 'missing', + type: 'ObjectExpression', + suggestions: [], + }, + ], }, { code: ` @@ -110,7 +118,32 @@ ruleTester.run('require-meta-docs-recommended', rule, { }; `, output: null, - errors: [{ messageId: 'missing', type: 'Property' }], + errors: [ + { + messageId: 'missing', + type: 'Property', + suggestions: [ + { + messageId: 'setRecommendedTrue', + output: ` + module.exports = { + meta: { docs: { recommended: true } }, + create(context) {} + }; + `, + }, + { + messageId: 'setRecommendedFalse', + output: ` + module.exports = { + meta: { docs: { recommended: false } }, + create(context) {} + }; + `, + }, + ], + }, + ], }, { code: ` @@ -120,7 +153,32 @@ ruleTester.run('require-meta-docs-recommended', rule, { }; `, output: null, - errors: [{ messageId: 'incorrect', type: 'Identifier' }], + errors: [ + { + messageId: 'incorrect', + type: 'Identifier', + suggestions: [ + { + messageId: 'setRecommendedTrue', + output: ` + module.exports = { + meta: { docs: { recommended: true } }, + create(context) {} + }; + `, + }, + { + messageId: 'setRecommendedFalse', + output: ` + module.exports = { + meta: { docs: { recommended: false } }, + create(context) {} + }; + `, + }, + ], + }, + ], }, { code: ` @@ -130,7 +188,32 @@ ruleTester.run('require-meta-docs-recommended', rule, { }; `, output: null, - errors: [{ messageId: 'incorrect', type: 'Literal' }], + errors: [ + { + messageId: 'incorrect', + type: 'Literal', + suggestions: [ + { + messageId: 'setRecommendedTrue', + output: ` + module.exports = { + meta: { docs: { recommended: true } }, + create(context) {} + }; + `, + }, + { + messageId: 'setRecommendedFalse', + output: ` + module.exports = { + meta: { docs: { recommended: false } }, + create(context) {} + }; + `, + }, + ], + }, + ], }, { code: ` @@ -142,13 +225,44 @@ ruleTester.run('require-meta-docs-recommended', rule, { }; `, output: null, - errors: [{ messageId: 'missing', type: 'Property' }], + errors: [ + { + messageId: 'missing', + type: 'Property', + suggestions: [ + { + messageId: 'setRecommendedTrue', + output: ` + const extraDocs = { }; + const extraMeta = { docs: { ...extraDocs, recommended: true } }; + module.exports = { + meta: { ...extraMeta }, + create(context) {} + }; + `, + }, + { + messageId: 'setRecommendedFalse', + output: ` + const extraDocs = { }; + const extraMeta = { docs: { ...extraDocs, recommended: false } }; + module.exports = { + meta: { ...extraMeta }, + create(context) {} + }; + `, + }, + ], + }, + ], }, { code: 'module.exports = { create(context) {} };', output: null, options: [{ allowNonBoolean: true }], - errors: [{ messageId: 'missing', type: 'FunctionExpression' }], + errors: [ + { messageId: 'missing', type: 'FunctionExpression', suggestions: [] }, + ], }, ], }); @@ -178,7 +292,9 @@ ruleTesterTypeScript.run('require-meta-docs-recommended (TypeScript)', rule, { }); `, output: null, - errors: [{ messageId: 'missing', type: 'ObjectExpression' }], + errors: [ + { messageId: 'missing', type: 'ObjectExpression', suggestions: [] }, + ], }, ], }); From c49ea40a2714faed437a9be53bbbc2b29ebebb84 Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Mon, 18 Nov 2024 14:50:37 +0100 Subject: [PATCH 2/2] Update docs --- README.md | 2 +- docs/rules/require-meta-docs-recommended.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fb1e2e6c..36454181 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ module.exports = [ | [prefer-replace-text](docs/rules/prefer-replace-text.md) | require using `replaceText()` instead of `replaceTextRange()` | | | | | | [report-message-format](docs/rules/report-message-format.md) | enforce a consistent format for rule report messages | | | | | | [require-meta-docs-description](docs/rules/require-meta-docs-description.md) | require rules to implement a `meta.docs.description` property with the correct format | | | | | -| [require-meta-docs-recommended](docs/rules/require-meta-docs-recommended.md) | require rules to implement a `meta.docs.recommended` property | | | | | +| [require-meta-docs-recommended](docs/rules/require-meta-docs-recommended.md) | require rules to implement a `meta.docs.recommended` property | | | 💡 | | | [require-meta-docs-url](docs/rules/require-meta-docs-url.md) | require rules to implement a `meta.docs.url` property | | 🔧 | | | | [require-meta-fixable](docs/rules/require-meta-fixable.md) | require rules to implement a `meta.fixable` property | ✅ | | | | | [require-meta-has-suggestions](docs/rules/require-meta-has-suggestions.md) | require suggestable rules to implement a `meta.hasSuggestions` property | ✅ | 🔧 | | | diff --git a/docs/rules/require-meta-docs-recommended.md b/docs/rules/require-meta-docs-recommended.md index 28ff3feb..816a7b41 100644 --- a/docs/rules/require-meta-docs-recommended.md +++ b/docs/rules/require-meta-docs-recommended.md @@ -1,5 +1,7 @@ # Require rules to implement a `meta.docs.recommended` property (`eslint-plugin/require-meta-docs-recommended`) +💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). + Utilizing `meta.docs.recommended` makes it clear from each rule implementation whether a rule is part of the `recommended` config. Some plugins also have scripting for conveniently generating their config based on this flag.