routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+155
@@ -0,0 +1,155 @@
|
||||
import {isNotSemicolonToken} from '@eslint-community/eslint-utils';
|
||||
import {isParenthesized, needsSemicolon} from './utils/index.js';
|
||||
import {removeSpacesAfter} from './fix/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 isIfStatementWithoutAlternate = node => node.type === 'IfStatement' && !node.alternate;
|
||||
|
||||
// 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 = {
|
||||
ifToken: sourceCode.getFirstToken(node),
|
||||
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, context) {
|
||||
const {sourceCode} = context;
|
||||
|
||||
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, context, fixer);
|
||||
|
||||
// Remove outer `{}`
|
||||
if (outer.openingBraceToken) {
|
||||
yield fixer.remove(outer.openingBraceToken);
|
||||
yield removeSpacesAfter(outer.openingBraceToken, context, fixer);
|
||||
yield fixer.remove(outer.closingBraceToken);
|
||||
|
||||
const tokenBefore = sourceCode.getTokenBefore(outer.closingBraceToken, {includeComments: true});
|
||||
yield removeSpacesAfter(tokenBefore, context, 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` doesn't need it
|
||||
for (const {test, openingParenthesisToken, closingParenthesisToken} of [outer, inner]) {
|
||||
if (
|
||||
isParenthesized(test, context)
|
||||
|| !needParenthesis(test)
|
||||
) {
|
||||
yield fixer.remove(openingParenthesisToken);
|
||||
yield fixer.remove(closingParenthesisToken);
|
||||
}
|
||||
|
||||
yield removeSpacesAfter(closingParenthesisToken, context, 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 (nextToken && needsSemicolon(lastToken, context, nextToken.value)) {
|
||||
yield fixer.insertTextBefore(nextToken, ';');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {import('eslint').Rule.RuleContext} context */
|
||||
const create = context => {
|
||||
context.on('IfStatement', ifStatement => {
|
||||
if (!(
|
||||
isIfStatementWithoutAlternate(ifStatement)
|
||||
&& (
|
||||
// `if (a) { if (b) {} }`
|
||||
(
|
||||
ifStatement.parent.type === 'BlockStatement'
|
||||
&& ifStatement.parent.body.length === 1
|
||||
&& ifStatement.parent.body[0] === ifStatement
|
||||
&& isIfStatementWithoutAlternate(ifStatement.parent.parent)
|
||||
&& ifStatement.parent.parent.consequent === ifStatement.parent
|
||||
)
|
||||
// `if (a) if (b) {}`
|
||||
|| (
|
||||
isIfStatementWithoutAlternate(ifStatement.parent)
|
||||
&& ifStatement.parent.consequent === ifStatement
|
||||
)
|
||||
)
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
node: ifStatement,
|
||||
messageId: MESSAGE_ID,
|
||||
fix: fix(ifStatement, context),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
const config = {
|
||||
create,
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'Disallow `if` statements as the only statement in `if` blocks without `else`.',
|
||||
recommended: 'unopinionated',
|
||||
},
|
||||
fixable: 'code',
|
||||
messages,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
Reference in New Issue
Block a user