ESLint로 AI 생성 코드의 잠재 오류를 자동 탐지하는 방법

AI가 만든 코드는 왜 ESLint 검사가 필요할까?

바이브 코딩을 이용하면 로그인, 게시판, API 호출과 같은 기능을 빠르게 만들 수 있다. 문제는 화면이 정상적으로 표시된다는 이유만으로 코드까지 안전하다고 판단하기 쉽다는 것이다.

AI는 사용하지 않는 변수를 남기거나, 존재하지 않는 변수를 참조하거나, 비동기 함수의 결과를 처리하지 않는 코드를 생성할 수 있다. 복사한 코드를 수정하는 과정에서 조건문이 항상 같은 결과를 반환하거나, 실행되지 않는 코드가 남는 경우도 있다.

이러한 문제를 사람이 모든 파일에서 찾아내기는 어렵다. 프로젝트 규모가 커질수록 AI가 수정한 코드와 기존 코드를 구분하는 일도 복잡해진다. 이때 활용할 수 있는 도구가 ESLint다.

ESLint는 JavaScript와 관련 언어의 코드를 분석해 정해진 규칙에 어긋나는 부분을 알려주는 정적 분석 도구다. 프로그램을 직접 실행하지 않아도 코드의 구조와 패턴을 검사할 수 있다. ESLint 공식 문서에서도 코드의 일관성을 유지하고 버그를 피하기 위한 패턴을 식별하는 도구로 설명한다. ESLint 시작 문서

ESLint가 찾아낼 수 있는 AI 생성 코드 문제

ESLint를 적용하면 다음과 같은 문제를 자동으로 발견할 수 있다.

사용하지 않는 변수

AI가 코드를 여러 번 수정하면 이전 로직에서 사용하던 변수가 남을 수 있다.

const userName = user.name;
const userEmail = user.email;

return userEmail;

위 코드에서 userName은 선언됐지만 사용되지 않는다. 당장 오류를 일으키지는 않더라도 불필요한 코드가 쌓이고 유지보수를 어렵게 만든다.

선언하지 않은 변수 사용

AI가 변수명을 변경하면서 일부 위치를 놓치면 존재하지 않는 변수를 참조할 수 있다.

const totalPrice = price * quantity;

console.log(finalPrice);

브라우저에서 해당 코드가 실행되는 순간 finalPrice is not defined 오류가 발생한다. no-undef 규칙은 이런 참조를 미리 탐지할 수 있다.

도달할 수 없는 코드

함수가 이미 값을 반환했는데 그 아래에 코드가 남는 경우다.

function getUserName(user) {
  return user.name;
  console.log("사용자 이름 확인");
}

return 아래의 코드는 실행되지 않는다. AI가 여러 함수의 내용을 합치거나 코드를 부분 수정할 때 자주 생길 수 있는 형태다.

배열 콜백의 반환값 누락

const activeUsers = users.filter((user) => {
  user.active;
});

filter의 콜백에서 값을 반환하지 않았기 때문에 예상한 결과를 얻을 수 없다. ESLint의 array-callback-return 같은 규칙은 배열 메서드 콜백에서 반환문이 누락된 문제를 찾는 데 도움을 준다.

잘못된 반복문과 조건문

반복문의 증감 방향이 종료 조건과 맞지 않거나 조건이 항상 참인 코드도 검사 대상이 될 수 있다.

for (let i = 0; i < 10; i--) {
  console.log(i);
}

이 코드는 i가 계속 감소하기 때문에 종료되지 않을 가능성이 높다. ESLint는 가능한 논리 오류를 검사하는 다양한 내장 규칙을 제공한다. 어떤 규칙이 추천 설정에 포함되는지와 자동 수정 가능 여부는 ESLint 규칙 목록에서 확인할 수 있다.

기존 프로젝트에서는 설정부터 확인하자

AI가 만든 프로젝트에 ESLint를 추가하기 전에 프로젝트 루트에서 다음 파일을 찾아본다.

eslint.config.js
eslint.config.mjs
eslint.config.cjs
.eslintrc
.eslintrc.json
.eslintrc.js

이미 설정 파일이 있다면 새로운 파일로 교체하지 말고 기존 설정이 어떤 프레임워크와 플러그인을 사용하는지 확인해야 한다. Next.js, React, Vue처럼 프레임워크가 포함된 프로젝트는 자체 규칙이나 공유 설정을 사용하고 있을 수 있다.

