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

Issue:
Rule: avoid deprecated `context` passthroughs
Disallows rules from using the following deprecated methods on `context`:

* `getSource`
* `getSourceLines`
* `getAllComments`
* `getNodeByRangeIndex`
* `getComments`
* `getCommentsBefore`
* `getCommentsAfter`
* `getCommentsInside`
* `getJSDocComment`
* `getFirstToken`
* `getFirstTokens`
* `getLastToken`
* `getLastTokens`
* `getTokenAfter`
* `getTokenBefore`
* `getTokenByRangeStart`
* `getTokens`
* `getTokensAfter`
* `getTokensBefore`
* `getTokensBetween`

Pull request:
New: no-deprecated-context-methods rule (fixes #40)

Patch:
diff --git a/README.md b/README.md
index 20a1a08..4912531 100644
--- a/README.md
+++ b/README.md
@@ -50,6 +50,7 @@ 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-context-methods](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-deprecated-context-methods.md) | | | Disallows usage of deprecated methods on rule context objects
 [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
diff --git a/docs/rules/no-deprecated-context-methods.md b/docs/rules/no-deprecated-context-methods.md
new file mode 100644
index 0000000..537bd74
--- /dev/null
+++ b/docs/rules/no-deprecated-context-methods.md
@@ -0,0 +1,68 @@
+# Disallows usage of deprecated methods on rule context objects (no-deprecated-context-methods)
+
+This rule disallows the use of deprecated methods on rule `context` objects.
+
+The deprecated methods are:
+
+* `getSource`
+* `getSourceLines`
+* `getAllComments`
+* `getNodeByRangeIndex`
+* `getComments`
+* `getCommentsBefore`
+* `getCommentsAfter`
+* `getCommentsInside`
+* `getJSDocComment`
+* `getFirstToken`
+* `getFirstTokens`
+* `getLastToken`
+* `getLastTokens`
+* `getTokenAfter`
+* `getTokenBefore`
+* `getTokenByRangeStart`
+* `getTokens`
+* `getTokensAfter`
+* `getTokensBefore`
+* `getTokensBetween`
+
+Instead of using these methods, you should use the equivalent methods on [`SourceCode`](https://eslint.org/docs/developer-guide/working-with-rules#contextgetsourcecode), e.g. `context.getSourceCode().getText()` instead of `context.getSource()`.
+
+## Rule Details
+
+Examples of **incorrect** code for this rule:
+
+```js
+module.exports = {
+  create(context) {
+    return {
+      Program(node) {
+        const firstToken = context.getFirstToken(node);
+      }
+    }
+  }
+}
+```
+
+Examples of **correct** code for this rule:
+
+```js
+module.exports = {
+  create(context) {
+    const sourceCode = context.getSourceCode();
+
+    return {
+      Program(node) {
+        const firstToken = sourceCode.getFirstToken(node);
+      }
+    }
+  }
+};
+```
+
+## When Not To Use It
+
+If you need to support very old versions of ESLint where `SourceCode` doesn't exist, you should not enable this rule.
+
+## Further Reading
+
+* [`SourceCode` API](https://eslint.org/docs/developer-guide/working-with-rules#contextgetsourcecode)
diff --git a/lib/rules/no-deprecated-context-methods.js b/lib/rules/no-deprecated-context-methods.js
new file mode 100644
index 0000000..a12dd20
--- /dev/null
+++ b/lib/rules/no-deprecated-context-methods.js
@@ -0,0 +1,83 @@
+/**
+ * @fileoverview Disallows usage of deprecated methods on rule context objects
+ * @author Teddy Katz
+ */
+
+'use strict';
+
+const utils = require('../utils');
+
+const DEPRECATED_PASSTHROUGHS = {
+  getSource: 'getText',
+  getSourceLines: 'getLines',
+  getAllComments: 'getAllComments',
+  getNodeByRangeIndex: 'getNodeByRangeIndex',
+  getComments: 'getComments',
+  getCommentsBefore: 'getCommentsBefore',
+  getCommentsAfter: 'getCommentsAfter',
+  getCommentsInside: 'getCommentsInside',
+  getJSDocComment: 'getJSDocComment',
+  getFirstToken: 'getFirstToken',
+  getFirstTokens: 'getFirstTokens',
+  getLastToken: 'getLastToken',
+  getLastTokens: 'getLastTokens',
+  getTokenAfter: 'getTokenAfter',
+  getTokenBefore: 'getTokenBefore',
+  getTokenByRangeStart: 'getTokenByRangeStart',
+  getTokens: 'getTokens',
+  getTokensAfter: 'getTokensAfter',
+  getTokensBefore: 'getTokensBefore',
+  getTokensBetween: 'getTokensBetween',
+};
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallows usage of deprecated methods on rule context objects',
+      category: 'Rules',
+      recommended: false,
+    },
+    fixable: 'code',
+    schema: [],
+  },
+
+  create (context) {
+    const sourceCode = context.getSourceCode();
+
+    // ----------------------------------------------------------------------
+    // Public
+    // ----------------------------------------------------------------------
+
+    return {
+      'Program:exit' () {
+        Array.from(utils.getContextIdentifiers(context, sourceCode.ast))
+          .filter(
+            contextId =>
+              contextId.parent.type === 'MemberExpression' &&
+              contextId === contextId.parent.object &&
+              contextId.parent.property.type === 'Identifier' &&
+              Object.prototype.hasOwnProperty.call(DEPRECATED_PASSTHROUGHS, contextId.parent.property.name)
+          ).forEach(
+            contextId =>
+              context.report({
+                node: contextId.parent,
+                message: 'Use `{{contextName}}.getSourceCode().{{replacement}}` instead of `{{contextName}}.{{original}}`.',
+                data: {
+                  contextName: contextId.name,
+                  original: contextId.parent.property.name,
+                  replacement: DEPRECATED_PASSTHROUGHS[contextId.parent.property.name],
+                },
+                fix: fixer => [
+                  fixer.insertTextAfter(contextId, '.getSourceCode()'),
+                  fixer.replaceText(contextId.parent.property, DEPRECATED_PASSTHROUGHS[contextId.parent.property.name]),
+                ],
+              })
+          );
+      },
+    };
+  },
+};


