Noções Básicas de Linting
10 exemplos para você começar com Linting & Formatting - 7 básicos e 3 intermediários.
Busque em todas as páginas da documentação
10 exemplos para você começar com Linting & Formatting - 7 básicos e 3 intermediários.
Comece com um projeto Expo SDK 57. O template default@sdk-57 já inclui TypeScript e os scripts recomendados - você só precisa adicionar o ESLint uma vez.
npx create-expo-app@latest MyApp --template default@sdk-57
cd MyApp
npx expo lintnpx expo lint instala eslint, eslint-config-expo e escreve eslint.config.js na raiz do projeto. Confirme o pin do SDK antes de personalizar as regras:
{
"dependencies": {
"expo": "~57.0.4",
"react": "19.2.3",
"react-native": "0.86.0"
},
"devDependencies": {
"eslint": "^9.0.0",
"eslint-config-expo": "~57.0.0"
}
}Ferramentas: Estes exemplos têm como alvo Expo SDK 57 (
expo~57.0.4), React Native 0.86.0 e React 19.2.3. Instale a extensão ESLint VS Code para que as violações apareçam enquanto você edita.
npx expo lintA Expo CLI é o caminho suportado para uma configuração plana funcional - ela fixa versões compatíveis de eslint e eslint-config-expo para o seu SDK.
# Primeira execução: instala devDependencies e cria eslint.config.js
npx expo lint
# Execuções subsequentes: faz o lint de todo o projeto
npx expo lint
# Corrige violações seguras automaticamente
npx expo lint --fixnpx expo lint lê o script lint de package.json e encaminha flags para o ESLinteslint.config.js e no lockfile juntas.eslintrc.* restantes antes de adotar a configuração plana - o ESLint 9 prefere a configuração plana, mas volta para o legado quando ambos existemRelacionado: ../project-setup/project-setup-basics.md - Checklist de verificação do pin do SDK e da primeira execução
eslint-config-expoProjetos SDK 53+ usam ESLint flat config. O arquivo gerado estende o preset do Expo - regras de React, hooks, TypeScript e importações já estão configuradas.
// eslint.config.js
const { defineConfig } = require("eslint/config");
const expoConfig = require("eslint-config-expo/flat");
module.exports = defineConfig([
expoConfig,
{
ignores: ["dist/*"],
},
]);eslint-config-expo/flat exporta um array defineConfig - espalhe ou referencie-o como o primeiro bloco para que os padrões do Expo carreguem antes das suas substituições__DEV__, fetch, APIs amigáveis ao Hermes) para que os arquivos de tela façam lint sem comentários /* global */@typescript-eslint se aplicam a arquivos .ts e .tsx quando o TypeScript está presente no projetoexpoConfig para que você substitua em vez de substituir o presetRelacionado: Prettier & Ordenação de Importações - Camada Prettier sem conflitar com as regras de formatação do ESLint
npm run lintIntegre o linting ao package.json para que colegas de equipe, hooks de pré-commit e CI invoquem o mesmo comando.
{
"scripts": {
"start": "expo start",
"lint": "expo lint",
"lint:fix": "expo lint --fix",
"typecheck": "tsc --noEmit"
}
}npm run lint
npm run lint:fix"lint": "expo lint" é o que npx expo lint executa internamente - prefira o script npm na documentação e no YAML da CI para que o ponto de entrada permaneça óbviolint:fix para limpeza local; evite auto-fix na CI, a menos que seu pipeline seja idempotente e rápidotypecheck com lint - ESLint captura padrões que o TypeScript ignora (deps de hooks, ordem de importação) e vice-versaRelacionado: Portões de Qualidade da CI - Falha nas builds com lint e typecheck juntos
Comece com um pequeno bloco de regras após o preset do Expo. Esses dois ajustes cobrem a maioria das novas equipes Expo sem transformar o lint em um projeto de reescrita.
// eslint.config.js
const { defineConfig } = require("eslint/config");
const expoConfig = require("eslint-config-expo/flat");
module.exports = defineConfig([
expoConfig,
{
rules: {
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
"react/react-in-jsx-scope": "off",
},
},
{
ignores: ["dist/*", ".expo/*"],
},
]);// app/index.tsx - `_event` não é usado intencionalmente; o lint passa
import { Pressable, Text } from "react-native";
export default function Home() {
return (
<Pressable onPress={(_event) => console.warn("pressed")}>
<Text>Tap me</Text>
</Pressable>
);
}argsIgnorePattern: "^_" é a saída de escape padrão para manipuladores de eventos que você deve aceitar, mas não lerreact/react-in-jsx-scope: off está correto para o transform JSX do React 17+ - o preset Babel do Expo já o habilita"warn" temporariamente ao implementar uma regra em uma grande base de código existenteRelacionado: Modo Estrito do TypeScript - Alinhe a rigorosidade do compilador com as regras de lint ao longo do tempo
Caminhos gerados e fornecidos nunca devem bloquear um merge. Em configuração plana, coloque ignores em um objeto dedicado com ignores como a única chave.
// eslint.config.js
const { defineConfig, globalIgnores } = require("eslint/config");
const expoConfig = require("eslint-config-expo/flat");
module.exports = defineConfig([
globalIgnores([
"node_modules/**",
".expo/**",
"dist/**",
"web-build/**",
"ios/**",
"android/**",
]),
expoConfig,
{
rules: {
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
},
},
]);globalIgnores (ou um objeto { ignores: [...] } independente) pula arquivos inteiramente - execuções mais rápidas e menos falsos positivos.expo/ - estado do servidor de desenvolvimento local, não código fonteios/ e android/ no fluxo gerenciado até que você confirme intencionalmente projetos nativos (CNG / bare)ignores com rules no mesmo objeto cria um filtro com escopo de arquivo, não um skip global - mantenha os ignores isoladosRelacionado: Código Morto e Análise de Dependências - Encontre arquivos não utilizados que o lint nunca toca
--fixMire em uma pasta de recursos enquanto itera para que o feedback seja rápido em aplicativos grandes.
# Faz o lint apenas da árvore do roteador do app
npx expo lint app
# Faz o lint de um pacote compartilhado em um monorepo
npx expo lint packages/ui/src
# Corrige problemas corrigíveis automaticamente em uma tela
npx expo lint app/(tabs)/index.tsx --fix// app/(tabs)/index.tsx - dependência de hook ausente sinalizada antes de você abrir um PR
import { useEffect, useState } from "react";
import { Text, View } from "react-native";
export default function TabHome() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => setCount((c) => c + 1), 1000);
return () => clearInterval(id);
}, []); // eslint pode avisar: react-hooks/exhaustive-deps
return (
<View>
<Text>Count: {count}</Text>
</View>
);
}app/ corresponde a como as equipes do Expo Router organizam recursos - faça o lint da pasta que você está alterando ativamente--fix reescreve a ordem de importação e problemas de estilo simples; ele não criará arrays de dependência de hook para vocêreact-hooks/exhaustive-deps frequentemente expõem closures obsoletas em timers e assinaturas - leia a mensagem antes de silenciá-lanpm run lint de projeto completo continua sendo o portão de merge, mesmo quando você faz lint localmente por caminhoRelacionado: Regras de Lint de React Hooks - Padrões
exhaustive-depsem código móvel assíncrono
As telas do aplicativo rodam no Hermes ou no navegador; metro.config.js e app.config.ts rodam no Node. O preset do Expo já relaxa os globais do Node para nomes de arquivos de configuração comuns - estenda a lista quando adicionar arquivos de ferramentas personalizadas.
// eslint.config.js
const { defineConfig } = require("eslint/config");
const expoConfig = require("eslint-config-expo/flat");
const globals = require("globals");
module.exports = defineConfig([
expoConfig,
{
files: ["metro.config.js", "app.config.ts", "babel.config.js"],
languageOptions: {
globals: globals.node,
},
},
{
ignores: ["dist/*"],
},
]);// metro.config.js - __dirname é válido sob globais do Node
const { getDefaultConfig } = require("expo/metro-config");
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
module.exports = config;__dirname, require e module como indefinidos em arquivos de configuraçãoeslint-config-expo já trata vários caminhos de configuração como casos especiais - adicione blocos files explícitos apenas para nomes personalizados (tailwind.config.js, scripts/**)/* eslint-env node */ como último recurso em um único arquivo legado - prefira languageOptions centralizado em configuração plana.js ou .ts na raiz do repositório para que o glob files permaneça simplesRelacionado: ../project-setup/project-setup-basics.md - Layout de
metro.config.jseapp.config.ts
Após a passagem da linha de base, adicione regras focadas em mobile: proíba texto bruto fora de <Text>, desaconselhe console.log em código de produção e exija testID em pressables cobertos em E2E.
// eslint.config.js
const { defineConfig } = require("eslint/config");
const expoConfig = require("eslint-config-expo/flat");
module.exports = defineConfig([
expoConfig,
{
rules: {
"no-console": ["warn", { allow: ["warn", "error"] }],
"react-native/no-raw-text": [
"error",
{ skip: ["Trans", "Animated.Text"] },
],
},
},
{
ignores: ["dist/*", ".expo/*"],
},
]);// Violação: string bruta como filho de View - envolva com Text
import { View, Text, Pressable } from "react-native";
export function SettingsRow({ label }: { label: string }) {
return (
<Pressable accessibilityRole="button" testID="settings-row">
<View>
<Text>{label}</Text>
</View>
</Pressable>
);
}react-native/no-raw-text captura uma classe comum de falhas no Android - strings devem viver dentro de <Text>no-console como warn mantém logs de depuração visíveis na revisão sem bloquear merges no primeiro diaeslint-plugin-react-native quando habilitar regras específicas do RN: npx expo install eslint-plugin-react-native --devRelacionado: Regras ESLint Personalizadas para RN - Proíba
console.log, props de a11y e limites de importação
Em um layout Turborepo ou npm-workspaces, aponte o ESLint para as raízes de origem de cada aplicativo e compartilhe uma configuração base de packages/eslint-config.
{
"name": "mobile",
"private": true,
"scripts": {
"lint": "expo lint app src",
"lint:all": "turbo run lint"
}
}// apps/mobile/eslint.config.js
const { defineConfig } = require("eslint/config");
const expoConfig = require("eslint-config-expo/flat");
const baseRules = require("@myorg/eslint-config/expo");
module.exports = defineConfig([
expoConfig,
baseRules,
{
ignores: ["dist/*", ".expo/*", "ios/**", "android/**"],
},
]);expo lint quando o código do aplicativo estiver fora de app/ (por exemplo, src/features/)packages/eslint-config para que aplicativos mobile e web herdem as mesmas regras de variáveis não utilizadas e importaçõeslint:all da raiz do repositório na CI para que uma alteração de hook compartilhada em packages/ui falhe antes de publicarignores - pastas nativas e saída de build diferem por alvoRelacionado: ../project-setup/monorepo-with-turborepo.md - Layout de workspace e grafo de tarefas | Portões de Qualidade da CI - Builds de matriz por aplicativo
Trate lint e TypeScript como portões complementares antes do eas build ou de uma submissão para a loja - eles capturam classes de defeitos diferentes.
{
"scripts": {
"lint": "expo lint",
"typecheck": "tsc --noEmit",
"validate": "npm run typecheck && npm run lint"
}
}npm run validate// TypeScript passa; ESLint sinaliza o hook - validate pega ambos
import { useCallback, useState } from "react";
import { Button, View } from "react-native";
export function Counter() {
const [n, setN] = useState(0);
const increment = useCallback(() => setN(n + 1), []); // risco de closure obsoleto
return (
<View>
<Button title={`${n}`} onPress={increment} />
</View>
);
}tsc --noEmit valida tipos sem emitir JS - rápido o suficiente para cada PRexpo lint executa o plugin de hooks - closures obsoletos como useCallback(..., []) lendo n são descobertas de lint, não erros de tipovalidate ou check e chame-o da CI, hooks de pré-push e rituais locais de pré-lançamentovalidate - nunca pule o portão porque o simulador iniciouRelacionado: Modo Estrito do TypeScript - Aperte
strictincrementalmente | Melhores Práticas de Linting - Política de zero avisos sem diminuir a velocidade
Versões da Stack: Esta página foi escrita para React 19.2.3, React Native 0.86.0 e Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026