跳至內容

新增 Linter 規則

對 Oxlint 做出貢獻的最佳和最簡單方法是新增新的 linter 規則。

本指南將使用 ESLint 的 no-debugger 規則為例,引導您完成此過程。

提示

請先閱讀設定說明

步驟 1:選擇規則

我們的Linter 產品計劃和進度議題追蹤了我們想要從現有 ESLint 外掛實作的所有規則狀態。從那裡選擇一個您感興趣的外掛,並找到一個尚未實作的規則。

大多數 ESLint 規則的文件頁面都包含指向規則原始碼的連結。以此作為參考將有助於您的實作。

步驟 2:規則生成

接下來,執行 rulegen 指令碼以生成新規則的樣板程式碼。

bash
just new-rule no-debugger

這將在 crates/oxc_linter/rules/<外掛名稱>/<規則名稱>.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.dev.org.tw/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中適當的 mod (例如此處no-debugger) 並將其新增至 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 函數,該函數會傳回一個物件,其鍵是觸發規則的 AST 節點,值是在這些節點上執行 lint 的函數。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 來自動修正任何 lint、格式或錯字問題。一旦 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 個位元組),這表示我們不需要配置記憶體。如果字串太大而無法內聯儲存,它將會配置必要的空間。CompactStr 可以用在幾乎任何具有類型 String&str 的地方,並且與 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()
};

在 MIT 許可下發布。