@@ -37,91 +37,75 @@ Output:
37
37
38
38
import type * as md from 'mdast'
39
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
40
import * as path from 'node:path'
42
41
import * as prettier from 'prettier'
43
42
import { blankSourceFile } from 'ts-blank-space'
44
43
import * as ts from 'typescript'
45
44
import type { Plugin } from 'unified'
46
- import { visitParents } from 'unist-util-visit-parents'
45
+ import { visit } from 'unist-util-visit'
46
+ import { assertSupportedLanguage , shouldVisitNode } from './util/code-blocks'
47
47
48
48
// Wrapped in \b to denote a word boundary
49
49
const META_FLAG_REGEX = / \b a u t o - j s \b /
50
- const SUPPORTED_LANGS = new Set ( [ 'ts' , 'tsx' ] )
50
+ const SUPPORTED_LANGS = new Set ( [ 'ts' , 'tsx' ] as const )
51
51
52
52
const autoJSCodePlugin : Plugin < [ ] , md . Root > = ( ) => async ( tree , file ) => {
53
- const nodesToProcess = new Set < { node : md . Code ; ancestors : md . Parents [ ] } > ( )
54
-
55
- visitParents ( tree , 'code' , ( node , ancestors ) => {
56
- if ( node . meta && META_FLAG_REGEX . test ( node . meta ) ) {
57
- if ( ! node . lang ) {
58
- file . fail ( 'No language specified' , { place : node . position } )
59
- }
60
- if ( ! SUPPORTED_LANGS . has ( node . lang ) ) {
61
- file . fail ( `Unsupported language: ${ node . lang } ` , {
62
- place : node . position ,
63
- } )
53
+ const asyncFns : ( ( ) => Promise < void > ) [ ] = [ ]
54
+
55
+ visit ( tree , shouldVisitNode ( META_FLAG_REGEX ) , ( node , idx , parent ) => {
56
+ assertSupportedLanguage ( node , file , SUPPORTED_LANGS )
57
+
58
+ // We save the computation for later
59
+ // because `visit` does not allow async visitors.
60
+
61
+ asyncFns . push ( async ( ) => {
62
+ // Remove our flag from the meta so other plugins don't trip up
63
+ const newMeta = node . meta . replace ( META_FLAG_REGEX , '' )
64
+
65
+ const jsCodeBlock = await makeJsCodeBlock ( newMeta , node , {
66
+ location : file . path ,
67
+ } )
68
+ const tsCodeBlock = await makeTsCodeBlock ( newMeta , node , {
69
+ location : file . path ,
70
+ } )
71
+
72
+ // The specific structure of the new node was retrieved by copy-pasting
73
+ // an example into the MDX playground and inspecting the AST.
74
+ // https://mdxjs.com/playground
75
+ const newNode : md . RootContent = {
76
+ type : 'mdxJsxFlowElement' ,
77
+ name : 'Tabs' ,
78
+ attributes : [
79
+ { type : 'mdxJsxAttribute' , name : 'groupId' , value : 'js-ts' } ,
80
+ ] ,
81
+ children : [
82
+ {
83
+ type : 'mdxJsxFlowElement' ,
84
+ name : 'TabItem' ,
85
+ attributes : [
86
+ { type : 'mdxJsxAttribute' , name : 'value' , value : 'js' } ,
87
+ { type : 'mdxJsxAttribute' , name : 'label' , value : 'JavaScript' } ,
88
+ ] ,
89
+ children : [ jsCodeBlock ] ,
90
+ } ,
91
+ {
92
+ type : 'mdxJsxFlowElement' ,
93
+ name : 'TabItem' ,
94
+ attributes : [
95
+ { type : 'mdxJsxAttribute' , name : 'value' , value : 'ts' } ,
96
+ { type : 'mdxJsxAttribute' , name : 'label' , value : 'TypeScript' } ,
97
+ ] ,
98
+ children : [ tsCodeBlock ] ,
99
+ } ,
100
+ ] ,
64
101
}
65
102
66
- // We put these aside for processing later
67
- // because `visitParents` does not allow
68
- // async visitors.
69
- nodesToProcess . add ( { node, ancestors } )
70
- }
71
- } )
72
-
73
- for ( const { node, ancestors } of nodesToProcess ) {
74
- const parent = ancestors . at ( - 1 )
75
- assert ( parent ) // The node is never a `Root` node, so it will always have a parent
76
- assert ( node . meta && node . lang ) // Already checked in the visitor
77
-
78
- // Remove our flag from the meta so other plugins don't trip up
79
- const newMeta = node . meta . replace ( META_FLAG_REGEX , '' )
80
-
81
- const jsCodeBlock = await makeJsCodeBlock ( newMeta , node , {
82
- location : file . path ,
83
- } )
84
- const tsCodeBlock = await makeTsCodeBlock ( newMeta , node , {
85
- location : file . path ,
103
+ // Replace input node for the new ones in the parent's children array
104
+ parent . children . splice ( idx , 1 , newNode )
86
105
} )
106
+ } )
87
107
88
- // The specific structure of the new node was retrieved by copy-pasting
89
- // an example into the MDX playground and inspecting the AST.
90
- // https://mdxjs.com/playground
91
- const newNode : md . RootContent = {
92
- type : 'mdxJsxFlowElement' ,
93
- name : 'Tabs' ,
94
- attributes : [
95
- { type : 'mdxJsxAttribute' , name : 'groupId' , value : 'js-ts' } ,
96
- ] ,
97
- children : [
98
- {
99
- type : 'mdxJsxFlowElement' ,
100
- name : 'TabItem' ,
101
- attributes : [
102
- { type : 'mdxJsxAttribute' , name : 'value' , value : 'js' } ,
103
- { type : 'mdxJsxAttribute' , name : 'label' , value : 'JavaScript' } ,
104
- ] ,
105
- children : [ jsCodeBlock ] ,
106
- } ,
107
- {
108
- type : 'mdxJsxFlowElement' ,
109
- name : 'TabItem' ,
110
- attributes : [
111
- { type : 'mdxJsxAttribute' , name : 'value' , value : 'ts' } ,
112
- { type : 'mdxJsxAttribute' , name : 'label' , value : 'TypeScript' } ,
113
- ] ,
114
- children : [ tsCodeBlock ] ,
115
- } ,
116
- ] ,
117
- }
118
-
119
- const idx = parent . children . findIndex ( ( someNode ) => someNode === node )
120
- assert ( idx !== - 1 , "Node not found in parent's children" )
121
-
122
- // Replace input node for the new ones in the parent's children array
123
- parent . children . splice ( idx , 1 , newNode )
124
- }
108
+ await Promise . all ( asyncFns . map ( ( fn ) => fn ( ) ) )
125
109
}
126
110
127
111
export default autoJSCodePlugin
0 commit comments