@truenine/eslint9-config
Version:
ESLint 9 configuration package for Compose Client projects with TypeScript, Vue, and modern JavaScript support
211 lines (209 loc) • 8.18 kB
JavaScript
//#region src/rules/single-line/if.ts
const MAX_LINE_LENGTH = 160;
const rule = {
meta: {
type: "layout",
docs: {
description: "Prefer single-line if statements when possible",
recommended: false
},
fixable: "code",
schema: [],
messages: {
preferSingleLine: "If-else chain with simple statements should be single-line format",
preferSingleLineBranch: "Simple if/else branch should be single-line format"
}
},
create(context) {
const { sourceCode } = context;
function getSingleStatement(node) {
if (!node) return null;
if (node.type !== "BlockStatement") return node;
const { body } = node;
if (!Array.isArray(body) || body.length !== 1) return null;
return body[0];
}
function hasComments(node) {
return sourceCode.getCommentsInside(node).length > 0;
}
function isSimpleStatement(stmt) {
if (!stmt) return false;
return new Set([
"ExpressionStatement",
"ReturnStatement",
"ThrowStatement",
"BreakStatement",
"ContinueStatement"
]).has(stmt.type);
}
function isNodeSingleLine(node) {
return node.loc?.start.line === node.loc?.end.line;
}
function normalizeConditionText(text) {
return text.split("\n").map((line) => line.trim()).join(" ").replaceAll(/\s+/g, " ").trim();
}
function canConditionBeSingleLine(test) {
return normalizeConditionText(sourceCode.getText(test)).length < MAX_LINE_LENGTH - 40;
}
function collectIfChain(node) {
const conditions = [];
let current = node;
while (current && current.type === "IfStatement") {
conditions.push(current);
current = "alternate" in current ? current.alternate : null;
}
return {
conditions,
finalElse: current
};
}
function canBranchBeSimplified(consequent, test) {
if (consequent.type !== "BlockStatement") return false;
if (!canConditionBeSingleLine(test)) return false;
if (hasComments(consequent)) return false;
const stmt = getSingleStatement(consequent);
if (!stmt || !isSimpleStatement(stmt)) return false;
if (!isNodeSingleLine(stmt)) return false;
return `if (${normalizeConditionText(sourceCode.getText(test))}) ${sourceCode.getText(stmt)}`.length < MAX_LINE_LENGTH;
}
function canConvertToSingleLine(node) {
const { conditions, finalElse } = collectIfChain(node);
for (const cond of conditions) {
if (!("consequent" in cond) || !("test" in cond)) return false;
const test = cond.test;
if (!canConditionBeSingleLine(test)) return false;
const consequent = cond.consequent;
if (hasComments(consequent)) return false;
const stmt = getSingleStatement(consequent);
if (!stmt || !isSimpleStatement(stmt)) return false;
if (!isNodeSingleLine(stmt)) return false;
}
if (finalElse) {
if (hasComments(finalElse)) return false;
const stmt = getSingleStatement(finalElse);
if (!stmt || !isSimpleStatement(stmt)) return false;
if (!isNodeSingleLine(stmt)) return false;
}
const lines = [];
for (let i = 0; i < conditions.length; i++) {
const cond = conditions[i];
if (!("consequent" in cond) || !("test" in cond)) continue;
const stmt = getSingleStatement(cond.consequent);
const condText = normalizeConditionText(sourceCode.getText(cond.test));
const stmtText = sourceCode.getText(stmt);
const prefix = i === 0 ? "if" : "else if";
lines.push(`${prefix} (${condText}) ${stmtText}`);
}
if (!finalElse) return lines.every((line) => line.length < MAX_LINE_LENGTH);
const stmt = getSingleStatement(finalElse);
lines.push(`else ${sourceCode.getText(stmt)}`);
return lines.every((line) => line.length < MAX_LINE_LENGTH);
}
function isAlreadySingleLine(node) {
const { conditions, finalElse } = collectIfChain(node);
for (const cond of conditions) {
if (!("consequent" in cond)) return false;
if (cond.consequent.type === "BlockStatement") return false;
}
if (finalElse && finalElse.type === "BlockStatement") return false;
for (const cond of conditions) {
if (!("consequent" in cond)) return false;
const consequent = cond.consequent;
if (cond.loc?.start.line !== consequent.loc?.end.line) return false;
}
return true;
}
function isBranchAlreadySingleLine(consequent, ifNode) {
if (consequent.type === "BlockStatement") return false;
return ifNode.loc?.start.line === consequent.loc?.end.line;
}
return { IfStatement(node) {
const { parent } = node;
if (parent?.type === "IfStatement" && "alternate" in parent && parent.alternate === node) return;
if (!isAlreadySingleLine(node) && canConvertToSingleLine(node)) {
context.report({
node,
messageId: "preferSingleLine",
fix(fixer) {
const { conditions, finalElse } = collectIfChain(node);
const indent = " ".repeat(node.loc?.start.column ?? 0);
const lines = [];
for (let i = 0; i < conditions.length; i++) {
const cond = conditions[i];
if (!("consequent" in cond) || !("test" in cond)) continue;
const stmt = getSingleStatement(cond.consequent);
const condText = normalizeConditionText(sourceCode.getText(cond.test));
let stmtText = sourceCode.getText(stmt);
if (!stmtText.endsWith(";") && !stmtText.endsWith("}")) stmtText = stmtText.trimEnd();
const prefix = i === 0 ? "if" : `${indent}else if`;
lines.push(`${prefix} (${condText}) ${stmtText}`);
}
if (!finalElse) return fixer.replaceText(node, lines.join("\n"));
const stmt = getSingleStatement(finalElse);
let stmtText = sourceCode.getText(stmt);
if (!stmtText.endsWith(";") && !stmtText.endsWith("}")) stmtText = stmtText.trimEnd();
lines.push(`${indent}else ${stmtText}`);
return fixer.replaceText(node, lines.join("\n"));
}
});
return;
}
const { conditions, finalElse } = collectIfChain(node);
const branchSimplifiable = [];
for (const cond of conditions) {
if (!("consequent" in cond) || !("test" in cond)) {
branchSimplifiable.push(false);
continue;
}
const consequent = cond.consequent;
const test = cond.test;
const canSimplify = !isBranchAlreadySingleLine(consequent, cond) && canBranchBeSimplified(consequent, test);
branchSimplifiable.push(canSimplify);
}
let finalElseSimplifiable = false;
if (finalElse && finalElse.type === "BlockStatement" && !hasComments(finalElse)) {
const stmt = getSingleStatement(finalElse);
if (stmt && isSimpleStatement(stmt) && isNodeSingleLine(stmt)) finalElseSimplifiable = sourceCode.getText(stmt).length < MAX_LINE_LENGTH - 20;
}
if (!(branchSimplifiable.some(Boolean) || finalElseSimplifiable)) return;
context.report({
node,
messageId: "preferSingleLineBranch",
fix(fixer) {
const indent = " ".repeat(node.loc?.start.column ?? 0);
const lines = [];
for (let i = 0; i < conditions.length; i++) {
const cond = conditions[i];
if (!("consequent" in cond) || !("test" in cond)) continue;
const consequent = cond.consequent;
const test = cond.test;
const condText = sourceCode.getText(test);
const prefix = i === 0 ? "if" : `${indent}else if`;
if (branchSimplifiable[i]) {
const stmt = getSingleStatement(consequent);
let stmtText = sourceCode.getText(stmt);
if (!stmtText.endsWith(";") && !stmtText.endsWith("}")) stmtText = stmtText.trimEnd();
lines.push(`${prefix} (${condText}) ${stmtText}`);
} else {
const consequentText = sourceCode.getText(consequent);
lines.push(`${prefix} (${condText}) ${consequentText}`);
}
}
if (finalElse) if (finalElseSimplifiable) {
const stmt = getSingleStatement(finalElse);
let stmtText = sourceCode.getText(stmt);
if (!stmtText.endsWith(";") && !stmtText.endsWith("}")) stmtText = stmtText.trimEnd();
lines.push(`${indent}else ${stmtText}`);
} else {
const elseText = sourceCode.getText(finalElse);
lines.push(`${indent}else ${elseText}`);
}
return fixer.replaceText(node, lines.join("\n"));
}
});
} };
}
};
//#endregion
module.exports = rule;
//# sourceMappingURL=if.cjs.map