eslint-plugin-unicorn 的 consistent-compound-words 规则:统一标识符中复合词拼写的完整指南
2026/9/18 1:20:06 网站建设 项目流程

eslint-plugin-unicorn 的 consistent-compound-words 规则:统一标识符中复合词拼写的完整指南

【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn

本篇指南以 eslint-plugin-unicorn 仓库中consistent-compound-words规则为主线,系统讲解该规则如何统一标识符中复合词的拼写风格、内置的 52 组默认替换词表、全部 8 个可配置选项(replacementsallowListcheckProperties等)以及底层的正则匹配与安全重命名实现原理。读完本文,你将能够在自己的 ESLint 配置中精准启用、定制并理解这条规则,从而让代码库中passWord/passworduserName/username这类写法不一致的问题被自动发现并一键修复。

规则是什么:为什么复合词拼写需要被约束

在遵循 camelCase 等标识符命名规范的代码库中,同一个复合词往往会被不同开发者写成不同形态:有人写passWord,有人写password;有人写isInViewPort,有人写isInViewport。这些写法在 JavaScript 语义上完全等价,但会导致同一概念在代码中出现多种大小写形态,破坏检索一致性、增加心智负担。

consistent-compound-words规则的职责正是解决这个问题:把复合词当作一个整体来套用标识符大小写约定,从而保证同一复合词在全部标识符中拼写一致。该规则文档位于 docs/rules/consistent-compound-words.md,其实现位于 rules/consistent-compound-words.js。

需要特别强调的是,这条规则不是拼写检查器,也不是散文风格规则——它只针对一份保守精选的、代码标识符中常见的复合词拼写错误清单进行检查,避免误伤正常命名。

基本示例

// ❌ 错误:passWord 应为 password const passWord = 'secret'; // ✅ 正确 const password = 'secret';
// ❌ 错误:ViewPort 应作为一个整体单词 const isInViewPort = true; // ✅ 正确 const isInViewport = true;
// ❌ 错误:unSubscribe 应为 unsubscribe function unSubscribe() {} // ✅ 正确 function unsubscribe() {}

从源码的 meta 定义可见,该规则类型为suggestionrecommended级别为unopinionated,并且hasSuggestions: true,意味着它可以通过编辑器建议(editor suggestions)手动修复,即不需要--fix也会在编辑器中给出可点击的一次性重命名建议(rules/consistent-compound-words.js)。同时该规则已启用在上文文档头部声明的recommendedunopinionated两套预设配置中(对应 configs/flat-config-base.js 等配置文件所导出的规则集)。

内置默认替换词表:一份保守的复合词清单

规则的全部默认替换映射定义在源码的defaultReplacements对象中(rules/consistent-compound-words.js)。下表完整列出默认检查的 52 组词(被禁止写法推荐写法):

禁止写法推荐写法禁止写法推荐写法
backGroundbackgroundsideBarsidebar
callBackcallbacksubClasssubclass
checkBoxcheckboxsubDirectorysubdirectory
clipBoardclipboardsubDomainsubdomain
codeBasecodebasesubMenusubmenu
dataBasedatabasesubProcesssubprocess
downLoaddownloadsubStringsubstring
feedBackfeedbacksubTreesubtree
foreGroundforegroundsubTypesubtype
frameWorkframeworksubTitlesubtitle
headLineheadlinetimeOuttimeout
keyBoardkeyboardtimeStamptimestamp
keyFramekeyframetoolBartoolbar
lifeCyclelifecycletoolKittoolkit
metaDatametadatatoolTiptooltip
midPointmidpointtouchScreentouchscreen
nameSpacenamespaceunSubscribeunsubscribe
overRideoverrideunderScoreunderscore
passWordpasswordupLoadupload
payLoadpayloaduserNameusername
placeHolderplaceholderviewPortviewport
preViewpreviewwebCamwebcam
screenShotscreenshotwebHookwebhook
whiteSpacewhitespacewebSitewebsite
wildCardwildcardweekEndweekend
workFlowworkflowworkSpaceworkspace

匹配的大小写适应性

该规则对大小写是自适应的:上述词表以驼峰形态给出,但匹配时同时考虑小写首字母形态与大写首字母形态。根据源码中的注释(rules/consistent-compound-words.js),小写首字母形态只匹配标识符开头,而大写首字母形态可以匹配标识符的任意复合段。这意味着:

  • passWordmyPassWordpassWordAndUserName都会被命中(测试用例见 test/consistent-compound-words.js);
  • 类名ViewPortStateclass ViewPort {}也会被报告,替换时会把ViewPort段替换为Viewport
  • 全大写常量VIEW_PORT不会被检查(源码getNameReplacement中显式跳过isUpperCase(name),见 rules/consistent-compound-words.js),因为全大写形态本身就是对复合词的可接受表达。

单词边界的判断

