语言
从 ESLint v9.7.0 开始,你可以通过插件用其他语言扩展 ESLint。虽然 ESLint 最初是作为专门针对 JavaScript 的代码检查工具,但 ESLint 核心是通用的,可以用于检查任何编程语言。每种语言都定义为一个对象,其中包含 lint 一个文件所需的所有解析、评估和遍历功能。这些语言随后以插件的形式分发,供用户配置使用。
🌐 Starting with ESLint v9.7.0, you can extend ESLint with additional languages through plugins. While ESLint began as a linter strictly for JavaScript, the ESLint core is generic and can be used to lint any programming language. Each language is defined as an object that contains all of the parsing, evaluating, and traversal functionality required to lint a file. These languages are then distributed in plugins for use in user configurations.
语言要求
🌐 Language Requirements
为了创建一种语言,你需要:
🌐 In order to create a language, you need:
- 解析器。 解析器是将纯文本转换为数据结构的部分。ESLint 并不要求数据结构使用特定格式,因此你可以使用任何现有的解析器,或者自己编写一个。
- 一个
SourceCode对象。 ESLint 与 AST 的工作方式是通过一个SourceCode对象。每个SourceCode上都有一些必需的方法,你也可以添加更多你想要向规则公开的方法或属性。 - 一个
Language对象。Language对象包含有关语言本身的信息,以及用于解析和创建SourceCode对象的方法。
语言的解析器要求
🌐 Parser Requirements for Languages
首先,确保你拥有一个可以从 JavaScript 调用的解析器。解析器必须返回一个表示已解析代码的数据结构。大多数解析器返回一个抽象语法树(AST)来表示代码,但它们也可以返回一个具体语法树(CST)。返回的是 AST 还是 CST 对 ESLint 来说无关紧要,重要的是有一个可以遍历的数据结构。
🌐 To get started, make sure you have a parser that can be called from JavaScript. The parser must return a data structure representing the code that was parsed. Most parsers return an abstract syntax tree (AST) to represent the code, but they can also return a concrete syntax tree (CST). Whether an AST or CST is returned doesn’t matter to ESLint, it only matters that there is a data structure to traverse.
虽然 AST 或 CST 没有必须遵循的特定结构,但当树中的每个节点包含以下信息时,与 ESLint 集成会更容易:
🌐 While there is no specific structure an AST or CST must follow, it’s easier to integrate with ESLint when each node in the tree contains the following information:
-
类型 - 每个节点上都需要有一个表示节点类型的属性。例如,在 JavaScript 中,
type属性包含了每个节点的此类信息。ESLint 规则使用节点类型来定义访问者方法,因此每个节点能够通过字符串被识别是很重要的。属性的名称无关紧要(下面会进一步讨论),只要存在即可。大多数解析器通常将此属性命名为type或kind。 -
位置 - 每个节点上必须有一个属性,用于表示该节点在原始源代码中的位置。该位置必须包含:
- 节点开始的行
- 节点开始的列
- 节点结束的行
- 节点结束的列
与节点类型一样,属性名称无关紧要。两个常用的属性名称是
loc(如 ESTree 所示)和position(如 Unist 所示)。这些信息被 ESLint 用于报告错误和规则违规。 -
Range - 每个节点上都需要有一个属性,用于表示节点在源代码中的位置。范围指示第一个字符所在的索引以及最后一个字符之后的索引,这样调用
code.slice(start, end)就可以返回节点表示的文本。同样,这个属性名称没有特定要求,这些信息甚至可以与位置信息合并。ESTree 使用range属性,而 Unist 则在position上包含这些信息及位置信息。ESLint 使用这些信息来应用自动修复。
SourceCode 对象
🌐 The SourceCode Object
ESLint 在 SourceCode 对象中保存关于源代码的信息。这个对象是 ESLint 内部以及为代码编写的规则(通过 context.sourceCode)使用的 API。SourceCode 对象必须实现 @eslint/core 包中定义的 TextSourceCode 接口。
🌐 ESLint holds information about source code in a SourceCode object. This object is the API used both by ESLint internally and by rules written to work on the code (via context.sourceCode). The SourceCode object must implement the TextSourceCode interface as defined in the @eslint/core package.
一个基本的 SourceCode 对象必须实现以下内容:
🌐 A basic SourceCode object must implement the following:
ast- 包含源代码的 AST 或 CST 的属性。text- 源代码的文本。getLoc(nodeOrToken)- 一种返回给定节点或标记位置的方法。这必须与 ESTree 使用的loc结构相匹配。getRange(nodeOrToken)- 一种返回给定节点或标记范围的方法。此方法必须返回一个数组,其中第一个元素是起始索引,第二个元素是结束索引。traverse()- 一种返回可迭代对象的方法,用于遍历 AST 或 CST。该迭代器必须返回实现了@eslint/core中的VisitTraversalStep或CallTraversalStep的对象。
以下可选成员允许你自定义 ESLint 与对象的交互方式:
🌐 The following optional members allow you to customize how ESLint interacts with the object:
visitorKeys- 仅针对这个SourceCode对象的访客键。通常不需要,因为大多数情况下使用的是Language#visitorKeys。applyLanguageOptions(languageOptions)- 如果你有需要在解析后应用的特定语言选项,则可以使用此方法执行此操作。getDisableDirectives()- 返回代码中的任何禁用指令。ESLint 使用它来应用禁用指令并跟踪未使用的指令。getInlineConfigNodes()- 返回任何内联配置节点。当启用noInlineConfig时,ESLint 使用它来报告错误。applyInlineConfig()- 返回 ESLint 的内联配置元素。ESLint 使用它来更改正在进行 lint 的文件的配置。finalize()- 此方法在开始 linting 之前被调用,这是你修改SourceCode的最后机会。如果你已经定义了applyLanguageOptions()或applyInlineConfig(),那么在SourceCode对象准备好之前,你可能还需要进行额外的修改。
此外,以下成员在 SourceCode 对象上很常见,建议实现:
🌐 Additionally, the following members are common on SourceCode objects and are recommended to implement:
lines- 源代码的各个行作为字符串数组。getParent(node)- 返回给定节点的父节点,如果该节点是根节点,则返回undefined。getAncestors(node)- 返回一个节点的祖级数组,第一个元素为树的根,每个后续元素为通向node的从根开始的后代。getText(node, beforeCount, afterCount)- 返回表示给定节点的字符串,以及可选的节点范围前后指定数量的字符。
将 JSONSourceCode 作为基本 SourceCode 类的示例。
🌐 See JSONSourceCode as an example of a basic SourceCode class.
Language 对象
🌐 The Language Object
Language 对象包含关于编程语言的所有信息,以及与该语言编写的代码进行交互的方法。ESLint 使用此对象来确定如何处理特定文件。Language 对象必须实现 @eslint/core 包中定义的 Language 接口。
🌐 The Language object contains all of the information about the programming language as well as methods for interacting with code written in that language. ESLint uses this object to determine how to deal with a particular file. The Language object must implement the Language interface as defined in the @eslint/core package.
一个基本的 Language 对象必须实现以下内容:
🌐 A basic Language object must implement the following:
fileType- 应该是"text"(将来,我们也会支持"binary")lineStart- 使用 0 或 1 来指示 AST 如何表示文件中的第一行。ESLint 使用此信息来正确显示错误位置。columnStart- 使用 0 或 1 来指示 AST 如何表示每行的第一列。ESLint 使用它来正确显示错误位置。nodeTypeKey- 表示节点类型的属性名称(通常是"type"或"kind")。validateLanguageOptions(languageOptions)- 验证该语言的语言选项。当预期的语言选项没有正确的类型或值时,预计此方法会抛出验证错误。意外的语言选项应被悄无声息地忽略,不应抛出任何错误。即使该语言未指定任何选项,也需要此方法。parse(file, context)- 将给定文件解析为 AST 或 CST,并且还可以包括用于规则的附加值。由 ESLint 内部调用。createSourceCode(file, parseResult, context)- 创建一个SourceCode对象。在parse()之后由 ESLint 内部调用,第二个参数是来自parse()的精确返回值。
以下可选成员允许你自定义 ESLint 与对象的交互方式:
🌐 The following optional members allow you to customize how ESLint interacts with the object:
visitorKeys- 访问者键是特定于 AST 或 CST 的。这用于优化在 ESLint 内部对 AST 或 CST 的遍历。虽然不是必需的,但强烈推荐使用,尤其是对于与 ESTree 格式差异较大的 AST 或 CST 格式。defaultLanguageOptions- 当使用该语言时,默认languageOptions。在计算被 lint 的文件的配置时,用户指定的languageOptions会与此对象合并。matchesSelectorClass(className, node, ancestry)- 允许你指定选择器类,例如:expression,以匹配多个节点。每当一个 esquery 选择器包含:后跟标识符时,将调用此方法。normalizeLanguageOptions(languageOptions)- 接受一个已验证的语言选项对象并规范其值。当语言选项属性发生变化时,这对于向后兼容非常有用,同时也可以通过toJSON()方法添加自定义序列化。
将 JSONLanguage 视为一个基本的 Language 类的示例。
🌐 See JSONLanguage as an example of a basic Language class.
在插件中发布语言
🌐 Publish a Language in a Plugin
语言以类似于处理器和规则的插件形式发布。在你的插件中将 languages 键定义为一个对象,该对象的名称是语言名称,值是语言对象。示例如下:
🌐 Languages are published in plugins similar to processors and rules. Define the languages key in your plugin as an object whose names are the language names and the values are the language objects. Here’s an example:
import { myLanguage } from "../languages/my.js";
const plugin = {
// preferred location of name and version
meta: {
name: "eslint-plugin-example",
version: "1.2.3",
},
languages: {
my: myLanguage,
},
rules: {
// add rules here
},
};
// for ESM
export default plugin;
// OR for CommonJS
module.exports = plugin;
为了在配置文件中使用来自插件的语言,导入该插件并将其包含在 plugins 键中,指定一个命名空间。然后,在 language 配置中使用该命名空间来引用该语言,如下所示:
🌐 In order to use a language 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 language in the language configuration, like this:
// eslint.config.js
import { defineConfig } from "eslint/config";
import example from "eslint-plugin-example";
export default defineConfig([
{
files: ["**/*.my"],
plugins: {
example,
},
language: "example/my",
},
]);
有关更多详情,请参阅插件配置文档中的 指定语言。
🌐 See Specify a Language in the Plugin Configuration documentation for more details.