Repository: not-an-aardvark/eslint-plugin-eslint-plugin (js)

Issue:
Rule: Always return from a fixer function
```js
/* eslint eslint-plugin/fixer-return: error */

// invalid:

context.report({
  node,
  message,
  fix(fixer) {
    fixer.replaceText(node, "foo");
  }
});

// valid:

context.report({
  node,
  message,
  fix(fixer) {
    return fixer.replaceText(node, "foo");
  }
});

// valid:

context.report({
  node,
  message,
  fix: fixer => fixer.replaceText(node, "foo")
});
```

Pull request description:
New: fixer-return
fixes #3

Patch:
diff --git a/README.md b/README.md
index 424d486..9398525 100644
--- a/README.md
+++ b/README.md
@@ -49,10 +49,12 @@ Then configure the rules you want to use under the rules section.
 Name | ✔️ | 🛠 | Description
 ----- | ----- | ----- | -----
 [consistent-output](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/consistent-output.md) | | | Enforces consistent use of output assertions in rule tests
+[fixer-return](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/fixer-return.md) | | | Enforces always return from a fixer function
 [no-deprecated-report-api](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-deprecated-report-api.md) | ✔️ | 🛠 | Prohibits the deprecated `context.report(node, message)` API
 [no-identical-tests](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-identical-tests.md) | | 🛠 | Disallows identical tests
 [no-missing-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-missing-placeholders.md) | ✔️ | | Disallows missing 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) | ✔️ | 🛠 | Disallows unnecessary calls to sourceCode.getFirstToken and sourceCode.getLastToken
+[prefer-output-null](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-output-null.md) | | | Disallows 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) | | | Disallows template literals as report messages
 [report-message-format](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/report-message-format.md) | | | Enforces a consistent format for report messages
 [require-meta-fixable](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-fixable.md) | ✔️ | | Requires a `meta.fixable` property for fixable rules
