TypeScript ESLint — 타입 인식 린팅 완전 가이드
@typescript-eslint/parser와 eslint-plugin을 활용한 타입 인식 린팅, recommended vs strict 구성, no-floating-promises 등 핵심 규칙, tsconfig 연동 방법을 정리합니다.
지난 글에서 Prettier와 ESLint의 역할을 분리하는 방법을 다뤘습니다. 이번에는 TypeScript 코드를 위한 ESLint 설정, 즉 @typescript-eslint 패키지 생태계를 깊이 살펴봅니다. 일반 ESLint 규칙은 TypeScript 타입 정보를 모릅니다. 예를 들어 Promise를 반환하는 함수를 await 없이 호출해도 기본 규칙은 침묵합니다. TypeScript ESLint는 TypeScript 컴파일러의 **타입 체커(Type Checker)**를 ESLint 규칙 안으로 끌어들여 이 공백을 메웁니다.
아키텍처 개요
TypeScript ESLint는 크게 두 패키지로 나뉩니다.
@typescript-eslint/parser— ESLint가.ts/.tsx파일을 파싱할 수 있도록 TypeScript 컴파일러 API를 이용해 TSESTree 형식의 AST를 생성합니다. 선택적으로 TypeScriptProgram객체(타입 정보 포함)도 넘겨줍니다.@typescript-eslint/eslint-plugin— TSESTree + 타입 정보를 활용하는 규칙 100개 이상을 제공합니다. 타입 정보를 요구하는 규칙은parserOptions.project가 설정되어야 동작합니다.
typescript-eslint 패키지는 위 두 패키지를 묶어서 더 편리한 API(tseslint.config())와 사전 구성된 config 객체를 제공합니다.
설치
# 최신 통합 패키지 (권장)
npm install -D typescript-eslint
# 또는 개별 패키지
npm install -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
TypeScript 자체와 ESLint 9+ 가 이미 설치되어 있어야 합니다.
npm install -D eslint typescript
기본 설정 (Flat Config)
// eslint.config.mjs
import tseslint from 'typescript-eslint'
export default tseslint.config(
// 타입 정보 없이 동작하는 기본 규칙
...tseslint.configs.recommended,
)
타입 인식 규칙까지 활성화하려면 recommendedTypeChecked를 쓰고 parserOptions.project를 설정합니다.
// eslint.config.mjs
import tseslint from 'typescript-eslint'
export default tseslint.config(
...tseslint.configs.recommendedTypeChecked,
{
languageOptions: {
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
)
project: true는 각 파일에서 가장 가까운 tsconfig.json을 자동으로 찾습니다.
설정 단계와 강도
| 프리셋 | 타입 정보 필요 | 설명 |
|---|---|---|
recommended | ❌ | 기본 품질 규칙, CI 첫 도입에 적합 |
recommendedTypeChecked | ✅ | 타입 인식 규칙 포함, 대부분의 프로젝트 권장 |
strict | ✅ | recommended + 더 엄격한 규칙 |
strictTypeChecked | ✅ | 가장 엄격한 세트 |
stylistic | ❌ | 코드 스타일(일관성) 규칙 |
stylisticTypeChecked | ✅ | 타입 인식 스타일 규칙 |
주요 타입 인식 규칙
@typescript-eslint/no-floating-promises
Promise를 반환하는 함수 호출에서 await 또는 .catch()가 없으면 오류를 냅니다. 런타임에서 조용히 실패하는 비동기 코드의 가장 흔한 버그를 잡아냅니다.
// 오류: fetchData()가 Promise를 반환하지만 await 없음
fetchData()
// 정상
await fetchData()
fetchData().catch(console.error)
// 결과를 쓰지 않는다는 명시적 표시
void fetchData()
@typescript-eslint/no-unsafe-assignment
any 타입의 값을 다른 변수에 할당할 때 경고합니다. any가 코드베이스 전체로 전파되는 것을 막습니다.
const data: any = getExternalData()
const name = data.name // 오류: any 할당
const name2: string = data.name // 오류: 여전히 any
// 해결: 런타임 검증 후 타입 단언
if (typeof data.name === 'string') {
const name3 = data.name // string으로 좁혀짐
}
@typescript-eslint/consistent-type-imports
타입 전용 import에 import type을 강제합니다. 번들러 tree-shaking과 isolatedModules 모드에서 중요합니다.
// 오류
import { User, createUser } from './user'
// 정상
import type { User } from './user'
import { createUser } from './user'
// 또는 인라인 type
import { type User, createUser } from './user'
@typescript-eslint/await-thenable
await 뒤에 Promise가 아닌 값이 오면 경고합니다.
const value = 42
await value // 오류: number는 Thenable이 아님
JS 파일 처리
TypeScript ESLint 규칙 중 일부는 .js 파일에서도 사용할 수 있습니다. allowJs: true가 tsconfig.json에 있으면 JS 파일도 타입 체킹 대상이 됩니다.
// eslint.config.mjs
export default tseslint.config(
{
files: ['**/*.ts', '**/*.tsx'],
extends: [...tseslint.configs.recommendedTypeChecked],
languageOptions: {
parserOptions: { project: true, tsconfigRootDir: import.meta.dirname },
},
},
{
// JS 파일은 타입 정보 없는 세트만 적용
files: ['**/*.js', '**/*.mjs'],
extends: [...tseslint.configs.recommended],
},
)
성능: parserOptions.project의 비용
타입 인식 규칙은 TypeScript 컴파일러를 내부적으로 실행하므로 린팅 속도가 크게 느려집니다. 대형 모노레포에서는 project를 세분화하거나 projectService 옵션을 사용하는 것이 좋습니다.
// 성능 최적화: projectService 사용 (v6+)
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
}
projectService는 파일별로 필요한 tsconfig만 로드해 메모리와 시간을 줄입니다.
단계적 마이그레이션 전략
기존 JS 프로젝트를 TS로 마이그레이션하는 경우, 처음부터 strict를 적용하면 수백 개의 오류가 발생합니다. 단계적으로 올리는 것이 현실적입니다.
// 1단계: warn으로 시작
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unsafe-assignment': 'warn',
}
// 2단계: 오류 수 줄인 후 error로 전환
rules: {
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unsafe-assignment': 'error',
}
React + TypeScript 조합
// eslint.config.mjs
import tseslint from 'typescript-eslint'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
export default tseslint.config(
...tseslint.configs.recommendedTypeChecked,
{
plugins: { react, 'react-hooks': reactHooks },
rules: {
...react.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
},
settings: { react: { version: 'detect' } },
languageOptions: {
parserOptions: { project: true, tsconfigRootDir: import.meta.dirname },
},
},
)
지난 글: Prettier와 ESLint 역할 분리 — 포맷터와 린터 공존
다음 글: Husky + lint-staged — 커밋 훅으로 품질 게이트 구축
읽어주셔서 감사합니다. 😊