ESLint 插件笔记

什么是 ESLint 插件?

ESLint 是一个可插拔的 JavaScript 代码检查工具。它的核心功能是基于规则(rules)来分析代码,并找出潜在的问题。ESLint 插件就是一组自定义的规则、配置(configurations)或处理器(processors),它们扩展了 ESLint 的能力,使其能够检查特定场景、特定框架(如 React、Vue)或特定代码风格的问题。

为什么需要自定义 ESLint 插件?

  • 团队规范:强制执行团队内部特有的代码规范,例如禁止使用某个废弃的函数,或者要求特定的命名约定。
  • 框架/库特定检查:为项目使用的特定框架(如 Vue 的模板语法、React 的 Hooks 规则)提供额外的检查。
  • 代码质量提升:发现并阻止一些潜在的运行时错误或性能问题。
  • 自动化修复:某些规则可以提供自动修复功能,提高开发效率。

一个 ESLint 插件通常包含以下部分:

  1. 规则(Rules) :这是插件的核心,定义了要检查的代码模式和相应的错误信息。
  2. 配置(Configurations) :预设的规则集,方便用户快速启用插件的推荐配置。
  3. 处理器(Processors) :用于从非 JavaScript 文件中提取 JavaScript 代码进行检查(例如,从 .vue 文件中提取 <script> 标签内容)。

本次讲解主要聚焦于最常用的 规则(Rules) 的编写。

编写一个简单的 ESLint 插件:eslint-plugin-my-awesome-rules

我们将创建一个简单的规则:no-console-log,它会禁止在代码中使用 console.log,并提供自动修复功能。

第一步:创建插件项目结构

首先,创建一个新的目录作为你的 ESLint 插件项目。

bash 复制代码
# 创建项目目录
mkdir eslint-plugin-my-awesome-rules
cd eslint-plugin-my-awesome-rules

# 初始化 Node.js 项目
npm init -y

# 安装 ESLint 作为开发依赖,用于测试和类型提示
npm install eslint --save-dev

现在,你的 package.json 文件应该已经生成。

第二步:定义插件入口文件

在项目根目录下创建一个 index.js 文件,作为插件的入口。这个文件会导出插件的规则和配置。

js 复制代码
// eslint-plugin-my-awesome-rules/index.js
module.exports = {
  rules: {
    // 导出我们的规则
    'no-console-log': require('./rules/no-console-log'),
  },
  configs: {
    // 也可以定义预设配置,例如 'recommended'
    recommended: {
      plugins: ['my-awesome-rules'], // 这里的名字要与 package.json 中的 name 对应
      rules: {
        'my-awesome-rules/no-console-log': 'error', // 使用插件名/规则名
      },
    },
  },
  // processors: { /* 可以定义处理器 */ }
};

第三步:编写规则文件

rules 目录下创建 no-console-log.js 文件,这是我们规则的实现。

bash 复制代码
mkdir rules
touch rules/no-console-log.js

rules/no-console-log.js 的内容如下:

js 复制代码
// eslint-plugin-my-awesome-rules/rules/no-console-log.js

"use strict";

module.exports = {
  // 元数据 (meta) 定义了规则的类型、文档、配置 Schema 和是否可修复
  meta: {
    type: "suggestion", // "problem", "suggestion", or "layout"
    docs: {
      description: "Disallow console.log statements", // 规则的简短描述
      category: "Possible Problems", // 规则分类
      recommended: false, // 是否在推荐配置中启用
      url: "https://example.com/no-console-log", // 规则文档的URL (可选)
    },
    fixable: "code", // "code" 表示可以自动修复,"whitespace" 表示只修复空白,null 表示不可修复
    schema: [], // 规则的配置选项,这里为空数组表示没有额外配置
    messages: { // 定义规则报告的消息模板
      noConsoleLog: "Unexpected console.log statement.",
    },
  },

  // create 方法返回一个对象,其中包含访问 AST 节点的访问器方法
  create(context) {
    // context 对象提供了与规则相关的实用方法和属性
    // 例如:context.report() 报告问题
    // context.getFilename() 获取当前文件路径
    // context.getSourceCode() 获取 SourceCode 对象

    return {
      // 访问器方法:当 ESLint 遍历 AST 遇到特定的节点类型时,会调用相应的方法
      // 这里我们监听 CallExpression 节点,即函数调用表达式
      CallExpression(node) {
        // 检查调用的 callee(被调用的函数)是否是 MemberExpression
        // 例如:console.log() 中,callee 是 console.log
        if (node.callee.type === "MemberExpression") {
          // 检查对象是否是 'console'
          // 例如:console.log() 中,object 是 console
          if (node.callee.object.type === "Identifier" && node.callee.object.name === "console") {
            // 检查属性是否是 'log'
            // 例如:console.log() 中,property 是 log
            if (node.callee.property.type === "Identifier" && node.callee.property.name === "log") {
              // 报告问题
              context.report({
                node: node, // 报告问题的 AST 节点
                messageId: "noConsoleLog", // 使用 meta.messages 中定义的消息 ID
                // 如果规则可修复 (fixable: "code"),则提供 fix 方法
                fix(fixer) {
                  // fixer 对象提供了用于修改代码的方法
                  // 例如:fixer.remove(node) 移除节点
                  // fixer.replaceText(node, newText) 替换节点文本
                  // fixer.insertTextBefore(node, text) 在节点前插入文本
                  // fixer.insertTextAfter(node, text) 在节点后插入文本

                  // 这里的修复是移除整个 console.log 语句
                  // 注意:如果 console.log 是独立一行,直接移除即可
                  // 如果是表达式的一部分,移除可能会导致语法错误,需要更复杂的逻辑
                  // 为了简单起见,我们假设它是独立语句
                  return fixer.remove(node);
                },
              });
            }
          }
        }
      },
    };
  },
};

