devops2025-02-17·8 min·194/348

ESLint 설정 커스터마이징: 프로젝트별 규칙 정의

ESLint 설정을 커스터마이징하여 프로젝트에 맞는 코드 품질 규칙을 정의하는 방법을 설명합니다.

ESLint 설정 커스터마이징

Introduction

ESLint는 JavaScript/TypeScript 코드의 품질을 유지하고 일관된 코드 스타일을 강제하는 강력한 린팅 도구입니다. 프로젝트의 요구사항에 맞게 규칙을 커스터마이징하면 개발 생산성과 코드 품질을 크게 향상시킬 수 있습니다. 이 글에서는 ESLint 설정 커스터마이징과 실용적인 규칙 정의 방법을 다루겠습니다.

Environment

// package.json
{
  "name": "my-project",
  "devDependencies": {
    "eslint": "^8.56.0",
    "@typescript-eslint/eslint-plugin": "^6.19.0",
    "@typescript-eslint/parser": "^6.19.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-import": "^2.29.1",
    "eslint-plugin-react": "^7.33.2",
    "eslint-plugin-react-hooks": "^4.6.0"
  }
}
npx eslint --version
# v8.56.0

Problem

기본 ESLint 설정의 한계:

// .eslintrc.js (기본 설정)
module.exports = {
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended'
  ]
};

// 문제점:
// 1. 프로젝트 맞춤 규칙 부족
// 2. import 정렬 미적용
// 3. 커스텀 네이밍 컨벤션 없음
// 4. 레거시 코드와 새 코드 구분 없음
# 기본 설정으로 검사 시
npx eslint src/

# 결과: 247 warnings, 12 errors
# 프로젝트에 맞지 않는 규칙들 포함

Analysis

ESLint 설정 구조를 분석했습니다:

// ESLint 설정 파일 우선순위
// 1. .eslintrc.js
// 2. .eslintrc.cjs
// 3. .eslintrc.yaml
// 4. .eslintrc.yml
// 5. .eslintrc.json
// 6. package.json의 "eslintConfig"

// flat config (ESLint 9+)
// eslint.config.js

// 규칙 충돌 해결
// extends 배열의 마지막 요소가 가장 높은 우선순위
// 프로젝트 구조 확인
{
  "src": {
    "components": [],
    "hooks": [],
    "utils": [],
    "types": []
  }
}

Solution

1단계: 기본 커스텀 설정

// .eslintrc.js
module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
    es2022: true
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'prettier'  // 반드시 마지막에 위치
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true
    }
  },
  settings: {
    react: {
      version: 'detect'
    }
  },
  rules: {
    // TypeScript 관련 규칙
    '@typescript-eslint/no-unused-vars': ['error', {
      argsIgnorePattern: '^_',
      varsIgnorePattern: '^_'
    }],
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/consistent-type-imports': 'error',
    
    // 일반 규칙
    'no-console': ['warn', { allow: ['warn', 'error'] }],
    'no-debugger': 'error',
    'no-duplicate-imports': 'error',
    'prefer-const': 'error',
    'no-var': 'error'
  },
  overrides: [
    {
      files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts'],
      rules: {
        '@typescript-eslint/no-explicit-any': 'off',
        'no-console': 'off'
      }
    }
  ]
};

2단계: Import 정렬 규칙

// .eslintrc.js (import 플러그인 추가)
module.exports = {
  // ... 기존 설정
  plugins: ['import'],
  rules: {
    // import 정렬 규칙
    'import/order': ['error', {
      groups: [
        'builtin',
        'external',
        'internal',
        ['parent', 'sibling'],
        'index',
        'type'
      ],
      'newlines-between': 'always',
      alphabetize: {
        order: 'asc',
        caseInsensitive: true
      }
    }],
    'import/no-duplicates': 'error',
    'import/no-unresolved': 'error',
    'import/no-cycle': 'error',
    'import/no-self-import': 'error'
  }
};

// 결과:
// import React from 'react';
//
// import { Button } from '@/components/Button';
//
// import { useAuth } from '@/hooks/useAuth';
//
// import type { User } from '@/types/user';

3단계: 커스텀 규칙 생성

// .eslintrc.js
module.exports = {
  rules: {
    // 네이밍 컨벤션 규칙
    '@typescript-eslint/naming-convention': [
      'error',
      {
        selector: 'default',
        format: ['camelCase']
      },
      {
        selector: 'variable',
        format: ['camelCase', 'UPPER_CASE']
      },
      {
        selector: 'parameter',
        format: ['camelCase'],
        leadingUnderscore: 'allow'
      },
      {
        selector: 'typeLike',
        format: ['PascalCase']
      },
      {
        selector: 'enumMember',
        format: ['PascalCase']
      },
      {
        selector: 'property',
        format: null  // 프로퍼티는 검사하지 않음
      }
    ],
    
    // 함수명 규칙
    'func-names': ['error', 'always'],
    
    // 컴포넌트 규칙 (React)
    'react/function-component-definition': ['error', {
      namedComponents: 'arrow-function',
      unnamedComponents: 'arrow-function'
    }]
  }
};

4단계: 프로젝트별 오버라이드

// .eslintrc.js
module.exports = {
  overrides: [
    // 테스트 파일
    {
      files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts'],
      env: {
        jest: true
      },
      rules: {
        '@typescript-eslint/no-explicit-any': 'off',
        '@typescript-eslint/explicit-function-return-type': 'off',
        'no-console': 'off'
      }
    },
    
    // 설정 파일
    {
      files: ['**/*.config.js', '**/*.config.ts'],
      env: {
        node: true
      },
      rules: {
        '@typescript-eslint/no-var-requires': 'off',
        'no-console': 'off'
      }
    },
    
    // 레거시 코드
    {
      files: ['src/legacy/**/*.js'],
      rules: {
        '@typescript-eslint/no-explicit-any': 'off',
        'no-undef': 'off',
        'prefer-const': 'warn'
      }
    },
    
    // 스크립트
    {
      files: ['scripts/**/*.js'],
      env: {
        node: true
      },
      rules: {
        'no-console': 'off'
      }
    }
  ]
};

Lessons Learned

  1. extends 순서: prettier는 반드시 마지막에 위치하여 포맷팅 규칙이 충돌하지 않도록 해야 합니다
  2. 오버라이드 활용: 파일 유형별로 다른 규칙을 적용하여 프로젝트 구조에 맞는 설정이 가능합니다
  3. 커스텀 규칙: 프로젝트 컨벤션에 맞는 커스텀 규칙을 생성하면 코드 일관성을 유지할 수 있습니다
  4. IDE 통합: ESLint를 IDE와 통합하면 실시간으로 코드 품질 문제를 발견할 수 있습니다
  5. 점진적 적용: 기존 코드베이스에서는 --fix 옵션을 활용하여 점진적으로 규칙을 적용해야 합니다

이 블로그는 외부 스폰서십, 제휴 마케팅 또는 광고 수익을 받지 않습니다.