Skip to content

ライタールールの追加

Oxlintへの貢献の最良かつ最も簡単な方法は、新しいライタールールを追加することです。

このガイドでは、ESLintの no-debugger ルールを例に、このプロセスについて説明します。

TIP

まずセットアップ手順を読んでください。

ステップ1: ルールの選択

私たちの Linter製品計画と進捗 の問題には、既存のESLintプラグインから実装したいすべてのルールの状態が記録されています。そこから興味のあるプラグインを選んで、まだ実装されていないルールを見つけましょう。

重要: 今やESLint互換のJavaScriptプラグインサポートが利用可能になったため、新しいRustベースのプラグインの追加は計画していません。ただし、既存プラグインにルールを追加する貢献は強く推奨されています。ルールまたはプラグインをRustで書くことでメリットがあると考える場合は、プルリクエストを送る前にまず議論を開いてください。

ESLintルールのほとんどドキュメントページには、ルールのソースコードへのリンクが含まれています。これを参考にすることで、実装に役立ちます。

ステップ2: ルール生成

次に、ルール生成スクリプトを実行して、新しいルール用のボイラープレートコードを作成します。

bash
just new-rule no-debugger

これにより以下が行われます:

  1. crates/oxc_linter/src/rules/<plugin-name>/<rule-name>.rs に、ルール実装の初歩と、ESLintからのテストケースの移植がされた新しいファイルが作成される
  2. rules.rs 内の適切な mod にルールを登録する
  3. oxc_macros::declare_all_lint_rules! にルールを追加する

別のプラグインのルールの場合、そのプラグイン独自のルール生成スクリプトを使用する必要があります。

TIP

just に引数なしで実行すると、利用可能なすべてのコマンドが表示されます。

bash
just new-rule [name]            # ESLintコアルール用
just new-jest-rule [name]       # eslint-plugin-jest用
just new-ts-rule [name]         # @typescript-eslint/eslint-plugin用
just new-unicorn-rule [name]    # eslint-plugin-unicorn用
just new-import-rule [name]     # eslint-plugin-import用
just new-react-rule [name]      # eslint-plugin-reactおよびeslint-plugin-react-hooks用
just new-jsx-a11y-rule [name]   # eslint-plugin-jsx-a11y用
just new-oxc-rule [name]        # oxc自身のルール用
just new-nextjs-rule [name]     # eslint-plugin-next用
just new-jsdoc-rule [name]      # eslint-plugin-jsdoc用
just new-react-perf-rule [name] # eslint-plugin-react-perf用
just new-n-rule [name]          # eslint-plugin-n用
just new-promise-rule [name]    # eslint-plugin-promise用
just new-vitest-rule [name]     # 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!(
    /// ### 何をしますか
    ///
    ///
    /// ### なぜ問題なのでしょうか?
    ///
    ///
    /// ### 例
    ///
    /// このルールに対して**不正な**コードの例:
    /// ```js
    /// FIXME: 例が欠けているか構文的に誤っている場合、テストが失敗します。
    /// ```
    ///
    /// このルールに対して**正しい**コードの例:
    /// ```js
    /// FIXME: 例が欠けているか構文的に誤っている場合、テストが失敗します。
    /// ```
    NoDebugger,
    nursery, // TODO: カテゴリを `correctness`、`suspicious`、`pedantic`、`perf`、`restriction`、`style` のいずれかに変更してください
             // 詳細は <https://oxc.rs/docs/contribute/linter.html#rule-category> を参照してください

    pending  // TODO: フィックス機能について説明してください。フィックスが不可能な場合は削除し、
             // いつかフィックスを追加できると想定する場合は 'pending' を維持してください。
             // オプションは 'fix'、'fix_dangerous'、'suggestion'、'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();
}

あなたのルールは今すぐ実行可能になっています!cargo test -p oxc_linter で試してみましょう。まだルールの実装をしていないため、テストは失敗するはずです。

ステップ3: テンプレートの埋め込み

ドキュメント

さまざまなドキュメントセクションを埋め込みます。

  • ルールが何をするのかを明確かつ簡潔に説明してください。
  • ルールが重要な理由と、防ぐべき不適切な動作を説明してください。
  • ルール違反となるコードと、違反しないコードの例を提供してください。

注意してください。このドキュメントは、このウェブサイトのルールドキュメントページを生成するために使用するため、わかりやすく役立つものにしてください!

設定ドキュメント

ルールに設定オプションがある場合、それらをドキュメント化する必要があります。自動ドキュメント生成システムを使って行うべきです。ルール生成スクリプトによって、これはある程度自動的に生成されます。

