ESLint 自定义规则实战:用 AST 分析封堵团队特有的代码坏味道
ESLint 自定义规则实战用 AST 分析封堵团队特有的代码坏味道一、通用 Lint 规则的盲区为什么团队特有的坏习惯永远被漏掉ESLint 有数百条内置规则和社区插件。no-unused-vars、no-console、prefer-const这些通用规则覆盖了 80% 的常见问题。但每个团队都有自己特有的代码坏味道这些是通用规则永远无法覆盖的。比如你的团队约定 API 请求必须使用封装好的request()方法不能直接用fetch()。因为request()内置了认证 Token 注入、错误处理和日志上报。新人入职后可能不知道这个约定直接用fetch()调接口。又比如你的团队规定组件文件夹结构必须是ComponentName/index.tsxComponentName/style.module.cssComponentName/types.ts。如果有人在组件目录下直接创建ComponentName.tsx应该被 lint 拦下来。这些不是 bug不会导致程序崩溃。它们是代码坏味道——不会立即造成问题但长期积累会导致代码库一致性崩溃。ESLint 的自定义规则可以帮你自动化这些检查。graph LR A[源代码] -- B[ESLint Parser] B -- C[AST 抽象语法树] C -- D[自定义规则 1br/禁止直接使用 fetch] C -- E[自定义规则 2br/强制组件目录结构] C -- F[自定义规则 3br/禁止特定 import] D -- G{匹配?} G --|是| H[report message] G --|否| I[pass] E -- J{匹配?} E -- H E -- I F -- K{匹配?} F -- H F -- I style H fill:#ff6b6b,color:#fff style I fill:#51cf66,color:#fff本文将手把手教你用 AST 分析编写 ESLint 自定义规则覆盖三种真实场景。二、ESLint 规则的底层机制AST 遍历与 Context ReportESLint 规则的本质是AST 遍历器。ESLint 使用 espree 将源代码解析为 AST抽象语法树规则的每个方法会在遍历到对应节点类型时被调用。一个 ESLint 规则的生命周期ESLint 读取源代码文件espree 将代码解析为 AST遍历 AST 的每个节点对于每个节点调用匹配的规则方法如CallExpression、ImportDeclaration规则方法检查节点是否符合预期如果不符合调用context.report()报告问题理解 AST 结构是编写规则的关键。推荐使用 AST Explorer 来可视化任意代码的 AST 结构。三、三个实战规则从简单到复杂规则 1禁止直接使用fetch()// eslint-local-rules/no-direct-fetch.js module.exports { meta: { type: problem, docs: { description: 禁止直接使用 fetch()请使用封装的 request() 方法, recommended: true, }, messages: { noDirectFetch: 禁止直接使用 fetch()。请使用封装的 {{replacement}} 方法以确保统一的认证和错误处理。, }, schema: [ { type: object, properties: { replacement: { type: string }, }, additionalProperties: false, }, ], }, create(context) { const options context.options[0] || {}; const replacement options.replacement || request; return { CallExpression(node) { // 检查是否是 fetch(...) 调用 if (node.callee.type Identifier node.callee.name fetch) { context.report({ node, messageId: noDirectFetch, data: { replacement }, }); } // 也检查 window.fetch(...) 和 global.fetch(...) if (node.callee.type MemberExpression) { const obj node.callee.object; const prop node.callee.property; if ( (obj.name window || obj.name global) prop.name fetch ) { context.report({ node, messageId: noDirectFetch, data: { replacement }, }); } } }, }; }, };规则 2强制组件目录结构// eslint-local-rules/component-directory-structure.js const path require(path); module.exports { meta: { type: problem, docs: { description: 组件必须放在 ComponentName/index.tsx 目录下, recommended: true, }, messages: { invalidStructure: 组件文件应位于 {{componentName}}/index.tsx 目录下而不是 {{currentPath}}。, }, }, create(context) { const filename context.getFilename(); const dir path.dirname(filename); const ext path.extname(filename); // 只检查 .tsx 和 .jsx 文件 if (![.tsx, .jsx].includes(ext)) return {}; // 检查是否在 components 目录下 if (!dir.includes(components)) return {}; const basename path.basename(filename, ext); const parentDir path.basename(dir); // 如果文件名和父目录名不同且文件不是 index.tsx if (basename ! index basename ! parentDir) { // 检查是否存在同名目录 const componentName basename; context.report({ loc: { line: 1, column: 0 }, messageId: invalidStructure, data: { componentName, currentPath: filename, }, }); } return {}; }, };规则 3禁止特定的 import 来源// eslint-local-rules/no-restricted-imports-extended.js module.exports { meta: { type: problem, docs: { description: 禁止从特定模块导入支持动态匹配, recommended: true, }, messages: { restrictedImport: 禁止从 {{source}} 导入 {{name}}。原因: {{reason}}, }, schema: [ { type: array, items: { type: object, properties: { module: { type: string }, names: { type: array, items: { type: string } }, message: { type: string }, }, required: [module], additionalProperties: false, }, }, ], }, create(context) { const restrictions context.options[0] || []; return { ImportDeclaration(node) { const source node.source.value; for (const rule of restrictions) { // 支持正则匹配模块名 const pattern new RegExp(rule.module); if (!pattern.test(source)) continue; for (const specifier of node.specifiers) { const name specifier.imported?.name || specifier.local.name; // 如果指定了 names只检查列出的名称 if (rule.names !rule.names.includes(name)) continue; context.report({ node: specifier, messageId: restrictedImport, data: { source, name, reason: rule.message || 请使用团队规定的替代方案。, }, }); } } }, }; }, };在.eslintrc.js中使用module.exports { plugins: [local-rules], rules: { local-rules/no-direct-fetch: [error, { replacement: request }], local-rules/component-directory-structure: error, local-rules/no-restricted-imports-extended: [error, [ { module: lodash, message: 请使用 lodash-es 以支持 Tree Shaking。, }, { module: moment, message: 请使用 dayjs 替代 moment。, }, { module: axios, names: [default], message: 请使用封装的 apiClient 替代直接导入 axios。, }, ]], }, };四、自定义规则的维护成本性能影响每个自定义规则都会增加 ESLint 的运行时间。如果规则在每个文件上做了大量 I/O 操作如读取文件系统会严重影响 lint 速度。建议为 I/O 操作添加缓存。AST 兼容性ESLint 的 AST 格式稳定但如果你使用了 TypeScript 或 JSX需要确保 parser 配置支持相应的语法。规则的粒度不要为一次性的事件编写规则。好的规则应该覆盖可重复出现的模式。不适用场景代码风格偏好如用单引号还是双引号——用 Prettier 处理对运行时逻辑的检查如这个 API 应该带 debounce——这是 Code Review 的职责复杂的跨文件分析ESLint 单文件规则的限制让它不适合跨文件检查五、总结ESLint 自定义规则的核心价值是将团队的隐性约定转为显性约束。当每个人都应该知道变成一个报错提示时新人不再需要靠口口相传学习规范。落地路径先记录团队中反复在 Code Review 中被指出的问题持续 2 周找出其中有固定模式的 3 个问题用 AST Explorer 分析代码结构后编写自定义规则最后在 CI 中强制执行error 级别不允许绕过。少即是多。不要为所有事情写规则——只为那些反复出现且能自动化检查的模式写规则。

