自定义规则教程

本教程介绍如何为 ESLint 创建自定义规则并使用插件分发它。

This tutorial covers how to create a custom rule for ESLint and distribute it with a plugin.

你可以创建自定义规则来验证你的代码是否满足特定期望,并确定在不满足该期望时要做什么。Plugins 封装了自定义规则和其他配置,让你可以轻松地在不同的项目中共享和重用它们。

You can create custom rules to validate if your code meets a certain expectation, and determine what to do if it does not meet that expectation. Plugins package custom rules and other configuration, allowing you to easily share and reuse them in different projects.

要了解有关自定义规则和插件的更多信息,请参阅以下文档:

To learn more about custom rules and plugins refer to the following documentation:

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

Why Create a Custom Rule?

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

Create a custom rule if the ESLint built-in rules and community-published custom rules do not meet your needs. You might create a custom rule to enforce a best practice for your company or project, prevent a particular bug from recurring, or ensure compliance with a style guide.

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

Before creating a custom rule that isn’t specific to your company or project, it’s worth searching the web to see if someone has published a plugin with a custom rule that solves your use case. It’s quite possible the rule may already exist.

先决条件

Prerequisites

在开始之前,请确保你的开发环境中安装了以下内容:

Before you begin, make sure you have the following installed in your development environment:

本教程还假设你对 ESLint 和 ESLint 规则有基本的了解。

This tutorial also assumes that you have a basic understanding of ESLint and ESLint rules.

自定义规则

The Custom Rule

本教程中的自定义规则要求为所有名为 fooconst 变量分配字符串字面 "bar"。该规则在文件 enforce-foo-bar.js 中定义。该规则还建议用 "bar" 替换分配给 const foo 的任何其他值。

The custom rule in this tutorial requires that all const variables named foo are assigned the string literal "bar". The rule is defined in the file enforce-foo-bar.js. The rule also suggests replacing any other value assigned to const foo with "bar".

例如,假设你有以下 foo.js 文件:

For example, say you had the following foo.js file:

// foo.js

const foo = "baz123";

使用该规则运行 ESLint 会将 "baz123" 标记为变量 foo 的错误值。如果 ESLint 以自动修复模式运行,那么 ESLint 将修复文件以包含以下内容:

Running ESLint with the rule would flag "baz123" as an incorrect value for variable foo. If ESLint is running in autofix mode, then ESLint would fix the file to contain the following:

// foo.js

const foo = "bar";

步骤 1:设置你的项目

Step 1: Set up Your Project

首先,为你的自定义规则创建一个新项目。新建一个目录,在里面启动一个新的 npm 项目,为自定义规则新建一个文件:

First, create a new project for your custom rule. Create a new directory, initiate a new npm project in it, and create a new file for the custom rule:

mkdir eslint-custom-rule-example # create directory
cd eslint-custom-rule-example # enter the directory
npm init -y # init new npm project
touch enforce-foo-bar.js # create file enforce-foo-bar.js

步骤 2:删除规则文件

Step 2: Stub Out the Rule File

enforce-foo-bar.js 文件中,为 enforce-foo-bar 自定义规则添加一些脚手架。此外,添加一个 meta 对象,其中包含有关规则的一些基本信息。

In the enforce-foo-bar.js file, add some scaffolding for the enforce-foo-bar custom rule. Also, add a meta object with some basic information about the rule.

// enforce-foo-bar.js

module.exports = {
    meta: {
       // TODO: add metadata
    },
    create(context) {
        return {
            // TODO: add callback function(s)
        };
    }
};

步骤 3:添加规则元数据

Step 3: Add Rule Metadata

在编写规则之前,向规则对象添加一些元数据。ESLint 在运行规则时使用此信息。

Before writing the rule, add some metadata to the rule object. ESLint uses this information when running the rule.

首先导出具有 meta 属性的对象,该属性包含规则的元数据,例如规则类型、文档和可修复性。在本例中,规则类型为 “问题,”,描述为“强制名为 foo 的变量只能分配值‘bar’。”,并且可以通过修改代码来修复该规则。

Start by exporting an object with a meta property containing the rule’s metadata, such as the rule type, documentation, and fixability. In this case, the rule type is “problem,” the description is “Enforce that a variable named foo can only be assigned a value of ‘bar’.”, and the rule is fixable by modifying the code.

// enforce-foo-bar.js

module.exports = {
    meta: {
        type: "problem",
        docs: {
            description: "Enforce that a variable named `foo` can only be assigned a value of 'bar'.",
        },
        fixable: "code",
        schema: []
    },
    create(context) {
        return {
            // TODO: add callback function(s)
        };
    }
};

要了解有关规则元数据的更多信息,请参阅 规则结构

To learn more about rule metadata, refer to Rule Structure.

步骤 4:添加规则访问者方法

