@@ -84,6 +84,7 @@ export const ProblemTypeMessages: Record<ProblemType, string> = {
84
84
[ ProblemType . typeMismatchWarning ] : 'Incorrect type. Expected "{0}".' ,
85
85
[ ProblemType . constWarning ] : 'Value must be {0}.' ,
86
86
} ;
87
+
87
88
export interface IProblem {
88
89
location : IRange ;
89
90
severity : DiagnosticSeverity ;
@@ -161,6 +162,7 @@ export abstract class ASTNodeImpl {
161
162
export class NullASTNodeImpl extends ASTNodeImpl implements NullASTNode {
162
163
public type : 'null' = 'null' as const ;
163
164
public value = null ;
165
+
164
166
constructor ( parent : ASTNode , internalNode : Node , offset : number , length ?: number ) {
165
167
super ( parent , internalNode , offset , length ) ;
166
168
}
@@ -281,27 +283,36 @@ export enum EnumMatch {
281
283
282
284
export interface ISchemaCollector {
283
285
schemas : IApplicableSchema [ ] ;
286
+
284
287
add ( schema : IApplicableSchema ) : void ;
288
+
285
289
merge ( other : ISchemaCollector ) : void ;
290
+
286
291
include ( node : ASTNode ) : boolean ;
292
+
287
293
newSub ( ) : ISchemaCollector ;
288
294
}
289
295
290
296
class SchemaCollector implements ISchemaCollector {
291
297
schemas : IApplicableSchema [ ] = [ ] ;
298
+
292
299
constructor (
293
300
private focusOffset = - 1 ,
294
301
private exclude : ASTNode = null
295
302
) { }
303
+
296
304
add ( schema : IApplicableSchema ) : void {
297
305
this . schemas . push ( schema ) ;
298
306
}
307
+
299
308
merge ( other : ISchemaCollector ) : void {
300
309
this . schemas . push ( ...other . schemas ) ;
301
310
}
311
+
302
312
include ( node : ASTNode ) : boolean {
303
313
return ( this . focusOffset === - 1 || contains ( node , this . focusOffset ) ) && node !== this . exclude ;
304
314
}
315
+
305
316
newSub ( ) : ISchemaCollector {
306
317
return new SchemaCollector ( - 1 , this . exclude ) ;
307
318
}
@@ -311,22 +322,27 @@ class NoOpSchemaCollector implements ISchemaCollector {
311
322
private constructor ( ) {
312
323
// ignore
313
324
}
325
+
314
326
// eslint-disable-next-line @typescript-eslint/no-explicit-any
315
327
get schemas ( ) : any [ ] {
316
328
return [ ] ;
317
329
}
330
+
318
331
// eslint-disable-next-line @typescript-eslint/no-unused-vars
319
332
add ( schema : IApplicableSchema ) : void {
320
333
// ignore
321
334
}
335
+
322
336
// eslint-disable-next-line @typescript-eslint/no-unused-vars
323
337
merge ( other : ISchemaCollector ) : void {
324
338
// ignore
325
339
}
340
+
326
341
// eslint-disable-next-line @typescript-eslint/no-unused-vars
327
342
include ( node : ASTNode ) : boolean {
328
343
return true ;
329
344
}
345
+
330
346
newSub ( ) : ISchemaCollector {
331
347
return this ;
332
348
}
@@ -620,13 +636,15 @@ export class JSONDocument {
620
636
* @param focusOffset offsetValue
621
637
* @param exclude excluded Node
622
638
* @param didCallFromAutoComplete true if method called from AutoComplete
639
+ * @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
623
640
* @returns array of applicable schemas
624
641
*/
625
642
public getMatchingSchemas (
626
643
schema : JSONSchema ,
627
644
focusOffset = - 1 ,
628
645
exclude : ASTNode = null ,
629
- didCallFromAutoComplete ?: boolean
646
+ didCallFromAutoComplete ?: boolean ,
647
+ gracefulMatches ?: boolean
630
648
) : IApplicableSchema [ ] {
631
649
const matchingSchemas = new SchemaCollector ( focusOffset , exclude ) ;
632
650
if ( this . root && schema ) {
@@ -635,17 +653,21 @@ export class JSONDocument {
635
653
disableAdditionalProperties : this . disableAdditionalProperties ,
636
654
uri : this . uri ,
637
655
callFromAutoComplete : didCallFromAutoComplete ,
656
+ gracefulMatches : gracefulMatches ,
638
657
} ) ;
639
658
}
640
659
return matchingSchemas . schemas ;
641
660
}
642
661
}
662
+
643
663
interface Options {
644
664
isKubernetes : boolean ;
645
665
disableAdditionalProperties : boolean ;
646
666
uri : string ;
647
667
callFromAutoComplete ?: boolean ;
668
+ gracefulMatches ?: boolean ;
648
669
}
670
+
649
671
function validate (
650
672
node : ASTNode ,
651
673
schema : JSONSchema ,
@@ -655,7 +677,7 @@ function validate(
655
677
options : Options
656
678
// eslint-disable-next-line @typescript-eslint/no-explicit-any
657
679
) : any {
658
- const { isKubernetes, callFromAutoComplete } = options ;
680
+ const { isKubernetes, callFromAutoComplete, gracefulMatches } = options ;
659
681
if ( ! node ) {
660
682
return ;
661
683
}
@@ -952,6 +974,7 @@ function validate(
952
974
} ) ;
953
975
}
954
976
}
977
+
955
978
function getExclusiveLimit ( limit : number | undefined , exclusive : boolean | number | undefined ) : number | undefined {
956
979
if ( isNumber ( exclusive ) ) {
957
980
return exclusive ;
@@ -961,12 +984,14 @@ function validate(
961
984
}
962
985
return undefined ;
963
986
}
987
+
964
988
function getLimit ( limit : number | undefined , exclusive : boolean | number | undefined ) : number | undefined {
965
989
if ( ! isBoolean ( exclusive ) || ! exclusive ) {
966
990
return limit ;
967
991
}
968
992
return undefined ;
969
993
}
994
+
970
995
const exclusiveMinimum = getExclusiveLimit ( schema . minimum , schema . exclusiveMinimum ) ;
971
996
if ( isNumber ( exclusiveMinimum ) && val <= exclusiveMinimum ) {
972
997
validationResult . problems . push ( {
@@ -1102,6 +1127,7 @@ function validate(
1102
1127
}
1103
1128
}
1104
1129
}
1130
+
1105
1131
function _validateArrayNode (
1106
1132
node : ArrayASTNode ,
1107
1133
schema : JSONSchema ,
@@ -1267,7 +1293,12 @@ function validate(
1267
1293
for ( const propertyName of schema . required ) {
1268
1294
if ( seenKeys [ propertyName ] === undefined ) {
1269
1295
const keyNode = node . parent && node . parent . type === 'property' && node . parent . keyNode ;
1270
- const location = keyNode ? { offset : keyNode . offset , length : keyNode . length } : { offset : node . offset , length : 1 } ;
1296
+ const location = keyNode
1297
+ ? { offset : keyNode . offset , length : keyNode . length }
1298
+ : {
1299
+ offset : node . offset ,
1300
+ length : 1 ,
1301
+ } ;
1271
1302
validationResult . problems . push ( {
1272
1303
location : location ,
1273
1304
severity : DiagnosticSeverity . Warning ,
@@ -1520,10 +1551,14 @@ function validate(
1520
1551
return bestMatch ;
1521
1552
}
1522
1553
1554
+ function gracefulMatchFilter ( maxOneMatch : boolean , propertiesValueMatches : number ) : boolean {
1555
+ return gracefulMatches && ! maxOneMatch && callFromAutoComplete && propertiesValueMatches > 0 ;
1556
+ }
1557
+
1523
1558
//genericComparison tries to find the best matching schema using a generic comparison
1524
1559
function genericComparison (
1525
1560
node : ASTNode ,
1526
- maxOneMatch ,
1561
+ maxOneMatch : boolean ,
1527
1562
subValidationResult : ValidationResult ,
1528
1563
bestMatch : IValidationMatch ,
1529
1564
subSchema ,
@@ -1552,7 +1587,8 @@ function validate(
1552
1587
} ;
1553
1588
} else if (
1554
1589
compareResult === 0 ||
1555
- ( ( node . value === null || node . type === 'null' ) && node . length === 0 ) // node with no value can match any schema potentially
1590
+ ( ( node . value === null || node . type === 'null' ) && node . length === 0 ) || // node with no value can match any schema potentially
1591
+ gracefulMatchFilter ( maxOneMatch , subValidationResult . propertiesValueMatches )
1556
1592
) {
1557
1593
// there's already a best matching but we are as good
1558
1594
mergeValidationMatches ( bestMatch , subMatchingSchemas , subValidationResult ) ;
0 commit comments