This commit is contained in:
Martin Aeschlimann 2022-05-12 08:21:54 +02:00
Родитель 1db7a6cc2b
Коммит b29e9b2b5d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 2609A01E695523E3
3 изменённых файлов: 142 добавлений и 24 удалений

Просмотреть файл

@ -55,6 +55,10 @@ export interface JSONSchema {
// schema 2019-09
unevaluatedProperties?: boolean | JSONSchemaRef;
unevaluatedItems?: boolean | JSONSchemaRef;
// schema 2020-12
prefixItems?: JSONSchemaRef[];
// VSCode extensions

Просмотреть файл

@ -714,53 +714,66 @@ function validate(n: ASTNode | undefined, schema: JSONSchema, validationResult:
}
function _validateArrayNode(node: ArrayASTNode): void {
if (Array.isArray(schema.items)) {
const subSchemas = schema.items;
for (let index = 0; index < subSchemas.length; index++) {
const subSchemaRef = subSchemas[index];
const prefixItemsSchemas = Array.isArray(schema.items) ? schema.items : schema.prefixItems;
const additionalItemSchema = schema.items && !Array.isArray(schema.items) ? schema.items : schema.additionalItems;
if (prefixItemsSchemas !== undefined) {
const max = Math.min(prefixItemsSchemas.length, node.items.length);
for (let index = 0; index < max; index++) {
const subSchemaRef = prefixItemsSchemas[index];
const subSchema = asSchema(subSchemaRef);
const itemValidationResult = new ValidationResult();
const item = node.items[index];
if (item) {
validate(item, subSchema, itemValidationResult, matchingSchemas);
validationResult.mergePropertyMatch(itemValidationResult);
} else if (node.items.length >= subSchemas.length) {
validationResult.propertiesValueMatches++;
}
validationResult.processedProperties.add(String(index));
}
if (node.items.length > subSchemas.length) {
if (typeof schema.additionalItems === 'object') {
for (let i = subSchemas.length; i < node.items.length; i++) {
const itemValidationResult = new ValidationResult();
validate(node.items[i], <any>schema.additionalItems, itemValidationResult, matchingSchemas);
validationResult.mergePropertyMatch(itemValidationResult);
if (node.items.length > prefixItemsSchemas.length) {
if (additionalItemSchema !== undefined) {
if (typeof additionalItemSchema === 'object') {
for (let i = prefixItemsSchemas.length; i < node.items.length; i++) {
const itemValidationResult = new ValidationResult();
validate(node.items[i], <any>schema.additionalItems, itemValidationResult, matchingSchemas);
validationResult.mergePropertyMatch(itemValidationResult);
}
} else if (additionalItemSchema === false) {
validationResult.problems.push({
location: { offset: node.offset, length: node.length },
message: localize('additionalItemsWarning', 'Array has too many items according to schema. Expected {0} or fewer.', subSchemas.length)
});
}
for (let i = prefixItemsSchemas.length; i < node.items.length; i++) {
validationResult.processedProperties.add(String(i));
validationResult.propertiesValueMatches++;
}
} else if (schema.additionalItems === false) {
validationResult.problems.push({
location: { offset: node.offset, length: node.length },
message: localize('additionalItemsWarning', 'Array has too many items according to schema. Expected {0} or fewer.', subSchemas.length)
});
}
}
} else {
const itemSchema = asSchema(schema.items);
} else if (additionalItemSchema) {
const itemSchema = asSchema(additionalItemSchema);
if (itemSchema) {
for (const item of node.items) {
for (let index = 0; index < node.items.length; index++) {
const item = node.items[index];
const itemValidationResult = new ValidationResult();
validate(item, itemSchema, itemValidationResult, matchingSchemas);
validationResult.mergePropertyMatch(itemValidationResult);
validationResult.processedProperties.add(String(index));
}
}
}
const containsSchema = asSchema(schema.contains);
if (containsSchema) {
const doesContain = node.items.some(item => {
let doesContain = false;
for (let index = 0; index < node.items.length; index++) {
const item = node.items[index];
const itemValidationResult = new ValidationResult();
validate(item, containsSchema, itemValidationResult, NoOpSchemaCollector.instance);
return !itemValidationResult.hasProblems();
});
if (!itemValidationResult.hasProblems()) {
doesContain = true;
validationResult.processedProperties.add(String(index));
}
}
if (!doesContain) {
validationResult.problems.push({
location: { offset: node.offset, length: node.length },
@ -769,6 +782,26 @@ function validate(n: ASTNode | undefined, schema: JSONSchema, validationResult:
}
}
const unevaluatedItems = schema.unevaluatedItems;
if (unevaluatedItems !== undefined) {
for (let i = 0; i < node.items.length; i++) {
if (!validationResult.processedProperties.has(String(i))) {
if (unevaluatedItems === false) {
validationResult.problems.push({
location: { offset: node.offset, length: node.length },
message: localize('unevaluatedItemsWarning', 'Item does not match any validation rule from the array.')
});
} else {
const itemValidationResult = new ValidationResult();
validate(node.items[i], <any>schema.additionalItems, itemValidationResult, matchingSchemas);
validationResult.mergePropertyMatch(itemValidationResult);
}
}
validationResult.processedProperties.add(String(i));
validationResult.propertiesValueMatches++;
}
}
if (isNumber(schema.minItems) && node.items.length < schema.minItems) {
validationResult.problems.push({
location: { offset: node.offset, length: node.length },

Просмотреть файл

@ -1916,6 +1916,87 @@ suite('JSON Parser', () => {
}
});
test.only('unevaluatedItems', function () {
let schema: JSONSchema = {
type: 'array',
items: [
{
type: 'integer'
},
{
type: 'boolean'
}
],
unevaluatedItems: false
};
{
const { textDoc, jsonDoc } = toDocument('[1, true]');
const semanticErrors = jsonDoc.validate(textDoc, schema);
assert.strictEqual(semanticErrors!.length, 0);
}
{
const { textDoc, jsonDoc } = toDocument('[1, true, "string", 42]');
const semanticErrors = jsonDoc.validate(textDoc, schema);
assert.strictEqual(semanticErrors!.length, 2);
}
schema = {
anyOf: [
{
type: 'array',
items: [
{
type: 'integer'
},
{
type: 'integer'
}
],
},
{
type: 'array',
items: [
{
type: 'integer'
},
{
type: 'boolean'
},
{
type: 'boolean'
}
],
},
],
unevaluatedItems: false
};
{
const { textDoc, jsonDoc } = toDocument('[1, 1]');
const semanticErrors = jsonDoc.validate(textDoc, schema);
assert.strictEqual(semanticErrors!.length, 0);
}
{
const { textDoc, jsonDoc } = toDocument('[1, true, true]');
const semanticErrors = jsonDoc.validate(textDoc, schema);
assert.strictEqual(semanticErrors!.length, 0);
}
{
const { textDoc, jsonDoc } = toDocument('[1, true, true, true, "Hello"]');
const semanticErrors = jsonDoc.validate(textDoc, schema);
assert.strictEqual(semanticErrors!.length, 2);
}
schema = {
"type": "array",
"prefixItems": [{ "type": "string" }, { "type": "string" }],
"contains": { "type": "string", "minLength": 3 },
"unevaluatedItems": false
};
});
test('multipleOf', function () {
const schema: JSONSchema = {
type: 'array',