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 // schema 2019-09
unevaluatedProperties?: boolean | JSONSchemaRef; unevaluatedProperties?: boolean | JSONSchemaRef;
unevaluatedItems?: boolean | JSONSchemaRef;
// schema 2020-12
prefixItems?: JSONSchemaRef[];
// VSCode extensions // VSCode extensions

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

@ -714,53 +714,66 @@ function validate(n: ASTNode | undefined, schema: JSONSchema, validationResult:
} }
function _validateArrayNode(node: ArrayASTNode): void { function _validateArrayNode(node: ArrayASTNode): void {
if (Array.isArray(schema.items)) { const prefixItemsSchemas = Array.isArray(schema.items) ? schema.items : schema.prefixItems;
const subSchemas = schema.items; const additionalItemSchema = schema.items && !Array.isArray(schema.items) ? schema.items : schema.additionalItems;
for (let index = 0; index < subSchemas.length; index++) { if (prefixItemsSchemas !== undefined) {
const subSchemaRef = subSchemas[index]; 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 subSchema = asSchema(subSchemaRef);
const itemValidationResult = new ValidationResult(); const itemValidationResult = new ValidationResult();
const item = node.items[index]; const item = node.items[index];
if (item) { if (item) {
validate(item, subSchema, itemValidationResult, matchingSchemas); validate(item, subSchema, itemValidationResult, matchingSchemas);
validationResult.mergePropertyMatch(itemValidationResult); validationResult.mergePropertyMatch(itemValidationResult);
} else if (node.items.length >= subSchemas.length) {
validationResult.propertiesValueMatches++;
} }
validationResult.processedProperties.add(String(index));
} }
if (node.items.length > subSchemas.length) { if (node.items.length > prefixItemsSchemas.length) {
if (typeof schema.additionalItems === 'object') { if (additionalItemSchema !== undefined) {
for (let i = subSchemas.length; i < node.items.length; i++) { if (typeof additionalItemSchema === 'object') {
const itemValidationResult = new ValidationResult(); for (let i = prefixItemsSchemas.length; i < node.items.length; i++) {
validate(node.items[i], <any>schema.additionalItems, itemValidationResult, matchingSchemas); const itemValidationResult = new ValidationResult();
validationResult.mergePropertyMatch(itemValidationResult); 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 { } else if (additionalItemSchema) {
const itemSchema = asSchema(schema.items); const itemSchema = asSchema(additionalItemSchema);
if (itemSchema) { 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(); const itemValidationResult = new ValidationResult();
validate(item, itemSchema, itemValidationResult, matchingSchemas); validate(item, itemSchema, itemValidationResult, matchingSchemas);
validationResult.mergePropertyMatch(itemValidationResult); validationResult.mergePropertyMatch(itemValidationResult);
validationResult.processedProperties.add(String(index));
} }
} }
} }
const containsSchema = asSchema(schema.contains); const containsSchema = asSchema(schema.contains);
if (containsSchema) { 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(); const itemValidationResult = new ValidationResult();
validate(item, containsSchema, itemValidationResult, NoOpSchemaCollector.instance); validate(item, containsSchema, itemValidationResult, NoOpSchemaCollector.instance);
return !itemValidationResult.hasProblems(); if (!itemValidationResult.hasProblems()) {
}); doesContain = true;
validationResult.processedProperties.add(String(index));
}
}
if (!doesContain) { if (!doesContain) {
validationResult.problems.push({ validationResult.problems.push({
location: { offset: node.offset, length: node.length }, 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) { if (isNumber(schema.minItems) && node.items.length < schema.minItems) {
validationResult.problems.push({ validationResult.problems.push({
location: { offset: node.offset, length: node.length }, 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 () { test('multipleOf', function () {
const schema: JSONSchema = { const schema: JSONSchema = {
type: 'array', type: 'array',