top of page

JavaScript Lint 규칙의 작동 방식과 추상 구문 트리(Abstract Syntax Trees, AST)의 중요성

린터(Linter)는 언어, 프레임워크, 워크플로 등 모든 곳에 존재합니다. 오류를 포착하고, 일관된 서식을 적용하며, 모범 사례를 장려하는 데 도움이 됩니다. 새 프로젝트에 설치하는 첫 번째 도구 중 하나이지만, 동시에 가장 과소평가되고 이해도가 낮은 도구 중 하나이기도 합니다.


AST

이 글에서는 린트(Lint)의 원리를 자세히 알아보겠습니다. JavaScript 린트 규칙의 작동 방식, AST(추상 구문 트리)가 왜 중요한지, 그리고 이러한 이해를 바탕으로 린터를 직접 작성하거나 기여하는 방법을 살펴보겠습니다.



📚 목차



📓 린터(Linter)란 무엇인가?


린터는 코드를 자동으로 분석하여 오류를 표시하고, 스타일 규칙을 적용하고 , 잠재적 버그를 잡아내는 도구입니다. 코딩 세계의 문법을 잡아주는 도구라고 생각하면 됩니다. 문제를 조기에 발견하여 더 깔끔하고 일관된 코드를 작성할 수 있도록 도와줍니다.


유명한 예로는 JavaScript와 TypeScript를 위한 오픈소스 린터인 ESLint가 있는데 , 이는 코드의 문제를 검사하고 심지어 일부 문제를 자동으로 수정할 수도 있습니다.


린터는:

  • 텍스트 편집기나 IDE에 통합됨

  • CI 파이프라인 또는 사전 커밋 후크의 일부로 실행

  • Prettier와 같은 포맷터와 함께 사용하면 더욱 엄격한 일관성을 유지할 수 있습니다.


하지만 어떤 항목을 문제로 표시할지 어떻게 결정할까요? 바로 여기서 Lint 규칙이 사용됩니다.



🧱 Lint 규칙: Linter의 두뇌


린트 규칙은 모든 린터의 기본 요소입니다. 각 규칙은 다음을 정의합니다:


  1. 찾아야 할 것 : 코드의 특정 패턴.

  2. 이에 대한 대처 방법 : 경고, 오류 또는 자동 수정.


규칙에는 여러 유형이 있으며, 종종 다음과 같은 범주로 그룹화됩니다:


  • 오류 방지 : 선언되지 않은 변수 사용 등의 버그를 잡습니다.

  • 코드 스타일 : 일관된 서식 및 명명 규칙을 적용합니다.

  • 모범 사례 : 더 안전하거나 가독성이 높은 코딩 패턴을 장려합니다.

  • 보안 : 직접 eval() 호출이나 안전하지 않은 정규식과 같은 위험한 코드를 표시합니다.


다음과 같은 ESLint 메시지를 본 적이 있다면:


Unexpected console.log

Missing semicolon

'myVar' is assigned a value but never used

린트 규칙이 실제로 적용되는 것을 보신 것 입니다.


린터는 단순한 스타일을 바로잡는 도구가 아닙니다 . 린터는 작은 문제를 조기에 포착하여 정신적 부담을 줄이는 데 도움이 되므로, 코드의 큰 그림을 보는데 집중할 수 있습니다.



⌨️ 코드에서 트리까지: AST 입력


Lint 규칙이 내부적으로 어떻게 작동하는지 이해하려면 모든 linter의 핵심이 되는 데이터 구조인 추상 구문 트리(Abstract Syntax Tree, AST) 에 대해 알아야 합니다 .


AST는 코드를 구조화된 트리 형태로 표현한 것입니다. 린터는 코드를 원시 텍스트로 읽는 대신, 코드의 각 부분(변수, 문자열, 함수 등)을 트리 의 노드 로 변환합니다.


예를 들어 보겠습니다:


실시간으로 코드의 AST를 볼 수 있는 도구인 AST Explorer 에 이 코드를 붙여넣으세요.


언어를 JavaScript 로 설정하고 Espree 와 같은 ESLint 파서 중 하나를 선택하세요 . 오른쪽 패널에 다음과 같은 내용이 표시됩니다.


AST 1
상수 선언을 위한 VariableDeclaration 노드를 보여주는 AST(추상 구문 트리). 그 안에는 'name'이라는 식별자에 문자열 값 'Frank'를 갖는 Literal 노드를 할당하는 VariableDeclarator가 있습니다.