diff --git a/docs/rules/fixer-return.md b/docs/rules/fixer-return.md
new file mode 100644
index 0000000..edb0b26
--- /dev/null
+++ b/docs/rules/fixer-return.md
@@ -0,0 +1,41 @@
+# Enforces always return from a fixer function (fixer-return)
+
+In a fixable rule, missing return from a fixer function will not apply fixes.
+
+## Rule Details
+
+This rule enforces that fixer functions always return a value.
+
+Examples of **incorrect** code for this rule:
+
+```js
+/* eslint eslint-plugin/fixer-return: error */
+module.exports = {
+    create: function(context) {
+        context.report( {
+            fix: function(fixer) {
+                fixer.foo();
+            }
+        });
+    }
+};
+```
+
+Examples of **correct** code for this rule:
+
+```js
+/* eslint eslint-plugin/fixer-return: error */
+module.exports = {
+    create: function(context) {
+        context.report( {
+            fix: function(fixer) {
+                return fixer.foo();
+            }
+        });
+    }
+};
+```
+
+## When Not To Use It
+
+If you don't want to enforce always return from a fixer function, do not enable this rule.
diff --git a/docs/rules/prefer-output-null.md b/docs/rules/prefer-output-null.md
new file mode 100644
index 0000000..3a95b52
--- /dev/null
+++ b/docs/rules/prefer-output-null.md
@@ -0,0 +1,31 @@
+# Disallows invalid RuleTester test cases with the output the same as the code. (prefer-output-null)
+
+## Rule Details
+
+The rule reports an error if it encounters a test case where the output is the same as the code.
+
+Examples of **incorrect** code for this rule:
+
+```js
+/* eslint eslint-plugin/prefer-output-null: error */
+
+new RuleTester().run('foo', bar, {
+    valid: [],
+    invalid: [
+      { code: 'foo', output: 'foo', errors: [{ message: 'bar' }] },
+    ]
+});
+```
+
+Examples of **correct** code for this rule:
+
+```js
+/* eslint eslint-plugin/prefer-output-null: error */
+
+new RuleTester().run('foo', bar, {
+    valid: [],
+    invalid: [
+      { code: 'foo', output: null, errors: [{ message: 'bar' }] },
+    ]
+});
+```
diff --git a/lib/rules/fixer-return.js b/lib/rules/fixer-return.js
new file mode 100644
index 0000000..e4b1ca6
--- /dev/null
+++ b/lib/rules/fixer-return.js
@@ -0,0 +1,106 @@
+/**
+ * @fileoverview Enforces always return from a fixer function
+ * @author 薛定谔的猫<hh_2013@foxmail.com>
+ */
+
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Expected fixer function to always return a value.',
+      category: 'Possible Errors',
+      recommended: false,
+    },
+    fixable: null,
+  },
+
+  create (context) {
+    const message = 'Expected fixer function to always return a value.';
+    let funcInfo = {
+      upper: null,
+      codePath: null,
+      hasReturn: false,
+      shouldCheck: false,
+      node: null,
+    };
+    let contextIdentifiers;
+
+    /**
+     * Checks whether or not the last code path segment is reachable.
+     * Then reports this function if the segment is reachable.
+     *
+     * If the last code path segment is reachable, there are paths which are not
+     * returned or thrown.
+     *
+     * @param {ASTNode} node - A node to check.
+     * @returns {void}
+     */
+    function checkLastSegment (node) {
+      if (funcInfo.shouldCheck && funcInfo.codePath.currentSegments.some(segment => segment.reachable)) {
+        context.report({
+          node,
+          loc: (node.id || node).loc.start,
+          message,
+        });
+      }
+    }
+
+    return {
+      Program (node) {
+        contextIdentifiers = utils.getContextIdentifiers(context, node);
+      },
+
+      // Stacks this function's information.
+      onCodePathStart (codePath, node) {
+        const parent = node.parent;
+        const shouldCheck = node.type === 'FunctionExpression' &&
+          parent.parent.type === 'ObjectExpression' &&
+          parent.parent.parent.type === 'CallExpression' &&
+          contextIdentifiers.has(parent.parent.parent.callee.object) &&
+          parent.parent.parent.callee.property.name === 'report' &&
+          utils.getReportInfo(parent.parent.parent.arguments).fix === node;
+
+        funcInfo = {
+          upper: funcInfo,
+          codePath,
+          hasReturn: false,
+          shouldCheck,
+          node,
+        };
+      },
+
+      // Pops this function's information.
+      onCodePathEnd () {
+        funcInfo = funcInfo.upper;
+      },
+
+      // Checks the return statement is valid.
+      ReturnStatement (node) {
+        if (funcInfo.shouldCheck) {
+          funcInfo.hasReturn = true;
+
+          if (!node.argument) {
+            context.report({
+              node,
+              message,
+            });
+          }
+        }
+      },
+
+      // Reports a given function if the last path is reachable.
+      'FunctionExpression:exit': checkLastSegment,
+    };
+  },
+};
diff --git a/lib/rules/prefer-output-null.js b/lib/rules/prefer-output-null.js
new file mode 100644
index 0000000..9f31645
--- /dev/null
+++ b/lib/rules/prefer-output-null.js
@@ -0,0 +1,61 @@
+/**
+ * @fileoverview disallows invalid RuleTester test cases with the output the same as the code.
+ * @author 薛定谔的猫<hh_2013@foxmail.com>
+ */
+
+'use strict';
+
+const utils = require('../utils');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    docs: {
+      description: 'disallows invalid RuleTester test cases with the output the same as the code.',
+      category: 'Tests',
+      recommended: false,
+    },
+    fixable: null,
+    schema: [],
+  },
+
+  create (context) {
+    // ----------------------------------------------------------------------
+    // Public
+    // ----------------------------------------------------------------------
+
+    const message = 'Use `output: null` to assert that a test case is not autofixed.';
+    const sourceCode = context.getSourceCode();
+
+    return {
+      Program (ast) {
+        utils.getTestInfo(context, ast).forEach(testRun => {
+          (testRun.invalid || []).forEach(test => {
+            /**
+            * Get a test case's giving keyname node.
+            * @param {string} the keyname to find.
+            * @returns {Node} found node; if not found, return null;
+            */
+            function getTestInfo (key) {
+              const res = test.properties.filter(item => item.key.name === key);
+              return res.length ? res[res.length - 1] : null;
+            }
+
+            const code = getTestInfo('code');
+            const output = getTestInfo('output');
+
+            if (output && sourceCode.getText(output.value) === sourceCode.getText(code.value)) {
+              context.report({
+                node: test,
+                message,
+              });
+            }
+          });
+        });
+      },
+    };
+  },
+};


