@truenine/eslint9-config
Version:
ESLint 9 configuration package for Compose Client projects with TypeScript, Vue, and modern JavaScript support
1 lines • 9.88 kB
Source Map (JSON)
{"version":3,"file":"control.mjs","names":[],"sources":["../../../src/rules/single-line/control.ts"],"sourcesContent":["import type {Rule} from 'eslint'\n\nconst MAX_LINE_LENGTH = 160\n\n/**\n * ESLint rule: prefer-single-line-control\n * Prefer single-line switch cases, for loops, while loops, and try-catch when possible.\n */\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'layout',\n docs: {description: 'Prefer single-line switch cases, for loops, while loops, and try-catch when possible', recommended: false},\n fixable: 'code',\n schema: [],\n messages: {\n preferSingleLineCase: 'Switch case with simple statement should be single-line format',\n preferSingleLineFor: 'For loop with simple body should be single-line format',\n preferSingleLineWhile: 'While loop with simple body should be single-line format',\n preferSingleLineTry: 'Try-catch with simple bodies should be single-line format',\n preferBraceCatch: '} catch should be on the same line',\n preferBraceFinally: '} finally should be on the same line'\n }\n },\n create(context) {\n const {sourceCode} = context\n\n function getSingleStatement(node: Rule.Node | null | undefined): Rule.Node | null {\n if (!node) return null\n if (node.type !== 'BlockStatement') return node\n const {body} = node as Rule.Node & {body: Rule.Node[]}\n return Array.isArray(body) && body.length === 1 ? body[0] : null\n }\n\n function hasComments(node: Rule.Node): boolean { return sourceCode.getCommentsInside(node).length > 0 }\n\n function isSimpleStatement(stmt: Rule.Node | null): boolean {\n if (!stmt) return false\n return new Set(['ExpressionStatement', 'ReturnStatement', 'ThrowStatement', 'BreakStatement', 'ContinueStatement']).has(stmt.type)\n }\n\n function isNodeSingleLine(node: Rule.Node): boolean { return node.loc?.start.line === node.loc?.end.line }\n function normalizeText(text: string): string { return text.split('\\n').map(l => l.trim()).join(' ').replaceAll(/\\s+/g, ' ').trim() }\n function ensureSemicolon(text: string): string { return text.trimEnd().endsWith(';') ? text.trimEnd() : `${text.trimEnd()};` }\n\n type CaseNode = Rule.Node & {test: Rule.Node | null, consequent: Rule.Node[]}\n type LoopNode = Rule.Node & {body: Rule.Node}\n type WhileNode = LoopNode & {test: Rule.Node}\n\n function extractCaseStatements(consequent: Rule.Node[]): {mainStmt: Rule.Node | null, hasBreak: boolean} { /* 从 case 的 consequent 中提取语句和 break */\n if (!Array.isArray(consequent) || consequent.length === 0) return {mainStmt: null, hasBreak: false}\n if (consequent.length === 1) {\n if (isSimpleStatement(consequent[0])) return {mainStmt: consequent[0], hasBreak: false}\n }\n if (consequent.length === 2 && isSimpleStatement(consequent[0]) && consequent[1].type === 'BreakStatement') return {mainStmt: consequent[0], hasBreak: true}\n\n if (consequent.length !== 1 || consequent[0].type !== 'BlockStatement') return {mainStmt: null, hasBreak: false}\n const {body} = consequent[0] as Rule.Node & {body: Rule.Node[]}\n if (!Array.isArray(body) || body.length === 0 || body.length > 2) return {mainStmt: null, hasBreak: false}\n const hasBreak = body.length === 2 && body[1].type === 'BreakStatement'\n return (body.length === 1 || hasBreak) && isSimpleStatement(body[0]) ? {mainStmt: body[0], hasBreak} : {mainStmt: null, hasBreak: false}\n }\n\n function canCaseBeSimplified(caseNode: CaseNode): boolean {\n const {consequent, test} = caseNode\n const {mainStmt, hasBreak} = extractCaseStatements(consequent)\n if (!mainStmt || !isNodeSingleLine(mainStmt) || hasComments(caseNode)) return false\n const testText = test != null ? normalizeText(sourceCode.getText(test)) : 'default'\n return `case ${testText}: ${ensureSemicolon(sourceCode.getText(mainStmt))}${hasBreak ? ' break' : ''}`.length < MAX_LINE_LENGTH\n }\n\n function canLoopBeSimplified(loopNode: LoopNode): boolean {\n const {body} = loopNode\n if (body?.type !== 'BlockStatement' || hasComments(body)) return false\n const stmt = getSingleStatement(body)\n if (!stmt || !isSimpleStatement(stmt) || !isNodeSingleLine(stmt)) return false\n const headerText = normalizeText(sourceCode.getText(loopNode).slice(0, (body.range?.[0] ?? 0) - (loopNode.range?.[0] ?? 0)))\n return `${headerText} ${sourceCode.getText(stmt)}`.length < MAX_LINE_LENGTH\n }\n\n function isLoopAlreadySingleLine(l: LoopNode): boolean { return l.body == null || l.body.type === 'BlockStatement' ? false : l.loc?.start.line === l.body.loc?.end.line }\n\n const loopVisitor = (messageId: 'preferSingleLineFor' | 'preferSingleLineWhile') => (node: Rule.Node) => {\n const l = node as LoopNode\n if (isLoopAlreadySingleLine(l) || !canLoopBeSimplified(l)) return\n context.report({\n node,\n messageId,\n fix: fixer => {\n const stmt = getSingleStatement(l.body)!\n if (node.type === 'WhileStatement') return fixer.replaceText(node, `while (${normalizeText(sourceCode.getText((node as WhileNode).test))}) ${sourceCode.getText(stmt).trimEnd()}`)\n const h = normalizeText(sourceCode.getText(node).slice(0, (l.body.range?.[0] ?? 0) - (node.range?.[0] ?? 0)))\n return fixer.replaceText(node, `${h} ${sourceCode.getText(stmt).trimEnd()}`)\n }\n })\n }\n\n return {\n SwitchStatement(node) {\n const {cases} = node as Rule.Node & {cases: CaseNode[]}\n cases?.forEach(c => {\n if (c.loc?.start.line !== c.loc?.end.line && canCaseBeSimplified(c)) {\n context.report({\n node: c,\n messageId: 'preferSingleLineCase',\n fix: fixer => {\n const {mainStmt, hasBreak} = extractCaseStatements(c.consequent)\n const tText = c.test ? `case ${normalizeText(sourceCode.getText(c.test))}:` : 'default:'\n return fixer.replaceText(c, `${tText} ${ensureSemicolon(sourceCode.getText(mainStmt!))}${hasBreak ? ' break' : ''}`)\n }\n })\n }\n })\n },\n ForStatement: loopVisitor('preferSingleLineFor'),\n ForInStatement: loopVisitor('preferSingleLineFor'),\n ForOfStatement: loopVisitor('preferSingleLineFor'),\n WhileStatement: loopVisitor('preferSingleLineWhile'),\n DoWhileStatement() { /* do-while 结构特殊,暂不处理 */ }\n }\n }\n}\n\nexport default rule\n"],"mappings":"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"}