@@ -643,6 +643,7 @@ export class YAMLSchemaService extends JSONSchemaService {
643643 dialect ?: SchemaDialect ;
644644 recursiveAnchorBase ?: string ;
645645 inheritedDynamicScope ?: Map < string , JSONSchema [ ] > ;
646+ siblingRefCycleKeys ?: Set < string > ;
646647 } ;
647648 const toWalk : WalkItem [ ] = [ { node, baseURL : parentSchemaURL , recursiveAnchorBase, inheritedDynamicScope } ] ;
648649 const seen = new WeakSet < JSONSchema > ( ) ; // prevents re-walking the same schema object graph
@@ -656,7 +657,8 @@ export class YAMLSchemaService extends JSONSchemaService {
656657 nodeBaseURL : string ,
657658 nodeDialect : SchemaDialect ,
658659 recursiveAnchorBase ?: string ,
659- inheritedDynamicScope ?: Map < string , JSONSchema [ ] >
660+ inheritedDynamicScope ?: Map < string , JSONSchema [ ] > ,
661+ siblingRefCycleKeys ?: Set < string >
660662 ) : void => {
661663 const currentDynamicScope = _addResourceDynamicAnchors ( inheritedDynamicScope , nodeBaseURL ) ;
662664
@@ -708,28 +710,57 @@ export class YAMLSchemaService extends JSONSchemaService {
708710 } ;
709711
710712 const seenRefs = new Set < string > ( ) ;
713+
714+ const _mergeIfResourceAlreadyInResolutionStack = ( ref : string , resolvedResource : string , frag : string ) : boolean => {
715+ if ( ! resolutionStack . has ( resolvedResource ) ) return false ;
716+ if ( ! seenRefs . has ( ref ) ) {
717+ const source = resourceIndexByUri . get ( resolvedResource ) ?. root ;
718+ if ( source && typeof source === 'object' ) {
719+ _merge ( next , source , resolvedResource , frag , ! ! recursiveAnchorBase ) ;
720+ }
721+ seenRefs . add ( ref ) ;
722+ }
723+ return true ;
724+ } ;
725+
711726 while ( next . $dynamicRef || next . $recursiveRef || next . $ref ) {
712727 const isDynamicRef = typeof next . $dynamicRef === 'string' ;
713728 const isRecursiveRef = ! isDynamicRef && typeof next . $recursiveRef === 'string' ;
714729 const rawRef = next . $dynamicRef ?? next . $recursiveRef ?? next . $ref ;
715730 if ( typeof rawRef !== 'string' ) break ;
716731 next . _$ref = rawRef ;
717732
733+ // parse ref into base URI and fragment
734+ const ref = decodeURIComponent ( rawRef ) ;
735+ const segments = ref . split ( '#' , 2 ) ;
736+ const baseUri = segments [ 0 ] ;
737+ const frag = segments . length > 1 ? segments [ 1 ] : '' ;
738+ const resolvedRefKey = `${ baseUri ? _resolveAgainstBase ( nodeBaseURL , baseUri ) : nodeBaseURL } #${ frag } ` ;
739+
718740 if ( _hasRefSiblings ( next ) ) {
719741 // Draft-07 and earlier: ignore siblings
720742 if ( nodeDialect === SchemaDialect . draft04 || nodeDialect === SchemaDialect . draft07 ) {
721743 _stripRefSiblings ( next ) ;
722744 } else {
745+ if ( siblingRefCycleKeys ?. has ( resolvedRefKey ) ) break ;
746+
723747 // Draft-2019+: support sibling keywords
724748 _rewriteRefWithSiblingsToAllOf ( next ) ;
725749 if ( Array . isArray ( next . allOf ) ) {
726- for ( const entry of next . allOf ) {
750+ for ( let i = 0 ; i < next . allOf . length ; i ++ ) {
751+ const entry = next . allOf [ i ] ;
727752 if ( entry && typeof entry === 'object' ) {
753+ let nextSiblingRefCycleKeys : Set < string > | undefined ;
754+ if ( i === 0 ) {
755+ nextSiblingRefCycleKeys = new Set ( siblingRefCycleKeys ) ;
756+ nextSiblingRefCycleKeys . add ( resolvedRefKey ) ;
757+ }
728758 toWalk . push ( {
729759 node : entry as JSONSchema ,
730760 baseURL : nodeBaseURL ,
731761 recursiveAnchorBase,
732762 inheritedDynamicScope : currentDynamicScope ,
763+ siblingRefCycleKeys : nextSiblingRefCycleKeys ,
733764 } ) ;
734765 }
735766 }
@@ -738,16 +769,10 @@ export class YAMLSchemaService extends JSONSchemaService {
738769 }
739770 }
740771
741- const ref = decodeURIComponent ( rawRef ) ;
742772 delete next . $dynamicRef ;
743773 delete next . $recursiveRef ;
744774 delete next . $ref ;
745775
746- // parse ref into base URI and fragment
747- const segments = ref . split ( '#' , 2 ) ;
748- const baseUri = segments [ 0 ] ;
749- const frag = segments . length > 1 ? segments [ 1 ] : '' ;
750-
751776 // Draft-2019+: $recursiveRef
752777 if ( isRecursiveRef && ( ref === '#' || ref === '' ) ) {
753778 const targetRoot = resourceIndexByUri . get ( nodeBaseURL ) ?. root ;
@@ -796,6 +821,7 @@ export class YAMLSchemaService extends JSONSchemaService {
796821 }
797822
798823 if ( baseUri . length > 0 || targetHasDynamicAnchor ) {
824+ if ( _mergeIfResourceAlreadyInResolutionStack ( ref , resolveResource , frag ) ) continue ;
799825 openPromises . push (
800826 resolveExternalLink (
801827 next ,
@@ -813,6 +839,8 @@ export class YAMLSchemaService extends JSONSchemaService {
813839 }
814840 // normal $ref with external baseUri
815841 else if ( baseUri . length > 0 ) {
842+ const resolvedBaseUri = _resolveAgainstBase ( nodeBaseURL , baseUri ) ;
843+ if ( _mergeIfResourceAlreadyInResolutionStack ( ref , resolvedBaseUri , frag ) ) continue ;
816844 // resolve relative to this node's base URL
817845 openPromises . push (
818846 resolveExternalLink (
@@ -831,7 +859,7 @@ export class YAMLSchemaService extends JSONSchemaService {
831859
832860 // local $ref or $dynamicRef
833861 if ( ! seenRefs . has ( ref ) ) {
834- _merge ( next , parentSchema , nodeBaseURL , frag , ! ! currentDynamicScope ) ;
862+ _merge ( next , parentSchema , nodeBaseURL , frag , isDynamicRef && ! ! currentDynamicScope ) ;
835863 seenRefs . add ( ref ) ;
836864 }
837865 }
@@ -902,7 +930,7 @@ export class YAMLSchemaService extends JSONSchemaService {
902930 const nodeRecursiveAnchorBase = item . recursiveAnchorBase ?? ( next . $recursiveAnchor ? nodeBaseURL : undefined ) ;
903931 if ( seen . has ( next ) ) continue ;
904932 seen . add ( next ) ;
905- _handleRef ( next , nodeBaseURL , nodeDialect , nodeRecursiveAnchorBase , item . inheritedDynamicScope ) ;
933+ _handleRef ( next , nodeBaseURL , nodeDialect , nodeRecursiveAnchorBase , item . inheritedDynamicScope , item . siblingRefCycleKeys ) ;
906934 }
907935 return Promise . all ( openPromises ) ;
908936 } ;
0 commit comments