Test patch:
diff --git a/tests/lib/rules/fixer-return.js b/tests/lib/rules/fixer-return.js
new file mode 100644
index 0000000..23ee5ca
--- /dev/null
+++ b/tests/lib/rules/fixer-return.js
@@ -0,0 +1,62 @@
+/**
+ * @fileoverview enforces always return from a fixer function
+ * @author 薛定谔的猫<hh_2013@foxmail.com>
+ */
+
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/fixer-return');
+const RuleTester = require('eslint').RuleTester;
+
+const ERROR = { message: 'Expected fixer function to always return a value.' };
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+ruleTester.run('fixer-return', rule, {
+  valid: [
+    `
+    module.exports = {
+        create: function(context) {
+            context.report( {
+                fix: function(fixer) {
+                    return fixer.foo();
+                }
+            });
+        }
+    };
+    `,
+    `
+    module.exports = {
+        create: function(context) {
+            context.report({
+                fix: fixer => fixer.foo()
+            });
+        }
+    };
+    `,
+  ],
+
+  invalid: [
+    {
+      code: `
+      module.exports = {
+          create: function(context) {
+              context.report({
+                  fix(fixer) {
+                      fixer.foo();
+                  }
+              });
+          }
+      };
+      `,
+      errors: [ERROR],
+    },
+  ],
+});
diff --git a/tests/lib/rules/no-deprecated-report-api.js b/tests/lib/rules/no-deprecated-report-api.js
index f0eeafe..e5e3559 100644
--- a/tests/lib/rules/no-deprecated-report-api.js
+++ b/tests/lib/rules/no-deprecated-report-api.js
@@ -124,13 +124,7 @@ ruleTester.run('no-deprecated-report-api', rule, {
           }
         };
       `,
-      output: `
-        module.exports = {
-          create(context) {
-            context.report(theNode, theMessage, theData, theFix);
-          }
-        };
-      `,
+      output: null,
       errors: [ERROR],
     },
     {
@@ -175,13 +169,7 @@ ruleTester.run('no-deprecated-report-api', rule, {
           }
         };
       `,
-      output: `
-        module.exports = {
-          create(notContext) {
-            notContext.report(theNode, theMessage, theData, theFix);
-          }
-        };
-      `,
+      output: null,
       errors: [ERROR],
     },
     {
@@ -218,13 +206,7 @@ ruleTester.run('no-deprecated-report-api', rule, {
           }
         };
       `,
-      output: `
-        module.exports = {
-          create(context) {
-            context.report(theNode, theLocation, theMessage, theData, theFix, somethingElse, somethingElse, somethingElse);
-          }
-        };
-      `,
+      output: null,
       errors: [ERROR],
     },
   ],
diff --git a/tests/lib/rules/prefer-output-null.js b/tests/lib/rules/prefer-output-null.js
new file mode 100644
index 0000000..24ee3ea
--- /dev/null
+++ b/tests/lib/rules/prefer-output-null.js
@@ -0,0 +1,55 @@
+/**
+ * @fileoverview disallows invalid RuleTester test cases with the output the same as the code.
+ * @author 薛定谔的猫<hh_2013@foxmail.com>
+ */
+
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/prefer-output-null');
+const RuleTester = require('eslint').RuleTester;
+
+const ERROR = { message: 'Use `output: null` to assert that a test case is not autofixed.' };
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester();
+ruleTester.run('prefer-output-null', rule, {
+  valid: [
+    `
+      new RuleTester().run('foo', bar, {
+        valid: [],
+        invalid: [
+          {code: 'foo', output: 'bar', errors: ['bar']},
+        ]
+      });
+    `,
+    `
+      new RuleTester().run('foo', bar, {
+        valid: [],
+        invalid: [
+          {code: 'foo', output: null, errors: ['bar']},
+        ]
+      });
+    `,
+  ],
+
+  invalid: [
+    {
+      code: `
+        new RuleTester().run('foo', bar, {
+          valid: [],
+          invalid: [
+            {code: 'foo', output: 'foo', errors: ['bar']},
+          ]
+        });
+      `,
+      errors: [ERROR],
+    },
+  ],
+});