UNPKG

eslint-plugin-unicorn

Version:
197 lines (170 loc) • 4.96 kB
/* Based on ESLint builtin `no-negated-condition` rule https://github.com/eslint/eslint/blob/5c39425fc55ecc0b97bbd07ac22654c0eb4f789c/lib/rules/no-negated-condition.js */ import { removeParentheses, fixSpaceAroundKeyword, addParenthesesToReturnOrThrowExpression, } from './fix/index.js'; import { getParenthesizedRange, isParenthesized, isOnSameLine, needsSemicolon, isLogicalExpression, getLogicalExpressionOperands, shouldAddParenthesesToExpressionStatementExpression, } from './utils/index.js'; const MESSAGE_ID = 'no-negated-condition'; const messages = { [MESSAGE_ID]: 'Unexpected negated condition.', }; function * convertNegatedCondition(fixer, test, context) { const {sourceCode} = context; if (isLogicalExpression(test)) { const token = sourceCode.getTokenAfter(test.left, token => token.value === test.operator); yield fixer.replaceText(token, test.operator === '&&' ? '||' : '&&'); yield convertNegatedCondition(fixer, test.left, context); yield convertNegatedCondition(fixer, test.right, context); return; } if (test.type === 'UnaryExpression') { const token = sourceCode.getFirstToken(test); yield fixer.remove(token); return; } const token = sourceCode.getTokenAfter( test.left, token => token.type === 'Punctuator' && token.value === test.operator, ); yield fixer.replaceText(token, '=' + token.value.slice(1)); } function * swapConsequentAndAlternate(fixer, node, context, abort) { const {sourceCode} = context; const isIfStatement = node.type === 'IfStatement'; const [consequent, alternate] = [ node.consequent, node.alternate, ].map(branch => { const range = getParenthesizedRange(branch, context); if (sourceCode.getCommentsAfter({range}).length > 0) { abort(); } const firstComment = sourceCode.getCommentsBefore({range})[0]; if (firstComment) { range[0] = sourceCode.getRange(firstComment)[0]; } let text = sourceCode.text.slice(...range); // `if (!a) b(); else c()` can't fix to `if (!a) c() else b();` if (isIfStatement && branch.type !== 'BlockStatement') { text = `{${text}}`; } return { range, text, }; }); if (consequent.text === alternate.text) { return; } yield fixer.replaceTextRange(sourceCode.getRange(consequent), alternate.text); yield fixer.replaceTextRange(sourceCode.getRange(alternate), consequent.text); } /** @param {import('eslint').Rule.RuleContext} context */ const create = context => { context.on(['IfStatement', 'ConditionalExpression'], node => { if ( node.type === 'IfStatement' && ( !node.alternate || node.alternate.type === 'IfStatement' ) ) { return; } const {test} = node; const operands = isLogicalExpression(test) ? getLogicalExpressionOperands(test, test.operator) : [test]; if (operands.some(operand => !((operand.type === 'UnaryExpression' && operand.operator === '!') || (operand.type === 'BinaryExpression' && (operand.operator === '!=' || operand.operator === '!=='))), )) { return; } return { node: test, messageId: MESSAGE_ID, /** @param {import('eslint').Rule.RuleFixer} fixer */ * fix(fixer, {abort}) { if ( operands[0].type === 'UnaryExpression' && shouldAddParenthesesToExpressionStatementExpression(operands[0].argument, context) ) { abort(); } if (node.type === 'IfStatement' && test.type === 'UnaryExpression') { yield removeParentheses(test.argument, fixer, context); } yield convertNegatedCondition(fixer, test, context); yield swapConsequentAndAlternate(fixer, node, context, abort); if ( node.type !== 'ConditionalExpression' || operands[0].type !== 'UnaryExpression' ) { return; } yield fixSpaceAroundKeyword(fixer, node, context); const {sourceCode} = context; const {parent} = node; const [firstToken, secondToken] = sourceCode.getFirstTokens(test, 2); if (firstToken.value !== '!') { return; } if ( ( parent.type === 'ReturnStatement' || parent.type === 'ThrowStatement' || (parent.type === 'YieldExpression' && !parent.delegate) ) && parent.argument === node && !isOnSameLine(firstToken, secondToken, context) && !isParenthesized(node, context) && !isParenthesized(test, context) ) { if (parent.type === 'YieldExpression') { abort(); } yield addParenthesesToReturnOrThrowExpression(fixer, parent, context); return; } const tokenBefore = sourceCode.getTokenBefore(node); if (needsSemicolon(tokenBefore, context, secondToken.value)) { yield fixer.insertTextBefore(node, ';'); } }, }; }); }; /** @type {import('eslint').Rule.RuleModule} */ const config = { create, meta: { type: 'suggestion', docs: { description: 'Disallow negated conditions.', recommended: 'unopinionated', }, fixable: 'code', messages, languages: [ 'js/js', ], }, }; export default config;