위의 AST Explorer 이미지에서 트리가 어떻게 구성되어 있는지 확인할 수 있습니다.


Program:

  • AST의 루트 노드입니다. 전체 코드를 감싸고 있습니다.

  • 배열인 body 문장을 포함합니다 .


VariableDeclaration:

  • 유형: "VariableDeclaration"

  • const 키워드를 사용한 선언을 나타냅니다.

  • kind"const"declarations 목록이 있습니다.


VariableDeclarator:

  • 유형: "VariableDeclarator"

  • 단일 변수가 선언되는 것을 나타냅니다.

  • 두 가지 주요 부분으로 구성되어 있습니다.

  • Identifier

    • 유형: "Identifier"

    • 이름: "name"

    • 이것이 선언되는 변수입니다.

  • Literal

    • 유형: "Literal"

    • 값: "Frank"

    • 이는 변수에 할당되는 문자열입니다.


이러한 중첩 구조는 구조를 "트리 형태" 로 만듭니다 . 각 노드는 더 작은 노드(자식 노드)의 부모 역할을 하며, 이는 린터가 코드를 안정적으로 탐색하는 데 도움이 됩니다.


따라서 우리의 눈에는 짧은 JavaScript 한 줄이 보이지만, 린터는 해당 줄이 구조적으로 무엇을 의미하는지 에 대한 자세한 지도를 봅니다. 이러한 계층 구조를 통해 ESLint와 같은 도구는 어떤 종류의 코드가 어디에 사용되었는지 정확하게 파악할 수 있으므로, 규칙을 통해 다음과 같은 패턴을 타겟팅할 수 있습니다.


  • "모든 const 변수에 플래그 지정"

  • "변수에 name이라는 이름이 지정되면 경고"

  • "Frank 와 같은 하드코딩된 문자열을 허용하지 않는다"



🤔 Linting에 AST가 중요한 이유


핵심은 다음과 같습니다. Lint 규칙은 코드를 텍스트처럼 읽는 방식이 아니라, AST의 특정 노드 패턴을 매칭하는 방식으로 작동합니다.


JavaScript에서 동일한 로직을 작성하는 방법은 수십 가지가 있기 때문에 이는 매우 중요합니다. 동일한 로직의 두 가지 버전을 살펴보겠습니다. 하나는 함수 선언 으로 작성하고, 다른 하나는 화살표 함수 로 작성했습니다 .



언뜻 보기에는 달라 보입니다. 하지만 AST를 살펴보면 둘 다 유사한 구조적 패턴을 따른다는 것을 알 수 있습니다. 이것이 린터가 코드의 작성 방식과 관계없이 코드의 동작을 인식할 수 있게 해주는 것입니다.



🌳 함수 선언의 트리


함수 이름 식별자가 포함된 FunctionDeclaration 노드를 보여주는 추상 구문 트리(AST). 이 함수는 ReturnStatement 노드가 있는 BlockStatement를 포함합니다. ReturnStatement 내부에는 'hello' 문자열을 반환하는 Literal 노드가 있습니다.
함수 이름 식별자가 포함된 FunctionDeclaration 노드를 보여주는 추상 구문 트리(AST). 이 함수는 ReturnStatement 노드가 있는 BlockStatement를 포함합니다. ReturnStatement 내부에는 'hello' 문자열을 반환하는 Literal 노드가 있습니다.

함수 선언을 작성할 때 ESLint가 AST 트리에서 확인하는 내용은 다음과 같습니다:


  • 모든 것은 FunctionDeclaration 노드로부터 시작됩니다.

  • 해당 노드에는 다음이 포함됩니다:

    • Identifier (함수 이름: greet)

    • 함수 본문을 나타내는 BlockStatement

    • BlockStatement의 내부에는 ReturnStatement가 존재

    • ReturnStatement는 문자열 "hello" 라는 Literal을 반환



🌳 화살표 함수의 트리


const 화살표 함수의 VariableDeclaration 노드를 보여주는 추상 구문 트리(AST). AST 내부에는 ArrowFunctionExpression을 식별자에 할당하는 VariableDeclarator가 있습니다. ArrowFunctionExpression은 'hello' 문자열을 반환하는 Literal 노드를 포함하는 본문을 포함합니다.
const 화살표 함수의 VariableDeclaration 노드를 보여주는 추상 구문 트리(AST). AST 내부에는 ArrowFunctionExpression을 식별자에 할당하는 VariableDeclarator가 있습니다. ArrowFunctionExpression은 'hello' 문자열을 반환하는 Literal 노드를 포함하는 본문을 포함합니다.