关于 AST (Abstract Syntax Tree) 抽象语法树:

ESLint 在检查代码时,首先会将 JavaScript 代码解析成一个抽象语法树(AST)。AST 是代码的树形表示,每个节点都代表了代码中的一个结构(如变量声明、函数调用、表达式等)。

  • 如何查看 AST?
    你可以使用 AST Explorer 这个在线工具。将你的 JavaScript 代码粘贴进去,它会实时显示对应的 AST 结构。这对于编写 ESLint 规则至关重要,因为你需要知道你想要检查的代码模式在 AST 中是如何表示的。例如,输入 console.log('hello'),你会看到它被解析为一个 CallExpression 节点,其 callee 属性是一个 MemberExpression,等等。

第四步:测试插件(可选但推荐)

package.json 中添加一个测试脚本,方便本地测试规则。

json 复制代码
{
  "name": "eslint-plugin-my-awesome-rules",
  "version": "1.0.0",
  "description": "My custom ESLint rules",
  "main": "index.js",
  "scripts": {
    "test": "eslint rules --format compact" // 简单的测试,检查规则文件自身
  },
  "keywords": [
    "eslint",
    "eslintplugin",
    "eslint-plugin"
  ],
  "author": "Your Name",
  "license": "MIT",
  "devDependencies": {
    "eslint": "^8.0.0"
  }
}

为了测试我们的规则,我们可以在项目根目录下创建一个测试文件 test.js

js 复制代码
// eslint-plugin-my-awesome-rules/test.js
const foo = 'bar';
console.log(foo); // 应该被标记
function baz() {
  console.warn('warning'); // 不应该被标记
  console.log('another log'); // 应该被标记
}

然后,在 package.json 中添加一个临时的 eslintConfig 配置,指向我们的插件:

json 复制代码
{
  "name": "eslint-plugin-my-awesome-rules",
  // ... 其他内容 ...
  "scripts": {
    "test": "eslint rules --format compact",
    "lint:test": "eslint test.js" // 添加这个脚本来测试 test.js
  },
  "eslintConfig": { // 临时配置,用于本地测试
    "plugins": ["./"], // 使用相对路径引用当前插件
    "rules": {
      "my-awesome-rules/no-console-log": "error"
    }
  }
}

现在运行:

bash 复制代码
npm run lint:test

你应该会看到类似以下的输出:

bash 复制代码
/path/to/eslint-plugin-my-awesome-rules/test.js
  2:1  error  Unexpected console.log statement.  my-awesome-rules/no-console-log
  5:3  error  Unexpected console.log statement.  my-awesome-rules/no-console-log

✖ 2 problems (2 errors, 0 warnings)

这表明我们的规则已经生效了!

在前端项目中集成和使用自定义 ESLint 插件

现在我们已经创建了一个 ESLint 插件,接下来是如何在实际的前端项目中使用它。

第一步:创建一个新的前端项目(或使用现有项目)

perl 复制代码
# 创建一个测试用的前端项目
mkdir my-frontend-app
cd my-frontend-app
npm init -y
npm install eslint --save-dev

第二步:将自定义插件引入前端项目

有两种主要方式:

方法 A: 本地链接 (推荐开发阶段)

如果你正在开发插件并希望在前端项目中实时测试,可以使用 npm linkyarn link

在插件项目目录 (eslint-plugin-my-awesome-rules) 中运行:

bash 复制代码
npm link

然后在你的前端项目目录 (my-frontend-app) 中运行:

perl 复制代码
npm link eslint-plugin-my-awesome-rules

这会在你的前端项目的 node_modules 中创建一个符号链接,指向你的本地插件项目。

方法 B: 发布到 npm (推荐生产环境)

如果你希望其他人也能使用你的插件,或者在 CI/CD 环境中部署,你应该将插件发布到 npm。

在插件项目目录 (eslint-plugin-my-awesome-rules) 中运行:

复制代码
npm publish

(请确保你的 package.json 中的 name 字段是唯一的,并且版本号已更新。)

发布后,在你的前端项目目录 (my-frontend-app) 中安装它:

sql 复制代码
npm install eslint-plugin-my-awesome-rules --save-dev
# 或者 yarn add eslint-plugin-my-awesome-rules --dev

第三步:配置前端项目的 .eslintrc.js

在你的前端项目根目录下创建或修改 .eslintrc.js 文件。

