TypeScript Compiler API: Preserving Child Node Narrowing in Reusable Type Guards 🔧
TypeScript Compiler API: 재사용 타입 가드에서 자식 노드 좁히기 유지하기
TypeScript AST에서 부모 노드와 자식 속성을 함께 좁히는 재사용 타입 가드를 `is-kit`의 `and`, `refineKey`, `refineDefinedKey`, `refineIndex`로 구성하는 방법을 설명합니다. TypeScript 7의 AST 타입과 타입 검사 성능에 관한 고려 사항도 다룹니다.
- 주제
AI 요약
TypeScript Compiler API를 쓰다 보면 부모 AST 노드의 종류와 특정 자식 속성의 타입을 함께 확인하는 일이 반복됩니다. 예를 들어 CallExpression의 expression이 Identifier인지 확인하려면 ts.isCallExpression(node)와 ts.isIdentifier(node.expression)을 결합해야 합니다. 조건문 안에서는 TypeScript가 두 타입을 모두 좁히지만, 이 검사를 함수로 추출하면 반환 타입을 별도로 선언하지 않는 한 결과가 단순한 boolean이 됩니다.
부모와 자식의 타입 좁히기
재사용 함수의 반환 타입을 node is ts.CallExpression & { expression: ts.Identifier }로 직접 적으면 두 조건을 보존할 수 있습니다. 다만 런타임에서 이미 검사한 내용을 교차 타입으로 다시 기술해야 합니다. 글에서는 is-kit의 and와 refineKey를 조합해 이 반복을 줄입니다.
and(ts.isCallExpression, refineKey("expression", ts.isIdentifier))처럼 작성하면 첫 번째 가드가 부모를 좁히고, 두 번째 가드가 좁혀진 부모의 expression 속성을 검사합니다. 결과적으로 조건문 안에서 node.expression.text에 접근할 수 있고, 같은 가드를 filter, find, 방문 함수에서도 재사용할 수 있습니다. ts.isStringLiteral이나 ts.isIdentifier처럼 Compiler API가 제공하는 가드는 그대로 사용합니다. is-kit의 역할은 개별 API를 복제하는 데 있지 않고, 기존 가드를 조합하는 데 있습니다.
선택 속성과 배열 인덱스
선택 속성은 존재 여부까지 확인해야 합니다. VariableDeclaration의 initializer를 CallExpression으로 좁히려면 refineDefinedKey("initializer", ts.isCallExpression)을 사용합니다. 이 가드는 속성이 없거나 값이 undefined일 때 실패합니다. 글은 속성 존재 여부를 타입 표기만이 아니라 런타임 동작으로 다루기 위해 refineKey와 별도 함수로 구분합니다.
배열에서도 비슷한 검사가 필요합니다. 호출 인자의 첫 항목을 확인할 때는 배열이 비어 있을 수 있으므로, refineKey("arguments", refineIndex(0, ts.isStringLiteral))로 인덱스 0의 존재와 StringLiteral 타입을 함께 확인합니다. 여러 단계를 중첩하면 함수 본문이 존재하고, 그 본문이 블록이며, 첫 문장이 ReturnStatement인 형태도 작은 가드들의 조합으로 표현할 수 있습니다.
좁히기의 범위와 TypeScript 7
각 검사는 실제로 확인한 구체적인 속성 하나나 인덱스 하나만 좁힙니다. expression을 검사했다고 해서 더 넓은 키 집합에 속한 모든 속성이 같은 조건을 만족한다고 주장하지 않습니다. 글은 검사 범위를 넘는 타입을 만들지 않도록 이 제약을 둔다고 설명합니다.
TypeScript 7.0.2 예시에서는 AST 타입과 가드를 typescript/unstable/ast에서 가져옵니다. 이때도 ast.isCallExpression 같은 가드가 필요합니다. 넓은 타입인 ast.Node는 kind 값만 확인한다고 CallExpression의 속성이 자동으로 드러나는 닫힌 판별 유니온이 아니기 때문입니다. 반대로 모든 AST 종류를 닫힌 유니온으로 표현하면 never를 활용해 분기 처리를 컴파일 시점에 검사할 수 있지만, 큰 유니온은 타입 검사기에 더 많은 작업을 요구합니다. 글은 완전성 검사와 타입 검사 성능 사이의 절충을 설명하며, typescript/unstable/ast가 아직 불안정한 API라는 점도 함께 짚습니다.
언제 재사용할까요
한 번만 쓰는 조건은 ts.isX 가드를 그대로 조합해 인라인으로 두고, 같은 AST 형태가 여러 곳에서 반복될 때 이름 있는 가드로 추출하라고 권합니다. is-kit은 AST 프레임워크나 전체 노드 구조 검증 도구가 아니며, 순회 제어와 순환 참조 감지도 맡지 않습니다. 목적은 런타임에서 확인한 사실과 TypeScript의 타입 좁히기를 재사용 가능한 작은 가드 안에서 함께 유지하는 것입니다.
원문: dev.to / 번역·요약: Trawling