跳到内容

添加 Linter 规则

通过添加新的 linter 规则,可以对 Oxlint 做出最好、最简单的贡献。

本指南将指导你完成此流程,使用 ESLint 的 no-debugger 规则作为示例。

提示

请确保先阅读 设置说明

步骤 1:挑选一项规则

我们的 Linter 产品计划与进度 问题追踪了我们计划从现有 ESLint 插件中实施的所有规则的状态。从中挑选一个你感兴趣的插件,并查找一条尚未实施的规则。

大多数 ESLint 规则的文档页面都会包含一条指向该规则 源代码 的链接。将此作为参考资料将有助于你实现规则。

步骤 2:生成规则

接下来,运行 rulegen 脚本,为你的新规则生成样板代码。

bash
just new-rule no-debugger

这将在 crates/oxc_linter/rules/<plugin-name>/<rule-name>.rs 中创建一个新文件,其中包含规则实现的开头,以及从 ESLint 移植的所有测试用例。

对于属于不同插件的规则,你需要使用该插件自己的 rulegen 脚本。

提示

运行没有参数的 just 可查看所有可用命令。

bash
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

生成的文件看起来类似于这样

单击可展开
rust
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 作为占位符。这有助于其他贡献者查找和实现遗漏的修复器。

诊断

创建一个函数以创建规则违规的诊断。遵循以下原则

  1. message 应是对错误内容的命令式陈述,而不是对规则功能的描述。
  2. help 消息应为命令式陈述,告诉用户如何解决问题。
rust
fn no_debugger_diagnostic(span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn("`debugger` statement is not allowed")
        .with_help("Remove this `debugger` statement")
        .with_label(span)
}
rust
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

  1. 在每个 AST 节点上运行(通过 run
  2. 在每个符号上运行(通过 run_on_symbol
  3. 在整个文件中运行一次(通过 run_once

对于 no-debugger,我们正在查找 DebuggerStatement 节点,因此我们将使用 run。以下是简化版的规则

单击可展开
rust
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));
        }
    }
}

提示

您将希望熟悉 Semantic 中存储的数据,它是存储语义分析期间提取的所有数据的地方。您还将希望熟悉 AST 结构。这里最重要的两个数据结构是 AstNodeAstKind

步骤 5:测试

要在每次进行更改时测试您的规则,请运行

bash
just watch "test -p oxc_linter -- rule-name"

或者仅测试一次,请运行

bash
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 readyjust r 以在本地运行 CI 检查。您还可以运行 just fix 以自动修复任何轻微错误、格式或拼写问题。一旦通过 just ready,则创建一个 PR,维护人员将查看您的更改。

一般建议

针对最短的代码范围指出错误信息

我们希望用户专注于有问题的代码,而不是破译错误信息以识别代码的哪个部分有误。

使用 let-else 语句

如果您发现自己深度嵌套 if-let 语句,请考虑改用 let-else

提示

CodeAesthetic 的 从不嵌套视频 更详细地阐述了这个概念。

rust
// 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;
    };
    // ...
}
rust
// 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 周期。

rust
struct Element {
  name: CompactStr
}

let element = Element {
  name: "div".into()
};
rust
struct Element {
  name: String
}

let element = Element {
  name: "div".to_string()
};

根据麻省理工学院许可证发布。