自定义ESLint 插件

214 阅读3分钟

为什么要创建自定义规则?

如果 ESLint 内置规则 和社区发布的自定义规则不能满足你的需求,请创建自定义规则。你可以创建自定义规则来为你的公司或项目实现最佳实践,防止特定错误再次出现,或确保符合风格指南。

在创建不特定于你的公司或项目的自定义规则之前,值得在网上搜索一下,看看是否有人发布了一个带有自定义规则的插件来解决你的用例。该规则很可能已经存在。

插件

创建 ESLint 插件,ESLint 推荐使用 Yeoman generator。首先需要安装 Yeoman,安装命令如下: npm i -g yo

Yeoman 是一款通用的初始化工具,想要初始化 ESLint 插件,需要安装 ESLint 模板,安装命令如下: npm i -g generator-eslint

接下来,新建一个目录,eslint-plugin-utils (通常与发布npm的包名一致)

切换到上面新建的目录,执行“yo eslint:plugin”命令会进入交互界面,询问作者、插件名字等,输入如图所示的内容即可。

稍等片刻即可完成自动初始化

编写插件

举个例子,写一个限制可选链操作符数据不能超过3个的规则

const maxOptionalChainConfig = {
    meta: {
      type: "suggestion",
      docs: {
        description: "限制可选链操作符的数量"
      },
      schema: [
        {
          type: "object",
          properties: {
            maxCount: {
              type: "integer",
              minimum: 1,
              default: 2 // 默认值为2
            },
            message: {
              type: "string",
              default: '可选链数量不能超过{maxCount}个,请声明常量' // 默认消息
            }
          },
          additionalProperties: false
        }
      ],
      messages: {
        optional_chain: '{{message}}',
      }
    },
    create(context) {
      const options = context.options[0] || {};
      const maxCount = options.maxCount || 2; // 获取最大可选链数量
      const message = options.message || '可选链数量不能超过' + maxCount + '个'; // 获取自定义消息
  
      const reportedLines = new Set(); // 用于跟踪已报告的行
  
      return {
        MemberExpression(node) {
          const sourceCode = context.sourceCode;
          const lines = sourceCode?.lines || [];
          const { start } = node.loc || {};
          const lineNumber = start.line; // 获取当前节点的行号
  
          // 只在当前行进行检查
          if (lines[lineNumber - 1]) {
            const lineContent = lines[lineNumber - 1];
            const questionMarkCount = (lineContent.match(/\?/g) || []).length;
  
            // 检查是否超过最大可选链操作符数量
            if (questionMarkCount > maxCount && !reportedLines.has(lineNumber)) {
              reportedLines.add(lineNumber); // 标记该行已报告
              context.report({
                node,
                messageId: 'optional_chain',
                data: {
                  message: message.replace('{maxCount}', maxCount) // 替换消息中的占位符
                },
                loc: {
                  start: { line: lineNumber, column: 0 }, // 报告位置为行的开始
                  end: { line: lineNumber, column: lineContent.length } // 报告位置为行的结束
                },
              });
            }
          }
        }
      };
    }
  };

入口文件直接导出

const plugin = { 
  rules:  { 
    "max-optional-chain": maxOptionalChainConfig
  }
};
module.exports = plugin;

本地调试是否生效

新建eslint.config.js文件

const eslintPluginExample = require("./src/index");


module.exports = [    {        files: ["**/*.js","**/*.jsx"],
        languageOptions: {
            sourceType: "commonjs",
            ecmaVersion: "latest",
        },
        plugins: {"eslint-plugin-utils": eslintPluginExample},  
        rules: {
            "eslint-plugin-utils/max-optional-chain": ["error",
                {
                    maxCount: 3, 
                    message: '傻逼,不要写那么多可选链,声明常量' 
                }
            ],
        },
    }
]

写个example.js的测试文件


function incorrectFoo() {
  const obj = {};

  if (obj.name === '') {
  }

  if (obj?.name?.a?.c?.e === '') {
  }

  if (typeof obj === 'object') {
    
  }

  if (typeof obj === 'function') {
    
  }

}

然后执行 npx eslint example.js 规则生效的话终端会输出eslint的错误提示

接着正常发布npm即可

附package.josn

{
  "name": "eslint-plugin-utils",
  "version": "0.0.1",
  "description": "自定义eslint-plugin",
  "keywords": [
    "eslint",
    "eslintplugin",
    "eslint-plugin"
  ],
  "author": "crp",
  "main": "./index.js",
  "exports": {
    ".": {
      "import": "./index.mjs",
      "require": "./index.js"
    }
  },
  "module": "./index.mjs",
  "files": [
    "lib"
  ],
  "scripts": {
    "lint": "npm-run-all \"lint:*\"",
    "lint:eslint-docs": "npm-run-all \"update:eslint-docs -- --check\"",
    "lint:js": "eslint .",
    "test": "mocha tests --recursive",
    "update:eslint-docs": "eslint-doc-generator"
  },
  "dependencies": {
    "react": "^19.0.0",
    "requireindex": "^1.2.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.0.0",
    "eslint": "^9.17.0",
    "eslint-doc-generator": "^1.0.0",
    "eslint-plugin-eslint-plugin": "^6.0.0",
    "eslint-plugin-n": "^17.0.0",
    "mocha": "^10.0.0",
    "npm-run-all2": "^6.1.2"
  },
  "engines": {
    "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
  },
  "peerDependencies": {
    "eslint": ">=8.57.0"
  },
  "license": "MIT",
  "private": false
}