visit

import { visit } from "jsr:@ende/markdown";

function

function visit<T extends Nodes["type"]>(tree: Nodes, type: T, fn: (node: Extract<Nodes, { type: T }>, index: number | null, parent: Parent | null) => void): void

Calls fn for every node of the given type in a tree, in pre-order (a parent before its children, children in document order), with the node's index in its parent's children and the parent. For the node visit() starts from, the index and parent are null.

fn may change the tree in two ways:

  • Change the node itself, including replacing or editing its children: visit() reads node.children after fn returns, so it visits the new children.
  • Replace the node, by assigning another node to parent.children[index]. The replacement isn't visited, nor are its children, so it can safely contain the original node (for example a link around inline code); visit() goes on with the next sibling.

Inserting or removing siblings (anything that changes the length of parent.children) isn't supported: nodes can then be skipped or visited twice.

tree: Nodes

The node to start from, usually the root.

type: T

The type of the nodes to call fn for.

fn: (node: Extract<Nodes, { type: T }>, index: number | null, parent: Parent | null) => void

Called with each matching node, its index in its parent's children, and its parent.

Example: Usage

import { parse } from "@ende/markdown/parse";
import { visit } from "@ende/markdown/visit";
import { assertEquals } from "@ende/assert";

const urls: string[] = [];
visit(parse("[Ende](https://ende.sh) and [docs](/docs)"), "link", node => {
  urls.push(node.url);
});
assertEquals(urls, ["https://ende.sh", "/docs"]);

Example: Linking inline code that names an API

import { parse } from "@ende/markdown/parse";
import { render } from "@ende/markdown/render";
import type { PhrasingContent } from "@ende/markdown/types";
import { visit } from "@ende/markdown/visit";
import { assertEquals } from "@ende/assert";

const api: Record<string, string> = { "parse()": "/api/parse" };
const ast = parse("Call `parse()` first.");
visit(ast, "inlineCode", (node, index, parent) => {
  const url = api[node.value];
  if (url !== undefined && parent !== null && index !== null && parent.type !== "link") {
    (parent.children as PhrasingContent[])[index] = { type: "link", url, title: null, children: [node] };
  }
});
assertEquals(render(ast), "<p>Call <a href=\"/api/parse\"><code>parse()</code></a> first.</p>\n");

Source