各設定オプションは、ルールの構造体にフィールドを追加することで定義します:

rust
pub struct RuleName {
  option_name: bool,
  another_option: String,
  yet_another_option: Vec<CompactStr>,
}

あるいは、すべての設定オプションを保持する別々の Config 構造体を定義することもできます:

rust
pub struct RuleName(Box<RuleNameConfig>);

pub struct RuleNameConfig {
  option_name: bool,
}

設定オプションには JsonSchema を派生させ、さらに serde の装飾を付ける必要があります。たとえば:

rust
use schemars::JsonSchema;

#[derive(Debug, Default, Clone, JsonSchema)]
#[serde(rename_all = "camelCase", default)]
pub struct RuleName {
  option_name: bool,
}

各フィールドに説明コメント(///)を追加して、オプションについて説明してください。たとえば:

rust
use schemars::JsonSchema;

#[derive(Debug, Default, Clone, JsonSchema)]
#[serde(rename_all = "camelCase", default)]
pub struct RuleName {
  /// bazを評価する際に、fooとbarのチェックを行うかどうか。
  /// コメントは、オプションを完全に説明するのに必要な長さまで可能です。
  option_name: bool,
}

各オプションのデフォルト値と型は、構造体定義から自動的に抽出されるため、ドキュメントコメント内では言及しないでください。

すべての種類のルールで設定オプションを適切にドキュメント化する方法の例が数十個あるこちらの問題をご覧ください。

cargo run -p website -- linter-rules --rule-docs target/rule-docs --git-ref $(git rev-parse HEAD) を実行して target/rule-docs/<plugin-name>/<rule-name>.md を開くことで、生成されたドキュメントを確認できます。

ルールカテゴリ

まず、ルールに最も適したルールカテゴリを選んでください。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: ルールの実装

ルールのソースコードを読み、どのように動くのかを理解しましょう。オックシンはESLintと似た動作をしますが、ルールを直接移植することは可能性が低いです。

ESLintルールは、キーワードがルールをトリガーするASTノードであり、値がそのノード上でランチを実行する関数であるオブジェクトを返す create 関数を持っています。一方、Oxlintルールはいくつかのトリガーのうち1つに基づいて実行され、それぞれは Rule トレイトから来ています:

  1. 各個のASTノードに対して実行(run による)
  2. 各シンボルに対して実行(run_on_symbol による)
  3. ファイル全体に対して1回だけ実行(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!(
    /// ### 何をしますか
    /// `debugger` 文の使用を検査します
    ///
    /// ### なぜ問題なのでしょうか?
    /// デバッグツールが接続されていないとき、`debugger` 文は機能に影響を与えません。通常は、誤って残されたデバッグコードです。
    ///
    /// ### 例
    ///
    /// このルールに対して**不正な**コードの例:
    /// ```js
    /// async function main() {
    ///     const data = await getData();
    ///     const result = complexCalculation(data);
    ///     debugger;
    /// }
    /// ```
    NoDebugger,
    correctness
);

impl Rule for NoDebugger {
    // AST内の各ノードに対して実行
    fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
        // `debugger` 文には独自のAST種別があります
        if let AstKind::DebuggerStatement(stmt) = node.kind() {
            // 偽りを報告
            ctx.diagnostic(no_debugger_diagnostic(stmt.span));
        }
    }
}

TIP

Semantic に格納されているデータについてよく理解しておく必要があるでしょう。これは、意味解析中に抽出されたすべてのデータが格納される場所です。また、AST構造についてもよく理解しておくべきです。ここでの2つの最も重要なデータ構造は AstNodeAstKind です。

ステップ5: テスト

変更を行ったときにルールをテストするには、以下のコマンドを実行します:

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

一度だけテストするには、以下のコマンドを実行します:

bash
cargo test -p oxc_linter -- rule-name
# または
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 が使えます。

プルリクエストを提出する準備ができたら、just ready または just r を実行してローカルでCIチェックを実行してください。just fix を実行すると、ラベル、フォーマット、タイポなどの問題を自動的に修正できます。just ready が通ったら、プルリクエストを作成し、メンテナーが変更内容をレビューします。

一般的なアドバイス

エラーメッセージを最小限のコード範囲にピンポイントする

ユーザーが問題のあるコードに注目するようにするため、エラーメッセージを解読してどの部分が誤っているか特定する必要がないようにしたいです。

let-else 文を使用する

if-let 文の深層ネストを繰り返すようになった場合、代わりに let-else を使用することを検討してください。

TIP

CodeAestheticのネストしないことの動画では、この概念について詳しく説明しています。

rust
// let-elseは読みやすい
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
// 深いネストは読みにくい
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()
};