@@ -245,7 +245,7 @@ async function openapiFilter(oaObj, options) {
245245    } 
246246
247247    // Register components usage 
248-     if  ( this . key  ===  '$ref' )  { 
248+     if  ( this . key  ===  '$ref'   &&   typeof   node   ===   'string' )  { 
249249      if  ( node . startsWith ( '#/components/schemas/' ) )  { 
250250        const  compSchema  =  node . replace ( '#/components/schemas/' ,  '' ) ; 
251251        comps . schemas [ compSchema ]  =  { ...comps . schemas [ compSchema ] ,  used : true } ; 
@@ -647,63 +647,56 @@ async function openapiFilter(oaObj, options) {
647647  unusedComp . requestBodies  =  Object . keys ( comps . requestBodies  ||  { } ) . filter ( key  =>  ! comps . requestBodies [ key ] . used ) ; 
648648  unusedComp . headers  =  Object . keys ( comps . headers  ||  { } ) . filter ( key  =>  ! comps . headers [ key ] . used ) ; 
649649
650-   // Identify components that are only used by other unused components 
651-   let  foundNewUnused  =  true ; 
652-   while  ( foundNewUnused )  { 
653-     foundNewUnused  =  false ; 
654- 
655-     // Check each component type 
656-     for  ( const  compType  of  [ 'schemas' ,  'responses' ,  'parameters' ,  'examples' ,  'requestBodies' ,  'headers' ] )  { 
657-       // Get all components of this type that are currently marked as used 
658-       const  usedComps  =  Object . keys ( comps [ compType ]  ||  { } ) . filter ( 
659-         key  =>  comps [ compType ] [ key ] . used  &&  ! unusedComp [ compType ] . includes ( key ) 
660-       ) ; 
661- 
662-       // For each used component, check if it's only used by unused components 
663-       for  ( const  compKey  of  usedComps )  { 
664-         let  isOnlyUsedByUnusedComps  =  true ; 
665- 
666-         // Check if this component is used in paths (directly used) 
667-         traverse ( jsonObj . paths  ||  { } ) . forEach ( function  ( node )  { 
668-           if  ( this . key  ===  '$ref'  &&  node  ===  `#/components/${ compType } ${ compKey }  )  { 
669-             isOnlyUsedByUnusedComps  =  false ; 
670-             this . stop ( ) ; 
671-           } 
672-         } ) ; 
673- 
674-         if  ( isOnlyUsedByUnusedComps )  { 
675-           // Check if this component is used by any component that is not in the unused list 
676-           for  ( const  otherCompType  of  [ 'schemas' ,  'responses' ,  'parameters' ,  'examples' ,  'requestBodies' ,  'headers' ] )  { 
677-             const  otherUsedComps  =  Object . keys ( comps [ otherCompType ]  ||  { } ) . filter ( 
678-               key  =>  comps [ otherCompType ] [ key ] . used  &&  ! unusedComp [ otherCompType ] . includes ( key ) 
679-             ) ; 
680- 
681-             for  ( const  otherCompKey  of  otherUsedComps )  { 
682-               if  ( otherCompKey  ===  compKey  &&  otherCompType  ===  compType )  continue ;  // Skip self-reference 
650+   const  refGraph  =  {  schemas :{ } ,  responses :{ } ,  parameters :{ } , 
651+     examples :{ } ,  requestBodies :{ } ,  headers :{ }  } ; 
652+   const  rootRefs  =  new  Set ( ) ; 
683653
684-               traverse ( jsonObj . components ?. [ otherCompType ] ?. [ otherCompKey ]  ||  { } ) . forEach ( function  ( node )  { 
685-                 if  ( this . key  ===  '$ref'  &&  node  ===  `#/components/${ compType } ${ compKey }  )  { 
686-                   isOnlyUsedByUnusedComps  =  false ; 
687-                   this . stop ( ) ; 
688-                 } 
689-               } ) ; 
690- 
691-               if  ( ! isOnlyUsedByUnusedComps )  break ; 
692-             } 
693- 
694-             if  ( ! isOnlyUsedByUnusedComps )  break ; 
695-           } 
696-         } 
654+   traverse ( jsonObj ) . forEach ( function ( node )  { 
655+     if  ( this . key  !==  '$ref'  ||  typeof  node  !==  'string' )  return ; 
656+     const  m  =  node . match ( / ^ # \/ c o m p o n e n t s \/ ( [ ^ \/ ] + ) \/ ( .+ ) $ / ) ; 
657+     if  ( ! m )  return ; 
658+     const  [ ,  tgtType ,  tgtKey ]  =  m ; 
659+     if  ( this . path [ 0 ]  ===  'components' )  { 
660+       const  [  ,  ownType ,  ownKey  ]  =  this . path ; 
661+       refGraph [ ownType ]       ||=  { } ; 
662+       refGraph [ ownType ] [ ownKey ]  ||=  [ ] ; 
663+       refGraph [ ownType ] [ ownKey ] . push ( {  type : tgtType ,  key : tgtKey  } ) ; 
664+     }  else  { 
665+       rootRefs . add ( `${ tgtType } ${ tgtKey }  ) ; 
666+     } 
667+   } ) ; 
697668
698-         // If this component is only used by unused components, mark it as unused 
699-         if  ( isOnlyUsedByUnusedComps )  { 
700-           unusedComp [ compType ] . push ( compKey ) ; 
701-           foundNewUnused  =  true ; 
702-         } 
703-       } 
669+   // 2) BFS to mark all reachable components 
670+   const  visited  =  new  Set ( ) ; 
671+   const  queue    =  [ ...rootRefs ] ; 
672+   while  ( queue . length )  { 
673+     const  id  =  queue . shift ( ) ; 
674+     if  ( visited . has ( id ) )  continue ; 
675+     visited . add ( id ) ; 
676+     const  [ type ,  key ]  =  id . split ( ':' ) ; 
677+     const  edges  =  ( refGraph [ type ]  &&  refGraph [ type ] [ key ] )  ||  [ ] ; 
678+     for  ( const  { type : ct ,  key : ck }  of  edges )  { 
679+       const  childId  =  `${ ct } ${ ck }  ; 
680+       if  ( ! visited . has ( childId ) )  queue . push ( childId ) ; 
704681    } 
705682  } 
706683
684+   // 3) collect everything *not* visited as unused 
685+   // const unusedComp = { schemas:[], responses:[], parameters:[], examples:[], requestBodies:[], headers:[] }; 
686+   for  ( const  t  of  [ 'schemas' , 'responses' , 'parameters' , 'examples' , 'requestBodies' , 'headers' ] )  { 
687+     unusedComp [ t ]  =  Object 
688+       . keys ( comps [ t ] || { } ) 
689+       . filter ( k  =>  ! visited . has ( `${ t } ${ k }  ) ) ; 
690+   } 
691+   unusedComp . meta  =  { 
692+     total : unusedComp . schemas . length 
693+          +  unusedComp . responses . length 
694+          +  unusedComp . parameters . length 
695+          +  unusedComp . examples . length 
696+          +  unusedComp . requestBodies . length 
697+          +  unusedComp . headers . length 
698+   } ; 
699+ 
707700  // Update options.unusedComp with the newly identified unused components 
708701  if  ( optFs . includes ( 'schemas' ) )  options . unusedComp . schemas  =  [ ...options . unusedComp . schemas ,  ...unusedComp . schemas ] ; 
709702  if  ( optFs . includes ( 'responses' ) ) 
0 commit comments