diff --git a/README.md b/README.md index 20a1a083..49125317 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 00000000..537bd745 --- /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 00000000..a12dd20a --- /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]), + ], + }) + ); + }, + }; + }, +}; 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 00000000..09d99099 --- /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', + }, + ], + }, + ], +});