From 8de48ab080f291ec22757723449f394e313fe9e0 Mon Sep 17 00:00:00 2001 From: AcE Date: Tue, 19 May 2026 09:33:30 +0530 Subject: [PATCH] lint: add incoherent_exclusive_limits rule Signed-off-by: AcE --- src/alterschema/CMakeLists.txt | 1 + src/alterschema/alterschema.cc | 3 + .../common/incoherent_exclusive_limits.h | 76 ++++++++ .../alterschema_canonicalize_2019_09_test.cc | 145 +++++++++++++++ .../alterschema_canonicalize_2020_12_test.cc | 165 ++++++++++++++++++ .../alterschema_canonicalize_draft6_test.cc | 138 +++++++++++++++ .../alterschema_canonicalize_draft7_test.cc | 138 +++++++++++++++ .../alterschema_lint_2019_09_test.cc | 86 +++++++++ .../alterschema_lint_2020_12_test.cc | 86 +++++++++ .../alterschema_lint_draft6_test.cc | 86 +++++++++ .../alterschema_lint_draft7_test.cc | 86 +++++++++ 11 files changed, 1010 insertions(+) create mode 100644 src/alterschema/common/incoherent_exclusive_limits.h diff --git a/src/alterschema/CMakeLists.txt b/src/alterschema/CMakeLists.txt index 72acac902..9832482fd 100644 --- a/src/alterschema/CMakeLists.txt +++ b/src/alterschema/CMakeLists.txt @@ -96,6 +96,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT blaze NAME alterschema common/exclusive_minimum_number_and_minimum.h common/if_without_then_else.h common/ignored_metaschema.h + common/incoherent_exclusive_limits.h common/max_contains_without_contains.h common/maximum_real_for_integer.h common/min_contains_without_contains.h diff --git a/src/alterschema/alterschema.cc b/src/alterschema/alterschema.cc index 30b5417cd..9dc80e6d5 100644 --- a/src/alterschema/alterschema.cc +++ b/src/alterschema/alterschema.cc @@ -202,6 +202,7 @@ auto WALK_UP_IN_PLACE_APPLICATORS(const JSON &root, const SchemaFrame &frame, #include "common/flatten_nested_extends.h" #include "common/if_without_then_else.h" #include "common/ignored_metaschema.h" +#include "common/incoherent_exclusive_limits.h" #include "common/max_contains_without_contains.h" #include "common/maximum_real_for_integer.h" #include "common/min_contains_without_contains.h" @@ -339,6 +340,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { if (mode == AlterSchemaMode::Canonicalizer) { bundle.add(); bundle.add(); + bundle.add(); bundle.add(); bundle.add(); bundle.add(); @@ -464,6 +466,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { bundle.add(); bundle.add(); bundle.add(); + bundle.add(); bundle.add(); bundle.add(); bundle.add(); diff --git a/src/alterschema/common/incoherent_exclusive_limits.h b/src/alterschema/common/incoherent_exclusive_limits.h new file mode 100644 index 000000000..176d88964 --- /dev/null +++ b/src/alterschema/common/incoherent_exclusive_limits.h @@ -0,0 +1,76 @@ +class IncoherentExclusiveLimits final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + IncoherentExclusiveLimits() + : SchemaTransformRule{ + "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, const Vocabularies &vocabularies, + const SchemaFrame &, const SchemaFrame::Location &, + const SchemaWalker &, const SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF(schema.is_object()); + ONLY_CONTINUE_IF(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Validation, + Vocabularies::Known::JSON_Schema_2019_09_Validation, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6})); + // If the schema is already marked as unsatisfiable by a previous transform, + // stop + if (schema.defines("not") && is_empty_schema(schema.at("not"))) { + return false; + } + if (schema.defines("allOf") && schema.at("allOf").is_array()) { + for (const auto &item : schema.at("allOf").as_array()) { + if (item.is_object() && item.defines("not") && + is_empty_schema(item.at("not"))) { + return false; + } + } + } + // Don't mutate schemas that are reference targets + if (schema.defines("$anchor") || schema.defines("$dynamicAnchor")) { + return false; + } + const auto *exclusive_minimum{schema.try_at("exclusiveMinimum")}; + ONLY_CONTINUE_IF(exclusive_minimum && exclusive_minimum->is_number()); + const auto *exclusive_maximum{schema.try_at("exclusiveMaximum")}; + ONLY_CONTINUE_IF(exclusive_maximum && exclusive_maximum->is_number() && + *exclusive_minimum >= *exclusive_maximum); + return APPLIES_TO_KEYWORDS("exclusiveMinimum", "exclusiveMaximum"); + } + + auto transform(JSON &schema, const Result &) const -> void override { + auto not_false = + JSON::make_object(); // {} = always true, not {} = never valid + + if (!schema.defines("not") && !schema.defines("allOf")) { + schema.assign("not", std::move(not_false)); + } else { + auto new_entry = JSON::make_object(); + new_entry.assign("not", std::move(not_false)); + + if (schema.defines("allOf") && schema.at("allOf").is_array()) { + schema.at("allOf").push_back(std::move(new_entry)); + } else { + auto all_of = JSON::make_array(); + if (schema.defines("not")) { + auto existing_not = JSON::make_object(); + existing_not.assign("not", schema.at("not")); + all_of.push_back(std::move(existing_not)); + schema.erase("not"); + } + all_of.push_back(std::move(new_entry)); + schema.assign("allOf", std::move(all_of)); + } + } + + schema.erase("exclusiveMinimum"); + schema.erase("exclusiveMaximum"); + } +}; diff --git a/test/alterschema/alterschema_canonicalize_2019_09_test.cc b/test/alterschema/alterschema_canonicalize_2019_09_test.cc index 5bb8b5c2c..da6318bff 100644 --- a/test/alterschema/alterschema_canonicalize_2019_09_test.cc +++ b/test/alterschema/alterschema_canonicalize_2019_09_test.cc @@ -323,6 +323,151 @@ TEST_F(Canonicalizer201909Test, exclusive_minimum_integer_to_minimum_3) { CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); } +TEST_F(Canonicalizer201909Test, incoherent_exclusive_limits_1) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer201909Test, incoherent_exclusive_limits_2) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer201909Test, incoherent_exclusive_limits_3) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2, + "not": { "type": "string" } + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + { + "not": { "type": "string", "minLength": 0 } + }, + { + "not": true + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer201909Test, incoherent_exclusive_limits_4) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "#/$defs/test/additionalProperties", + "$defs": { + "test": { + "additionalProperties": { + "type": "string" + }, + "exclusiveMaximum": 5, + "exclusiveMinimum": 8 + } + } + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "test": { + "allOf": [ + { + "not": true + }, + { + "anyOf": [ + { + "enum": [ null ] + }, + { + "enum": [ false, true ] + }, + { + "type": "object", + "additionalProperties": { + "type": "string", + "minLength": 0 + }, + "patternProperties": {}, + "propertyNames": true, + "minProperties": 0, + "properties": {} + }, + { + "type": "array", + "uniqueItems": false, + "minItems": 0, + "contains": true, + "minContains": 0, + "items": true + }, + { + "type": "string", + "minLength": 0 + }, + { + "type": "number" + } + ] + } + ] + } + }, + "allOf": [ + { + "$ref": "#/$defs/test/allOf/1/anyOf/2/additionalProperties" + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer201909Test, incoherent_exclusive_limits_5_anchor) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "number", + "$anchor": "my-anchor", + "exclusiveMinimum": 5.5, + "exclusiveMaximum": 2.5 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "number", + "$anchor": "my-anchor", + "exclusiveMinimum": 5.5, + "exclusiveMaximum": 2.5 + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + TEST_F(Canonicalizer201909Test, exclusive_minimum_integer_to_minimum_5) { auto document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", diff --git a/test/alterschema/alterschema_canonicalize_2020_12_test.cc b/test/alterschema/alterschema_canonicalize_2020_12_test.cc index f31d911bf..3a52d6a8a 100644 --- a/test/alterschema/alterschema_canonicalize_2020_12_test.cc +++ b/test/alterschema/alterschema_canonicalize_2020_12_test.cc @@ -665,6 +665,171 @@ TEST_F(Canonicalizer202012Test, exclusive_minimum_integer_to_minimum_3) { CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); } +TEST_F(Canonicalizer202012Test, incoherent_exclusive_limits_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer202012Test, incoherent_exclusive_limits_2) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer202012Test, incoherent_exclusive_limits_3) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2, + "not": { "type": "string" } + })JSON"); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "not": { "type": "string", "minLength": 0 } + }, + { + "not": true + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer202012Test, incoherent_exclusive_limits_4) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/test/additionalProperties", + "$defs": { + "test": { + "additionalProperties": { + "type": "string" + }, + "exclusiveMaximum": 5, + "exclusiveMinimum": 8 + } + } + })JSON"); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "test": { + "allOf": [ + { + "not": true + }, + { + "anyOf": [ + { + "enum": [ null ] + }, + { + "enum": [ false, true ] + }, + { + "type": "object", + "additionalProperties": { + "type": "string", + "minLength": 0 + }, + "patternProperties": {}, + "propertyNames": true, + "minProperties": 0, + "properties": {} + }, + { + "type": "array", + "uniqueItems": false, + "minItems": 0, + "contains": true, + "minContains": 0, + "items": true + }, + { + "type": "string", + "minLength": 0 + }, + { + "type": "number" + } + ] + } + ] + } + }, + "allOf": [ + { + "$ref": "#/$defs/test/allOf/1/anyOf/2/additionalProperties" + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer202012Test, incoherent_exclusive_limits_5_anchor) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "number", + "$anchor": "my-anchor", + "exclusiveMinimum": 5.5, + "exclusiveMaximum": 2.5 + })JSON"); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "number", + "$anchor": "my-anchor", + "exclusiveMinimum": 5.5, + "exclusiveMaximum": 2.5 + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer202012Test, incoherent_exclusive_limits_6_dynamic_anchor) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "number", + "$dynamicAnchor": "my-dynamic-anchor", + "exclusiveMinimum": 5.5, + "exclusiveMaximum": 2.5 + })JSON"); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "number", + "$dynamicAnchor": "my-dynamic-anchor", + "exclusiveMinimum": 5.5, + "exclusiveMaximum": 2.5 + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + TEST_F(Canonicalizer202012Test, exclusive_minimum_integer_to_minimum_5) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", diff --git a/test/alterschema/alterschema_canonicalize_draft6_test.cc b/test/alterschema/alterschema_canonicalize_draft6_test.cc index 67fecbe36..cb7c70872 100644 --- a/test/alterschema/alterschema_canonicalize_draft6_test.cc +++ b/test/alterschema/alterschema_canonicalize_draft6_test.cc @@ -437,6 +437,144 @@ TEST_F(CanonicalizerDraft6Test, integer_both_exclusive_bounds_fold) { CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); } +TEST_F(CanonicalizerDraft6Test, incoherent_exclusive_limits_1) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft6Test, incoherent_exclusive_limits_2) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft6Test, incoherent_exclusive_limits_3) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2, + "not": { "type": "string" } + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "allOf": [ + { + "not": { "type": "string", "minLength": 0 } + }, + { + "not": true + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft6Test, incoherent_exclusive_limits_4) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "properties": { + "main": { "$ref": "#/definitions/test/additionalProperties" } + }, + "definitions": { + "test": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "exclusiveMaximum": 5, + "exclusiveMinimum": 8 + } + } + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "definitions": { + "test": { + "allOf": [ + { + "not": { + "type": "object", + "patternProperties": {}, + "propertyNames": true, + "minProperties": 0, + "properties": {}, + "additionalProperties": true + } + }, + { + "type": "object", + "additionalProperties": { + "type": "string", + "minLength": 0 + }, + "patternProperties": {}, + "propertyNames": true, + "minProperties": 0, + "properties": {} + } + ] + } + }, + "anyOf": [ + { + "enum": [ null ] + }, + { + "enum": [ false, true ] + }, + { + "type": "object", + "properties": { + "main": { + "$ref": "#/definitions/test/allOf/1/additionalProperties" + } + }, + "patternProperties": {}, + "propertyNames": true, + "minProperties": 0, + "additionalProperties": true + }, + { + "type": "array", + "uniqueItems": false, + "items": true, + "minItems": 0 + }, + { + "type": "string", + "minLength": 0 + }, + { + "type": "number" + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + TEST_F(CanonicalizerDraft6Test, number_bare) { auto document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", diff --git a/test/alterschema/alterschema_canonicalize_draft7_test.cc b/test/alterschema/alterschema_canonicalize_draft7_test.cc index 0f85f895c..269a0ed74 100644 --- a/test/alterschema/alterschema_canonicalize_draft7_test.cc +++ b/test/alterschema/alterschema_canonicalize_draft7_test.cc @@ -437,6 +437,144 @@ TEST_F(CanonicalizerDraft7Test, integer_both_exclusive_bounds_fold) { CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); } +TEST_F(CanonicalizerDraft7Test, incoherent_exclusive_limits_1) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft7Test, incoherent_exclusive_limits_2) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft7Test, incoherent_exclusive_limits_3) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2, + "not": { "type": "string" } + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "allOf": [ + { + "not": { "type": "string", "minLength": 0 } + }, + { + "not": true + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft7Test, incoherent_exclusive_limits_4) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "main": { "$ref": "#/definitions/test/additionalProperties" } + }, + "definitions": { + "test": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "exclusiveMaximum": 5, + "exclusiveMinimum": 8 + } + } + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "test": { + "allOf": [ + { + "not": { + "type": "object", + "patternProperties": {}, + "propertyNames": true, + "minProperties": 0, + "properties": {}, + "additionalProperties": true + } + }, + { + "type": "object", + "additionalProperties": { + "type": "string", + "minLength": 0 + }, + "patternProperties": {}, + "propertyNames": true, + "minProperties": 0, + "properties": {} + } + ] + } + }, + "anyOf": [ + { + "enum": [ null ] + }, + { + "enum": [ false, true ] + }, + { + "type": "object", + "properties": { + "main": { + "$ref": "#/definitions/test/allOf/1/additionalProperties" + } + }, + "patternProperties": {}, + "propertyNames": true, + "minProperties": 0, + "additionalProperties": true + }, + { + "type": "array", + "uniqueItems": false, + "items": true, + "minItems": 0 + }, + { + "type": "string", + "minLength": 0 + }, + { + "type": "number" + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + TEST_F(CanonicalizerDraft7Test, number_bare) { auto document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/test/alterschema/alterschema_lint_2019_09_test.cc b/test/alterschema/alterschema_lint_2019_09_test.cc index eaef571a8..c2d792363 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -2417,6 +2417,92 @@ TEST(AlterSchema_lint_2019_09, incoherent_min_max_contains_9) { true); } +TEST(AlterSchema_lint_2019_09, incoherent_exclusive_limits_1) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1, + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_2019_09, incoherent_exclusive_limits_2) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_2019_09, incoherent_exclusive_limits_3) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_2019_09, incoherent_exclusive_limits_4) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_2019_09, incoherent_exclusive_limits_5) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + TEST(AlterSchema_lint_2019_09, equal_numeric_bounds_to_const_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", diff --git a/test/alterschema/alterschema_lint_2020_12_test.cc b/test/alterschema/alterschema_lint_2020_12_test.cc index 452493b61..22f8c3836 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -2324,6 +2324,92 @@ TEST(AlterSchema_lint_2020_12, incoherent_min_max_contains_9) { true); } +TEST(AlterSchema_lint_2020_12, incoherent_exclusive_limits_1) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1, + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_2020_12, incoherent_exclusive_limits_2) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_2020_12, incoherent_exclusive_limits_3) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_2020_12, incoherent_exclusive_limits_4) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_2020_12, incoherent_exclusive_limits_5) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + TEST(AlterSchema_lint_2020_12, equal_numeric_bounds_to_const_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", diff --git a/test/alterschema/alterschema_lint_draft6_test.cc b/test/alterschema/alterschema_lint_draft6_test.cc index cace750d0..3be67b8d4 100644 --- a/test/alterschema/alterschema_lint_draft6_test.cc +++ b/test/alterschema/alterschema_lint_draft6_test.cc @@ -1291,6 +1291,92 @@ TEST(AlterSchema_lint_draft6, exclusive_minimum_integer_to_minimum_4) { EXPECT_EQ(document, expected); } +TEST(AlterSchema_lint_draft6, incoherent_exclusive_limits_1) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1, + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft6, incoherent_exclusive_limits_2) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft6, incoherent_exclusive_limits_3) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_draft6, incoherent_exclusive_limits_4) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_draft6, incoherent_exclusive_limits_5) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + TEST(AlterSchema_lint_draft6, equal_numeric_bounds_to_const_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", diff --git a/test/alterschema/alterschema_lint_draft7_test.cc b/test/alterschema/alterschema_lint_draft7_test.cc index 13ce76588..559c30142 100644 --- a/test/alterschema/alterschema_lint_draft7_test.cc +++ b/test/alterschema/alterschema_lint_draft7_test.cc @@ -3041,6 +3041,92 @@ TEST(AlterSchema_lint_draft7, conflicting_readonly_writeonly_4) { false); } +TEST(AlterSchema_lint_draft7, incoherent_exclusive_limits_1) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1, + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft7, incoherent_exclusive_limits_2) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft7, incoherent_exclusive_limits_3) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_draft7, incoherent_exclusive_limits_4) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_draft7, incoherent_exclusive_limits_5) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + TEST(AlterSchema_lint_draft7, duplicate_examples_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#",