Index

创建插件

ESLint 插件通过附加功能扩展 ESLint。在大多数情况下,你将通过创建封装了你希望在多个项目中共享的附加功能的插件来扩展 ESLint。

🌐 ESLint plugins extend ESLint with additional functionality. In most cases, you’ll extend ESLint by creating plugins that encapsulate the additional functionality you want to share across multiple projects.

创建一个插件

🌐 Create a plugin

插件是一个 JavaScript 对象,它向 ESLint 公开某些属性:

🌐 A plugin is a JavaScript object that exposes certain properties to ESLint:

  • meta - 有关插件的信息。
  • configs - 包含命名配置的对象。
  • rules - 包含自定义规则定义的对象。
  • processors - 包含命名处理器的对象。

要开始,请创建一个 JavaScript 文件,并导出一个包含你希望 ESLint 使用的属性的对象。为了使你的插件尽可能易于维护,我们建议你将插件入口文件格式化为如下所示:

🌐 To get started, create a JavaScript file and export an object containing the properties you’d like ESLint to use. To make your plugin as easy to maintain as possible, we recommend that you format your plugin entrypoint file to look like this:

const plugin = {
	meta: {},
	configs: {},
	rules: {},
	processors: {},
};

// for ESM
export default plugin;

// OR for CommonJS
module.exports = plugin;

如果你计划将你的插件作为 npm 包发布,请确保导出插件对象的模块是你包的默认导出。这将使 ESLint 在命令行的 --plugin 选项 中指定该插件时能够导入它。

🌐 If you plan to distribute your plugin as an npm package, make sure that the module that exports the plugin object is the default export of your package. This will enable ESLint to import the plugin when it is specified in the command line in the --plugin option.

插件中的元数据

🌐 Meta Data in Plugins

为了更容易调试和更有效地缓存插件,建议在插件的根目录下的 meta 对象中提供 nameversionnamespace,如下所示:

🌐 For easier debugging and more effective caching of plugins, it’s recommended to provide a name, version, and namespace in a meta object at the root of your plugin, like this:

const plugin = {
	// preferred location of name and version
	meta: {
		name: "eslint-plugin-example",
		version: "1.2.3",
		namespace: "example",
	},
	rules: {
		// add rules here
	},
};

// for ESM
export default plugin;

// OR for CommonJS
module.exports = plugin;

meta.name 属性应与你的插件的 npm 包名称相匹配,meta.version 属性应与你的插件的 npm 包版本相匹配。meta.namespace 属性应与你希望用户用于访问插件的规则、处理器、语言和配置的前缀相匹配。命名空间通常是包名称中 eslint-plugin- 之后的部分,这就是为什么此示例使用 "example"。提供命名空间可以让 defineConfig() 函数即使在用户在其配置文件中分配了不同的命名空间时,也能找到你的插件。

🌐 The meta.name property should match the npm package name for your plugin and the meta.version property should match the npm package version for your plugin. The meta.namespace property should match the prefix you’d like users to use for accessing the plugin’s rules, processors, languages, and configs. The namespace is typically what comes after eslint-plugin- in your package name, which is why this example uses "example". Providing a namespace allows the defineConfig() function to find your plugin even when a user assigns a different namespace in their config file.

添加名称和版本的最简单方法是从你的 package.json 中读取此信息,如下例所示:

🌐 The easiest way to add the name and version is by reading this information from your package.json, as in this example:

import fs from "fs";

const pkg = JSON.parse(
	fs.readFileSync(new URL("./package.json", import.meta.url), "utf8"),
);

const plugin = {
	// preferred location of name and version
	meta: {
		name: pkg.name,
		version: pkg.version,
		namespace: "example",
	},
	rules: {
		// add rules here
	},
};

export default plugin;

作为一种替代方案,你也可以在插件的根部公开 nameversion 属性,例如:

🌐 As an alternative, you can also expose name and version properties at the root of your plugin, such as:

const plugin = {
	// alternate location of name and version
	name: "eslint-plugin-example",
	version: "1.2.3",
	rules: {
		// add rules here
	},
};

// for ESM
export default plugin;

// OR for CommonJS
module.exports = plugin;

插件中的规则

🌐 Rules in Plugins

插件可以为 ESLint 提供自定义规则。要实现这一点,插件必须导出一个包含规则 ID 到规则的键值映射的 rules 对象。规则 ID 不必遵循任何命名约定,但不能包含 / 字符(所以它可以只是 dollar-sign,但不能是 foo/dollar-sign,例如)。要了解有关在插件中创建自定义规则的更多信息,请参阅 自定义规则

