Ray Book
프론트엔드 인프라

린터와 포매터

ESLint는 코드를 어떻게 분석하는가? Prettier와 왜 분리하는가? AST 기반 분석의 내부를 시각화합니다

infraeslintprettierbiomeastlinter

린터와 포매터의 차이

린터(linter) 와 포매터 (formatter) 는 모두 코드 품질 도구이지만, 관심사가 다릅니다.

린터 (ESLint)포매터 (Prettier)
관심사코드의 의미 , 잠재적 버그, 안티패턴코드의 외형 , 들여쓰기, 줄바꿈, 따옴표
예시미사용 변수, == 대신 ===, 도달 불가 코드탭 vs 스페이스, 세미콜론 여부, 줄 길이
자동 수정일부 규칙만 가능전체 코드 자동 수정
설정규칙별 세밀한 설정최소한의 설정 (의도적)

이전 글에서 AST가 코드 변환의 핵심이라고 했습니다. 린터도 같은 AST를 사용합니다, 코드를 변환하는 대신 검사 합니다.

ESLint의 동작 과정

ESLint는 네 단계로 동작합니다: 코드를 파싱하여 AST를 만들고, 규칙이 AST 노드를 방문하며 위반을 찾고, 결과를 보고합니다.

1 / 4Source Code
문제가 있는 소스 코드
1var x = 1;var 사용
2const y = 2;
3if (x == y) {== 사용
4 console.log( "equal" );불필요한 공백
5}
6const unused = 42;미사용 변수
개발자가 작성한 코드에는 잠재적 버그, 스타일 위반, 미사용 변수 등의 문제가 있을 수 있습니다. 린터는 이런 문제를 코드 실행 전에 정적으로 분석하여 찾아냅니다.

1단계: 파싱

ESLint는 파서를 사용하여 소스 코드를 AST로 변환합니다.

// ESLint 기본 파서: Espree (ESTree 호환)
// TypeScript 사용 시: @typescript-eslint/parser

// eslint.config.js (Flat Config)
import tsParser from '@typescript-eslint/parser';

export default [{
  languageOptions: {
    parser: tsParser,
  },
}];

기본 파서인 Espree는 표준 JavaScript만 파싱합니다. TypeScript를 사용한다면 @typescript-eslint/parser를, Vue SFC를 사용한다면 vue-eslint-parser를 지정합니다.

2단계: Visitor 패턴으로 규칙 적용

ESLint의 핵심은 visitor 패턴 입니다. 각 규칙은 관심 있는 AST 노드 타입을 구독하는 함수입니다.

// ESLint 규칙의 구조, no-var 규칙 (단순화)
module.exports = {
  meta: {
    type: "suggestion",
    fixable: "code",
  },
  create(context) {
    return {
      // "VariableDeclaration" 노드를 만날 때마다 실행
      VariableDeclaration(node) {
        if (node.kind === "var") {
          context.report({
            node,
            message: "Unexpected var, use let or const instead.",
            fix(fixer) {
              return fixer.replaceText(
                context.sourceCode.getFirstToken(node),
                "let"
              );
            },
          });
        }
      },
    };
  },
};

ESLint가 AST를 깊이 우선 (depth-first) 으로 순회하면서, 각 노드 타입에 등록된 규칙들이 실행됩니다. VariableDeclaration 노드를 만나면 no-var 규칙이 실행되고, BinaryExpression 노드를 만나면 eqeqeq 규칙이 실행되는 식입니다.

3단계: 보고와 자동 수정

위반을 발견한 규칙은 context.report()로 보고합니다. fix 함수를 제공하면 eslint --fix로 자동 수정이 가능합니다.

# 검사만
npx eslint src/

# 자동 수정 포함
npx eslint src/ --fix

# 결과 예시
src/app.ts
  1:1  error  Unexpected var, use let or const instead  no-var
  3:7  warn   Expected '===' and instead saw '=='       eqeqeq
  6:7  error  'unused' is assigned but never used        no-unused-vars

 3 problems (2 errors, 1 warning)
  2 errors and 0 warnings potentially fixable with the `--fix` option.

Prettier, 의견이 있는 포매터

Prettier는 2017년에 등장한 코드 포매터입니다. "의견이 있는" (opinionated) 도구로, 설정 옵션을 최소화하여 포맷팅 논쟁을 종결 시키는 것이 목표입니다.

// .prettierrc, 설정할 수 있는 것이 많지 않다
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "all",
  "printWidth": 80
}

Prettier도 AST를 사용하지만, ESLint와 접근이 다릅니다. Prettier는 원본 코드의 포맷팅을 완전히 무시하고 , AST에서 처음부터 새로 예쁘게 출력합니다.

