diff --git a/__tests__/lib/mdxish/mdxishMdastToMd.test.ts b/__tests__/lib/mdxish/mdxishMdastToMd.test.ts
index f2c6f02cd..0ca26b5e0 100644
--- a/__tests__/lib/mdxish/mdxishMdastToMd.test.ts
+++ b/__tests__/lib/mdxish/mdxishMdastToMd.test.ts
@@ -1,4 +1,5 @@
-import type { Root as MdastRoot, RootContent, Table } from 'mdast';
+import type { MdxishMdastRoot } from '../../../types';
+import type { Root as MdastRoot, RootContent } from 'mdast';
import { NodeTypes } from '../../../enums';
import { mdxishMdastToMd } from '../../../lib';
@@ -144,7 +145,7 @@ describe('mdxishMdastToMd', () => {
describe('tables with flow content', () => {
it('should serialize a table with newlines in cells to JSX
', () => {
- const mdast: MdastRoot = {
+ const mdast: MdxishMdastRoot = {
type: 'root',
children: [
{
@@ -176,7 +177,7 @@ describe('mdxishMdastToMd', () => {
],
},
],
- } as Table,
+ },
],
};
@@ -216,7 +217,7 @@ describe('mdxishMdastToMd', () => {
});
it('should serialize a table with newlines in cells to JSX and separate the lines with an empty line between them', () => {
- const mdast: MdastRoot = {
+ const mdast: MdxishMdastRoot = {
type: 'root',
children: [
{
@@ -261,7 +262,7 @@ describe('mdxishMdastToMd', () => {
]
}
]
- } as Table
+ }
]
};
@@ -287,7 +288,7 @@ describe('mdxishMdastToMd', () => {
});
it('should serialize a table with list content in cells to JSX ', () => {
- const mdast: MdastRoot = {
+ const mdast: MdxishMdastRoot = {
type: 'root',
children: [
{
@@ -322,7 +323,7 @@ describe('mdxishMdastToMd', () => {
],
},
],
- } as Table,
+ },
],
};
@@ -358,7 +359,7 @@ describe('mdxishMdastToMd', () => {
});
it('should include align attribute and per-column styles when columns have alignment', () => {
- const mdast: MdastRoot = {
+ const mdast: MdxishMdastRoot = {
type: 'root',
children: [
{
@@ -392,7 +393,7 @@ describe('mdxishMdastToMd', () => {
],
},
],
- } as Table,
+ },
],
};
@@ -438,7 +439,7 @@ describe('mdxishMdastToMd', () => {
});
it('should omit align attribute when all alignments are null', () => {
- const mdast: MdastRoot = {
+ const mdast: MdxishMdastRoot = {
type: 'root',
children: [
{
@@ -470,7 +471,7 @@ describe('mdxishMdastToMd', () => {
],
},
],
- } as Table,
+ },
],
};
@@ -508,7 +509,7 @@ describe('mdxishMdastToMd', () => {
});
it('should handle a table with multiple body rows', () => {
- const mdast: MdastRoot = {
+ const mdast: MdxishMdastRoot = {
type: 'root',
children: [
{
@@ -543,7 +544,7 @@ describe('mdxishMdastToMd', () => {
],
},
],
- } as Table,
+ },
],
};
@@ -594,7 +595,7 @@ describe('mdxishMdastToMd', () => {
});
it('should handle an empty cell alongside flow content', () => {
- const mdast: MdastRoot = {
+ const mdast: MdxishMdastRoot = {
type: 'root',
children: [
{
@@ -619,7 +620,7 @@ describe('mdxishMdastToMd', () => {
],
},
],
- } as Table,
+ },
],
};
@@ -655,7 +656,7 @@ describe('mdxishMdastToMd', () => {
});
it('should handle inline formatting in phrasing-only cells as markdown', () => {
- const mdast: MdastRoot = {
+ const mdast: MdxishMdastRoot = {
type: 'root',
children: [
{
@@ -689,7 +690,7 @@ describe('mdxishMdastToMd', () => {
],
},
],
- } as Table,
+ },
],
};
@@ -702,7 +703,7 @@ describe('mdxishMdastToMd', () => {
});
it('should keep tables with raw html nodes as markdown to avoid breaking remarkMdx roundtrip', () => {
- const mdast: MdastRoot = {
+ const mdast: MdxishMdastRoot = {
type: 'root',
children: [
{
@@ -727,7 +728,7 @@ describe('mdxishMdastToMd', () => {
],
},
],
- } as Table,
+ },
],
};
@@ -740,7 +741,7 @@ describe('mdxishMdastToMd', () => {
});
it('should convert tables with code-tabs content to JSX', () => {
- const mdast: MdastRoot = {
+ const mdast: MdxishMdastRoot = {
type: 'root',
children: [
{
@@ -766,13 +767,13 @@ describe('mdxishMdastToMd', () => {
children: [
{ type: 'code', lang: 'js', meta: null, value: 'console.log("hi")' },
],
- } as unknown as MdastRoot['children'][number],
+ },
],
},
],
},
],
- } as Table,
+ },
],
};
@@ -809,7 +810,7 @@ describe('mdxishMdastToMd', () => {
});
it('should serialize a table with self-closing JSX component in cell to JSX ', () => {
- const mdast: MdastRoot = {
+ const mdast: MdxishMdastRoot = {
type: 'root',
children: [
{
@@ -839,7 +840,7 @@ describe('mdxishMdastToMd', () => {
],
},
],
- } as Table,
+ },
],
};
@@ -849,7 +850,7 @@ describe('mdxishMdastToMd', () => {
});
it('should keep table with plain HTML in cell as GFM', () => {
- const mdast: MdastRoot = {
+ const mdast: MdxishMdastRoot = {
type: 'root',
children: [
{
@@ -879,7 +880,7 @@ describe('mdxishMdastToMd', () => {
],
},
],
- } as Table,
+ },
],
};
@@ -889,7 +890,7 @@ describe('mdxishMdastToMd', () => {
});
it('should keep tables with readme-variable nodes as GFM markdown', () => {
- const mdast: MdastRoot = {
+ const mdast: MdxishMdastRoot = {
type: 'root',
children: [
{
@@ -909,7 +910,7 @@ describe('mdxishMdastToMd', () => {
type: NodeTypes.variable,
data: { hName: 'Variable', hProperties: { name: 'WHOA' } },
value: '{user.WHOA}',
- } as unknown as MdastRoot['children'][number],
+ },
],
},
],
@@ -923,7 +924,7 @@ describe('mdxishMdastToMd', () => {
],
},
],
- } as Table,
+ },
],
};
@@ -933,7 +934,7 @@ describe('mdxishMdastToMd', () => {
});
it('should keep tables with readme-variable alongside text as GFM markdown', () => {
- const mdast: MdastRoot = {
+ const mdast: MdxishMdastRoot = {
type: 'root',
children: [
{
@@ -954,7 +955,7 @@ describe('mdxishMdastToMd', () => {
type: NodeTypes.variable,
data: { hName: 'Variable', hProperties: { name: 'name' } },
value: '{user.name}',
- } as unknown as MdastRoot['children'][number],
+ },
],
},
],
@@ -970,7 +971,7 @@ describe('mdxishMdastToMd', () => {
],
},
],
- } as Table,
+ },
],
};
@@ -980,7 +981,7 @@ describe('mdxishMdastToMd', () => {
});
it('should keep phrasing-only tables as markdown tables', () => {
- const mdast: MdastRoot = {
+ const mdast: MdxishMdastRoot = {
type: 'root',
children: [
{
@@ -1002,7 +1003,7 @@ describe('mdxishMdastToMd', () => {
],
},
],
- } as Table,
+ },
],
};
diff --git a/processor/transform/mdxish/mdxish-jsx-to-mdast.ts b/processor/transform/mdxish/mdxish-jsx-to-mdast.ts
index ec630b06a..afbe6c312 100644
--- a/processor/transform/mdxish/mdxish-jsx-to-mdast.ts
+++ b/processor/transform/mdxish/mdxish-jsx-to-mdast.ts
@@ -1,7 +1,7 @@
import type { MagicBlockEmbed, MagicBlockImage } from './magic-blocks/types';
-import type { FigureNode } from './types';
+import type { FigureNode, MdxishTable, MdxishTableCell, MdxishTableRow } from './types';
import type { Anchor, Callout, EmbedBlock, ImageAlign, ImageBlock, Recipe } from '../../../types';
-import type { Html, Node, Paragraph, Parent, PhrasingContent, RootContent, Table, TableCell, TableRow } from 'mdast';
+import type { Html, Node, Paragraph, Parent, PhrasingContent, RootContent, Table } from 'mdast';
import type { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx-jsx';
import type { Plugin } from 'unified';
@@ -531,7 +531,7 @@ const isTableCell = (node: Node): node is MdxJsxFlowElement & { name: 'td' | 'th
* Converts a JSX element to an MDAST table node with alignment.
* Returns null for header-less tables since MDAST always promotes the first row to .
*/
-const transformTable = (jsx: MdxJsxFlowElement): Table | null => {
+const transformTable = (jsx: MdxJsxFlowElement): MdxishTable | null => {
let hasThead = false;
visit(jsx as Node, isMDXElement, (child: MdxJsxFlowElement | MdxJsxTextElement) => {
if (child.name === 'thead') hasThead = true;
@@ -542,7 +542,7 @@ const transformTable = (jsx: MdxJsxFlowElement): Table | null => {
const { align: alignAttr } = getAttrs>(jsx);
const align = Array.isArray(alignAttr) ? alignAttr : null;
- const rows: TableRow[] = [];
+ const rows: MdxishTableRow[] = [];
visit(jsx as Node, isMDXElement, (child: MdxJsxFlowElement | MdxJsxTextElement) => {
if (child.name !== 'thead' && child.name !== 'tbody') return;
@@ -550,7 +550,7 @@ const transformTable = (jsx: MdxJsxFlowElement): Table | null => {
visit(child as Node, isMDXElement, (row: MdxJsxFlowElement | MdxJsxTextElement) => {
if (row.name !== 'tr') return;
- const cells: TableCell[] = [];
+ const cells: MdxishTableCell[] = [];
visit(row as Node, isTableCell, (cell: MdxJsxFlowElement & { name: 'td' | 'th' }) => {
const parsedChildren = unwrapSoleParagraph(cell.children as Node[]);
@@ -559,7 +559,7 @@ const transformTable = (jsx: MdxJsxFlowElement): Table | null => {
type: 'tableCell',
children: parsedChildren,
position: cell.position,
- } as TableCell);
+ });
});
rows.push({
@@ -576,12 +576,14 @@ const transformTable = (jsx: MdxJsxFlowElement): Table | null => {
? align.slice(0, columnCount).concat(Array.from({ length: Math.max(0, columnCount - align.length) }, () => null))
: Array.from({ length: columnCount }, () => null);
- return {
+ const table: MdxishTable = {
type: 'table',
align: alignArray,
position: jsx.position,
children: rows,
};
+
+ return table;
};
/**
diff --git a/processor/transform/mdxish/tables/mdxish-tables-to-jsx.ts b/processor/transform/mdxish/tables/mdxish-tables-to-jsx.ts
index fbb42a3df..4f833141f 100644
--- a/processor/transform/mdxish/tables/mdxish-tables-to-jsx.ts
+++ b/processor/transform/mdxish/tables/mdxish-tables-to-jsx.ts
@@ -1,4 +1,5 @@
-import type { Literal, Node, Table, TableCell } from 'mdast';
+import type { MdxishTable, MdxishTableCell } from '../types';
+import type { Literal, Node } from 'mdast';
import type { Transform } from 'mdast-util-from-markdown';
import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
@@ -38,10 +39,11 @@ const mdxishTablesToJsx = (): Transform => tree => {
visit(
tree,
(node: Node) => ['table', 'tableau'].includes(node.type),
- (table: Table, index, parent) => {
+ (tableNode, index, parent) => {
+ const table = tableNode as MdxishTable;
let hasFlowContent = false;
- visit(table, isTableCell, (cell: TableCell) => {
+ visit(table, isTableCell, (cell: MdxishTableCell) => {
if (hasFlowContent || cell.children.length === 0) return;
visit(cell, 'break', (_, breakIndex, breakParent) => {
@@ -61,7 +63,7 @@ const mdxishTablesToJsx = (): Transform => tree => {
// serialization instead of GFM. `phrasing()` from mdast-util-phrasing returns
// true for inline node types (text, emphasis, strong, link, etc.) which are
// safe to keep in GFM cells.
- const hasFlowChild = (cell.children as Node[]).some(child => {
+ const hasFlowChild = cell.children.some(child => {
if (child.type === 'paragraph' || child.type === 'plain' || child.type === 'escape') return false;
if (child.type === NodeTypes.variable) return false;
if (phrasing(child as Parameters[0])) return false;
diff --git a/processor/transform/mdxish/tables/utils.ts b/processor/transform/mdxish/tables/utils.ts
index 48c647b6f..82e10af66 100644
--- a/processor/transform/mdxish/tables/utils.ts
+++ b/processor/transform/mdxish/tables/utils.ts
@@ -7,13 +7,13 @@ import type { Node } from 'mdast';
* When there are multiple paragraphs, leave them intact — they represent distinct lines
* of content that need to be preserved for JSX `` serialization.
*/
-export const unwrapSoleParagraph = (children: Node[]): Node[] => {
+export const unwrapSoleParagraph = (children: Node[]) => {
const paragraphCount = children.filter(c => c.type === 'paragraph').length;
if (paragraphCount !== 1) return children;
return children.flatMap(child => {
if (child.type === 'paragraph' && 'children' in child && Array.isArray(child.children)) {
- return child.children as Node[];
+ return child.children;
}
return [child];
});
diff --git a/processor/transform/mdxish/types.ts b/processor/transform/mdxish/types.ts
index 8bd03cbc1..64b833d35 100644
--- a/processor/transform/mdxish/types.ts
+++ b/processor/transform/mdxish/types.ts
@@ -1,4 +1,15 @@
-import type { RootContent } from 'mdast';
+import type {
+ Code,
+ Html,
+ List,
+ Paragraph,
+ PhrasingContent,
+ RootContent,
+ Table,
+ TableCell,
+ TableRow,
+} from 'mdast';
+import type { CodeTabs } from 'types';
import type { Position } from 'unist';
/**
@@ -14,3 +25,36 @@ export interface FigureNode {
position?: Position;
type: 'figure';
}
+
+/**
+ * Table cell content for the mdxish pipeline. mdast’s `TableCell` is typed as
+ * `PhrasingContent[]` only, but GFM / remark output and our transforms can
+ * place block-level nodes (e.g. paragraphs, lists, code) inside cells.
+ *
+ * If there's more node types that should be allowed, add them here.
+ */
+export type MdxishTableCellContent =
+ | Code
+ | CodeTabs
+ | Html
+ | List
+ | Paragraph
+ | PhrasingContent;
+
+/**
+ * A `tableCell` that allows the same content the serializer accepts at runtime.
+ */
+export interface MdxishTableCell extends Omit {
+ children: MdxishTableCellContent[];
+}
+
+export interface MdxishTableRow extends Omit {
+ children: MdxishTableCell[];
+}
+
+export interface MdxishTable extends Omit {
+ children: MdxishTableRow[];
+}
+
+/** `Root` content when `table` is allowed to use the wider cell shape. */
+export type MdxishMdastRootContent = Exclude | MdxishTable;
diff --git a/types.d.ts b/types.d.ts
index ac691225d..13ef96417 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -17,8 +17,17 @@ import type {
} from 'mdast';
import type { MdxJsxFlowElementHast } from 'mdast-util-mdx-jsx';
import type { MDXModule } from 'mdx/types';
+import type { MdxishMdastRootContent } from 'processor/transform/mdxish/types';
import type { Position } from 'unist';
+/**
+ * Extension of MDAST's Root that has some custom extended content types
+ * E.g. Tables whose cells carry flow content beyond mdast’s built-in `TableCell` typing.
+ */
+export interface MdxishMdastRoot extends Omit {
+ children: MdxishMdastRootContent[];
+}
+
/**
* Custom hast node emitted by `mdxJsxElementHandler` for every MDX JSX element.
* Registered with hast below so the unified/unist tooling (remark-rehype handlers,