Search
Search documentation
Syntax trees
Updated Nov 1, 2026
A pipeline passes one of three trees between its stages, each a unist tree as the unified ecosystem defines it:
- mdast — Markdown, from
md.parse - hast — HTML, from
html.parseandmd.toHtml - nlcst — natural language, from
nl.parse,md.toNaturalLanguageandhtml.toNaturalLanguage
parse returns the tree as plain JSON, node for node what the unified ecosystem produces,
and run and stringify take it back. Every node has a type; parents have children;
literals have a value. The parser types it: after md.parse(), html.parse() or
nl.parse(), parse returns the Root of @types/mdast,
@types/hast or @types/nlcst, which textlane depends on, and run and stringify take any
unist node, so trees pass to and from unified utilities without a cast.
textlane exports those types as the unist, mdast, hast, nlcst and estree type
namespaces, so import type { mdast } from "textlane" names mdast.Heading without adding
@types/mdast yourself. They also hold the nodes textlane's syntax extensions make, added to
the content unions as the extensions' unified utilities add them: mdast.MdxJsxFlowElement,
mdast.ContainerDirective, mdast.InlineMath, mdast.DefListNode, mdast.Toml and their
hast counterparts, hast.Raw and data.hName. The declarations are those utilities' own, so
installing mdast-util-mdx or mdast-util-directive beside textlane does not conflict.
import { textlane } from "textlane"
import * as md from "textlane/md"
const tree = textlane().use(md.parse()).parse("# Hi *there*")
const heading = tree.children[0]
heading.type
// heading
heading.depth
// 1
heading.children[1].type
// emphasisPositions
Each node parsed from text carries a position: its start and end points, each with a
1-indexed line and column and a 0-indexed offset.
import { textlane } from "textlane"
import * as md from "textlane/md"
const tree = textlane().use(md.parse()).parse("# Hi *there*")
tree.children[0].children[1].position.start
// {"line":1,"column":6,"offset":5}hast and nlcst
hast elements carry a tagName and properties, named as the DOM names them; nlcst nests
paragraphs, sentences and words:
import { textlane } from "textlane"
import * as html from "textlane/html"
import * as nl from "textlane/nl"
const element = textlane()
.use(html.parse({ fragment: true }))
.parse('<p class="note">Hi</p>').children[0]
element.properties
// {"className":["note"]}
const sentence = textlane().use(nl.parse()).parse("Hi there.").children[0].children[0]
sentence.children.map(node => node.type).join(" ")
// WordNode WhiteSpaceNode WordNode PunctuationNodeA tree changed in JavaScript goes back through run or stringify, which read it as the
kind of tree the next stage takes.
Created with ❤ and Livemark