js 复制代码
// my-frontend-app/.eslintrc.js
module.exports = {
  // 指定解析器,例如 @babel/eslint-parser 或 @typescript-eslint/parser
  parserOptions: {
    ecmaVersion: 2020, // ECMAScript 版本
    sourceType: 'module', // 模块类型
    ecmaFeatures: {
      jsx: true, // 如果是 React 项目,开启 JSX
    },
  },
  env: {
    browser: true, // 浏览器环境
    node: true, // Node.js 环境
    es2020: true, // ES2020 全局变量
  },
  // 引入你的自定义插件
  // 注意:这里 'my-awesome-rules' 是你在插件 package.json 中定义的 name
  plugins: [
    'my-awesome-rules',
  ],
  // 配置规则
  rules: {
    // 使用插件名/规则名来启用和配置规则
    'my-awesome-rules/no-console-log': 'error', // 将 console.log 视为错误
    // 你也可以使用插件提供的预设配置
    // 'plugin:my-awesome-rules/recommended', // 如果你定义了 recommended 配置
  },
  // 其他 ESLint 配置,例如继承其他配置
  extends: [
    'eslint:recommended', // 启用 ESLint 推荐规则
    // 'plugin:react/recommended', // 如果是 React 项目
  ],
};

第四步:在前端项目中使用 ESLint

在你的前端项目中创建一个测试文件,例如 src/index.js

js 复制代码
// my-frontend-app/src/index.js
const appName = 'My Awesome App';

function init() {
  console.log('App initialized!'); // 应该被标记为错误
  console.warn('This is a warning.'); // 不会被标记
}

init();

package.json 中添加一个 lint 脚本:

json 复制代码
{
  "name": "my-frontend-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "lint": "eslint src/**/*.js" // 运行 ESLint 检查 src 目录下的所有 JS 文件
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "eslint": "^8.0.0",
    "eslint-plugin-my-awesome-rules": "file:../eslint-plugin-my-awesome-rules" // 如果是本地链接,这里会显示链接路径
    // 如果是 npm 安装,这里会显示版本号,例如:"^1.0.0"
  }
}

现在运行 lint 脚本:

bash 复制代码
npm run lint

你应该会看到以下输出:

bash 复制代码
/path/to/my-frontend-app/src/index.js
  4:3  error  Unexpected console.log statement.  my-awesome-rules/no-console-log

✖ 1 problem (1 error, 0 warnings)

这表明你的自定义 ESLint 插件已经在前端项目中成功运行并识别出了 console.log 语句。

第五步:测试自动修复功能

由于我们的规则设置了 fixable: "code",并且提供了 fix 方法,我们可以尝试自动修复。

运行:

bash 复制代码
npm run lint -- --fix

再次检查 src/index.js 文件,你会发现 console.log('App initialized!'); 这一行已经被移除了。

总结

通过以上步骤,已经成功地编写了一个简单的 ESLint 插件,并将其应用到你的前端项目中。

核心概念回顾:

  • meta 对象:定义规则的元信息,包括类型、文档、是否可修复、配置 Schema 和消息模板。
  • create 方法:这是规则的核心逻辑,它返回一个对象,该对象包含用于访问 AST 节点的访问器方法。
  • AST (抽象语法树) :理解代码如何被解析成 AST 是编写 ESLint 规则的关键。AST Explorer 是一个非常有用的工具。
  • context 对象 :提供了报告问题 (context.report()) 和获取源代码信息 (context.getSourceCode()) 等实用方法。
  • fixer 对象 :当规则可修复时,fix 方法会接收一个 fixer 对象,用于执行代码修改操作。
  • 插件名称 :在 package.json 中的 name 字段决定了你在 .eslintrc.js 中引用插件的名称(例如 plugins: ['my-awesome-rules'])。
  • 规则引用 :在 .eslintrc.js 中,规则通过 插件名/规则名 的形式引用(例如 'my-awesome-rules/no-console-log': 'error')。
相关推荐
泯泷31 分钟前
「译」为 Rust 及所有语言优化 WebAssembly
前端·后端·rust
LinXunFeng38 分钟前
Flutter - GetX Helper 如何应用于旧页面
前端·flutter·开源
紫薯馍馍1 小时前
Dify创建 echarts图表 (二)dify+python后端flask实现
前端·flask·echarts·dify
梦想很大很大1 小时前
把业务逻辑写进数据库中:老办法的新思路(以 PostgreSQL 为例)
前端·后端·架构
李三岁_foucsli1 小时前
从生成器和协程的角度详解async和await,图文解析
前端·javascript
柚子8162 小时前
CSS自定义函数也来了
前端·css
zayyo2 小时前
面试官问我,后端一次性返回十万条数据,前端应该怎么处理 ?
前端·javascript·面试
Ai财富密码2 小时前
【Linux教程】Linux 生存指南:掌握常用命令,避开致命误操作
java·服务器·前端
鸿蒙预备高级程序员2 小时前
HarmonyOS5: LazyForEach的用法、功能及其与ForEach的区别
前端
实习生小黄2 小时前
双三次贝塞尔曲面-canvas 实现4x4网格图片变化功能
前端·算法