설정 파일이 없다면 프로젝트 루트에서 다음 명령으로 초기 설정을 시작할 수 있다.

npm init @eslint/config@latest

이 명령은 프로젝트가 JavaScript를 사용하는지, TypeScript를 사용하는지, 코드가 브라우저와 Node.js 중 어디에서 실행되는지 등을 질문하고 현재 형식의 ESLint 설정 파일을 생성한다.

JavaScript 프로젝트의 기본 설정 예시

현재 ESLint는 프로젝트 루트에 eslint.config.js 또는 eslint.config.mjs와 같은 flat config 파일을 두는 방식을 사용한다. 간단한 JavaScript 프로젝트에서는 다음과 같은 형태로 시작할 수 있다.

import js from "@eslint/js";
import { defineConfig } from "eslint/config";

export default defineConfig([
  {
    files: ["**/*.{js,mjs,cjs}"],
    extends: [js.configs.recommended],
  },
  {
    ignores: [
      "dist/**",
      "build/**",
      "coverage/**",
      ".next/**",
    ],
  },
]);

files는 검사할 파일을 지정하고 ignores는 빌드 결과물처럼 검사할 필요가 없는 폴더를 제외한다. 파일 패턴과 제외 대상은 프로젝트 구조에 맞게 조정해야 한다. 설정 파일의 종류와 적용 방식은 ESLint 설정 파일 공식 문서에서 확인할 수 있다.

TypeScript 프로젝트는 타입 기반 검사를 추가하자

TypeScript 프로젝트라면 일반 ESLint 검사와 타입 검사를 함께 사용해야 더 많은 문제를 찾을 수 있다. 필요한 패키지는 다음과 같이 설치할 수 있다.

npm install --save-dev eslint @eslint/js typescript typescript-eslint

기본적인 타입 정보 기반 설정 예시는 다음과 같다.

import js from "@eslint/js";
import { defineConfig } from "eslint/config";
import tseslint from "typescript-eslint";

export default defineConfig({
  files: ["**/*.{js,ts}"],
  extends: [
    js.configs.recommended,
    tseslint.configs.recommended,
    tseslint.configs.recommendedTypeChecked,
  ],
  languageOptions: {
    parserOptions: {
      projectService: true,
    },
  },
});

recommendedTypeChecked는 TypeScript 타입 정보를 이용하는 추천 규칙을 추가한다. projectService: true를 설정하면 각 소스 파일에 해당하는 TypeScript 프로젝트 정보를 사용해 검사할 수 있다.

타입 기반 검사를 적용하면 처리되지 않은 Promise, 잘못 사용된 비동기 함수, 안전하지 않은 any 값처럼 단순한 문법 검사만으로 찾기 어려운 문제를 발견할 수 있다. 다만 프로젝트 전체의 타입 정보를 분석하기 때문에 일반 ESLint 검사보다 시간이 더 걸릴 수 있다. typescript-eslint 타입 기반 검사 문서

ESLint를 실행하는 기본 명령어

프로젝트 전체를 검사하려면 루트 디렉터리에서 다음 명령을 실행한다.

npx eslint .

특정 폴더만 검사할 수도 있다.

npx eslint src

AI가 방금 수정한 파일 하나만 먼저 확인하려면 파일 경로를 지정한다.

npx eslint src/components/LoginForm.tsx

ESLint 결과에는 일반적으로 파일 경로, 줄 번호, 위치, 문제 설명, 심각도와 규칙 이름이 표시된다.

src/utils/user.js
  8:7  error  'userName' is assigned a value but never used  no-unused-vars

여기서 8:7은 8번째 줄의 7번째 위치를 의미한다. 마지막의 no-unused-vars는 문제를 보고한 규칙 이름이다. 규칙 이름으로 공식 문서를 검색하면 문제가 발생한 이유와 수정 예시를 확인할 수 있다.

자동 수정은 작은 범위부터 적용하자

일부 ESLint 문제는 --fix 옵션으로 자동 수정할 수 있다.

npx eslint src --fix

ESLint 공식 CLI 문서에 따르면 --fix는 자동으로 수정할 수 있는 문제를 실제 파일에 반영하고, 수정하지 못한 문제는 결과로 남긴다. 모든 문제가 자동 수정되는 것은 아니다. ESLint 명령줄 공식 문서

