yaml-language-server
Version:
126 lines • 6.85 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) IBM Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SchemaDraft } from '../../jsonLanguageTypes';
import { isNumber } from '../../utils/objects';
import * as l10n from '@vscode/l10n';
import { DiagnosticSeverity } from 'vscode-languageserver-types';
import { Draft2019Validator } from './draft2019Validator';
import { ValidationResult, asSchema } from './baseValidator';
export class Draft2020Validator extends Draft2019Validator {
getCurrentSchemaDraft() {
return SchemaDraft.v2020_12;
}
/**
* Keyword: prefixItems + items
*/
validateArrayNode(node, schema, originalSchema, validationResult, matchingSchemas, options) {
const items = (node.items ?? []);
// prefixItems/items/contains contribute to evaluatedItems
const evaluatedItems = validationResult.getEvaluatedItems(node);
const prefixItems = schema.prefixItems;
// validate prefixItems
if (Array.isArray(prefixItems)) {
const limit = Math.min(prefixItems.length, items.length);
for (let i = 0; i < limit; i++) {
const subSchema = asSchema(prefixItems[i]);
if (!subSchema) {
evaluatedItems.add(i);
continue;
}
const itemValidationResult = new ValidationResult(options.isKubernetes);
this.validateNode(items[i], subSchema, schema, itemValidationResult, matchingSchemas, options);
validationResult.mergePropertyMatch(itemValidationResult, false);
validationResult.mergeEnumValues(itemValidationResult);
// mark as evaluated even if invalid (avoids duplicate unevaluatedItems noise)
evaluatedItems.add(i);
}
}
// validate remaining items against items
const itemsKeyword = schema.items;
const prefixLen = Array.isArray(prefixItems) ? prefixItems.length : 0;
if (items.length > prefixLen) {
if (itemsKeyword === false) {
// "items": false => no items allowed beyond prefixItems
validationResult.problems.push({
location: { offset: node.offset, length: node.length },
severity: DiagnosticSeverity.Warning,
message: l10n.t('Array has too many items according to schema. Expected {0} or fewer.', prefixLen),
source: this.getSchemaSource(schema, originalSchema),
schemaUri: this.getSchemaUri(schema, originalSchema),
});
// mark these as evaluated by "items": false (so unevaluatedItems doesn't also complain)
for (let i = prefixLen; i < items.length; i++) {
evaluatedItems.add(i);
}
}
else {
const tailSchema = asSchema(itemsKeyword);
// if items is undefined, there's no constraint for remaining items and they remain unevaluated
if (tailSchema) {
for (let i = prefixLen; i < items.length; i++) {
const itemValidationResult = new ValidationResult(options.isKubernetes);
this.validateNode(items[i], tailSchema, schema, itemValidationResult, matchingSchemas, options);
validationResult.mergePropertyMatch(itemValidationResult, false);
validationResult.mergeEnumValues(itemValidationResult);
// mark as evaluated even if invalid (avoids duplicate unevaluatedItems noise)
evaluatedItems.add(i);
}
}
}
}
// contains enforces min/max and marks matching indices as evaluated
this.applyContains(node, schema, originalSchema, validationResult, matchingSchemas, options);
// generic array keywords
this.applyArrayLength(node, schema, originalSchema, validationResult, options);
this.applyUniqueItems(node, schema, originalSchema, validationResult);
}
/**
* Draft 2020-12: contains keyword affects the unevaluatedItems keyword
*/
applyContains(node, schema, originalSchema, validationResult, _matchingSchemas, options) {
const containsSchema = asSchema(schema.contains);
if (!containsSchema)
return;
const items = (node.items ?? []);
const minContainsRaw = schema.minContains;
const maxContainsRaw = schema.maxContains;
const minContains = isNumber(minContainsRaw) ? minContainsRaw : 1;
const maxContains = isNumber(maxContainsRaw) ? maxContainsRaw : undefined;
let matchCount = 0;
// ensure evaluatedItems exists
const evaluatedItems = validationResult.getEvaluatedItems(node);
for (let i = 0; i < items.length; i++) {
const itemValidationResult = new ValidationResult(options.isKubernetes);
this.validateNode(items[i], containsSchema, schema, itemValidationResult, this.getNoOpCollector(), options);
if (!itemValidationResult.hasProblems()) {
// items that match contains are considered evaluated
evaluatedItems.add(i);
matchCount++;
if (maxContains !== undefined && matchCount > maxContains) {
break;
}
}
}
if (matchCount < minContains) {
validationResult.problems.push({
location: { offset: node.offset, length: node.length },
severity: DiagnosticSeverity.Warning,
message: schema.errorMessage || l10n.t('Array has too few items matching "contains". Expected {0} or more.', minContains),
source: this.getSchemaSource(schema, originalSchema),
schemaUri: this.getSchemaUri(schema, originalSchema),
});
}
if (maxContains !== undefined && matchCount > maxContains) {
validationResult.problems.push({
location: { offset: node.offset, length: node.length },
severity: DiagnosticSeverity.Warning,
message: schema.errorMessage || l10n.t('Array has too many items matching "contains". Expected {0} or fewer.', maxContains),
source: this.getSchemaSource(schema, originalSchema),
schemaUri: this.getSchemaUri(schema, originalSchema),
});
}
}
}
//# sourceMappingURL=draft2020Validator.js.map