ESLint 완벽 가이드 | JavaScript Linter·Rules
이 글의 핵심
ESLint로 코드 품질을 관리하는 완벽 가이드. Espree·AST, 규칙 엔진, Autofix, 플러그인 내부, 프로덕션 패턴까지. Rules, Prettier, TypeScript, React 실전 예제.
솔직히 말하면 ESLint는 처음엔 “빨간 줄”만 늘어나는 스트레스 도구로 보이기 쉬워요. 그런데 팀이 커질수록 규칙이 없는 코드베이스가 훨씬 더 무섭다는 걸 겪으면 느낌이 바뀝니다. 이 글은 격식 있는 교과서 톤은 잠깐 내려놓고, 실제로 쓰면서 겪는 이야기랑 꼭 하고 싶은 주장(특히 Prettier) 위주로 정리했어요.
“ESLint 규칙 싸움”이 시작된 날
옛날 팀에선 any 뿌리는 사람, == 쓰는 사람, console.log는 커밋에 남기는 사람이 한 화면에 섞여 있었어요. 누군가 strict 룰 셋을 PR에 올리자마자 CI가 빨갛게 달궈지고, 슬랙엔 “이거 왜 갑자기 에러?” 스레드가 꼬리를 물었죠. 한동안은 eslint-disable을 남발하는 사람, 아예 로컬에서만 끄는 사람도 나왔고요. 그때 생각한 건 “규칙이 나쁜 게 아니라, 갑자기 이기려고 해서”라는 쪽이 컸어요. 결국 해결책은 (1) 한 번에 다 조이지 말고 경고로 시작하고, (2) lint:fix로 자동인 것은 자동에 맡기고, (3) 포맷은 ESLint한테 떠넘기지 말자—였습니다. Prettier 쪽이랑은 아래에서 또 말할게요.
ESLint가 하는 일, 한 방에
그냥 자바스크립트(또는 TS) 코드를 읽고, “우리가 합의한 잘못”에 표를 찍는 도구예요. 파서(기본은 Espree)가 소스 → AST로 만들고, 룰들이 그 트리를 돌면서 context.report로 불평(진단)을 냅니다. --fix가 되는 룰은 fixer로 텍스트 조각을 고쳐 주고, 안 되는 건 손으로 고치거나 리팩터링해야 하죠. 여기서 중요한 건 ESLint는 의미까지 완벽히 증명하는 컴파일러가 아니라 정적 힌트에 가깝다는 점—그래서 “이건 왜 틀렸지?”를 팀 룰북이랑 같이 쓰는 게 편해요.
먼저 띄우기
npm install -D eslint
npx eslint --init
.eslintrc.json 뼈대는 대충 이런 느낌이면 됩니다. extends에 eslint:recommended 넣는 건 거의 기본이고, extends랑 plugins 값은 전부 JSON 문자열로 따옴표 붙이는 것 잊지 마세요.
{
"env": { "browser": true, "es2021": true, "node": true },
"extends": ["eslint:recommended"],
"parserOptions": { "ecmaVersion": "latest", "sourceType": "module" },
"rules": {
"no-console": "warn",
"no-unused-vars": "error",
"prefer-const": "error"
}
}
package.json엔 eslint src / eslint src --fix 정도만 있어도 일상 루프는 돌아가요. 룰은 "off" | "warn" | "error" 숫자(0,1,2)도 됩니다—팀이 “이건 에러까지는 아냐”로 모을 땐 warn이 정신 건강에 좋아요.
TypeScript, React 쓰면
TS는 parser랑 plugin을 끼우고, React는 eslint-plugin-react랑 hooks 룰 정도—이제는 문서 봐가며 플러그인 이름 맞추는 것이 반 이상이에요. 타입 룰을 빡세게(예: no-floating-promises) 가져가려면 parserOptions.project로 타입스크립트 프로젝트를 묶는 type-aware 루트로 가는 경우가 있는데, 그건 느려질 수 있으니 “전부 파일에” 말고 overrides로 나누거나, CI 전용 스크립트로 쪼개는 팀도 많아요. 지연 뜨면 TIMING=1 붙여서 어느 룰에 시간 쓰는지 먼저 보는 게 괜찮고요.
Prettier랑 같이 쓰세요 (진심)
여기 제 의견이에요. 포맷(줄 바꿈, 세미콜론, 따옴표)은 Prettier에 맡기고, ESLint는 “버그·스멜·팀이 정한 제약”에 쓰는 쪽이 장기적으로 덜 피곤해요. 그래서 eslint-config-prettier로 ESLint 쪽 포맷 겹치는 룰을 끄는 것은 거의 필수에 가깝고, extends 맨 마지막에 prettier를 넣는 거 잊지 마세요. 반대로 eslint-plugin-prettier로 “Prettier를 룰 하나로 돌리기”는 느리고 에러 메시지가 헷갈리는 경우가 있어서, 개인적으론 prettier CLI랑 eslint를 따로 돌리는 조합을 선호해요(에디터에서 저장 시 둘 다 돌리면 됨).
npm install -D prettier eslint-config-prettier
extends 배열 끝에 prettier만 얹는 그림(플러그인 이름은 팀 셋업에 맞게):
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
]
}
.prettierrc는 팀 취향(세미콜론, 작은따옴표, printWidth) 정도만 잡고, 그다음은 싸우지 말기—그게 “규칙 싸움”을 끊는 가장 쉬운 방법이에요.
ignore, VS Code, pre-commit
node_modules/, dist/, coverage/ 같은 데는 .eslintignore로 빼세요. VS Code는 저장 시 source.fixAll.eslint 켜두면 덜 귀찮고, husky+lint-staged로 커밋 직전에 고친 파일만 eslint --fix랑 prettier --write 돌리는 패턴이 흔해요. 전체 린트는 CI에서, 로컬은 가볍게—이렇게 나누면 체감이 좋아요. CI에선 eslint . --max-warnings 0로 경고도 못 뚫게 할지, warn은 살짝 봐줄지 팀 정책 하나로 통일하세요.
규칙 엔진·AST·Autofix, 한 입만
규칙은 결국 create(context)가 돌려주는 visitor가 노드마다 돌고, fix는 문자열 범위를 패치로 고치는 거라 “AST 직접 rewrite”는 아니에요. Autofix가 일부만 먹는 건 충돌하거나, 아예 fix 없는 룰일 수 있으니, 그때 eslint-disable-next-line을 남기더라도 이유+티켓 정도는 같이 쓰는 쪽이 덜 미워요(나중에 grep 할 때 이득). 커스텀 룰·플러그인까지 가면 peerDependencies에 ESLint 버전 범위 꼭 맞추고, TS 쓰면 @typescript-eslint/utils 정도는 챙기는 게 정신 건강에 좋습니다.
마무리
ESLint는 팀이 “이건 싫다”를 코드로 박제하는 도구에 가깝고, Prettier는 “보기”를 통일해요. 둘을 붙이면 빨간 줄은 줄고, “스타일로 리뷰만 30분” 같은 일은 확 줄어요. 그래도 한 번씩 react-hooks/exhaustive-deps랑 useEffect 애가 낳은 의존성 배열 때문에 멘탈이 갈릴 땐… 그래, 그건 우리 쪽 인생이에요. 그때는 잠깐 끄지 말고(가능하면) 의존성을 맞추거나, 정말 예외면 주석 달고 넘기세요. 같이 읽을 만한 건 Prettier 쪽이랑 번들·런타임 Babel, Bun 글 정도? 키워드로는 ESLint, Espree, AST, Linter, TypeScript, Prettier, React 정도 쓰면 검색엔 뜰 거예요.