强迫症福音:自定义 eslint 规则来规范组内代码

eslint 是什么

eslint 是用来规范 js 代码的工具,它提供了几个个重要功能

  • 代码质量检查。会推荐使用更高可靠性的代码,如用 === 代替 ==

强迫症福音:自定义 eslint 规则来规范组内代码

  • 代码错误问题。可以粗略检查代码 bug,如使用未定义的变量

强迫症福音:自定义 eslint 规则来规范组内代码

  • 代码格式化。如给所有代码都加上; 来统一代码风格等。

为什么使用 eslint

  • 能在前期快速察觉到一些小bug,诸如O写成0
  • 统一团队代码风格,比如换行符与空格等,并且能快速修复不符合风格的问题。大大降低了团队内的代码阅读难度和维护成本,简直是强迫症的福音

强迫症福音:自定义 eslint 规则来规范组内代码

eslint 常规用法

使用步骤

一般来说分为如下几步,由于不同的构建工具需要不同的方式,具体情况就不多赘述。

  1. 安装本地 vscode 的 eslint 插件并启用
  2. 在项目中安装 eslint 包
  3. eslint 初始化生成 .eslintrc.cjs 配置文件
  4. 将 eslint 加入构建工具中

完成这几步即可启用 eslint 的默认能力,当代码出现问题的时候就会爆红。

我在这个过程中查阅了一些掘金资料,大多数 react eslint 教程都是直接npm i eslint然后就开用了。

其实我是有点疑惑的。理论上这种检测工具是需要 nodejs 来扫描代码实现的,但我们既没有将这个检测步骤加入脚手架,又没有手动 node 执行 eslint,那么它为什么能自己跑起来?

抱着这个想法去重新去创了个 react 项目来测试,发现 cra 与 vite 建出来的项目 是自带 eslint 的,也就是 eslint 已经集成进脚手架了,连 install eslint 都不需要 🤧

常用配置

.eslintrc.cjs 下,我们可以看到如下内容(以 vite-react 为例)

module.exports = {
  root: true,
  env: { browser: true, es2020: true },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react-hooks/recommended',
  ],
  ignorePatterns: ['dist', '.eslintrc.cjs'],
  parser: '@typescript-eslint/parser',
  plugins: ['react-refresh'],
  rules: {
    'react-refresh/only-export-components': [
      'warn',
      { allowConstantExport: true },
    ],
  },
}

我们来对关键属性进行分析

#root

由于 .eslintrc.cjs 文件可以不止存在一个,eslint 会逐级进行 .eslintrc.cjs 文件的扫描,添加该属性可提示 eslint 不再向上扫描,从而节约性能。

#env

主要包括 browser、node、es系列等,开启后,eslint 会开启对应的特殊语法解析

#rules

eslint 的核心,其中包含了大量规则配置,内容过多,这里提供两个链接

举个例子,如果希望代码中只能使用 === 而不是 ==,那么首先要去规则中找到对应的规则名

强迫症福音:自定义 eslint 规则来规范组内代码

接着将其添加到 rules 中即可

module.exports = {
  ...
  rules: {
    ...
    eqeqeq: ['error', "always", { null: "ignore" }], // 数组第一项表示 eslint 检测到该规则后的状态(0关闭,1警告,2错误)。后几项均为规则的参数,可查阅对应规则
  },
};

#plugins

虽然 eslint 规则五花八门,但总有不够用的时候。比如说需要 react 的一些定制化规则,eslint 默认规则里并没有,这时候就可以使用 plugins 了。

plugins 相当于拓展的规则集,仍需要你手动添加 rules,而不是添加一个 plugins 就结束了。

比方说,eslint-plugin-react 这个包里面有一些 react 规则,那么可以先使用 plugins 引入

 plugins: [
    'eslint-plugin-react'
 ],

此时我们的 rules 就能检查到这个包里的所有规则了,这时候通过 rules 来启用这个包其中某条规则,这样一来即可实现追加自定义规则了。

rules: {
    'eslint-plugin-react/jsx-boolean-value': 2
}

#extends

不过相信你也能发现上述 plugins 不方便的地方,假如有很多规则,那岂不是要一个一个输入名字,一个一个进行配置,有没有打包好的整套规则集呢?

强迫症福音:自定义 eslint 规则来规范组内代码

