diff --git a/docs/rules/prefer-ternary.md b/docs/rules/prefer-ternary.md index 67f916852b..e66bb90853 100644 --- a/docs/rules/prefer-ternary.md +++ b/docs/rules/prefer-ternary.md @@ -120,3 +120,26 @@ if (test) { baz = 2; } ``` + +## Options + +Type: `string`\ +Default: `'always'` + +- `'always'` (default) + - Always report when using an `IfStatement` where a ternary expression can be used. +- `'only-single-line'` + - Only check if the content of the `if` and/or `else` block is less than one line long. + +The following case is considered valid: + +```js +// eslint unicorn/prefer-ternary: ["error", "only-single-line"] +if (test) { + foo = [ + 'multiple line array' + ]; +} else { + bar = baz; +} +``` diff --git a/rules/prefer-ternary.js b/rules/prefer-ternary.js index 40524dbe6e..69b5641416 100644 --- a/rules/prefer-ternary.js +++ b/rules/prefer-ternary.js @@ -56,6 +56,7 @@ const getScopes = scope => [ ]; const create = context => { + const onlySingleLine = context.options[0] === 'only-single-line'; const sourceCode = context.getSourceCode(); const scopeToNamesGeneratedByFixer = new WeakMap(); const isSafeName = (name, scopes) => scopes.every(scope => { @@ -76,6 +77,11 @@ const create = context => { `(${text})` : text; }; + const isSingleLineNode = node => { + const [start, end] = node.range.map(index => sourceCode.getLocFromIndex(index)); + return start.line === end.line; + }; + function merge(options, mergeOptions) { const { before = '', @@ -189,6 +195,13 @@ const create = context => { const consequent = getNodeBody(node.consequent); const alternate = getNodeBody(node.alternate); + if ( + onlySingleLine && + [consequent, alternate, node.test].some(node => !isSingleLineNode(node)) + ) { + return; + } + const result = merge({node, consequent, alternate}, { checkThrowStatement: true, returnFalseIfNotMergeable: true @@ -199,7 +212,6 @@ const create = context => { } const scope = context.getScope(); - const sourceCode = context.getSourceCode(); context.report({ node, @@ -252,6 +264,13 @@ const create = context => { }; }; +const schema = [ + { + enum: ['always', 'only-single-line'], + default: 'always' + } +]; + module.exports = { create, meta: { @@ -262,6 +281,7 @@ module.exports = { messages: { [messageId]: 'This `if` statement can be replaced by a ternary expression.' }, + schema, fixable: 'code' } }; diff --git a/test/prefer-ternary.js b/test/prefer-ternary.js index c120340c3e..54c352d923 100644 --- a/test/prefer-ternary.js +++ b/test/prefer-ternary.js @@ -4,6 +4,8 @@ import {test} from './utils/test.js'; const messageId = 'prefer-ternary'; const errors = [{messageId}]; +const onlySingleLineOptions = ['only-single-line']; + // ReturnStatement test({ valid: [ @@ -957,6 +959,134 @@ test({ ] }); +// `only-single-line` +test({ + valid: [ + { + code: outdent` + if (test) { + a = { + multiline: 'in consequent' + }; + } else{ + a = foo; + } + `, + options: onlySingleLineOptions + }, + { + code: outdent` + if (test) { + a = foo; + } else{ + a = { + multiline: 'in alternate' + }; + } + `, + options: onlySingleLineOptions + }, + { + code: outdent` + if ( + test({ + multiline: 'in test' + }) + ) { + a = foo; + } else{ + a = bar; + } + `, + options: onlySingleLineOptions + }, + { + code: outdent` + if (test) { + a = foo; b = 1; + } else{ + a = bar; + } + `, + options: onlySingleLineOptions + } + ], + invalid: [ + { + code: outdent` + if (test) { + a = foo; + } else { + a = bar; + } + `, + output: 'a = test ? foo : bar;', + options: onlySingleLineOptions, + errors + }, + // Parentheses are not considered part of `Node` + { + code: outdent` + if ( + ( + test + ) + ) { + a = foo; + } else { + a = bar; + } + `, + output: 'a = (test) ? foo : bar;', + options: onlySingleLineOptions, + errors + }, + { + code: outdent` + if (test) { + ( + a = foo + ); + } else { + a = bar; + } + `, + output: 'a = test ? foo : bar;', + options: onlySingleLineOptions, + errors + }, + // Semicolon of `ExpressionStatement` is not considered part of `Node` + { + code: outdent` + if (test) { + a = foo + ; + } else { + a = bar; + } + `, + output: 'a = test ? foo : bar;', + options: onlySingleLineOptions, + errors + }, + // `EmptyStatement`s are excluded + { + code: outdent` + if (test) { + ;;;;;; + a = foo; + ;;;;;; + } else { + a = bar; + } + `, + output: 'a = test ? foo : bar;', + options: onlySingleLineOptions, + errors + } + ] +}); + test({ valid: [ // No `consequent` / `alternate`