@truenine/eslint9-config
Version:
ESLint 9 configuration package for Compose Client projects with TypeScript, Vue, and modern JavaScript support
145 lines (143 loc) • 5.47 kB
JavaScript
//#region src/rules/single-line/control.ts
const MAX_LINE_LENGTH = 160;
/**
* ESLint rule: prefer-single-line-control
* Prefer single-line switch cases, for loops, while loops, and try-catch when possible.
*/
const rule = {
meta: {
type: "layout",
docs: {
description: "Prefer single-line switch cases, for loops, while loops, and try-catch when possible",
recommended: false
},
fixable: "code",
schema: [],
messages: {
preferSingleLineCase: "Switch case with simple statement should be single-line format",
preferSingleLineFor: "For loop with simple body should be single-line format",
preferSingleLineWhile: "While loop with simple body should be single-line format",
preferSingleLineTry: "Try-catch with simple bodies should be single-line format",
preferBraceCatch: "} catch should be on the same line",
preferBraceFinally: "} finally should be on the same line"
}
},
create(context) {
const { sourceCode } = context;
function getSingleStatement(node) {
if (!node) return null;
if (node.type !== "BlockStatement") return node;
const { body } = node;
return Array.isArray(body) && body.length === 1 ? body[0] : null;
}
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 normalizeText(text) {
return text.split("\n").map((l) => l.trim()).join(" ").replaceAll(/\s+/g, " ").trim();
}
function ensureSemicolon(text) {
return text.trimEnd().endsWith(";") ? text.trimEnd() : `${text.trimEnd()};`;
}
function extractCaseStatements(consequent) {
if (!Array.isArray(consequent) || consequent.length === 0) return {
mainStmt: null,
hasBreak: false
};
if (consequent.length === 1) {
if (isSimpleStatement(consequent[0])) return {
mainStmt: consequent[0],
hasBreak: false
};
}
if (consequent.length === 2 && isSimpleStatement(consequent[0]) && consequent[1].type === "BreakStatement") return {
mainStmt: consequent[0],
hasBreak: true
};
if (consequent.length !== 1 || consequent[0].type !== "BlockStatement") return {
mainStmt: null,
hasBreak: false
};
const { body } = consequent[0];
if (!Array.isArray(body) || body.length === 0 || body.length > 2) return {
mainStmt: null,
hasBreak: false
};
const hasBreak = body.length === 2 && body[1].type === "BreakStatement";
return (body.length === 1 || hasBreak) && isSimpleStatement(body[0]) ? {
mainStmt: body[0],
hasBreak
} : {
mainStmt: null,
hasBreak: false
};
}
function canCaseBeSimplified(caseNode) {
const { consequent, test } = caseNode;
const { mainStmt, hasBreak } = extractCaseStatements(consequent);
if (!mainStmt || !isNodeSingleLine(mainStmt) || hasComments(caseNode)) return false;
return `case ${test != null ? normalizeText(sourceCode.getText(test)) : "default"}: ${ensureSemicolon(sourceCode.getText(mainStmt))}${hasBreak ? " break" : ""}`.length < MAX_LINE_LENGTH;
}
function canLoopBeSimplified(loopNode) {
const { body } = loopNode;
if (body?.type !== "BlockStatement" || hasComments(body)) return false;
const stmt = getSingleStatement(body);
if (!stmt || !isSimpleStatement(stmt) || !isNodeSingleLine(stmt)) return false;
return `${normalizeText(sourceCode.getText(loopNode).slice(0, (body.range?.[0] ?? 0) - (loopNode.range?.[0] ?? 0)))} ${sourceCode.getText(stmt)}`.length < MAX_LINE_LENGTH;
}
function isLoopAlreadySingleLine(l) {
return l.body == null || l.body.type === "BlockStatement" ? false : l.loc?.start.line === l.body.loc?.end.line;
}
const loopVisitor = (messageId) => (node) => {
const l = node;
if (isLoopAlreadySingleLine(l) || !canLoopBeSimplified(l)) return;
context.report({
node,
messageId,
fix: (fixer) => {
const stmt = getSingleStatement(l.body);
if (node.type === "WhileStatement") return fixer.replaceText(node, `while (${normalizeText(sourceCode.getText(node.test))}) ${sourceCode.getText(stmt).trimEnd()}`);
const h = normalizeText(sourceCode.getText(node).slice(0, (l.body.range?.[0] ?? 0) - (node.range?.[0] ?? 0)));
return fixer.replaceText(node, `${h} ${sourceCode.getText(stmt).trimEnd()}`);
}
});
};
return {
SwitchStatement(node) {
const { cases } = node;
cases?.forEach((c) => {
if (c.loc?.start.line !== c.loc?.end.line && canCaseBeSimplified(c)) context.report({
node: c,
messageId: "preferSingleLineCase",
fix: (fixer) => {
const { mainStmt, hasBreak } = extractCaseStatements(c.consequent);
const tText = c.test ? `case ${normalizeText(sourceCode.getText(c.test))}:` : "default:";
return fixer.replaceText(c, `${tText} ${ensureSemicolon(sourceCode.getText(mainStmt))}${hasBreak ? " break" : ""}`);
}
});
});
},
ForStatement: loopVisitor("preferSingleLineFor"),
ForInStatement: loopVisitor("preferSingleLineFor"),
ForOfStatement: loopVisitor("preferSingleLineFor"),
WhileStatement: loopVisitor("preferSingleLineWhile"),
DoWhileStatement() {}
};
}
};
//#endregion
module.exports = rule;
//# sourceMappingURL=control.cjs.map