@@ -174,7 +174,7 @@ export class ASTNode {
174174 isValid = ( < string [ ] > schema . type ) . indexOf ( 'string' ) >= 0 ;
175175 }
176176 if ( ! isValid ) {
177- validationResult . problems . push ( {
177+ validationResult . addProblem ( {
178178 location : { start : this . start , end : this . end } ,
179179 severity : ProblemSeverity . Warning ,
180180 getMessage : ( ) => schema . errorMessage || localize ( 'typeArrayMismatchWarning' , 'Incorrect type. Expected one of {0}.' , ( < string [ ] > schema . type ) . join ( ', ' ) )
@@ -186,7 +186,7 @@ export class ASTNode {
186186 if ( this . type !== schema . type ) {
187187 //count strings that look like numbers as strings
188188 if ( this . type != "number" || schema . type != "string" ) {
189- validationResult . problems . push ( {
189+ validationResult . addProblem ( {
190190 location : { start : this . start , end : this . end } ,
191191 severity : ProblemSeverity . Warning ,
192192 getMessage : ( ) => schema . errorMessage || localize ( 'typeMismatchWarning' , 'Incorrect type. Expected "{0}".' , schema . type )
@@ -204,7 +204,7 @@ export class ASTNode {
204204 let subMatchingSchemas = matchingSchemas . newSub ( ) ;
205205 this . validate ( schema . not , subValidationResult , subMatchingSchemas ) ;
206206 if ( ! subValidationResult . hasProblems ( ) ) {
207- validationResult . problems . push ( {
207+ validationResult . addProblem ( {
208208 location : { start : this . start , end : this . end } ,
209209 severity : ProblemSeverity . Warning ,
210210 getMessage : ( ) => localize ( 'notSchemaWarning' , "Matches a schema that is not allowed." )
@@ -224,7 +224,7 @@ export class ASTNode {
224224 const possibleMatches : JSONSchema [ ] = ( Array . isArray ( firstPropMatches ) && firstPropMatches . length > 0 ) ? firstPropMatches : alternatives ;
225225
226226 // remember the best match that is used for error messages
227- let bestMatch : { schema : JSONSchema ; validationResult : ValidationResult ; matchingSchemas : ISchemaCollector ; } = null ;
227+ let bestMatch : SchemaMatch = null ;
228228 possibleMatches . forEach ( ( subSchema ) => {
229229 let subValidationResult = new ValidationResult ( ) ;
230230 let subMatchingSchemas = matchingSchemas . newSub ( ) ;
@@ -243,15 +243,15 @@ export class ASTNode {
243243 } ) ;
244244
245245 if ( matches . length > 1 && maxOneMatch && ! this . parserSettings . isKubernetes ) {
246- validationResult . problems . push ( {
246+ validationResult . addProblem ( {
247247 location : { start : this . start , end : this . start + 1 } ,
248248 severity : ProblemSeverity . Warning ,
249249 getMessage : ( ) => localize ( 'oneOfWarning' , "Matches multiple schemas when only one must validate." )
250250 } ) ;
251251 }
252252
253253 if ( bestMatch !== null ) {
254- validationResult . merge ( bestMatch . validationResult ) ;
254+ validationResult . mergeSubResult ( bestMatch . validationResult ) ;
255255 validationResult . propertiesMatches += bestMatch . validationResult . propertiesMatches ;
256256 validationResult . propertiesValueMatches += bestMatch . validationResult . propertiesValueMatches ;
257257 matchingSchemas . merge ( bestMatch . matchingSchemas ) ;
@@ -285,7 +285,7 @@ export class ASTNode {
285285 validationResult . enumValues = schema . enum ;
286286 validationResult . enumValueMatch = enumValueMatch ;
287287 if ( ! enumValueMatch ) {
288- validationResult . problems . push ( {
288+ validationResult . addProblem ( {
289289 location : { start : this . start , end : this . end } ,
290290 severity : ProblemSeverity . Warning ,
291291 code : ErrorCode . EnumValueMismatch ,
@@ -295,7 +295,7 @@ export class ASTNode {
295295 }
296296
297297 if ( schema . deprecationMessage && this . parent ) {
298- validationResult . problems . push ( {
298+ validationResult . addProblem ( {
299299 location : { start : this . parent . start , end : this . parent . end } ,
300300 severity : ProblemSeverity . Hint ,
301301 getMessage : ( ) => schema . deprecationMessage
@@ -307,15 +307,15 @@ export class ASTNode {
307307
308308 protected validateStringValue ( schema : JSONSchema , value : string , validationResult : ValidationResult ) : void {
309309 if ( schema . minLength && value . length < schema . minLength ) {
310- validationResult . problems . push ( {
310+ validationResult . addProblem ( {
311311 location : { start : this . start , end : this . end } ,
312312 severity : ProblemSeverity . Warning ,
313313 getMessage : ( ) => localize ( 'minLengthWarning' , 'String is shorter than the minimum length of {0}.' , schema . minLength )
314314 } ) ;
315315 }
316316
317317 if ( schema . maxLength && value . length > schema . maxLength ) {
318- validationResult . problems . push ( {
318+ validationResult . addProblem ( {
319319 location : { start : this . start , end : this . end } ,
320320 severity : ProblemSeverity . Warning ,
321321 getMessage : ( ) => localize ( 'maxLengthWarning' , 'String is longer than the maximum length of {0}.' , schema . maxLength )
@@ -326,7 +326,7 @@ export class ASTNode {
326326 const flags : string = ASTNode . getIgnoreValueCase ( schema ) ? "i" : "" ;
327327 const regex : RegExp = new RegExp ( schema . pattern , flags ) ;
328328 if ( ! regex . test ( value ) ) {
329- validationResult . problems . push ( {
329+ validationResult . addProblem ( {
330330 location : { start : this . start , end : this . end } ,
331331 severity : ProblemSeverity . Warning ,
332332 getMessage : ( ) => schema . patternErrorMessage || schema . errorMessage || localize ( 'patternWarning' , 'String does not match the pattern of "{0}".' , schema . pattern )
@@ -442,7 +442,7 @@ export class ArrayASTNode extends ASTNode {
442442 validationResult . mergePropertyMatch ( itemValidationResult ) ;
443443 }
444444 } else if ( schema . additionalItems === false ) {
445- validationResult . problems . push ( {
445+ validationResult . addProblem ( {
446446 location : { start : this . start , end : this . end } ,
447447 severity : ProblemSeverity . Warning ,
448448 getMessage : ( ) => localize ( 'additionalItemsWarning' , 'Array has too many items according to schema. Expected {0} or fewer.' , subSchemas . length )
@@ -459,15 +459,15 @@ export class ArrayASTNode extends ASTNode {
459459 }
460460
461461 if ( schema . minItems && this . items . length < schema . minItems ) {
462- validationResult . problems . push ( {
462+ validationResult . addProblem ( {
463463 location : { start : this . start , end : this . end } ,
464464 severity : ProblemSeverity . Warning ,
465465 getMessage : ( ) => localize ( 'minItemsWarning' , 'Array has too few items. Expected {0} or more.' , schema . minItems )
466466 } ) ;
467467 }
468468
469469 if ( schema . maxItems && this . items . length > schema . maxItems ) {
470- validationResult . problems . push ( {
470+ validationResult . addProblem ( {
471471 location : { start : this . start , end : this . end } ,
472472 severity : ProblemSeverity . Warning ,
473473 getMessage : ( ) => localize ( 'maxItemsWarning' , 'Array has too many items. Expected {0} or fewer.' , schema . maxItems )
@@ -482,7 +482,7 @@ export class ArrayASTNode extends ASTNode {
482482 return index !== values . lastIndexOf ( value ) ;
483483 } ) ;
484484 if ( duplicates ) {
485- validationResult . problems . push ( {
485+ validationResult . addProblem ( {
486486 location : { start : this . start , end : this . end } ,
487487 severity : ProblemSeverity . Warning ,
488488 getMessage : ( ) => localize ( 'uniqueItemsWarning' , 'Array has duplicate items.' )
@@ -533,7 +533,7 @@ export class NumberASTNode extends ASTNode {
533533
534534 if ( typeof schema . multipleOf === 'number' ) {
535535 if ( val % schema . multipleOf !== 0 ) {
536- validationResult . problems . push ( {
536+ validationResult . addProblem ( {
537537 location : { start : this . start , end : this . end } ,
538538 severity : ProblemSeverity . Warning ,
539539 getMessage : ( ) => localize ( 'multipleOfWarning' , 'Value is not divisible by {0}.' , schema . multipleOf )
@@ -543,14 +543,14 @@ export class NumberASTNode extends ASTNode {
543543
544544 if ( typeof schema . minimum === 'number' ) {
545545 if ( schema . exclusiveMinimum && val <= schema . minimum ) {
546- validationResult . problems . push ( {
546+ validationResult . addProblem ( {
547547 location : { start : this . start , end : this . end } ,
548548 severity : ProblemSeverity . Warning ,
549549 getMessage : ( ) => localize ( 'exclusiveMinimumWarning' , 'Value is below the exclusive minimum of {0}.' , schema . minimum )
550550 } ) ;
551551 }
552552 if ( ! schema . exclusiveMinimum && val < schema . minimum ) {
553- validationResult . problems . push ( {
553+ validationResult . addProblem ( {
554554 location : { start : this . start , end : this . end } ,
555555 severity : ProblemSeverity . Warning ,
556556 getMessage : ( ) => localize ( 'minimumWarning' , 'Value is below the minimum of {0}.' , schema . minimum )
@@ -560,14 +560,14 @@ export class NumberASTNode extends ASTNode {
560560
561561 if ( typeof schema . maximum === 'number' ) {
562562 if ( schema . exclusiveMaximum && val >= schema . maximum ) {
563- validationResult . problems . push ( {
563+ validationResult . addProblem ( {
564564 location : { start : this . start , end : this . end } ,
565565 severity : ProblemSeverity . Warning ,
566566 getMessage : ( ) => localize ( 'exclusiveMaximumWarning' , 'Value is above the exclusive maximum of {0}.' , schema . maximum )
567567 } ) ;
568568 }
569569 if ( ! schema . exclusiveMaximum && val > schema . maximum ) {
570- validationResult . problems . push ( {
570+ validationResult . addProblem ( {
571571 location : { start : this . start , end : this . end } ,
572572 severity : ProblemSeverity . Warning ,
573573 getMessage : ( ) => localize ( 'maximumWarning' , 'Value is above the maximum of {0}.' , schema . maximum )
@@ -804,7 +804,7 @@ export class ObjectASTNode extends ASTNode {
804804 if ( ! hasProperty ( propertyName ) ) {
805805 const key = this . parent && ( < PropertyASTNode > this . parent ) . key ;
806806 const location = key ? { start : key . start , end : key . end } : { start : this . start , end : this . start + 1 } ;
807- validationResult . problems . push ( {
807+ validationResult . addProblem ( {
808808 location : location ,
809809 severity : ProblemSeverity . Warning ,
810810 getMessage : ( ) => localize ( 'MissingRequiredPropWarning' , 'Missing property "{0}".' , propertyName )
@@ -872,7 +872,7 @@ export class ObjectASTNode extends ASTNode {
872872
873873 if ( generateErrors ) {
874874 const childProperty : PropertyASTNode = < PropertyASTNode > ( children [ childKey ] . parent ) ;
875- validationResult . problems . push ( {
875+ validationResult . addProblem ( {
876876 location : { start : childProperty . key . start , end : childProperty . key . end } ,
877877 severity : ProblemSeverity . Error ,
878878 getMessage : ( ) => localize ( 'DuplicatePropError' , 'Multiple properties found matching {0}' , schemaPropertyName )
@@ -945,7 +945,7 @@ export class ObjectASTNode extends ASTNode {
945945 } else {
946946 propertyNode = child ;
947947 }
948- validationResult . problems . push ( {
948+ validationResult . addProblem ( {
949949 location : { start : propertyNode . key . start , end : propertyNode . key . end } ,
950950 severity : ProblemSeverity . Warning ,
951951 getMessage : ( ) => schema . errorMessage || localize ( 'DisallowedExtraPropWarning' , 'Unexpected property {0}' , propertyName )
@@ -958,7 +958,7 @@ export class ObjectASTNode extends ASTNode {
958958
959959 if ( schema . maxProperties ) {
960960 if ( this . properties . length > schema . maxProperties ) {
961- validationResult . problems . push ( {
961+ validationResult . addProblem ( {
962962 location : { start : this . start , end : this . end } ,
963963 severity : ProblemSeverity . Warning ,
964964 getMessage : ( ) => localize ( 'MaxPropWarning' , 'Object has more properties than limit of {0}.' , schema . maxProperties )
@@ -968,7 +968,7 @@ export class ObjectASTNode extends ASTNode {
968968
969969 if ( schema . minProperties ) {
970970 if ( this . properties . length < schema . minProperties ) {
971- validationResult . problems . push ( {
971+ validationResult . addProblem ( {
972972 location : { start : this . start , end : this . end } ,
973973 severity : ProblemSeverity . Warning ,
974974 getMessage : ( ) => localize ( 'MinPropWarning' , 'Object has fewer properties than the required number of {0}' , schema . minProperties )
@@ -983,7 +983,7 @@ export class ObjectASTNode extends ASTNode {
983983 if ( Array . isArray ( propertyDep ) ) {
984984 propertyDep . forEach ( ( requiredProp : string ) => {
985985 if ( ! hasProperty ( requiredProp ) ) {
986- validationResult . problems . push ( {
986+ validationResult . addProblem ( {
987987 location : { start : this . start , end : this . end } ,
988988 severity : ProblemSeverity . Warning ,
989989 getMessage : ( ) => localize ( 'RequiredDependentPropWarning' , 'Object is missing property {0} required by property {1}.' , requiredProp , key )
@@ -1038,14 +1038,14 @@ export class ObjectASTNode extends ASTNode {
10381038 validationResult . firstPropertyProblems ++ ;
10391039
10401040 if ( schema . firstProperty . length == 1 ) {
1041- validationResult . problems . push ( {
1041+ validationResult . addProblem ( {
10421042 location : { start : firstProperty . start , end : firstProperty . end } ,
10431043 severity : ProblemSeverity . Error ,
10441044 getMessage : ( ) => localize ( 'firstPropertyError' , "The first property must be {0}" , schema . firstProperty [ 0 ] )
10451045 } ) ;
10461046 }
10471047 else {
1048- validationResult . problems . push ( {
1048+ validationResult . addProblem ( {
10491049 location : { start : firstProperty . start , end : firstProperty . end } ,
10501050 severity : ProblemSeverity . Error ,
10511051 getMessage : ( ) => {
@@ -1134,6 +1134,12 @@ export interface ISchemaCollector {
11341134 newSub ( ) : ISchemaCollector ;
11351135}
11361136
1137+ interface SchemaMatch {
1138+ schema : JSONSchema ;
1139+ validationResult : ValidationResult ;
1140+ matchingSchemas : ISchemaCollector ;
1141+ }
1142+
11371143class SchemaCollector implements ISchemaCollector {
11381144 schemas : IApplicableSchema [ ] = [ ] ;
11391145 constructor ( private focusOffset = - 1 , private exclude : ASTNode = null ) {
@@ -1162,6 +1168,7 @@ class NoOpSchemaCollector implements ISchemaCollector {
11621168
11631169export class ValidationResult {
11641170 public problems : IProblem [ ] ;
1171+ public problemDepths : number [ ] ; //count of problems found at each level of the parse tree. problemDepths[0] is the root, problemDepths[1] is one level down, etc.
11651172
11661173 public firstPropertyProblems : number ;
11671174 public propertiesMatches : number ;
@@ -1172,6 +1179,7 @@ export class ValidationResult {
11721179
11731180 constructor ( ) {
11741181 this . problems = [ ] ;
1182+ this . problemDepths = [ 0 ] ;
11751183 this . firstPropertyProblems = 0 ;
11761184 this . propertiesMatches = 0 ;
11771185 this . propertiesValueMatches = 0 ;
@@ -1184,14 +1192,26 @@ export class ValidationResult {
11841192 return ! ! this . problems . length ;
11851193 }
11861194
1187- public mergeAll ( validationResults : ValidationResult [ ] ) : void {
1188- validationResults . forEach ( ( validationResult ) => {
1189- this . merge ( validationResult ) ;
1190- } ) ;
1195+ public addProblem ( problem : IProblem ) : void {
1196+ this . problems . push ( problem ) ;
1197+ this . problemDepths [ 0 ] ++ ;
11911198 }
11921199
1193- public merge ( validationResult : ValidationResult ) : void {
1200+ public mergeSubResult ( validationResult : ValidationResult ) : void {
11941201 this . problems = this . problems . concat ( validationResult . problems ) ;
1202+
1203+ //overlay the problem count array one level down
1204+
1205+ //first make sure the array is long enough
1206+ const subDepth : number = validationResult . problemDepths . length ;
1207+ while ( this . problemDepths . length <= subDepth ) {
1208+ this . problemDepths . push ( 0 ) ;
1209+ }
1210+
1211+ //then add the problem counts shifted lower in the parse tree
1212+ validationResult . problemDepths . forEach ( ( problemCount : number , depth : number ) => {
1213+ this . problemDepths [ depth + 1 ] += problemCount ;
1214+ } ) ;
11951215 }
11961216
11971217 public mergeEnumValues ( validationResult : ValidationResult ) : void {
@@ -1206,7 +1226,7 @@ export class ValidationResult {
12061226 }
12071227
12081228 public mergePropertyMatch ( propertyValidationResult : ValidationResult ) : void {
1209- this . merge ( propertyValidationResult ) ;
1229+ this . mergeSubResult ( propertyValidationResult ) ;
12101230 this . firstPropertyProblems += propertyValidationResult . firstPropertyProblems ;
12111231 this . propertiesMatches ++ ;
12121232 if ( propertyValidationResult . enumValueMatch || ! this . hasProblems ( ) && propertyValidationResult . propertiesMatches ) {
@@ -1226,6 +1246,13 @@ export class ValidationResult {
12261246 if ( hasProblems !== other . hasProblems ( ) ) {
12271247 return hasProblems ? - 1 : 1 ;
12281248 }
1249+ if ( hasProblems ) {
1250+ const depthOfFirstProblem : number = this . problemDepths . findIndex ( ( value : number , index : number ) => value > 0 ) ;
1251+ const depthOfOtherFirstProblem : number = other . problemDepths . findIndex ( ( value : number , index : number ) => value > 0 ) ;
1252+ if ( depthOfFirstProblem != depthOfOtherFirstProblem ) {
1253+ return depthOfFirstProblem - depthOfOtherFirstProblem ;
1254+ }
1255+ }
12291256 if ( this . enumValueMatch !== other . enumValueMatch ) {
12301257 return other . enumValueMatch ? - 1 : 1 ;
12311258 }
@@ -1313,7 +1340,7 @@ export class JSONDocument {
13131340}
13141341
13151342//Alternative comparison is specifically used by the kubernetes/openshift schema but may lead to better results then genericComparison depending on the schema
1316- function alternativeComparison ( subValidationResult , bestMatch , subSchema , subMatchingSchemas ) {
1343+ function alternativeComparison ( subValidationResult : ValidationResult , bestMatch : SchemaMatch , subSchema : JSONSchema , subMatchingSchemas : ISchemaCollector ) : SchemaMatch {
13171344 let compareResult = subValidationResult . compareKubernetes ( bestMatch . validationResult ) ;
13181345 if ( compareResult > 0 ) {
13191346 // our node is the best matching so far
@@ -1327,7 +1354,7 @@ function alternativeComparison(subValidationResult, bestMatch, subSchema, subMat
13271354}
13281355
13291356//genericComparison tries to find the best matching schema using a generic comparison
1330- function genericComparison ( maxOneMatch , subValidationResult , bestMatch , subSchema , subMatchingSchemas ) {
1357+ function genericComparison ( maxOneMatch : boolean , subValidationResult : ValidationResult , bestMatch : SchemaMatch , subSchema : JSONSchema , subMatchingSchemas : ISchemaCollector ) : SchemaMatch {
13311358 if ( ! maxOneMatch && ! subValidationResult . hasProblems ( ) && ! bestMatch . validationResult . hasProblems ( ) ) {
13321359 // no errors, both are equally good matches
13331360 bestMatch . matchingSchemas . merge ( subMatchingSchemas ) ;
0 commit comments