@@ -638,21 +638,78 @@ async function openapiFilter(oaObj, options) {
638638
639639  // Collect unused components 
640640  const  optFs  =  get ( options ,  'filterSet.unusedComponents' ,  [ ] )  ||  [ ] ; 
641+ 
642+   // First, identify components that are directly unused (not referenced anywhere) 
641643  unusedComp . schemas  =  Object . keys ( comps . schemas  ||  { } ) . filter ( key  =>  ! comps . schemas [ key ] . used ) ; 
642-   if  ( optFs . includes ( 'schemas' ) )  options . unusedComp . schemas  =  [ ...options . unusedComp . schemas ,  ...unusedComp . schemas ] ; 
643644  unusedComp . responses  =  Object . keys ( comps . responses  ||  { } ) . filter ( key  =>  ! comps . responses [ key ] . used ) ; 
644-   if  ( optFs . includes ( 'responses' ) ) 
645-     options . unusedComp . responses  =  [ ...options . unusedComp . responses ,  ...unusedComp . responses ] ; 
646645  unusedComp . parameters  =  Object . keys ( comps . parameters  ||  { } ) . filter ( key  =>  ! comps . parameters [ key ] . used ) ; 
647-   if  ( optFs . includes ( 'parameters' ) ) 
648-     options . unusedComp . parameters  =  [ ...options . unusedComp . parameters ,  ...unusedComp . parameters ] ; 
649646  unusedComp . examples  =  Object . keys ( comps . examples  ||  { } ) . filter ( key  =>  ! comps . examples [ key ] . used ) ; 
650-   if  ( optFs . includes ( 'examples' ) ) 
651-     options . unusedComp . examples  =  [ ...options . unusedComp . examples ,  ...unusedComp . examples ] ; 
652647  unusedComp . requestBodies  =  Object . keys ( comps . requestBodies  ||  { } ) . filter ( key  =>  ! comps . requestBodies [ key ] . used ) ; 
653-   if  ( optFs . includes ( 'requestBodies' ) ) 
654-     options . unusedComp . requestBodies  =  [ ...options . unusedComp . requestBodies ,  ...unusedComp . requestBodies ] ; 
655648  unusedComp . headers  =  Object . keys ( comps . headers  ||  { } ) . filter ( key  =>  ! comps . headers [ key ] . used ) ; 
649+ 
650+   // Then, 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 
683+ 
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+         } 
697+ 
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+       } 
704+     } 
705+   } 
706+ 
707+   // Update options.unusedComp with the newly identified unused components 
708+   if  ( optFs . includes ( 'schemas' ) )  options . unusedComp . schemas  =  [ ...options . unusedComp . schemas ,  ...unusedComp . schemas ] ; 
709+   if  ( optFs . includes ( 'responses' ) )  options . unusedComp . responses  =  [ ...options . unusedComp . responses ,  ...unusedComp . responses ] ; 
710+   if  ( optFs . includes ( 'parameters' ) )  options . unusedComp . parameters  =  [ ...options . unusedComp . parameters ,  ...unusedComp . parameters ] ; 
711+   if  ( optFs . includes ( 'examples' ) )  options . unusedComp . examples  =  [ ...options . unusedComp . examples ,  ...unusedComp . examples ] ; 
712+   if  ( optFs . includes ( 'requestBodies' ) )  options . unusedComp . requestBodies  =  [ ...options . unusedComp . requestBodies ,  ...unusedComp . requestBodies ] ; 
656713  if  ( optFs . includes ( 'headers' ) )  options . unusedComp . headers  =  [ ...options . unusedComp . headers ,  ...unusedComp . headers ] ; 
657714
658715  // Update unusedComp.meta.total after each recursion 
@@ -717,7 +774,7 @@ async function openapiFilter(oaObj, options) {
717774      if  ( 
718775        Array . isArray ( filterSet . preserveEmptyObjects )  && 
719776        ( ( ! [ 'security' ,  'schemas' ,  'default' ] . includes ( this . parent . key )  && 
720-           ! filterSet . preserveEmptyObjects . includes ( this . key ) )  || 
777+              ! filterSet . preserveEmptyObjects . includes ( this . key ) )  || 
721778          ! filterSet . preserveEmptyObjects . some ( v  =>  this . path . includes ( v ) ) ) 
722779      )  { 
723780        // debugFilterStep = 'Filter - Remove empty objects' 
0 commit comments