Skip to content

Pré-visualização técnica de lint orientado por tipos no Oxlint

Este post anuncia a pré-visualização técnica de lint orientado por tipos. Para o lançamento alfa atual com maior estabilidade, configuração e cobertura de regras, veja o anúncio do alfa de lint orientado por tipos.


Temos o prazer de anunciar lint orientado por tipos no oxlint!

Chegaram, enfim, no-floating-promises e regras relacionadas.

Esta prévia serve para envolver a comunidade na discussão ao documentar decisões e detalhes técnicos.

Início rápido

Se o oxlint já estiver configurado, instale oxlint-tsgolint e use a flag --type-aware:

bash
pnpm add -D oxlint-tsgolint@latest
pnpm dlx oxlint --type-aware

Se o oxlint ainda não estiver configurado e você só quiser ver no-floating-promises:

bash
pnpm add -D oxlint-tsgolint@latest
pnpm dlx oxlint@latest --type-aware -A all -D typescript/no-floating-promises

Espera-se uma saída como:

js
 × typescript-eslint(no-floating-promises): Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator.
   ╭─[packages/rolldown/src/api/watch/watcher.ts:30:7]
29await this.close();
30originClose();
   ·       ──────────────
31 │     };
   ╰────

Veja o guia de uso para mais opções de configuração.

Desempenho

Em nossos testes, repositórios que antes levavam cerca de um minuto com typescript-eslint agora terminam em menos de 10 segundos.

Isso usa typescript-go, o TypeScript cerca de 10× mais rápido em Go.

Projetos do oxc-ecosystem-ci:

ProjetoArquivosTempo
napi-rs1441,0 s
preact2452,7 s
rolldown3141,5 s
bluesky11527,0 s

Lint orientado por tipos

Leia Rust-Based JavaScript Linters: Fast, But No Typed Linting Right Now para entender o estado atual do lint tipado na ecologia de ferramentas.

Detalhes técnicos

O núcleo da nova funcionalidade é oxc-project/tsgolint.

O tsgolint começou como protótipo em typescript-eslint/tsgolint. A equipe do typescript-eslint optou por não investir naquele protótipo e continuar o lint tipado no ESLInt via typescript-eslint.

@boshen falou com @auvred sobre um fork mais enxuto para o oxlint — apenas regras orientadas a tipos, sem resolução completa de config de linter.

@auvred continuou o desenvolvimento na organização Oxc.

Arquitetura

oxlint (Rust) e tsgolint (Go) compilam em binários separados.

oxlint faz o “frontend”: CLI, percorrimento de caminhos, ignores e impressão dos diagnósticos.

tsgolint é o backend: recebe caminhos e configuração e devolve diagnósticos estruturados.

Pipeline simples:

CLI do oxlint (passa caminhos + regras + configuração)
  -> tsgolint (devolve diagnósticos)
  -> CLI do oxlint (imprime diagnósticos)

tsgolint

O tsgolint não fala com o typescript-go por APIs públicas.

Em vez disso compila o typescript-go com um shim que expõe APIs internas.

Todas as regras tipadas são escritas diretamente contra esses shims.

Não é o caminho recomendado para internals — mas funciona!

Como chegamos à decisão

Escrever nosso próprio type checker

Tentativas anteriores (abandonadas) incluem:

Há ainda Biome 2.0 em andamento com inferência própria.

Achamos inviável manter inferidor ou type checker próprio dado o ritmo de mudança do TypeScript.

Integração com o compilador TypeScript

Antes do typescript-go, projetos plugavam na API pública do TS mapeando AST para estree ou percorrendo o AST TS. Exemplos:

Também exploramos IPC com o oxlint, mas abandonamos.

Com typescript-go, o time TypeScript tende a codificar o AST TS e decodificar no lado JS por IPC.

Funciona, mas ainda há:

  • problemas de desempenho incompatíveis com o perfil do oxlint;
  • custo de manter mapeamento a partir do AST do TypeScript.

Pontos de atenção

O tsgolint resolve a performance, mas outros desafios permanecem.

