diff --git a/packages/eslint-plugin/docs/rules/prefer-optional-chain.md b/packages/eslint-plugin/docs/rules/prefer-optional-chain.md index 9a046b3d82f..be3352a282b 100644 --- a/packages/eslint-plugin/docs/rules/prefer-optional-chain.md +++ b/packages/eslint-plugin/docs/rules/prefer-optional-chain.md @@ -70,6 +70,21 @@ foo?.a?.b?.method?.(); foo?.a?.b?.c?.d?.e; ``` +## Options + +The rule accepts an options object with the following properties: + +```ts +type Options = { + // if true, the rule will only provide suggested fixes instead of automatically modifying code + suggestInsteadOfAutofix?: boolean; +}; + +const defaults = { + suggestInsteadOfAutofix: false, +}; +``` + ## When Not To Use It If you are not using TypeScript 3.7 (or greater), then you will not be able to use this rule, as the operator is not supported. diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts index 0ec33df8180..7c858008984 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts @@ -1,6 +1,7 @@ import { AST_NODE_TYPES, TSESTree, + TSESLint, } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; @@ -29,7 +30,16 @@ The AST will look like this: right: foo.bar.baz.buzz } */ -export default util.createRule({ + +type Options = [ + { + suggestInsteadOfAutofix?: boolean; + }, +]; + +type MessageIds = 'preferOptionalChain' | 'optionalChainSuggest'; + +export default util.createRule({ name: 'prefer-optional-chain', meta: { type: 'suggestion', @@ -43,11 +53,26 @@ export default util.createRule({ messages: { preferOptionalChain: "Prefer using an optional chain expression instead, as it's more concise and easier to read.", + optionalChainSuggest: 'Change to an optional chain.', }, - schema: [], + schema: [ + { + type: 'object', + properties: { + suggestInsteadOfAutofix: { + type: 'boolean', + }, + }, + additionalProperties: false, + }, + ], }, - defaultOptions: [], - create(context) { + defaultOptions: [ + { + suggestInsteadOfAutofix: false, + }, + ], + create(context, [options]) { const sourceCode = context.getSourceCode(); return { [[ @@ -163,13 +188,28 @@ export default util.createRule({ } ${sourceCode.getText(previous.right.right)}`; } - context.report({ - node: previous, - messageId: 'preferOptionalChain', - fix(fixer) { - return fixer.replaceText(previous, optionallyChainedCode); - }, - }); + if (!options.suggestInsteadOfAutofix) { + context.report({ + node: previous, + messageId: 'preferOptionalChain', + fix(fixer) { + return fixer.replaceText(previous, optionallyChainedCode); + }, + }); + } else { + context.report({ + node: previous, + messageId: 'preferOptionalChain', + suggest: [ + { + messageId: 'optionalChainSuggest', + fix: (fixer): TSESLint.RuleFix[] => [ + fixer.replaceText(previous, optionallyChainedCode), + ], + }, + ], + }); + } } }, }; diff --git a/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts b/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts index eed61d687e4..c6eb323827a 100644 --- a/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts @@ -291,5 +291,56 @@ ruleTester.run('prefer-optional-chain', rule, { }, ], }, + // using suggestion instead of autofix + { + code: + 'foo && foo.bar != null && foo.bar.baz !== undefined && foo.bar.baz.buzz;', + options: [ + { + suggestInsteadOfAutofix: true, + }, + ], + output: null, + errors: [ + { + messageId: 'preferOptionalChain', + line: 1, + column: 1, + suggestions: [ + { + messageId: 'optionalChainSuggest', + output: 'foo?.bar?.baz?.buzz;', + }, + ], + }, + ], + }, + { + code: 'foo && foo.bar(baz => );', + options: [ + { + suggestInsteadOfAutofix: true, + }, + ], + output: null, + errors: [ + { + messageId: 'preferOptionalChain', + line: 1, + column: 1, + suggestions: [ + { + messageId: 'optionalChainSuggest', + output: 'foo?.bar(baz => );', + }, + ], + }, + ], + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, ], });