extends 相当于他人的整个 .eslintrc.cjs 文件,直接与你本地 .eslintrc.cjs 进行融合,这样你的项目中自然就得到了它其中所有的 rules。

extends: [
    'eslint-plugin-react/recommended'
]

如果部分规则出现了冲突,也可以使用本地的 rules 来进行单规则覆盖

保存自动格式化(vscode)

如果使用的是vscode,有个简单的自动格式化可以配置,具体步骤如下

  1. vscode 安装 prettier 插件
  2. 在项目中新建 .vscode 文件夹,在其下新建 settings.json 并输入
{
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "esbenp.prettier-vscode"
}

这样即可不出现 prettier 和 eslint 冲突,eslint 负责校验代码,而格式化的工作交给 prettier

书写自己的 eslint 规则

现在回归正题,如何通过 eslint 来优雅的规范代码呢。

在大部分情况下,使用推荐的 eslint:recommended + 默认 rules 即可满足要求,但是如果组内有一些更定制化的场景,那就要自己来开发一个规则了。

问题

举个例子,为了代码的可读性和美观,路径中不允许出现三层以上的../,若有这种情况,需要换成绝对路径而不是相对路径。(当然,最好加个参数可以控制有几层,默认3层)

import A from '../../A'; // ok

import B from '../../../B'; // x
import B from '@/components/card/B'; // ok

import C from '../../../../../../../../../../../../C'; // 灾难,可读性很差

实现

#方案1:利用脚手架

个人觉得先学会利用脚手架实现,再去探究原生实现会更加好理解