Step 4: Add Rule Visitor Methods

定义规则的 create 函数,它接受一个 context 对象并返回一个对象,该对象具有你要处理的每个语法节点类型的属性。在这种情况下,你想要处理 VariableDeclarator 个节点。你可以选择任何 ESTree 节点类型选择器

Define the rule’s create function, which accepts a context object and returns an object with a property for each syntax node type you want to handle. In this case, you want to handle VariableDeclarator nodes. You can choose any ESTree node type or selector.

VariableDeclarator 访问者方法中,检查节点是否表示 const 变量声明,其名称是否为 foo,以及是否未分配给字符串 "bar"。你可以通过评估传递给 VariableDeclaration 方法的 node 来执行此操作。

Inside the VariableDeclarator visitor method, check if the node represents a const variable declaration, if its name is foo, and if it’s not assigned to the string "bar". You do this by evaluating the node passed to the VariableDeclaration method.

如果 const foo 声明的值是 "bar",则该规则不执行任何操作。如果 const foo 未分配值 "bar",则 context.report() 向 ESLint 报告错误。错误报告包含有关错误及其修复方法的信息。

If the const foo declaration is assigned a value of "bar", then the rule does nothing. If const foo is not assigned a value of "bar", then context.report() reports an error to ESLint. The error report includes information about the error and how to fix it.

// enforce-foo-bar.js

module.exports = {
    meta: {
        type: "problem",
        docs: {
            description: "Enforce that a variable named `foo` can only be assigned a value of 'bar'."
        },
        fixable: "code",
        schema: []
    },
    create(context) {
        return {

            // Performs action in the function on every variable declarator
            VariableDeclarator(node) {

                // Check if a `const` variable declaration
                if (node.parent.kind === "const") {

                    // Check if variable name is `foo`
                    if (node.id.type === "Identifier" && node.id.name === "foo") {

                        // Check if value of variable is "bar"
                        if (node.init && node.init.type === "Literal" && node.init.value !== "bar") {

                            /*

                             * Report error to ESLint. Error message uses

                             * a message placeholder to include the incorrect value

                             * in the error message.

                             * Also includes a `fix(fixer)` function that replaces

                             * any values assigned to `const foo` with "bar".
                             */
                            context.report({
                                node,
                                message: 'Value other than "bar" assigned to `const foo`. Unexpected value: {{ notBar }}.',
                                data: {
                                    notBar: node.init.value
                                },
                                fix(fixer) {
                                    return fixer.replaceText(node.init, '"bar"');
                                }
                            });
                        }
                    }
                }
            }
        };
    }
};

步骤 5:设置测试

Step 5: Set up Testing

编写规则后,你可以对其进行测试以确保它按预期工作。

With the rule written, you can test it to make sure it’s working as expected.

ESLint 提供了内置的 RuleTester 类来测试规则。你不需要使用第三方测试库来测试 ESLint 规则,但 RuleTester 可以与 Mocha 和 Jest 等工具无缝协作。

ESLint provides the built-in RuleTester class to test rules. You do not need to use third-party testing libraries to test ESLint rules, but RuleTester works seamlessly with tools like Mocha and Jest.

接下来,为测试创建文件 enforce-foo-bar.test.js

Next, create the file for the tests, enforce-foo-bar.test.js:

touch enforce-foo-bar.test.js

你将在测试文件中使用 eslint 包。将其安装为开发依赖:

You will use the eslint package in the test file. Install it as a development dependency:

npm install eslint --save-dev

并将测试脚本添加到 package.json 文件以运行测试:

And add a test script to your package.json file to run the tests:

// package.json
{
    // ...other configuration
    "scripts": {
        "test": "node enforce-foo-bar.test.js"
    },
    // ...other configuration
}

步骤 6:编写测试

Step 6: Write the Test

要使用 RuleTester 编写测试,请将类和你的自定义规则导入 enforce-foo-bar.test.js 文件。

To write the test using RuleTester, import the class and your custom rule into the enforce-foo-bar.test.js file.

RuleTester#run() 方法针对有效和无效测试用例测试规则。如果规则未能通过任何测试场景,此方法将引发错误。RuleTester 要求至少存在一个有效和一个无效的测试场景。

The RuleTester#run() method tests the rule against valid and invalid test cases. If the rule fails to pass any of the test scenarios, this method throws an error. RuleTester requires that at least one valid and one invalid test scenario be present.

// enforce-foo-bar.test.js
const {RuleTester} = require("eslint");
const fooBarRule = require("./enforce-foo-bar");

const ruleTester = new RuleTester({
  // Must use at least ecmaVersion 2015 because
  // that's when `const` variables were introduced.
  parserOptions: { ecmaVersion: 2015 }
});

