중괄호만 세는 JS 포매터가 for문을 깨뜨리는 이유
브라우저에서 즉시 실행되는 무료 JS 포매터 대부분은 사실 "코드를 이해"하지 않습니다. Prettier 같은 진짜 도구는 코드를 문법 트리(AST)로 파싱한 뒤 그 트리를 다시 출력하지만, 가벼운 포매터는 문자를 한 글자씩 훑으며 몇 가지 기호만 보고 줄바꿈 위치를 정합니다. 이 차이가 왜 생기고, 어디서 문제를 일으키는지 실제 코드로 확인했습니다.
1. 왜 AST 파서 대신 문자 카운팅을 쓰는가
완전한 JS 파서를 처음부터 구현하려면 토큰화·문법 분석·ASI(자동 세미콜론 삽입) 규칙까지 수백~수천 줄이 필요합니다. 반면 "중괄호가 열리면 들여쓰기를 늘리고, 닫히면 줄인다"는 규칙은 몇 줄이면 충분하고, 압축된 코드를 눈으로 보기 좋게 펴는 목적은 대부분 만족시킵니다. JavaScript 포매터도 이 방식을 택했는데, 실제로 코드를 열어보면 jsBeautify() 함수 안에 파서나 토크나이저 클래스 없이 for(let i=0;i<code.length;i++) 형태의 단일 while 루프만 존재합니다. 속도가 빠르고 코드가 짧다는 장점이 있지만, 그 대가로 "문법을 안다"는 보장이 전혀 없습니다.
2. 들여쓰기 깊이는 정확히 무엇으로 계산되는가
코드를 직접 확인한 결과, 깊이(depth) 변수는 오직 여는 중괄호 {를 만날 때 +1, 닫는 중괄호 }를 만날 때 -1 되는 것이 전부입니다. 소괄호 ( )와 대괄호 [ ]는 이 카운터에 전혀 관여하지 않습니다. 즉 if(a&&(b||c))처럼 소괄호가 깊게 중첩돼도 들여쓰기 깊이는 그대로입니다. 문자열(따옴표로 감싼 구간)만은 inStr()이라는 별도 처리로 보호되어 있어, 문자열 내부의 {나 ;는 안전하게 통과합니다.
3. for문이 깨지는 정확한 메커니즘
문제는 세미콜론 처리 규칙입니다. 코드에는 "소괄호 안인지 밖인지" 구분하는 상태가 없고, ; 문자를 만나면 무조건 줄바꿈을 삽입합니다. 그런데 for(let i=0;i<n;i++){...} 헤더 안에는 세미콜론이 정확히 2개(구문 종료 세미콜론까지 포함하면 실질적으로 3개 지점) 들어 있습니다. 스캐너 입장에서는 이 세미콜론들이 "for문 헤더 안의 구분자"인지 "문장의 끝"인지 구별할 방법이 없어, 매번 줄바꿈을 넣습니다. 그 결과 한 줄이어야 할 for문 헤더가 세 줄로 쪼개져 출력됩니다.
| 단계 | 내용 |
|---|---|
| 입력 | for(let i=0;i<10;i++){sum+=i;} |
| 기대하는 출력(사람이 손으로 정리한 형태) | for(let i=0;i<10;i++){
sum+=i;
} |
| 실제 출력(중괄호 카운팅 방식) | for(let i=0;
i<10;
i++){
sum+=i;
} |
구문상 틀린 코드는 아니지만(JS는 세미콜론 뒤 줄바꿈을 허용), 원래 의도였던 "for문 헤더는 한 줄"이라는 가독성 규칙은 깨집니다. 헤더가 복잡해질수록(예: 조건에 함수 호출이 섞인 경우) 가독성 손해가 커집니다.
4. 정규식 리터럴이라는 또 다른 함정
더 위험한 케이스는 정규식입니다. inStr()은 따옴표(', ", 백틱)만 인식하도록 설계되어 있어, 슬래시로 감싼 정규식 리터럴 /\{[0-9]+\}/은 전혀 보호받지 못합니다. 이 경우 정규식 안의 {와 }가 코드 블록의 여는/닫는 중괄호로 오인되어 깊이(depth) 카운터가 잘못 증가·감소하고, 그 뒤에 이어지는 실제 코드 전체의 들여쓰기가 밀리는 연쇄 오류로 번질 수 있습니다. 문자열은 안전하지만 정규식은 안전하지 않다는 이 비대칭이 이 도구의 가장 큰 한계입니다.
5. 그래서 언제 써도 되고 언제 위험한가
단순한 함수·객체 위주의 짧은 코드를 빠르게 훑어보는 용도라면 이런 방식도 충분히 실용적입니다. 반면 for문이 많은 알고리즘 코드나 정규식이 섞인 검증 로직을 정리할 때는 결과를 그대로 신뢰하지 말고 직접 눈으로 검산하는 편이 안전합니다. 압축을 반대로 되돌리는 작업이라면 JSON 압축기·CSS 포매터도 비슷한 문자 카운팅 구조를 쓰므로 같은 주의가 필요합니다. 정규식 패턴 자체를 미리 검증하고 싶다면 정규식 생성기로 별도 테스트하는 것을 권장합니다.
자주 묻는 질문
Q. 이 포매터가 코드를 실제로 실행하거나 문법 오류를 검사하나요?
아니요. 문자를 훑기만 할 뿐 자바스크립트 엔진으로 실행하거나 파싱하지 않으므로, 원래 문법 오류가 있던 코드를 넣어도 오류를 알려주지 않고 그대로 줄바꿈만 시도합니다.
Q. 배열 리터럴 [1,2,3]도 중괄호처럼 들여쓰기에 영향을 주나요?
아니요. 깊이 카운터는 중괄호 { } 등장 횟수로만 계산되고 대괄호는 별도로 세지 않습니다. 그래서 배열 안의 쉼표는 현재 중괄호 깊이가 0이면 줄바꿈 없이 공백만 추가되어 한 줄로 남는 경우가 많습니다.
Q. Prettier 같은 도구를 쓰면 이런 문제가 전혀 없나요?
Prettier는 코드를 실제로 파싱해 문법 트리를 만든 뒤 그 트리 기준으로 다시 출력하므로 for문·정규식·템플릿 리터럴까지 문맥을 이해하고 처리합니다. 다만 브라우저에서 설치 없이 즉시 돌아가는 가벼운 도구는 그런 파싱 엔진을 통째로 들고 있지 않다는 트레이드오프가 있습니다.
Q. 템플릿 리터럴 안의 ${} 표현식도 영향을 받나요?
백틱으로 시작한 문자열은 닫는 백틱이 나올 때까지 통째로 하나의 문자열로 취급되어 복사되므로, 그 안의 삽입 표현식에 들어간 중괄호나 세미콜론은 재정렬되지 않고 원본 그대로 남습니다.