ESLint camelcase 规则详解:强制标识符驼峰命名法(命名风格检查与配置实战)
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
本篇技术指南围绕 ESLint 内置规则camelcase展开,系统讲解其如何在 JavaScript 代码中强制变量、函数、属性、解构与导入等标识符采用驼峰命名(camelCase)。文中将完整覆盖该规则的检测逻辑、全部 6 个配置项及其默认值、正反例代码、与properties/ignoreDestructuring/ignoreImports/ignoreGlobals/allow组合的典型实战场景,并结合仓库源码 lib/rules/camelcase.js 与测试用例 tests/lib/rules/camelcase.js 剖析其底层实现原理,帮助读者在团队项目中精准启用、调优或豁免该命名检查。
规则概览:为什么需要 camelcase
在命名变量时,风格指南通常分为两大阵营:驼峰命名(variableName)与下划线命名(variable_name)。ESLint 的camelcase规则(rule type 为suggestion,见 docs/src/rules/camelcase.md 文件头的 front matter)专注于强制使用驼峰命名。如果你的团队风格指南要求变量名采用驼峰形式,那么这条规则就是为你准备的。
在仓库的规则注册表 lib/rules/index.js 中可以看到,camelcase通过懒加载方式注册:camelcase: () => require("./camelcase")。同时它并非eslint:recommended预设的一部分——测试 tests/conf/eslint-recommended.js 明确断言assert.notProperty(rules, "camelcase"),这意味着它需要你在配置中显式开启。
Rule Details:核心检测逻辑
该规则的检测逻辑可以概括为:查找源代码中任何出现在标识符中间位置的下划线(_)。具体规则如下:
- 忽略首尾下划线:只检查变量名中间部分的下划线,因此
_myFavoriteColor、myFavoriteColor_均视为合法。 - 常量豁免:如果 ESLint 判定该变量是常量(全大写,如
MY_FAVORITE_COLOR),则不会发出警告。 - 只标记定义与赋值:规则只针对定义(declaration)和赋值(assignment)进行检查,不检查函数调用。因此
do_something()这种调用不会报错。 - import 语句的特殊处理:对于 ES6
import语句,规则只检查导入到本地模块作用域的变量名(local name),不检查被导出的原始名称。
从源码实现看,这一逻辑对应 lib/rules/camelcase.js 中的isUnderscored函数:
function isUnderscored(name) { const nameBody = name.replace(/^_+|_+$/gu, ""); // if there's an underscore, it might be A_CONSTANT, which is okay return ( nameBody.includes("_") && nameBody !== nameBody.toUpperCase() ); }该函数先用正则/^_+|_+$/gu去掉首尾下划线,然后判断剩余部分是否包含下划线且不是全大写——二者同时成立才判定为"下划线命名",这正好对应文档中"忽略首尾下划线、全大写常量豁免"的两条规则。
当规则判定违规时,会通过context.report输出消息。源码 lib/rules/camelcase.js 中定义了两条消息:
notCamelCase:Identifier '{{name}}' is not in camel case.notCamelCasePrivate:#{{name}} is not in camel case.(用于类私有字段/方法)
此外,实现中通过reported集合(lib/rules/camelcase.js)记录已报告的节点起始位置,避免在简写形式(shorthand)的解构语法下对同一标识符重复报告。
Options:完整配置项说明
camelcase规则接受一个对象配置项,所有子项均有默认值。下表汇总了全部配置项:
| 配置项 | 取值 | 默认值 | 作用 |
|---|---|---|---|
properties | "always"/"never" | "always" | "always"强制对象/类属性名也遵循驼峰;"never"不检查属性名 |
ignoreDestructuring | true/false | false | true时不检查解构标识符(但之后对这些标识符的使用仍会被检查) |
ignoreImports | true/false | false | true时不检查 ES2015 import 导入名(但之后对这些导入的使用仍会被检查,函数参数除外) |
ignoreGlobals | true/false | false | true时不对全局变量强制驼峰命名 |
allow | string[] | [] | 允许命名的属性列表,支持正则字符串 |
这些默认值在源码 lib/rules/camelcase.js 的defaultOptions中均有体现,且其 JSON Schema(lib/rules/camelcase.js)约束了每个选项的类型:ignoreDestructuring、ignoreImports、ignoreGlobals为布尔值,properties限定为enum: ["always", "never"],allow为字符串数组(minItems: 0、uniqueItems: true,即允许空数组且元素不可重复),并且additionalProperties: false禁止传入未知选项。
properties: "always"(默认)
默认情况下,属性名也必须符合驼峰命名。以下代码会被判为错误(配置为/*eslint camelcase: "error"*/):
import { no_camelcased } from "external-module" const my_favorite_color = "#112C85"; function do_something() { // ... } obj.do_something = function() { // ... }; function foo({ no_camelcased }) { // ... }; function bar({ isCamelcased: no_camelcased }) { // ... } function baz({ no_camelcased = 'default value' }) { // ... }; const obj = { my_pref: 1 }; const { category_id = 1 } = query; const { foo: snake_cased } = bar; const { foo: bar_baz = 1 } = quz;而以下代码在默认配置下是正确的:
/*eslint camelcase: "error"*/ import { no_camelcased as camelCased } from "external-module"; const myFavoriteColor = "#112C85"; const _myFavoriteColor = "#112C85"; const myFavoriteColor_ = "#112C85"; const MY_FAVORITE_COLOR = "#112C85"; const foo1 = bar.baz_boom; const foo2 = { qux: bar.baz_boom }; obj.do_something(); do_something(); new do_something(); const { category_id: category } = query; function foo({ isCamelCased }) { // ... }; function bar({ isCamelCased: isAlsoCamelCased }) { // ... } function baz({ isCamelCased = 'default value' }) { // ... }; const { categoryId = 1 } = query; const { foo: isCamelCased } = bar; const { foo: camelCasedName = 1 } = quz;注意这些"正确"示例蕴含了几个关键细节:
const { category_id: category } = query;合法,因为只有新绑定的本地变量名(category)会被检查,属性来源名(category_id)不检查;bar.baz_boom作为只读引用不报错(规则不检查函数调用和只读属性读取);- 解构中若把
no_camelcased重命名为camelCased(as语法),本地名符合驼峰即通过。
这一点与源码中的检测范围一致:源码在ObjectExpression > Property[computed!=true] > Identifier.key等节点上检查属性名(lib/rules/camelcase.js),而MemberExpression[computed!=true] > Identifier.property的检查要求该成员表达式必须是赋值目标(isAssignmentTarget,见 lib/rules/camelcase.js),注释明确写着"ignore read-only references",这正是obj.do_something()只读调用不报错的原因。
properties: "never"
当你的代码需要与外部系统(如后端 API、数据库字段)交互而必须保留下划线属性时,可以关闭属性名检查。以下代码在{ properties: "never" }下是正确的:
/*eslint camelcase: ["error", {properties: "never"}]*/ const obj = { my_pref: 1 }; obj.foo_bar = "baz";测试 tests/lib/rules/camelcase.js 中还覆盖了obj.a_b = 2、obj._a = 2、obj.a_ = 2等赋值场景,均作为properties: "never"下的合法用例;同时 tests/lib/rules/camelcase.js 也验证了类字段(public/private)在两种配置下的行为:class C { snake_case; #snake_case; #snake_case2() {} }在properties: "never"下合法,而在properties: "always"下会报错(含私有名使用专门的notCamelCasePrivate消息)。
ignoreDestructuring: false(默认)
默认配置下,解构出来的标识符必须符合驼峰。以下代码都是错误的:
/*eslint camelcase: "error"*/ const { category_id } = query; const { category_name = 1 } = query; const { category_id: category_title } = query; const { category_id: category_alias } = query; const { category_id: categoryId, ...other_props } = query;注意最后一行:解构出的categoryId虽然合法,但 rest 属性other_props仍会被检查并报错。
ignoreDestructuring: true
开启后,只有解构模式内的标识符被豁免,但后续对这些变量的使用仍按默认或其它选项规则检查。该选项的常见用途是:当解构出的标识符后续根本不会被使用(或仅作为属性简写传给他人)时,避免无谓的重命名。
错误示例(解构模式内使用了非驼峰别名或 rest 属性):
/*eslint camelcase: ["error", {ignoreDestructuring: true}]*/ const { category_id: category_alias } = query; const { category_id, ...other_props } = query;正确示例:
/*eslint camelcase: ["error", {ignoreDestructuring: true}]*/ const { category_id } = query; const { category_name = 1 } = query; const { category_id_name: category_id_name } = query;请注意,该选项仅适用于解构模式内部的标识符,它并不会额外允许这些变量在后续代码中的任何特定使用。例如下面的代码会报错:
/*eslint camelcase: ["error", {ignoreDestructuring: true}]*/ const { some_property } = obj; // allowed by {ignoreDestructuring: true} const foo = some_property + 1; // error, ignoreDestructuring does not apply to this statement一个典型应用场景是:标识符后续不会使用,解构只是为了取出(或透传)该属性:
/*eslint camelcase: ["error", {ignoreDestructuring: true}]*/ const { some_property, ...rest } = obj; // do something with 'rest', nothing with 'some_property'另一个常见组合是配合{ "properties": "never" },当标识符只打算作为属性简写(shorthand)使用时:
/*eslint camelcase: ["error", {"properties": "never", ignoreDestructuring: true}]*/ const { some_property } = obj; doSomething({ some_property });该组合行为在测试 tests/lib/rules/camelcase.js 中有专门覆盖,其中引用了 eslint/eslint#15572 这一 issue 对应的场景。
从源码看,ignoreDestructuring依赖equalsToOriginalName函数(lib/rules/camelcase.js)判断:当解构标识符原样使用属性名(parent.key.name === localName、非计算属性、value 位置对应)时即豁免。同时reportReferenceId(lib/rules/camelcase.js)中,ignoreDestructuring只跳过这类"原样使用原属性名"的标识符,因此后续对some_property的其它使用(如some_property + 1)仍会报错——这与文档表述完全一致。另外 lib/rules/camelcase.js 还体现了另一个向后兼容细节:解构/参数的默认值(AssignmentPattern的right侧)中的引用被忽略。
ignoreImports: false(默认)
默认情况下,import 的本地绑定名必须符合驼峰。以下代码错误:
/*eslint camelcase: "error"*/ import { snake_cased } from 'mod';测试 tests/lib/rules/camelcase.js 中覆盖了大量 import 变体:默认导入、命名空间导入(import * as no_camelcased)、混合导入、字符串形式的导出名(import { 'snake_cased' as snake_cased })等,全部要求本地名符合驼峰。
ignoreImports: true
开启后,import 的本地绑定名不再被检查,但之后对这些导入标识符的使用仍会被检查,函数参数除外。
错误示例(默认导入与命名空间导入的本地名):
/*eslint camelcase: ["error", {ignoreImports: true}]*/ import default_import from 'mod'; import * as namespaced_import from 'mod';正确示例:
/*eslint camelcase: ["error", {ignoreImports: true}]*/ import { snake_cased } from 'mod';一个实用场景是:当第三方模块只导出下划线命名的成员,而你不想逐个as重命名时,ignoreImports: true允许导入语句直接照搬原名。测试 tests/lib/rules/camelcase.js 验证了import { snake_cased } from 'mod'、import { snake_cased as snake_cased }、import { 'snake_cased' as snake_cased }在ignoreImports: true下均合法;而 tests/lib/rules/camelcase.js 则说明默认导入import snake_cased from 'mod'与命名空间导入import * as snake_cased即使开启ignoreImports: true仍会报错(因为equalsToOriginalName只对ImportSpecifier生效,默认导入与命名空间导入不在此列)。
ignoreGlobals: false(默认)
默认情况下,全局变量引用也会被检查。以下代码错误:
/*eslint camelcase: ["error", {ignoreGlobals: false}]*/ /* global no_camelcased */ const foo = no_camelcased;测试 tests/lib/rules/camelcase.js 表明,通过languageOptions.globals或/* global */指令声明的全局变量,只要其名字含下划线且非全大写,在ignoreGlobals: false(或未配置)时都会被报告。
ignoreGlobals: true
开启后不再对全局变量强制驼峰。以下代码正确:
/*eslint camelcase: ["error", {ignoreGlobals: true}]*/ /* global no_camelcased */ const foo = no_camelcased;测试 tests/lib/rules/camelcase.js 中有大量组合验证:如var camelCased = a_global_variable、a_global_variable.foo()、({ a_global_variable } = foo)等,在ignoreGlobals: true下均合法。注意ignoreGlobals只管全局变量的引用——如果同一个名字又在本地被声明(如var a_global_variable),声明与本地引用仍会被报告(见 tests/lib/rules/camelcase.js 中ignoreGlobals: true下的错误用例)。从源码看,该逻辑对应Program处理器(lib/rules/camelcase.js):!ignoreGlobals时遍历scope.variables(配置或指令声明的全局),并通过scope.through(未定义的全局引用)始终进行检查。
allow
allow接受一个字符串数组,列出可被豁免的命名,支持正则字符串(以/^...$/u语义匹配)。典型的应用是 React 生命周期方法(如UNSAFE_componentWillMount)这类带下划线的历史遗留命名。
正确示例——精确字符串匹配:
/*eslint camelcase: ["error", {allow: ["UNSAFE_componentWillMount"]}]*/ function UNSAFE_componentWillMount() { // ... }正确示例——正则模式匹配:
/*eslint camelcase: ["error", {allow: ["^UNSAFE_"]}]*/ function UNSAFE_componentWillMount() { // ... } function UNSAFE_componentWillReceiveProps() { // ... }从源码 lib/rules/camelcase.js 看,isAllowed对allow中的每一项依次尝试两种匹配:先做严格相等比较,再做正则匹配(name.match(new RegExp(entry, "u")))。这意味着你可以混用字面量("UNSAFE_componentWillMount")与正则模式("^UNSAFE_"、"_id$")。测试中也展示了allow: ["_id$"]匹配user_id、allow: ["__option_foo__"]等场景(tests/lib/rules/camelcase.js),而allow: ["ignored_bar"]对not_ignored_foo不生效(tests/lib/rules/camelcase.js)。
实现细节与行为边界(源码视角)
深入源码 lib/rules/camelcase.js 可以梳理出该规则的完整检查面,这对理解"什么会被检查、什么不会被检查"很有帮助:
会被检查的语法面:
- 声明:
VariableDeclaration、FunctionDeclaration、FunctionExpression、ArrowFunctionExpression、ClassDeclaration、ClassExpression、CatchClause声明的变量(lib/rules/camelcase.js),声明及其后续引用都会被报告; - 对象/类属性名:
ObjectExpression、MethodDefinition、PropertyDefinition的非计算属性名,以及类的私有标识符(PrivateIdentifier,使用notCamelCasePrivate消息),受properties控制; - 成员表达式赋值目标:
obj.foo_bar = ...这种对非驼峰属性赋值的写法,受properties控制;只读引用obj.foo_bar不检查; - import 与 re-export:
ImportDeclaration的本地绑定名(受ignoreImports控制),以及ExportAllDeclaration、ExportSpecifier的导出名(lib/rules/camelcase.js); - 标签:
LabeledStatement、BreakStatement、ContinueStatement中的 label 名(lib/rules/camelcase.js)。
被豁免的情况:
- 首尾下划线(
_foo、foo_)、全大写常量(FOO_BAR); - 函数调用中的标识符(
do_something()、new do_something())——源码 lib/rules/camelcase.js 注释说明这是为向后兼容保留的行为; - 解构/参数的默认值(
AssignmentPattern右侧)中的引用; - 解构中"原样使用属性名"的标识符(配合
ignoreDestructuring); - import 属性键(Import attribute keys,如
import foo from 'foo.json' with { my_type: 'json' }中的my_type)——源码通过astUtils.isImportAttributeKey判断并始终忽略(lib/rules/camelcase.js),测试见 tests/lib/rules/camelcase.js; - 计算属性(computed)中的名称不受
properties的驼峰检查(相关选择器均带[computed!=true]限定); allow列表中命中的名称(精确匹配或正则匹配)。
配置示例与使用建议
在 flat config 中启用:
// eslint.config.js export default [ { rules: { camelcase: "error", }, }, ];在 eslintrc 配置中启用:
{ "rules": { "camelcase": "error" } }推荐组合示例——适配第三方下划线数据源:
{ "rules": { "camelcase": ["error", { "properties": "never", // 属性名可保留下划线(对接 API 字段) "ignoreDestructuring": true, // 解构出的原始字段名不强制重命名 "ignoreImports": true, // 第三方模块的下划线导出直接使用 "ignoreGlobals": true, // 不约束外部注入的全局变量 "allow": ["UNSAFE_", "_id$"] // 额外豁免指定命名模式 }] } }在单行/文件内豁免个别非驼峰命名:
const snake_case_value = 1; // eslint-disable-line camelcaseWhen Not To Use It:何时关闭该规则
如果团队已经建立了采用下划线分隔单词的命名规范(例如数据序列化字段、与后端数据库列名对齐的场景),那么应当关闭此规则。这正是官方文档"When Not To Use It"一节给出的明确指引,也与该规则未纳入eslint:recommended(见 tests/conf/eslint-recommended.js)、需要显式启用的定位一致。
在决定关闭之前,建议先评估能否用properties: "never"、ignoreDestructuring、allow等细粒度选项只豁免必要的场景,从而在保留变量/函数驼峰约束的同时兼容下划线数据源。
总结
camelcase是 ESLint 中覆盖面较广的命名风格规则:它同时约束声明、赋值、对象/类属性、解构、导入导出与全局引用。通过properties、ignoreDestructuring、ignoreImports、ignoreGlobals、allow五个子项的组合,团队可以在强制驼峰的大前提下,为第三方模块、后端字段、历史遗留命名等场景保留合理的豁免通道。如需深入验证规则行为,可直接查阅规则实现 lib/rules/camelcase.js 及其 1600 余行的测试套件 tests/lib/rules/camelcase.js。
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考