相关新闻

ARIMA模型实战:Python statsmodels 0.14 电商销量预测,MAPE误差降至8%

ARIMA模型实战:Python statsmodels 0.14 电商销量预测,MAPE误差降至8%

ARIMA模型实战:Python statsmodels 0.14 电商销量预测,MAPE误差降至8% 1. 电商销量预测的业务挑战与ARIMA解决方案 在电商运营中,精准的销量预测是库存管理、营销策略和供应链优化的核心。传统方法如移动平均或简单指数平滑往往难以捕捉销售…

2026/7/9 9:20:29阅读更多 →
web应用课程设计最终项目展示

web应用课程设计最终项目展示

云边书屋|基于Spring BootVue3的前后端分离图书管理系统一、项目简介 云边书屋是一款Spring Boot Vue3开发的前后端分离在线书库管理系统,项目寓意“于云端书海之畔,打造极简治愈的线上阅读空间”。系统兼具用户阅读体验与后台管理能力&…

2026/7/9 9:20:28阅读更多 →
京东app 滑块验证码逆向分析

京东app 滑块验证码逆向分析

声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!有相关问题请第一时间点击头像看简介或私…

2026/7/9 9:15:27阅读更多 →
终极指南:如何将你的旧电视盒子变身高性能Armbian服务器

终极指南:如何将你的旧电视盒子变身高性能Armbian服务器

终极指南:如何将你的旧电视盒子变身高性能Armbian服务器 【免费下载链接】amlogic-s9xxx-armbian Supports running Armbian on Amlogic, Allwinner, and Rockchip devices. Support a311d, s922x, s905x3, s905x2, s912, s905d, s905x, s905w, s905, s905l, rk3588…

2026/7/9 10:30:44阅读更多 →
MCP3551与TM4C1294NCPDT高精度ADC系统设计与实现

MCP3551与TM4C1294NCPDT高精度ADC系统设计与实现

1. MCP3551与TM4C1294NCPDT硬件架构解析 MCP3551是一款22位Δ-Σ型模数转换器(ADC),专为高精度测量应用设计。其核心特性包括: 22位无失码分辨率,有效位数(ENOB)可达21位 内置低噪声可编程增益放大器(PGA),增益可选1/2/4/8/16/3…