规则不是简单子串替换。匹配使用了一个精心构造的边界正则(?=$|[\d_$]|\p{Uppercase_Letter})(rules/consistent-compound-words.js),保证只命中“真正的复合词段”。因此测试中以下命名都被判定为合法:

  • foo_userNameversion2userName——下划线和数字会打断匹配;
  • compassWordmyViewPortionendPointpostFixpreFixprotoTyperoadMap——这些并不是清单中的禁止写法,只是形似;
  • XMLHttpRequest——全大写形态被跳过。

(以上合法用例均可在 test/consistent-compound-words.js 的 valid 列表中核对。)

刻意不检查的内容:保护外部 API 面

规则文档专门用一节说明“Intentionally not checked”(docs/rules/consistent-compound-words.md),这一点在源码与测试中有完整印证:

  1. 字符串键、计算属性、属性读取、JSX 属性、导出别名不检查。它们往往是外部 API 表面,保持原拼写比强行规范化更重要。例如const options = {"timeOut": 1000}foo[timeOut] = 1<input passWord="current" />均合法;export {username as userName}也不会被报告(见 test/consistent-compound-words.js 与 test/consistent-compound-words.js 的 JSX 用例)。
  2. 歧义或常见 API 拼写被显式排除:如fileNamesetUplookUpnewLine,以及源码注释中点名的onLineoffLinestyleSheetsuperClass(rules/consistent-compound-words.js)。这些词在“各词保持独立含义”的场景下是自然标识符——例如“一个新创建的行”而不是“换行符”。测试中const newLine = "\n"navigator.onLine = isOnlinedocument.styleSheet = styleSheetclass Foo { superClass = Base; }均为合法(见 valid 用例列表)。

完整选项指南

规则接受一个对象作为第二参数,全部选项及默认值如下(对应源码prepareOptions与 schema 定义,见 rules/consistent-compound-words.js 和 rules/consistent-compound-words.js):

