
Using is-kit with the TypeScript Compiler API
Compose ts.isX refinements directly, lift child-node checks onto their parents, and preserve precise AST narrowing without TypeScript-specific adapters.
The quick answer
Combine a Compiler API node guard with refineKey when a required child node must also be narrowed.
import * as ts from 'typescript';import { and, refineKey } from 'is-kit';const isCallWithIdentifierExpression = and(ts.isCallExpression,refineKey('expression', ts.isIdentifier),);declare const node: ts.Node;if (isCallWithIdentifierExpression(node)) {node.expression.text;// node: ts.CallExpression & { expression: ts.Identifier }}
ts.isCallExpression narrows the node. refineKey then applies ts.isIdentifier to its required expression property and carries that fact back to the parent type.
Check the child once at runtime, then preserve the same checked fact on the parent in TypeScript control flow.
Compose Compiler API refinements directly
Compiler API guards accept ts.Node, not arbitrary unknown. is-kit logic combinators preserve that known input domain, so wrappers and assertion casts are unnecessary.
import * as ts from 'typescript';import { oneOf, or } from 'is-kit';const isStringLike = or(ts.isStringLiteral,ts.isNoSubstitutionTemplateLiteral,);const isNamedDeclaration = oneOf(ts.isClassDeclaration,ts.isFunctionDeclaration,ts.isVariableDeclaration,);declare const nodes: readonly ts.Node[];const strings = nodes.filter(isStringLike);// (ts.StringLiteral | ts.NoSubstitutionTemplateLiteral)[]const declarations = nodes.filter(isNamedDeclaration);// (ts.ClassDeclaration | ts.FunctionDeclaration |// ts.VariableDeclaration)[]
The resulting functions remain ordinary type predicates. Reuse them in branches, visitors, filter, find, or other APIs that understand TypeScript predicates.
Reuse a refined node in find and visitors
A named guard can move between collection methods and AST traversal without losing its refined child type. This is especially useful for repeated JSX attribute checks.
import * as ts from 'typescript';import { and, refineKey } from 'is-kit';const isIdentifierNamedJsxAttribute = and(ts.isJsxAttribute,refineKey('name', ts.isIdentifier),);declare const attributes: readonly ts.JsxAttributeLike[];const attribute = attributes.find(isIdentifierNamedJsxAttribute);// (ts.JsxAttribute & { name: ts.Identifier }) | undefinedfunction visit(node: ts.Node): void {if (isIdentifierNamedJsxAttribute(node)) {node.name.text;}ts.forEachChild(node, visit);}
The same predicate narrows the result returned by find and the current node inside the visitor branch.
Choose the child contract explicitly
| Helper | Child contract | Failure before refinement |
|---|---|---|
| refineKey | Required property | None; the property belongs to the input contract |
| refineDefinedKey | Optional property that must be defined | Missing or undefined value |
| refineIndex | One readonly-array element | Out of bounds, sparse hole, or undefined value |
These are separate APIs because absence is runtime behavior, not only a type annotation. The helper name tells readers whether missing data is outside the contract or should make the predicate return false.
Require and refine an optional child
Compiler API declarations commonly expose optional children such as a variable initializer or method body. Use refineDefinedKey when absence should fail safely.
import * as ts from 'typescript';import { refineDefinedKey } from 'is-kit';const hasCallInitializer = refineDefinedKey('initializer',ts.isCallExpression,);declare const declaration: ts.VariableDeclaration;if (hasCallInitializer(declaration)) {declaration.initializer.expression;// initializer is present and is ts.CallExpression}
Missing and explicitly undefined initializers return false. They are never passed to the narrow-domain Compiler API refinement.
Refine one array element
Node arrays can be empty at runtime even when unchecked indexed access makes arguments[0] look defined. Use refineIndex to make the element check explicit.
import * as ts from 'typescript';import { and, refineIndex, refineKey } from 'is-kit';const isCallWithStringFirstArgument = and(ts.isCallExpression,refineKey('arguments', refineIndex(0, ts.isStringLiteral)),);declare const node: ts.Node;if (isCallWithStringFirstArgument(node)) {node.arguments[0].text;// arguments[0] is present and is ts.StringLiteral}
Index 0 is narrowed only after the value exists and the supplied node refinement succeeds.
Compose nested child checks
Build deeper checks one property or index at a time. A path DSL is not required to preserve each intermediate parent type.
import * as ts from 'typescript';import { and, refineDefinedKey, refineIndex, refineKey } from 'is-kit';const isBlockStartingWithReturn = and(ts.isBlock,refineKey('statements', refineIndex(0, ts.isReturnStatement)),);const hasBodyStartingWithReturn = refineDefinedKey('body',isBlockStartingWithReturn,);declare const method: ts.MethodDeclaration;if (hasBodyStartingWithReturn(method)) {method.body.statements[0].expression;// body is present, is a block, and starts with a return statement}
This composition remains safe with exactOptionalPropertyTypes and noUncheckedIndexedAccess enabled.
Use one concrete key or index
The key and index helpers accept one concrete runtime location. Broad keys, unions, template-literal patterns, and branded multi-value key domains are rejected at compile time.
One successful lookup proves one property. It cannot soundly claim that every property in a wider key domain passed the refinement.
Keep one-off checks inline
Composition pays off when a predicate is named, reused, nested, or passed to another API. A single local condition may remain clearer in the Compiler API's native style.
import * as ts from 'typescript';declare const node: ts.Node;declare function visit(node: ts.Node): void;// Keep a one-off local branch inline when no reusable guard is needed.if (ts.isReturnStatement(node) && node.expression) {visit(node.expression);}
Do not extract every boolean expression. Prefer is-kit when the guard becomes part of the program's reusable type vocabulary.
What the integration does not add
- is-kit does not wrap individual Compiler API functions.
- It does not depend on TypeScript at runtime or require a TypeScript peer dependency.
- It does not validate complete AST node shapes.
- It does not detect cycles or control AST traversal.
- It does not replace a clear one-off inline condition.
TypeScript 7 Compiler API compatibility
TypeScript 7 can type-check is-kit declarations, but TypeScript 7.0 does not ship the legacy JavaScript Compiler API used by the examples in this guide. API-based tooling should keep the TypeScript 6 API available through the official @typescript/typescript6 compatibility package.
This is a TypeScript 7 platform transition rather than an is-kit runtime limitation. is-kit itself has no TypeScript runtime or peer dependency.
Summary
- Compose
ts.isXfunctions directly. - Use
refineKeyfor required child properties. - Use
refineDefinedKeywhen an optional child must exist. - Use
refineIndexfor one defined array element. - Keep local one-off checks inline when extraction adds no value.
See the key API reference for the complete property contracts and the logic API reference for known-domain composition.