160 lines
3.7 KiB
JavaScript
160 lines
3.7 KiB
JavaScript
'use strict';
|
|
const {isParenthesized, getParenthesizedText} = require('./utils/parentheses.js');
|
|
const isSameReference = require('./utils/is-same-reference.js');
|
|
const shouldAddParenthesesToLogicalExpressionChild = require('./utils/should-add-parentheses-to-logical-expression-child.js');
|
|
const needsSemicolon = require('./utils/needs-semicolon.js');
|
|
|
|
const MESSAGE_ID_ERROR = 'prefer-logical-operator-over-ternary/error';
|
|
const MESSAGE_ID_SUGGESTION = 'prefer-logical-operator-over-ternary/suggestion';
|
|
const messages = {
|
|
[MESSAGE_ID_ERROR]: 'Prefer using a logical operator over a ternary.',
|
|
[MESSAGE_ID_SUGGESTION]: 'Switch to `{{operator}}` operator.',
|
|
};
|
|
|
|
function isSameNode(left, right, sourceCode) {
|
|
if (isSameReference(left, right)) {
|
|
return true;
|
|
}
|
|
|
|
if (left.type !== right.type) {
|
|
return false;
|
|
}
|
|
|
|
switch (left.type) {
|
|
case 'AwaitExpression': {
|
|
return isSameNode(left.argument, right.argument, sourceCode);
|
|
}
|
|
|
|
case 'LogicalExpression': {
|
|
return (
|
|
left.operator === right.operator
|
|
&& isSameNode(left.left, right.left, sourceCode)
|
|
&& isSameNode(left.right, right.right, sourceCode)
|
|
);
|
|
}
|
|
|
|
case 'UnaryExpression': {
|
|
return (
|
|
left.operator === right.operator
|
|
&& left.prefix === right.prefix
|
|
&& isSameNode(left.argument, right.argument, sourceCode)
|
|
);
|
|
}
|
|
|
|
case 'UpdateExpression': {
|
|
return false;
|
|
}
|
|
|
|
// No default
|
|
}
|
|
|
|
return sourceCode.getText(left) === sourceCode.getText(right);
|
|
}
|
|
|
|
function fix({
|
|
fixer,
|
|
sourceCode,
|
|
conditionalExpression,
|
|
left,
|
|
right,
|
|
operator,
|
|
}) {
|
|
let text = [left, right].map((node, index) => {
|
|
const isNodeParenthesized = isParenthesized(node, sourceCode);
|
|
let text = isNodeParenthesized ? getParenthesizedText(node, sourceCode) : sourceCode.getText(node);
|
|
|
|
if (
|
|
!isNodeParenthesized
|
|
&& shouldAddParenthesesToLogicalExpressionChild(node, {operator, property: index === 0 ? 'left' : 'right'})
|
|
) {
|
|
text = `(${text})`;
|
|
}
|
|
|
|
return text;
|
|
}).join(` ${operator} `);
|
|
|
|
// According to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#table
|
|
// There should be no cases need add parentheses when switching ternary to logical expression
|
|
|
|
// ASI
|
|
if (needsSemicolon(sourceCode.getTokenBefore(conditionalExpression), sourceCode, text)) {
|
|
text = `;${text}`;
|
|
}
|
|
|
|
return fixer.replaceText(conditionalExpression, text);
|
|
}
|
|
|
|
function getProblem({
|
|
sourceCode,
|
|
conditionalExpression,
|
|
left,
|
|
right,
|
|
}) {
|
|
return {
|
|
node: conditionalExpression,
|
|
messageId: MESSAGE_ID_ERROR,
|
|
suggest: ['??', '||'].map(operator => ({
|
|
messageId: MESSAGE_ID_SUGGESTION,
|
|
data: {operator},
|
|
fix: fixer => fix({
|
|
fixer,
|
|
sourceCode,
|
|
conditionalExpression,
|
|
left,
|
|
right,
|
|
operator,
|
|
}),
|
|
})),
|
|
};
|
|
}
|
|
|
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
const create = context => {
|
|
const sourceCode = context.getSourceCode();
|
|
|
|
return {
|
|
ConditionalExpression(conditionalExpression) {
|
|
const {test, consequent, alternate} = conditionalExpression;
|
|
|
|
// `foo ? foo : bar`
|
|
if (isSameNode(test, consequent, sourceCode)) {
|
|
return getProblem({
|
|
sourceCode,
|
|
conditionalExpression,
|
|
left: test,
|
|
right: alternate,
|
|
});
|
|
}
|
|
|
|
// `!bar ? foo : bar`
|
|
if (
|
|
test.type === 'UnaryExpression'
|
|
&& test.operator === '!'
|
|
&& test.prefix
|
|
&& isSameNode(test.argument, alternate, sourceCode)
|
|
) {
|
|
return getProblem({
|
|
sourceCode,
|
|
conditionalExpression,
|
|
left: test.argument,
|
|
right: consequent,
|
|
});
|
|
}
|
|
},
|
|
};
|
|
};
|
|
|
|
/** @type {import('eslint').Rule.RuleModule} */
|
|
module.exports = {
|
|
create,
|
|
meta: {
|
|
type: 'suggestion',
|
|
docs: {
|
|
description: 'Prefer using a logical operator over a ternary.',
|
|
},
|
|
|
|
hasSuggestions: true,
|
|
messages,
|
|
},
|
|
};
|