@@ -249,7 +249,19 @@ function arePropertiesMutuallyExclusive(
249249 return { isExclusive : false } ;
250250}
251251
252- function areSignaturesMutuallyExclusive ( sig1 : SchemaSignature , sig2 : SchemaSignature ) : ReturnType {
252+ function areSignaturesMutuallyExclusive (
253+ sig1 : SchemaSignature ,
254+ sig2 : SchemaSignature ,
255+ depth : number = 0
256+ ) : ReturnType {
257+ const MAX_DEPTH = 10 ;
258+ if ( depth > MAX_DEPTH ) {
259+ // If we've gone too deep, assume they're not mutually exclusive to be safe
260+ return {
261+ isExclusive : false ,
262+ reason : 'Maximum recursion depth reached while checking schema exclusivity.' ,
263+ } ;
264+ }
253265 // Check top-level constraint exclusivity (types, formats, const values, enum values, and all constraints)
254266 const topLevelResult = arePropertiesMutuallyExclusive ( sig1 , sig2 ) ;
255267 if ( ! topLevelResult . isExclusive && topLevelResult . reason ) {
@@ -333,14 +345,80 @@ function areSignaturesMutuallyExclusive(sig1: SchemaSignature, sig2: SchemaSigna
333345 const addlProps1 = sig1 . additionalProperties ;
334346 const addlProps2 = sig2 . additionalProperties ;
335347
336- const allowsAdditional1 = addlProps1 !== false ;
337- const allowsAdditional2 = addlProps2 !== false ;
348+ // Determine the type of additionalProperties for each schema
349+ const isBoolean1 = typeof addlProps1 === 'boolean' ;
350+ const isBoolean2 = typeof addlProps2 === 'boolean' ;
351+ const isSchema1 = isPlainObject ( addlProps1 ) ;
352+ const isSchema2 = isPlainObject ( addlProps2 ) ;
353+
354+ // Case 1: Both are booleans
355+ if ( isBoolean1 && isBoolean2 ) {
356+ const allowsAdditional1 = addlProps1 !== false ;
357+ const allowsAdditional2 = addlProps2 !== false ;
338358
339- if ( allowsAdditional1 !== allowsAdditional2 ) {
340- return {
341- isExclusive : false ,
342- reason : 'One schema allows additional properties while the other does not.' ,
343- } ;
359+ // If one allows and the other doesn't, they're not mutually exclusive (ambiguous)
360+ if ( allowsAdditional1 !== allowsAdditional2 ) {
361+ return {
362+ isExclusive : false ,
363+ reason : 'One schema allows additional properties while the other does not.' ,
364+ } ;
365+ }
366+ }
367+ // Case 2: One is boolean, the other is a schema/ref
368+ else if ( ( isBoolean1 && isSchema2 ) || ( isSchema1 && isBoolean2 ) ) {
369+ // If boolean is false, it means no additional properties allowed
370+ // If the other has a schema, they conflict (one allows with constraints, one disallows)
371+ const boolValue = isBoolean1 ? addlProps1 : addlProps2 ;
372+
373+ if ( boolValue === false ) {
374+ return {
375+ isExclusive : false ,
376+ reason :
377+ 'One schema disallows additional properties (false) while the other defines a schema for them.' ,
378+ } ;
379+ }
380+ // If boolean is true, one allows any additional properties, the other allows with constraints
381+ // This could be mutually exclusive if other constraints distinguish them, but the
382+ // additionalProperties themselves don't provide discrimination
383+ }
384+ // Case 3: Both are schemas/refs
385+ else if ( isSchema1 && isSchema2 ) {
386+ // Check if the additionalProperties schemas are mutually exclusive
387+ const addlPropsResult = areSignaturesMutuallyExclusive (
388+ addlProps1 as SchemaSignature ,
389+ addlProps2 as SchemaSignature ,
390+ depth + 1
391+ ) ;
392+
393+ // If the additionalProperties schemas are not mutually exclusive, it creates ambiguity
394+ if ( ! addlPropsResult . isExclusive ) {
395+ return {
396+ isExclusive : false ,
397+ reason : `Schemas have overlapping additionalProperties definitions. ${
398+ addlPropsResult . reason || ''
399+ } `,
400+ } ;
401+ }
402+ // If they ARE mutually exclusive, this helps distinguish the schemas
403+ // The additionalProperties provide discrimination, so schemas are mutually exclusive
404+ return { isExclusive : true } ;
405+ }
406+ // Case 4: One or both are undefined (default is true in JSON Schema)
407+ else {
408+ // Default behavior: if undefined, it typically means additionalProperties: true
409+ // If one is explicitly false and the other is undefined, they differ
410+ if ( addlProps1 === false || addlProps2 === false ) {
411+ const otherIsUndefined =
412+ addlProps1 === false ? ! isDefined ( addlProps2 ) : ! isDefined ( addlProps1 ) ;
413+
414+ if ( otherIsUndefined ) {
415+ return {
416+ isExclusive : false ,
417+ reason :
418+ 'One schema explicitly disallows additional properties while the other allows them by default.' ,
419+ } ;
420+ }
421+ }
344422 }
345423 }
346424
0 commit comments