2026/7/9 10:30:44阅读更多 →
常州星宇EDI案例:知行EDI驱动全球供应链协同

常州星宇EDI案例:知行EDI驱动全球供应链协同

星宇案例概览 常州星宇车灯股份有限公司是一家专注于汽车车灯、智能座舱、智能驾驶及控制模块等产品的汽车零部件企业。星宇创立于 1993 年,2011 年在上海证券交易所上市,公司总部位于常州,并在德国、日本、塞尔维亚、墨西哥、美国等国家设有…

2026/7/9 10:30:44阅读更多 →
【JAVA毕设源码分享】基于SpringBoot的新能源二手汽车销售平台的设计与实现(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于SpringBoot的新能源二手汽车销售平台的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/9 10:30:44阅读更多 →
5分钟掌握Figma设计转代码:终极自动化工具解放前端开发

5分钟掌握Figma设计转代码:终极自动化工具解放前端开发

5分钟掌握Figma设计转代码:终极自动化工具解放前端开发 【免费下载链接】FigmaToCode Generate responsive pages and apps on HTML, Tailwind, Flutter and SwiftUI. 项目地址: https://gitcode.com/gh_mirrors/fi/FigmaToCode 还在为Figma设计稿转代码而烦…

2026/7/9 10:30:44阅读更多 →
D3KeyHelper完全指南:5分钟打造你的暗黑3专属按键助手

D3KeyHelper完全指南:5分钟打造你的暗黑3专属按键助手

D3KeyHelper完全指南:5分钟打造你的暗黑3专属按键助手 【免费下载链接】D3keyHelper D3KeyHelper是一个有图形界面,可自定义配置的暗黑3鼠标宏工具。 项目地址: https://gitcode.com/gh_mirrors/d3/D3keyHelper 想要在《暗黑破坏神3》中解放双手&…

2026/7/9 10:25:43阅读更多 →
从GitHub安全案例解析常见漏洞与防护实践

从GitHub安全案例解析常见漏洞与防护实践

1. 项目概述:从GitHub Trending看安全实战 最近在GitHub Trending上看到一个项目,叫 skills4/skills ,它因为一些安全漏洞案例被大家讨论。这其实是一个挺典型的场景:一个旨在展示或教授某种技能的仓库,本身却成了安…

2026/7/9 5:56:19阅读更多 →
MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

# MLT 2026启示:因果推理与概率建模驱动下一代LLM应用## 一、背景与挑战:从“黑箱预测”到“可信推理”2026年6月,第7届机器学习与趋势国际会议(MLT 2026)将在悉尼召开。会议议程中,“因果与可解释机器学习…

2026/7/9 9:45:20阅读更多 →
通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

1. 项目概述与漏洞背景最近在梳理一些历史OA系统的安全风险时,通达OA v11.6版本中的一个老漏洞又进入了我的视线。这个漏洞位于/general/bi_design/appcenter/report_bi.func.php文件中,是一个典型的SQL注入点。虽然这个漏洞的利用方式看起来并不复杂&am…

2026/7/9 2:47:07阅读更多 →
Three.js 着色器光效教程

Three.js 着色器光效教程

着色器光效 Shader Light ▶ 在线运行案例 案例合集: 三维可视化功能案例(threehub.cn)开源仓库github地址: https://github.com/z2586300277/three-cesium-examples400个案例代码: 网盘链接 你将学到什么 ShaderMaterial 自…

2026/7/9 0:04:37阅读更多 →
如何5分钟掌握CS2智能库存管理:开源工具CASEMOVE终极指南

如何5分钟掌握CS2智能库存管理:开源工具CASEMOVE终极指南

如何5分钟掌握CS2智能库存管理:开源工具CASEMOVE终极指南 【免费下载链接】casemove A dedicated desktop app that enables you to move items in and out of storage units in CS2. 项目地址: https://gitcode.com/gh_mirrors/ca/casemove 还在为CS2存储单…

2026/7/9 0:04:37阅读更多 →
GBase 8a vs MySQL 8.0:ALTER TABLE语法与限制的5点关键差异对比

GBase 8a vs MySQL 8.0:ALTER TABLE语法与限制的5点关键差异对比

GBase 8a与MySQL 8.0:ALTER TABLE语法差异深度解析与实战指南1. 两种数据库的ALTER TABLE能力全景对比在数据库架构设计和运维过程中,表结构变更(DDL操作)是不可避免的需求。GBase 8a作为国产分析型数据库代表,与开源M…

2026/7/9 0:04:37阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/9 9:45:20阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/8 13:42:39阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/8 13:42:39阅读更多 →