1
1
import { inspect } from '../jsutils/inspect' ;
2
- import type { Maybe } from '../jsutils/Maybe' ;
3
2
4
3
import type { ASTNode , ASTKindToNode } from './ast' ;
5
4
import { isNode } from './ast' ;
5
+ import { Kind } from './kinds' ;
6
6
7
7
/**
8
8
* A visitor is provided to visit, it contains the collection of
@@ -256,6 +256,15 @@ export function visit(
256
256
visitor : ASTVisitor | ASTReducer < any > ,
257
257
visitorKeys : ASTVisitorKeyMap = QueryDocumentKeys ,
258
258
) : any {
259
+ const enterLeaveMap = new Map <
260
+ keyof ASTKindToNode ,
261
+ EnterLeaveVisitor < ASTNode >
262
+ > ( ) ;
263
+
264
+ for ( const kind of Object . values ( Kind ) ) {
265
+ enterLeaveMap . set ( kind , getEnterLeaveForKind ( visitor , kind ) ) ;
266
+ }
267
+
259
268
/* eslint-disable no-undef-init */
260
269
let stack : any = undefined ;
261
270
let inArray = Array . isArray ( root ) ;
@@ -318,29 +327,30 @@ export function visit(
318
327
if ( ! isNode ( node ) ) {
319
328
throw new Error ( `Invalid AST Node: ${ inspect ( node ) } .` ) ;
320
329
}
321
- const visitFn = getVisitFn ( visitor , node . kind , isLeaving ) ;
322
- if ( visitFn ) {
323
- result = visitFn . call ( visitor , node , key , parent , path , ancestors ) ;
330
+ const visitFn = isLeaving
331
+ ? enterLeaveMap . get ( node . kind ) ?. leave
332
+ : enterLeaveMap . get ( node . kind ) ?. enter ;
324
333
325
- if ( result === BREAK ) {
326
- break ;
327
- }
334
+ result = visitFn ?. call ( visitor , node , key , parent , path , ancestors ) ;
335
+
336
+ if ( result === BREAK ) {
337
+ break ;
338
+ }
328
339
329
- if ( result === false ) {
330
- if ( ! isLeaving ) {
340
+ if ( result === false ) {
341
+ if ( ! isLeaving ) {
342
+ path . pop ( ) ;
343
+ continue ;
344
+ }
345
+ } else if ( result !== undefined ) {
346
+ edits . push ( [ key , result ] ) ;
347
+ if ( ! isLeaving ) {
348
+ if ( isNode ( result ) ) {
349
+ node = result ;
350
+ } else {
331
351
path . pop ( ) ;
332
352
continue ;
333
353
}
334
- } else if ( result !== undefined ) {
335
- edits . push ( [ key , result ] ) ;
336
- if ( ! isLeaving ) {
337
- if ( isNode ( result ) ) {
338
- node = result ;
339
- } else {
340
- path . pop ( ) ;
341
- continue ;
342
- }
343
- }
344
354
}
345
355
}
346
356
}
@@ -380,16 +390,31 @@ export function visit(
380
390
export function visitInParallel (
381
391
visitors : ReadonlyArray < ASTVisitor > ,
382
392
) : ASTVisitor {
383
- const skipping = new Array ( visitors . length ) ;
384
-
385
- return {
386
- enter ( ...args ) {
387
- const node = args [ 0 ] ;
388
- for ( let i = 0 ; i < visitors . length ; i ++ ) {
389
- if ( skipping [ i ] == null ) {
390
- const fn = getVisitFn ( visitors [ i ] , node . kind , /* isLeaving */ false ) ;
391
- if ( fn ) {
392
- const result = fn . apply ( visitors [ i ] , args ) ;
393
+ const skipping = new Array ( visitors . length ) . fill ( null ) ;
394
+ const mergedVisitor = Object . create ( null ) ;
395
+
396
+ for ( const kind of Object . values ( Kind ) ) {
397
+ let hasVisitor = false ;
398
+ const enterList = new Array ( visitors . length ) . fill ( undefined ) ;
399
+ const leaveList = new Array ( visitors . length ) . fill ( undefined ) ;
400
+
401
+ for ( let i = 0 ; i < visitors . length ; ++ i ) {
402
+ const { enter, leave } = getEnterLeaveForKind ( visitors [ i ] , kind ) ;
403
+ hasVisitor ||= enter != null || leave != null ;
404
+ enterList [ i ] = enter ;
405
+ leaveList [ i ] = leave ;
406
+ }
407
+
408
+ if ( ! hasVisitor ) {
409
+ continue ;
410
+ }
411
+
412
+ const mergedEnterLeave : EnterLeaveVisitor < ASTNode > = {
413
+ enter ( ...args ) {
414
+ const node = args [ 0 ] ;
415
+ for ( let i = 0 ; i < visitors . length ; i ++ ) {
416
+ if ( skipping [ i ] === null ) {
417
+ const result = enterList [ i ] ?. apply ( visitors [ i ] , args ) ;
393
418
if ( result === false ) {
394
419
skipping [ i ] = node ;
395
420
} else if ( result === BREAK ) {
@@ -399,50 +424,65 @@ export function visitInParallel(
399
424
}
400
425
}
401
426
}
402
- }
403
- } ,
404
- leave ( ...args ) {
405
- const node = args [ 0 ] ;
406
- for ( let i = 0 ; i < visitors . length ; i ++ ) {
407
- if ( skipping [ i ] == null ) {
408
- const fn = getVisitFn ( visitors [ i ] , node . kind , /* isLeaving */ true ) ;
409
- if ( fn ) {
410
- const result = fn . apply ( visitors [ i ] , args ) ;
427
+ } ,
428
+ leave ( ...args ) {
429
+ const node = args [ 0 ] ;
430
+ for ( let i = 0 ; i < visitors . length ; i ++ ) {
431
+ if ( skipping [ i ] === null ) {
432
+ const result = leaveList [ i ] ?. apply ( visitors [ i ] , args ) ;
411
433
if ( result === BREAK ) {
412
434
skipping [ i ] = BREAK ;
413
435
} else if ( result !== undefined && result !== false ) {
414
436
return result ;
415
437
}
438
+ } else if ( skipping [ i ] === node ) {
439
+ skipping [ i ] = null ;
416
440
}
417
- } else if ( skipping [ i ] === node ) {
418
- skipping [ i ] = null ;
419
441
}
420
- }
421
- } ,
422
- } ;
442
+ } ,
443
+ } ;
444
+
445
+ mergedVisitor [ kind ] = mergedEnterLeave ;
446
+ }
447
+
448
+ return mergedVisitor ;
423
449
}
424
450
425
451
/**
426
- * Given a visitor instance, if it is leaving or not, and a node kind, return
427
- * the function the visitor runtime should call.
452
+ * Given a visitor instance and a node kind, return EnterLeaveVisitor for that kind.
428
453
*/
429
- export function getVisitFn (
454
+ export function getEnterLeaveForKind (
430
455
visitor : ASTVisitor ,
431
456
kind : keyof ASTKindToNode ,
432
- isLeaving : boolean ,
433
- ) : Maybe < ASTVisitFn < ASTNode > > {
457
+ ) : EnterLeaveVisitor < ASTNode > {
434
458
const kindVisitor :
435
459
| ASTVisitFn < ASTNode >
436
460
| EnterLeaveVisitor < ASTNode >
437
461
| undefined = ( visitor as any ) [ kind ] ;
438
- if ( kindVisitor ) {
439
- if ( typeof kindVisitor === 'function' ) {
440
- // { Kind() {} }
441
- return isLeaving ? undefined : kindVisitor ;
442
- }
462
+
463
+ if ( typeof kindVisitor === 'object' ) {
443
464
// { Kind: { enter() {}, leave() {} } }
444
- return isLeaving ? kindVisitor . leave : kindVisitor . enter ;
465
+ return kindVisitor ;
466
+ } else if ( typeof kindVisitor === 'function' ) {
467
+ // { Kind() {} }
468
+ return { enter : kindVisitor , leave : undefined } ;
445
469
}
470
+
446
471
// { enter() {}, leave() {} }
447
- return isLeaving ? ( visitor as any ) . leave : ( visitor as any ) . enter ;
472
+ return { enter : ( visitor as any ) . enter , leave : ( visitor as any ) . leave } ;
473
+ }
474
+
475
+ /**
476
+ * Given a visitor instance, if it is leaving or not, and a node kind, return
477
+ * the function the visitor runtime should call.
478
+ *
479
+ * @deprecated Please use `getEnterLeaveForKind` instead. Will be removed in v17
480
+ */
481
+ export function getVisitFn (
482
+ visitor : ASTVisitor ,
483
+ kind : keyof ASTKindToNode ,
484
+ isLeaving : boolean ,
485
+ ) : ASTVisitFn < ASTNode > | undefined {
486
+ const { enter, leave } = getEnterLeaveForKind ( visitor , kind ) ;
487
+ return isLeaving ? leave : enter ;
448
488
}
0 commit comments