린터 규칙 추가
Oxlint에 기여하는 가장 쉽고 좋은 방법은 새 린터 규칙을 추가하는 것입니다.
이 가이드는 ESLint의 no-debugger 규칙을 예로 전 과정을 안내합니다.
TIP
먼저 환경 설정을 읽었는지 확인하세요.
1단계: 규칙 고르기
Linter 제품 계획·진행 이슈에서 기존 ESLint 플러그인에서 구현할 규칙 목록을 추적합니다. 관심 가는 플러그인을 고른 뒤 아직 구현되지 않은 규칙을 찾습니다.
중요: ESLint 호환 JavaScript 플러그인이 가능해졌으므로 새 Rust 기반 플러그인은 추가할 계획이 없습니다. 다만 기존 플러그인에 규칙을 더하는 기여는 적극 환영합니다. Rust로 쓰면 좋은 규칙·플러그인이 있다면 PR 전에 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 코어 규칙
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생성된 파일 형태는 대략 다음과 같습니다.
펼치기
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/ko/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단계: 템플릿 채우기
문서
문서 섹션을 채웁니다.
- 규칙이 무엇을 하는지 명확하고 간단히 요약
- 왜 중요한지, 어떤 바람직하지 않은 동작을 막는지
- 위반 예·준수 예 코드
이 문서는 이 웹사이트 규칙 문서 페이지를 생성하는 데 쓰이므로 읽기 쉽고 유용하게 써 주세요.
설정 문서
규칙에 설정 옵션이 있으면 문서화해야 합니다. 문서 자동 생성 체계를 쓰며, 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를 derive하고 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 {
/// baz 평가 시 foo와 bar를 검사할지 여부
/// 필요한 만큼 길게 적어도 됩니다.
option_name: bool,
}기본값과 타입은 struct 정의에서 자동으로 추출되므로 /// 주석에 또 적지 않습니다.
다양한 규칙에서 설정을 문서화하는 예는 이 이슈에 많이 있습니다.
생성된 문서는 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! 매크로 안에서 지정합니다.
수정기(Fixer) 상태
수정기가 있으면 declare_oxc_lint! 안에서 어떤 종류의 수정을 제공하는지 등록합니다. 수정기 구현이 어려우면 pending 자리표시자를 쓸 수 있습니다. 나중에 다른 기여자가 수정기를 찾기 쉬워집니다.
Diagnostics
위반에 대한 진단을 만드는 함수를 작성합니다. 원칙:
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가 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!(
/// ### 하는 일
/// `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 kind
if let AstKind::DebuggerStatement(stmt) = node.kind() {
// 위반 보고
ctx.diagnostic(no_debugger_diagnostic(stmt.span));
}
}
}TIP
의미 분석 중 추출한 데이터가 모두 들어 있는 Semantic 에 익숙해지는 것이 좋습니다. AST 구조도 익히세요. 특히 중요한 자료구조는 AstNode 와 AstKind 입니다.
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을 낼 준비가 되면 로컬에서 just ready 또는 just r로 CI와 같은 검사를 돌립니다. just fix로 린트·포맷·오타 자동 수정도 할 수 있습니다. just ready가 통과하면 PR을 열면 메인테이너가 리뷰합니다.
일반 조언
오류 메시지는 가능한 한 짧은 코드 구간에 맞추기
사용자가 메시지를 해독해 어느 부분이 잘못됐는지 찾기보다, 문제 코드에 바로 집중하게 합니다.
let-else 사용
if-let이 깊게 중첩된다면 let-else를 고려하세요.
TIP
CodeAesthetic의 never-nesting 영상에서 개념을 더 자세히 설명합니다.
// 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은 힙 할당이 필요해 메모리와 CPU를 씁니다. 작은 문자열 인라인(64비트에서 최대 24바이트)으로 스택에 두는 CompactStr을 쓰면 할당이 필요 없을 수 있습니다. 문자열이 너무 크면 그때 필요한 만큼 할당합니다. 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()
};