概述 本文介绍如何自定义ESLint规则,并将其整合到SonarQube平台进行统一的代码质量管理。通过这种方式,可以将团队的编码规范和最佳实践落地到实际项目中,在编码阶段对开发者进行提示和约束。
背景介绍 ESLint简介 ESLint是目前最流行的JavaScript代码静态分析工具,通过设定的语法规则来检查代码,约束代码风格,提高代码的健壮性,避免因代码不规范导致应用出现bug。
核心特点:
规则可自定义,适应团队特定需求 支持使用社区热门规则集(如Airbnb、Standard等) 可扩展的插件机制 SonarQube简介 SonarQube是一个开源的代码质量管理平台,用于持续检测代码质量和安全漏洞。
支持语言:
Java, C#, C/C++, PL/SQL, Cobol JavaScript, TypeScript, Python, Go 等二十几种主流编程语言 为什么需要自定义规则 在实际业务中,通过自定义规则可以:
落地编码规范 :将团队约定的编码规范转化为可执行的检查规则提前发现问题 :在编码阶段即时提示,而非等到代码审查统一代码风格 :多人协作时保持一致的代码风格集中管理 :通过SonarQube统一管理所有项目的代码质量技术选型 根据SonarQube官方文档,JavaScript项目建议使用ESLint进行整合。
参考资料:
自定义ESLint规则开发 ESLint工作原理 ESLint使用Espree 解析器将代码转换为AST(抽象语法树),然后基于AST进行代码检查。
示例代码分析:
以 let age = 10 为例,使用AST Explorer 查看其AST结构:
AST结构解析:
顶层节点:VariableDeclaration(变量声明) 子节点包含:let 关键字age 标识符(Identifier)10 字面量值 通过AST,可以精确定位和检查代码的每个节点。
项目初始化 本示例将创建一个检查SQL代码中LEFT JOIN使用次数的ESLint规则。
参考项目:
安装脚手架工具 ESLint官方提供了Yeoman模板生成器,方便快速创建插件项目。
1 npm install -g yo generator-eslint
创建项目目录 目录命名格式:eslint-plugin-<plugin-name>
1 2 mkdir eslint-plugin-democd eslint-plugin-demo
初始化插件项目 交互式配置:
1 2 3 4 5 ? What is your name? xiaowu ? What is the plugin ID? eslint-plugin-demo ? Type a short description of this plugin: 最大左连接个数 ? Does this plugin contain custom ESLint rules? Yes ? Does this plugin contain one or more processors? No
创建具体规则 生成规则模板 交互式配置:
1 2 3 4 5 6 ? What is your name? xiaowu ? Where will this rule be published? ❯ ESLint Plugin ? What is the rule ID? no-more-left-join ? Type a short description of this rule: 检查SQL中LEFT JOIN的使用次数 ? Type a short example of the code that will fail:
项目结构 1 2 3 4 5 6 7 8 9 10 11 12 13 ├── README.md ├── docs │ └── rules │ └── no-more-left-join.md ├── lib │ ├── index.js │ └── rules │ └── no-more-left-join.js ├── package.json └── tests └── lib └── rules └── no-more-left-join.js
编写规则代码 理解核心对象 参考文档: ESLint自定义规则
核心对象说明:
meta对象 :包含规则的元数据
type:规则类型(problem/suggestion/layout)docs:规则文档描述fixable:是否支持自动修复schema:规则配置参数context对象 :提供额外功能
AST节点分析 将需要检查的SQL代码粘贴到AST Explorer 进行分析:
**分析结果:**需要监听的AST节点类型为 ExportNamedDeclaration
规则实现代码 文件路径: lib/rules/no-more-left-join.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 "use strict" ;const { joinValid } = require ("../utilities/index" );module .exports = { meta : { type : 'problem' , docs : { description : "限制SQL文件中的LEFT JOIN数量" , recommended : true , url : null , }, fixable : 'code' , schema : [ { type : 'object' , properties : { maxLeftJoin : { type : 'number' , }, }, additionalProperties : false , }, ], messages : { exceedMaxJoin : 'SQL文件中的{{joinType}}数量({{count}})超过限制({{max}}次)' , }, }, create (context ) { return { ExportNamedDeclaration (node) { if (node.declaration ?.declarations ?.length ) { node.declaration .declarations .forEach (declaration => { joinValid (declaration.init , context, 'left join' ); }); } }, }; }, };
辅助函数实现 文件路径: lib/utilities/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 function joinValid (node, context, joinStr ) { if (!node) { return ; } let maxJoin = 3 ; if (context.options [0 ]?.maxLeftJoin !== undefined && joinStr === 'left join' ) { maxJoin = context.options [0 ].maxLeftJoin ; } if (context.options [0 ]?.maxRightJoin !== undefined && joinStr === 'right join' ) { maxJoin = context.options [0 ].maxRightJoin ; } if (node.type === 'TaggedTemplateExpression' ) { node = node.quasi ; } let literal = "" ; if (node.type === 'ArrowFunctionExpression' ) { if (node.body .type === 'TemplateLiteral' && node.body .quasis .length ) { literal = node.body .quasis .map (quasi => quasi.value .raw ).join ('' ); } } else if (node.type === 'TemplateLiteral' && node.quasis .length ) { literal = node.quasis .map (quasi => quasi.value .raw ).join ('' ); } if (literal.length > 0 && isSqlQuery (literal)) { literal = sqlFormat (literal); const joinCount = literal.split (joinStr).length - 1 ; if (joinCount > maxJoin) { context.report ({ node, message : `SQL文件中的${joinStr} 数量超过${maxJoin} 次` , }); } } } module .exports = { joinValid, };
单元测试 文件路径: tests/lib/rules/no-more-left-join.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 "use strict" ;const rule = require ("../../../lib/rules/no-more-left-join" );const RuleTester = require ("eslint" ).RuleTester ;const ruleTester = new RuleTester ({ parserOptions : { sourceType : 'module' , ecmaVersion : 2015 , }, }); ruleTester.run ("no-more-left-join" , rule, { invalid : [ { code : `export const getContractsSql = \` SELECT * FROM x LEFT JOIN a ON a.id = x.id LEFT JOIN b ON b.id = x.id WHERE 1=1 \${sqlConditions} ORDER BY update_time DESC \${sqlLimit} \`;` , options : [{ maxLeftJoin : 1 }], errors : [{ message : 'SQL文件中的 left join 数量超过 1 次' , }], }, ], valid : [ { code : "import {inspect} from 'util';" , options : [{ maxLeftJoin : 1 }], }, { code : `export const getSimpleSql = \` SELECT * FROM x LEFT JOIN a ON a.id = x.id WHERE 1=1 \`;` , options : [{ maxLeftJoin : 1 }], }, ], });
运行测试:
预期输出: 2 passing
项目发布 详细的npm包发布流程请参考:npm包发布指南
发布步骤概览:
确保package.json配置正确 登录npm账号:npm login 发布包:npm publish 规则应用 安装依赖 在需要使用该规则的项目中安装:
1 npm install eslint-plugin-demo -D
配置ESLint 文件路径: .eslintrc.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 module .exports = { plugins : ['demo' ], rules : { "demo/no-more-left-join" : ["error" , { maxLeftJoin : 1 }], }, };
规则配置说明:
"error":违反规则时报错{ maxLeftJoin: 1 }:最大允许1次LEFT JOINSonarQube整合 导出ESLint报告 在项目构建阶段导出ESLint检查报告:
1 2 3 eslint ./ --ext .ts,.js -f json -o eslint_report.json; exit 0
命令参数说明:
./:检查当前目录--ext .ts,.js:检查TypeScript和JavaScript文件-f json:输出JSON格式-o eslint_report.json:输出到指定文件; exit 0:确保命令总是成功退出SonarQube导入报告 使用SonarScanner导入ESLint报告到SonarQube:
1 2 3 4 5 6 /usr/local/src/sonar-scanner/bin/sonar-scanner \ -Dsonar.projectKey=${CI_PROJECT_NAME} \ -Dsonar.sources=. \ -Dsonar.eslint.reportPaths=eslint_report.json \ -Dsonar.host.url=${SONAR_HOST} \ -Dsonar.login=${SONAR_LOGIN}
参数说明:
sonar.projectKey:项目唯一标识sonar.sources:源代码路径sonar.eslint.reportPaths:ESLint报告文件路径sonar.host.url:SonarQube服务器地址sonar.login:认证令牌效果展示 导入成功后,可以在SonarQube平台查看检查结果:
展示内容包括:
代码问题总览 具体违规代码位置 问题严重程度 建议修复方案 最佳实践 规则设计原则 简单明确 :规则逻辑清晰,易于理解可配置 :提供配置选项,适应不同场景性能优化 :避免过度复杂的AST遍历错误提示 :提供清晰的错误信息和修复建议团队协作建议 统一配置 :在项目根目录维护统一的ESLint配置文档完善 :为自定义规则编写详细的使用文档持续优化 :根据实际使用情况不断优化规则版本管理 :使用语义化版本管理规则插件CI/CD集成 建议将ESLint检查和SonarQube分析集成到CI/CD流程中:
1 2 3 4 5 6 7 lint: stage: test script: - npm run lint - eslint ./ --ext .ts,.js -f json -o eslint_report.json; exit 0 - sonar-scanner -Dsonar.projectKey=$CI_PROJECT_NAME ...
总结 通过自定义ESLint规则并整合SonarQube,我们可以:
自动化检查 :将编码规范自动化,减少人工审查成本提前发现问题 :在开发阶段就发现潜在问题统一标准 :团队使用统一的代码质量标准可视化管理 :通过SonarQube平台直观查看代码质量这种方案特别适合中大型团队,能够有效提升代码质量和开发效率。