如何仅对联合类型或枚举类型的switch语句应用ESLint规则?
问题
已实现一个自定义ESLint规则,要求所有switch语句必须包含返回assertUnreachable调用结果的default分支,且传入参数需与switch的变量匹配。但当前规则会作用于所有switch语句,无类型限制。需要调整规则,仅对判别式为联合类型或枚举类型的switch语句触发检查。
已安装typescript-eslint以支持ESLint中的类型检查,现有规则代码如下:
// eslint-disable-next-line no-undef module.exports = { rules: {'exhaustive-switch-on-unions-and-enums': { create(context) { function getTypeName(node) { if (node.type === 'TSTypeReference') { return node.typeName.name; } else if (node.type === 'TSUnionType') { return node.types.map(getTypeName).join(' | '); } else if (node.type === 'TSEnumDeclaration') { return 'enum'; } return null; } function hasDefaultCaseWithAssertUnreachable(node) { if (!node.cases || node.cases.length === 0) { return false; } const defaultCase = node.cases.find((caseClause) => caseClause.test === null); if (!defaultCase) { return false; } const { consequent } = defaultCase; if ( consequent.length === 1 && consequent[0].type === 'ReturnStatement' && consequent[0].argument && consequent[0].argument.type === 'CallExpression' && consequent[0].argument.callee.name === 'assertUnreachable' ) { const switchType = context.getTypeChecker().getTypeAtLocation(context.getNodeFromContext(node.discriminant)); const switchTypeName = getTypeName(switchType); const argumentType = consequent[0].argument.arguments[0]; if (argumentType.type === 'Identifier' && argumentType.name === switchTypeName) { return true; } } return false; } return { SwitchStatement(node) { if (!hasDefaultCaseWithAssertUnreachable(node)) { context.report({ node, message: 'Switch statement must have a default case returning assertUnreachable with the correct type.', }); } }, }; }, }}}
解决方案
核心修改点
- 仅针对联合/枚举类型触发规则:新增类型判断逻辑,过滤非目标类型的switch语句
- 修正参数匹配逻辑:原代码错误匹配类型名,改为匹配switch变量名
修改后的完整代码
// eslint-disable-next-line no-undef const ts = require('typescript'); module.exports = { rules: {'exhaustive-switch-on-unions-and-enums': { create(context) { const parserServices = context.parserServices; const typeChecker = parserServices.program.getTypeChecker(); // 判断类型是否为联合类型或枚举类型 function isUnionOrEnumType(type) { // 识别联合类型 if (type.isUnion()) { return true; } // 通过TypeFlags识别枚举类型 return !!(type.flags & ts.TypeFlags.Enum); } function hasValidDefaultCase(node) { if (!node.cases || node.cases.length === 0) { return false; } // 查找default分支 const defaultCase = node.cases.find(caseClause => caseClause.test === null); if (!defaultCase) { return false; } const { consequent } = defaultCase; // 检查default分支是否为返回assertUnreachable的语句 if ( consequent.length === 1 && consequent[0].type === 'ReturnStatement' && consequent[0].argument && consequent[0].argument.type === 'CallExpression' && consequent[0].argument.callee.name === 'assertUnreachable' ) { const switchVarName = node.discriminant.name; // 检查assertUnreachable的参数是否为switch变量 const arg = consequent[0].argument.arguments[0]; return arg.type === 'Identifier' && arg.name === switchVarName; } return false; } return { SwitchStatement(node) { const switchDiscriminant = node.discriminant; if (!switchDiscriminant) return; // 将ESLint AST节点转换为TypeScript AST节点,用于类型检查 const tsNode = parserServices.esTreeNodeToTSNodeMap.get(switchDiscriminant); const type = typeChecker.getTypeAtLocation(tsNode); // 仅对联合/枚举类型的判别式触发规则检查 if (isUnionOrEnumType(type)) { if (!hasValidDefaultCase(node)) { context.report({ node, message: 'Switch statement on union/enum type must have a default case returning assertUnreachable with the switch variable.', }); } } }, }; }, }}}
关键说明
- 借助
parserServices.esTreeNodeToTSNodeMap完成ESLint AST到TypeScript AST的转换,确保类型检查器能正确获取判别式的类型信息 isUnionOrEnumType通过TypeScript内置的isUnion()方法和TypeFlags枚举,精准识别联合类型与枚举类型hasValidDefaultCase修正了参数匹配逻辑,直接对比assertUnreachable的参数名与switch变量名,符合需求
内容的提问来源于stack exchange,提问作者user2956284
相关产品推荐
相关产品推荐