🌐 Plugins can expose custom rules for use in ESLint. To do so, the plugin must export a rules object containing a key-value mapping of rule ID to rule. The rule ID does not have to follow any naming convention except that it should not contain a / character (so it can just be dollar-sign but not foo/dollar-sign, for instance). To learn more about creating custom rules in plugins, refer to Custom Rules.

const plugin = {
	meta: {
		name: "eslint-plugin-example",
		version: "1.2.3",
	},
	rules: {
		"dollar-sign": {
			create(context) {
				// rule implementation ...
			},
		},
	},
};

// for ESM
export default plugin;

// OR for CommonJS
module.exports = plugin;

为了在配置文件中使用插件的规则,导入该插件并将其包含在 plugins 键中,指定一个命名空间。然后,使用该命名空间在 rules 配置中引用该规则,如下所示:

🌐 In order to use a rule from a plugin in a configuration file, import the plugin and include it in the plugins key, specifying a namespace. Then, use that namespace to reference the rule in the rules configuration, like this:

// eslint.config.js
import { defineConfig } from "eslint/config";
import example from "eslint-plugin-example";

export default defineConfig([
	{
		files: ["**/*.js"], // any patterns you want to apply the config to
		plugins: {
			example,
		},
		rules: {
			"example/dollar-sign": "error",
		},
	},
]);

插件中的处理器

🌐 Processors in Plugins

插件可以通过提供一个 processors 对象来在配置文件中暴露 处理器。与规则类似,processors 对象中的每个键都是处理器的名称,每个值则是处理器对象本身。示例如下:

🌐 Plugins can expose processors for use in configuration file by providing a processors object. Similar to rules, each key in the processors object is the name of a processor and each value is the processor object itself. Here’s an example:

const plugin = {
	meta: {
		name: "eslint-plugin-example",
		version: "1.2.3",
	},
	processors: {
		"processor-name": {
			preprocess(text, filename) {
				/* ... */
			},
			postprocess(messages, filename) {
				/* ... */
			},
		},
	},
};

// for ESM
export default plugin;

// OR for CommonJS
module.exports = plugin;

为了在配置文件中使用来自插件的处理器,需要导入该插件并将其包含在 plugins 键中,同时指定一个命名空间。然后,使用该命名空间在 processor 配置中引用该处理器,如下所示:

🌐 In order to use a processor from a plugin in a configuration file, import the plugin and include it in the plugins key, specifying a namespace. Then, use that namespace to reference the processor in the processor configuration, like this:

// eslint.config.js
import { defineConfig } from "eslint/config";
import example from "eslint-plugin-example";

export default defineConfig([
	{
		files: ["**/*.txt"],
		plugins: {
			example,
		},
		processor: "example/processor-name",
	},
]);

插件中的配置

🌐 Configs in Plugins

你可以通过在 configs 键下指定它们,将配置打包在插件中。当你想要将一组自定义规则与启用推荐选项的配置打包在一起时,这会很有用。每个插件支持多个配置。

🌐 You can bundle configurations inside a plugin by specifying them under the configs key. This can be useful when you want to bundle a set of custom rules with a configuration that enables the recommended options. Multiple configurations are supported per plugin.

你可以在一个配置中包含插件的单独规则,该配置也包含在插件中。在配置中,你必须在 plugins 对象中指定你的插件名称,以及你想启用的属于该插件的任何规则。任何插件规则必须以插件命名空间作为前缀。下面是一个示例:

🌐 You can include individual rules from a plugin in a config that’s also included in the plugin. In the config, you must specify your plugin name in the plugins object as well as any rules you want to enable that are part of the plugin. Any plugin rules must be prefixed with the plugin namespace. Here’s an example:

const plugin = {
	meta: {
		name: "eslint-plugin-example",
		version: "1.2.3",
	},
	configs: {},
	rules: {
		"dollar-sign": {
			create(context) {
				// rule implementation ...
			},
		},
	},
};

// assign configs here so we can reference `plugin`
Object.assign(plugin.configs, {
	recommended: [
		{
			plugins: {
				example: plugin,
			},
			rules: {
				"example/dollar-sign": "error",
			},
			languageOptions: {
				globals: {
					myGlobal: "readonly",
				},
				parserOptions: {
					ecmaFeatures: {
						jsx: true,
					},
				},
			},
		},
	],
});

// for ESM
export default plugin;

// OR for CommonJS
module.exports = plugin;

这个插件导出一个 recommended 配置,它是一个包含一个配置对象的数组。当只有一个配置对象时,你也可以只导出这个对象而不使用外部数组。

🌐 This plugin exports a recommended config that is an array with one config object. When there is just one config object, you can also export just the object without an enclosing array.

为了在配置文件中使用插件的配置,导入插件并使用 extends 键来引用配置的名称,如下所示:

