in language-service/src/parser/jsonParser.ts [164:306]
public validate(schema: JSONSchema, validationResult: ValidationResult, matchingSchemas: ISchemaCollector): void {
if (!matchingSchemas.include(this)) {
return;
}
if (Array.isArray(schema.type)) {
if ((<string[]>schema.type).indexOf(this.type) === -1) {
//allow numbers to be validated as strings
let isValid: boolean = false;
if (this.type === 'number') {
isValid = (<string[]>schema.type).indexOf('string') >= 0;
}
if (!isValid) {
validationResult.addProblem({
location: { start: this.start, end: this.end },
severity: ProblemSeverity.Warning,
getMessage: () => schema.errorMessage || localize('typeArrayMismatchWarning', 'Incorrect type. Expected one of {0}.', (<string[]>schema.type).join(', '))
});
}
}
}
else if (schema.type) {
if (this.type !== schema.type) {
//count strings that look like numbers as strings
if (this.type != "number" || schema.type != "string") {
validationResult.addProblem({
location: { start: this.start, end: this.end },
severity: ProblemSeverity.Warning,
getMessage: () => schema.errorMessage || localize('typeMismatchWarning', 'Incorrect type. Expected "{0}".', schema.type as string)
});
}
}
}
if (Array.isArray(schema.allOf)) {
schema.allOf.forEach((subSchema) => {
this.validate(subSchema, validationResult, matchingSchemas);
});
}
if (schema.not) {
let subValidationResult = new ValidationResult();
let subMatchingSchemas = matchingSchemas.newSub();
this.validate(schema.not, subValidationResult, subMatchingSchemas);
if (!subValidationResult.hasProblems()) {
validationResult.addProblem({
location: { start: this.start, end: this.end },
severity: ProblemSeverity.Warning,
getMessage: () => localize('notSchemaWarning', "Matches a schema that is not allowed.")
});
}
subMatchingSchemas.schemas.forEach((ms) => {
ms.inverted = !ms.inverted;
matchingSchemas.add(ms);
});
}
let testAlternatives = (alternatives: JSONSchema[], maxOneMatch: boolean) => {
const matches: JSONSchema[] = [];
const firstPropMatches: JSONSchema[] = this.getFirstPropertyMatches(alternatives);
const possibleMatches: JSONSchema[] = (Array.isArray(firstPropMatches) && firstPropMatches.length > 0) ? firstPropMatches : alternatives;
// remember the best match that is used for error messages
let bestMatch: SchemaMatch = null;
possibleMatches.forEach((subSchema) => {
let subValidationResult = new ValidationResult();
let subMatchingSchemas = matchingSchemas.newSub();
this.validate(subSchema, subValidationResult, subMatchingSchemas);
if (!subValidationResult.hasProblems()) {
matches.push(subSchema);
}
if (!bestMatch) {
bestMatch = { schema: subSchema, validationResult: subValidationResult, matchingSchemas: subMatchingSchemas };
} else if(this.parserSettings.isKubernetes) {
bestMatch = alternativeComparison(subValidationResult, bestMatch, subSchema, subMatchingSchemas);
} else {
bestMatch = genericComparison(maxOneMatch, subValidationResult, bestMatch, subSchema, subMatchingSchemas);
}
});
if (matches.length > 1 && maxOneMatch && !this.parserSettings.isKubernetes) {
validationResult.addProblem({
location: { start: this.start, end: this.start + 1 },
severity: ProblemSeverity.Warning,
getMessage: () => localize('oneOfWarning', "Matches multiple schemas when only one must validate.")
});
}
if (bestMatch !== null) {
validationResult.mergeSubResult(bestMatch.validationResult);
validationResult.propertiesMatches += bestMatch.validationResult.propertiesMatches;
validationResult.propertiesValueMatches += bestMatch.validationResult.propertiesValueMatches;
matchingSchemas.merge(bestMatch.matchingSchemas);
}
return matches.length;
};
if (Array.isArray(schema.anyOf)) {
testAlternatives(schema.anyOf, false);
}
if (Array.isArray(schema.oneOf)) {
testAlternatives(schema.oneOf, true);
}
if (Array.isArray(schema.enum)) {
let val = this.getValue();
//force number values to strings for the comparison
if (typeof val === "number") {
val = val.toString();
}
let enumValueMatch: boolean = false;
if (val) {
const ignoreCase: boolean = ASTNode.getIgnoreValueCase(schema);
for (const e of schema.enum) {
if (objects.equals(val, e) ||
(ignoreCase && typeof e === "string" && typeof val === "string" && e.toUpperCase() === val.toUpperCase())) {
enumValueMatch = true;
break;
}
}
}
validationResult.enumValues = schema.enum;
validationResult.enumValueMatch = enumValueMatch;
if (!enumValueMatch) {
validationResult.addProblem({
location: { start: this.start, end: this.end },
severity: ProblemSeverity.Warning,
code: ErrorCode.EnumValueMismatch,
getMessage: () => schema.errorMessage || localize('enumWarning', 'Value is not accepted. Valid values: {0}.', schema.enum.map(v => JSON.stringify(v)).join(', '))
});
}
}
if (schema.deprecationMessage && this.parent) {
validationResult.addProblem({
location: { start: this.parent.start, end: this.parent.end },
severity: ProblemSeverity.Hint,
getMessage: () => schema.deprecationMessage
});
}
matchingSchemas.add({ node: this, schema: schema });
}