1
- import type {
2
- BaseNode ,
3
- FunctionDeclaration ,
4
- Identifier ,
5
- Literal ,
6
- ObjectExpression ,
7
- Program ,
8
- Property ,
9
- ReturnStatement ,
10
- SpreadElement
11
- } from 'estree'
12
- import type {
13
- JsxAttribute ,
14
- JsxExpressionContainer
15
- } from 'estree-util-to-js/lib/jsx'
1
+ import type { FunctionDeclaration , Program , ReturnStatement } from 'estree'
2
+ import type { JsxAttribute } from 'estree-util-to-js/lib/jsx'
16
3
import type { Plugin } from 'unified'
4
+ import { visit } from 'unist-util-visit'
5
+ import { DEFAULT_PROPERTY_PROPS } from '../constants.js'
17
6
18
7
const HEADING_NAMES = new Set ( [ 'h2' , 'h3' , 'h4' , 'h5' , 'h6' ] )
19
8
20
- export const recmaRewriteJsx : Plugin < [ ] , Program > = ( ) => ast => {
9
+ export const recmaRewriteJsx : Plugin < [ ] , Program > = ( ) => ( ast , file ) => {
21
10
const createMdxContent = ast . body . find (
22
- // @ts -expect-error
23
- o => o . type === 'FunctionDeclaration' && o . id . name === '_createMdxContent'
11
+ o => o . type === 'FunctionDeclaration' && o . id ! . name === '_createMdxContent'
24
12
) as FunctionDeclaration
25
- const returnStatementIndex = createMdxContent . body . body . findIndex (
26
- o => o . type === 'ReturnStatement'
27
- )
28
13
29
- const returnStatement = createMdxContent . body . body [
30
- returnStatementIndex
31
- ] as ReturnStatement
14
+ const mdxContent = ast . body . find (
15
+ node =>
16
+ node . type === 'FunctionDeclaration' && node . id ! . name === 'MDXContent'
17
+ ) as FunctionDeclaration
32
18
33
- // @ts -expect-error
34
- function isHeading ( o ) : boolean {
35
- const name = o . openingElement ?. name . property ?. name
36
- return name && HEADING_NAMES . has ( name )
19
+ if ( ! mdxContent ) {
20
+ throw new Error ( '`MDXContent` not found!' )
37
21
}
38
22
39
- // @ts -expect-error
40
- const headings = returnStatement . argument . children . filter ( isHeading )
41
- const toc = ast . body . find (
42
- node =>
43
- node . type === 'ExportNamedDeclaration' &&
44
- node . declaration &&
45
- 'declarations' in
46
- node . declaration /* doesn't exist for FunctionDeclaration */ &&
47
- // @ts -expect-error
48
- node . declaration . declarations [ 0 ] . id . name === 'toc'
49
- ) as any
23
+ const returnStatement = createMdxContent . body . body . find (
24
+ o => o . type === 'ReturnStatement'
25
+ ) as ReturnStatement
50
26
51
- const tocProperties = toc . declaration . declarations [ 0 ] . init . elements as (
52
- | ObjectExpression
53
- | SpreadElement
54
- ) [ ]
27
+ const { argument } = returnStatement as any
55
28
56
- for ( const heading of headings ) {
57
- const idNode = heading . openingElement . attributes . find (
58
- ( attr : JsxAttribute ) => attr . name . name === 'id'
59
- )
29
+ // if return statements doesn't wrap in fragment children will be []
30
+ const returnBody = argument . children . length ? argument . children : [ argument ]
60
31
61
- const id = idNode . value . value
32
+ const tocProperties = file . data . toc as (
33
+ | { properties : { id : string } }
34
+ | string
35
+ ) [ ]
62
36
63
- const foundIndex = tocProperties . findIndex ( node => {
64
- if ( node . type !== 'ObjectExpression' ) return
65
- const object = Object . fromEntries (
66
- // @ts -expect-error
67
- node . properties . map ( prop => [ prop . key . name , prop . value . value ] )
37
+ visit (
38
+ // @ts -expect-error -- fixes type error
39
+ { children : returnBody } ,
40
+ 'JSXElement' ,
41
+ ( heading : any , _index , parent ) => {
42
+ const { openingElement } = heading
43
+ const name = openingElement ?. name . property ?. name
44
+ const isHeading = name && HEADING_NAMES . has ( name )
45
+ // @ts -expect-error -- fixes type error
46
+ const isFootnotes = parent . openingElement ?. attributes . some (
47
+ ( attr : JsxAttribute ) => attr . name . name === 'data-footnotes'
68
48
)
69
- return object . id === id
70
- } )
71
-
72
- if ( foundIndex === - 1 ) continue
73
-
74
- // @ts -expect-error
75
- const valueNode = tocProperties [ foundIndex ] . properties . find (
76
- ( node : Property ) => ( node . key as Identifier ) . name === 'value'
77
- )
78
-
79
- const isExpressionContainer = (
80
- node : BaseNode
81
- ) : node is JsxExpressionContainer => node . type === 'JSXExpressionContainer'
82
-
83
- const isIdentifier = ( node : BaseNode ) : node is Identifier =>
84
- isExpressionContainer ( node ) && node . expression . type === 'Identifier'
49
+ if ( ! isHeading || isFootnotes ) return
50
+ const idNode = openingElement . attributes . find (
51
+ ( attr : JsxAttribute ) => attr . name . name === 'id'
52
+ )
53
+ if ( ! idNode ) return
85
54
86
- const isLiteral = ( node : BaseNode ) : node is Literal =>
87
- isExpressionContainer ( node ) && node . expression . type === 'Literal'
55
+ const id = idNode . value . value
88
56
89
- idNode . value = {
90
- type : 'JSXExpressionContainer' ,
91
- expression : {
92
- type : 'Identifier' ,
93
- name : `toc[${ foundIndex } ].id`
94
- }
95
- }
57
+ const foundIndex = tocProperties . findIndex ( node => {
58
+ if ( typeof node === 'string' ) return
59
+ return node . properties . id === id
60
+ } )
96
61
97
- if (
98
- heading . children . every (
99
- ( node : BaseNode ) => isLiteral ( node ) || isIdentifier ( node )
100
- )
101
- ) {
102
- if ( ! heading . children . every ( isLiteral ) ) {
103
- valueNode . value = {
104
- type : 'JSXFragment' ,
105
- openingFragment : { type : 'JSXOpeningFragment' } ,
106
- closingFragment : { type : 'JSXClosingFragment' } ,
107
- children : heading . children
62
+ if ( foundIndex === - 1 ) return
63
+ idNode . value = {
64
+ type : 'JSXExpressionContainer' ,
65
+ expression : {
66
+ type : 'Identifier' ,
67
+ name : `toc[${ foundIndex } ].id`
108
68
}
109
69
}
110
70
71
+ delete openingElement . selfClosing
111
72
heading . children = [
112
73
{
113
74
type : 'JSXExpressionContainer' ,
@@ -117,6 +78,75 @@ export const recmaRewriteJsx: Plugin<[], Program> = () => ast => {
117
78
}
118
79
}
119
80
]
81
+ heading . closingElement = {
82
+ ...openingElement ,
83
+ type : 'JSXClosingElement' ,
84
+ attributes : [ ]
85
+ }
120
86
}
121
- }
87
+ )
88
+
89
+ mdxContent . body . body . unshift (
90
+ {
91
+ type : 'VariableDeclaration' ,
92
+ kind : 'const' ,
93
+ declarations : [
94
+ {
95
+ type : 'VariableDeclarator' ,
96
+ id : { type : 'Identifier' , name : 'toc' } ,
97
+ init : {
98
+ type : 'CallExpression' ,
99
+ callee : { type : 'Identifier' , name : 'useTOC' } ,
100
+ arguments : [ { type : 'Identifier' , name : 'props' } ] ,
101
+ optional : false
102
+ }
103
+ }
104
+ ]
105
+ } ,
106
+ {
107
+ type : 'ExpressionStatement' ,
108
+ expression : {
109
+ type : 'AssignmentExpression' ,
110
+ operator : '=' ,
111
+ left : { type : 'Identifier' , name : 'props' } ,
112
+ right : {
113
+ type : 'ObjectExpression' ,
114
+ properties : [
115
+ {
116
+ type : 'SpreadElement' ,
117
+ argument : { type : 'Identifier' , name : 'props' }
118
+ } ,
119
+ {
120
+ ...DEFAULT_PROPERTY_PROPS ,
121
+ key : { type : 'Identifier' , name : 'toc' } ,
122
+ value : { type : 'Identifier' , name : 'toc' } ,
123
+ shorthand : true
124
+ }
125
+ ]
126
+ }
127
+ }
128
+ }
129
+ )
130
+
131
+ createMdxContent . body . body . unshift ( {
132
+ type : 'VariableDeclaration' ,
133
+ kind : 'const' ,
134
+ declarations : [
135
+ {
136
+ type : 'VariableDeclarator' ,
137
+ id : {
138
+ type : 'ObjectPattern' ,
139
+ properties : [
140
+ {
141
+ ...DEFAULT_PROPERTY_PROPS ,
142
+ key : { type : 'Identifier' , name : 'toc' } ,
143
+ value : { type : 'Identifier' , name : 'toc' } ,
144
+ shorthand : true
145
+ }
146
+ ]
147
+ } ,
148
+ init : { type : 'Identifier' , name : 'props' }
149
+ }
150
+ ]
151
+ } )
122
152
}
0 commit comments