1- import {
2- createTransformerFactory ,
3- rendererRich ,
4- transformerTwoslash ,
5- type TransformerTwoslashOptions ,
6- } from '@shikijs/twoslash' ;
1+ import { transformerTwoslash } from '@shikijs/twoslash' ;
72import type { Element , Root } from 'hast' ;
83import { toString } from 'hast-util-to-string' ;
94import type { MdxJsxFlowElementHast , MdxJsxTextElementHast } from 'mdast-util-mdx-jsx' ;
105import { createHighlighter , type Highlighter } from 'shiki' ;
11- import { createTwoslashFromCDN } from 'twoslash-cdn' ;
12- import ts from 'typescript' ;
136import type { Plugin } from 'unified' ;
147import { visit } from 'unist-util-visit' ;
158
@@ -27,34 +20,19 @@ import {
2720 DEFAULT_LANGS ,
2821 SHIKI_TRANSFORMERS ,
2922} from './shiki-constants.js' ;
23+ import {
24+ cdnTransformerTwoslash ,
25+ cdnTwoslash ,
26+ getTwoslashOptions ,
27+ parseLineComment ,
28+ } from './twoslash/config.js' ;
3029import { getLanguage } from './utils.js' ;
3130
32- const twoslashCompilerOptions = {
33- target : ts . ScriptTarget . ESNext ,
34- lib : [ 'ESNext' , 'DOM' , 'esnext' , 'dom' , 'es2020' ] ,
35- } ;
36-
37- const twoslashOptions : TransformerTwoslashOptions = {
38- onTwoslashError ( err , code , lang ) {
39- console . error ( JSON . stringify ( { err, code, lang } ) ) ;
40- } ,
41- onShikiError ( err , code , lang ) {
42- console . error ( JSON . stringify ( { err, code, lang } ) ) ;
43- } ,
44- renderer : rendererRich ( ) ,
45- langs : [ 'ts' , 'typescript' , 'js' , 'javascript' , 'tsx' , 'jsx' ] ,
46- explicitTrigger : / m i n t - t w o s l a s h / ,
47- twoslashOptions : { compilerOptions : twoslashCompilerOptions } ,
48- } ;
49-
50- const cdnTwoslash = createTwoslashFromCDN ( { compilerOptions : twoslashCompilerOptions } ) ;
51-
52- const cdnTransformerTwoslash = createTransformerFactory ( cdnTwoslash . runSync ) ;
53-
5431export type RehypeSyntaxHighlightingOptions = {
5532 theme ?: ShikiTheme ;
5633 themes ?: Record < 'light' | 'dark' , ShikiTheme > ;
5734 codeStyling ?: 'dark' | 'system' ;
35+ linkMap ?: Map < string , string > ;
5836} ;
5937
6038let highlighterPromise : Promise < Highlighter > | null = null ;
@@ -73,7 +51,8 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
7351 options = { }
7452) => {
7553 return async ( tree ) => {
76- const asyncNodesToProcess : Promise < void > [ ] = [ ] ;
54+ const nodesToProcess : Promise < void > [ ] = [ ] ;
55+
7756 const themesToLoad : ShikiTheme [ ] = [ ] ;
7857 if ( options . themes ) {
7958 themesToLoad . push ( options . themes . dark ) ;
@@ -120,32 +99,35 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
12099 getLanguage ( child , DEFAULT_LANG_ALIASES ) ??
121100 DEFAULT_LANG ;
122101
123- asyncNodesToProcess . push (
102+ nodesToProcess . push (
124103 ( async ( ) => {
125104 await cdnTwoslash . prepareTypes ( toString ( node ) ) ;
126- if ( ! DEFAULT_LANGS . includes ( lang ) ) {
127- await highlighter . loadLanguage ( lang ) ;
128- traverseNode ( node , index , parent , highlighter , lang , options ) ;
129- } else {
130- traverseNode ( node , index , parent , highlighter , lang , options ) ;
131- }
105+ if ( ! DEFAULT_LANGS . includes ( lang ) ) await highlighter . loadLanguage ( lang ) ;
106+ traverseNode ( { node, index, parent, highlighter, lang, options } ) ;
132107 } ) ( )
133108 ) ;
134109 } ) ;
135- await Promise . all ( asyncNodesToProcess ) ;
110+ await Promise . all ( nodesToProcess ) ;
136111 } ;
137112} ;
138113
139- const traverseNode = (
140- node : Element ,
141- index : number ,
142- parent : Element | Root | MdxJsxTextElementHast | MdxJsxFlowElementHast ,
143- highlighter : Highlighter ,
144- lang : ShikiLang ,
145- options : RehypeSyntaxHighlightingOptions
146- ) => {
114+ function traverseNode ( {
115+ node,
116+ index,
117+ parent,
118+ highlighter,
119+ lang,
120+ options,
121+ } : {
122+ node : Element ;
123+ index : number ;
124+ parent : Element | Root | MdxJsxTextElementHast | MdxJsxFlowElementHast ;
125+ highlighter : Highlighter ;
126+ lang : ShikiLang ;
127+ options : RehypeSyntaxHighlightingOptions ;
128+ } ) {
147129 try {
148- const code = toString ( node ) ;
130+ let code = toString ( node ) ;
149131
150132 const meta = node . data ?. meta ?. split ( ' ' ) ?? [ ] ;
151133 const twoslashIndex = meta . findIndex ( ( str ) => str . toLowerCase ( ) === 'mint-twoslash' ) ;
@@ -156,6 +138,20 @@ const traverseNode = (
156138 node . data . meta = meta . join ( ' ' ) . trim ( ) || undefined ;
157139 }
158140
141+ const linkMap = options . linkMap ?? new Map ( ) ;
142+ const splitCode = code . split ( '\n' ) ;
143+ for ( const [ i , line ] of splitCode . entries ( ) ) {
144+ const parsedLineComment = parseLineComment ( line ) ;
145+ if ( ! parsedLineComment ) continue ;
146+ const { word, href } = parsedLineComment ;
147+ linkMap . set ( word , href ) ;
148+ splitCode . splice ( i , 1 ) ;
149+ }
150+
151+ code = splitCode . join ( '\n' ) ;
152+
153+ const twoslashOptions = getTwoslashOptions ( { linkMap } ) ;
154+
159155 const hast = highlighter . codeToHast ( code , {
160156 lang : lang ?? DEFAULT_LANG ,
161157 meta : shouldUseTwoslash ? { __raw : 'mint-twoslash' } : undefined ,
@@ -195,6 +191,6 @@ const traverseNode = (
195191 }
196192 throw err ;
197193 }
198- } ;
194+ }
199195
200196export { UNIQUE_LANGS , DEFAULT_LANG_ALIASES , SHIKI_THEMES , ShikiLang , ShikiTheme } ;
0 commit comments