JS 插件
🌐 JS Plugins
Oxlint 支持用 JS 编写的插件——可以是自定义编写的,也可以来自 NPM。
🌐 Oxlint supports plugins written in JS - either custom-written, or from NPM.
Oxlint 的插件 API 与 ESLint v9 及以上版本兼容,因此大多数现有的 ESLint 插件应能在 Oxlint 上开箱即用。
🌐 Oxlint's plugin API is compatible with ESLint v9+, so most existing ESLint plugins should work out of the box with Oxlint.
我们正在努力实现 ESLint 的所有插件 API,Oxlint 很快将能够运行任何 ESLint 插件。
🌐 We are working towards implementing all of ESLint's plugin APIs, and Oxlint will soon be able to run any ESLint plugin.
WARNING
JS 插件目前处于技术预览阶段,并且仍在积极开发中。几乎 ESLint 的所有插件 API 都已实现(见下文[#api-support])。
所有API的行为都应与ESLint保持一致。如果你发现任何行为上的差异, 这是个bug——请[举报](https://github.com/oxc-project/oxc/issues/new?template=linter_bug_report.yaml)。
🌐 All APIs should behave identically to ESLint. If you find any differences in behavior, that's a bug - please report it. :::
使用 JS 插件
🌐 Using JS plugins
- 在
jsPlugins下,将插件路径添加到.oxlintrc.json配置文件中。 - 在“rules”下添加插件中的规则。
路径可以是任何有效的导入说明符,例如 ./plugin.js、eslint-plugin-foo 或 @foo/eslint-plugin。路径是相对于配置文件本身解析的。
🌐 The path can be any valid import specifier e.g. ./plugin.js, eslint-plugin-foo, or @foo/eslint-plugin. Paths are resolved relative to the config file itself.
// .oxlintrc.json
{
"jsPlugins": ["./path/to/my-plugin.js", "eslint-plugin-whatever", "@foobar/eslint-plugin"],
"rules": {
"my-plugin/rule1": "error",
"my-plugin/rule2": "warn",
"whatever/rule1": "error",
"whatever/rule2": "warn",
"@foobar/rule1": "error"
}
// ... other config ...
}插件别名
🌐 Plugin aliases
你还可以为插件定义一个不同的名称(别名)。如果出现以下情况,这将非常有用:
🌐 You can also define a different name (alias) for a plugin. This is useful if:
- 默认插件名称与本地 Oxlint 插件的名称冲突(例如 jsdoc、react 等)。
- 默认插件名称很长。
- 你想使用 Oxlint 原生支持的插件,但你需要的特定规则尚未在 Oxlint 原生版本中实现。
{
"jsPlugins": [
// `jsdoc` is a reserved name, as Oxlint supports it natively
{
"name": "jsdoc-js",
"specifier": "eslint-plugin-jsdoc"
},
// Shorten name
{
"name": "short",
"specifier": "eslint-plugin-with-name-so-very-very-long"
},
// List plugins you don't want to alias as just specifiers
"eslint-plugin-whatever"
],
"rules": {
"jsdoc-js/check-alignment": "error",
"short/rule1": "error",
"whatever/rule2": "error"
}
}编写 JS 插件
🌐 Writing JS plugins
兼容 ESLint 的 API
🌐 ESLint-compatible API
Oxlint 提供了与 ESLint 相同的插件 API。请参阅 ESLint 关于创建插件和自定义规则的文档。
🌐 Oxlint provides a plugin API identical to ESLint's. See ESLint's docs on creating a plugin and custom rules.
一个简单的插件,用于标记包含超过 5 个类声明的文件:
🌐 A simple plugin which flags files containing more than 5 class declarations:
// plugin.js
const rule = {
create(context) {
let classCount = 0;
return {
ClassDeclaration(node) {
classCount++;
if (classCount === 6) {
context.report({ message: "Too many classes", node });
}
},
};
},
};
const plugin = {
meta: {
name: "best-plugin-ever",
},
rules: {
"max-classes": rule,
},
};
export default plugin;// .oxlintrc.json
{
"jsPlugins": ["./plugin.js"],
"rules": {
"best-plugin-ever/max-classes": "error"
}
}替代 API
🌐 Alternative API
Oxlint 还提供了一个略有不同的备用 API,性能更高。
🌐 Oxlint also provides a slightly different alternative API which is more performant.
使用此 API 创建的规则仍与 ESLint 兼容(见下文)。
🌐 Rules created with this API remain compatible with ESLint (see below).
与上面相同的规则,使用替代 API:
🌐 Same rule as above, using the alternative API:
import { defineRule } from "oxlint";
const rule = defineRule({
createOnce(context) {
// Define counter variable
let classCount;
return {
before() {
// Reset counter before traversing AST of each file
classCount = 0;
},
// Same as before
ClassDeclaration(node) {
classCount++;
if (classCount === 6) {
context.report({ message: "Too many classes", node });
}
},
};
},
});区别如下:
🌐 The differences are:
- 将规则对象封装在
defineRule(...)中。
- const rule = {
+ const rule = defineRule({- 使用
createOnce替代create。
- create(context) {
+ createOnce(context) {create(ESLint 的 API)会针对 每个文件 被重复调用,而createOnce只会被调用一次。请改为在before钩子中执行任何每个文件的初始化操作。
- let classCount = 0;
+ let classCount;
return {
+ before() {
+ classCount = 0; // Reset counter
+ },
ClassDeclaration(node) {
classCount++;
if (classCount === 6) {
context.report({ message: "Too many classes", node });
}
},
};
},
});defineRule 是做什么的?
🌐 What does defineRule do?
defineRule 为规则添加了一个 create 方法,该方法委托给 createOnce。
这意味着该规则可以与 Oxlint 或 ESLint 一起使用。
- 在 Oxlint 中,它将从更快的
createOnceAPI 中获得性能提升。 - 在 ESLint 中,它的工作方式与使用原始 ESLint
createAPI 编写时完全相同。
definePlugin
如果你的插件包含多个规则,将整个插件封装在 definePlugin 中,与将每个单独的规则封装在 defineRule 中效果相同。
🌐 If your plugin includes multiple rules, wrapping the whole plugin in definePlugin has same effect as wrapping each individual rule in defineRule.
import { definePlugin } from "oxlint";
const plugin = definePlugin({
meta: { name: "my-plugin" },
rules: {
"no-foo": rule1,
"no-bar": rule2,
},
});跳过 AST 遍历
🌐 Skipping AST traversal
从 before 钩子返回 false 会导致规则跳过此文件。
🌐 Returning false from before hook causes the rule to skip this file.
// This rule does not run on files which start with a `// @skip-me` comment
const rule = defineRule({
createOnce(context) {
return {
before() {
if (context.sourceCode.text.startsWith("// @skip-me")) {
return false;
}
},
FunctionDeclaration(node) {
// Do stuff
},
};
},
});这相当于 ESLint 中的此模式:
🌐 This is equivalent to this pattern in ESLint:
const rule = {
create(context) {
if (context.sourceCode.text.startsWith("// @skip-me")) {
return {};
}
return {
FunctionDeclaration(node) {
// Do stuff
},
};
},
};before 钩
🌐 before hook
before 钩子在访问 AST 之前运行。
重要:before 钩子不能保证在每个文件上运行。
🌐 IMPORTANT: before hook is NOT guaranteed to run on every file.
目前是这样的,但将来我们打算在 Rust 端添加逻辑,以确定规则是否需要执行,基于规则“感兴趣的”AST节点以及AST包含的内容。这将通过跳过 Rust 调用到 JS 的冗余操作来提高性能。
🌐 At present it does, but in future we intend to add logic on Rust side to determine if the rule needs to run or not, based on what AST nodes the rule is "interested in", and what the AST contains. This will enable better performance by skipping redundant calls from Rust into JS.
在上面的例子中,如果文件不包含任何 FunctionDeclaration,则对该文件运行规则将被完全跳过,_包括_跳过 before 钩子。
🌐 In example above, if a file does not contain any FunctionDeclarations, running the rule on that file will be skipped entirely, including skipping the before hook.
如果你需要代码对每个文件都只运行一次,请改为实现一个 Program 访问器:
🌐 If you need code to always run once for every file, implement a Program visitor instead:
const rule = defineRule({
createOnce(context) {
return {
Program(node) {
// This always runs for every file, even if
// it doesn't contain any `FunctionDeclaration`s
},
FunctionDeclaration(node) {
/* do stuff */
},
};
},
});after 钩
🌐 after hook
还有一个 after 钩子。它每个文件运行一次,在整个 AST 遍历完成之后(在 Program:exit 之后)。
🌐 There is also an after hook. It runs once per file, after the whole AST has been traversed (after Program:exit).
在规则的 AST 遍历过程中,用它来清理任何使用的昂贵资源。
🌐 Use it to clean up any expensive resources used during the rule's AST traversal.
如果 before 钩子返回 false 来跳过对该文件运行规则,那么 after 钩子也将被跳过。
🌐 If before hook returns false to skip running the rule on the file, after hook will be skipped too.
与 before 钩子相同,after 钩子并不保证会在每个文件上运行(见上文)。
🌐 Same as before hook, after hook is NOT guaranteed to run on every file (see above).
为什么替代 API 更快?
🌐 Why is the alternative API faster?
简短回答:现在还不是。但它_很快就会_。
🌐 Short answer: Right now it isn't. But it will be soon.
在 JS 插件的初始技术预览版发布之前,我们经历了漫长的“研发”过程。我们已经发现了许多优化的机会,并且已经为 Oxlint 插件原型开发了下一版本,其性能非常出色。
🌐 Prior to the initial technical preview release of JS plugins, we have undergone a lengthy "R&D" process. We have identified many optimization opportunities, and have prototyped the next version of Oxlint plugins, which has extremely good performance.
其中许多优化尚未出现在当前版本中,但我们将在接下来的几个月里对它们进行打磨并整合到 Oxlint 中。
🌐 Many of those optimizations are not in the current release, but we'll be polishing them and folding them into Oxlint over the next few months.
替代 API 的设计旨在启用并利用这些优化。通过现在采用替代 API,插件作者只需提升 oxlint 版本号,而无需进行任何代码更改,就能在未来让他们的插件获得显著的速度提升,几乎是“免费”的。
🌐 The alternative API is designed to enable and capitalize on these optimizations. By adopting the alternative API now, plugin authors will see their plugins get a significant speed boost in future "for free", just by bumping oxlint version, without any code changes.
那些优化措施是什么?
🌐 What are those optimizations?
回到上面提到的“不超过5节课”规则的例子:
🌐 Returning to the "no more than 5 classes" rule example from above:
const rule = {
create(context) {
let classCount = 0;
return {
ClassDeclaration(node) {
classCount++;
if (classCount === 6) {
context.report({ message: "Too many classes", node });
}
},
};
},
};create 方法每个文件调用一次,每次都使用一个新的 context 对象。
🌐 The create method is called once per file, each time with a new context object.
那为什么会是个问题呢?
🌐 Why is that a problem?
为了获得最佳性能,理想情况下我们希望静态地知道规则“感兴趣”的 AST 节点。拥有这些信息后,我们可以执行两种优化:
🌐 For maximum performance, ideally we want to statically know what AST nodes the rule is "interested in". With that information, we can perform 2 optimizations:
- 不要在 JS 端遍历 AST。相反,在 Rust 端遍历 AST 的过程中,编译一个指向相关 AST 节点的“指针”列表。将该列表发送到 JS,JS 可以直接“跳转”到相关的 AST 节点,而不是搜索整个 AST。
- 如果 AST 中不包含任何与规则相关的 AST 节点(在上面的示例中,如果文件不包含类声明),则完全跳过对该文件的 JS 调用。
但是 JS 是一种动态语言,而 create 可以做任何事。它每次被调用时可能返回完全不同的访客对象。因此我们必须调用 create 来确定是否需要调用 create!
🌐 But JS is a dynamic language, and create could do anything. It could return a completely different visitor each time it's called. So we have to call create to find out whether we needed to call create!
相比之下,使用替代 API 时,createOnce 只会被调用一次,我们随后就能知道规则的作用。这使得上述优化成为可能。
🌐 In comparison, with the alternative API, createOnce is called only once, and we then know what the rule does. This enables the above optimizations.
需要明确的是,create API 并不是 ESLint 的糟糕设计决策。只是在 Rust 与 JS 互操作时会带来一些困难。
🌐 To be clear, the create API was not a poor design decision on ESLint's part. It just presents some difficulties once Rust-JS interop comes into play.
API 支持
🌐 API support
Oxlint 几乎支持 ESLint 的所有 API 接口:
🌐 Oxlint supports almost all of ESLint's API surface:
- AST 遍历
- AST 探索(
node.parent、context.sourceCode.getAncestors)。 - 修复。
- 规则选项。
- 选择器(ESLint 文档)。
SourceCodeAPI(例如context.sourceCode.getText(node))。SourceCode令牌 API(例如context.sourceCode.getTokens(node))。- 范围分析
- 控制流分析(代码路径)。
- 内联禁用指令。(
// oxlint-disable)
尚不支持:
🌐 Not supported yet:
- 语言服务器(IDE)支持 + 建议(所以还没有编辑器内诊断或快速修复功能)。
- 自定义文件格式和解析器(例如 Svelte、Vue、Angular)。
在 ESLint v9 或更早版本中被移除的 ESLint API 在大多数情况下将不会被重新实现。如果某个 ESLint 插件无人维护且从未更新以适配 ESLint v9 的 API 使用,你可能需要自己修改该插件或寻找替代方案。
🌐 ESLint APIs that were removed in ESLint v9 or earlier will not be implemented in most cases. If an ESLint plugin is unmaintained and was never updated to upgrade their API usage for ESLint v9, you may need to modify the plugin yourself or find an alternative.
我们将在接下来的几个月内实现剩余的功能,目标是支持 ESLint 插件 API 的 100% 功能。
🌐 We will be implementing the remaining features over the next few months, aiming to support 100% of ESLint's plugin API surface.
