| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 | 'use strict';const {isParenthesized, isNotSemicolonToken} = require('@eslint-community/eslint-utils');const needsSemicolon = require('./utils/needs-semicolon.js');const {removeSpacesAfter} = require('./fix/index.js');const {matches} = require('./selectors/index.js');const MESSAGE_ID = 'no-lonely-if';const messages = {	[MESSAGE_ID]: 'Unexpected `if` as the only statement in a `if` block without `else`.',};const ifStatementWithoutAlternate = 'IfStatement:not([alternate])';const selector = matches([	// `if (a) { if (b) {} }`	[		ifStatementWithoutAlternate,		' > ',		'BlockStatement.consequent',		'[body.length=1]',		' > ',		`${ifStatementWithoutAlternate}.body`,	].join(''),	// `if (a) if (b) {}`	`${ifStatementWithoutAlternate} > ${ifStatementWithoutAlternate}.consequent`,]);// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table// Lower precedence than `&&`const needParenthesis = node => (	(node.type === 'LogicalExpression' && (node.operator === '||' || node.operator === '??'))	|| node.type === 'ConditionalExpression'	|| node.type === 'AssignmentExpression'	|| node.type === 'YieldExpression'	|| node.type === 'SequenceExpression');function getIfStatementTokens(node, sourceCode) {	const tokens = {};	tokens.ifToken = sourceCode.getFirstToken(node);	tokens.openingParenthesisToken = sourceCode.getFirstToken(node, 1);	const {consequent} = node;	tokens.closingParenthesisToken = sourceCode.getTokenBefore(consequent);	if (consequent.type === 'BlockStatement') {		tokens.openingBraceToken = sourceCode.getFirstToken(consequent);		tokens.closingBraceToken = sourceCode.getLastToken(consequent);	}	return tokens;}function fix(innerIfStatement, sourceCode) {	return function * (fixer) {		const outerIfStatement = (			innerIfStatement.parent.type === 'BlockStatement'				? innerIfStatement.parent				: innerIfStatement		).parent;		const outer = {			...outerIfStatement,			...getIfStatementTokens(outerIfStatement, sourceCode),		};		const inner = {			...innerIfStatement,			...getIfStatementTokens(innerIfStatement, sourceCode),		};		// Remove inner `if` token		yield fixer.remove(inner.ifToken);		yield removeSpacesAfter(inner.ifToken, sourceCode, fixer);		// Remove outer `{}`		if (outer.openingBraceToken) {			yield fixer.remove(outer.openingBraceToken);			yield removeSpacesAfter(outer.openingBraceToken, sourceCode, fixer);			yield fixer.remove(outer.closingBraceToken);			const tokenBefore = sourceCode.getTokenBefore(outer.closingBraceToken, {includeComments: true});			yield removeSpacesAfter(tokenBefore, sourceCode, fixer);		}		// Add new `()`		yield fixer.insertTextBefore(outer.openingParenthesisToken, '(');		yield fixer.insertTextAfter(			inner.closingParenthesisToken,			`)${inner.consequent.type === 'EmptyStatement' ? '' : ' '}`,		);		// Add ` && `		yield fixer.insertTextAfter(outer.closingParenthesisToken, ' && ');		// Remove `()` if `test` don't need it		for (const {test, openingParenthesisToken, closingParenthesisToken} of [outer, inner]) {			if (				isParenthesized(test, sourceCode)				|| !needParenthesis(test)			) {				yield fixer.remove(openingParenthesisToken);				yield fixer.remove(closingParenthesisToken);			}			yield removeSpacesAfter(closingParenthesisToken, sourceCode, fixer);		}		// If the `if` statement has no block, and is not followed by a semicolon,		// make sure that fixing the issue would not change semantics due to ASI.		// Similar logic https://github.com/eslint/eslint/blob/2124e1b5dad30a905dc26bde9da472bf622d3f50/lib/rules/no-lonely-if.js#L61-L77		if (inner.consequent.type !== 'BlockStatement') {			const lastToken = sourceCode.getLastToken(inner.consequent);			if (isNotSemicolonToken(lastToken)) {				const nextToken = sourceCode.getTokenAfter(outer);				if (needsSemicolon(lastToken, sourceCode, nextToken.value)) {					yield fixer.insertTextBefore(nextToken, ';');				}			}		}	};}/** @param {import('eslint').Rule.RuleContext} context */const create = context => {	const sourceCode = context.getSourceCode();	return {		[selector](node) {			return {				node,				messageId: MESSAGE_ID,				fix: fix(node, sourceCode),			};		},	};};/** @type {import('eslint').Rule.RuleModule} */module.exports = {	create,	meta: {		type: 'suggestion',		docs: {			description: 'Disallow `if` statements as the only statement in `if` blocks without `else`.',		},		fixable: 'code',		messages,	},};
 |