6
6
ImportSpecifier,
7
7
JSXElement,
8
8
ModuleDeclaration,
9
- Node,
10
9
ObjectPattern,
11
10
Program,
12
11
Property,
13
12
SpreadElement,
14
13
Statement,
15
14
VariableDeclarator
16
15
* } from 'estree-jsx'
17
- * @import {Scope as PeriscopicScope } from 'periscopic '
16
+ * @import {Scope} from 'estree-util-scope '
18
17
* @import {VFile} from 'vfile'
19
18
* @import {ProcessorOptions} from '../core.js'
20
19
*/
21
20
22
21
/**
23
- * @typedef {PeriscopicScope & {node: Node} } Scope
24
- * Scope (with a `node`).
25
- *
26
22
* @typedef StackEntry
27
23
* Entry.
28
24
* @property {Array<string> } components
40
36
*/
41
37
42
38
import { name as isIdentifierName } from 'estree-util-is-identifier-name'
39
+ import { createVisitors } from 'estree-util-scope'
43
40
import { walk } from 'estree-walker'
44
- import { analyze } from 'periscopic'
45
41
import { stringifyPosition } from 'unist-util-stringify-position'
46
42
import { positionFromEstree } from 'unist-util-position-from-estree'
47
43
import { specifiersToDeclarations } from '../util/estree-util-specifiers-to-declarations.js'
@@ -75,21 +71,15 @@ export function recmaJsxRewrite(options) {
75
71
* Nothing.
76
72
*/
77
73
return function ( tree , file ) {
78
- // Find everything that’s defined in the top-level scope.
79
- const scopeInfo = analyze ( tree )
74
+ const visitors = createVisitors ( )
80
75
/** @type {Array<StackEntry> } */
81
76
const functionStack = [ ]
82
77
let importProvider = false
83
78
let createErrorHelper = false
84
- /** @type {Scope | undefined } */
85
- let currentScope
86
79
87
80
walk ( tree , {
88
81
enter ( node ) {
89
- // Cast because we match `node`.
90
- const newScope = /** @type {Scope | undefined } */ (
91
- scopeInfo . map . get ( node )
92
- )
82
+ visitors . enter ( node )
93
83
94
84
if (
95
85
node . type === 'FunctionDeclaration' ||
@@ -105,31 +95,26 @@ export function recmaJsxRewrite(options) {
105
95
tags : [ ]
106
96
} )
107
97
108
- // MDXContent only ever contains MDXLayout
98
+ // ` MDXContent` only ever contains ` MDXLayout`.
109
99
if (
110
100
isNamedFunction ( node , 'MDXContent' ) &&
111
- newScope &&
112
- ! inScope ( newScope , 'MDXLayout' )
101
+ ! inScope ( visitors . scopes , 'MDXLayout' )
113
102
) {
114
103
functionStack [ 0 ] . components . push ( 'MDXLayout' )
115
104
}
116
105
}
117
106
118
- const functionScope = functionStack [ 0 ]
107
+ const functionInfo = functionStack [ 0 ]
108
+
119
109
if (
120
- ! functionScope ||
121
- ( ! isNamedFunction ( functionScope . node , '_createMdxContent' ) &&
110
+ ! functionInfo ||
111
+ ( ! isNamedFunction ( functionInfo . node , '_createMdxContent' ) &&
122
112
! providerImportSource )
123
113
) {
124
114
return
125
115
}
126
116
127
- if ( newScope ) {
128
- newScope . node = node
129
- currentScope = newScope
130
- }
131
-
132
- if ( currentScope && node . type === 'JSXElement' ) {
117
+ if ( node . type === 'JSXElement' ) {
133
118
let name = node . openingElement . name
134
119
135
120
// `<x.y>`, `<Foo.Bar>`, `<x.y.z>`.
@@ -146,28 +131,22 @@ export function recmaJsxRewrite(options) {
146
131
ids . unshift ( name . name )
147
132
const fullId = ids . join ( '.' )
148
133
const id = name . name
134
+ const isInScope = inScope ( visitors . scopes , id )
149
135
150
- const isInScope = inScope ( currentScope , id )
151
-
152
- if ( ! Object . hasOwn ( functionScope . references , fullId ) ) {
153
- // Cast because we match `node`.
154
- const parentScope = /** @type {Scope | undefined } */ (
155
- currentScope . parent
156
- )
157
- if (
158
- ! isInScope ||
136
+ if (
137
+ ! Object . hasOwn ( functionInfo . references , fullId ) &&
138
+ ( ! isInScope ||
159
139
// If the parent scope is `_createMdxContent`, then this
160
140
// references a component we can add a check statement for.
161
- ( parentScope &&
162
- parentScope . node . type === 'FunctionDeclaration' &&
163
- isNamedFunction ( parentScope . node , '_createMdxContent' ) )
164
- ) {
165
- functionScope . references [ fullId ] = { component : true , node}
166
- }
141
+ ( functionStack . length === 1 &&
142
+ functionStack [ 0 ] . node . type === 'FunctionDeclaration' &&
143
+ isNamedFunction ( functionStack [ 0 ] . node , '_createMdxContent' ) ) )
144
+ ) {
145
+ functionInfo . references [ fullId ] = { component : true , node}
167
146
}
168
147
169
- if ( ! functionScope . objects . includes ( id ) && ! isInScope ) {
170
- functionScope . objects . push ( id )
148
+ if ( ! functionInfo . objects . includes ( id ) && ! isInScope ) {
149
+ functionInfo . objects . push ( id )
171
150
}
172
151
}
173
152
// `<xml:thing>`.
@@ -181,18 +160,18 @@ export function recmaJsxRewrite(options) {
181
160
else if ( isIdentifierName ( name . name ) && ! / ^ [ a - z ] / . test ( name . name ) ) {
182
161
const id = name . name
183
162
184
- if ( ! inScope ( currentScope , id ) ) {
163
+ if ( ! inScope ( visitors . scopes , id ) ) {
185
164
// No need to add an error for an undefined layout — we use an
186
165
// `if` later.
187
166
if (
188
167
id !== 'MDXLayout' &&
189
- ! Object . hasOwn ( functionScope . references , id )
168
+ ! Object . hasOwn ( functionInfo . references , id )
190
169
) {
191
- functionScope . references [ id ] = { component : true , node}
170
+ functionInfo . references [ id ] = { component : true , node}
192
171
}
193
172
194
- if ( ! functionScope . components . includes ( id ) ) {
195
- functionScope . components . push ( id )
173
+ if ( ! functionInfo . components . includes ( id ) ) {
174
+ functionInfo . components . push ( id )
196
175
}
197
176
}
198
177
} else if ( node . data && node . data . _mdxExplicitJsx ) {
@@ -202,18 +181,18 @@ export function recmaJsxRewrite(options) {
202
181
} else {
203
182
const id = name . name
204
183
205
- if ( ! functionScope . tags . includes ( id ) ) {
206
- functionScope . tags . push ( id )
184
+ if ( ! functionInfo . tags . includes ( id ) ) {
185
+ functionInfo . tags . push ( id )
207
186
}
208
187
209
188
/** @type {Array<number | string> } */
210
189
let jsxIdExpression = [ '_components' , id ]
211
190
if ( isIdentifierName ( id ) === false ) {
212
191
let invalidComponentName =
213
- functionScope . idToInvalidComponentName . get ( id )
192
+ functionInfo . idToInvalidComponentName . get ( id )
214
193
if ( invalidComponentName === undefined ) {
215
- invalidComponentName = `_component${ functionScope . idToInvalidComponentName . size } `
216
- functionScope . idToInvalidComponentName . set (
194
+ invalidComponentName = `_component${ functionInfo . idToInvalidComponentName . size } `
195
+ functionInfo . idToInvalidComponentName . set (
217
196
id ,
218
197
invalidComponentName
219
198
)
@@ -233,6 +212,8 @@ export function recmaJsxRewrite(options) {
233
212
}
234
213
} ,
235
214
leave ( node ) {
215
+ visitors . exit ( node )
216
+
236
217
/** @type {Array<Property | SpreadElement> } */
237
218
const defaults = [ ]
238
219
/** @type {Array<string> } */
@@ -242,22 +223,17 @@ export function recmaJsxRewrite(options) {
242
223
/** @type {Array<VariableDeclarator> } */
243
224
const declarations = [ ]
244
225
245
- if ( currentScope && currentScope . node === node ) {
246
- // Cast to patch our `node`.
247
- currentScope = /** @type {Scope } */ ( currentScope . parent )
248
- }
249
-
250
226
if (
251
227
node . type === 'FunctionDeclaration' ||
252
228
node . type === 'FunctionExpression' ||
253
229
node . type === 'ArrowFunctionExpression'
254
230
) {
255
- const scopeNode = node
256
- const scope = functionStack [ functionStack . length - 1 ]
231
+ const functionInfo = functionStack [ functionStack . length - 1 ]
232
+
257
233
/** @type {string } */
258
234
let name
259
235
260
- for ( name of scope . tags . sort ( ) ) {
236
+ for ( name of functionInfo . tags . sort ( ) ) {
261
237
defaults . push ( {
262
238
type : 'Property' ,
263
239
kind : 'init' ,
@@ -271,9 +247,9 @@ export function recmaJsxRewrite(options) {
271
247
} )
272
248
}
273
249
274
- actual . push ( ...scope . components )
250
+ actual . push ( ...functionInfo . components )
275
251
276
- for ( name of scope . objects ) {
252
+ for ( name of functionInfo . objects ) {
277
253
// In some cases, a component is used directly (`<X>`) but it’s also
278
254
// used as an object (`<X.Y>`).
279
255
if ( ! actual . includes ( name ) ) {
@@ -289,7 +265,7 @@ export function recmaJsxRewrite(options) {
289
265
if (
290
266
defaults . length > 0 ||
291
267
actual . length > 0 ||
292
- scope . idToInvalidComponentName . size > 0
268
+ functionInfo . idToInvalidComponentName . size > 0
293
269
) {
294
270
if ( providerImportSource ) {
295
271
importProvider = true
@@ -304,8 +280,8 @@ export function recmaJsxRewrite(options) {
304
280
// Accept `components` as a prop if this is the `MDXContent` or
305
281
// `_createMdxContent` function.
306
282
if (
307
- isNamedFunction ( scope . node , 'MDXContent' ) ||
308
- isNamedFunction ( scope . node , '_createMdxContent' )
283
+ isNamedFunction ( functionInfo . node , 'MDXContent' ) ||
284
+ isNamedFunction ( functionInfo . node , '_createMdxContent' )
309
285
) {
310
286
parameters . push ( toIdOrMemberExpression ( [ 'props' , 'components' ] ) )
311
287
}
@@ -360,7 +336,7 @@ export function recmaJsxRewrite(options) {
360
336
}
361
337
}
362
338
363
- if ( scope . tags . length > 0 ) {
339
+ if ( functionInfo . tags . length > 0 ) {
364
340
declarations . push ( {
365
341
type : 'VariableDeclarator' ,
366
342
id : { type : 'Identifier' , name : '_components' } ,
@@ -369,9 +345,9 @@ export function recmaJsxRewrite(options) {
369
345
componentsInit = { type : 'Identifier' , name : '_components' }
370
346
}
371
347
372
- if ( isNamedFunction ( scope . node , '_createMdxContent' ) ) {
348
+ if ( isNamedFunction ( functionInfo . node , '_createMdxContent' ) ) {
373
349
for ( const [ id , componentName ] of [
374
- ...scope . idToInvalidComponentName
350
+ ...functionInfo . idToInvalidComponentName
375
351
] . sort ( function ( [ a ] , [ b ] ) {
376
352
return a . localeCompare ( b )
377
353
} ) ) {
@@ -418,27 +394,28 @@ export function recmaJsxRewrite(options) {
418
394
let key
419
395
420
396
// Add partials (so for `x.y.z` it’d generate `x` and `x.y` too).
421
- for ( key in scope . references ) {
422
- if ( Object . hasOwn ( scope . references , key ) ) {
397
+ for ( key in functionInfo . references ) {
398
+ if ( Object . hasOwn ( functionInfo . references , key ) ) {
423
399
const parts = key . split ( '.' )
424
400
let index = 0
425
401
while ( ++ index < parts . length ) {
426
402
const partial = parts . slice ( 0 , index ) . join ( '.' )
427
- if ( ! Object . hasOwn ( scope . references , partial ) ) {
428
- scope . references [ partial ] = {
403
+ if ( ! Object . hasOwn ( functionInfo . references , partial ) ) {
404
+ functionInfo . references [ partial ] = {
429
405
component : false ,
430
- node : scope . references [ key ] . node
406
+ node : functionInfo . references [ key ] . node
431
407
}
432
408
}
433
409
}
434
410
}
435
411
}
436
412
437
- const references = Object . keys ( scope . references ) . sort ( )
413
+ const references = Object . keys ( functionInfo . references ) . sort ( )
414
+
438
415
let index = - 1
439
416
while ( ++ index < references . length ) {
440
417
const id = references [ index ]
441
- const info = scope . references [ id ]
418
+ const info = functionInfo . references [ id ]
442
419
const place = stringifyPosition ( positionFromEstree ( info . node ) )
443
420
/** @type {Array<Expression> } */
444
421
const parameters = [
@@ -475,14 +452,14 @@ export function recmaJsxRewrite(options) {
475
452
476
453
if ( statements . length > 0 ) {
477
454
// Arrow functions with an implied return:
478
- if ( scopeNode . body . type !== 'BlockStatement' ) {
479
- scopeNode . body = {
455
+ if ( node . body . type !== 'BlockStatement' ) {
456
+ node . body = {
480
457
type : 'BlockStatement' ,
481
- body : [ { type : 'ReturnStatement' , argument : scopeNode . body } ]
458
+ body : [ { type : 'ReturnStatement' , argument : node . body } ]
482
459
}
483
460
}
484
461
485
- scopeNode . body . body . unshift ( ...statements )
462
+ node . body . body . unshift ( ...statements )
486
463
}
487
464
488
465
functionStack . pop ( )
@@ -620,26 +597,22 @@ function isNamedFunction(node, name) {
620
597
}
621
598
622
599
/**
623
- * @param {Readonly <Scope> } scope
600
+ * @param {Array <Scope> } scopes
624
601
* Scope.
625
602
* @param {string } id
626
603
* Identifier.
627
604
* @returns {boolean }
628
605
* Whether `id` is in `scope`.
629
606
*/
630
- function inScope ( scope , id ) {
631
- /** @type {Scope | undefined } */
632
- let currentScope = scope
607
+ function inScope ( scopes , id ) {
608
+ let index = scopes . length
633
609
634
- while ( currentScope ) {
635
- if ( currentScope . declarations . has ( id ) ) {
610
+ while ( index -- ) {
611
+ const scope = scopes [ index ]
612
+
613
+ if ( scope . defined . includes ( id ) ) {
636
614
return true
637
615
}
638
-
639
- // Cast to patch our `node`.
640
- currentScope = /** @type {Scope | undefined } */ (
641
- currentScope . parent || undefined
642
- )
643
616
}
644
617
645
618
return false
0 commit comments