🌐 In order to use a config from a plugin in a configuration file, import the plugin and use the extends key to reference the name of the config, like this:

// eslint.config.js
import { defineConfig } from "eslint/config";
import example from "eslint-plugin-example";

export default defineConfig([
	{
		files: ["**/*.js"], // any patterns you want to apply the config to
		plugins: {
			example,
		},
		extends: ["example/recommended"],
	},
]);

旧配置的向后兼容性

🌐 Backwards Compatibility for Legacy Configs

如果你的插件需要导出既能与当前(扁平配置)系统一起使用,又能与旧的(eslintrc)系统一起使用的配置,你可以从 configs 键导出两种配置类型。在导出旧版配置时,我们建议在名称前加上 "legacy-" 前缀(例如 "legacy-recommended"),以明确配置的使用方式。

🌐 If your plugin needs to export configs that work both with the current (flat config) system and the old (eslintrc) system, you can export both config types from the configs key. When exporting legacy configs, we recommend prefixing the name with "legacy-" (for example, "legacy-recommended") to make it clear how the config should be used.

如果你正在开发一个在 ESLint v9.0.0 之前就存在的插件,那么你可能已经有名为 "recommended" 的旧配置。如果你不想更新配置名称,你也可以在 configs 对象中创建一个以 "flat/" 为前缀的额外条目(例如,"flat/recommended")。示例如下:

🌐 If you’re working on a plugin that has existed prior to ESLint v9.0.0, then you may already have legacy configs with names such as "recommended". If you don’t want to update the config name, you can also create an additional entry in the configs object prefixed with "flat/" (for example, "flat/recommended"). Here’s an example:

const plugin = {
	meta: {
		name: "eslint-plugin-example",
		version: "1.2.3",
	},
	configs: {},
	rules: {
		"dollar-sign": {
			create(context) {
				// rule implementation ...
			},
		},
	},
};

// assign configs here so we can reference `plugin`
Object.assign(plugin.configs, {
	// flat config format
	"flat/recommended": [
		{
			plugins: {
				example: plugin,
			},
			rules: {
				"example/dollar-sign": "error",
			},
			languageOptions: {
				globals: {
					myGlobal: "readonly",
				},
				parserOptions: {
					ecmaFeatures: {
						jsx: true,
					},
				},
			},
		},
	],

	// eslintrc format
	recommended: {
		plugins: ["example"],
		rules: {
			"example/dollar-sign": "error",
		},
		globals: {
			myGlobal: "readonly",
		},
		parserOptions: {
			ecmaFeatures: {
				jsx: true,
			},
		},
	},
});

// for ESM
export default plugin;

// OR for CommonJS
module.exports = plugin;

通过这种方法,两个配置系统都能识别 "recommended"。旧的配置系统使用 recommended 键,而当前的配置系统使用 flat/recommended 键。defineConfig() 辅助程序首先查看 recommended 键,如果格式不正确,它会查找 flat/recommended 键。这允许你在以后如果不再需要支持旧的配置系统时,将 flat/recommended 重命名为 recommended,提供升级路径。

🌐 With this approach, both configuration systems recognize "recommended". The old config system uses the recommended key while the current config system uses the flat/recommended key. The defineConfig() helper first looks at the recommended key, and if that is not in the correct format, it looks for the flat/recommended key. This allows you an upgrade path if you’d later like to rename flat/recommended to recommended when you no longer need to support the old config system.

测试插件

🌐 Testing a Plugin

ESLint 提供了 RuleTester 工具,使测试你的插件规则变得容易。

🌐 ESLint provides the RuleTester utility to make it easy to test the rules of your plugin.

对插件进行代码检查

🌐 Linting a Plugin

ESLint 插件也应该进行代码检查!建议使用以下 recommended 配置对你的插件进行检查:

🌐 ESLint plugins should be linted too! It’s suggested to lint your plugin with the recommended configurations of:

共享插件

🌐 Share Plugins

为了让你的插件公开可用,你必须将其发布到 npm。在此过程中,请务必:

🌐 In order to make your plugin available publicly, you have to publish it on npm. When doing so, please be sure to:

  1. 将 ESLint 列为 peer 依赖。 因为插件是为与 ESLint 一起使用而设计的,所以将 eslint 包添加为 peer 依赖非常重要。为此,请手动编辑你的 package.json 文件以包含一个 peerDependencies 块,如下所示:

    {
    	"peerDependencies": {
    		"eslint": ">=10.0.0"
    	}
    }
    
  2. 指定关键字。 ESLint 插件应在你的 package.json 文件中将 eslinteslintplugineslint-plugin 指定为 关键字