@@ -3,6 +3,7 @@ import { createHash, Hash } from 'node:crypto';
33import fs from 'node:fs' ;
44import path from 'node:path' ;
55import ts from 'typescript' ;
6+ import * as marked from 'marked' ;
67import { codeToHtml , createCssVariablesTheme } from 'shiki' ;
78import { transformerTwoslash } from '@shikijs/twoslash' ;
89import { SHIKI_LANGUAGE_MAP , slugify , smart_quotes , transform } from './utils' ;
@@ -185,11 +186,11 @@ const snippets = await create_snippet_cache();
185186export async function render_content_markdown (
186187 filename : string ,
187188 body : string ,
188- options : { check ?: boolean } ,
189+ options ? : { check ?: boolean } ,
189190 twoslashBanner ?: TwoslashBanner
190191) {
191192 const headings : string [ ] = [ ] ;
192- const { check = true } = options ;
193+ const { check = true } = options ?? { } ;
193194
194195 return await transform ( body , {
195196 async walkTokens ( token ) {
@@ -675,6 +676,66 @@ async function syntax_highlight({
675676 } ) ;
676677
677678 html = html . replace ( / { 27 , } / g, ( ) => redactions . shift ( ) ! ) ;
679+
680+ if ( check ) {
681+ // munge the twoslash output so that it renders sensibly. the order of operations
682+ // here is important — we need to work backwards, to avoid corrupting the offsets
683+ const replacements : Array < { start : number ; end : number ; content : string } > = [ ] ;
684+
685+ for ( const match of html . matchAll ( / < d i v c l a s s = " t w o s l a s h - p o p u p - d o c s " > ( [ ^ ] + ?) < \/ d i v > / g) ) {
686+ const content = await render_content_markdown ( '<twoslash>' , match [ 1 ] , { check : false } ) ;
687+
688+ replacements . push ( {
689+ start : match . index ,
690+ end : match . index + match [ 0 ] . length ,
691+ content : '<div class="twoslash-popup-docs">' + content + '</div>'
692+ } ) ;
693+ }
694+
695+ while ( replacements . length > 0 ) {
696+ const { start, end, content } = replacements . pop ( ) ! ;
697+ html = html . slice ( 0 , start ) + content + html . slice ( end ) ;
698+ }
699+
700+ for ( const match of html . matchAll (
701+ / < s p a n c l a s s = " t w o s l a s h - p o p u p - d o c s - t a g " > < s p a n c l a s s = " t w o s l a s h - p o p u p - d o c s - t a g - n a m e " > ( [ ^ ] + ?) < \/ s p a n > < s p a n c l a s s = " t w o s l a s h - p o p u p - d o c s - t a g - v a l u e " > ( [ ^ ] + ?) < \/ s p a n > < \/ s p a n > / g
702+ ) ) {
703+ const tag = match [ 1 ] ;
704+ let value = match [ 2 ] ;
705+
706+ let content = `<span class="tag">${ tag } </span><span class="value">` ;
707+
708+ if ( tag === '@param' || tag === '@throws' ) {
709+ const words = value . split ( ' ' ) ;
710+ let param = words . shift ( ) ! ;
711+ value = words . join ( ' ' ) ;
712+
713+ if ( tag === '@throws' ) {
714+ if ( param [ 0 ] !== '{' || param [ param . length - 1 ] !== '}' ) {
715+ throw new Error ( 'TODO robustify @throws handling' ) ;
716+ }
717+
718+ param = param . slice ( 1 , - 1 ) ;
719+ }
720+
721+ content += `<span class="param">${ param } </span> ` ;
722+ }
723+
724+ content += marked . parseInline ( value ) ;
725+ content += '</span>' ;
726+
727+ replacements . push ( {
728+ start : match . index ,
729+ end : match . index + match [ 0 ] . length ,
730+ content : '<div class="tags">' + content + '</div>'
731+ } ) ;
732+ }
733+
734+ while ( replacements . length > 0 ) {
735+ const { start, end, content } = replacements . pop ( ) ! ;
736+ html = html . slice ( 0 , start ) + content + html . slice ( end ) ;
737+ }
738+ }
678739 } catch ( e ) {
679740 console . error ( ( e as Error ) . message ) ;
680741 console . warn ( prelude + redacted ) ;
0 commit comments