화살표 함수를 사용하여 동일한 논리를 작성하면 ESLint에서 다음과 같은 결과가 나타납니다:


  • kind: "const"가 붙여진 VariableDeclaration

    • 그 안에는 변수 greet에 값을 할당하는 VariableDeclarator가 존재.

    • 값은 ArrowFunctionExpression

    • 화살표 함수의 본문은 문자열 "hello"Literal 입니다.


구문은 다르지만 두 경로 모두 결국에는 "hello" 값을 가지고 있는 리터럴 노드 로 이어집니다. 여기에는 linter가 모니터 해야 할 모든 것이 포함됩니다 .



💡 예제


팀에 다음과 같은 규칙이 있다고 가정해 보겠습니다.

👉 어떠한 함수도 "hello" 와 같은 하드코딩된 문자열을 반환해서는 안 됨

이를 표시하는 린터가 필요합니다.


AST를 사용하면 ReturnStatement 또는 ArrowFunctionExpression 본문이 Literal 과 일치하는 하나의 lint 규칙을 작성할 수 있습니다.


기본적인 아이디어는 다음과 같습니다:


표현식 본문이 있는 화살표 함수의 경우:


코드 스타일이 다르더라도 AST의 구조는 매우 유사하므로 두 함수 모두 규칙을 트리거합니다. 왜냐하면 linter는 코드가 어떻게 작성되었는지가 아니라 AST의 실제 구조만 보기 때문입니다.


AST가 유용한 이유는 바로 이것입니다. 린터가 표면적인 차이를 무시하고 코드의 실제 의미와 구조에 집중할 수 있도록 해주기 때문입니다. 결과적으로, 다른 사람이 JavaScript를 어떻게 작성했는지와 관계없이 다양한 스타일에서 패턴을 포착하는 더욱 스마트하고 유연한 규칙을 작성할 수 있습니다.



📃 ESLint가 AST를 내부적으로 사용하는 방식


ESLint는 JavaScript 코드를 추상 구문 트리(AST)로 표현하기 위해 ESTree(ECMAScript Tree)라는 표준화된 형식을 사용합니다. ESTree 자체는 파서(parser)가 아니라 JavaScript 코드를 트리 형태로 표현하는 방식을 정의하는 사양입니다. 이를 통해 ESLint(및 유사 도구)는 일관되고 체계적인 방식으로 코드를 이해할 수 있습니다.


코드에서 ESLint를 실행하면 다음과 같은 작업이 수행됩니다:


1. 코드가 AST로 구문 분석됩니다.

ESLint는 코드를 ESTree 형식을 따르는 AST로 변환합니다. 이 트리는 노드로 구성되며, 각 노드는 코드의 각 부분(변수, 함수 또는 표현식 등)을 나타냅니다. 모든 Lint 규칙은 결과 구조를 분석합니다.


2. Lint 규칙은 특정 노드 유형에 "구독"합니다.

각 린트 규칙은 ESLint가 어떤 노드 유형을 수신할지 지정합니다. 예를 들어, 규칙은 다음과 같은 노드 유형을 처리할 수 있습니다.

  • Identifier

  • CallExpression

  • VariableDeclaration

이러한 노드 유형은 AST Explorer와 같은 도구에서 볼 수 있는 구조와 일치합니다.


3. ESLint는 트리를 탐색하고 규칙을 트리거합니다.

ESLint는 AST를 탐색하며 한 번에 한 노드씩 방문합니다. 규칙이 구독한 노드 유형에 도달하면 해당 규칙의 해당 함수를 트리거합니다.


이 프로세스는 효율적이고 선언적이므로 모든 코드 줄을 수동으로 검토할 필요가 없습니다. ESLint가 모든 작업을 처리하고, 규칙은 그저 듣고만 있으면 됩니다.


4. 규칙 노드 검사 및 문제 리포팅

각 규칙 내부에서 ESLint가 전달한 노드를 받습니다. 이름, 값 또는 주변 구조와 같은 속성을 살펴보고 의도한 패턴을 위반하는지 여부를 판단할 수 있습니다.


그렇다면 ESLint에 검사 항목을 문제로 표시하도록 context.report()를 이용하여 요청할 수 있습니다. 아래의 예제 코드와 같이, context.report() 내에 fix() 함수를 제공하면 ESLint가 자동으로 문제를 해결할 수도 있습니다.