Test patch:
diff --git a/tests/lib/rules/no-deprecated-context-methods.js b/tests/lib/rules/no-deprecated-context-methods.js
new file mode 100644
index 0000000..09d9909
--- /dev/null
+++ b/tests/lib/rules/no-deprecated-context-methods.js
@@ -0,0 +1,89 @@
+/**
+ * @fileoverview Disallows usage of deprecated methods on rule context objects
+ * @author Teddy Katz
+ */
+
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-deprecated-context-methods');
+const RuleTester = require('eslint').RuleTester;
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+ruleTester.run('no-deprecated-context-methods', rule, {
+
+  valid: [
+    `
+      module.exports = {
+        create(context) {
+          context.getSourceCode();
+        }
+      }
+    `,
+    `
+    module.exports = context => {
+      const sourceCode = context.getSourceCode();
+
+      sourceCode.getFirstToken();
+    }
+  `,
+  ],
+
+  invalid: [
+    {
+      code: `
+        module.exports = {
+          create(context) {
+            return {
+              Program(node) {
+                context.getSource(node);
+              }
+            }
+          }
+        }
+      `,
+      output: `
+        module.exports = {
+          create(context) {
+            return {
+              Program(node) {
+                context.getSourceCode().getText(node);
+              }
+            }
+          }
+        }
+      `,
+      errors: [
+        {
+          message: 'Use `context.getSourceCode().getText` instead of `context.getSource`.',
+          type: 'MemberExpression',
+        },
+      ],
+    },
+    {
+      code: `
+        module.exports = myRuleContext => {
+          myRuleContext.getFirstToken;
+        }
+      `,
+      output: `
+        module.exports = myRuleContext => {
+          myRuleContext.getSourceCode().getFirstToken;
+        }
+      `,
+      errors: [
+        {
+          message: 'Use `myRuleContext.getSourceCode().getFirstToken` instead of `myRuleContext.getFirstToken`.',
+          type: 'MemberExpression',
+        },
+      ],
+    },
+  ],
+});