From 8f8b6fec1a74cb4666f5273bf81850676a602398 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 17 Feb 2024 11:08:31 -0500 Subject: [PATCH 1/6] feat: add require-meta-docs-recommended rule --- README.md | 1 + docs/rules/require-meta-docs-recommended.md | 39 +++++++ lib/rules/require-meta-docs-description.js | 31 ++--- lib/rules/require-meta-docs-recommended.js | 46 ++++++++ lib/rules/require-meta-docs-url.js | 34 +++--- lib/utils.js | 19 ++++ .../rules/require-meta-docs-recommended.js | 107 ++++++++++++++++++ 7 files changed, 241 insertions(+), 36 deletions(-) create mode 100644 docs/rules/require-meta-docs-recommended.md create mode 100644 lib/rules/require-meta-docs-recommended.js create mode 100644 tests/lib/rules/require-meta-docs-recommended.js diff --git a/README.md b/README.md index 2e95af12..e90c996f 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,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-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 new file mode 100644 index 00000000..84a89988 --- /dev/null +++ b/docs/rules/require-meta-docs-recommended.md @@ -0,0 +1,39 @@ +# Require rules to implement a `meta.docs.recommended` property (`eslint-plugin/require-meta-docs-recommended`) + + + +Defining a whether recommended value for each rule can help developers understand whether they're recommended. + +## Rule Details + +This rule requires ESLint rules to have a valid `meta.docs.recommended` property. + +Examples of **incorrect** code for this rule: + +```js +/* eslint eslint-plugin/require-meta-docs-recommended: error */ + +module.exports = { + meta: {}, + create(context) { + /* ... */ + }, +}; +``` + +Examples of **correct** code for this rule: + +```js +/* eslint eslint-plugin/require-meta-docs-recommended: error */ + +module.exports = { + meta: { recommended: true }, + create(context) { + /* ... */ + }, +}; +``` + +## Further Reading + +- [working-with-rules#options-schemas](https://eslint.org/docs/developer-guide/working-with-rules#options-schemas) diff --git a/lib/rules/require-meta-docs-description.js b/lib/rules/require-meta-docs-description.js index 3afd6d8d..5872dd4e 100644 --- a/lib/rules/require-meta-docs-description.js +++ b/lib/rules/require-meta-docs-description.js @@ -56,22 +56,19 @@ module.exports = { const scope = sourceCode.getScope?.(ast) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0 const { scopeManager } = sourceCode; - const pattern = - context.options[0] && context.options[0].pattern - ? new RegExp(context.options[0].pattern) - : DEFAULT_PATTERN; + const { + docsNode, + metaNode, + metaPropertyNode: descriptionNode, + } = utils.getMetaProperty('description', ruleInfo, scopeManager); - const metaNode = ruleInfo.meta; - const docsNode = utils - .evaluateObjectProperties(metaNode, scopeManager) - .find((p) => p.type === 'Property' && utils.getKeyName(p) === 'docs'); - - const descriptionNode = utils - .evaluateObjectProperties(docsNode && docsNode.value, scopeManager) - .find( - (p) => - p.type === 'Property' && utils.getKeyName(p) === 'description' - ); + if (!descriptionNode) { + context.report({ + node: docsNode || metaNode || ruleInfo.create, + messageId: 'missing', + }); + return; + } if (!descriptionNode) { context.report({ @@ -87,6 +84,10 @@ module.exports = { return; } + const pattern = context.options[0]?.pattern + ? new RegExp(context.options[0].pattern) + : DEFAULT_PATTERN; + if (typeof staticValue.value !== 'string' || staticValue.value === '') { context.report({ node: descriptionNode.value, diff --git a/lib/rules/require-meta-docs-recommended.js b/lib/rules/require-meta-docs-recommended.js new file mode 100644 index 00000000..7083f139 --- /dev/null +++ b/lib/rules/require-meta-docs-recommended.js @@ -0,0 +1,46 @@ +'use strict'; + +const utils = require('../utils'); + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: + 'require rules to implement a `meta.docs.recommended` property', + category: 'Rules', + recommended: false, + url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/require-meta-docs-recommended.md', + }, + fixable: null, + schema: [], + messages: { + missing: '`meta.docs.recommended` is required.', + }, + }, + + create(context) { + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 + const ruleInfo = utils.getRuleInfo(sourceCode); + if (!ruleInfo) { + return {}; + } + + const { scopeManager } = sourceCode; + const { + docsNode, + metaNode, + metaPropertyNode: descriptionNode, + } = utils.getMetaProperty('recommended', ruleInfo, scopeManager); + + if (!descriptionNode) { + context.report({ + node: docsNode || metaNode || ruleInfo.create, + messageId: 'missing', + }); + } + + return {}; + }, +}; diff --git a/lib/rules/require-meta-docs-url.js b/lib/rules/require-meta-docs-url.js index d52f1984..a32ccbda 100644 --- a/lib/rules/require-meta-docs-url.js +++ b/lib/rules/require-meta-docs-url.js @@ -9,7 +9,7 @@ // ----------------------------------------------------------------------------- const path = require('path'); -const util = require('../utils'); +const utils = require('../utils'); const { getStaticValue } = require('eslint-utils'); // ----------------------------------------------------------------------------- @@ -77,7 +77,7 @@ module.exports = { } const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 - const ruleInfo = util.getRuleInfo(sourceCode); + const ruleInfo = utils.getRuleInfo(sourceCode); if (!ruleInfo) { return {}; } @@ -87,16 +87,11 @@ module.exports = { const scope = sourceCode.getScope?.(ast) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0 const { scopeManager } = sourceCode; - const metaNode = ruleInfo.meta; - const docsPropNode = util - .evaluateObjectProperties(metaNode, scopeManager) - .find((p) => p.type === 'Property' && util.getKeyName(p) === 'docs'); - const urlPropNode = util - .evaluateObjectProperties( - docsPropNode && docsPropNode.value, - scopeManager - ) - .find((p) => p.type === 'Property' && util.getKeyName(p) === 'url'); + const { + docsNode, + metaNode, + metaPropertyNode: urlPropNode, + } = utils.getMetaProperty('url', ruleInfo, scopeManager); const staticValue = urlPropNode ? getStaticValue(urlPropNode.value, scope) @@ -113,7 +108,7 @@ module.exports = { context.report({ node: (urlPropNode && urlPropNode.value) || - (docsPropNode && docsPropNode.value) || + (docsNode && docsNode.value) || metaNode || ruleInfo.create, @@ -143,22 +138,19 @@ module.exports = { ) { return fixer.replaceText(urlPropNode.value, urlString); } - } else if ( - docsPropNode && - docsPropNode.value.type === 'ObjectExpression' - ) { - return util.insertProperty( + } else if (docsNode && docsNode.value.type === 'ObjectExpression') { + return utils.insertProperty( fixer, - docsPropNode.value, + docsNode.value, `url: ${urlString}`, sourceCode ); } else if ( - !docsPropNode && + !docsNode && metaNode && metaNode.type === 'ObjectExpression' ) { - return util.insertProperty( + return utils.insertProperty( fixer, metaNode, `docs: {\nurl: ${urlString}\n}`, diff --git a/lib/utils.js b/lib/utils.js index 4ad42c24..e517ce4a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -800,6 +800,25 @@ module.exports = { }); }, + getMetaProperty(propertyName, ruleInfo, scopeManager) { + const metaNode = ruleInfo.meta; + + const docsNode = module.exports + .evaluateObjectProperties(metaNode, scopeManager) + .find( + (p) => p.type === 'Property' && module.exports.getKeyName(p) === 'docs' + ); + + const metaPropertyNode = module.exports + .evaluateObjectProperties(docsNode?.value, scopeManager) + .find( + (p) => + p.type === 'Property' && module.exports.getKeyName(p) === propertyName + ); + + return { docsNode, metaNode, metaPropertyNode }; + }, + /** * Get the `meta.messages` node from a rule. * @param {RuleInfo} ruleInfo diff --git a/tests/lib/rules/require-meta-docs-recommended.js b/tests/lib/rules/require-meta-docs-recommended.js new file mode 100644 index 00000000..13dd2bd2 --- /dev/null +++ b/tests/lib/rules/require-meta-docs-recommended.js @@ -0,0 +1,107 @@ +'use strict'; + +const rule = require('../../../lib/rules/require-meta-docs-recommended'); +const RuleTester = require('eslint').RuleTester; + +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 9 } }); +ruleTester.run('require-meta-docs-recommended', rule, { + valid: [ + 'foo()', + 'module.exports = {};', + ` + module.exports = { + meta: { docs: { recommended: true } }, + create(context) {} + }; + `, + { + code: ` + export default { + meta: { docs: { recommended: 'disallow unused variables' } }, + create(context) {} + }; + `, + parserOptions: { sourceType: 'module' }, + }, + ` + const RECOMMENDED = true; + module.exports = { + meta: { docs: { recommended: RECOMMENDED } }, + create(context) {} + }; + `, + + ` + const meta = { docs: { recommended: 'enforce foo' } }; + module.exports = { + meta, + create(context) {} + }; + `, + ` + const extraDocs = { recommended: 123 }; + const extraMeta = { docs: { ...extraDocs } }; + module.exports = { + meta: { ...extraMeta }, + create(context) {} + }; + `, + ], + + invalid: [ + { + code: 'module.exports = { create(context) {} };', + output: null, + errors: [{ messageId: 'missing', type: 'FunctionExpression' }], + }, + { + code: ` + module.exports = { + meta: {}, + create(context) {} + }; + `, + output: null, + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], + }, + { + code: ` + const extraDocs = { }; + const extraMeta = { docs: { ...extraDocs } }; + module.exports = { + meta: { ...extraMeta }, + create(context) {} + }; + `, + output: null, + errors: [{ messageId: 'missing', type: 'Property' }], + }, + ], +}); + +const ruleTesterTypeScript = new RuleTester({ + parserOptions: { sourceType: 'module' }, + parser: require.resolve('@typescript-eslint/parser'), +}); +ruleTesterTypeScript.run('require-meta-docs-recommended (TypeScript)', rule, { + valid: [ + ` + export default createESLintRule({ + meta: { docs: { recommended: true } }, + create(context) {} + }); + `, + ], + invalid: [ + { + code: ` + export default createESLintRule({ + meta: {}, + create(context) {} + }); + `, + output: null, + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], + }, + ], +}); From 4813c95403cc3f722a2e2efea818ad15d5961027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Tue, 23 Apr 2024 17:24:13 -0400 Subject: [PATCH 2/6] Apply suggestions from code review Co-authored-by: Bryan Mishkin <698306+bmish@users.noreply.github.com> --- docs/rules/require-meta-docs-recommended.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/rules/require-meta-docs-recommended.md b/docs/rules/require-meta-docs-recommended.md index 84a89988..cb78bdb7 100644 --- a/docs/rules/require-meta-docs-recommended.md +++ b/docs/rules/require-meta-docs-recommended.md @@ -2,7 +2,13 @@ -Defining a whether recommended value for each rule can help developers understand whether they're recommended. +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. + +However, this flag may not be appropriate for all plugins: + +* Extra scripting/tooling is needed to keep the flags in sync with the config +* The flag may not scale to plugins that have multiple/many configs or don't have a recommended config +* Or some may simply prefer to keep the source of truth solely in the config rather than duplicating config membership data in the rules ## Rule Details @@ -36,4 +42,4 @@ module.exports = { ## Further Reading -- [working-with-rules#options-schemas](https://eslint.org/docs/developer-guide/working-with-rules#options-schemas) +- [Rule Structure](https://eslint.org/docs/latest/extend/custom-rules#rule-structure) From 30b859c16636ba13f619dbc05b7a3cf8f3d00d0b Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 23 Apr 2024 17:25:30 -0400 Subject: [PATCH 3/6] Apply rename --- lib/rules/require-meta-docs-description.js | 2 +- lib/rules/require-meta-docs-recommended.js | 2 +- lib/rules/require-meta-docs-url.js | 2 +- lib/utils.js | 2 +- tests/lib/rules/require-meta-docs-recommended.js | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/rules/require-meta-docs-description.js b/lib/rules/require-meta-docs-description.js index 5872dd4e..081b4ff6 100644 --- a/lib/rules/require-meta-docs-description.js +++ b/lib/rules/require-meta-docs-description.js @@ -60,7 +60,7 @@ module.exports = { docsNode, metaNode, metaPropertyNode: descriptionNode, - } = utils.getMetaProperty('description', ruleInfo, scopeManager); + } = utils.getMetaDocsProperty('description', ruleInfo, scopeManager); if (!descriptionNode) { context.report({ diff --git a/lib/rules/require-meta-docs-recommended.js b/lib/rules/require-meta-docs-recommended.js index 7083f139..d5f05a53 100644 --- a/lib/rules/require-meta-docs-recommended.js +++ b/lib/rules/require-meta-docs-recommended.js @@ -32,7 +32,7 @@ module.exports = { docsNode, metaNode, metaPropertyNode: descriptionNode, - } = utils.getMetaProperty('recommended', ruleInfo, scopeManager); + } = utils.getMetaDocsProperty('recommended', ruleInfo, scopeManager); if (!descriptionNode) { context.report({ diff --git a/lib/rules/require-meta-docs-url.js b/lib/rules/require-meta-docs-url.js index a32ccbda..a7f736b7 100644 --- a/lib/rules/require-meta-docs-url.js +++ b/lib/rules/require-meta-docs-url.js @@ -91,7 +91,7 @@ module.exports = { docsNode, metaNode, metaPropertyNode: urlPropNode, - } = utils.getMetaProperty('url', ruleInfo, scopeManager); + } = utils.getMetaDocsProperty('url', ruleInfo, scopeManager); const staticValue = urlPropNode ? getStaticValue(urlPropNode.value, scope) diff --git a/lib/utils.js b/lib/utils.js index e517ce4a..d7773858 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -800,7 +800,7 @@ module.exports = { }); }, - getMetaProperty(propertyName, ruleInfo, scopeManager) { + getMetaDocsProperty(propertyName, ruleInfo, scopeManager) { const metaNode = ruleInfo.meta; const docsNode = module.exports diff --git a/tests/lib/rules/require-meta-docs-recommended.js b/tests/lib/rules/require-meta-docs-recommended.js index 13dd2bd2..088c4f24 100644 --- a/tests/lib/rules/require-meta-docs-recommended.js +++ b/tests/lib/rules/require-meta-docs-recommended.js @@ -83,6 +83,7 @@ const ruleTesterTypeScript = new RuleTester({ parserOptions: { sourceType: 'module' }, parser: require.resolve('@typescript-eslint/parser'), }); + ruleTesterTypeScript.run('require-meta-docs-recommended (TypeScript)', rule, { valid: [ ` From 754890b7adfad6d42a571597c73ecf28650be2ee Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 24 Apr 2024 08:50:49 -0400 Subject: [PATCH 4/6] Fix lint complaints --- lib/utils.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 936eed32..940001fe 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -838,14 +838,15 @@ module.exports = { const docsNode = module.exports .evaluateObjectProperties(metaNode, scopeManager) .find( - (p) => p.type === 'Property' && module.exports.getKeyName(p) === 'docs' + (p) => p.type === 'Property' && module.exports.getKeyName(p) === 'docs', ); const metaPropertyNode = module.exports .evaluateObjectProperties(docsNode?.value, scopeManager) .find( (p) => - p.type === 'Property' && module.exports.getKeyName(p) === propertyName + p.type === 'Property' && + module.exports.getKeyName(p) === propertyName, ); return { docsNode, metaNode, metaPropertyNode }; From 76303c0c5ab9ef0d5a93203d2494663609adeeb9 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 24 Apr 2024 16:09:34 -0400 Subject: [PATCH 5/6] Added allowNonBoolean opt-in option --- docs/rules/require-meta-docs-recommended.md | 36 +++++++++- lib/rules/require-meta-docs-recommended.js | 33 ++++++++- lib/rules/require-meta-docs-url.js | 3 +- lib/rules/require-meta-has-suggestions.js | 3 +- lib/rules/require-meta-schema.js | 5 +- lib/utils.js | 8 +++ .../rules/require-meta-docs-recommended.js | 71 ++++++++++++++++++- 7 files changed, 144 insertions(+), 15 deletions(-) diff --git a/docs/rules/require-meta-docs-recommended.md b/docs/rules/require-meta-docs-recommended.md index cb78bdb7..28ff3feb 100644 --- a/docs/rules/require-meta-docs-recommended.md +++ b/docs/rules/require-meta-docs-recommended.md @@ -6,9 +6,11 @@ Utilizing `meta.docs.recommended` makes it clear from each rule implementation w However, this flag may not be appropriate for all plugins: -* Extra scripting/tooling is needed to keep the flags in sync with the config -* The flag may not scale to plugins that have multiple/many configs or don't have a recommended config -* Or some may simply prefer to keep the source of truth solely in the config rather than duplicating config membership data in the rules +- Extra scripting/tooling is needed to keep the flags in sync with the config +- The flag may not scale to plugins that have multiple/many configs or don't have a recommended config +- Or some may simply prefer to keep the source of truth solely in the config rather than duplicating config membership data in the rules + +By default, this rule enforces a `recommended` property be set to a `boolean` value. ## Rule Details @@ -40,6 +42,34 @@ module.exports = { }; ``` +## Options + + + +| Name | Description | Type | Default | +| :---------------- | :--------------------------------------------------- | :------ | :------ | +| `allowNonBoolean` | Whether to allow values of types other than boolean. | Boolean | `false` | + + + +### `allowNonBoolean` + +Some plugins require `meta.docs.recommended` values but allow value types other than `boolean`. +This option changes the rule to only enforce that the values exist. + +Example of **correct** code for this rule with `allowNonBoolean`: + +```js +/* eslint eslint-plugin/require-meta-docs-recommended: ["error", { "allowNonBoolean": true }] */ + +module.exports = { + meta: { recommended: 'strict' }, + create(context) { + /* ... */ + }, +}; +``` + ## Further Reading - [Rule Structure](https://eslint.org/docs/latest/extend/custom-rules#rule-structure) diff --git a/lib/rules/require-meta-docs-recommended.js b/lib/rules/require-meta-docs-recommended.js index d5f05a53..d620f34b 100644 --- a/lib/rules/require-meta-docs-recommended.js +++ b/lib/rules/require-meta-docs-recommended.js @@ -1,5 +1,6 @@ 'use strict'; +const { getStaticValue } = require('eslint-utils'); const utils = require('../utils'); /** @type {import('eslint').Rule.RuleModule} */ @@ -14,8 +15,21 @@ module.exports = { url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/require-meta-docs-recommended.md', }, fixable: null, - schema: [], + schema: [ + { + type: 'object', + properties: { + allowNonBoolean: { + default: false, + description: 'Whether to allow values of types other than boolean.', + type: 'boolean', + }, + }, + additionalProperties: false, + }, + ], messages: { + incorrect: '`meta.docs.recommended` is required to be a boolean.', missing: '`meta.docs.recommended` is required.', }, }, @@ -39,6 +53,23 @@ module.exports = { node: docsNode || metaNode || ruleInfo.create, messageId: 'missing', }); + return {}; + } + + if (context.options[0]?.allowNonBoolean) { + return {}; + } + + const staticValue = utils.isUndefinedIdentifier(descriptionNode.value) + ? { value: undefined } + : getStaticValue(descriptionNode.value); + + if (staticValue && typeof staticValue.value !== 'boolean') { + context.report({ + node: descriptionNode.value, + messageId: 'incorrect', + }); + return {}; } return {}; diff --git a/lib/rules/require-meta-docs-url.js b/lib/rules/require-meta-docs-url.js index 3d3352d9..af3e6d40 100644 --- a/lib/rules/require-meta-docs-url.js +++ b/lib/rules/require-meta-docs-url.js @@ -133,8 +133,7 @@ module.exports = { if (urlPropNode) { if ( urlPropNode.value.type === 'Literal' || - (urlPropNode.value.type === 'Identifier' && - urlPropNode.value.name === 'undefined') + utils.isUndefinedIdentifier(urlPropNode.value) ) { return fixer.replaceText(urlPropNode.value, urlString); } diff --git a/lib/rules/require-meta-has-suggestions.js b/lib/rules/require-meta-has-suggestions.js index 323e138c..a7b5547a 100644 --- a/lib/rules/require-meta-has-suggestions.js +++ b/lib/rules/require-meta-has-suggestions.js @@ -139,8 +139,7 @@ module.exports = { fix(fixer) { if ( hasSuggestionsProperty.value.type === 'Literal' || - (hasSuggestionsProperty.value.type === 'Identifier' && - hasSuggestionsProperty.value.name === 'undefined') + utils.isUndefinedIdentifier(hasSuggestionsProperty.value) ) { return fixer.replaceText( hasSuggestionsProperty.value, diff --git a/lib/rules/require-meta-schema.js b/lib/rules/require-meta-schema.js index bbb6e00a..9c3e908c 100644 --- a/lib/rules/require-meta-schema.js +++ b/lib/rules/require-meta-schema.js @@ -106,10 +106,7 @@ module.exports = { hasEmptySchema = true; } - if ( - value.type === 'Literal' || - (value.type === 'Identifier' && value.name === 'undefined') - ) { + if (value.type === 'Literal' || utils.isUndefinedIdentifier(value)) { context.report({ node: value, messageId: 'wrongType' }); } }, diff --git a/lib/utils.js b/lib/utils.js index 940001fe..d59555e5 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -939,6 +939,14 @@ module.exports = { }); }, + /** + * @param {Node} node + * @returns {boolean} Whether the node is an Identifier with name `undefined`. + */ + isUndefinedIdentifier(node) { + return node.type === 'Identifier' && node.name === 'undefined'; + }, + /** * Check whether a variable's definition is from a function parameter. * @param {Node} node - the Identifier node for the variable. diff --git a/tests/lib/rules/require-meta-docs-recommended.js b/tests/lib/rules/require-meta-docs-recommended.js index d472d826..3567d64b 100644 --- a/tests/lib/rules/require-meta-docs-recommended.js +++ b/tests/lib/rules/require-meta-docs-recommended.js @@ -20,7 +20,7 @@ ruleTester.run('require-meta-docs-recommended', rule, { { code: ` export default { - meta: { docs: { recommended: 'disallow unused variables' } }, + meta: { docs: { recommended: true } }, create(context) {} }; `, @@ -35,20 +35,49 @@ ruleTester.run('require-meta-docs-recommended', rule, { `, ` - const meta = { docs: { recommended: 'enforce foo' } }; + const meta = { docs: { recommended: true } }; module.exports = { meta, create(context) {} }; `, ` - const extraDocs = { recommended: 123 }; + const extraDocs = { recommended: true }; const extraMeta = { docs: { ...extraDocs } }; module.exports = { meta: { ...extraMeta }, create(context) {} }; `, + { + code: ` + module.exports = { + meta: { docs: { recommended: undefined } }, + create(context) {} + }; + `, + options: [{ allowNonBoolean: true }], + }, + { + code: ` + module.exports = { + meta: { docs: { recommended: 'strict' } }, + create(context) {} + }; + `, + options: [{ allowNonBoolean: true }], + }, + { + code: ` + const extraDocs = { recommended: 'strict' }; + const extraMeta = { docs: { ...extraDocs } }; + module.exports = { + meta: { ...extraMeta }, + create(context) {} + }; + `, + options: [{ allowNonBoolean: true }], + }, ], invalid: [ @@ -67,6 +96,36 @@ ruleTester.run('require-meta-docs-recommended', rule, { output: null, errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, + { + code: ` + module.exports = { + meta: { docs: {} }, + create(context) {} + }; + `, + output: null, + errors: [{ messageId: 'missing', type: 'Property' }], + }, + { + code: ` + module.exports = { + meta: { docs: { recommended: undefined } }, + create(context) {} + }; + `, + output: null, + errors: [{ messageId: 'incorrect', type: 'Identifier' }], + }, + { + code: ` + module.exports = { + meta: { docs: { recommended: 'strict' } }, + create(context) {} + }; + `, + output: null, + errors: [{ messageId: 'incorrect', type: 'Literal' }], + }, { code: ` const extraDocs = { }; @@ -79,6 +138,12 @@ ruleTester.run('require-meta-docs-recommended', rule, { output: null, errors: [{ messageId: 'missing', type: 'Property' }], }, + { + code: 'module.exports = { create(context) {} };', + output: null, + options: [{ allowNonBoolean: true }], + errors: [{ messageId: 'missing', type: 'FunctionExpression' }], + }, ], }); From 1b23436dd96230fda92ae89e14f89e8a276d37d9 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 24 Apr 2024 16:19:46 -0400 Subject: [PATCH 6/6] Unit test for unknown value --- tests/lib/rules/require-meta-docs-recommended.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/lib/rules/require-meta-docs-recommended.js b/tests/lib/rules/require-meta-docs-recommended.js index 3567d64b..591a2c77 100644 --- a/tests/lib/rules/require-meta-docs-recommended.js +++ b/tests/lib/rules/require-meta-docs-recommended.js @@ -49,6 +49,12 @@ ruleTester.run('require-meta-docs-recommended', rule, { create(context) {} }; `, + ` + module.exports = { + meta: { docs: { recommended: RECOMMENDED } }, + create(context) {} + }; + `, { code: ` module.exports = {