网上的脚手架不少,我们用 eslint 赞助的 generator-eslint 项目(github.com/eslint/gene…

  1. 新建文件夹,这里取名 eslint-demo
  2. 安装脚手架包
# 脚手架需要 yeoman 启动
npm i yo -g

# 脚手架包
pnpm i generator-eslint
  1. 生成 plugins (只是个架子,还需要生成其中的 rules
yo eslint:plugin

# 编写者
? What is your name? imoo

# 插件名,建议用 eslint-plugin- 开头
? What is the plugin ID? eslint-plugin-imoo-tools

# 插件描述
? Type a short description of this plugin: imoo 工具箱

? Does this plugin contain custom ESLint rules? Yes
? Does this plugin contain one or more processors? No

# 是否重写 package.json,注意重写后 generator-eslint 会没掉,需要重新 install 一个
? Overwrite package.json? overwrite
  1. 生成 rules
 yo eslint:rule 
 
# 编写者
 ? What is your name? imoo
? Where will this rule be published? ESLint Plugin

# 规则名
? What is the rule ID? absolutize

# 规则描述
? Type a short description of this rule: 绝对链接化

# 失败代码示例(可以先空着后面写)
? Type a short example of the code that will fail:  import A from '../../../../A'

这么一来就可以生成我们的核心文件了

强迫症福音:自定义 eslint 规则来规范组内代码

我们来分析一下这个文件

module.exports = {
    meta: {
        type: "", // problem 代码会导致错误,suggestion 建议修正,layout 主要关心分号空格等
        docs: {
            description: "", // 描述规则功能
            recommended: true, // 后面可以使用extends: ['eslint:recommended']继承规则
            url: "" // 文档的url
        },
        fixable: "code", // 标识规则是否可以修复,假如没有这属性,eslint不会帮你修复
        schema: [], // 规则参数,比如规则使用时,rules: { myRule: ['error', param1, param2....]} ,error后面的就是参数
        messages: "", // 错误信息
    },
    create: function(context) {
        // 核心校验代码,使用 AST
        return {
            
        };
    }
};
  1. 书写 rules

来整理一下思路,我们需要一个能这样调用的规则

module.exports = {
  ...
  plugins: ['imoo-tools'],
  rules: {
    ...
    'imoo-tools/absolutize': ['error', { level: 3 }]
  },
}

就可以实现这样的校验

import B from '../../B'; // ok
import B from '../../../B'; // x,提示应该修正为绝对路径
import B from '@/components/card/B'; // ok

只要利用一点 AST 知识(具体可看上一篇 AST 介绍),就可以写出如下代码

const path = require("path");
module.exports = {
  meta: {
    type: "problem",
    docs: {
      description: "绝对链接化",
      recommended: true,
      url: "",
    },
    fixable: "code",
    schema: [
      {
        type: "object",
        properties: {
          level: {
            type: "integer",
            minimum: 1,
            default: 3,
          },
        },
      },
    ],
    messages: {
      notAbsolute: "路径应为绝对路径",
    },
  },
  create(context) {
    const level = context.options[0].level;
    const absolutePathPattern = new RegExp("^(\.{2}/){" + level + ",}");

    return {
      ImportDeclaration(node) {
        const currentPath = node.source.value;
        if (absolutePathPattern.test(currentPath)) {
          context.report({
            node: node,
            messageId: "notAbsolute",
            // 获取当前的绝对路径并修复
            fix(fixer) {
              // 获得完整路径
              const absolutePath = path.resolve(
                path.dirname(context.getFilename()),
                currentPath
              );

              // 我们需要从 src 开始,src 前面的可以去掉
              const srcIndex = absolutePath.indexOf("/src");
              const projectPath = absolutePath.substring(srcIndex + 1);
              return fixer.replaceText(node.source, `'${projectPath}'`);
            },
          });
        }
      },
    };
  },
};

我们可以在 test 中写下测试代码(这里应该贴代码的,但是不知道掘金为啥贴过来不换行了…)

强迫症福音:自定义 eslint 规则来规范组内代码

尽可能多的覆盖测试场景(我这里只是 demo),最后 run 一下 test,通过的话就万事大吉了。

#方案2:利用原生 js

如果用原生来实现,需要较多步骤,不过和脚手架思路类似,这里贴一份官网教程

eslint.nodejs.cn/docs/latest…

发布 eslint 规则并应用进组内项目中

发布 npm

  1. npm 注册一个账号
  2. 如果在本地使用了淘宝镜像源,发布的时候需要切换回来
npm config set registry https://registry.npmjs.org/
  1. 这样即可进行登录并发布了
npm login
npm publish

可以在这里看到你发布的包,跟 github 差不多

强迫症福音:自定义 eslint 规则来规范组内代码

💡注意事项:

  • 如果使用淘宝源进行 login,会报错并提示“不允许公开注册”
  • 切换 npm 源后,注意换回来,不然 pnpm install 可能会报错。
npm config set registry https://registry.npmmirror.com
  • publish 后如果想第二次发布,别忘了修改版本号

使用 npm

  1. 回到你的项目中,安装这个包(这里以 eslint-plugin-imoo-tools 为例)
  2. 配置 .eslintrc.cjs
module.exports = {
  ...
  plugins: ["imoo-tools"], // 值得一提的是,我们可以省略 eslint-plugin- 前缀
  rules: {
    "imoo-tools/absolutize": ["error", { level: 3 }], // 别忘了 plugins 前缀 imoo-tools/
  },
};
  1. eslint 变动的时候,建议重启一下编译器
  2. 写一个 demo 试一下

强迫症福音:自定义 eslint 规则来规范组内代码

强迫症福音:自定义 eslint 规则来规范组内代码

可以看到已经生效了,我们再试一下修复功能

强迫症福音:自定义 eslint 规则来规范组内代码

— 🎉 主线任务完结撒花,能看到这里的掘友们也太强了,希望大家都有所收获 🎉–

强迫症福音:自定义 eslint 规则来规范组内代码

番外:eslint 底层机制

这里就直接套用掘金大佬的文章结论了

ESLint 的核心类是 Linter,它分为这样几步:

  1. preprocess,把非 js 文本处理成 js
  2. 确定 parser(默认是 espree)
  3. 调用 parser,把源码 parse 成 SourceCode(ast)
  4. 调用 rules,对 SourceCode 进行检查,返回 linting problems
  5. 扫描出注释中的 directives,对 problems 进行过滤
  6. postprocess,对 problems 做一次处理
  7. 基于字符串替换实现自动 fix

基于 AST 做检查,基于字符串做 fix,之前之后还有 pre 与 post 的process,支持注释来配置过滤掉一些 problems。

原文链接:https://juejin.cn/post/7356447891822542857 作者:imoo

(0)
上一篇 2024年4月13日 下午4:17
下一篇 2024年4月13日 下午4:28

相关推荐

发表回复

登录后才能评论