@@ -604,13 +604,82 @@ export class JSONSchemaService implements IJSONSchemaService {
604604 }
605605 } ;
606606
607- // Check if schemas need scope isolation for unevaluated keywords
608- const needsScopeIsolation = ( section : JSONSchema , target : JSONSchema ) : boolean => {
609- const hasUnevaluated = section . unevaluatedProperties !== undefined || section . unevaluatedItems !== undefined ;
610- const hasSiblingEvaluation = target . properties !== undefined || target . patternProperties !== undefined ||
611- target . additionalProperties !== undefined || target . allOf !== undefined || target . anyOf !== undefined ||
612- target . oneOf !== undefined || target . if !== undefined ;
613- return hasUnevaluated && hasSiblingEvaluation ;
607+ type SchemaKeyword = keyof JSONSchema ;
608+
609+ const hasKeyword = ( schema : JSONSchema , keyword : SchemaKeyword ) : boolean => schema [ keyword ] !== undefined ;
610+ const hasAnyKeyword = ( schema : JSONSchema , keywords : readonly SchemaKeyword [ ] ) : boolean =>
611+ keywords . some ( keyword => hasKeyword ( schema , keyword ) ) ;
612+
613+ const objectPropertyKeywords : readonly SchemaKeyword [ ] = [ 'properties' , 'patternProperties' ] ;
614+ const objectEvaluatorKeywords : readonly SchemaKeyword [ ] = [
615+ ...objectPropertyKeywords ,
616+ 'additionalProperties' ,
617+ 'dependentSchemas' ,
618+ 'allOf' ,
619+ 'anyOf' ,
620+ 'oneOf' ,
621+ 'if' ,
622+ 'then' ,
623+ 'else'
624+ ] ;
625+ const arrayEvaluatorKeywords : readonly SchemaKeyword [ ] = [
626+ 'items' ,
627+ 'additionalItems' ,
628+ 'prefixItems' ,
629+ 'allOf' ,
630+ 'anyOf' ,
631+ 'oneOf' ,
632+ 'if' ,
633+ 'then' ,
634+ 'else'
635+ ] ;
636+ const containsBoundKeywords : readonly SchemaKeyword [ ] = [ 'minContains' , 'maxContains' ] ;
637+ const conditionalBranchKeywords : readonly SchemaKeyword [ ] = [ 'then' , 'else' ] ;
638+ const uses2020_12ArrayAnnotations = schemaDraft === undefined || schemaDraft >= SchemaDraft . v2020_12 ;
639+
640+ // Some keywords only make sense relative to adjacent keywords in the same schema object.
641+ // If they live behind $ref, sibling keywords on the referencing schema must not be
642+ // flattened into the same scope.
643+ const needsScopeIsolation = ( referencedSchema : JSONSchema , referencingSchema : JSONSchema ) : boolean => {
644+ const hasSiblingObjectProperties = hasAnyKeyword ( referencingSchema , objectPropertyKeywords ) ;
645+ if ( hasKeyword ( referencedSchema , 'additionalProperties' ) && hasSiblingObjectProperties ) {
646+ return true ;
647+ }
648+
649+ if ( hasKeyword ( referencedSchema , 'additionalItems' ) && Array . isArray ( referencingSchema . items ) ) {
650+ return true ;
651+ }
652+
653+ const hasSiblingObjectEvaluators = hasAnyKeyword ( referencingSchema , objectEvaluatorKeywords ) ;
654+ if ( hasKeyword ( referencedSchema , 'unevaluatedProperties' ) && hasSiblingObjectEvaluators ) {
655+ return true ;
656+ }
657+
658+ const hasSiblingArrayEvaluators = hasAnyKeyword ( referencingSchema , arrayEvaluatorKeywords ) ||
659+ ( uses2020_12ArrayAnnotations && hasKeyword ( referencingSchema , 'contains' ) ) ;
660+ if ( hasKeyword ( referencedSchema , 'unevaluatedItems' ) && hasSiblingArrayEvaluators ) {
661+ return true ;
662+ }
663+
664+ if ( hasKeyword ( referencedSchema , 'contains' ) && hasAnyKeyword ( referencingSchema , containsBoundKeywords ) ) {
665+ return true ;
666+ }
667+
668+ if ( hasAnyKeyword ( referencedSchema , containsBoundKeywords ) && hasKeyword ( referencingSchema , 'contains' ) ) {
669+ return true ;
670+ }
671+
672+ if ( hasKeyword ( referencedSchema , 'if' ) && hasAnyKeyword ( referencingSchema , conditionalBranchKeywords ) ) {
673+ return true ;
674+ }
675+
676+ if ( hasAnyKeyword ( referencedSchema , conditionalBranchKeywords ) && hasKeyword ( referencingSchema , 'if' ) ) {
677+ return true ;
678+ }
679+
680+ return uses2020_12ArrayAnnotations &&
681+ hasKeyword ( referencedSchema , 'items' ) &&
682+ hasKeyword ( referencingSchema , 'prefixItems' ) ;
614683 } ;
615684
616685 const mergeRef = ( target : JSONSchema , sourceRoot : JSONSchema , sourceHandle : SchemaHandle , refSegment : string | undefined ) : void => {
@@ -653,9 +722,9 @@ export class JSONSchemaService implements IJSONSchemaService {
653722 }
654723 merge ( target , section ) ;
655724 } else if ( needsScopeIsolation ( section , target ) ) {
656- // In JSON Schema 2019-09 or greater, $ref creates a new scope when it has sibling keywords.
657- // When the $ref'd schema has unevaluatedProperties/unevaluatedItems, it should not
658- // see properties/items evaluated by sibling keywords .
725+ // In JSON Schema 2019-09 or greater, $ref creates a new scope when sibling
726+ // keywords would otherwise change the meaning of same-object-dependent keywords
727+ // from the referenced schema .
659728 // To achieve this, we wrap the $ref in an allOf when needed.
660729 const siblingSchema : JSONSchema = { } ;
661730 const refSchema = { ...section } ;
0 commit comments