Configurando ESLint com typescript-eslint

1. Por que usar ESLint com TypeScript?

O TypeScript Compiler (tsc) é excelente para detectar erros de tipo, mas possui limitações significativas na identificação de más práticas de código, padrões problemáticos e violações de estilo. Enquanto tsc foca em segurança de tipos, o ESLint complementa essa análise com linting de código, detectando problemas como variáveis não utilizadas, expressões booleanas complexas e práticas inconsistentes.

A combinação typescript-eslint oferece o melhor dos dois mundos: utiliza o parser TypeScript para entender a estrutura de tipos do seu código e aplica regras de linting que consideram essas informações. Isso permite, por exemplo, detectar variáveis declaradas mas nunca usadas considerando tipos, ou sugerir substituições seguras de @ts-ignore por @ts-expect-error.

A diferença fundamental é:
- Erros de tipo (TypeScript): tsc verifica se você está passando argumentos corretos, acessando propriedades existentes, etc.
- Erros de estilo/lógica (ESLint): ESLint verifica se você está seguindo convenções, evitando padrões problemáticos, mantendo consistência no código.

2. Instalação e dependências necessárias

Para começar, instale as dependências principais:

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

Certifique-se de usar versões compatíveis. Para projetos com TypeScript 5.x, recomenda-se:

npm install --save-dev eslint@^8.56.0 @typescript-eslint/parser@^7.0.0 @typescript-eslint/eslint-plugin@^7.0.0

Se você já possui outras dependências de linting, como eslint-plugin-react ou eslint-plugin-import, verifique a compatibilidade consultando a documentação oficial de cada plugin.

3. Configuração básica do eslint.config.js

A partir do ESLint v9, a configuração flat config (eslint.config.js) é o padrão. Abaixo está um exemplo de configuração mínima para TypeScript:

// eslint.config.js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

export default tseslint.config(
  eslint.configs.recommended,
  ...tseslint.configs.recommended,
  {
    languageOptions: {
      parserOptions: {
        project: ['./tsconfig.json'],
        tsconfigRootDir: import.meta.dirname,
      },
    },
    rules: {
      '@typescript-eslint/no-unused-vars': 'error',
      '@typescript-eslint/explicit-function-return-type': 'warn',
    },
  }
);

Se você ainda usa o formato .eslintrc (legado), a configuração equivalente seria:

{
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "project": "./tsconfig.json"
  },
  "plugins": ["@typescript-eslint"],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ]
}

4. Regras essenciais do typescript-eslint

Regras de tipo

  • @typescript-eslint/no-unnecessary-type-assertion: Detecta type assertions desnecessárias, quando o TypeScript já infere o tipo corretamente.
// Ruim
const nome: string = 'João' as string;

// Bom
const nome: string = 'João';
  • @typescript-eslint/prefer-ts-expect-error: Prefere @ts-expect-error a @ts-ignore, pois o primeiro gera erro se a linha subjacente não tiver erro de tipo.
// Ruim
// @ts-ignore
const x: number = 'string';

// Bom
// @ts-expect-error - Intencional: testando compatibilidade
const x: number = 'string';

Regras de boas práticas

  • @typescript-eslint/no-unused-vars: Versão melhorada da regra do ESLint, que entende variáveis TypeScript como tipos importados.
// Ruim
function soma(a: number, b: number) {
  return a;
}

// Bom
function soma(a: number, b: number): number {
  return a + b;
}
  • @typescript-eslint/explicit-function-return-type: Exige que funções tenham tipo de retorno explícito.
// Ruim
function getNome() {
  return 'Maria';
}

// Bom
function getNome(): string {
  return 'Maria';
}

Regras de consistência

  • @typescript-eslint/consistent-type-definitions: Prefere interfaces a type aliases (ou vice-versa, conforme configurado).
// Ruim (se configurado para preferir interfaces)
type Usuario = {
  nome: string;
};

// Bom
interface Usuario {
  nome: string;
}
  • @typescript-eslint/consistent-type-imports: Garante que imports de tipos usem import type.
// Ruim
import { Usuario } from './types';

// Bom
import type { Usuario } from './types';

5. Integração com outras configurações e plugins

Combinando com React

Para projetos React, instale o plugin específico:

npm install --save-dev eslint-plugin-react eslint-plugin-react-hooks

E configure:

// eslint.config.js
import reactPlugin from 'eslint-plugin-react';
import reactHooksPlugin from 'eslint-plugin-react-hooks';

export default tseslint.config(
  // ... outras configurações
  {
    plugins: {
      react: reactPlugin,
      'react-hooks': reactHooksPlugin,
    },
    rules: {
      'react/jsx-uses-react': 'error',
      'react/jsx-uses-vars': 'error',
      'react-hooks/rules-of-hooks': 'error',
      'react-hooks/exhaustive-deps': 'warn',
    },
  }
);

Combinando com import sorting

npm install --save-dev eslint-plugin-import
rules: {
  'import/order': ['error', {
    groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
    'newlines-between': 'always',
  }],
}

Evitando conflitos com Prettier

npm install --save-dev eslint-config-prettier

No formato flat config:

import prettierConfig from 'eslint-config-prettier';

export default tseslint.config(
  // ... outras configurações
  prettierConfig,
);

6. Configurando scripts e automação

Adicione ao package.json:

{
  "scripts": {
    "lint": "eslint src/",
    "lint:fix": "eslint src/ --fix",
    "lint:watch": "esw src/ --watch"
  }
}

Para integração com VS Code, instale a extensão ESLint e adicione ao settings.json:

{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "eslint.validate": ["typescript", "typescriptreact"]
}

Para CI/CD com GitHub Actions:

name: Lint
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run lint

7. Resolução de problemas comuns

Erro "Parsing error: Cannot read file 'tsconfig.json'"

Verifique se o tsconfig.json está no diretório raiz do projeto e se o parserOptions.project aponta para o caminho correto. Se usar monorepo, especifique o caminho relativo:

parserOptions: {
  project: ['./packages/*/tsconfig.json'],
}

Conflitos entre regras TypeScript e ESLint

Desative regras do ESLint que conflitam com as versões TypeScript:

rules: {
  'no-unused-vars': 'off',
  '@typescript-eslint/no-unused-vars': 'error',
  'no-use-before-define': 'off',
  '@typescript-eslint/no-use-before-define': 'error',
}

Ignorando arquivos específicos

No flat config, use a propriedade ignores:

export default tseslint.config(
  {
    ignores: ['dist/', 'node_modules/', '*.config.js'],
  },
  // ... outras configurações
);

Ou crie um arquivo .eslintignore (para configuração legada):

dist/
node_modules/
*.config.js

Com essa configuração, seu projeto TypeScript estará protegido contra más práticas comuns, mantendo consistência e qualidade de código. Lembre-se de revisar periodicamente as regras conforme seu projeto evolui, ajustando níveis de severidade e adicionando novas regras quando necessário.

Referências