diff --git a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md index 140ade2dbe7..f0ef0872d4e 100644 --- a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md +++ b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md @@ -46,13 +46,15 @@ type Options = [ { ignoreConditionalTests?: boolean; ignoreMixedLogicalExpressions?: boolean; + forceSuggestionFixer?: boolean; }, ]; const defaultOptions = [ { ignoreConditionalTests: true, - ignoreMixedLogicalExpressions: true; + ignoreMixedLogicalExpressions: true, + forceSuggestionFixer: false, }, ]; ``` @@ -129,7 +131,13 @@ a ?? (b && c) ?? d; a ?? (b && c && d); ``` -**_NOTE:_** Errors for this specific case will be presented as suggestions, instead of fixes. This is because it is not always safe to automatically convert `||` to `??` within a mixed logical expression, as we cannot tell the intended precedence of the operator. Note that by design, `??` requires parentheses when used with `&&` or `||` in the same expression. +**_NOTE:_** Errors for this specific case will be presented as suggestions (see below), instead of fixes. This is because it is not always safe to automatically convert `||` to `??` within a mixed logical expression, as we cannot tell the intended precedence of the operator. Note that by design, `??` requires parentheses when used with `&&` or `||` in the same expression. + +### forceSuggestionFixer + +Setting this option to `true` will cause the rule to use ESLint's "suggested fix" mode for all fixes. _This option is provided as to aid in transitioning your codebase onto this rule_. + +Suggestion fixes cannot be automatically applied via the `--fix` CLI command, but can be _manually_ chosen to be applied one at a time via an IDE or similar. This makes it safe to run autofixers on an existing codebase without worrying about potential runtime behaviour changes from this rule's fixer. ## When Not To Use It diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index afb564025fd..649c8b3db21 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -11,6 +11,7 @@ export type Options = [ { ignoreConditionalTests?: boolean; ignoreMixedLogicalExpressions?: boolean; + forceSuggestionFixer?: boolean; }, ]; export type MessageIds = 'preferNullish'; @@ -41,6 +42,9 @@ export default util.createRule({ ignoreMixedLogicalExpressions: { type: 'boolean', }, + forceSuggestionFixer: { + type: 'boolean', + }, }, additionalProperties: false, }, @@ -50,9 +54,19 @@ export default util.createRule({ { ignoreConditionalTests: true, ignoreMixedLogicalExpressions: true, + forceSuggestionFixer: false, }, ], - create(context, [{ ignoreConditionalTests, ignoreMixedLogicalExpressions }]) { + create( + context, + [ + { + ignoreConditionalTests, + ignoreMixedLogicalExpressions, + forceSuggestionFixer, + }, + ], + ) { const parserServices = util.getParserServices(context); const sourceCode = context.getSourceCode(); const checker = parserServices.program.getTypeChecker(); @@ -79,30 +93,34 @@ export default util.createRule({ return; } - const barBarOperator = sourceCode.getTokenAfter( - node.left, - token => - token.type === AST_TOKEN_TYPES.Punctuator && - token.value === node.operator, - )!; // there _must_ be an operator - - const fixer = isMixedLogical - ? // suggestion instead for cases where we aren't sure if the fixer is completely safe - ({ - suggest: [ - { - messageId: 'preferNullish', - fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix { - return fixer.replaceText(barBarOperator, '??'); + const barBarOperator = util.nullThrows( + sourceCode.getTokenAfter( + node.left, + token => + token.type === AST_TOKEN_TYPES.Punctuator && + token.value === node.operator, + ), + util.NullThrowsReasons.MissingToken('operator', node.type), + ); + + const fixer = + isMixedLogical || forceSuggestionFixer + ? // suggestion instead for cases where we aren't sure if the fixer is completely safe + ({ + suggest: [ + { + messageId: 'preferNullish', + fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix { + return fixer.replaceText(barBarOperator, '??'); + }, }, + ], + } as const) + : { + fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix { + return fixer.replaceText(barBarOperator, '??'); }, - ], - } as const) - : { - fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix { - return fixer.replaceText(barBarOperator, '??'); - }, - }; + }; context.report({ node: barBarOperator, diff --git a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts index 1f05d6b9a3f..7dd9212770c 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -435,5 +435,33 @@ if (function werid() { return x ?? 'foo' }) {} }, ], })), + + // testing the suggestion fixer option + { + code: ` +declare const x: string | null; +x || 'foo'; + `.trimRight(), + output: null, + options: [{ forceSuggestionFixer: true }], + errors: [ + { + messageId: 'preferNullish', + line: 3, + column: 3, + endLine: 3, + endColumn: 5, + suggestions: [ + { + messageId: 'preferNullish', + output: ` +declare const x: string | null; +x ?? 'foo'; + `.trimRight(), + }, + ], + }, + ], + }, ], });