import { visitParents } from 'unist-util-visit-parents' interface ElementNode { type: 'element' tagName: string properties: { // Properties can have any value type (strings, booleans, arrays, etc.) [key: string]: any } _scoped?: boolean } interface AncestorNode { properties?: { className?: string[] } } /** * Where it can mutate the AST to swap from: * *
* * ... * * * ... * ... * ... * *
* * to: * * *
* * ... * * * ... * ... * ... * *
* * In other words, if contained in a `.rowheaders`, the *first* `` * in a `` should have `scope="row"`. * * */ function matcher(node: any): node is ElementNode { return node.type === 'element' && node.tagName === 'td' && !('scope' in node.properties) } function insideRowheaders(ancestors: AncestorNode[]): boolean { return ancestors.some( (node: AncestorNode) => node.properties && node.properties.className && node.properties.className.includes('rowheaders'), ) } // ancestors is an array of hast nodes without proper TypeScript definitions function visitor(node: ElementNode, ancestors: any[]): void { if (insideRowheaders(ancestors)) { const tr = ancestors.at(-1) as ElementNode if (!tr._scoped) { tr._scoped = true node.properties.scope = 'row' node.tagName = 'th' } } } // tree is a hast root node without proper TypeScript definitions export default function rewriteForRowheaders() { return (tree: any) => visitParents(tree, matcher, visitor) }