// Throws error if the tests in ruleTester.run() do not pass
ruleTester.run(
  "enforce-foo-bar", // rule name
  fooBarRule, // rule code
  { // checks
    // 'valid' checks cases that should pass
    valid: [{
      code: "const foo = 'bar';",
    }],
    // 'invalid' checks cases that should not pass
    invalid: [{
      code: "const foo = 'baz';",
      output: 'const foo = "bar";',
      errors: 1,
    }],
  }
);

console.log("All tests passed!");

使用以下命令运行测试:

Run the test with the following command:

npm test

如果测试通过,你应该会在控制台中看到以下内容:

If the test passes, you should see the following in your console:

All tests passed!

步骤 7:将自定义规则打包在插件中

Step 7: Bundle the Custom Rule in a Plugin

现在你已经编写了自定义规则并验证了它的工作原理,你可以将它包含在一个插件中。使用插件,你可以在 npm 包中共享规则以在其他项目中使用。

Now that you’ve written the custom rule and validated that it works, you can include it in a plugin. Using a plugin, you can share the rule in an npm package to use in other projects.

为插件创建文件:

Create the file for the plugin:

touch eslint-plugin-example.js

现在编写插件代码。插件只是导出的 JavaScript 对象。要在插件中包含规则,请将其包含在插件的 rules 对象中,该对象包含规则名称及其源代码的键值对。

And now write the plugin code. Plugins are just exported JavaScript objects. To include a rule in a plugin, include it in the plugin’s rules object, which contains key-value pairs of rule names and their source code.

要了解有关创建插件的更多信息,请参阅 创建插件

To learn more about creating plugins, refer to Create Plugins.

// eslint-plugin-example.js

const fooBarRule = require("./enforce-foo-bar");
const plugin = { rules: { "enforce-foo-bar": fooBarRule } };
module.exports = plugin;

步骤 8:在局部使用插件

Step 8: Use the Plugin Locally

你可以使用局部定义的插件在你的项目中执行自定义规则。要使用局部插件,请在 ESLint 配置文件的 plugins 属性中指定插件的路径。

You can use a locally defined plugin to execute the custom rule in your project. To use a local plugin, specify the path to the plugin in the plugins property of your ESLint configuration file.

在以下情况之一中,你可能希望使用局部定义的插件:

You might want to use a locally defined plugin in one of the following scenarios:

  • 你想在将插件发布到 npm 之前对其进行测试。

    You want to test the plugin before publishing it to npm.

  • 你想使用一个插件,但不想将它发布到 npm。

    You want to use a plugin, but do not want to publish it to npm.

在将插件添加到项目之前,请使用 扁平配置文件eslint.config.js 为你的项目创建 ESLint 配置:

Before you can add the plugin to the project, create an ESLint configuration for your project using a flat configuration file, eslint.config.js:

touch eslint.config.js

然后,在 eslint.config.js 中添加以下代码:

Then, add the following code to eslint.config.js:

// eslint.config.js
"use strict";

// Import the ESLint plugin locally
const eslintPluginExample = require("./eslint-plugin-example");

module.exports = [
    {
        files: ["**/*.js"],
        languageOptions: {
            sourceType: "commonjs",
            ecmaVersion: "latest",
        },
        // Using the eslint-plugin-example plugin defined locally
        plugins: {"example": eslintPluginExample},
        rules: {
            "example/enforce-foo-bar": "error",
        },
    }
]

在测试规则之前,你必须创建一个文件来测试规则。

Before you can test the rule, you must create a file to test the rule on.

创建文件 example.js

Create a file example.js:

touch example.js

example.js 中添加以下代码:

Add the following code to example.js:

// example.js

function correctFooBar() {
  const foo = "bar";
}

function incorrectFoo(){
  const foo = "baz"; // Problem!
}

现在你已准备好使用局部定义的插件测试自定义规则。

Now you’re ready to test the custom rule with the locally defined plugin.

example.js 上运行 ESLint:

Run ESLint on example.js:

npx eslint example.js

这会在终端中产生以下输出:

This produces the following output in the terminal:

/<path-to-directory>/eslint-custom-rule-example/example.js
  8:11  error  Value other than "bar" assigned to `const foo`. Unexpected value: baz  example/enforce-foo-bar

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

步骤 9:发布插件

Step 9: Publish the Plugin

要将包含规则的插件发布到 npm,你需要配置 package.json。在相应字段中添加以下内容:

To publish a plugin containing a rule to npm, you need to configure the package.json. Add the following in the corresponding fields:

  1. "name":包的唯一名称。npm 上的其他包不能有相同的名称。

    "name": A unique name for the package. No other package on npm can have the same name.

  2. "main":插件文件的相对路径。按照这个例子,路径是 "eslint-plugin-example.js"

    "main": The relative path to the plugin file. Following this example, the path is "eslint-plugin-example.js".

  3. "description":可在 npm 上查看的包的描述。

    "description": A description of the package that’s viewable on npm.

  4. "peerDependencies":添加 "eslint": ">=8.0.0" 作为对等依赖。任何大于或等于该版本的版本都是使用该插件所必需的。将 eslint 声明为对等依赖要求用户将包与插件分开添加到项目中。

    "peerDependencies": Add "eslint": ">=8.0.0" as a peer dependency. Any version greater than or equal to that is necessary to use the plugin. Declaring eslint as a peer dependency requires that users add the package to the project separately from the plugin.

  5. "keywords":包含标准关键字 ["eslint", "eslintplugin", "eslint-plugin"] 以使包易于查找。你也可以添加可能与你的插件相关的任何其他关键字。

    "keywords": Include the standard keywords ["eslint", "eslintplugin", "eslint-plugin"] to make the package easy to find. You can add any other keywords that might be relevant to your plugin as well.

插件的 package.json 文件应该是什么样子的完整注释示例:

A complete annotated example of what a plugin’s package.json file should look like:

// package.json
{
  // Name npm package.
  // Add your own package name. eslint-plugin-example is taken!
  "name": "eslint-plugin-example",
  "version": "1.0.0",
  "description": "ESLint plugin for enforce-foo-bar rule.",
  "main": "eslint-plugin-example.js", // plugin entry point
  "scripts": {
    "test": "node enforce-foo-bar.test.js"
  },
  // Add eslint>=8.0.0 as a peer dependency.
  "peerDependencies": {
    "eslint": ">=8.0.0"
  },
  // Add these standard keywords to make plugin easy to find!
  "keywords": [
    "eslint",
    "eslintplugin",
    "eslint-plugin"
  ],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "eslint": "^8.36.0"
  }
}

要发布包,请运行 npm publish 并按照 CLI 提示进行操作。

To publish the package, run npm publish and follow the CLI prompts.

你应该在 npm 上看到这个包!

You should see the package live on npm!

步骤 10:使用已发布的自定义规则

Step 10: Use the Published Custom Rule

接下来,你可以使用已发布的插件。

Next, you can use the published plugin.

在你的项目中运行以下命令以下载包:

Run the following command in your project to download the package:

npm install --save-dev eslint-plugin-example # Add your package name here

更新 eslint.config.js 以使用插件的打包版本:

Update the eslint.config.js to use the packaged version of the plugin:

// eslint.config.js
"use strict";

// Import the plugin downloaded from npm
const eslintPluginExample = require("eslint-plugin-example");

// ... rest of configuration

现在你已准备好测试自定义规则。

Now you’re ready to test the custom rule.

在步骤 8 中创建的 example.js 文件上运行 ESLint,现在使用下载的插件:

Run ESLint on the example.js file you created in step 8, now with the downloaded plugin:

npx eslint example.js

这会在终端中产生以下输出:

This produces the following output in the terminal:

/<path-to-directory>/eslint-custom-rule-example/example.js
  8:11  error  Value other than "bar" assigned to `const foo`. Unexpected value: baz  example/enforce-foo-bar

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

正如你在上面的消息中看到的,你实际上可以使用 --fix 标志修复问题,将变量赋值更正为 "bar"

As you can see in the above message, you can actually fix the issue with the --fix flag, correcting the variable assignment to be "bar".

使用 --fix 标志再次运行 ESLint:

Run ESLint again with the --fix flag:

npx eslint example.js --fix

运行此命令时终端中没有错误输出,但你可以看到 example.js 中应用的修复程序。你应该看到以下内容:

There is no error output in the terminal when you run this, but you can see the fix applied in example.js. You should see the following:

// example.js

// ... rest of file

function incorrectFoo(){
  const foo = "bar"; // Fixed!
}

概括

Summary

在本教程中,你制定了一条自定义规则,要求为所有名为 fooconst 变量分配字符串 "bar",并建议将分配给 const foo 的任何其他值替换为 "bar"。你还向插件添加了规则,并在 npm 上发布了插件。

In this tutorial, you’ve made a custom rule that requires all const variables named foo to be assigned the string "bar" and suggests replacing any other value assigned to const foo with "bar". You’ve also added the rule to a plugin, and published the plugin on npm.

通过这样做,你了解了以下实践,你可以应用这些实践来创建其他自定义规则和插件:

Through doing this, you’ve learned the following practices which you can apply to create other custom rules and plugins:

  1. 创建自定义 ESLint 规则

    Creating a custom ESLint rule

  2. 测试自定义规则

    Testing the custom rule

  3. 将规则打包在插件中

    Bundling the rule in a plugin

  4. 发布插件

    Publishing the plugin

  5. 使用插件中的规则

    Using the rule from the plugin

查看教程代码

View the Tutorial Code

你可以查看教程 此处 的注释源代码。

You can view the annotated source code for the tutorial here.