replacements(object,默认{}

在默认替换表的基础上扩展自定义替换。值为false可以显式禁用某个默认替换,值为字符串则是自定义的“禁止写法 → 推荐写法”映射:

'unicorn/consistent-compound-words': [ 'error', { replacements: { fooBar: 'foobar', passWord: false, // 禁用内置的 passWord → password }, }, ]

注意 schema 约束:键名长度至少为 1,值必须是长度至少为 1 的字符串或false(rules/consistent-compound-words.js)。测试中也覆盖了自定义替换的验证(test/consistent-compound-words.js)。

extendDefaultReplacements(boolean,默认true

当设为false时,replacements完全覆盖默认替换表而不是扩展:

'unicorn/consistent-compound-words': [ 'error', { extendDefaultReplacements: false, replacements: { fooBar: 'foobar', }, }, ]

该逻辑在源码中体现为extendDefaultReplacements ? {...defaultReplacements, ...replacements} : replacements(rules/consistent-compound-words.js)。

allowList(object,默认{}

按大小写精确跳过整个标识符名称,值必须是true。适用于希望在个别位置保留历史命名的场景:

'unicorn/consistent-compound-words': [ 'error', { allowList: { legacyUserName: true, }, }, ]

允许列表在源码中转换为Set并参与getNameReplacement的前置判断(rules/consistent-compound-words.js 与 rules/consistent-compound-words.js)。schema 强制其值必须为true(rules/consistent-compound-words.js),测试中allowList: {userName: false}会触发校验错误(test/consistent-compound-words.js)。

checkVariables(boolean,默认true

是否检查变量名。设为false可关闭对变量(包括函数名、类名等绑定标识符)的检查,只保留属性检查能力(配合checkProperties使用):

'unicorn/consistent-compound-words': [ 'error', { checkVariables: false, checkProperties: true, }, ]

源码中变量检查在Program:exit阶段基于 scope 变量统一进行(rules/consistent-compound-words.js)。

checkProperties(boolean,默认false

设为true后,将检查属性定义与属性写入。源码中reportProperty通过IdentifierPrivateIdentifier事件处理(rules/consistent-compound-words.js),具体覆盖:

  • 属性写入:foo.userName = 1foo.userName++++foo.userName
  • 对象字面量属性:({passWord: 1})(非简写形式);
  • 类成员与 TypeScript 声明:类字段viewPort、私有字段#passWordinterface/type成员、accessor、抽象成员等(见 TypeScript 测试组 test/consistent-compound-words.js);
  • 特别地,__proto__ExportSpecifier会被跳过(rules/consistent-compound-words.js)。

checkDefaultAndNamespaceImports'internal' | boolean,默认'internal'

控制默认导入与命名空间导入(含静态require())的变量名是否检查:

  • 'internal'(默认):只检查指向内部模块的导入,即模块路径以./开头且不包含node_modules(判断逻辑见 rules/shared/identifier-checks.js)。因此import userName from "user-name"(外部包)默认合法,而import userName from "./user-name.js"会被报告;
  • true:所有默认/命名空间导入都检查(import userName from "user-name"也会被报告,见 test/consistent-compound-words.js);
  • false:完全不检查这类导入变量。

checkShorthandImports'internal' | boolean,默认'internal'

与上一项对称,控制简写导入import {userName} from "...")的本地绑定名是否检查。'internal'同样只检查内部模块的简写导入(rules/shared/identifier-checks.js)。测试中import {userName} from "user-name"默认合法,开启checkShorthandImports: true后会被报告。

checkShorthandProperties(boolean,默认false

设为true后,检查对象解构模式中作为简写属性声明的变量,例如const {userName} = object中的userName。默认关闭是因为简写属性同时是变量声明,其重命名会影响外部契约,源码中通过isShorthandPropertyValue判断(rules/consistent-compound-words.js)。

选项合法性校验

规则的 JSON schema 对上述选项做了严格约束:不允许出现未声明属性(如extendDefaultAllowList会直接报错)、checkDefaultAndNamespaceImports/checkShorthandImports只能是'internal'或布尔值、replacements的键名和值均有最小长度要求、allowList的值必须为true。这些约束都有对应的 schema 校验测试兜底(test/consistent-compound-words.js)。

底层原理:单次正则扫描与安全重命名

合并正则:一次扫描全部词表

规则将每个禁止写法生成小写首字母与大写首字母两种形态,并把它们合并到一个正则中(buildReplacementRegExp,rules/consistent-compound-words.js)。源码注释解释了这样做的动机:如果不合并,规则就要为每个替换词、对文件里每个标识符分别编译并执行一次正则,性能开销巨大;合并后每个标识符只需一次replaceAll扫描即可完成所有词的匹配。

替换时保持大小写

getReplacementForPart会根据被匹配段的首字母大小写,将推荐写法相应调整为upperFirstlowerFirst(rules/consistent-compound-words.js),保证ViewPortViewportviewPortviewport这种大小写自适应的替换行为。

编辑器建议修复:先查冲突再重命名

当命中变量时,规则不会盲目建议重命名,而是先做两件事:

  1. 作用域冲突检查:通过getAvailableVariableName在同作用域内寻找无冲突的替换名,并用scopeToNamesGeneratedByFixer记录本次报告已生成的名称,避免多个建议互相冲突(rules/consistent-compound-words.js 与 rules/consistent-compound-words.js);
  2. 安全重命名判断shouldRenameVariable(rules/shared/identifier-checks.js)会跳过被导出的标识符(避免破坏export的外部契约)与 JSX 标签名(<UserNameField />这类组件名重命名会引发解析错误)。此外,若变量被 Vue 模板引用(reference.vueUsedInTemplate),也只报错不给出重命名建议。

满足条件时,规则会生成一条带有fix的 suggestion,实际执行重命名的是 rules/fix/rename-variable.js,它通过getVariableIdentifiers收集该变量的全部标识符(定义、引用、解构别名等),逐一用replaceReferenceIdentifier替换为新名称。这也是为什么测试中能对function getUserName(userName) { return userName; }这类跨引用场景给出完整修复(见 test/consistent-compound-words.js 的 invalid 用例)。

类作用域与 TypeScript 参数属性的特殊处理

源码还处理了两个相对隐蔽的场景:

  • 类名合成变量:ESLint 的 scope 分析会为类名创建合成变量,规则通过isClassVariable识别并合并外层类变量的引用,避免重复报告(rules/consistent-compound-words.js,辅助函数见 rules/shared/identifier-checks.js);
  • TypeScript 构造函数参数属性:如constructor(private userName: string) {},这类标识符兼具“参数”与“属性”双重身份。规则中的isTSParameterPropertyName专门判断该场景:当checkVariables关闭时仍可作为属性被检查,而当两个开关都开启时,测试显示它会被当作变量给出带修复的重命名建议(输出constructor(private username: string) {},见 test/consistent-compound-words.js)。

使用建议与注意事项

  • 从默认配置开始:该规则已包含在recommendedunopinionated预设中,直接启用预设即可获得默认词表的检查;单独启用可写作'unicorn/consistent-compound-words': 'error'
  • 渐进式落地:对存量代码库,可先用allowList放行无法立即改动的历史名称(如legacyUserName),或通过replacements: {passWord: false}单独关闭某组争议词,再逐步收敛。
  • 区分变量与属性:默认checkProperties: false意味着属性名(尤其是对象字面量键)默认不受影响;如果你的项目属性命名也需要统一,再显式开启,并留意它不会触碰字符串键、计算属性与 JSX 属性等外部 API 表面。
  • 导入名默认只查内部模块checkDefaultAndNamespaceImportscheckShorthandImports'internal'默认值保证了外部依赖的命名不会被强行改写,这通常是想要的行为;只有对自研模块的导入名有强约束需求时再改为true

需要提醒的是,该规则刻意不检查fileNamesetUplookUpnewLine等常见歧义拼写(详见“刻意不检查的内容”一节),因此它解决的是“同一概念不同写法”的一致性问题,而不是完整的命名风格规范——后者需要与仓库中其他命名类规则(如 name-replacements)配合使用。若在编辑器中看到“PreferusernameoveruserName.”或“Rename tousername.”的提示,前者是规则报错信息,后者是可点击执行的编辑器建议,点击后规则会基于作用域分析自动完成全部引用的安全重命名。

【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询