🧐 린트 규칙의 해부학


아주 간단한 사용자 지정 ESLint 규칙을 살펴보겠습니다. 이 규칙은 any라는 이름의 변수에 플래그를 지정합니다.


🔎 코드블록의 설명

  • meta 섹션은 규칙에 대한 정보를 제공합니다(ESLint 문서와 도구에서 사용됨).

  • create() 함수는 규칙이 어떤 노드 유형을 수신하는지 정의합니다.

  • 코드에서 식별자가 발견될 때마다 Identifier(node)가 트리거됩니다.

  • 식별자의 이름이 any인 경우, 규칙은 context.report()를 호출하여 경고를 발생시킵니다.



🛠 AST 탐색을 위한 유용한 도구


AST를 이해하는 것은 처음에는 추상적으로 느껴질 수 있지만, 일부 도구를 사용하면 학습이 훨씬 수월해집니다. 이러한 도구는 코드가 트리 구조로 어떻게 변환되는지 시각화하거나 사용자 지정 규칙을 디버깅할 때 특히 유용합니다.



이 도구는 AST 작업에 있어 초보자에게 가장 친화적이고 강력한 도구입니다. 다음과 같은 작업을 수행할 수 있습니다.


  • JavaScript 코드를 복사 붙여넣기

  • ESLint 호환 파서(예: Espree)를 선택

  • 오른쪽 창에서 실시간으로 AST 구조 확인

  • 트리 노드 위에 마우스를 올려놓으면 왼쪽 코드의 어떤 부분에 매핑되는지 표시


사용자 지정 규칙을 작성하는 경우 AST Explorer는 가장 유용한 도구입니다. AST Explorer를 사용하면 어떤 노드 유형을 대상으로 삼아야 하는지, 그리고 해당 노드에서 어떤 속성을 사용할 수 있는지 정확하게 파악할 수 있습니다.


2. ESLint 규칙 예제 및 테스트


때로는 실제 코드를 리뷰하는 것이 학습하는 가장 좋은 방법입니다. ESLint의 핵심 규칙(또는 eslint-plugin-react 와 같은 유명 플러그인의 규칙)은 다음과 같습니다:


  • 규칙 정의

  • 규칙을 트리거 해야 하는 코드와 트리거 하지 않아야 하는 코드를 보여주는 테스트 파일

  • 예시 수정 (규칙이 자동으로 수정 가능한 경우)


이러한 내용을 살펴보면 실제 규칙이 어떻게 구성되어 있는지, 테스트 설정이 어떻게 작동하는지 이해하는 데 도움이 됩니다.


💡팁: ESLint나 플러그인 저장소의 폴더 tests/lib/rules/ 또는 lib/rules/ 폴더를 살펴보세요.

3. ESLint 문서


ESLint 사이트에는 규칙 작업에 대한 포괄적인 문서들이 존재합니다.




✅ 마무리: 왜 이것을 이해해야 할까요?


AST 작동 방식을 이해하면 린팅 도구를 커스터마이징 할 때 엄청난 능력을 발휘할 수 있습니다. 팀 코드베이스에 특정 패턴을 적용하거나 eslint-plugin-react와 같은 플러그인을 커스터마이징 할 경우 이러한 지식이 도움이 될 것입니다.


  • 기존 규칙이 무엇을 어떻게 검사하는지 이해하여 기존 규칙을 커스터마이징 하세요 .

  • 규칙이 예기치 않게 실행될 때 (또는 전혀 실행되지 않을 때) 혼란스러운 linter 동작을 디버깅합니다.

  • 특정 코딩 표준, 프로젝트 규칙 또는 모범 사례를 적용하기 위해 사용자 정의 규칙을 작성하세요.


컴파일러 전문가가 되거나 사양에 있는 모든 노드 유형을 완벽하게 이해할 필요는 없습니다. 패턴을 인식하고, 트리를 탐색하고, 규칙에서 중요하게 여기는 노드를 식별하는 데 익숙해지면 됩니다.

pngegg (11)_result.webp

<Raank:랑크 /> 구독 하기 : Subscribe

감사합니다! : Thanks for submitting!

©2024 by <Raank:랑크 /> Knowledge is Power

  • Linkedin
  • Knowledge Arcadia - Icon 8c
  • Raanktone - Icon 16 - 1
  • Qubitronix
  • Naver Blog
bottom of page