이 글은 ESLint 9.x(Flat Config)
를 기준으로 작성했습니다.
기존 .eslintrc.*가 아니라
eslint.config.js/cjs 파일을 사용합니다.


🧭 도입: 내가 ESLint를 배워야겠다고 생각한 이유

우아한테크코스 프리코스 8기 1주차
과제 유의사항을 살펴보는데

자바스크립트 코드 컨벤션을 지키면서 프로그래밍 한다.
- 기본적으로 JavaScript Style Guide를 원칙으로 한다.

요런 문구가 있어서 JavaScript Style Guide 를 공부하기 시작했습니다.

유의사항이 꽤 많기도 했고 (#1~#39)
자바스크립트 초심자이다 보니
숙지해도 계속 잊어버리더라구요 ㅜ.ㅜ

그래서 제가 개발을 할 때
자바스크립트 코드 컨벤션을 준수하면서
코딩을 할 수 있도록 도와주는 도구가 있는지
찾아보니 바로 ESLint 였습니다.





⚙️ ESLint란?

ESLint는 자바스크립트 코드의 문법, 스타일, 잠재적 오류를 자동으로 검사하고 수정해주는 정적 분석 도구다.

간단히 말하면,
"내가 짠 코드를 대신 검사해주는 자동 리뷰어"
라고 할 수 있다.

✅ ESLint의 핵심 구성요소

  • Parser : 코드를 구조적으로 분석(AST)
  • Rules : 어떤 규칙을 위반했는지 정의 (ex. 세미콜론 필수)
  • Plugins / Config : 규칙 모음과 확장 세트
  • CLI / VSCode 연동 : 실행 환경





⚡ v8 vs v9 차이비교

ESLint v9부터 설정 방식이 바뀌어 기존 .eslintrc.* 대신 Flat Config(eslint.config.js/cjs) 를 사용한다.

구분ESLint v8 이하ESLint v9 이상
설정 파일 이름.eslintrc.jseslint.config.js / eslint.config.cjs
구조객체(Object) 하나배열(Array) 구조
환경 설정env 속성 사용languageOptions.globals 사용
플러그인 로딩extends로 불러오기모듈에서 직접 import/require

Flat Config는 명시적이고 확장성 있는 구성이라 최신 프로젝트에 적합하다.





💻 설치 및 실행 (ESLint 9.x 기준)

1) 패키지 설치

npm install -D eslint eslint-plugin-import globals

2) 설정 파일 생성

프로젝트 루트에 eslint.config.cjs 파일 생성하고
아래 코드 복붙하기

// javascript-style-guide 반영
// eslint.config.cjs  (ESLint v9+ flat config)
// 필요한 패키지: eslint, eslint-plugin-import, globals
// $ npm i -D eslint eslint-plugin-import globals

const pluginImport = require('eslint-plugin-import');
const globals = require('globals');

