添加 Linter 规则
通过添加新的 linter 规则,可以对 Oxlint 做出最好、最简单的贡献。
本指南将指导你完成此流程,使用 ESLint 的 no-debugger
规则作为示例。
提示
请确保先阅读 设置说明。
步骤 1:挑选一项规则
我们的 Linter 产品计划与进度 问题追踪了我们计划从现有 ESLint 插件中实施的所有规则的状态。从中挑选一个你感兴趣的插件,并查找一条尚未实施的规则。
大多数 ESLint 规则的文档页面都会包含一条指向该规则 源代码 的链接。将此作为参考资料将有助于你实现规则。
步骤 2:生成规则
接下来,运行 rulegen 脚本,为你的新规则生成样板代码。
just new-rule no-debugger
这将在 crates/oxc_linter/rules/<plugin-name>/<rule-name>.rs
中创建一个新文件,其中包含规则实现的开头,以及从 ESLint 移植的所有测试用例。
对于属于不同插件的规则,你需要使用该插件自己的 rulegen 脚本。
提示
运行没有参数的 just
可查看所有可用命令。
just new-jest-rule [name] # for eslint-plugin-jest
just new-jsx-a11y-rule [name] # for eslint-plugin-jsx-a11y
just new-n-rule [name] # for eslint-plugin-n
just new-nextjs-rule [name] # for eslint-plugin-next
just new-oxc-rule [name] # for oxc's own rules
just new-promise-rule [name] # for eslint-plugin-promise
just new-react-rule [name] # for eslint-plugin-react and eslint-plugin-react-hooks
just new-ts-rule [name] # for @typescript-eslint/eslint-plugin
just new-unicorn-rule [name] # for eslint-plugin-unicorn
just new-vitest-rule [name] # for eslint-plugin-vitest
生成的文件看起来类似于这样
单击可展开
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use crate::{
context::LintContext,
fixer::{RuleFix, RuleFixer},
rule::Rule,
AstNode,
};
#[derive(Debug, Default, Clone)]
pub struct NoDebugger;
declare_oxc_lint!(
/// ### What it does
///
///
/// ### Why is this bad?
///
///
/// ### Examples
///
/// Examples of **incorrect** code for this rule:
/// ```js
/// FIXME: Tests will fail if examples are missing or syntactically incorrect.
/// ```
///
/// Examples of **correct** code for this rule:
/// ```js
/// FIXME: Tests will fail if examples are missing or syntactically incorrect.
/// ```
NoDebugger,
nursery, // TODO: change category to `correctness`, `suspicious`, `pedantic`, `perf`, `restriction`, or `style`
// See <https://oxc.npmjs.net.cn/docs/contribute/linter.html#rule-category> for details
pending // TODO: describe fix capabilities. Remove if no fix can be done,
// keep at 'pending' if you think one could be added but don't know how.
// Options are 'fix', 'fix_dangerous', 'suggestion', and 'conditional_fix_suggestion'
);
impl Rule for NoDebugger {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {}
}
#[test]
fn test() {
use crate::tester::Tester;
let pass = vec!["var test = { debugger: 1 }; test.debugger;"];
let fail = vec!["if (foo) debugger"];
Tester::new(NoDebugger::NAME, pass, fail).test_and_snapshot();
}
然后需要向 linter 注册新创建的规则。将规则添加到 rules.rs
(例如 此处,适用于 no-debugger
)中的合适 mod
,并将其添加到 oxc_macros::declare_all_lint_rules!
(例如 此处)。
现在应该可以运行规则了!可以通过 cargo test -p oxc_linter
试用。测试应该会失败,因为尚未实现该规则。
步骤 3:填写模板
文档
填写各个文档部分。
- 明确简洁地总结该规则的功能。
- 说明该规则的重要性以及它防止的哪些不良行为。
- 提供违反该规则和不违反该规则的代码示例。
请记住,我们使用此文档为本网站生成 规则文档页面,因此请确保文档清晰且有帮助!
规则类别
首先,选择一个最适合该规则的 规则类别。请记住,correctness
规则将默认运行,因此选择此类别时要小心。在 declare_oxc_lint!
宏中设置类别。
修复器状态
如果该规则有修复器,请在 declare_oxc_lint!
中注册它提供的修复类型。如果不熟悉修复器的实现,也可以将 pending
作为占位符。这有助于其他贡献者查找和实现遗漏的修复器。
诊断
创建一个函数以创建规则违规的诊断。遵循以下原则
message
应是对错误内容的命令式陈述,而不是对规则功能的描述。help
消息应为命令式陈述,告诉用户如何解决问题。
fn no_debugger_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("`debugger` statement is not allowed")
.with_help("Remove this `debugger` statement")
.with_label(span)
}
fn no_debugger_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Disallow `debugger` statements")
.with_help("`debugger` statements are not allowed.")
.with_label(span)
步骤 4:规则实施
阅读规则的源代码,了解其工作原理。尽管 Oxlint 的工作方式与 ESLint 类似,但不太可能直接移植规则。
ESLint 规则具有一个 create
函数,该函数返回一个 object,其 key 为触发规则的 AST 节点,value 为在这些节点上运行 lints 的函数。Oxlint 规则在一些触发器上运行,每个触发器都来自 Rule
trait
- 在每个 AST 节点上运行(通过
run
) - 在每个符号上运行(通过
run_on_symbol
) - 在整个文件中运行一次(通过
run_once
)
对于 no-debugger
,我们正在查找 DebuggerStatement
节点,因此我们将使用 run
。以下是简化版的规则
单击可展开
use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use crate::{context::LintContext, rule::Rule, AstNode};
fn no_debugger_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("`debugger` statement is not allowed")
.with_label(span)
}
#[derive(Debug, Default, Clone)]
pub struct NoDebugger;
declare_oxc_lint!(
/// ### What it does
/// Checks for usage of the `debugger` statement
///
/// ### Why is this bad?
/// `debugger` statements do not affect functionality when a
/// debugger isn't attached. They're most commonly an
/// accidental debugging leftover.
///
/// ### Example
///
/// Examples of **incorrect** code for this rule:
/// ```js
/// async function main() {
/// const data = await getData();
/// const result = complexCalculation(data);
/// debugger;
/// }
/// ```
NoDebugger,
correctness
);
impl Rule for NoDebugger {
// Runs on each node in the AST
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
// `debugger` statements have their own AST kind
if let AstKind::DebuggerStatement(stmt) = node.kind() {
// Report a violation
ctx.diagnostic(no_debugger_diagnostic(stmt.span));
}
}
}
步骤 5:测试
要在每次进行更改时测试您的规则,请运行
just watch "test -p oxc_linter -- rule-name"
或者仅测试一次,请运行
cargo test -p oxc_linter -- rule-name
# Or
cargo insta test -p oxc_linter -- rule-name
Oxlint 使用 cargo insta
进行快照测试。如果快照已更改或刚刚创建,则 cargo test
将失败。您可以运行 cargo insta test -p oxc_linter
来不查看测试结果中的差异。您可以通过运行 cargo insta review
来查看快照,或跳过查看并使用 cargo insta accept
接受所有更改。
当您准备好提交 PR 时,运行 just ready
或 just r
以在本地运行 CI 检查。您还可以运行 just fix
以自动修复任何轻微错误、格式或拼写问题。一旦通过 just ready
,则创建一个 PR,维护人员将查看您的更改。
一般建议
针对最短的代码范围指出错误信息
我们希望用户专注于有问题的代码,而不是破译错误信息以识别代码的哪个部分有误。
使用 let-else
语句
如果您发现自己深度嵌套 if-let
语句,请考虑改用 let-else
。
提示
CodeAesthetic 的 从不嵌套视频 更详细地阐述了这个概念。
// let-else is easier to read
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::JSXOpeningElement(jsx_opening_elem) = node.kind() else {
return;
};
let Some(expr) = container.expression.as_expression() else {
return;
};
let Expression::BooleanLiteral(expr) = expr.without_parenthesized() else {
return;
};
// ...
}
// deep nesting is hard to read
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::JSXOpeningElement(jsx_opening_elem) = node.kind() {
if let Some(expr) = container.expression.as_expression() {
if let Expression::BooleanLiteral(expr) = expr.without_parenthesized() {
// ...
}
}
}
}
尽可能使用 CompactStr
尽可能减少分配对于 oxc
的性能至关重要。String
类型需要在堆上分配内存,这会消耗内存和 CPU 周期。有可能使用 CompactStr
将小字符串内联存储(在 64 位系统上最多 24 个字节)在堆栈上,这意味着我们无需分配内存。如果字符串太大而无法内联存储,它将分配必要的空间。几乎任何具有 String
或 &str
类型的地方都可以使用 CompactStr
,与 String
类型相比,它可以节省大量的内存和 CPU 周期。
struct Element {
name: CompactStr
}
let element = Element {
name: "div".into()
};
struct Element {
name: String
}
let element = Element {
name: "div".to_string()
};