Custom

AST API

Traverse and modify the ComarkTree AST after parsing using the visit() utility.

The comark/utils module exports visit — a tree traversal utility for transforming AST nodes inside a post hook.

visit(tree, checker, visitor)

Traverses all nodes in a ComarkTree, calling visitor on every node that passes checker.

Parameters:

  • tree - The ComarkTree to traverse
  • checker - A predicate (node: ComarkNode) => boolean — return true to visit a node
  • visitor - A transform (node: ComarkNode) => ComarkNode | false | void — see ComarkNode

Returns: void — mutations are applied in place

The visitor return value controls what happens to the node:

Return valueEffect
voidLeave the node unchanged
ComarkNodeReplace the node with the returned value
falseRemove the node from the tree

Example:

import { defineComarkPlugin } from 'comark/parse'
import { visit } from 'comark/utils'

export default defineComarkPlugin(() => ({
  name: 'word-count',
  post(state) {
    let count = 0
    visit(state.tree,
      (node) => typeof node === 'string',
      (node) => { count += (node as string).split(/\s+/).filter(Boolean).length }
    )
    state.tree.meta.wordCount = count
  },
}))

Use Cases

Replacing Nodes

Return a new node to replace the matched one. The following example wraps bare URL text nodes in anchor elements:

import type { ComarkNode } from 'comark'
import { defineComarkPlugin } from 'comark/parse'
import { visit } from 'comark/utils'

export default defineComarkPlugin(() => ({
  name: 'auto-link',
  post(state) {
    const urlPattern = /https?:\/\/[^\s]+/g

    visit(state.tree,
      (node) => typeof node === 'string',
      (node) => {
        const text = node as string
        if (!urlPattern.test(text)) return
        return ['a', { href: text }, text] as ComarkNode
      }
    )
  },
}))

Mutating Attributes

Return void and mutate the node in place when you only need to update attributes:

import { defineComarkPlugin } from 'comark/parse'
import { visit } from 'comark/utils'

export default defineComarkPlugin(() => ({
  name: 'styled-tables',
  post(state) {
    visit(state.tree,
      (node) => Array.isArray(node) && node[0] === 'table',
      (node) => {
        const el = node as [string, Record<string, any>, ...any[]]
        el[1].class = [el[1].class, 'styled-table'].filter(Boolean).join(' ')
      }
    )
  },
}))

Removing Nodes

Return false to remove matched nodes entirely:

import { defineComarkPlugin } from 'comark/parse'
import { visit } from 'comark/utils'

export default defineComarkPlugin(() => ({
  name: 'strip-html-comments',
  post(state) {
    visit(state.tree,
      (node) => Array.isArray(node) && node[0] === null,
      () => false
    )
  },
}))