@@ -35,76 +35,86 @@ Output:
35
35
36
36
*/
37
37
38
- import assert from 'assert/strict'
39
- import { Code , Parents , Root , RootContent } from 'mdast'
40
- import * as path from 'path'
38
+ import type * as md from 'mdast'
39
+ import type { } from 'mdast-util-mdx' ; // Type-only empty import to register MDX types into mdast
40
+ import assert from 'node:assert/strict'
41
+ import * as path from 'node:path'
41
42
import * as prettier from 'prettier'
42
43
import tsBlankSpace from 'ts-blank-space'
43
- import { Plugin } from 'unified'
44
+ import type { Plugin } from 'unified'
44
45
import { visitParents } from 'unist-util-visit-parents'
45
46
46
47
// Wrapped in \b to denote a word boundary
47
48
const META_FLAG_REGEX = / \b a u t o - j s \b /
48
49
const SUPPORTED_LANGS = new Set ( [ 'ts' , 'tsx' ] )
49
50
50
- const autoJSCodePlugin : Plugin < [ ] , Root > = ( ) => {
51
- return async ( tree , file ) => {
52
- const nodesToProcess = new Set < { node : Code ; ancestors : Parents [ ] } > ( )
51
+ const autoJSCodePlugin : Plugin < [ ] , md . Root > = ( ) => async ( tree , file ) => {
52
+ const nodesToProcess = new Set < { node : md . Code ; ancestors : md . Parents [ ] } > ( )
53
53
54
- visitParents ( tree , 'code' , ( node , ancestors ) => {
55
- if ( node . meta && META_FLAG_REGEX . test ( node . meta ) ) {
56
- if ( ! node . lang || ! SUPPORTED_LANGS . has ( node . lang ) ) {
57
- throw new Error ( `Unsupported language: ${ node . lang } ` )
58
- }
59
-
60
- // We put these aside for processing later
61
- // because `visitParents` does not allow
62
- // async visitors.
63
- nodesToProcess . add ( { node, ancestors } )
54
+ visitParents ( tree , 'code' , ( node , ancestors ) => {
55
+ if ( node . meta && META_FLAG_REGEX . test ( node . meta ) ) {
56
+ if ( ! node . lang || ! SUPPORTED_LANGS . has ( node . lang ) ) {
57
+ throw new Error ( `Unsupported language: ${ node . lang } ` )
64
58
}
65
- } )
66
59
67
- for ( const { node, ancestors } of nodesToProcess ) {
68
- const parent = ancestors . at ( - 1 )
69
- assert ( parent ) // It must have a parent because the root node is a fully formed tree
70
- assert ( node . meta && node . lang ) // Already checked in the visitor
60
+ // We put these aside for processing later
61
+ // because `visitParents` does not allow
62
+ // async visitors.
63
+ nodesToProcess . add ( { node, ancestors } )
64
+ }
65
+ } )
71
66
72
- // Remove our flag from the meta so other plugins don't trip up
73
- const newMeta = node . meta . replace ( META_FLAG_REGEX , '' )
67
+ for ( const { node, ancestors } of nodesToProcess ) {
68
+ const parent = ancestors . at ( - 1 )
69
+ assert ( parent ) // It must have a parent because the root node is a fully formed tree
70
+ assert ( node . meta && node . lang ) // Already checked in the visitor
74
71
75
- const jsCodeBlock = await makeJsCodeBlock ( newMeta , node , {
76
- location : file . path ,
77
- } )
78
- const tsCodeBlock = await makeTsCodeBlock ( newMeta , node , {
79
- location : file . path ,
80
- } )
72
+ // Remove our flag from the meta so other plugins don't trip up
73
+ const newMeta = node . meta . replace ( META_FLAG_REGEX , '' )
81
74
82
- const newNodes : RootContent [ ] = [
83
- {
84
- // @ts -expect-error This is an MDX extension
85
- type : 'jsx' ,
86
- value : `<Tabs groupId="js-ts"><TabItem value="js" label="JavaScript">` ,
87
- } ,
88
- jsCodeBlock ,
75
+ const jsCodeBlock = await makeJsCodeBlock ( newMeta , node , {
76
+ location : file . path ,
77
+ } )
78
+ const tsCodeBlock = await makeTsCodeBlock ( newMeta , node , {
79
+ location : file . path ,
80
+ } )
81
+
82
+ // The specific structure of the new node was retrieved by copy-pasting
83
+ // an example into the MDX playground and inspecting the AST.
84
+ // https://mdxjs.com/playground
85
+ const newNode : md . RootContent = {
86
+ type : 'mdxJsxFlowElement' ,
87
+ name : 'Tabs' ,
88
+ attributes : [
89
+ { type : 'mdxJsxAttribute' , name : 'groupId' , value : 'js-ts' } ,
90
+ ] ,
91
+ children : [
89
92
{
90
- // @ts -expect-error This is an MDX extension
91
- type : 'jsx' ,
92
- value : `</TabItem><TabItem value="ts" label="TypeScript">` ,
93
+ type : 'mdxJsxFlowElement' ,
94
+ name : 'TabItem' ,
95
+ attributes : [
96
+ { type : 'mdxJsxAttribute' , name : 'value' , value : 'js' } ,
97
+ { type : 'mdxJsxAttribute' , name : 'label' , value : 'JavaScript' } ,
98
+ ] ,
99
+ children : [ jsCodeBlock ] ,
93
100
} ,
94
- tsCodeBlock ,
95
101
{
96
- // @ts -expect-error This is an MDX extension
97
- type : 'jsx' ,
98
- value : `</TabItem></Tabs>` ,
102
+ type : 'mdxJsxFlowElement' ,
103
+ name : 'TabItem' ,
104
+ attributes : [
105
+ { type : 'mdxJsxAttribute' , name : 'value' , value : 'ts' } ,
106
+ { type : 'mdxJsxAttribute' , name : 'label' , value : 'TypeScript' } ,
107
+ ] ,
108
+ children : [ tsCodeBlock ] ,
99
109
} ,
100
- ]
110
+ ] ,
111
+ }
101
112
102
- const idx = parent . children . findIndex ( ( someNode ) => someNode === node )
103
- assert ( idx !== - 1 , "Node not found in parent's children" )
113
+ const idx = parent . children . findIndex ( ( someNode ) => someNode === node )
114
+ assert ( idx !== - 1 , "Node not found in parent's children" )
104
115
105
- // Replace input node for the new ones in the parent's children array
106
- parent . children . splice ( idx , 1 , ...newNodes )
107
- }
116
+ // Replace input node for the new ones in the parent's children array
117
+ parent . children . splice ( idx , 1 , newNode )
108
118
}
109
119
}
110
120
@@ -116,9 +126,9 @@ const CODE_BLOCK_TITLE_REGEX = /title=(?<quote>["'])(?<title>.*?)\1/
116
126
117
127
async function makeJsCodeBlock (
118
128
metaString : string ,
119
- node : Code ,
129
+ node : md . Code ,
120
130
{ location } : { location : string }
121
- ) : Promise < RootContent > {
131
+ ) : Promise < md . Code > {
122
132
// Find the `title=` meta param and change the extension
123
133
const meta = metaString . replace (
124
134
CODE_BLOCK_TITLE_REGEX ,
@@ -143,9 +153,9 @@ async function makeJsCodeBlock(
143
153
144
154
async function makeTsCodeBlock (
145
155
metaString : string ,
146
- node : Code ,
156
+ node : md . Code ,
147
157
{ location } : { location : string }
148
- ) : Promise < RootContent > {
158
+ ) : Promise < md . Code > {
149
159
const lang = node . lang
150
160
const code = await format ( node . value , { parser : 'babel-ts' , location } )
151
161
0 commit comments