diff --git a/packages/eslint-plugin/docs/rules/prefer-optional-chain.md b/packages/eslint-plugin/docs/rules/prefer-optional-chain.md index 9a046b3d82f8..be3352a282bb 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 0ec33df81801..7c8580089849 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 eed61d687e42..c6eb323827a5 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, + }, + }, + }, ], });