Skip to content

Escrevendo plugins JS

INFO

Plugins JS estão em alfa.

A API deve ser idêntica à do ESLint. Comportamento diferente é bug — reporte.

API compatível com ESLint

Documentação ESLint sobre criar plugin e regras customizadas.

Plugin mínimo que reclama quando há mais de 5 declarações de classe:

js
// plugin.js
const rule = {
  create(context) {
    let classCount = 0;

    return {
      ClassDeclaration(node) {
        classCount++;
        if (classCount === 6) {
          context.report({ message: "Too many classes", node });
        }
      },
    };
  },
};

const plugin = {
  meta: {
    name: "best-plugin-ever",
  },
  rules: {
    "max-classes": rule,
  },
};

export default plugin;
json
{
  "jsPlugins": ["./plugin.js"],
  "rules": {
    "best-plugin-ever/max-classes": "error"
  }
}
ts
import { defineConfig } from "oxlint";

export default defineConfig({
  jsPlugins: ["./plugin.js"],
  rules: {
    "best-plugin-ever/max-classes": "error",
  },
});

API alternativa

O Oxlint oferece variante pensada para desempenho; regras continuam compatíveis com ESLint (explicação abaixo).

Mesma regra com a API alternativa:

js
import { eslintCompatPlugin } from "@oxlint/plugins";

const rule = {
  createOnce(context) {
    let classCount;

    return {
      before() {
        classCount = 0;
      },
      ClassDeclaration(node) {
        classCount++;
        if (classCount === 6) {
          context.report({ message: "Too many classes", node });
        }
      },
    };
  },
};

const plugin = eslintCompatPlugin({
  meta: {
    name: "best-plugin-ever",
  },
  rules: {
    "max-classes": rule,
  },
});

export default plugin;

Diferenças:

  1. Envolva o objeto do plugin em eslintCompatPlugin(...).
diff
- const plugin = {
+ const plugin = eslintCompatPlugin({
  1. Use createOnce em vez de create.
diff
-   create(context) {
+   createOnce(context) {
  1. No ESLint, create roda por arquivo; aqui createOnce roda uma vez. Setup por arquivo vai no gancho before.
diff
-     let classCount = 0;
+     let classCount;

       return {
+       before() {
+         classCount = 0;
+       },
         ClassDeclaration(node) {

O que eslintCompatPlugin faz?

Acrescenta create delegando para createOnce.

Plugin serve para Oxlint e ESLint.

  • No Oxlint, ganho com createOnce.
  • No ESLint, igual a usar só create.

Publicando no npm: @oxlint/plugins como dependência de runtime (não só dev).

Pular travessia do AST

Retornar false em before faz a regra pular aquele arquivo.

js
// Regra não roda em arquivos que começam com // @skip-me
const rule = {
  createOnce(context) {
    return {
      before() {
        if (context.sourceCode.text.startsWith("// @skip-me")) {
          return false;
        }
      },
      FunctionDeclaration(node) {
        // ...
      },
    };
  },
};

Equivalente no ESLint:

js
const rule = {
  create(context) {
    if (context.sourceCode.text.startsWith("// @skip-me")) {
      return {};
    }

    return {
      FunctionDeclaration(node) {
        // ...
      },
    };
  },
};

Gancho before

Roda antes de visitar o AST.

Importante: hoje não está garantido que before rode em todo arquivo. No futuro, o lado Rust poderá omitir chamadas quando a regra não “interessa” aos nós presentes — before também seria omitido em alguns casos.

Se não houver FunctionDeclaration, a regra (e inclusive before) pode ser inteiramente pulada.

Para código que precisa rodar sempre por arquivo, use visitante Program:

js
const rule = {
  createOnce(context) {
    return {
      Program(node) {
        // Sempre executa uma vez por arquivo
      },
      FunctionDeclaration(node) {
        /* ... */
      },
    };
  },
};

Gancho after

Uma vez por arquivo, depois de percorrer o AST inteiro (Program:exit).

Para liberar recursos caros da regra.

Se before retornar false, after também não roda.

Como before, after pode não rodar em todo arquivo (veja acima).

Por que a API alternativa é mais rápida?

Hoje na prática ainda não é — mas será.

Há espaço grande de otimização Rust↔JS; a API alternativa existe para quando createOnce permitir não percorrer o AST em JS (só nós pré-filtrados) e até pular a chamada para JS quando o arquivo não tiver nós relevantes.

Sem isso, create obriga chamar sempre para saber qual visitante usar.

Detalhes técnicos (regra das 5 classes): com create por arquivo não dá inferir estaticamente quais nós interessam. Com createOnce, dá planejar essas otimizações — sem culpar o ESLint; é limitação aparece com interop Rust.

Próximo passo

Seção Suporte à API: quais APIs ESLint já funcionam no Oxlint.