nlcst-to-string
nlcst-to-string is a one-function serializer for nlcst natural-language syntax trees. Pass one nlcst node or an array of nodes to the named toString export and it concatenates their plain-text values recursively. A node's own value takes priority over its children, while parent nodes without a value are reduced by joining each child with no added separator. It does not parse prose, print syntax, preserve source ranges, or understand language; it is the small text-extraction step inside unified and retext-style AST work.
Install it when you already operate on nlcst and want the conventional, unsurprising text helper. Do not install it to parse language or serialize arbitrary unist trees; its entire contract is value-first recursive concatenation.
Use it if
- You already have nlcst nodes from retext or another parser and need their plain-text content
- You are writing an nlcst transform or lint rule and repeatedly need the text represented by a subtree
- You need the same helper to accept one node or a contiguous array of sibling nodes
- You want a tiny typed utility that follows unified collective conventions and has no behavioral configuration
- You need to turn source text into an AST: this package only serializes an existing nlcst node and provides no parser
- You use CommonJS or Node.js older than 16: version 4 is ESM-only and the README sets Node 16 as its compatibility floor
- You expect a pretty-printer to insert spaces, punctuation, or line breaks: it joins child output with an empty separator and relies on the tree to contain WhiteSpaceNode and PunctuationNode values
- You need original source slices or source maps after transforms: start and end positions are ignored, so output comes only from value fields and child order
- Your tree is mdast or hast rather than nlcst: their node semantics differ, and dedicated mdast-util-to-string or hast-util-to-text packages are a better fit
Setup reality
Installation is simple, but this is not a standalone natural-language tool. You need an existing nlcst producer, commonly a retext parser or a package that emits @types/nlcst-compatible nodes. Version 4 is ESM-only, uses a named toString export, and documents Node.js 16 or newer; require('nlcst-to-string') is not the supported interface and there is no default export. Type declarations are bundled and the only package dependency is @types/nlcst, so there is no native build, credential, config file, or runtime service. Output fidelity depends completely on tree shape. TextNode, WhiteSpaceNode, PunctuationNode, SymbolNode, and other literal nodes must carry their exact value. Parent nodes contribute no separators or formatting of their own. If a node has both value and children, value wins and the children are ignored. If a typed node has neither, it contributes an empty string. Passing null, undefined, a primitive, or an object without a type throws an Expected node error, while an empty array is valid and returns an empty string. Positions and other metadata do not affect output. This behavior is ideal for reading plain text from a subtree, but it is not round-trip serialization once a transform removes whitespace nodes, normalizes punctuation, or stores significant content outside value and children.
Patterns
Read text from a word subtreeserialize-word
import { toString } from 'nlcst-to-string';
const word = {
type: 'WordNode',
children: [
{ type: 'TextNode', value: 'AT' },
{ type: 'SymbolNode', value: '&' },
{ type: 'TextNode', value: 'T' },
],
};
console.log(toString(word)); // AT&TThe parent adds nothing; output is the exact concatenation of child values.
Serialize adjacent sibling nodesserialize-node-list
const nodes = [
{ type: 'WordNode', children: [{ type: 'TextNode', value: 'hello' }] },
{ type: 'WhiteSpaceNode', value: ' ' },
{ type: 'WordNode', children: [{ type: 'TextNode', value: 'there' }] },
];
console.log(toString(nodes)); // hello thereArrays are accepted directly, but the space appears only because a WhiteSpaceNode is present.
Read a complete sentence nodeserialize-sentence
const sentence = {
type: 'SentenceNode',
children: [
{ type: 'WordNode', children: [{ type: 'TextNode', value: 'Hello' }] },
{ type: 'PunctuationNode', value: ',' },
{ type: 'WhiteSpaceNode', value: ' ' },
{ type: 'WordNode', children: [{ type: 'TextNode', value: 'world' }] },
{ type: 'PunctuationNode', value: '!' },
],
};
toString(sentence); // Hello, world!Punctuation and spacing are ordinary valued nodes, not formatting inferred by the serializer.
Inspect subtree text in a lint ruleread-rule-target
function checkWord(node, file) {
const text = toString(node);
if (text.toLowerCase() === 'utilize') {
file.message('Prefer use', { ancestors: [node] });
}
}toString does not normalize case or Unicode; apply any comparison policy after extracting the exact node text.
Compare text before and after an AST transformread-before-transform
const before = toString(tree);
normalizeQuotes(tree);
const after = toString(tree);
if (before !== after) {
console.log({ before, after });
}Only changes to value fields, child membership, or child order can affect output; position changes do not.
Understand value precedencevalue-precedes-children
const unusualNode = {
type: 'TextNode',
value: 'preferred',
children: [{ type: 'TextNode', value: 'ignored' }],
};
console.log(toString(unusualNode)); // preferredWhen value exists on a node, the function returns it immediately and never visits children.
Handle an empty selectionserialize-empty-list
const selected = [];
const text = toString(selected);
console.log(text === ''); // trueAn empty node array is valid and joins to an empty string; null and undefined are invalid.
Validate unknown input before serializingguard-untrusted-input
function textFromUnknown(value: unknown): string | undefined {
if (Array.isArray(value)) return toString(value);
if (value && typeof value === 'object' && 'type' in value) {
return toString(value);
}
return undefined;
}The package throws for primitives, null, undefined, and non-array objects without a type property.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| mdast-util-to-string | npm | Use it to extract text from Markdown mdast nodes, including its mdast-specific fields and options |
| hast-util-to-text | npm | Use it for HTML hast trees when browser-like rendered-text rules matter |
| unist-util-visit | npm | Use it when you need to inspect or collect selected nodes instead of flattening an entire nlcst subtree |