module.exports = [
  // 무시할 경로(옵션)
  {
    ignores: ['node_modules', 'dist', 'build', 'coverage'],
  },

  // JS 파일들에 적용
  {
    files: ['**/*.js', '**/*.mjs', '**/*.cjs'],
    languageOptions: {
      ecmaVersion: 'latest',
      sourceType: 'module',
      globals: {
        ...globals.browser,
        ...globals.node,
        ...globals.jest,
      },
    },
    plugins: {
      import: pluginImport,
    },
    rules: {
      /* === 공백/들여쓰기/포맷 === */
      indent: ['error', 2, { SwitchCase: 1 }],
      'space-before-blocks': ['error', 'always'],
      'keyword-spacing': ['error', { before: true, after: true }],
      'space-in-parens': ['error', 'never'],
      'array-bracket-spacing': ['error', 'never'],
      'object-curly-spacing': ['error', 'always'],
      'comma-spacing': ['error', { before: false, after: true }],
      'eol-last': ['error', 'always'],
      'no-multiple-empty-lines': ['error', { max: 1 }],
      'no-trailing-spaces': ['error'],
      'max-len': [
        'warn',
        {
          code: 100,
          ignoreComments: true,
          ignoreStrings: true,
          ignoreTemplateLiterals: true,
          ignoreUrls: true,
        },
      ],

      /* === 문자열/세미콜론 === */
      quotes: ['error', 'single', { avoidEscape: true }],
      semi: ['error', 'always'],
      'prefer-template': ['error'],
      'template-curly-spacing': ['error', 'never'],

      /* === 화살표 함수 === */
      'arrow-parens': ['error', 'always'],
      'arrow-spacing': ['error', { before: true, after: true }],
      'prefer-arrow-callback': ['error'],
      'arrow-body-style': ['error', 'as-needed'],
      'no-confusing-arrow': ['error'],

      /* === 변수/스코프 === */
      'no-var': ['error'],
      'prefer-const': ['error'],
      'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
      'no-undef': ['error'],
      'no-use-before-define': [
        'error',
        { functions: false, classes: true, variables: true },
      ],
      'no-shadow': ['error'],

      /* === 비교/제어문/블록 === */
      eqeqeq: ['error', 'always'],
      'no-else-return': ['error', { allowElseIf: false }],
      curly: ['error', 'all'],
      'default-case': ['error'],
      'consistent-return': ['error'],
      'guard-for-in': ['error'],
      'dot-notation': ['error'],

      /* === 함수 관련 === */
      'func-names': ['error', 'as-needed'],
      'no-empty-function': ['error'],
      'no-loop-func': ['error'],
      'no-param-reassign': ['error'],
      'no-return-assign': ['error', 'always'],
      'no-nested-ternary': ['error'],
      'no-unneeded-ternary': ['error'],
      'nonblock-statement-body-position': ['error', 'beside'],
      'prefer-rest-params': ['error'],

      /* === 객체/배열 === */
      'object-shorthand': ['error', 'always'],
      'quote-props': ['error', 'as-needed'],
      'array-callback-return': ['error'],
      'prefer-destructuring': [
        'error',
        {
          VariableDeclarator: { array: false, object: true },
          AssignmentExpression: { array: true, object: true },
        },
      ],
      'prefer-spread': ['error'],

      /* === 모듈(import/export) === */
      'import/first': ['error'],
      'import/newline-after-import': ['error', { count: 1 }],
      'import/no-duplicates': ['error'],
      'import/no-mutable-exports': ['error'],
      'import/extensions': [
        'error',
        'ignorePackages',
        { js: 'always', mjs: 'always', cjs: 'always' },
      ],
      'import/prefer-default-export': ['warn'],

      /* === 클래스/생성자 === */
      'constructor-super': ['error'],
      'no-this-before-super': ['error'],
      'no-useless-constructor': ['error'],
      'no-dupe-class-members': ['error'],

      /* === 이터레이터/제너레이터 지양 === */
      'no-restricted-syntax': [
        'error',
        {
          selector: 'ForOfStatement',
          message: '배열 반복엔 map/filter/reduce 등 메서드를 사용하세요.',
        },
        {
          selector: 'ForInStatement',
          message: '객체 반복엔 Object.keys/values/entries를 사용하세요.',
        },
      ],

      /* === 형변환/수치 === */
      radix: ['error', 'always'],
      'prefer-exponentiation-operator': ['error'],
      'use-isnan': ['error'],
      'no-new-wrappers': ['error'],
      'no-multi-assign': ['error'],
      yoda: ['error', 'never'],
      'no-bitwise': ['error'],

      /* === 기타 === */
      'no-console': ['warn', { allow: ['warn', 'error'] }],
      'no-debugger': ['error'],
      'no-alert': ['error'],
      'no-new': ['error'],
      'no-continue': ['error'],
      'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
    },
  },
];

3) 실행

  • 현재 디렉터리 전체 검사
npx eslint .
  • 특정 파일만 검사하려면:
npx eslint src/index.js

4) 자동 수정

npx eslint . --fix

5) npm 스크립트 추가

package.json에 스크립트를 넣어두면 매번 명령어를 기억하지 않아도 된다.

{
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix"
  }
}

이제 아래처럼 실행 가능하다.

# 검사
npm run lint 
# 자동수정
npm run lint:fix 





⚙️ VSCode에서 저장(ctrl + S) 시 자동 실행

  1. VSCode 확장 프로그램 → ESLint (by Microsoft) 설치
  2. 프로젝트 루트에 .vscode/settings.json 추가 👇
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "dbaeumer.vscode-eslint",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "eslint.validate": ["javascript", "javascriptreact"],
  "eslint.format.enable": true
}

💡 이제 저장만 해도 ESLint가 자동으로 실행되어 코드가 깔끔하게 정리된다!

🧠 작동 흐름

[Ctrl + S] (파일 저장)
     ↓
VSCode → ESLint 확장 플러그인 트리거
     ↓
프로젝트 루트에서 eslint 탐색
     ↓
1️⃣ Flat Config (eslint.config.cjs/js)
    또는
2️⃣ 전통적 .eslintrc.*
     ↓
ESLint Core 엔진이 AST 분석 수행
     ↓
rules, plugins, globals 등 적용
     ↓
자동 수정 가능한 부분 (--fix) 즉시 수정
     ↓
VSCode에 수정 반영





🔍 자주 쓰는 규칙 예시

규칙설명예시
eqeqeq== 대신 === 사용if (a === b)
no-varvar 금지, let/const 사용const x = 1
quotes작은따옴표 사용'hello'
semi세미콜론 필수console.log('hi');
no-console콘솔 사용 금지(경고)console.warn() 가능

eslint.config.cjsrules에서 "error", "warn", "off" 로 조정 가능





💭 내 생각

처음 ESLint를 사용해보면서
개발자들은 역시 천재들이구나라고 생각했습니다.
저장만 해도 코드가 자동으로 교정되다니...

아직은 배우는 입장이라서
자동교정기능 (npx eslint . --fix) 보다는
코드점검기능 (npx eslint .) 을 위주로
ESLint한테 혼나가며 코딩을 해야겠다고 생각을 했습니다.

그래도 언제나 제곁에서(?!)
매의눈으로 제 코드를 지켜봐주는
선생님이 생긴 것 같아
마음이 든든해졌습니다. ^^





📚 마무리

한 줄 요약: ESLint는 내 코드를 검사하고 교정해주는 코치 선생님 ^^

profile
1.01^365

0개의 댓글