diff --git a/docs/rules/require-meta-fixable.md b/docs/rules/require-meta-fixable.md index 38944128..7f3f6881 100644 --- a/docs/rules/require-meta-fixable.md +++ b/docs/rules/require-meta-fixable.md @@ -2,11 +2,11 @@ ✔️ The `"extends": "plugin:eslint-plugin/recommended"` property in a configuration file enables this rule. -A fixable ESLint rule must have a valid `meta.fixable` property. A rule reports a problem with a `fix()` function but does not export a `meta.fixable` property is likely to cause an unexpected error. +ESLint requires fixable rules to specify a valid `meta.fixable` property (with value `code` or `whitespace`). ## Rule Details -This rule aims to require ESLint rules to have a `meta.fixable` property if necessary. +This rule aims to require fixable ESLint rules to have a valid `meta.fixable` property. Examples of **incorrect** code for this rule: @@ -14,7 +14,7 @@ Examples of **incorrect** code for this rule: /* eslint eslint-plugin/require-meta-fixable: "error" */ module.exports = { - meta: {}, + meta: {}, // missing `fixable` property create (context) { context.report({ node, @@ -44,20 +44,6 @@ module.exports = { }; ``` -```js -/* eslint eslint-plugin/require-meta-fixable: "error" */ - -module.exports = { create (context) { - context.report({ - node, - message: 'foo', - fix (fixer) { - return fixer.remove(node); - }, - }); -} }; -``` - Examples of **correct** code for this rule: ```js @@ -91,10 +77,7 @@ module.exports = { }; ``` -## When Not To Use It - -If you do not plan to implement autofixable rules, you can turn off this rule. - ## Further Reading * [ESLint's autofix API](http://eslint.org/docs/developer-guide/working-with-rules#applying-fixes) +* [ESLint's rule basics mentioning `meta.fixable`](https://eslint.org/docs/developer-guide/working-with-rules#rule-basics) diff --git a/lib/rules/require-meta-fixable.js b/lib/rules/require-meta-fixable.js index fb9dc501..e65e561d 100644 --- a/lib/rules/require-meta-fixable.js +++ b/lib/rules/require-meta-fixable.js @@ -5,6 +5,7 @@ 'use strict'; +const { getStaticValue } = require('eslint-utils'); const utils = require('../utils'); // ------------------------------------------------------------------------------ @@ -20,6 +21,10 @@ module.exports = { recommended: true, }, schema: [], + messages: { + invalid: '`meta.fixable` must be either `code`, `whitespace` or `null`.', + missing: 'Fixable rules must export a `meta.fixable` property.', + }, }, create (context) { @@ -62,19 +67,25 @@ module.exports = { ruleInfo.meta.properties.find(prop => utils.getKeyName(prop) === 'fixable'); if (metaFixableProp) { - const VALID_VALUES = new Set(['code', 'whitespace', null, undefined]); - const valueIsValid = metaFixableProp.value.type === 'Literal' - ? VALID_VALUES.has(metaFixableProp.value.value) - // eslint-disable-next-line unicorn/no-nested-ternary - : metaFixableProp.value.type === 'TemplateLiteral' && metaFixableProp.value.quasis.length === 1 - ? VALID_VALUES.has(metaFixableProp.value.quasis[0].value.cooked) - : metaFixableProp.value.type === 'Identifier' && metaFixableProp.value.name === 'undefined'; + const staticValue = getStaticValue(metaFixableProp.value, context.getScope()); + if (!staticValue) { + // Ignore non-static values since we can't determine what they look like. + return; + } + + if (!['code', 'whitespace', null, undefined].includes(staticValue.value)) { + // `fixable` property has an invalid value. + context.report({ node: metaFixableProp.value, messageId: 'invalid' }); + return; + } - if (!valueIsValid) { - context.report({ node: metaFixableProp, message: '`meta.fixable` must be either `code`, `whitespace` or `null`.' }); + if (usesFixFunctions && !['code', 'whitespace'].includes(staticValue.value)) { + // Rule is fixable but `fixable` property does not have a fixable value. + context.report({ node: metaFixableProp.value, messageId: 'missing' }); } - } else if (usesFixFunctions) { - context.report({ node: ruleInfo.create, message: 'Fixable rules must export a `meta.fixable` property.' }); + } else if (!metaFixableProp && usesFixFunctions) { + // Rule is fixable but is missing the `fixable` property. + context.report({ node: ruleInfo.meta || ruleInfo.create, messageId: 'missing' }); } }, }; diff --git a/tests/lib/rules/require-meta-fixable.js b/tests/lib/rules/require-meta-fixable.js index 875e7016..3b59311a 100644 --- a/tests/lib/rules/require-meta-fixable.js +++ b/tests/lib/rules/require-meta-fixable.js @@ -12,9 +12,6 @@ const rule = require('../../../lib/rules/require-meta-fixable'); const RuleTester = require('eslint').RuleTester; -const MISSING_ERROR = { message: 'Fixable rules must export a `meta.fixable` property.', type: 'FunctionExpression' }; -const INVALID_ERROR = { message: '`meta.fixable` must be either `code`, `whitespace` or `null`.', type: 'Property' }; - // ------------------------------------------------------------------------------ // Tests // ------------------------------------------------------------------------------ @@ -37,6 +34,16 @@ ruleTester.run('require-meta-fixable', rule, { } }; `, + // Value in variable. + ` + const fixable = 'code'; + module.exports = { + meta: { fixable }, + create(context) { + context.report({node, message, fix: foo}); + } + }; + `, ` module.exports = { meta: { fixable: 'whitespace' }, @@ -93,6 +100,13 @@ ruleTester.run('require-meta-fixable', rule, { } }; `, + // `fixable` uses variable but no static value available. + ` + module.exports = { + meta: { fixable: foo }, + create(context) { context.report({node, message, fix: foo}); } + }; + `, ` module.exports = { meta: {}, @@ -128,7 +142,15 @@ ruleTester.run('require-meta-fixable', rule, { create(context) { context.report({node, message, fix: foo}); } }; `, - errors: [MISSING_ERROR], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], + }, + { + code: ` + module.exports = { + create(context) { context.report({node, message, fix: foo}); } + }; + `, + errors: [{ messageId: 'missing', type: 'FunctionExpression' }], }, { code: ` @@ -137,7 +159,7 @@ ruleTester.run('require-meta-fixable', rule, { create(context) { context.report(node, loc, message, data, fix); } }; `, - errors: [MISSING_ERROR], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { code: ` @@ -146,7 +168,7 @@ ruleTester.run('require-meta-fixable', rule, { create(context) { context.report({node, message}); } }; `, - errors: [INVALID_ERROR], + errors: [{ messageId: 'invalid', type: 'Literal' }], }, { code: ` @@ -155,16 +177,35 @@ ruleTester.run('require-meta-fixable', rule, { create(context) { context.report({node, message, fix: foo}); } }; `, - errors: [INVALID_ERROR], + errors: [{ messageId: 'invalid', type: 'Literal' }], + }, + { + code: ` + const fixable = 'invalid'; + module.exports = { + meta: { fixable }, + create(context) { context.report({node, message, fix: foo}); } + }; + `, + errors: [{ messageId: 'invalid', type: 'Identifier' }], + }, + { + code: ` + module.exports = { + meta: { fixable: null }, + create(context) { context.report({node, message, fix: foo}); } + }; + `, + errors: [{ messageId: 'missing', type: 'Literal' }], }, { code: ` module.exports = { - meta: { fixable: foo }, + meta: { fixable: undefined }, create(context) { context.report({node, message, fix: foo}); } }; `, - errors: [INVALID_ERROR], + errors: [{ messageId: 'missing', type: 'Identifier' }], }, ], });