Adicionar regras ao linter
A forma mais simples de contribuir com o Oxlint costuma ser adicionando novas regras.
Este guia percorre o processo usando a regra no-debugger do ESLint como exemplo.
TIP
Leia antes as instruções de configuração.
Passo 1: Escolher uma regra
Nossa issue do plano e progresso do produto Linter acompanha o status de todas as regras que queremos trazer de plugins ESLint existentes. Escolha um plugin que te interesse e uma regra ainda não implementada.
Importante: como já há suporte a plugins JavaScript compatíveis com ESLint, não planejamos adicionar novos plugins escritos em Rust. Mesmo assim, contribuições que acrescentem regras a plugins já existentes são muito bem-vindas. Se achar que uma regra ou plugin ganharia em ser Rust nativo, abra uma discussion antes de abrir o PR.
Na maioria das páginas de documentação das regras ESLint há link para o código-fonte da regra. Use isso como referência na implementação.
Passo 2: Geração da regra
Em seguida, rode o script rulegen para gerar o esqueleto da nova regra.
just new-rule no-debuggerIsso irá:
- Criar um arquivo em
crates/oxc_linter/src/rules/<plugin-name>/<rule-name>.rscom o começo da implementação e casos de teste portados do ESLint - Registrar a regra no
modcorreto emrules.rs - Incluir a regra em
oxc_macros::declare_all_lint_rules!
Para regras de outro plugin, use o script rulegen daquele plugin.
TIP
Rode just sem argumentos para ver todos os comandos disponíveis.
just new-rule [name] # for eslint core rules
just new-jest-rule [name] # for eslint-plugin-jest
just new-ts-rule [name] # for @typescript-eslint/eslint-plugin
just new-unicorn-rule [name] # for eslint-plugin-unicorn
just new-import-rule [name] # for eslint-plugin-import
just new-react-rule [name] # for eslint-plugin-react and eslint-plugin-react-hooks
just new-jsx-a11y-rule [name] # for eslint-plugin-jsx-a11y
just new-oxc-rule [name] # for oxc's own rules
just new-nextjs-rule [name] # for eslint-plugin-next
just new-jsdoc-rule [name] # for eslint-plugin-jsdoc
just new-react-perf-rule [name] # for eslint-plugin-react-perf
just new-n-rule [name] # for eslint-plugin-n
just new-promise-rule [name] # for eslint-plugin-promise
just new-vitest-rule [name] # for eslint-plugin-vitestO arquivo gerado será algo neste estilo:
Clique para expandir
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.rs/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();
}Sua regra já deve rodar! Teste com cargo test -p oxc_linter. Os testes devem falhar, porque a regra ainda não foi implementada.
Passo 3: Preencher o template
Documentação
Preencha as seções de documentação.
- Resumo claro do que a regra faz.
- Por que importa e que comportamento indesejável evita.
- Exemplos de código que violam e de código correto.
Usamos essa documentação para gerar as páginas de regras deste site — capricha para ficar clara e útil!
Documentação de configuração
Se a regra tiver opções de configuração, documente-as pelo sistema de geração automática de docs. O rulegen costuma gerar parte disso.
Cada opção deve aparecer como campo na struct da regra:
pub struct RuleName {
option_name: bool,
another_option: String,
yet_another_option: Vec<CompactStr>,
}Alternativamente, use uma struct Config separada:
pub struct RuleName(Box<RuleNameConfig>);
pub struct RuleNameConfig {
option_name: bool,
}As opções precisam ter JsonSchema derivado e anotação serde, por exemplo:
use schemars::JsonSchema;
#[derive(Debug, Default, Clone, JsonSchema)]
#[serde(rename_all = "camelCase", default)]
pub struct RuleName {
option_name: bool,
}Documente cada campo com comentários ///, por exemplo:
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,
}Valor padrão e tipo são extraídos automaticamente da struct e não devem ser repetidos nos comentários.
Veja esta issue com dezenas de exemplos de como documentar opções em vários tipos de regra.
Para ver a documentação gerada: cargo run -p website -- linter-rules --rule-docs target/rule-docs --git-ref $(git rev-parse HEAD) e abra target/rule-docs/<plugin-name>/<rule-name>.md.
Categoria da regra
Escolha uma categoria de regra que combine com a regra. Lembre-se de que regras correctness rodam por padrão — escolha com cuidado. Defina a categoria na macro declare_oxc_lint!.
Status do fixer
Se houver fixer, registre em declare_oxc_lint! que tipo de correção oferece. Se não quiser implementar o fixer agora, pode usar pending como marcador — ajuda outros contribuidores a acharem fixers faltantes depois.
Diagnósticos
Crie uma função que produza diagnósticos para violações. Princípios:
- A
messagedeve ser uma frase no imperativo sobre o que está errado, não uma descrição da regra. - A mensagem
helpdeve soar como instrução direta de como corrigir.
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)Passo 4: Implementação da regra
Leia o código-fonte da regra no ESLint para entender o comportamento. Embora o Oxlint se pareça com o ESLint, raramente dá para portar a regra direto.
Regras ESLint têm uma função create que devolve um objeto: chaves são tipos de nó AST que disparam a regra e valores são funções que rodam o lint. Regras do Oxlint disparam de poucos pontos definidos pelo trait Rule:
- Em cada nó da AST (
run) - Em cada símbolo (
run_on_symbol) - Uma vez por arquivo (
run_once)
No caso de no-debugger, queremos nós DebuggerStatement, então usamos run. Versão simplificada da regra:
Clique para expandir
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));
}
}
}TIP
Vale estudar os dados em Semantic, onde fica tudo que a análise semântica extrai. Também familiarize-se com a estrutura da AST. As duas estruturas mais importantes são AstNode e AstKind
Passo 5: Testes
Para testar a cada mudança:
just watch "cargo test -p oxc_linter -- rule-name"Ou só uma vez:
cargo test -p oxc_linter -- rule-name
# Or
cargo insta test -p oxc_linter -- rule-nameO Oxlint usa cargo insta para snapshot testing. cargo test falha se snapshots mudarem ou forem criados. Você pode rodar cargo insta test -p oxc_linter para não ver diffs nos resultados. Revise com cargo insta review, ou aceite tudo com cargo insta accept.
Antes de abrir o PR, rode just ready ou just r para checagens locais de CI. just fix corrige automaticamente lint, formatação e typos. Com just ready verde, abra o PR para revisão.
Conselhos gerais
Mensagem de erro no menor span possível
Queremos que o usuário olhe para o código problemático, não que decifre o texto do erro para achar o trecho certo.
Use let-else
Se estiver aninhando muito if-let, considere let-else.
TIP
O vídeo never-nesting do CodeAesthetic explica melhor.
// 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;
};
// ...
}// 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() {
// ...
}
}
}
}Use CompactStr quando der
Menos alocações é crítico para desempenho no oxc. String exige heap; dá para guardar strings curtas inline (até 24 bytes em sistemas 64-bit) com CompactStr, sem alocar. Se passar desse tamanho, aloca o necessário. CompactStr substitui bem String ou &str na maioria dos casos e economiza memória e CPU frente a String.
struct Element {
name: CompactStr
}
let element = Element {
name: "div".into()
};struct Element {
name: String
}
let element = Element {
name: "div".to_string()
};