@@ -177,6 +177,12 @@ class SchemaHandle implements ISchemaHandle {
177177 this . anchors = undefined ;
178178 return hasChanges ;
179179 }
180+
181+ public setSchemaContent ( schemaContent : JSONSchema ) : void {
182+ this . unresolvedSchema = this . service . promise . resolve ( new UnresolvedSchema ( schemaContent ) ) ;
183+ this . resolvedSchema = undefined ;
184+ this . anchors = undefined ;
185+ }
180186}
181187
182188
@@ -503,11 +509,12 @@ export class JSONSchemaService implements IJSONSchemaService {
503509 if ( ! metaschema . $vocabulary || typeof metaschema . $vocabulary !== 'object' ) {
504510 return undefined ;
505511 }
506- const vocabs = new Set < string > ( ) ;
512+ // Both true and false values indicate the vocabulary is active.
513+ // The boolean indicates whether the vocabulary is required (true) or optional (false),
514+ // not whether it's in use. All listed vocabularies should be included.
515+ const vocabs = new Map < string , boolean > ( ) ;
507516 for ( const [ uri , required ] of Object . entries ( metaschema . $vocabulary ) ) {
508- if ( required === true ) {
509- vocabs . add ( uri ) ;
510- }
517+ vocabs . set ( uri , required ) ;
511518 }
512519 return vocabs . size > 0 ? vocabs : undefined ;
513520 } ;
@@ -587,13 +594,25 @@ export class JSONSchemaService implements IJSONSchemaService {
587594 section = findSchemaById ( sourceRoot , sourceHandle , refSegment ) ;
588595 }
589596 if ( section ) {
590- // In JSON Schema 2019-09 or greater, $ref creates a new scope when it has sibling keywords.
591- // When the $ref'd schema has unevaluatedProperties/unevaluatedItems, it should not
592- // see properties/items evaluated by sibling keywords.
593- // To achieve this, we wrap the $ref in an allOf when needed.
594- if ( needsScopeIsolation ( section , target ) ) {
595- // Extract sibling keywords into a separate schema
596- const reservedKeys = new Set ( [ '$ref' , '$defs' , 'definitions' , '$schema' , '$id' , 'id' ] ) ;
597+ const reservedKeys = new Set ( [ '$ref' , '$defs' , 'definitions' , '$schema' , '$id' , 'id' ] ) ;
598+
599+ // In JSON Schema draft-04 through draft-07, $ref completely overrides any sibling keywords.
600+ // Starting in 2019-09, sibling keywords are processed alongside $ref.
601+ // Only strip siblings when schema explicitly declares a pre-2019-09 draft via $schema.
602+ const isPreDraft201909 = schemaDraft !== undefined && schemaDraft < SchemaDraft . v2019_09 ;
603+ if ( isPreDraft201909 ) {
604+ // Clear all sibling keywords from target - $ref takes precedence
605+ for ( const key in target ) {
606+ if ( target . hasOwnProperty ( key ) && ! reservedKeys . has ( key ) ) {
607+ delete ( target as any ) [ key ] ;
608+ }
609+ }
610+ merge ( target , section ) ;
611+ } else if ( needsScopeIsolation ( section , target ) ) {
612+ // In JSON Schema 2019-09 or greater, $ref creates a new scope when it has sibling keywords.
613+ // When the $ref'd schema has unevaluatedProperties/unevaluatedItems, it should not
614+ // see properties/items evaluated by sibling keywords.
615+ // To achieve this, we wrap the $ref in an allOf when needed.
597616 const siblingSchema : JSONSchema = { } ;
598617 const refSchema = { ...section } ;
599618
@@ -728,7 +747,11 @@ export class JSONSchemaService implements IJSONSchemaService {
728747 }
729748
730749 // Collect anchor from this node
731- const anchor = isString ( id ) && id . charAt ( 0 ) === '#' ? id . substring ( 1 ) : node . $anchor ;
750+ // In draft-04/06/07, anchors are defined via $id/#fragment (e.g., "$id": "#myanchor")
751+ // $anchor was introduced in draft-2019-09, so only use it for 2019-09 and later
752+ const fragmentAnchor = isString ( id ) && id . charAt ( 0 ) === '#' ? id . substring ( 1 ) : undefined ;
753+ const dollarAnchor = ( schemaDraft === undefined || schemaDraft >= SchemaDraft . v2019_09 ) ? node . $anchor : undefined ;
754+ const anchor = fragmentAnchor ?? dollarAnchor ;
732755 if ( anchor ) {
733756 if ( result . has ( anchor ) ) {
734757 resolveErrors . push ( toDiagnostic ( l10n . t ( 'Duplicate anchor declaration: \'{0}\'' , anchor ) , ErrorCode . SchemaResolveError ) ) ;
@@ -771,8 +794,13 @@ export class JSONSchemaService implements IJSONSchemaService {
771794 let newBaseUri = currentBaseUri ;
772795 if ( isString ( id ) && id . charAt ( 0 ) !== '#' ) {
773796 const resolvedUri = resolveId ( id , currentBaseUri ) ;
774- if ( ! this . schemasById [ resolvedUri ] ) {
797+ const existingHandle = this . schemasById [ resolvedUri ] ;
798+ if ( ! existingHandle ) {
775799 this . addSchemaHandle ( resolvedUri , node ) ;
800+ } else {
801+ // Update existing handle with embedded schema content
802+ // This ensures embedded schemas take precedence over external schemas
803+ existingHandle . setSchemaContent ( node ) ;
776804 }
777805 newBaseUri = resolvedUri ;
778806 }
@@ -803,7 +831,7 @@ export class JSONSchemaService implements IJSONSchemaService {
803831 // Only extract vocabularies if the meta-schema has a $vocabulary property
804832 // or if it's draft 2019-09 or later which support vocabularies.
805833 const metaschemaDraft = unresolvedMetaschema . schema . $schema ? getSchemaDraftFromId ( unresolvedMetaschema . schema . $schema ) : undefined ;
806- const isDraft2019OrLater = metaschemaDraft === SchemaDraft . v2019_09 || metaschemaDraft === SchemaDraft . v2020_12 ;
834+ const isDraft2019OrLater = metaschemaDraft && metaschemaDraft >= SchemaDraft . v2019_09 ;
807835 const hasVocabulary = unresolvedMetaschema . schema . $vocabulary && typeof unresolvedMetaschema . schema . $vocabulary === 'object' ;
808836
809837 if ( hasVocabulary || isDraft2019OrLater ) {
0 commit comments