| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 | '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,	},};
 |