Index

自定义规则教程

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

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

你可以创建自定义规则来验证你的代码是否符合某些期望,并确定如果不符合这些期望该怎么办。插件打包自定义规则和其他配置,使你能够轻松地在不同项目中共享和重用它们。

🌐 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 中。该规则还建议将分配给 const foo 的其他任何值替换为 "bar"

🌐 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.

因为这个规则针对的是 JavaScript,我们还添加了 languages: ["js/js"] 来声明它只适用于内置的 JavaScript 语言。如果在非 JavaScript 语言中启用此规则,ESLint 将抛出错误。

🌐 Because this rule targets JavaScript, we also add languages: ["js/js"] to declare that it only applies to the built-in JavaScript language. ESLint will throw an error if this rule is enabled for a non-JavaScript language.

// 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: [],
		languages: ["js/js"],
	},
	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: [],
        languages: ["js/js"]
    },
    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

npm install --save-dev eslint

yarn

yarn add --dev eslint

pnpm

pnpm add --save-dev eslint

bun

bun add --dev eslint

并在你的 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.
	languageOptions: { 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 之前对其进行测试。
  • 你想使用一个插件,但不想将它发布到 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 `defineConfig` helper function
const { defineConfig } = require("eslint/config");
// Import the ESLint plugin locally
const eslintPluginExample = require("./eslint-plugin-example");

module.exports = defineConfig([
	{
		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:

npm

npx eslint example.js 

yarn

yarn dlx eslint example.js 

pnpm

pnpm dlx eslint example.js 

bun

bunx 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 上没有其他包可以使用相同的名称。
  2. "main":插件文件的相对路径。按照这个例子,路径是 "eslint-plugin-example.js"
  3. "description":可在 npm 上查看的包的描述。
  4. "peerDependencies":将 "eslint": ">=10.0.0" 添加为对等依赖。要使用该插件,任何大于或等于该版本的版本都是必要的。将 eslint 声明为对等依赖要求用户在项目中单独添加该包,而不是通过插件添加。
  5. "keywords":包含标准关键字 ["eslint", "eslintplugin", "eslint-plugin"] 以便使软件包容易被找到。你也可以添加任何可能与你的插件相关的其他关键字。

一个完整的带注释示例,展示插件的 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>=10.0.0 as a peer dependency.
  "peerDependencies": {
    "eslint": ">=10.0.0"
  },
  // Add these standard keywords to make plugin easy to find!
  "keywords": [
    "eslint",
    "eslintplugin",
    "eslint-plugin"
  ],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "eslint": "^10.0.0"
  }
}

要发布该包,请运行 npm publish 并按照命令行提示操作。

🌐 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

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

yarn

# Add your package name here
yarn add --dev eslint-plugin-example

pnpm

# Add your package name here
pnpm add --save-dev eslint-plugin-example

bun

# Add your package name here
bun add --dev eslint-plugin-example

更新 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:

npm

npx eslint example.js 

yarn

yarn dlx eslint example.js 

pnpm

pnpm dlx eslint example.js 

bun

bunx 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:

npm

npx eslint example.js --fix 

yarn

yarn dlx eslint example.js --fix 

pnpm

pnpm dlx eslint example.js --fix 

bun

bunx 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 规则
  2. 测试自定义规则
  3. 将规则打包在插件中
  4. 发布插件
  5. 使用插件中的规则

查看教程代码

🌐 View the Tutorial Code

你可以查看本教程的带注释的源代码

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