securityos/node_modules/eslint-plugin-unicorn/rules/no-negated-condition.js

139 lines
3.6 KiB
JavaScript
Raw Permalink Normal View History

2024-09-06 15:32:35 +00:00
/*
Based on ESLint builtin `no-negated-condition` rule
https://github.com/eslint/eslint/blob/5c39425fc55ecc0b97bbd07ac22654c0eb4f789c/lib/rules/no-negated-condition.js
*/
'use strict';
const {matches} = require('./selectors/index.js');
const {
removeParentheses,
fixSpaceAroundKeyword,
addParenthesizesToReturnOrThrowExpression,
} = require('./fix/index.js');
const {
getParenthesizedRange,
isParenthesized,
} = require('./utils/parentheses.js');
const isOnSameLine = require('./utils/is-on-same-line.js');
const needsSemicolon = require('./utils/needs-semicolon.js');
const MESSAGE_ID = 'no-negated-condition';
const messages = {
[MESSAGE_ID]: 'Unexpected negated condition.',
};
const selector = [
matches([
'IfStatement[alternate][alternate.type!="IfStatement"]',
'ConditionalExpression',
]),
matches([
'[test.type="UnaryExpression"][test.operator="!"]',
'[test.type="BinaryExpression"][test.operator="!="]',
'[test.type="BinaryExpression"][test.operator="!=="]',
]),
].join('');
function * convertNegatedCondition(fixer, node, sourceCode) {
const {test} = node;
if (test.type === 'UnaryExpression') {
const token = sourceCode.getFirstToken(test);
if (node.type === 'IfStatement') {
yield * removeParentheses(test.argument, fixer, sourceCode);
}
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, sourceCode) {
const isIfStatement = node.type === 'IfStatement';
const [consequent, alternate] = [
node.consequent,
node.alternate,
].map(node => {
const range = getParenthesizedRange(node, sourceCode);
let text = sourceCode.text.slice(...range);
// `if (!a) b(); else c()` can't fix to `if (!a) c() else b();`
if (isIfStatement && node.type !== 'BlockStatement') {
text = `{${text}}`;
}
return {
range,
text,
};
});
if (consequent.text === alternate.text) {
return;
}
yield fixer.replaceTextRange(consequent.range, alternate.text);
yield fixer.replaceTextRange(alternate.range, consequent.text);
}
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[selector](node) {
return {
node: node.test,
messageId: MESSAGE_ID,
/** @param {import('eslint').Rule.RuleFixer} fixer */
* fix(fixer) {
const sourceCode = context.getSourceCode();
yield * convertNegatedCondition(fixer, node, sourceCode);
yield * swapConsequentAndAlternate(fixer, node, sourceCode);
if (
node.type !== 'ConditionalExpression'
|| node.test.type !== 'UnaryExpression'
) {
return;
}
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
const {test, parent} = node;
const [firstToken, secondToken] = sourceCode.getFirstTokens(test, 2);
if (
(parent.type === 'ReturnStatement' || parent.type === 'ThrowStatement')
&& parent.argument === node
&& !isOnSameLine(firstToken, secondToken)
&& !isParenthesized(node, sourceCode)
&& !isParenthesized(test, sourceCode)
) {
yield * addParenthesizesToReturnOrThrowExpression(fixer, parent, sourceCode);
return;
}
const tokenBefore = sourceCode.getTokenBefore(node);
if (needsSemicolon(tokenBefore, sourceCode, secondToken.value)) {
yield fixer.insertTextBefore(node, ';');
}
},
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Disallow negated conditions.',
},
fixable: 'code',
messages,
},
};