リンタールールの追加
Oxlint へのコントリビューで、最も手堅く簡単なのは新しいリンタールールを追加することです。
ここでは ESLint の no-debugger ルールを例に、手順を説明します。
TIP
先に セットアップ手順 を読んでください。
ステップ 1: ルールを選ぶ
リンターのロードマップと進捗 の Issue で、既存の ESLint プラグインから実装したいルールの状況を追っています。興味のありそうなプラグインを開き、まだ実装されていないルールを探してください。
重要: ESLint 互換の JavaScript プラグインが利用できるようになったため、新しい Rust 製プラグインは追加する予定はありません。ただし、既存プラグインへのルール追加は積極的に歓迎します。Rust で書いた方がよいルールやプラグインがある場合は、Pull Request の前に Discussion を開いて相談してください。
多くの ESLint ルールドキュメントには、ソースコード へのリンクがあります。実装の参考にしてください。
ステップ 2: ルール生成
次に rulegen スクリプトを実行し、ボイラープレートを生成します。
just new-rule no-debuggerこれにより次が行われます。
crates/oxc_linter/src/rules/<plugin-name>/<rule-name>.rsに、ESLint から移植したテストを含む実装のたたき台が作成されるrules.rsの適切なmodにルールが登録されるoxc_macros::declare_all_lint_rules!にルールが追加される
別プラグインのルールなら、そのプラグイン用の rulegen コマンドを使います。
TIP
引数なしで just を叩くと、利用できるコマンド一覧が表示されます。
just new-rule [name] # eslint core
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 / 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生成されるファイルのイメージは次のとおりです。
クリックして展開
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
///
/// このルールで**適合しない**コード例:
/// ```js
/// FIXME: Tests will fail if examples are missing or syntactically incorrect.
/// ```
///
/// このルールで**適合する**コード例:
/// ```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.rs/ja/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();
}ルールのたたき台はこれで動かせます。cargo test -p oxc_linter を試してください。まだ実装していないのでテストは失敗します。
ステップ 3: テンプレートを埋める
ドキュメント
各セクションを埋めます。
- ルールが何をするかを、短くはっきり書く
- なぜ重要で、どんな望ましくない挙動を防ぐのかを書く
- 違反例と問題ない例を示す
このドキュメントはサイトの ルール一覧 を生成するのに使われるので、読みやすくて役に立つ内容にしてください。
設定項目のドキュメント
ルールに設定オプションがある場合は、自動生成ドキュメントの仕組みで説明を書きます。その一部は rulegen が既に用意します。
設定はルールの struct にフィールドとして足します。
pub struct RuleName {
option_name: bool,
another_option: String,
yet_another_option: Vec<CompactStr>,
}または、別の Config struct にまとめても構いません。
pub struct RuleName(Box<RuleNameConfig>);
pub struct RuleNameConfig {
option_name: bool,
}JsonSchema の導出と serde の属性が必要です。例:
use schemars::JsonSchema;
#[derive(Debug, Default, Clone, JsonSchema)]
#[serde(rename_all = "camelCase", default)]
pub struct RuleName {
option_name: bool,
}各フィールドに /// で説明を付けます。
use schemars::JsonSchema;
#[derive(Debug, Default, Clone, JsonSchema)]
#[serde(rename_all = "camelCase", default)]
pub struct RuleName {
/// Whether to check for foo and bar when evaluating baz.
/// The comment can be as long as you need to fully describe the option.
option_name: bool,
}既定値や型は struct 定義から自動抽出されるため、ドキュメントコメント本文には書かないでください。
様々なルールでの設定ドキュメントの書き方は、この Issue に多数あります。
生成結果は次で確認できます。target/rule-docs/<plugin-name>/<rule-name>.md を開いてください。
cargo run -p website -- linter-rules --rule-docs target/rule-docs --git-ref $(git rev-parse HEAD)ルールカテゴリ
まず ルールカテゴリ のうち、ルールに最も合うものを選びます。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: ルールの実装
ESLint のソースを読み、挙動を把握してください。Oxlint は ESLint に似ていますが、そのまま移植できることは稀です。
ESLint のルールは create が返すオブジェクトで、AST ノードごとのハンドラを登録します。Oxlint は Rule トレイトが提供する、次のような粒度で動きます。
- 各 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.
///
/// ### Examples
///
/// このルールで**適合しない**コード例:
/// ```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 "cargo test -p oxc_linter -- rule-name"一回だけなら:
cargo test -p oxc_linter -- rule-name
# または
cargo insta test -p oxc_linter -- rule-nameOxlint はスナップショットテストに cargo insta を使います。スナップショットが変わった・新規だと cargo test は失敗します。テスト出力に diff を出したくない場合は cargo insta test -p oxc_linter。cargo insta review で確認するか、cargo insta accept で一括採用できます。
PR 前にローカルで CI に近いチェックをするなら just ready(短縮 just r)。Lint やフォーマットは just fix も利用できます。just ready が通れば PR を出し、メンテナがレビューします。
一般的なアドバイス
エラーのスパンは最短に
ユーザーがメッセージを読み解いて場所を特定するより、問題のコードそのものに目が向くようにします。
let-else を使う
if let のネストが深くなるなら let-else を検討してください。
TIP
CodeAesthetic の ネストを減らす動画 が参考になります。
// 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;
};
// ...
}// 深いネストは読みづらい
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 は毎回ヒープ確保がありますが、短い文字列はインラインで格納できる CompactStr を使うと(64-bit では最大 24 バイトまで)、余計な確保を避けられます。大きすぎる場合は自動でヒープに回ります。String や &str を期待する多くの場面で置き換え可能で、メモリと CPU の両方を節約できます。
struct Element {
name: CompactStr
}
let element = Element {
name: "div".into()
};struct Element {
name: String
}
let element = Element {
name: "div".to_string()
};