123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- /*
- 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,
- },
- };
|