AI 프로젝트에서는 처음부터 전체 프로젝트에 자동 수정을 적용하기보다 다음 순서가 안전하다.

  1. 현재 변경 내용을 Git에 저장하거나 확인한다.
  2. npx eslint .로 문제 목록만 확인한다.
  3. 오류가 많은 폴더와 규칙을 분류한다.
  4. 특정 파일이나 작은 폴더에만 --fix를 적용한다.
  5. git diff로 자동 변경된 내용을 검토한다.
  6. 테스트와 빌드를 다시 실행한다.

저장하지 않고 자동 수정 결과를 미리 계산하려면 --fix-dry-run을 활용할 수 있다.

npx eslint src --fix-dry-run --format json

자동 수정은 검토 과정을 줄여주는 기능이지 테스트를 대체하는 기능이 아니다. 수정 후에는 화면 동작, API 호출, 인증, 권한 검사와 같은 실제 기능도 다시 확인해야 한다.

오류를 없애기 위해 규칙부터 끄면 안 되는 이유

ESLint 오류가 많이 나오면 AI에게 모든 경고를 제거해 달라고 요청하거나 파일 상단에 다음 주석을 추가하기 쉽다.

/* eslint-disable */

이 주석은 해당 파일에서 ESLint 규칙을 광범위하게 비활성화할 수 있다. 오류 메시지는 사라지지만 문제 자체가 해결된 것은 아니다.

규칙을 예외 처리해야 한다면 먼저 다음 사항을 확인한다.

  1. 코드가 실제로 잘못된 것은 아닌가?
  2. 프레임워크 설정이나 실행 환경이 누락된 것은 아닌가?
  3. JavaScript용 규칙이 TypeScript 코드와 충돌하는 것은 아닌가?
  4. 전체 규칙이 아니라 특정 줄이나 특정 파일에만 예외가 필요한가?
  5. 예외 이유를 다른 개발자가 이해할 수 있는가?

ESLint도 가능한 경우 인라인 비활성화 주석보다 설정 파일을 이용한 일관된 규칙 관리를 권장한다. ESLint 규칙 설정 문서

package.json에 검사 명령 등록하기

매번 긴 명령어를 입력하지 않도록 package.jsonscripts에 등록할 수 있다.

{
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "lint:check": "eslint . --max-warnings 0"
  }
}

이후에는 다음과 같이 실행한다.

npm run lint
npm run lint:fix
npm run lint:check

--max-warnings 0은 경고가 남아 있는 상태를 자동화 검사에서 실패로 처리하고 싶을 때 사용할 수 있다. 기존 프로젝트에 경고가 많다면 먼저 경고 수를 줄인 후 적용하는 것이 좋다.

ESLint가 통과하면 코드를 믿어도 될까?

ESLint 결과가 0이라고 해서 프로그램의 모든 문제가 해결됐다는 의미는 아니다. ESLint는 설정된 규칙과 분석 가능한 코드 패턴만 검사한다.

다음과 같은 문제는 ESLint만으로 확인하기 어렵다.

  • API 키와 비밀번호가 외부에 노출됐는지
  • 로그인하지 않은 사용자가 관리자 API에 접근할 수 있는지
  • 데이터베이스 권한 정책이 올바르게 설정됐는지
  • 결제 금액과 할인 계산이 요구사항에 맞는지
  • 외부 API가 실패했을 때 화면이 정상적으로 처리되는지
  • 모바일 환경에서 버튼과 입력 폼이 제대로 동작하는지

따라서 바이브 코딩 프로젝트는 다음 순서로 검증하는 것이 좋다.

ESLint 검사
→ TypeScript 타입 검사
→ Git Diff 검토
→ 단위 테스트
→ 실제 기능 테스트
→ 빌드 확인
→ 배포 전 보안 점검

마무리

ESLint는 AI 생성 코드의 모든 버그를 해결해 주는 도구는 아니지만 사람이 놓치기 쉬운 잠재 오류를 빠르게 좁혀주는 첫 번째 필터로 유용하다.

중요한 것은 오류 개수를 억지로 0으로 만드는 것이 아니다. 어떤 규칙이 어떤 위험을 발견했는지 확인하고, 작은 범위에서 수정한 뒤 Git Diff와 테스트로 다시 검증해야 한다.

AI에게 코드를 생성하게 했다면 같은 AI에게 무조건 수정까지 맡기기 전에 ESLint 결과를 구체적으로 전달해 보자. 파일 경로, 줄 번호, 규칙 이름, 기대하는 동작을 함께 제공하면 불필요한 전체 파일 재작성도 줄일 수 있다.

댓글 남기기