@@ -81,6 +81,7 @@ export const ProblemTypeMessages: Record<ProblemType, string> = {
81
81
[ ProblemType . typeMismatchWarning ] : 'Incorrect type. Expected "{0}".' ,
82
82
[ ProblemType . constWarning ] : 'Value must be {0}.' ,
83
83
} ;
84
+
84
85
export interface IProblem {
85
86
location : IRange ;
86
87
severity : DiagnosticSeverity ;
@@ -158,6 +159,7 @@ export abstract class ASTNodeImpl {
158
159
export class NullASTNodeImpl extends ASTNodeImpl implements NullASTNode {
159
160
public type : 'null' = 'null' as const ;
160
161
public value = null ;
162
+
161
163
constructor ( parent : ASTNode , internalNode : Node , offset : number , length ?: number ) {
162
164
super ( parent , internalNode , offset , length ) ;
163
165
}
@@ -278,27 +280,36 @@ export enum EnumMatch {
278
280
279
281
export interface ISchemaCollector {
280
282
schemas : IApplicableSchema [ ] ;
283
+
281
284
add ( schema : IApplicableSchema ) : void ;
285
+
282
286
merge ( other : ISchemaCollector ) : void ;
287
+
283
288
include ( node : ASTNode ) : boolean ;
289
+
284
290
newSub ( ) : ISchemaCollector ;
285
291
}
286
292
287
293
class SchemaCollector implements ISchemaCollector {
288
294
schemas : IApplicableSchema [ ] = [ ] ;
295
+
289
296
constructor (
290
297
private focusOffset = - 1 ,
291
298
private exclude : ASTNode = null
292
299
) { }
300
+
293
301
add ( schema : IApplicableSchema ) : void {
294
302
this . schemas . push ( schema ) ;
295
303
}
304
+
296
305
merge ( other : ISchemaCollector ) : void {
297
306
this . schemas . push ( ...other . schemas ) ;
298
307
}
308
+
299
309
include ( node : ASTNode ) : boolean {
300
310
return ( this . focusOffset === - 1 || contains ( node , this . focusOffset ) ) && node !== this . exclude ;
301
311
}
312
+
302
313
newSub ( ) : ISchemaCollector {
303
314
return new SchemaCollector ( - 1 , this . exclude ) ;
304
315
}
@@ -308,22 +319,27 @@ class NoOpSchemaCollector implements ISchemaCollector {
308
319
private constructor ( ) {
309
320
// ignore
310
321
}
322
+
311
323
// eslint-disable-next-line @typescript-eslint/no-explicit-any
312
324
get schemas ( ) : any [ ] {
313
325
return [ ] ;
314
326
}
327
+
315
328
// eslint-disable-next-line @typescript-eslint/no-unused-vars
316
329
add ( schema : IApplicableSchema ) : void {
317
330
// ignore
318
331
}
332
+
319
333
// eslint-disable-next-line @typescript-eslint/no-unused-vars
320
334
merge ( other : ISchemaCollector ) : void {
321
335
// ignore
322
336
}
337
+
323
338
// eslint-disable-next-line @typescript-eslint/no-unused-vars
324
339
include ( node : ASTNode ) : boolean {
325
340
return true ;
326
341
}
342
+
327
343
newSub ( ) : ISchemaCollector {
328
344
return this ;
329
345
}
@@ -616,13 +632,15 @@ export class JSONDocument {
616
632
* @param focusOffset offsetValue
617
633
* @param exclude excluded Node
618
634
* @param didCallFromAutoComplete true if method called from AutoComplete
635
+ * @param gracefulMatches true if graceful matching should be done, meaning that if at least one property is validated in a sub schema, it's kept as a candidate
619
636
* @returns array of applicable schemas
620
637
*/
621
638
public getMatchingSchemas (
622
639
schema : JSONSchema ,
623
640
focusOffset = - 1 ,
624
641
exclude : ASTNode = null ,
625
- didCallFromAutoComplete ?: boolean
642
+ didCallFromAutoComplete ?: boolean ,
643
+ gracefulMatches ?: boolean
626
644
) : IApplicableSchema [ ] {
627
645
const matchingSchemas = new SchemaCollector ( focusOffset , exclude ) ;
628
646
if ( this . root && schema ) {
@@ -631,17 +649,21 @@ export class JSONDocument {
631
649
disableAdditionalProperties : this . disableAdditionalProperties ,
632
650
uri : this . uri ,
633
651
callFromAutoComplete : didCallFromAutoComplete ,
652
+ gracefulMatches : gracefulMatches ,
634
653
} ) ;
635
654
}
636
655
return matchingSchemas . schemas ;
637
656
}
638
657
}
658
+
639
659
interface Options {
640
660
isKubernetes : boolean ;
641
661
disableAdditionalProperties : boolean ;
642
662
uri : string ;
643
663
callFromAutoComplete ?: boolean ;
664
+ gracefulMatches ?: boolean ;
644
665
}
666
+
645
667
function validate (
646
668
node : ASTNode ,
647
669
schema : JSONSchema ,
@@ -651,7 +673,7 @@ function validate(
651
673
options : Options
652
674
// eslint-disable-next-line @typescript-eslint/no-explicit-any
653
675
) : any {
654
- const { isKubernetes, callFromAutoComplete } = options ;
676
+ const { isKubernetes, callFromAutoComplete, gracefulMatches } = options ;
655
677
if ( ! node ) {
656
678
return ;
657
679
}
@@ -942,6 +964,7 @@ function validate(
942
964
} ) ;
943
965
}
944
966
}
967
+
945
968
function getExclusiveLimit ( limit : number | undefined , exclusive : boolean | number | undefined ) : number | undefined {
946
969
if ( isNumber ( exclusive ) ) {
947
970
return exclusive ;
@@ -951,12 +974,14 @@ function validate(
951
974
}
952
975
return undefined ;
953
976
}
977
+
954
978
function getLimit ( limit : number | undefined , exclusive : boolean | number | undefined ) : number | undefined {
955
979
if ( ! isBoolean ( exclusive ) || ! exclusive ) {
956
980
return limit ;
957
981
}
958
982
return undefined ;
959
983
}
984
+
960
985
const exclusiveMinimum = getExclusiveLimit ( schema . minimum , schema . exclusiveMinimum ) ;
961
986
if ( isNumber ( exclusiveMinimum ) && val <= exclusiveMinimum ) {
962
987
validationResult . problems . push ( {
@@ -1086,6 +1111,7 @@ function validate(
1086
1111
}
1087
1112
}
1088
1113
}
1114
+
1089
1115
function _validateArrayNode (
1090
1116
node : ArrayASTNode ,
1091
1117
schema : JSONSchema ,
@@ -1247,7 +1273,12 @@ function validate(
1247
1273
for ( const propertyName of schema . required ) {
1248
1274
if ( seenKeys [ propertyName ] === undefined ) {
1249
1275
const keyNode = node . parent && node . parent . type === 'property' && node . parent . keyNode ;
1250
- const location = keyNode ? { offset : keyNode . offset , length : keyNode . length } : { offset : node . offset , length : 1 } ;
1276
+ const location = keyNode
1277
+ ? { offset : keyNode . offset , length : keyNode . length }
1278
+ : {
1279
+ offset : node . offset ,
1280
+ length : 1 ,
1281
+ } ;
1251
1282
validationResult . problems . push ( {
1252
1283
location : location ,
1253
1284
severity : DiagnosticSeverity . Warning ,
@@ -1490,10 +1521,14 @@ function validate(
1490
1521
return bestMatch ;
1491
1522
}
1492
1523
1524
+ function gracefulMatchFilter ( maxOneMatch : boolean , propertiesValueMatches : number ) : boolean {
1525
+ return gracefulMatches && ! maxOneMatch && callFromAutoComplete && propertiesValueMatches > 0 ;
1526
+ }
1527
+
1493
1528
//genericComparison tries to find the best matching schema using a generic comparison
1494
1529
function genericComparison (
1495
1530
node : ASTNode ,
1496
- maxOneMatch ,
1531
+ maxOneMatch : boolean ,
1497
1532
subValidationResult : ValidationResult ,
1498
1533
bestMatch : IValidationMatch ,
1499
1534
subSchema ,
@@ -1522,7 +1557,8 @@ function validate(
1522
1557
} ;
1523
1558
} else if (
1524
1559
compareResult === 0 ||
1525
- ( ( node . value === null || node . type === 'null' ) && node . length === 0 ) // node with no value can match any schema potentially
1560
+ ( ( node . value === null || node . type === 'null' ) && node . length === 0 ) || // node with no value can match any schema potentially
1561
+ gracefulMatchFilter ( maxOneMatch , subValidationResult . propertiesValueMatches )
1526
1562
) {
1527
1563
// there's already a best matching but we are as good
1528
1564
mergeValidationMatches ( bestMatch , subMatchingSchemas , subValidationResult ) ;
0 commit comments