나만의 Linter 구현하기 - 1

Dev
2025년 05월 04일

들어가며

이 글을 읽는 대부분의 개발자들은 Prettier, ESLint, Biome 등의 도구들에 대해 알고 있거나 현재도 사용중일 것이다. 그러나 이러한 도구들이 내부적으로 어떻게 동작하는지 알고 있는가?

필자는 최근 Biome 프로젝트에 관심을 가지게 되었다. 호기롭게 오픈소스 기여를 위해 이슈 하나를 할당받았다. 하지만 필자에게 익숙지 않은 Rust 언어로 작성되었으며, Linter의 동작 원리 또한 모르고 있었기에 해당 이슈를 해결하지 못햇다.

이 경험을 바탕으로 필자는 Linter의 핵심 동작 원리를 이해하고 싶다는 강한 동기를 얻었다. 간단하게나마 JS 기반의 Linter를 직접 구현하여 npm에 배포까지 해보자는 목표가 세워졌고, 그 과정을 이 포스팅에 담아보려고 한다.

렉싱이란 무엇인가?

우리가 작성한 코드는 컴퓨터에게는 단순한 문자열이다. 이 문자열을 이해 가능한 구조로 바꾸려면, 먼저 코드를 의미 있는 조각인 토큰으로 나누는 작업이 필요하다. 이를 렉싱(Lexing) 또는 어휘 분석(Lexical Analysis)라고 한다.

렉싱에서는 보통 공백, 줄바꿈, 주석 등은 문법적으로 의미가 없거나 무시해도 되는 경우가 많기 때문에 필터링된다.

토큰 (Token)

토큰은 코드에서 의미를 갖는 조각이다. 언어에 따라 다양하지만, 일반적으로 다음과 같은 종류가 있다.

종류설명예시
키워드 (keyword)언어에 내장된 예약어let, if, return
식별자 (identifier)변수명, 함수명, 클래스명 등 사용자가 정의한 이름count, renderButton
리터럴 (literal)값 자체를 나타내는 요소42, 'hello', true
연산자 (operator)계산이나 대입 등의 연산을 수행하는 기호+, -, =, ===
구분자 (delimiter)구문을 구분하거나 구조를 표현하는 기호(, ), {, }, ;

렉싱 예제

const total = price * quantity;

위 코드를 렉싱하면 다음과 같은 토큰이 생성된다. 이렇게 생성된 토큰들은 이후 파싱 단계에서 문법적 구조를 만들기 위한 재료로 사용된다.

토큰 값유형
constKeyword
totalIdentifier
=Operator
priceIdentifier
*Operator
quantityIdentifier
;Punctuation

파싱이란 무엇인가?

위키피디아에 정의된 내용을 인용하자면 구문 분석 또는 파싱 은 일련의 문자열을 의미있는 토큰으로 분해하고 이들로 이루어진 파스 트리를 만드는 과정을 말한다.

위키피디아 파싱

파스 트리

파스 트리란 파서가 토큰들을 언어의 문법 규칙에 따라 구조화한 결과물이다. 이름에서 알 수 있듯이 트리 구조이며, 토큰들이 어떤 문장 구조를 이루는지를 나타낸다.

트리 구조란 그래프의 일종으로 한 노드에서 시작해서 다른 정점들을 순회하여 자신에게 돌아오는 순환이 없는 연결 그래프이다.

출처: 나무위키출처: 나무위키

AST (Abstract Syntax Tree)

AST는 추상 구문 트리이다. 위에서 살펴본 파스 트리에서 불필요한 문법적 디테일을 제거한 축약 버전이다. Linter를 구현하면서는 이 AST를 사용할 예정이다.
결론적으로 작성된 코드를 파싱(렉싱 -> AST 생성)하고 생성된 AST를 순회하며 정의해둔 규칙을 적용하도록 구현할 것이다.

아래 사이트를 통해 간단한 코드를 AST로 변환해보자. 변환할 코드는 다음과 같다.
AST 코드 확인 사이트

var a = 10;
let x = 20;

변환된 결과변환된 결과

구분파스 트리(Parse Tree)AST (추상 구문 트리)
목적문법 규칙을 완전하게 표현의미 중심으로 구조 요약
디테일매우 세세함 (괄호, 세미콜론 등 포함)핵심 정보만 유지
크기크고 복잡함작고 단순함

마무리

이번 포스팅에서는 Linter를 구현하기 위해 반드시 이해해야 할 렉싱(Lexing)과 파싱(Parsing)의 기본 원리에 대해 살펴보았다.

실제로 동작하는 Linter를 직접 구현하는 과정은 다음 포스팅에서 자세히 다뤄보려고 한다. 구체적인 코드와 함께, 어떤 식으로 규칙을 정의하고 오류를 감지하는지를 하나씩 구현해볼 예정이다.