How to narrow Nodes in TypeScript
In most cases, when working with unist (the syntax trees used by unified) in TypeScript, one actually works with a more specific syntax tree, such as mdast (markdown) or hast (HTML). These syntax trees extend Node and add more specific types. For example, a Link is a particular and more narrow Node in mdast and Element in hast.
import type {Link} from 'mdast'
// TS checks the types, here knowing that `url` is missing:
const node: Link = {type: 'link', children: []}
(alias) interface Link
import Link
Property 'url' is missing in type '{ type: "link"; children: never[]; }' but required in type 'Link'. (2741)
(alias) interface Link
import Link
(property) Link.type: "link"
(property) Link.children: PhrasingContent[]
When you don’t know the exact input node, you likely still know you work with mdast or hast or something else. In that case, you can use the Nodes type (the s is important) from the respective package. That type is a discriminated union of all possible node types in that syntax tree. Then, regular narrowing in TypeScript works:
import type {Nodes} from 'hast'
const node = {type: 'comment', value: 'Hi!'} as Nodes
if (node.type === 'comment') {
console.log(node) // TS knows this is `Comment`.
} else {
console.log(node) // TS knows this is *not* `Comment`.
}
if ('value' in node) {
console.log(node) // TS knows this is `Comment` or `Text`.
} else {
console.log(node) // TS knows this is *not* `Comment` or `Text`.
}
(alias) type Nodes = Root | RootContent
import Nodes
Union of registered hast nodes.
To register custom hast nodes, add them to {@link RootContentMap } and other places where relevant. They will be automatically added here.
(property) Comment.type: "comment"
Node type of HTML comments in hast.
(property) Literal.value: string
(alias) type Nodes = Root | RootContent
import Nodes
Union of registered hast nodes.
To register custom hast nodes, add them to {@link RootContentMap } and other places where relevant. They will be automatically added here.
(property) type: "comment" | "doctype" | "element" | "text" | "root"
Node type of hast root.
Node type of HTML comments in hast.
Node type of HTML document types in hast.
Node type of elements.
Node type of HTML character data (plain text) in hast.
(method) console.Console.log(...data: any[]): void
(method) console.Console.log(...data: any[]): void
const node: Root | Doctype | Element | Text
(method) console.Console.log(...data: any[]): void
const node: Comment | Text
(method) console.Console.log(...data: any[]): void
const node: Root | Doctype | Element
TypeScript sometimes gets confused when using a large union of many possible nodes. So, when making plugins and utilities that accept syntax trees representing whole documents, you can use the Root type.
import type {Root} from 'mdast'
import {visit} from 'unist-util-visit'
export default function myRemarkPlugin() {
return function (tree: Root) {
visit(tree, 'heading', function (node) {
node.depth += 1
})
}
}
(alias) interface Root
import Root
Document fragment or a whole document.
Should be used as the root of a tree and must not be used as a child.
(alias) function visit<Tree extends Node, Check extends Test>(tree: Tree, check: Check, visitor: BuildVisitor<Tree, Check>, reverse?: boolean | null | undefined): undefined (+1 overload)
import visit
Visit nodes.
This algorithm performs depth-first tree traversal in preorder (NLR) or if reverse is given, in reverse preorder (NRL).
You can choose for which nodes visitor is called by passing a test. For complex tests, you should test yourself in visitor, as it will be faster and will have improved type information.
Walking the tree is an intensive task. Make use of the return values of the visitor when possible. Instead of walking a tree multiple times, walk it once, use unist-util-is to check if a node matches, and then perform different operations.
You can change the tree. See Visitor for more info.
- @overload
- @overload
- @param tree Tree to traverse.
- @param testOrVisitor
unist-util-is-compatible test (optional, omit to pass a visitor). - @param visitorOrReverse Handle each node (when test is omitted, pass
reverse). - @param maybeReverse Traverse in reverse preorder (NRL) instead of the default preorder (NLR).
- @returns Nothing.
- @template {UnistNode} Tree Node type.
- @template {Test} Check
unist-util-is-compatible test.
function myRemarkPlugin(): (tree: Root) => void
(alias) interface Root
import Root
Document fragment or a whole document.
Should be used as the root of a tree and must not be used as a child.
(alias) visit<Root, "heading">(tree: Root, check: "heading", visitor: BuildVisitor<Root, "heading">, reverse?: boolean | null | undefined): undefined (+1 overload)
import visit
Visit nodes.
This algorithm performs depth-first tree traversal in preorder (NLR) or if reverse is given, in reverse preorder (NRL).
You can choose for which nodes visitor is called by passing a test. For complex tests, you should test yourself in visitor, as it will be faster and will have improved type information.
Walking the tree is an intensive task. Make use of the return values of the visitor when possible. Instead of walking a tree multiple times, walk it once, use unist-util-is to check if a node matches, and then perform different operations.
You can change the tree. See Visitor for more info.
- @overload
- @overload
- @param tree Tree to traverse.
- @param testOrVisitor
unist-util-is-compatible test (optional, omit to pass a visitor). - @param visitorOrReverse Handle each node (when test is omitted, pass
reverse). - @param maybeReverse Traverse in reverse preorder (NRL) instead of the default preorder (NLR).
- @returns Nothing.
- @template {UnistNode} Tree Node type.
- @template {Test} Check
unist-util-is-compatible test.
(parameter) node: Heading
(parameter) node: Heading
(property) Heading.depth: number
Heading rank.
A value of 1 is said to be the highest rank and 6 the lowest.