规范化命名规则
我们坚持一件事情的时候,不是因为这样做了会有效果,而是坚信,这样做是对的。
规范化命名规则
背景
在团队协作时,不同成员可能有不同的编码习惯,这可能导致代码风格不统一、文件结构混乱等问题,进而影响代码的可读性、可维护性和项目的整体质量。
为了确保代码库的整洁有序,制定并遵循一套清晰的命名约定和代码风格指南是非常必要的。
在项目开发中,不同的成员会有各式各样的开发风格,比如:
- 小驼峰命名(camelCase)
- 第一个单词首字母小写,后续单词首字母大写。例如:userName, getFullName
- 大驼峰命名(PascalCase)
- 每个单词首字母都大写。例如:UserName, GetFullName
- 下划线(snake_case)
- 所有单词小写,单词之间用下划线连接。例如:user_name, get_full_name
- 短横线,也称烤肉串命名法(kebab-case)
- 所有单词小写,单词之间用短横线连接。例如:user-name, get-full-name
- 空间前缀(Namespace Prefixing)
- 在名称前加上模块或包的前缀,这类常见于一些配置文件中。例如:eslint.config.js, vite.config.ts
- 全大写(UPPER_CASE)
- 所有单词大写,单词之间可以用下划线分隔。例如:MAX_LENGTH, API_KEY
其中,在1-5方式中也是较为常见的,因此为了统一在团队协作的项目中开发风格,以达到见名知其意
,通过ESLint 插件来规范命名
ESLint
-
ESLint 是一个用于识别和报告在 ECMAScript/JavaScript 代码中发现的模式的工具,其目标是使代码更加一致并避免错误。
-
ESLint 是完全插件化的。每条规则都是一个插件,你可以在运行时添加更多。你还可以添加社区插件、配置和解析器来扩展 ESLint 的功能。
先决条件
要使用 ESLint,你必须安装并构建 Node.js(^18.18.0
、^20.9.0
或 >=21.1.0
)并支持 SSL。(如果你使用的是官方 Node.js 发行版,则始终内置 SSL。)
注意
相比较于以前的版本,ESLint9.x版本中,会有较大的变更,如果是以前的版本,有许多将不会再适用,而且现在也是推荐以9.x版本为主要版本。就我个人而言,9.x通过扁平化的配置,深得我意,一个爽哈哈哈哈!!!
这里关于ESLint的信息就不过多介绍(主要是赖),具体可以去官网进行了解。ESLint 中文网
规则插件
如果将插件作为 npm 包分发,确保导出插件对象的模块是包的默认导出。并且,如果是非官方插件,强烈建议插件名称以eslint-plugin-
为前缀(不要问我为什么)。
自定义规则
自定义规则应该满足一下格式
export default {
meta: {
type: "suggestion",
docs: {
description: "Description of the rule",
},
fixable: "code",
schema: [] // no options
},
create: function(context) {
return {
// callback functions
};
}
};
meta
:(object
)包含规则的元数据:
-
type
:(string
)指示规则的类型,为"problem"
、"suggestion"
或"layout"
之一:"problem"
:该规则是识别将导致错误或可能导致混淆行为的代码。开发者应将此视为高度优先解决的问题。"suggestion"
:规则是确定可以以更好的方式完成的事情,但如果不更改代码就不会发生错误。"layout"
:该规则主要关注空格、分号、逗号和括号,程序的所有部分决定代码的外观而不是代码的执行方式。这些规则适用于 AST 中未指定的部分代码。
-
docs
:(object
)通常用于文档生成和工具的属性。对于核心规则是必需的,对于自定义规则是可选的。自定义规则可以根据需要在此处包含其他属性。description
:(string
)提供规则的简短描述。对于核心规则,这是在 规则索引 中使用的。recommended
:(boolean
)对于核心规则,这指定规则是否由@eslint/js
中的recommended
配置启用。url
:(string
)指定可以访问完整文档的 URL。代码编辑器经常使用它来提供有关高亮的规则违规的有用链接。
-
fixable
:(string
)如果 命令行 上的--fix
选项自动修复规则报告的问题,则为"code"
或"whitespace"
。重要:对于可修复规则,
fixable
属性是强制性的。如果未指定此属性,ESLint 将在规则尝试生成修复时抛出错误。如果规则不可修复,则省略fixable
属性。 -
hasSuggestions
:(boolean
)指定规则是否可以返回建议(如果省略,则默认为false
)。重要:对于提供建议的规则,
hasSuggestions
属性是强制性的。如果此属性未设置为true
,ESLint 将在规则尝试生成建议时抛出错误。如果规则不提供建议,则省略hasSuggestions
属性。 -
schema
:(object | array | false
)指定 选项,以便 ESLint 可以防止无效的 规则配置。当规则有选项时为强制。 -
defaultOptions
:(array
)为规则指定 默认选项。如果存在,其配置中的任何用户提供的选项都将递归合并到它们之上。 -
deprecated
:(boolean
)指示规则是否已被弃用。如果规则没有被弃用,你可以省略deprecated
属性。 -
replacedBy
:(array
)如果规则已弃用,请指定替换规则。
create()
:返回一个对象,其中包含 ESLint 在遍历 JavaScript 代码的抽象语法树(ESTree 定义的 AST)时调用到 “visit” 节点的方法:
- 如果键是节点类型或 选择器,ESLint 在沿着树向下移动时会调用该访问者函数。
- 如果键是节点类型或 选择器 加
:exit
,则 ESLint 在向上查找树时会调用该访问者函数。 - 如果键是事件名称,ESLint 会调用 代码路径分析 的处理函数。
context上下文对象
context
对象具有以下属性:
id
:(string
)规则 ID。filename
:(string
)与源关联的文件名。physicalFilename
:(string
)对文件进行 linting 时,它会提供磁盘上文件的完整路径,而无需任何代码块信息。当检查文本时,如果未指定,它会提供传递给—stdin-filename
或<text>
的值。cwd
:(string
)cwd
选项传递给 Linter。它是应被视为当前工作目录的目录的路径。options
:(array
)此规则的 配置选项 数组。此数组不包括规则严重性(请参阅 专用部分)。sourceCode
:(object
)一个SourceCode
对象,可用于处理传递给 ESLint 的源(请参阅 访问源代码)。settings
:(object
)从配置上看是 共享的设置。languageOptions
:(object
) 每个属性的更多详细信息 此处sourceType
:('script' | 'module' | 'commonjs'
)当前文件的模式。ecmaVersion
:(number
)用于解析当前文件的 ECMA 版本。parser
:(object
):用于解析当前文件的解析器。parserOptions
:(object
)为此文件配置的解析器选项。globals
:(object
)指定的全局变量。
parserPath
:(string
,已删除,使用context.languageOptions.parser
代替。)配置中parser
的名称。parserOptions
:(已弃用,请使用context.languageOptions.parserOptions
代替。)为此运行配置的解析器选项(更多详细信息 此处)。
此外,context
对象具有以下方法:
getCwd()
:(已弃用:使用context.cwd
代替。)返回传递给 Linter 的cwd
选项。它是应被视为当前工作目录的目录的路径。getFilename()
:(已弃用:使用context.filename
代替。)返回与源关联的文件名。getPhysicalFilename()
:(已弃用:使用context.physicalFilename
代替。)对文件进行 linting 时,它会返回磁盘上文件的完整路径,而不包含任何代码块信息。当检查文本时,如果未指定,它会返回传递给—stdin-filename
或<text>
的值。getSourceCode()
:(已弃用:使用context.sourceCode
代替。)返回一个SourceCode
对象,你可以使用该对象来处理传递给 ESLint 的源(请参阅 访问源代码)。report(descriptor)
。报告代码中的问题(参见 专用部分)。
报告问题
编写自定义规则时你将使用的主要方法是 context.report()
,它会发布警告或错误(取决于所使用的配置)。此方法接受一个参数,该参数是一个包含以下属性的对象:
messageId
:(string
)消息的 ID(参见 messageIds)(在message
上推荐)。message
:(string
)问题消息(messageId
的替代)。node
:(可选的object
)与问题相关的 AST 节点。如果存在且未指定loc
,则将节点的起始位置用作问题的位置。loc
:(可选的object
)指定问题的位置。如果同时指定了loc
和node
,则使用loc
而不是node
的位置。start
:起始位置的对象。line
:(number
)发生问题的从 1 开始的行号。column
:(number
)发生问题的从 0 开始的列号。
end
:结束位置的对象。line
:(number
)发生问题的从 1 开始的行号。column
:(number
)发生问题的从 0 开始的列号。
data
:(可选的object
)message
的 占位符 数据。fix(fixer)
:(可选的function
)应用 fix 来解决问题。
请注意,至少需要 node
或 loc
之一。
以上来自官方的介绍,贴这么多,主要是为了记录一下。ESLint插件的创建具体可以去了解创建插件,这里不过多介绍(想了解一个东西,必须亲身体会到,才能是真正的“会”,做人做事亦是如此)
组件命名(component-naming)
在通过框架开发的系统中,少不了与组件打交道,如在react
中常见的以.jsx
、.tsx
, 在vue
中常见的有.tsx
、.vue
等。
一个组件应该单独负责一个功能,尽量避免减少全部功能揉在一块(非常不喜欢这种模式),这样会显得很臃肿,而且不易维护(当然,如果不想维护,可尽情发挥哈哈哈哈)。
同时,一个组件的命名应该与该功能相对应,如``List表示一个数据列表模块等等,而不应该去命名什么
index`这样没有头绪的方式。
在react
中,组件命名时,一般都是以大驼峰(PascalCase)的命名方式;在vue
中为了避免与普通的标签产生歧义,官方也建议组件应该为大驼峰命名。


为了统一组件名命名规则,应该采用统一个命名方式,通过ESLint插件实现这一点
功能需求
- 可通过配置项来确定那个文件夹下的组件需要指定的规则
- 对象中键应该是指定的某个文件夹下,可通过glob匹配方式匹配,这里使用第三方库micromatch来解析匹配
- 对象中的值应该是指定的命名规则
如:
[
'error',
// 下面参数为自定义的配置项
{
'src/**/*.{jsx,tsx,vue}': 'PASCAL_CASE', // 对组件统一使用大驼峰
}
]
实现
// rules/component-naming.ts
import micromatch from "micromatch";
import {
COMPONENT_EXTNAME,
DEFAULT_PATH_PATTERN,
NAMING_MAP,
} from "../constants";
import { getExtname, getFilenameNotExt, getFullPath } from "../utils";
import type { AST, Rule } from "eslint";
import {COMPONENT_ERROR_MESSAGE} from "../constants/error-message";
export default {
meta: {
type: "layout",
docs: {
description:
"Enforce PascalCase for component filenames with .vue or .tsx extension",
category: "Stylistic Issues",
recommended: false,
},
fixable: null, // or "code" or "whitespace"
schema: [
{
additionalProperties: {
type: "string",
},
},
{
type: "object",
},
],
messages: {
componentError: COMPONENT_ERROR_MESSAGE // 错误信息ID,下面会用到
}
},
create(context: Rule.RuleContext) {
return {
// 抽象语法树的顶级
Program(node: AST.Program) {
// 获取配置项
const rulesOptions = context.options[0] as Record<
any,
keyof typeof NAMING_MAP
>;
// 通过上下文信息,获取当前文件的完路径
const filenameWithPath = getFullPath(context);
// 根据文件路径,获取文件名称,不包含文件后缀
const basename = getFilenameNotExt(filenameWithPath);
// 获取扩展名
const extname = getExtname(filenameWithPath);
let rules = rulesOptions;
// 如果没有配置项,使用默认规则(配置项应该配置为一个对象)
if (!rules || Object.keys(rules).length === 0) {
rules = { [DEFAULT_PATH_PATTERN]: "PASCAL_CASE" }; // 对组件统一使用大驼峰}
}
for (const [filenamePattern, targetPattern] of Object.entries(rules)) {
// 是否匹配到指定文件
if (
micromatch.isMatch(filenameWithPath, filenamePattern) &&
COMPONENT_EXTNAME.includes(extname.toLowerCase())
) {
const target = NAMING_MAP[targetPattern] ?? NAMING_MAP.PASCAL_CASE;
// 如果指定的路径下,文件名没有匹配到,导出错误
if (!micromatch.isMatch(basename, target)) {
context.report({
node,
messageId: 'componentError', // 错误信息ID
data: { // 错误信息的占位符数据
basename: `${basename}${extname}`,
target: targetPattern ?? "PASCAL_CASE"
},
});
}
}
}
},
};
},
} as unknown as Rule.RuleModule;
其他命名
以下命名规则的实现,与组件命名规则实现类似,这里就不一一赘述,感兴趣可以查看完整代码eslint-plugin-file-naming
-
文件名命名(filename-naming)
-
文件夹命名(folder-naming)
-
禁止index(no-index-naming)
插件测试
ESLint 提供了 RuleTester
实用程序,可以轻松测试插件的规则。
在__test__
目录下新建component-naming.test.ts
测试文件
import { RuleTester } from "eslint";
import componentFile from "../rules/component-naming";
const ruleTester = new RuleTester();
ruleTester.run("component-naming", componentFile, {
valid: [
{
code: "const name = 'test';",
filename: "src/components/Login.vue",
},
{
code: "const name = 'test';",
filename: "src/components/LoginLog.vue",
},
],
invalid: [
... // 省略部分
{
code: "const name = 'test';",
filename: "src/components/login.log.vue",
errors: [
{
message:
"Component filename 'login.log.vue' should be in PASCAL_CASE",
},
],
},
],
});
// 携带配置
ruleTester.run(
"component-naming with options:[{ '**/components/**/*.{jsx,tsx,vue}': 'PASCAL_CASE' }]",
componentFile,
{
valid: [
... // 省略部分
{
code: "const name = 'test';",
filename: "src/components/LoginLog.vue",
options: [{ "**/components/**/*.{jsx,tsx,vue}": "PASCAL_CASE" }],
},
],
invalid: [
... // 省略部分
{
code: "const name = 'test';",
filename: "src/components/login.log.vue",
options: [{ "**/components/**/*.{jsx,tsx,vue}": "PASCAL_CASE" }],
errors: [
{
message:
"Component filename 'login.log.vue' should be in PASCAL_CASE",
},
],
},
],
},
);
使用tsx
或者esno
来运行该测试文件即可,如果测试通过,将不会有任何错误信息返回,否则根据错误信息修改插件代码即可。
君与鹤山皆人杰,倘功名、到手还须做