Skip to content

Commit

Permalink
Merge a325e56 into b5c0a12
Browse files Browse the repository at this point in the history
  • Loading branch information
jmoore914 committed Mar 16, 2020
2 parents b5c0a12 + a325e56 commit 59efd5b
Show file tree
Hide file tree
Showing 9 changed files with 575 additions and 25 deletions.
96 changes: 96 additions & 0 deletions docs/rules/prefer-ternary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Prefer ternary expressions over simple `if-else` statements

This rule enforces the use of ternary expressions over 'simple' `if-else` statements where 'simple' means the consequent and alternate are each one line and have the same basic type and form.

Using an `if-else` statement typically results in more lines of code than a single lined ternary expression, which leads to an unnecessarily larger codebase that is more difficult to maintain.

Additionally, using an `if-else` statement can result in defining variables using `let` or `var` solely to be reassigned within the blocks. This leads to varaibles being unnecessarily mutable and prevents `prefer-const` from flagging the variable.

This rule is fixable.


## Fail

```js
let foo =''
if(bar){
foo = 3
}
else{
foo = 4
}
```

```js
if(bar){
return 3
}
else{
return 4
}
```

```js
if(bar){
await firstPromise()
}
else{
await secondPromise()
}
```

```js
if(bar){
yield bat
}
else{
yield baz
}
```

## Pass

```js
let foo = bar ? 3 : 4
```

```js
return bar ? 3 : 4
```

```js
await (bar ? 3 : 4)
```

```js
yield (bar ? 3 : 4)
```

```js
let foo = ''
if(bar){
baz()
foo = 3
}
else{
foo = 4
}
```

```js
if(bar){
foo = 3
}
else{
return 4
}
```

