@@ -81,6 +81,7 @@ export const ProblemTypeMessages: Record<ProblemType, string> = {
8181 [ ProblemType . typeMismatchWarning ] : 'Incorrect type. Expected "{0}".' ,
8282 [ ProblemType . constWarning ] : 'Value must be {0}.' ,
8383} ;
84+
8485export interface IProblem {
8586 location : IRange ;
8687 severity : DiagnosticSeverity ;
@@ -158,6 +159,7 @@ export abstract class ASTNodeImpl {
158159export class NullASTNodeImpl extends ASTNodeImpl implements NullASTNode {
159160 public type : 'null' = 'null' as const ;
160161 public value = null ;
162+
161163 constructor ( parent : ASTNode , internalNode : Node , offset : number , length ?: number ) {
162164 super ( parent , internalNode , offset , length ) ;
163165 }
@@ -278,27 +280,36 @@ export enum EnumMatch {
278280
279281export interface ISchemaCollector {
280282 schemas : IApplicableSchema [ ] ;
283+
281284 add ( schema : IApplicableSchema ) : void ;
285+
282286 merge ( other : ISchemaCollector ) : void ;
287+
283288 include ( node : ASTNode ) : boolean ;
289+
284290 newSub ( ) : ISchemaCollector ;
285291}
286292
287293class SchemaCollector implements ISchemaCollector {
288294 schemas : IApplicableSchema [ ] = [ ] ;
295+
289296 constructor (
290297 private focusOffset = - 1 ,
291298 private exclude : ASTNode = null
292299 ) { }
300+
293301 add ( schema : IApplicableSchema ) : void {
294302 this . schemas . push ( schema ) ;
295303 }
304+
296305 merge ( other : ISchemaCollector ) : void {
297306 this . schemas . push ( ...other . schemas ) ;
298307 }
308+
299309 include ( node : ASTNode ) : boolean {
300310 return ( this . focusOffset === - 1 || contains ( node , this . focusOffset ) ) && node !== this . exclude ;
301311 }
312+
302313 newSub ( ) : ISchemaCollector {
303314 return new SchemaCollector ( - 1 , this . exclude ) ;
304315 }
@@ -308,22 +319,27 @@ class NoOpSchemaCollector implements ISchemaCollector {
308319 private constructor ( ) {
309320 // ignore
310321 }
322+
311323 // eslint-disable-next-line @typescript-eslint/no-explicit-any
312324 get schemas ( ) : any [ ] {
313325 return [ ] ;
314326 }
327+
315328 // eslint-disable-next-line @typescript-eslint/no-unused-vars
316329 add ( schema : IApplicableSchema ) : void {
317330 // ignore
318331 }
332+
319333 // eslint-disable-next-line @typescript-eslint/no-unused-vars
320334 merge ( other : ISchemaCollector ) : void {
321335 // ignore
322336 }
337+
323338 // eslint-disable-next-line @typescript-eslint/no-unused-vars
324339 include ( node : ASTNode ) : boolean {
325340 return true ;
326341 }
342+
327343 newSub ( ) : ISchemaCollector {
328344 return this ;
329345 }
@@ -616,13 +632,15 @@ export class JSONDocument {
616632 * @param focusOffset offsetValue
617633 * @param exclude excluded Node
618634 * @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
619636 * @returns array of applicable schemas
620637 */
621638 public getMatchingSchemas (
622639 schema : JSONSchema ,
623640 focusOffset = - 1 ,
624641 exclude : ASTNode = null ,
625- didCallFromAutoComplete ?: boolean
642+ didCallFromAutoComplete ?: boolean ,
643+ gracefulMatches ?: boolean
626644 ) : IApplicableSchema [ ] {
627645 const matchingSchemas = new SchemaCollector ( focusOffset , exclude ) ;
628646 if ( this . root && schema ) {
@@ -631,17 +649,21 @@ export class JSONDocument {
631649 disableAdditionalProperties : this . disableAdditionalProperties ,
632650 uri : this . uri ,
633651 callFromAutoComplete : didCallFromAutoComplete ,
652+ gracefulMatches : gracefulMatches ,
634653 } ) ;
635654 }
636655 return matchingSchemas . schemas ;
637656 }
638657}
658+
639659interface Options {
640660 isKubernetes : boolean ;
641661 disableAdditionalProperties : boolean ;
642662 uri : string ;
643663 callFromAutoComplete ?: boolean ;
664+ gracefulMatches ?: boolean ;
644665}
666+
645667function validate (
646668 node : ASTNode ,
647669 schema : JSONSchema ,
@@ -651,7 +673,7 @@ function validate(
651673 options : Options
652674 // eslint-disable-next-line @typescript-eslint/no-explicit-any
653675) : any {
654- const { isKubernetes, callFromAutoComplete } = options ;
676+ const { isKubernetes, callFromAutoComplete, gracefulMatches } = options ;
655677 if ( ! node ) {
656678 return ;
657679 }
@@ -942,6 +964,7 @@ function validate(
942964 } ) ;
943965 }
944966 }
967+
945968 function getExclusiveLimit ( limit : number | undefined , exclusive : boolean | number | undefined ) : number | undefined {
946969 if ( isNumber ( exclusive ) ) {
947970 return exclusive ;
@@ -951,12 +974,14 @@ function validate(
951974 }
952975 return undefined ;
953976 }
977+
954978 function getLimit ( limit : number | undefined , exclusive : boolean | number | undefined ) : number | undefined {
955979 if ( ! isBoolean ( exclusive ) || ! exclusive ) {
956980 return limit ;
957981 }
958982 return undefined ;
959983 }
984+
960985 const exclusiveMinimum = getExclusiveLimit ( schema . minimum , schema . exclusiveMinimum ) ;
961986 if ( isNumber ( exclusiveMinimum ) && val <= exclusiveMinimum ) {
962987 validationResult . problems . push ( {
@@ -1086,6 +1111,7 @@ function validate(
10861111 }
10871112 }
10881113 }
1114+
10891115 function _validateArrayNode (
10901116 node : ArrayASTNode ,
10911117 schema : JSONSchema ,
@@ -1247,7 +1273,12 @@ function validate(
12471273 for ( const propertyName of schema . required ) {
12481274 if ( seenKeys [ propertyName ] === undefined ) {
12491275 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+ } ;
12511282 validationResult . problems . push ( {
12521283 location : location ,
12531284 severity : DiagnosticSeverity . Warning ,
@@ -1490,10 +1521,14 @@ function validate(
14901521 return bestMatch ;
14911522 }
14921523
1524+ function gracefulMatchFilter ( maxOneMatch : boolean , propertiesValueMatches : number ) : boolean {
1525+ return gracefulMatches && ! maxOneMatch && callFromAutoComplete && propertiesValueMatches > 0 ;
1526+ }
1527+
14931528 //genericComparison tries to find the best matching schema using a generic comparison
14941529 function genericComparison (
14951530 node : ASTNode ,
1496- maxOneMatch ,
1531+ maxOneMatch : boolean ,
14971532 subValidationResult : ValidationResult ,
14981533 bestMatch : IValidationMatch ,
14991534 subSchema ,
@@ -1522,7 +1557,8 @@ function validate(
15221557 } ;
15231558 } else if (
15241559 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 )
15261562 ) {
15271563 // there's already a best matching but we are as good
15281564 mergeValidationMatches ( bestMatch , subMatchingSchemas , subValidationResult ) ;
0 commit comments