unified

Learn/Recipe/Tree traversal with TypeScript

Traversing trees with TypeScript

📓 Please read the introduction to tree traversal in JavaScript before reading this section.

A frequent task when working with unified is to traverse trees to find certain nodes and then doing something with them (often validating or transforming them). Several type safe unified utilities can be used to help with this.

unist-util-visit

unist-util-visit takes a syntax tree, a Test, and a callback. The callback is called for each node in the tree that passes Test.

For example if we want to increasing the heading level of all headings in a markdown document:

import {remark} from 'remark'
import type {Root} from 'mdast'
import {visit} from 'unist-util-visit'

const markdownFile = await remark()
  .use(() => (mdast: Root) => {
    visit(
      mdast,
      // Check that the Node is a heading:
      'heading',
      (node) => {
        // The types know `node` is a heading.
        node.depth += 1
      }
    )
  })
  .process('## Hello, *World*!')

console.log(markdownFile.toString())

Or if we want to make all ordered lists in a markdown document unordered:

import {remark} from 'remark'
import type {Root} from 'mdast'
import {visit} from 'unist-util-visit'

const markdownFile = await remark()
  .use(() => (mdast: Root) => {
    visit(
      mdast,
      // Check that the Node is a list:
      'list',
      (node) => {
        if (node.ordered) {
          // The types know `node` is an ordered list.
          node.ordered = false
        }
      }
    )
  })
  .process('1. list item')

console.log(markdownFile.toString())

unist-util-visit-parents

Sometimes it’s needed to know the ancestors of a node (all its parents). unist-util-visit-parents is like unist-util-visit but includes a list of all parent nodes.

For example if we want to check if all markdown ListItem are inside a List we could:

import remark from 'remark'
import type {Root, ListItem} from 'mdast'
import {visitParents} from 'unist-util-visit-parents'

remark()
  .use(() => (mdast: Root) => {
    visitParents(mdast, 'listItem', (listItem, parents) => {
      // The types know `listItem` is a list item, and that `parents` are mdast
      // parents.
      if (!parents.some((parent) => parent.type === 'list')) {
        console.warn('listItem is outside a list')
      }
    })
  })
  .process('1. list item')

unist-util-select

Sometimes CSS selectors are easier to read than several (nested) if/else statements. unist-util-select lets you do that. For example if we want to find all Paragraphs that are somewhere in a Blockquote, we could:

import remark from 'remark'
import type {Root} from 'mdast'
import {selectAll} from 'unist-util-select'

remark()
  .use(() => (mdast: Root) => {
    const matches = selectAll('blockquote paragraph', mdast)
    console.log(matches)
  })
  .process('1. list item')