```js
if(bar){
foo = 3
}
else{
baz = 4
}
```
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ module.exports = {
'unicorn/prefer-spread': 'error',
'unicorn/prefer-starts-ends-with': 'error',
'unicorn/prefer-string-slice': 'error',
'unicorn/prefer-ternary': 'error',
'unicorn/prefer-text-content': 'error',
'unicorn/prefer-trim-start-end': 'error',
'unicorn/prefer-type-error': 'error',
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Configure it in `package.json`.
"unicorn/prefer-spread": "error",
"unicorn/prefer-starts-ends-with": "error",
"unicorn/prefer-string-slice": "error",
"unicorn/prefer-ternary": "off",
"unicorn/prefer-text-content": "error",
"unicorn/prefer-trim-start-end": "error",
"unicorn/prefer-type-error": "error",
Expand Down Expand Up @@ -129,6 +130,7 @@ Configure it in `package.json`.
- [prefer-spread](docs/rules/prefer-spread.md) - Prefer the spread operator over `Array.from()`. *(fixable)*
- [prefer-starts-ends-with](docs/rules/prefer-starts-ends-with.md) - Prefer `String#startsWith()` & `String#endsWith()` over more complex alternatives.
- [prefer-string-slice](docs/rules/prefer-string-slice.md) - Prefer `String#slice()` over `String#substr()` and `String#substring()`. *(partly fixable)*
- [prefer-ternary](docs/rules/prefer-ternary.md) - Prefer ternary expressions over simple `if-else` statements. *(fixable)*
- [prefer-text-content](docs/rules/prefer-text-content.md) - Prefer `.textContent` over `.innerText`. *(fixable)*
- [prefer-trim-start-end](docs/rules/prefer-trim-start-end.md) - Prefer `String#trimStart()` / `String#trimEnd()` over `String#trimLeft()` / `String#trimRight()`. *(fixable)*
- [prefer-type-error](docs/rules/prefer-type-error.md) - Enforce throwing `TypeError` in type checking conditions. *(fixable)*
Expand Down
16 changes: 2 additions & 14 deletions rules/no-nested-ternary.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');

const isParenthesized = (sourceCode, node) => {
const previousToken = sourceCode.getTokenBefore(node);
const nextToken = sourceCode.getTokenAfter(node);

return (
Boolean(previousToken && nextToken) &&
previousToken.value === '(' &&
previousToken.end <= node.range[0] &&
nextToken.value === ')' &&
nextToken.start >= node.range[1]
);
};
const isParenthesized = require('./utils/is-parenthesized');

const create = context => {
const sourceCode = context.getSourceCode();
Expand All @@ -35,7 +23,7 @@ const create = context => {
) {
context.report({node, message});
break;
} else if (!isParenthesized(sourceCode, childNode)) {
} else if (!isParenthesized(childNode, sourceCode)) {
context.report({
node: childNode,
message,
Expand Down
8 changes: 3 additions & 5 deletions rules/prefer-string-slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,10 @@ const create = context => {
if (argumentNodes.length === 0) {
slice = [];
} else if (argumentNodes.length === 1) {
if (firstNumber !== undefined) {
slice = [Math.max(0, firstNumber)];
} else if (isLengthProperty(argumentNodes[0])) {
slice = [firstArgument];
if (firstNumber === undefined) {
slice = (isLengthProperty(argumentNodes[0])) ? [firstArgument] : [`Math.max(0, ${firstArgument})`];
} else {
slice = [`Math.max(0, ${firstArgument})`];
slice = [Math.max(0, firstNumber)];
}
} else if (argumentNodes.length === 2) {
const secondNumber = argumentNodes[1] ? getNumericValue(argumentNodes[1]) : undefined;
Expand Down
123 changes: 123 additions & 0 deletions rules/prefer-ternary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const isParenthesized = require('./utils/is-parenthesized');

const create = context => {
function checkIfStatement(node) {
if (isSingleBlockStatement(node) &&
isSameType(node) &&
checkConsequentAndAlternateType(node)) {
context.report({
node,
message: 'This `if` statement can be replaced by a ternary operator.',
fix(fixer) {
return fixFunction(node, fixer);
}
});
}
}

function fixFunction(node, fixer) {
const sourceCode = context.getSourceCode();
let prefix = '';
const ifCondition = sourceCode.getText(node.test);
let left = '';
let right = '';
if (getNodeBody(node.consequent).type === 'ExpressionStatement') {
const expressionType = getNodeBody(node.consequent).expression.type;
if (expressionType === 'AssignmentExpression') {
prefix = sourceCode.getText(getNodeBody(node.consequent).expression.left) + ' = ';
left = getNodeBody(node.consequent).expression.right;
right = getNodeBody(node.alternate).expression.right;
} else {
if (expressionType === 'AwaitExpression') {
prefix = 'await ';
} else {
prefix = getNodeBody(node.consequent).expression.delegate ? 'yield* ' : 'yield ';
}

left = getNodeBody(node.consequent).expression.argument;
right = getNodeBody(node.alternate).expression.argument;
}
} else {
prefix = 'return ';
left = getNodeBody(node.consequent).argument;
right = getNodeBody(node.alternate).argument;
}

let leftCode = sourceCode.getText(left);
if (isParenthesized(left, sourceCode)) {
leftCode = `(${leftCode})`;
}

let rightCode = sourceCode.getText(right);
if (isParenthesized(right, sourceCode)) {
rightCode = `(${rightCode})`;
}

const replacement = prefix + '(' + ifCondition + ' ? ' + leftCode + ' : ' + rightCode + ')';
return fixer.replaceText(node, replacement);
}

return {
IfStatement: checkIfStatement
};
};

function isSingleBlockStatement(node) {
return [node.consequent, node.alternate].every(node => {
return node && (node.type !== 'BlockStatement' || node.body.length === 1);
});
}

function getNodeBody(node) {
return node.type === 'BlockStatement' ? node.body[0] : node;
}

function isSameType(node) {
return getNodeBody(node.consequent).type === getNodeBody(node.alternate).type;
}

function checkConsequentAndAlternateType(node) {
return (
(getNodeBody(node.consequent).type === 'ReturnStatement' ||
(getNodeBody(node.consequent).type === 'ExpressionStatement' && checkConsequentAndAlternateExpressionStatement(node))));
}

function checkConsequentAndAlternateExpressionStatement(node) {
const consequentType = getNodeBody(node.consequent).expression.type;
return consequentType === getNodeBody(node.alternate).expression.type &&
(
consequentType === 'AwaitExpression' ||
(consequentType === 'YieldExpression' && compareYieldExpressions(node)) ||
(consequentType === 'AssignmentExpression' && compareConsequentAndAlternateAssignments(node))
) &&
checkNotAlreadyTernary(node);
}

function compareYieldExpressions(node) {
return getNodeBody(node.consequent).expression.delegate === getNodeBody(node.alternate).expression.delegate;
}

function compareConsequentAndAlternateAssignments(node) {
return getNodeBody(node.consequent).expression.left.name === getNodeBody(node.alternate).expression.left.name;
}

function checkNotAlreadyTernary(node) {
return getNodeBody(node.consequent).expression.type === 'AssignmentExpression' ?
getNodeBody(node.consequent).expression.right.type !== 'ConditionalExpression' &&
getNodeBody(node.alternate).expression.right.type !== 'ConditionalExpression' :
getNodeBody(node.consequent).expression.argument.type !== 'ConditionalExpression' &&
getNodeBody(node.alternate).expression.argument.type !== 'ConditionalExpression';
}

module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
10 changes: 4 additions & 6 deletions rules/string-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,16 @@ const create = context => {
}

const fixed = string.replace(replacement.regex, suggest);
if (type === 'Literal') {
problem.fix = fixer => fixer.replaceText(
problem.fix = type === 'Literal' ?
fixer => fixer.replaceText(
node,
quoteString(fixed, node.raw[0])
);
} else {
problem.fix = fixer => replaceTemplateElement(
) :
fixer => replaceTemplateElement(
fixer,
node,
escapeTemplateElementRaw(fixed)
);
}

context.report(problem);
}
Expand Down
16 changes: 16 additions & 0 deletions rules/utils/is-parenthesized.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict';

const isParenthesized = (node, sourceCode) => {
const previousToken = sourceCode.getTokenBefore(node);
const nextToken = sourceCode.getTokenAfter(node);

return (
Boolean(previousToken && nextToken) &&
previousToken.value === '(' &&
previousToken.end <= node.range[0] &&
nextToken.value === ')' &&
nextToken.start >= node.range[1]
);
};

module.exports = isParenthesized;

0 comments on commit 59efd5b

Please sign in to comment.