// 입력 (아무리 못생겨도)
const x={a:1,b:2,c:3,d:4,e:5,f:6,g:7};
const y = [   1,2,    3,
4,5,6   ];

// Prettier 출력 (항상 일관됨)
const x = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 };
const y = [1, 2, 3, 4, 5, 6];

ESLint + Prettier, 왜 분리하는가

ESLint에도 포맷팅 관련 규칙 (indent, semi, quotes 등) 이 있었습니다. 그러나 2023년에 ESLint은 이 포맷팅 규칙들을 공식적으로 deprecated 처리했습니다.

이유는 명확합니다.

  1. 관심사 분리 , 린터는 코드의 의미를, 포매터는 코드의 외형을 담당합니다
  2. 충돌 방지 , ESLint의 포맷팅 규칙과 Prettier가 서로 다른 결과를 만들어 무한 수정 루프가 발생할 수 있습니다
  3. 성능 , AST 기반 포맷팅보다 Prettier의 전용 알고리즘이 더 효율적입니다

실전에서는 ESLint (코드 품질) + Prettier (포맷팅) 조합이 사실상 표준입니다.

// eslint.config.js, Prettier와 충돌하는 규칙 비활성화
import eslintConfigPrettier from 'eslint-config-prettier';

export default [
  // ... 다른 설정
  eslintConfigPrettier, // Prettier와 충돌하는 ESLint 규칙 비활성화
];

Biome, 통합의 시도

Biome은 린터와 포매터를 하나의 도구로 통합한 Rust 기반 툴체인입니다. ESLint + Prettier의 두 도구를 하나로 대체합니다.

// biome.json
{
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "style": {
        "noVar": "error",
        "useConst": "error"
      }
    }
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2
  }
}

Biome의 장점

항목ESLint + PrettierBiome
속도기준10~25배 빠름
설정 파일2~4개1개
npm 패키지127개 이상1개
Prettier 호환성-97%
린트 규칙생태계 전체450개 이상 (ESLint, typescript-eslint 포함)

Biome의 한계

Biome은 빠르게 성장하고 있지만, ESLint의 10년 넘은 플러그인 생태계를 완전히 대체하지는 못합니다. eslint-plugin-react-hooks, eslint-plugin-import 같은 프레임워크 특화 플러그인이 필요하다면 여전히 ESLint이 필요할 수 있습니다. 다만 Biome 2.0 (2025년 3월) 부터는 플러그인 시스템이 도입되어 생태계가 확장되고 있습니다.

Flat Config, ESLint의 현대화

ESLint 9 (2024년) 부터 Flat Config 가 기본 설정 방식이 되었습니다. 기존의 .eslintrc 방식은 deprecated 되었습니다.

// eslint.config.js, Flat Config
import js from '@eslint/js';
import tseslint from 'typescript-eslint';

export default [
  js.configs.recommended,
  ...tseslint.configs.recommended,
  {
    rules: {
      'no-unused-vars': 'error',
      'eqeqeq': ['error', 'always'],
    },
  },
  {
    ignores: ['dist/', 'node_modules/'],
  },
];

Flat Config의 핵심 변화:

  • 단일 배열 , 설정이 하나의 배열로 평탄화됩니다. extends 체인의 복잡성이 사라집니다
  • ESM , eslint.config.js는 ESM 모듈입니다. require() 대신 import를 사용합니다
  • 명시적 , 어떤 플러그인이 어떤 파일에 적용되는지 투명하게 보입니다

개발 워크플로우에서의 위치

린터와 포매터는 개발 과정의 여러 시점에서 실행됩니다.

1. 에디터 저장 시, Prettier로 자동 포맷팅
2. 에디터 실시간, ESLint 경고/에러 표시
3. pre-commit hook, lint-staged + husky로 커밋 전 검사
4. CI 파이프라인, PR에서 린트/포맷 검사 실행
// package.json, lint-staged 설정
{
  "lint-staged": {
    "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.{json,md}": ["prettier --write"]
  }
}

가장 빠른 피드백 루프는 에디터 통합입니다. VS Code의 ESLint 확장과 Prettier 확장을 설치하면, 파일을 저장할 때마다 자동으로 포맷팅되고 린트 오류가 표시됩니다.

다음 단계

지금까지 번들러, 트랜스파일러, 린터, 코드를 합치고, 변환하고, 검사하는도구를 살펴봤습니다. 다음 글에서는 이 모든 도구의 기반이 되는 패키지 매니저 , npm, yarn, pnpm의 내부 동작과 진화를 알아보겠습니다.