summaryrefslogtreecommitdiff
path: root/packages/markdown/remark/src/remark-mark-and-unravel.ts
blob: 294d4211d7b0c3e6836e75bd47782bed7c143df1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//  https://github.com/mdx-js/mdx/blob/main/packages/mdx/lib/plugin/remark-mark-and-unravel.js
/**
 * @typedef {import('mdast').Root} Root
 * @typedef {import('mdast').Content} Content
 * @typedef {Root|Content} Node
 * @typedef {Extract<Node, import('unist').Parent>} Parent
 *
 * @typedef {import('remark-mdx')} DoNotTouchAsThisImportItIncludesMdxInTree
 */

import { visit } from 'unist-util-visit';

/**
 * A tiny plugin that unravels `<p><h1>x</h1></p>` but also
 * `<p><Component /></p>` (so it has no knowledge of “HTML”).
 * It also marks JSX as being explicitly JSX, so when a user passes a `h1`
 * component, it is used for `# heading` but not for `<h1>heading</h1>`.
 *
 * @type {import('unified').Plugin<Array<void>, Root>}
 */
export default function remarkMarkAndUnravel() {
	return (tree: any) => {
		visit(tree, (node, index, parent_) => {
			const parent = /** @type {Parent} */ parent_;
			let offset = -1;
			let all = true;
			/** @type {boolean|undefined} */
			let oneOrMore;

			if (parent && typeof index === 'number' && node.type === 'paragraph') {
				const children = node.children;

				while (++offset < children.length) {
					const child = children[offset];

					if (child.type === 'mdxJsxTextElement' || child.type === 'mdxTextExpression') {
						oneOrMore = true;
					} else if (child.type === 'text' && /^[\t\r\n ]+$/.test(String(child.value))) {
						// Empty.
					} else {
						all = false;
						break;
					}
				}

				if (all && oneOrMore) {
					offset = -1;

					while (++offset < children.length) {
						const child = children[offset];

						if (child.type === 'mdxJsxTextElement') {
							child.type = 'mdxJsxFlowElement';
						}

						if (child.type === 'mdxTextExpression') {
							child.type = 'mdxFlowExpression';
						}
					}

					parent.children.splice(index, 1, ...children);
					return index;
				}
			}

			if (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') {
				const data = node.data || (node.data = {});
				data._mdxExplicitJsx = true;
			}
		});
	};
}