From 001a8174cd954cf844bfbc066021fcd7943a1413 Mon Sep 17 00:00:00 2001 From: eagletrhost Date: Thu, 23 Apr 2026 22:54:27 +1000 Subject: [PATCH 1/7] fix: dont unwrap multiline paragraphs in table cells to retain separate lines --- __tests__/transformers/mdxish-tables.test.ts | 112 ++++++++++++++++++ .../transform/mdxish/mdxish-jsx-to-mdast.ts | 18 ++- processor/transform/mdxish/mdxish-tables.ts | 19 ++- 3 files changed, 137 insertions(+), 12 deletions(-) create mode 100644 __tests__/transformers/mdxish-tables.test.ts diff --git a/__tests__/transformers/mdxish-tables.test.ts b/__tests__/transformers/mdxish-tables.test.ts new file mode 100644 index 000000000..2d29f68a1 --- /dev/null +++ b/__tests__/transformers/mdxish-tables.test.ts @@ -0,0 +1,112 @@ +import type { Root } from 'mdast'; + +import { mdxishAstProcessor } from '../../lib/mdxish'; + +const astProcessor = (md: string): Root => { + const { processor, parserReadyContent } = mdxishAstProcessor(md); + return processor.runSync(processor.parse(parserReadyContent)) as Root; +}; + +describe('mdxish tables transformation', () => { + describe('given \\n in table cell', () => { + it('should split lines in a magic block table cell into paragraphs', () => { + const md = `[block:parameters] + { + "data": { + "h-0": "Item", + "0-0": "Line 1 \\n\\nLine 2 \\n\\nLine 3" + }, + "cols": 1, + "rows": 1, + "align": ["left"] + } + [/block]`; + const ast = astProcessor(md); + + expect(ast).toMatchObject({ + type: 'root', + children: [ + { + type: 'table', + children: [ + { + type: 'tableRow', + children: [ + { + type: 'tableHead', + children: [{ type: 'text', value: 'Item' }], + }, + ], + }, + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [ + { type: 'paragraph', children: [{ type: 'text', value: 'Line 1' }] }, + { type: 'paragraph', children: [{ type: 'text', value: 'Line 2' }] }, + { type: 'paragraph', children: [{ type: 'text', value: 'Line 3' }] }, + ], + }, + ], + }, + ], + }, + ], + }); + }); + + it('should split lines in a JSX cell into paragraphs', () => { + const md = `
+ + + + + + + + + + + +
+
+ Line 1 + + Line 3 + + Line 5 +
`; + const ast = astProcessor(md); + + expect(ast).toMatchObject({ + type: 'root', + children: [ + { + type: 'table', + children: [ + { + type: 'tableRow', + children: [{ type: 'tableCell', children: [] }], + }, + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [ + { type: 'paragraph', children: [{ type: 'text', value: 'Line 1' }] }, + { type: 'paragraph', children: [{ type: 'text', value: 'Line 3' }] }, + { type: 'paragraph', children: [{ type: 'text', value: 'Line 5' }] }, + ], + }, + ], + }, + ], + }, + ], + }); + }); + }); +}); diff --git a/processor/transform/mdxish/mdxish-jsx-to-mdast.ts b/processor/transform/mdxish/mdxish-jsx-to-mdast.ts index 8272f1ad5..f13158305 100644 --- a/processor/transform/mdxish/mdxish-jsx-to-mdast.ts +++ b/processor/transform/mdxish/mdxish-jsx-to-mdast.ts @@ -551,12 +551,18 @@ const transformTable = (jsx: MdxJsxFlowElement): Table | null => { const cells: TableCell[] = []; visit(row as Node, isTableCell, (cell: MdxJsxFlowElement & { name: 'td' | 'th' }) => { - const parsedChildren = (cell.children as Node[]).flatMap(parsedNode => { - if (parsedNode.type === 'paragraph' && 'children' in parsedNode && parsedNode.children) { - return parsedNode.children; - } - return [parsedNode]; - }); + // Unwrap a paragraph child when it is the cell's sole paragraph as it might affect rendering + // However if there are multiple paragraphs, they are legitimate paragraphs of separate lines of content + const paragraphCount = cell.children.filter(c => c.type === 'paragraph').length; + const parsedChildren = + paragraphCount === 1 + ? cell.children.flatMap((parsedNode: Node) => { + if (parsedNode.type === 'paragraph' && 'children' in parsedNode && parsedNode.children) { + return parsedNode.children; + } + return [parsedNode]; + }) + : cell.children; cells.push({ type: 'tableCell', diff --git a/processor/transform/mdxish/mdxish-tables.ts b/processor/transform/mdxish/mdxish-tables.ts index 29dc701b8..bc230e437 100644 --- a/processor/transform/mdxish/mdxish-tables.ts +++ b/processor/transform/mdxish/mdxish-tables.ts @@ -166,12 +166,18 @@ const processTableNode = ( const rowChildren: TableCell[] = []; visit(row as Node, isTableCell, ({ name, children: cellChildren, position: cellPosition }: MdxJsxTableCell) => { - const parsedChildren = (cellChildren as Node[]).flatMap(parsedNode => { - if (parsedNode.type === 'paragraph' && 'children' in parsedNode && parsedNode.children) { - return parsedNode.children; - } - return [parsedNode]; - }); + // Unwrap a paragraph child when it is the cell's sole paragraph as it might affect rendering + // However if there are multiple paragraphs, they are legitimate paragraphs of separate lines of content + const paragraphCount = (cellChildren as Node[]).filter(c => c.type === 'paragraph').length; + const parsedChildren = + paragraphCount === 1 + ? (cellChildren as Node[]).flatMap(parsedNode => { + if (parsedNode.type === 'paragraph' && 'children' in parsedNode && parsedNode.children) { + return parsedNode.children; + } + return [parsedNode]; + }) + : (cellChildren as Node[]); rowChildren.push({ type: tableTypes[name], @@ -251,6 +257,7 @@ const mdxishTables = (): Transform => tree => { // we let it get handled naturally return EXIT; } + return undefined; }); } catch { // If parsing fails, leave the node as-is From 3f17522ed6ad0d7d0c98dde3a1514430b26de267 Mon Sep 17 00:00:00 2001 From: eagletrhost Date: Thu, 23 Apr 2026 22:54:47 +1000 Subject: [PATCH 2/7] fix: paragraphs in cells should serialize to jsx table --- __tests__/lib/mdxish/mdxishMdastToMd.test.ts | 101 +++++++++++++++--- .../transform/mdxish/mdxish-tables-to-jsx.ts | 9 ++ 2 files changed, 95 insertions(+), 15 deletions(-) diff --git a/__tests__/lib/mdxish/mdxishMdastToMd.test.ts b/__tests__/lib/mdxish/mdxishMdastToMd.test.ts index f1e33c063..f2c6f02cd 100644 --- a/__tests__/lib/mdxish/mdxishMdastToMd.test.ts +++ b/__tests__/lib/mdxish/mdxishMdastToMd.test.ts @@ -1,4 +1,4 @@ -import type { Root as MdastRoot, RootContent } from 'mdast'; +import type { Root as MdastRoot, RootContent, Table } from 'mdast'; import { NodeTypes } from '../../../enums'; import { mdxishMdastToMd } from '../../../lib'; @@ -176,7 +176,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - }, + } as Table, ], }; @@ -215,6 +215,77 @@ 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 = { + type: 'root', + children: [ + { + type: 'table', + align: ['left'], + children: [ + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [ + { + type: 'paragraph', + children: [ + { + type: 'text', + value: 'Line 1' + } + ] + }, + { + type: 'paragraph', + children: [ + { + type: 'text', + value: 'Line 2' + } + ] + }, + { + type: 'paragraph', + children: [ + { + type: 'text', + value: 'Line 3' + } + ] + } + ] + } + ] + } + ] + } as Table + ] + }; + + const serialized = mdxishMdastToMd(mdast); + expect(serialized).toMatchInlineSnapshot(` + "
+ + + + + + + +
+ Line 1 + + Line 2 + + Line 3 +
+ " + `); + }); + it('should serialize a table with list content in cells to JSX ', () => { const mdast: MdastRoot = { type: 'root', @@ -251,7 +322,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - }, + } as Table, ], }; @@ -321,7 +392,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - }, + } as Table, ], }; @@ -399,7 +470,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - }, + } as Table, ], }; @@ -472,7 +543,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - }, + } as Table, ], }; @@ -548,7 +619,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - }, + } as Table, ], }; @@ -618,7 +689,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - }, + } as Table, ], }; @@ -656,7 +727,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - }, + } as Table, ], }; @@ -701,7 +772,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - }, + } as Table, ], }; @@ -768,7 +839,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - }, + } as Table, ], }; @@ -808,7 +879,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - }, + } as Table, ], }; @@ -852,7 +923,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - }, + } as Table, ], }; @@ -899,7 +970,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - }, + } as Table, ], }; @@ -931,7 +1002,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - }, + } as Table, ], }; diff --git a/processor/transform/mdxish/mdxish-tables-to-jsx.ts b/processor/transform/mdxish/mdxish-tables-to-jsx.ts index 3eb27119b..2a7ab61fb 100644 --- a/processor/transform/mdxish/mdxish-tables-to-jsx.ts +++ b/processor/transform/mdxish/mdxish-tables-to-jsx.ts @@ -48,6 +48,15 @@ const mdxishTablesToJsx = (): Transform => tree => { breakParent.children.splice(breakIndex, 1, { type: 'text', value: '\n' }); }); + // A cell with more than one paragraph child cannot be serialized as GFM + // (pipe tables are single-line per cell), so force JSX
output to + // preserve paragraph separation. + const paragraphCount = cell.children.filter(child => child.type === 'paragraph').length; + if (paragraphCount > 1) { + hasFlowContent = true; + return; + } + // Check if any child is "flow" content (block-level) that requires JSX
// serialization instead of GFM. `phrasing()` from mdast-util-phrasing returns // true for inline node types (text, emphasis, strong, link, etc.) which are From 5b417b93b9980afc7d6cc2797c536dcde6bf483c Mon Sep 17 00:00:00 2001 From: eagletrhost Date: Fri, 24 Apr 2026 13:36:44 +1000 Subject: [PATCH 3/7] refactor: move table transformers --- .../transformers/mdxish-tables-to-jsx.test.ts | 35 ++++++++++--------- .../normalize-table-separator.test.ts | 2 +- lib/mdxish.ts | 6 ++-- processor/transform/index.ts | 2 +- .../transform/mdxish/mdxish-jsx-to-mdast.ts | 17 ++------- .../{ => tables}/mdxish-tables-to-jsx.ts | 2 +- .../mdxish/{ => tables}/mdxish-tables.ts | 30 +++++----------- .../{ => tables}/normalize-table-separator.ts | 0 processor/transform/mdxish/tables/utils.ts | 20 +++++++++++ 9 files changed, 57 insertions(+), 57 deletions(-) rename processor/transform/mdxish/{ => tables}/mdxish-tables-to-jsx.ts (99%) rename processor/transform/mdxish/{ => tables}/mdxish-tables.ts (88%) rename processor/transform/mdxish/{ => tables}/normalize-table-separator.ts (100%) create mode 100644 processor/transform/mdxish/tables/utils.ts diff --git a/__tests__/transformers/mdxish-tables-to-jsx.test.ts b/__tests__/transformers/mdxish-tables-to-jsx.test.ts index b4e8cba42..e0089c357 100644 --- a/__tests__/transformers/mdxish-tables-to-jsx.test.ts +++ b/__tests__/transformers/mdxish-tables-to-jsx.test.ts @@ -4,11 +4,14 @@ import remarkGfm from 'remark-gfm'; import remarkParse from 'remark-parse'; import { unified } from 'unified'; -import mdxishTablesToJsx from '../../processor/transform/mdxish/mdxish-tables-to-jsx'; +import mdxishTablesToJsx from '../../processor/transform/mdxish/tables/mdxish-tables-to-jsx'; import { collectNodes } from '../helpers'; const parseWithPlugin = (markdown: string): Root => { - const processor = unified().use(remarkParse).use(remarkGfm).use(mdxishTablesToJsx); + const processor = unified() + .use(remarkParse) + .use(remarkGfm) + .use(() => (tree: Root) => mdxishTablesToJsx()(tree) ?? undefined); const tree = processor.parse(markdown); processor.runSync(tree); return tree as Root; @@ -17,7 +20,7 @@ const parseWithPlugin = (markdown: string): Root => { describe('mdxish-tables-to-jsx', () => { describe('plain GFM tables (no flow content)', () => { it('leaves a simple text table as a GFM table node', () => { - const tree = parseWithPlugin(`| a | b |\n| --- | --- |\n| c | d |`); + const tree = parseWithPlugin('| a | b |\n| --- | --- |\n| c | d |'); const tables = collectNodes(tree, 'table'); expect(tables).toHaveLength(1); @@ -25,14 +28,14 @@ describe('mdxish-tables-to-jsx', () => { }); it('leaves a table with inline formatting as GFM', () => { - const tree = parseWithPlugin(`| Header |\n| --- |\n| **bold** and _italic_ |`); + const tree = parseWithPlugin('| Header |\n| --- |\n| **bold** and _italic_ |'); expect(collectNodes(tree, 'table')).toHaveLength(1); expect(collectNodes(tree, 'mdxJsxFlowElement')).toHaveLength(0); }); it('leaves a table with empty cells as GFM', () => { - const tree = parseWithPlugin(`| a | b |\n| --- | --- |\n| | d |`); + const tree = parseWithPlugin('| a | b |\n| --- | --- |\n| | d |'); expect(collectNodes(tree, 'table')).toHaveLength(1); }); @@ -40,7 +43,7 @@ describe('mdxish-tables-to-jsx', () => { describe('tables with flow content (converted to JSX)', () => { it('converts a table containing a self-closing JSX component to JSX', () => { - const tree = parseWithPlugin(`| Header |\n| --- |\n| |`); + const tree = parseWithPlugin('| Header |\n| --- |\n| |'); const jsxElements = collectNodes(tree, 'mdxJsxFlowElement'); const tables = jsxElements.filter((n) => (n as { name?: string }).name === 'Table'); @@ -51,14 +54,14 @@ describe('mdxish-tables-to-jsx', () => { describe('tables with raw HTML (kept as GFM)', () => { it('keeps a table with a raw HTML block as GFM', () => { - const tree = parseWithPlugin(`| Header |\n| --- |\n|
hello
|`); + const tree = parseWithPlugin('| Header |\n| --- |\n|
hello
|'); expect(collectNodes(tree, 'table')).toHaveLength(1); expect(collectNodes(tree, 'mdxJsxFlowElement')).toHaveLength(0); }); it('keeps a table with unclosed HTML tags as GFM', () => { - const tree = parseWithPlugin(`| Header |\n| --- |\n|
|`); + const tree = parseWithPlugin('| Header |\n| --- |\n|
|'); expect(collectNodes(tree, 'table')).toHaveLength(1); }); @@ -66,7 +69,7 @@ describe('mdxish-tables-to-jsx', () => { describe('break node replacement', () => { it('replaces break nodes with text newlines', () => { - const md = `| Header |\n| --- |\n| line1
line2 |`; + const md = '| Header |\n| --- |\n| line1
line2 |'; const tree = parseWithPlugin(md); const textNodes: Parent[] = []; @@ -88,7 +91,7 @@ describe('mdxish-tables-to-jsx', () => { describe('JSX Table structure', () => { it('generates thead, tbody, tr, th, and td elements', () => { const tree = parseWithPlugin( - `| H1 | H2 |\n| --- | --- |\n| | text |`, + '| H1 | H2 |\n| --- | --- |\n| | text |', ); const jsxElements = collectNodes(tree, 'mdxJsxFlowElement') as (Parent & { name: string })[]; const names = jsxElements.map((n) => n.name); @@ -103,11 +106,11 @@ describe('mdxish-tables-to-jsx', () => { it('preserves alignment as an attribute on the Table element', () => { const tree = parseWithPlugin( - `| Left | Center | Right |\n| :--- | :---: | ---: |\n| | b | c |`, + '| Left | Center | Right |\n| :--- | :---: | ---: |\n| | b | c |', ); const jsxElements = collectNodes(tree, 'mdxJsxFlowElement') as (Parent & { - name: string; attributes: { name: string; value: { value: string } }[]; + name: string; })[]; const tableNode = jsxElements.find((n) => n.name === 'Table'); @@ -119,11 +122,11 @@ describe('mdxish-tables-to-jsx', () => { it('omits the align attribute when all columns are left-aligned', () => { const tree = parseWithPlugin( - `| A | B |\n| --- | --- |\n| | x |`, + '| A | B |\n| --- | --- |\n| | x |', ); const jsxElements = collectNodes(tree, 'mdxJsxFlowElement') as (Parent & { - name: string; attributes: { name: string }[]; + name: string; })[]; const tableNode = jsxElements.find((n) => n.name === 'Table'); @@ -134,7 +137,7 @@ describe('mdxish-tables-to-jsx', () => { describe('multi-child cell scanning', () => { it('detects flow content in any child of a cell, not just the first', () => { - const md = `| Header |\n| --- |\n| text |`; + const md = '| Header |\n| --- |\n| text |'; const tree = parseWithPlugin(md); const jsxElements = collectNodes(tree, 'mdxJsxFlowElement') as (Parent & { name?: string })[]; const tableNode = jsxElements.find((n) => n.name === 'Table'); @@ -143,7 +146,7 @@ describe('mdxish-tables-to-jsx', () => { }); it('keeps GFM when all children are phrasing content', () => { - const tree = parseWithPlugin(`| Header |\n| --- |\n| hello **world** |`); + const tree = parseWithPlugin('| Header |\n| --- |\n| hello **world** |'); expect(collectNodes(tree, 'table')).toHaveLength(1); expect(collectNodes(tree, 'mdxJsxFlowElement')).toHaveLength(0); diff --git a/__tests__/transformers/normalize-table-separator.test.ts b/__tests__/transformers/normalize-table-separator.test.ts index 0feabd9bd..4dc8788b8 100644 --- a/__tests__/transformers/normalize-table-separator.test.ts +++ b/__tests__/transformers/normalize-table-separator.test.ts @@ -1,4 +1,4 @@ -import { normalizeTableSeparator } from '../../processor/transform/mdxish/normalize-table-separator'; +import { normalizeTableSeparator } from '../../processor/transform/mdxish/tables/normalize-table-separator'; describe('normalize-table-separator', () => { describe('malformed left alignment', () => { diff --git a/lib/mdxish.ts b/lib/mdxish.ts index 3b8542170..e472b66bd 100644 --- a/lib/mdxish.ts +++ b/lib/mdxish.ts @@ -37,12 +37,9 @@ import magicBlockTransformer from '../processor/transform/mdxish/magic-blocks/ma import mdxishHtmlBlocks from '../processor/transform/mdxish/mdxish-html-blocks'; import mdxishJsxToMdast from '../processor/transform/mdxish/mdxish-jsx-to-mdast'; import mdxishMermaidTransformer from '../processor/transform/mdxish/mdxish-mermaid'; -import mdxishTables from '../processor/transform/mdxish/mdxish-tables'; -import mdxishTablesToJsx from '../processor/transform/mdxish/mdxish-tables-to-jsx'; import { normalizeCompactHeadings } from '../processor/transform/mdxish/normalize-compact-headings'; import normalizeEmphasisAST from '../processor/transform/mdxish/normalize-malformed-md-syntax'; import normalizeMdxJsxNodes from '../processor/transform/mdxish/normalize-mdx-jsx-nodes'; -import { normalizeTableSeparator } from '../processor/transform/mdxish/normalize-table-separator'; import { preprocessJSXExpressions, removeJSXComments, @@ -52,6 +49,9 @@ import { preserveBooleanProperties, restoreBooleanProperties, } from '../processor/transform/mdxish/retain-boolean-attributes'; +import mdxishTables from '../processor/transform/mdxish/tables/mdxish-tables'; +import mdxishTablesToJsx from '../processor/transform/mdxish/tables/mdxish-tables-to-jsx'; +import { normalizeTableSeparator } from '../processor/transform/mdxish/tables/normalize-table-separator'; import { terminateHtmlFlowBlocks } from '../processor/transform/mdxish/terminate-html-flow-blocks'; import variablesCodeResolver from '../processor/transform/mdxish/variables-code'; import variablesTextTransformer from '../processor/transform/mdxish/variables-text'; diff --git a/processor/transform/index.ts b/processor/transform/index.ts index b72e7afff..4b43dcd75 100644 --- a/processor/transform/index.ts +++ b/processor/transform/index.ts @@ -8,7 +8,7 @@ import handleMissingComponents from './handle-missing-components'; import imageTransformer from './images'; import injectComponents from './inject-components'; import mdxToHast from './mdx-to-hast'; -import mdxishTables from './mdxish/mdxish-tables'; +import mdxishTables from './mdxish/tables/mdxish-tables'; import mermaidTransformer from './mermaid'; import readmeComponentsTransformer from './readme-components'; import readmeToMdx from './readme-to-mdx'; diff --git a/processor/transform/mdxish/mdxish-jsx-to-mdast.ts b/processor/transform/mdxish/mdxish-jsx-to-mdast.ts index f13158305..0ae363e50 100644 --- a/processor/transform/mdxish/mdxish-jsx-to-mdast.ts +++ b/processor/transform/mdxish/mdxish-jsx-to-mdast.ts @@ -11,6 +11,8 @@ import { NodeTypes } from '../../../enums'; import { mdast } from '../../../lib'; import { getAttrs, isMDXElement } from '../../utils'; +import { unwrapSoleParagraph } from './tables/utils'; + function toImageAlign(value: string | undefined): ImageAlign | undefined { if (value === 'left' || value === 'center' || value === 'right') { return value; @@ -551,22 +553,9 @@ const transformTable = (jsx: MdxJsxFlowElement): Table | null => { const cells: TableCell[] = []; visit(row as Node, isTableCell, (cell: MdxJsxFlowElement & { name: 'td' | 'th' }) => { - // Unwrap a paragraph child when it is the cell's sole paragraph as it might affect rendering - // However if there are multiple paragraphs, they are legitimate paragraphs of separate lines of content - const paragraphCount = cell.children.filter(c => c.type === 'paragraph').length; - const parsedChildren = - paragraphCount === 1 - ? cell.children.flatMap((parsedNode: Node) => { - if (parsedNode.type === 'paragraph' && 'children' in parsedNode && parsedNode.children) { - return parsedNode.children; - } - return [parsedNode]; - }) - : cell.children; - cells.push({ type: 'tableCell', - children: parsedChildren, + children: unwrapSoleParagraph(cell.children as Node[]), position: cell.position, } as TableCell); }); diff --git a/processor/transform/mdxish/mdxish-tables-to-jsx.ts b/processor/transform/mdxish/tables/mdxish-tables-to-jsx.ts similarity index 99% rename from processor/transform/mdxish/mdxish-tables-to-jsx.ts rename to processor/transform/mdxish/tables/mdxish-tables-to-jsx.ts index 2a7ab61fb..fbb42a3df 100644 --- a/processor/transform/mdxish/mdxish-tables-to-jsx.ts +++ b/processor/transform/mdxish/tables/mdxish-tables-to-jsx.ts @@ -5,7 +5,7 @@ import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx'; import { phrasing } from 'mdast-util-phrasing'; import { visit } from 'unist-util-visit'; -import { NodeTypes } from '../../../enums'; +import { NodeTypes } from '../../../../enums'; const SELF_CLOSING_JSX_REGEX = /^\s*<[A-Z][^>]*\/>\s*$/; diff --git a/processor/transform/mdxish/mdxish-tables.ts b/processor/transform/mdxish/tables/mdxish-tables.ts similarity index 88% rename from processor/transform/mdxish/mdxish-tables.ts rename to processor/transform/mdxish/tables/mdxish-tables.ts index bc230e437..ccaacff41 100644 --- a/processor/transform/mdxish/mdxish-tables.ts +++ b/processor/transform/mdxish/tables/mdxish-tables.ts @@ -9,14 +9,15 @@ import remarkParse from 'remark-parse'; import { unified } from 'unified'; import { EXIT, visit } from 'unist-util-visit'; -import { gemojiFromMarkdown } from '../../../lib/mdast-util/gemoji'; -import { gemoji } from '../../../lib/micromark/gemoji'; -import { getAttrs, isMDXElement } from '../../utils'; -import calloutTransformer from '../callouts'; -import codeTabsTransformer from '../code-tabs'; -import { extractText } from '../extract-text'; +import { gemojiFromMarkdown } from '../../../../lib/mdast-util/gemoji'; +import { gemoji } from '../../../../lib/micromark/gemoji'; +import { getAttrs, isMDXElement } from '../../../utils'; +import calloutTransformer from '../../callouts'; +import codeTabsTransformer from '../../code-tabs'; +import { extractText } from '../../extract-text'; +import normalizeEmphasisAST from '../normalize-malformed-md-syntax'; -import normalizeEmphasisAST from './normalize-malformed-md-syntax'; +import { unwrapSoleParagraph } from './utils'; interface MdxJsxTableCell extends Omit { name: 'td' | 'th'; @@ -166,22 +167,9 @@ const processTableNode = ( const rowChildren: TableCell[] = []; visit(row as Node, isTableCell, ({ name, children: cellChildren, position: cellPosition }: MdxJsxTableCell) => { - // Unwrap a paragraph child when it is the cell's sole paragraph as it might affect rendering - // However if there are multiple paragraphs, they are legitimate paragraphs of separate lines of content - const paragraphCount = (cellChildren as Node[]).filter(c => c.type === 'paragraph').length; - const parsedChildren = - paragraphCount === 1 - ? (cellChildren as Node[]).flatMap(parsedNode => { - if (parsedNode.type === 'paragraph' && 'children' in parsedNode && parsedNode.children) { - return parsedNode.children; - } - return [parsedNode]; - }) - : (cellChildren as Node[]); - rowChildren.push({ type: tableTypes[name], - children: parsedChildren, + children: unwrapSoleParagraph(cellChildren as Node[]), position: cellPosition, } as TableCell); }); diff --git a/processor/transform/mdxish/normalize-table-separator.ts b/processor/transform/mdxish/tables/normalize-table-separator.ts similarity index 100% rename from processor/transform/mdxish/normalize-table-separator.ts rename to processor/transform/mdxish/tables/normalize-table-separator.ts diff --git a/processor/transform/mdxish/tables/utils.ts b/processor/transform/mdxish/tables/utils.ts new file mode 100644 index 000000000..48c647b6f --- /dev/null +++ b/processor/transform/mdxish/tables/utils.ts @@ -0,0 +1,20 @@ +import type { Node } from 'mdast'; + +/** + * If the cell has exactly one paragraph child, unwrap it so its inline children sit + * directly under the cell (matches GFM table cell shape and avoids stray `

` wrappers). + * + * 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[] => { + 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]; + }); +}; From 21452983bcb31b9334e843ee2049753bb011c3ce Mon Sep 17 00:00:00 2001 From: eagletrhost Date: Fri, 24 Apr 2026 14:43:35 +1000 Subject: [PATCH 4/7] refactor: add extended table types --- __tests__/lib/mdxish/mdxishMdastToMd.test.ts | 69 ++++++++++---------- lib/mdxish.ts | 6 +- processor/transform/mdxish/types.ts | 46 ++++++++++++- types.d.ts | 9 +++ 4 files changed, 92 insertions(+), 38 deletions(-) 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/lib/mdxish.ts b/lib/mdxish.ts index e472b66bd..45905f65d 100644 --- a/lib/mdxish.ts +++ b/lib/mdxish.ts @@ -1,4 +1,4 @@ -import type { CustomComponents, Variables } from '../types'; +import type { CustomComponents, Variables, MdxishMdastRoot } from '../types'; import type { Root } from 'hast'; import type { Root as MdastRoot } from 'mdast'; import type { Extension } from 'micromark-util-types'; @@ -227,7 +227,7 @@ function mdxJsxStringify(this: ReturnType) { /** * Serializes an Mdast back into a markdown string. */ -export function mdxishMdastToMd(mdast: MdastRoot) { +export function mdxishMdastToMd(mdast: MdxishMdastRoot) { const processor = unified() .use(remarkGfm) .use(mdxishTablesToJsx) @@ -237,7 +237,7 @@ export function mdxishMdastToMd(mdast: MdastRoot) { bullet: '-', emphasis: '_', }); - return processor.stringify(processor.runSync(mdast)); + return processor.stringify(processor.runSync(mdast as MdastRoot)); } /** diff --git a/processor/transform/mdxish/types.ts b/processor/transform/mdxish/types.ts index 8bd03cbc1..5e194971f 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. + */ +type MdxishTableCellContent = + | Code + | CodeTabs + | Html + | List + | Paragraph + | PhrasingContent; + +/** + * A `tableCell` that allows the same content the serializer accepts at runtime. + */ +interface MdxishTableCell extends Omit { + children: MdxishTableCellContent[]; +} + +interface MdxishTableRow extends Omit { + children: MdxishTableCell[]; +} + +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..60bf50a99 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'; +/** + * MDAST root accepted by mdxish serialization, including 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, From 1374d81222e4d79b6bc7d33638c6f745ae8907e2 Mon Sep 17 00:00:00 2001 From: eagletrhost Date: Fri, 24 Apr 2026 15:59:14 +1000 Subject: [PATCH 5/7] refactor: add new table type to transformer, leave no need to change API --- lib/mdxish.ts | 6 +++--- .../transform/mdxish/mdxish-jsx-to-mdast.ts | 18 ++++++++++-------- .../mdxish/tables/mdxish-tables-to-jsx.ts | 9 +++++---- .../transform/mdxish/tables/mdxish-tables.ts | 2 +- processor/transform/mdxish/types.ts | 8 ++++---- types.d.ts | 4 ++-- 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/lib/mdxish.ts b/lib/mdxish.ts index 45905f65d..e472b66bd 100644 --- a/lib/mdxish.ts +++ b/lib/mdxish.ts @@ -1,4 +1,4 @@ -import type { CustomComponents, Variables, MdxishMdastRoot } from '../types'; +import type { CustomComponents, Variables } from '../types'; import type { Root } from 'hast'; import type { Root as MdastRoot } from 'mdast'; import type { Extension } from 'micromark-util-types'; @@ -227,7 +227,7 @@ function mdxJsxStringify(this: ReturnType) { /** * Serializes an Mdast back into a markdown string. */ -export function mdxishMdastToMd(mdast: MdxishMdastRoot) { +export function mdxishMdastToMd(mdast: MdastRoot) { const processor = unified() .use(remarkGfm) .use(mdxishTablesToJsx) @@ -237,7 +237,7 @@ export function mdxishMdastToMd(mdast: MdxishMdastRoot) { bullet: '-', emphasis: '_', }); - return processor.stringify(processor.runSync(mdast as MdastRoot)); + return processor.stringify(processor.runSync(mdast)); } /** diff --git a/processor/transform/mdxish/mdxish-jsx-to-mdast.ts b/processor/transform/mdxish/mdxish-jsx-to-mdast.ts index 0ae363e50..790152ebe 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, MdxishTableCellContent, 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,14 +550,14 @@ 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' }) => { cells.push({ type: 'tableCell', - children: unwrapSoleParagraph(cell.children as Node[]), + children: unwrapSoleParagraph(cell.children) as MdxishTableCellContent[], position: cell.position, - } as TableCell); + }); }); rows.push({ @@ -574,12 +574,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..a7e3c15b3 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, Table } from 'mdast'; import type { Transform } from 'mdast-util-from-markdown'; import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx'; @@ -38,10 +39,10 @@ const mdxishTablesToJsx = (): Transform => tree => { visit( tree, (node: Node) => ['table', 'tableau'].includes(node.type), - (table: Table, index, parent) => { + (table: MdxishTable | Table, index, parent) => { 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 +62,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/mdxish-tables.ts b/processor/transform/mdxish/tables/mdxish-tables.ts index ccaacff41..796bb0797 100644 --- a/processor/transform/mdxish/tables/mdxish-tables.ts +++ b/processor/transform/mdxish/tables/mdxish-tables.ts @@ -169,7 +169,7 @@ const processTableNode = ( visit(row as Node, isTableCell, ({ name, children: cellChildren, position: cellPosition }: MdxJsxTableCell) => { rowChildren.push({ type: tableTypes[name], - children: unwrapSoleParagraph(cellChildren as Node[]), + children: unwrapSoleParagraph(cellChildren), position: cellPosition, } as TableCell); }); diff --git a/processor/transform/mdxish/types.ts b/processor/transform/mdxish/types.ts index 5e194971f..64b833d35 100644 --- a/processor/transform/mdxish/types.ts +++ b/processor/transform/mdxish/types.ts @@ -33,7 +33,7 @@ export interface FigureNode { * * If there's more node types that should be allowed, add them here. */ -type MdxishTableCellContent = +export type MdxishTableCellContent = | Code | CodeTabs | Html @@ -44,15 +44,15 @@ type MdxishTableCellContent = /** * A `tableCell` that allows the same content the serializer accepts at runtime. */ -interface MdxishTableCell extends Omit { +export interface MdxishTableCell extends Omit { children: MdxishTableCellContent[]; } -interface MdxishTableRow extends Omit { +export interface MdxishTableRow extends Omit { children: MdxishTableCell[]; } -interface MdxishTable extends Omit { +export interface MdxishTable extends Omit { children: MdxishTableRow[]; } diff --git a/types.d.ts b/types.d.ts index 60bf50a99..13ef96417 100644 --- a/types.d.ts +++ b/types.d.ts @@ -21,8 +21,8 @@ import type { MdxishMdastRootContent } from 'processor/transform/mdxish/types'; import type { Position } from 'unist'; /** - * MDAST root accepted by mdxish serialization, including tables whose cells - * carry flow content beyond mdast’s built-in `TableCell` typing. + * 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[]; From ee1aed810ebcb53c2d973d737513be1c3b11fb97 Mon Sep 17 00:00:00 2001 From: eagletrhost Date: Fri, 24 Apr 2026 16:23:26 +1000 Subject: [PATCH 6/7] fix: remove some casts --- processor/transform/mdxish/mdxish-jsx-to-mdast.ts | 4 ++-- processor/transform/mdxish/tables/mdxish-tables-to-jsx.ts | 5 +++-- processor/transform/mdxish/tables/utils.ts | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/processor/transform/mdxish/mdxish-jsx-to-mdast.ts b/processor/transform/mdxish/mdxish-jsx-to-mdast.ts index 790152ebe..391d7ecf4 100644 --- a/processor/transform/mdxish/mdxish-jsx-to-mdast.ts +++ b/processor/transform/mdxish/mdxish-jsx-to-mdast.ts @@ -1,5 +1,5 @@ import type { MagicBlockEmbed, MagicBlockImage } from './magic-blocks/types'; -import type { FigureNode, MdxishTable, MdxishTableCell, MdxishTableCellContent, MdxishTableRow } 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 } from 'mdast'; import type { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx-jsx'; @@ -555,7 +555,7 @@ const transformTable = (jsx: MdxJsxFlowElement): MdxishTable | null => { visit(row as Node, isTableCell, (cell: MdxJsxFlowElement & { name: 'td' | 'th' }) => { cells.push({ type: 'tableCell', - children: unwrapSoleParagraph(cell.children) as MdxishTableCellContent[], + children: unwrapSoleParagraph(cell.children), position: cell.position, }); }); diff --git a/processor/transform/mdxish/tables/mdxish-tables-to-jsx.ts b/processor/transform/mdxish/tables/mdxish-tables-to-jsx.ts index a7e3c15b3..4f833141f 100644 --- a/processor/transform/mdxish/tables/mdxish-tables-to-jsx.ts +++ b/processor/transform/mdxish/tables/mdxish-tables-to-jsx.ts @@ -1,5 +1,5 @@ import type { MdxishTable, MdxishTableCell } from '../types'; -import type { Literal, Node, Table } from 'mdast'; +import type { Literal, Node } from 'mdast'; import type { Transform } from 'mdast-util-from-markdown'; import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx'; @@ -39,7 +39,8 @@ const mdxishTablesToJsx = (): Transform => tree => { visit( tree, (node: Node) => ['table', 'tableau'].includes(node.type), - (table: MdxishTable | Table, index, parent) => { + (tableNode, index, parent) => { + const table = tableNode as MdxishTable; let hasFlowContent = false; visit(table, isTableCell, (cell: MdxishTableCell) => { 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]; }); From d444c71421b08a3f3789e4d72c7d4d32e720bc62 Mon Sep 17 00:00:00 2001 From: eagletrhost Date: Fri, 24 Apr 2026 16:51:43 +1000 Subject: [PATCH 7/7] fix: casts --- __tests__/lib/mdxish/mdxishMdastToMd.test.ts | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/__tests__/lib/mdxish/mdxishMdastToMd.test.ts b/__tests__/lib/mdxish/mdxishMdastToMd.test.ts index eb1cc1156..0ca26b5e0 100644 --- a/__tests__/lib/mdxish/mdxishMdastToMd.test.ts +++ b/__tests__/lib/mdxish/mdxishMdastToMd.test.ts @@ -177,7 +177,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - } as Table, + }, ], }; @@ -323,7 +323,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - } as Table, + }, ], }; @@ -393,7 +393,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - } as Table, + }, ], }; @@ -471,7 +471,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - } as Table, + }, ], }; @@ -544,7 +544,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - } as Table, + }, ], }; @@ -620,7 +620,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - } as Table, + }, ], }; @@ -690,7 +690,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - } as Table, + }, ], }; @@ -728,7 +728,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - } as Table, + }, ], }; @@ -773,7 +773,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - } as Table, + }, ], }; @@ -840,7 +840,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - } as Table, + }, ], }; @@ -880,7 +880,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - } as Table, + }, ], }; @@ -924,7 +924,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - } as Table, + }, ], }; @@ -971,7 +971,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - } as Table, + }, ], }; @@ -1003,7 +1003,7 @@ describe('mdxishMdastToMd', () => { ], }, ], - } as Table, + }, ], };