Outra versão do TypeScript

Pretendemos publicar snapshots do typescript-go alinhados às versões do TypeScript para você instalar oxlint-typescript com a versão correta.

Contras: pode ser preciso atualizar o TypeScript quando o oxlint-tsgolint exigir mudanças.

Custo de manter o tsgolint

Shim de APIs internas tem risco. Porém AST e visitors do TS são relativamente estáveis. Aceitamos o risco e corrigimos quebras ao atualizar o typescript-go.

Nosso fork sincroniza diariamente.

Problemas de desempenho

Em monorepos enormes com centenas de projetos ou muitas project references o tsgolint ainda não escala bem.

Pode travar (deadlock) ou causar OOM em caso de bug.

Estamos atacando isso com perfil e melhorias enviadas ao typescript-go, beneficiando todos.

@camc314 já abriu vários PRs que aceleraram trechos relevantes.

Release v1.0 do tsgolint

Para v1.0 do tsgolint planejamos:

  • desempenho em monorepos grandes;
  • configuração fina por regra;
  • correção de cada regra;
  • suporte a IDE;
  • estabilidade geral.

Agradecimentos

  • À equipe TypeScript pelo typescript-go.
  • À equipe typescript-eslint pelo apoio.
  • A @auvred por criar o tsgolint.
  • A @camchenry pela integração oxlint + tsgolint.
  • A @camc314 pelo trabalho de performance.

Comunidade

Queremos feedback sobre oxlint e lint orientado por tipos.

Próximos passos

Instale o oxlint:

bash
pnpm add -D oxlint@latest oxlint-tsgolint@latest
pnpm dlx oxlint --init # gera .oxlintrc.json

Ou siga o guia de instalação.

Use a flag --type-aware:

bash
pnpm dlx oxlint --type-aware

Experimente regras tipadas em .oxlintrc.json:

json
{
  "$schema": "./node_modules/oxlint/configuration_schema.json",
  "rules": {
    "typescript/await-thenable": "error",
    "typescript/no-array-delete": "error",
    "typescript/no-base-to-string": "error",
    "typescript/no-confusing-void-expression": "error",
    "typescript/no-duplicate-type-constituents": "error",
    "typescript/no-floating-promises": "error",
    "typescript/no-for-in-array": "error",
    "typescript/no-implied-eval": "error",
    "typescript/no-meaningless-void-operator": "error",
    "typescript/no-misused-promises": "error",
    "typescript/no-misused-spread": "error",
    "typescript/no-mixed-enums": "error",
    "typescript/no-redundant-type-constituents": "error",
    "typescript/no-unnecessary-boolean-literal-compare": "error",
    "typescript/no-unnecessary-template-expression": "error",
    "typescript/no-unnecessary-type-arguments": "error",
    "typescript/no-unnecessary-type-assertion": "error",
    "typescript/no-unsafe-argument": "error",
    "typescript/no-unsafe-assignment": "error",
    "typescript/no-unsafe-call": "error",
    "typescript/no-unsafe-enum-comparison": "error",
    "typescript/no-unsafe-member-access": "error",
    "typescript/no-unsafe-return": "error",
    "typescript/no-unsafe-type-assertion": "error",
    "typescript/no-unsafe-unary-minus": "error",
    "typescript/non-nullable-type-assertion-style": "error",
    "typescript/only-throw-error": "error",
    "typescript/prefer-promise-reject-errors": "error",
    "typescript/prefer-reduce-type-parameter": "error",
    "typescript/prefer-return-this-type": "error",
    "typescript/promise-function-async": "error",
    "typescript/related-getter-setter-pairs": "error",
    "typescript/require-array-sort-compare": "error",
    "typescript/require-await": "error",
    "typescript/restrict-plus-operands": "error",
    "typescript/restrict-template-expressions": "error",
    "typescript/return-await": "error",
    "typescript/switch-exhaustiveness-check": "error",
    "typescript/unbound-method": "error",
    "typescript/use-unknown-in-catch-callback-variable": "error"
  }
}