Introdução ao ESLint com TypeScript
ESLint é uma ferramenta de análise estática de código que identifica padrões problemáticos em JavaScript e TypeScript. Quando você trabalha com TypeScript, o ESLint sozinho não compreende características específicas da linguagem como tipos, interfaces e decoradores. É neste ponto que entra o typescript-eslint, um projeto que integra o TypeScript compiler ao ESLint, permitindo que regras sejam criadas e executadas com total consciência dos tipos.
O typescript-eslint não é apenas um parser melhorado. Ele fornece um ambiente completo onde o ESLint pode acessar a árvore de sintaxe abstrata (AST) gerada pelo TypeScript, incluindo informações semânticas que o JavaScript puro não possui. Isso permite criar regras muito mais sofisticadas, capazes de detectar erros que um simples linter tradicional jamais encontraria.
Configuração Inicial do typescript-eslint
Instalação e Setup Básico
Para começar, você precisa instalar o typescript-eslint e suas dependências. O processo é direto e segue um padrão bem definido:
npm install --save-dev eslint typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser
Após a instalação, crie ou atualize seu arquivo .eslintrc.json na raiz do projeto:
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module",
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
],
"rules": {}
}
A opção project é fundamental. Ela aponta para seu tsconfig.json, permitindo que o ESLint acesse informações de tipo do TypeScript. Sem isso, muitas regras avançadas não funcionarão adequadamente.
Configurando o tsconfig.json
Seu tsconfig.json deve estar corretamente configurado para que o typescript-eslint extraia as informações necessárias:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
A flag strict deve estar ativa para aproveitar ao máximo as capacidades do TypeScript e do typescript-eslint. Isso garante que você esteja escrevendo código type-safe desde o início.
Regras Avançadas do typescript-eslint
Entendendo Regras com Consciência de Tipos
As regras mais poderosas do typescript-eslint são aquelas que utilizam informações de tipo para tomar decisões. Diferentemente de regras sintáticas simples, essas regras conseguem entender o significado do código. Vamos explorar algumas das mais úteis:
@typescript-eslint/no-unsafe-argument — Previne passar argumentos com tipo any para funções:
// ❌ ERRADO
function processUser(id: number): void {
const data: any = fetchData();
saveToDatabase(data); // ESLint alerta aqui
}
// ✅ CORRETO
interface User {
id: number;
name: string;
}
function processUser(id: number): void {
const data: User = fetchData();
saveToDatabase(data); // Seguro, tipo conhecido
}
@typescript-eslint/no-floating-promises — Detecta Promises que não são aguardadas:
// ❌ ERRADO
function loadData(): void {
fetchUserData(); // Promise criada mas nunca aguardada
}
async function loadData(): Promise<void> {
await fetchUserData(); // ✅ CORRETO
}
// Ou explicitamente ignora a Promise
function loadData(): void {
void fetchUserData(); // ✅ CORRETO - Intent explícito
}
@typescript-eslint/explicit-function-return-types — Força declaração explícita do tipo de retorno:
// ❌ ERRADO
function calculateTotal(items: number[]) {
return items.reduce((sum, item) => sum + item, 0);
}
// ✅ CORRETO
function calculateTotal(items: number[]): number {
return items.reduce((sum, item) => sum + item, 0);
}
Regras de Null Safety
O TypeScript foi feito para prevenir erros de null e undefined. As regras abaixo reforçam essa segurança:
@typescript-eslint/strict-boolean-expressions — Garante que expressões booleanas sejam realmente booleanas:
// ❌ ERRADO
const user: { name?: string } = {};
if (user.name) { // Pode ser undefined, o que é truthy?
console.log(user.name);
}
// ✅ CORRETO
if (user.name !== undefined && user.name !== "") {
console.log(user.name);
}
// Ou com optional chaining
if (user.name?.length) {
console.log(user.name);
}
@typescript-eslint/prefer-nullish-coalescing — Prefere o operador nullish coalescing (??) ao invés de ||:
// ❌ ERRADO - Falha com valores falsy legítimos
const port = process.env.PORT || 3000; // Se PORT for "0", usa 3000
// ✅ CORRETO - Só usa padrão se null ou undefined
const port = process.env.PORT ?? 3000; // "0" é mantido
Regras de Naming e Convenção
Convenções consistentes melhoram a legibilidade. O typescript-eslint fornece regras poderosas para isso:
// Configuração de regra
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "variable",
"format": ["camelCase"],
"leadingUnderscore": "allow"
},
{
"selector": "typeLike",
"format": ["PascalCase"]
}
]
// ❌ ERRADO
const user_name = "John"; // snake_case em variável
type user = { name: string }; // lowercase em tipo
// ✅ CORRETO
const userName = "John";
type User = { name: string };
Configurações Práticas e Otimizações
Criando um Arquivo .eslintrc Profissional
A configuração abaixo representa um setup profissional, balanceado entre segurança e praticidade:
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2021,
"sourceType": "module",
"project": ["./tsconfig.json"],
"tsconfigRootDir": "."
},
"env": {
"es2021": true,
"node": true
},
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
],
"rules": {
"@typescript-eslint/explicit-function-return-types": [
"warn",
{
"allowExpressions": true,
"allowTypedFunctionExpressions": true
}
],
"@typescript-eslint/no-unsafe-argument": "error",
"@typescript-eslint/no-unsafe-assignment": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_"
}
],
"@typescript-eslint/strict-boolean-expressions": "warn",
"@typescript-eslint/prefer-nullish-coalescing": "warn",
"@typescript-eslint/prefer-optional-chain": "warn",
"no-console": "warn"
},
"overrides": [
{
"files": ["*.spec.ts", "*.test.ts"],
"rules": {
"@typescript-eslint/explicit-function-return-types": "off"
}
}
]
}
Integrando no Package.json e Workflow
Adicione scripts para facilitar a execução do ESLint:
{
"scripts": {
"lint": "eslint src --ext .ts,.tsx",
"lint:fix": "eslint src --ext .ts,.tsx --fix",
"type-check": "tsc --noEmit",
"validate": "npm run type-check && npm run lint"
}
}
O script validate executa tanto o type-check do TypeScript quanto o lint, garantindo qualidade máxima antes de fazer commit.
Performance: Lidando com Grandes Projetos
Em projetos grandes, o typescript-eslint pode ficar lento porque recompila o projeto a cada execução. Existem estratégias para otimizar:
# Use cache para evitar reprocessar arquivos inalterados
eslint src --ext .ts,.tsx --cache --cache-location .eslintcache
Se ainda assim ficar lento, considere não usar project para todas as regras. Você pode criar uma configuração separada para CI/CD que roda com type-checking, e outra local mais rápida:
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
]
}
Esta configuração mais leve roda sem project, sendo muito mais rápida para feedback durante desenvolvimento.
Conclusão
Dominar o typescript-eslint transforma sua abordagem ao desenvolvimento TypeScript. Três pontos-chave que consolidam esse aprendizado:
-
Tipagem e Segurança — O typescript-eslint não é apenas um linter, é um protetor que usa informações de tipo para prevenir classes inteiras de bugs em tempo de desenvolvimento, não em produção.
-
Configuração Inteligente — Uma configuração bem pensada, com overrides para testes e balanceamento entre strictness e produtividade, é fundamental. Não copie configurações cegamente; entenda cada regra que você ativa.
-
Integração no Workflow — ESLint é mais valioso quando integrado ao editor, pré-commit hooks e CI/CD. Um desenvolvedor que vê erros em tempo real é um desenvolvedor mais produtivo.