From 8f9b2da1e84bdb1ec81d90b1827086accc3424e0 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Fri, 19 Jun 2020 09:51:39 -0700 Subject: [PATCH] Add rule `prefer-object-rule` --- README.md | 1 + docs/rules/prefer-object-rule.md | 49 ++++++++++++ lib/rules/prefer-object-rule.js | 74 +++++++++++++++++ tests/lib/rules/prefer-object-rule.js | 109 ++++++++++++++++++++++++++ 4 files changed, 233 insertions(+) create mode 100644 docs/rules/prefer-object-rule.md create mode 100644 lib/rules/prefer-object-rule.js create mode 100644 tests/lib/rules/prefer-object-rule.js diff --git a/README.md b/README.md index c1eb4711..1e4ae467 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Name | ✔️ | 🛠 | Description [no-missing-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-missing-placeholders.md) | ✔️ | | disallow missing placeholders in rule report messages [no-unused-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-unused-placeholders.md) | ✔️ | | disallow unused placeholders in rule report messages [no-useless-token-range](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-useless-token-range.md) | ✔️ | 🛠 | disallow unnecessary calls to sourceCode.getFirstToken and sourceCode.getLastToken +[prefer-object-rule](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-object-rule.md) | | 🛠 | disallow rule exports where the export is a function. [prefer-output-null](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-output-null.md) | | 🛠 | disallow invalid RuleTester test cases with the output the same as the code. [prefer-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-placeholders.md) | | | disallow template literals as report messages [prefer-replace-text](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-replace-text.md) | | | require using replaceText instead of replaceTextRange. diff --git a/docs/rules/prefer-object-rule.md b/docs/rules/prefer-object-rule.md new file mode 100644 index 00000000..5ddd6def --- /dev/null +++ b/docs/rules/prefer-object-rule.md @@ -0,0 +1,49 @@ +# Disallow rule exports where the export is a function. (prefer-object-rule) + +(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixes problems reported by this rule. + +## Rule Details + +The rule reports an error if it encounters a rule that's defined using the old style of just a `create` function. + +Examples of **incorrect** code for this rule: + +```js +/* eslint eslint-plugin/prefer-object-rule: error */ + +module.exports = function (context) { + return { Program() { context.report() } }; +}; + +module.exports = function create(context) { + return { Program() { context.report() } }; +}; + +module.exports = (context) => { + return { Program() { context.report() } }; +}; +``` + +Examples of **correct** code for this rule: + +```js +/* eslint eslint-plugin/prefer-object-rule: error */ + +module.exports = { + create(context) { + return { Program() { context.report() } }; + }, +}; + +module.exports = { + create(context) { + return { Program() { context.report() } }; + }, +}; + +module.exports = { + create: (context) => { + return { Program() { context.report() } }; + }, +}; +``` diff --git a/lib/rules/prefer-object-rule.js b/lib/rules/prefer-object-rule.js new file mode 100644 index 00000000..4cd781a6 --- /dev/null +++ b/lib/rules/prefer-object-rule.js @@ -0,0 +1,74 @@ +/** + * @author Brad Zacher + */ + +'use strict'; + +const utils = require('../utils'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'disallow rule exports where the export is a function.', + category: 'Rules', + recommended: false, + }, + messages: { + preferObject: 'Rules should be declared using the object style.', + }, + type: 'suggestion', + fixable: 'code', + schema: [], + }, + + create (context) { + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + const sourceCode = context.getSourceCode(); + const ruleInfo = utils.getRuleInfo(sourceCode.ast); + + return { + Program () { + if (!ruleInfo || ruleInfo.isNewStyle) { + return; + } + + context.report({ + node: ruleInfo.create, + messageId: 'preferObject', + *fix (fixer) { + // note - we intentionally don't worry about formatting here, as otherwise we have + // to indent the function correctly + + if (ruleInfo.create.type === 'FunctionExpression') { + const openParenToken = sourceCode.getFirstToken( + ruleInfo.create, + token => token.type === 'Punctuator' && token.value === '(' + ); + + if (!openParenToken) { + // this shouldn't happen, but guarding against crashes just in case + return null; + } + + yield fixer.replaceTextRange( + [ruleInfo.create.range[0], openParenToken.range[0]], + '{create' + ); + yield fixer.insertTextAfter(ruleInfo.create, '}'); + } else if (ruleInfo.create.type === 'ArrowFunctionExpression') { + yield fixer.insertTextBefore(ruleInfo.create, '{create: '); + yield fixer.insertTextAfter(ruleInfo.create, '}'); + } + }, + }); + }, + }; + }, +}; diff --git a/tests/lib/rules/prefer-object-rule.js b/tests/lib/rules/prefer-object-rule.js new file mode 100644 index 00000000..1e48ec2d --- /dev/null +++ b/tests/lib/rules/prefer-object-rule.js @@ -0,0 +1,109 @@ +/** + * @author Brad Zacher + */ + +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/prefer-object-rule'); +const RuleTester = require('eslint').RuleTester; + +const ERROR = { messageId: 'preferObject', line: 2, column: 26 }; + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); +ruleTester.run('prefer-object-rule', rule, { + valid: [ + ` + module.exports = { + create(context) { + return { Program() { context.report() } }; + }, + }; + `, + ` + module.exports = { + create: (context) => { + return { Program() { context.report() } }; + }, + }; + `, + ` + module.exports.create = (context) => { + return { Program() { context.report() } }; + }; + `, + ` + module.exports.create = function (context) { + return { Program() { context.report() } }; + }; + `, + ` + module.exports.create = function create(context) { + return { Program() { context.report() } }; + }; + `, + ` + function create(context) { + return { Program() { context.report() } }; + }; + module.exports.create = create; + `, + ` + const rule = { + create(context) { + return { Program() { context.report() } }; + }, + }; + module.exports = rule; + `, + ], + + invalid: [ + { + code: ` + module.exports = function (context) { + return { Program() { context.report() } }; + }; + `, + output: ` + module.exports = {create(context) { + return { Program() { context.report() } }; + }}; + `, + errors: [ERROR], + }, + { + code: ` + module.exports = function create(context) { + return { Program() { context.report() } }; + }; + `, + output: ` + module.exports = {create(context) { + return { Program() { context.report() } }; + }}; + `, + errors: [ERROR], + }, + { + code: ` + module.exports = (context) => { + return { Program() { context.report() } }; + }; + `, + output: ` + module.exports = {create: (context) => { + return { Program() { context.report() } }; + }}; + `, + errors: [ERROR], + }, + ], +});