import fs from 'node:fs/promises'; import parse, { type DOMNode } from 'html-dom-parser'; import type { ChildNode } from 'domhandler'; const JSD_STRING = /\(\s*(<.*)>\s*\)/gs; export async function parseJSDFile(filename: string) { const content = (await fs.readFile(filename)).toString(); const matches = JSD_STRING.exec(content); if (matches) { let html = matches[1] + '>'; const root = parse(html); const translated = translate(root[0]); const str = content.replace(matches[1] + '>', translated); await fs.writeFile(filename.replace('.tsd', '.ts'), str); } return true; } interface state { inInterpolation?: boolean; children?: string[][]; parent?: Text[]; } function translate(root: DOMNode | ChildNode | null, state: state = {}): string | null { if (!root || typeof root !== 'object') return null; let children = []; if ('children' in root && Array.isArray(root.children) && root.children.length > 0) { for (const child of root.children) { const translated = translate(child, state); if (translated) { if (state.inInterpolation && state.parent[state.children.length - 1] === child) { state.children[state.children.length - 1].push(translated); } else { children.push(translated); } } } } if ('nodeType' in root && root.nodeType === 3) { if (root.data.trim() === '') return null; return parseText(root.data.trim(), state, root); } if ('name' in root && root.name) { let tagName = root.name || 'unknown'; let attrs = 'attribs' in root ? root.attribs : {}; return `StarKitten.createElement("${tagName}", ${JSON.stringify(attrs)}${children.length > 0 ? ', ' + children.join(', ') : ''})`; } } const JSD_INTERPOLATION = /\{(.+)\}/gs; const JSD_START_EXP_INTERPOLATION = /\{(.+)\(/gs; const JSD_END_EXP_INTERPOLATION = /\)(.+)\}/gs; function parseText(text: string, state: state = {}, parent: Text = {}): string { let interpolations = text.match(JSD_INTERPOLATION); if (!interpolations) { if (text.match(JSD_START_EXP_INTERPOLATION)) { state.inInterpolation = true; state.children = state.children || [[]]; state.parent = state.parent || []; state.parent.push(parent); return text.substring(1, text.length - 1); } else if (text.match(JSD_END_EXP_INTERPOLATION)) { const combined = state.children?.[state.children.length - 1].join(' '); state.children?.[state.children.length - 1].splice(0); state.children?.pop(); state.parent?.pop(); if (state.children.length === 0) { state.inInterpolation = false; return combined + ' ' + text.substring(1, text.length - 1); } } return `"${text}"`; } else { text = replaceInterpolations(text); return `"${text}"`; } } function replaceInterpolations(text: string, isOnJSON: boolean = false) { let interpolations = null; while ((interpolations = JSD_INTERPOLATION.exec(text))) { if (isOnJSON) { text = text.replace(`"{${interpolations[1]}}"`, interpolations[1]); } else { text = text.replace(`{${interpolations[1]}}`, `"+ ${interpolations[1]} +"`); } } return text; }