diff --git a/src/extension/alterschema/CMakeLists.txt b/src/extension/alterschema/CMakeLists.txt index 3d3c88df9..4f525eb79 100644 --- a/src/extension/alterschema/CMakeLists.txt +++ b/src/extension/alterschema/CMakeLists.txt @@ -82,6 +82,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema linter/duplicate_examples.h linter/enum_to_const.h linter/equal_numeric_bounds_to_const.h + linter/forbid_empty_enum.h linter/items_array_default.h linter/items_schema_default.h linter/multiple_of_default.h diff --git a/src/extension/alterschema/alterschema.cc b/src/extension/alterschema/alterschema.cc index be4bf3ba9..0d1e49035 100644 --- a/src/extension/alterschema/alterschema.cc +++ b/src/extension/alterschema/alterschema.cc @@ -110,6 +110,7 @@ inline auto APPLIES_TO_POINTERS(std::vector &&keywords) #include "linter/duplicate_examples.h" #include "linter/enum_to_const.h" #include "linter/equal_numeric_bounds_to_const.h" +#include "linter/forbid_empty_enum.h" #include "linter/items_array_default.h" #include "linter/items_schema_default.h" #include "linter/multiple_of_default.h" @@ -226,6 +227,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/extension/alterschema/linter/forbid_empty_enum.h b/src/extension/alterschema/linter/forbid_empty_enum.h new file mode 100644 index 000000000..279d20f55 --- /dev/null +++ b/src/extension/alterschema/linter/forbid_empty_enum.h @@ -0,0 +1,36 @@ +class ForbidEmptyEnum final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + ForbidEmptyEnum() + : SchemaTransformRule{"forbid_empty_enum", + "An empty `enum` validates nothing and is " + "unsatisfiable"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> sourcemeta::core::SchemaTransformRule::Result override { + 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, + Vocabularies::Known::JSON_Schema_Draft_4}) && + schema.is_object() && !schema.defines("not") && + schema.defines("enum") && schema.at("enum").is_array() && + schema.at("enum").empty()); + ONLY_CONTINUE_IF(!frame.has_references_through(location.pointer)); + return APPLIES_TO_KEYWORDS("enum"); + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.at("enum").into(JSON::make_object()); + schema.rename("enum", "not"); + } +}; diff --git a/test/alterschema/alterschema_lint_2019_09_test.cc b/test/alterschema/alterschema_lint_2019_09_test.cc index 81863c7da..9effb3edc 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -4495,14 +4495,13 @@ TEST(AlterSchema_lint_2019_09, empty_object_as_true_1) { EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_2019_09, const_in_enum_1) { +TEST(AlterSchema_lint_2019_09, forbid_empty_enum_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": 1, - "enum": [1, 2, 3] + "enum": [] })JSON"); LINT_AND_FIX(document, result, traces); @@ -4511,22 +4510,22 @@ TEST(AlterSchema_lint_2019_09, const_in_enum_1) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": 1 + "not": true })JSON"); EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_2019_09, const_without_enum) { +TEST(AlterSchema_lint_2019_09, forbid_empty_enum_2) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": "foo" + "enum": [1, 2] })JSON"); LINT_AND_FIX(document, result, traces); @@ -4535,22 +4534,22 @@ TEST(AlterSchema_lint_2019_09, const_without_enum) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": "foo" + "enum": [1, 2] })JSON"); EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_2019_09, enum_without_const) { +TEST(AlterSchema_lint_2019_09, forbid_empty_enum_3) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "enum": [1, 2, 3] + "type": "string" })JSON"); LINT_AND_FIX(document, result, traces); @@ -4559,25 +4558,24 @@ TEST(AlterSchema_lint_2019_09, enum_without_const) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "enum": [1, 2, 3] + "type": "string" })JSON"); EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_2019_09, const_in_enum_4) { +TEST(AlterSchema_lint_2019_09, forbid_empty_enum_4) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [{}], "properties": { "foo": { - "const": 1, - "enum": [1, 2, 3] + "enum": [] } } })JSON"); @@ -4588,12 +4586,12 @@ TEST(AlterSchema_lint_2019_09, const_in_enum_4) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [{}], "properties": { "foo": { - "const": 1 + "not": true } } })JSON"); @@ -4601,75 +4599,178 @@ TEST(AlterSchema_lint_2019_09, const_in_enum_4) { EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_2019_09, const_in_enum_edge_case_preserves_siblings) { +TEST(AlterSchema_lint_2019_09, forbid_empty_enum_5) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "https://example.com/schemas/my-schema", - "description": "Edge case schema", - "examples": [{}], - "title": "Edge Case Schema", - "x-custom-annotation": "should not be deleted", - "const": 1, - "enum": [1, 2, 3] + "$defs": { + "A": { + "enum": [] + } + }, + "$ref": "#/$defs/A" })JSON"); LINT_AND_FIX(document, result, traces); - EXPECT_TRUE(result.first); + EXPECT_FALSE(result.first); const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "https://example.com/schemas/my-schema", - "description": "Edge case schema", - "examples": [{}], - "title": "Edge Case Schema", - "x-custom-annotation": "should not be deleted", - "const": 1 + "$defs": { + "A": { + "enum": [] + } + }, + "$ref": "#/$defs/A" })JSON"); EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_2019_09, const_not_in_enum_1) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ +TEST(AlterSchema_lint_2019_09, forbid_empty_enum_6) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", - "title": "Test", - "description": "Test description", - "examples": [1], - "const": 1, - "enum": [2, 3] + "$defs": { + "A": { + "enum": [], + "$defs": { + "inner": { "type": "string" } + } + } + }, + "$ref": "#/$defs/A/$defs/inner" })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); EXPECT_FALSE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "", "const_not_in_enum", - "Do not set the `const` and `enum` keyword at the same " - "time, mainly when their values diverge", - false); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "A": { + "enum": [], + "$defs": { + "inner": { "type": "string" } + } + } + }, + "$ref": "#/$defs/A/$defs/inner" + })JSON"); + + EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_2019_09, const_not_in_enum_2) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ +TEST(AlterSchema_lint_2019_09, forbid_empty_enum_7) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", - "title": "Test", - "description": "Test description", + "$defs": { + "A": { + "$id": "https://example.com/schemas/A", + "enum": [] + } + }, + "$ref": "https://example.com/schemas/A" + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "A": { + "$id": "https://example.com/schemas/A", + "enum": [] + } + }, + "$ref": "https://example.com/schemas/A" + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, forbid_empty_enum_8) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "Example", + "description": "Example schema", "examples": [{}], "properties": { "foo": { - "const": 1, - "enum": [2, 3] + "x-note": "placeholder", + "enum": [] } } })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "Example", + "description": "Example schema", + "examples": [{}], + "properties": { + "foo": { + "x-note": "placeholder", + "not": true + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, forbid_empty_enum_9) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "A": { + "enum": [], + "additionalProperties": { "type": "string" } + } + }, + "$ref": "#/$defs/A/additionalProperties" + })JSON"); + + LINT_AND_FIX(document, result, traces); EXPECT_FALSE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "const_not_in_enum", - "Do not set the `const` and `enum` keyword at the same " - "time, mainly when their values diverge", - false); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "A": { + "enum": [], + "additionalProperties": { "type": "string" } + } + }, + "$ref": "#/$defs/A/additionalProperties" + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, forbid_empty_enum_10) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": { "type": "string" }, + "enum": [] + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": { "type": "string" }, + "enum": [] + })JSON"); + + EXPECT_EQ(document, expected); } diff --git a/test/alterschema/alterschema_lint_2020_12_test.cc b/test/alterschema/alterschema_lint_2020_12_test.cc index 0d99b32c9..43339fed7 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -9665,14 +9665,13 @@ TEST(AlterSchema_lint_2020_12, object_oneof_required_not_required_6) { true); } -TEST(AlterSchema_lint_2020_12, const_in_enum_1) { +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": 1, - "enum": [1, 2, 3] + "enum": [] })JSON"); LINT_AND_FIX(document, result, traces); @@ -9681,22 +9680,22 @@ TEST(AlterSchema_lint_2020_12, const_in_enum_1) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": 1 + "not": true })JSON"); EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_2020_12, const_without_enum) { +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_2) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": "foo" + "enum": [1, 2] })JSON"); LINT_AND_FIX(document, result, traces); @@ -9705,22 +9704,22 @@ TEST(AlterSchema_lint_2020_12, const_without_enum) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": "foo" + "enum": [1, 2] })JSON"); EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_2020_12, enum_without_const) { +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_3) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "enum": [1, 2, 3] + "type": "string" })JSON"); LINT_AND_FIX(document, result, traces); @@ -9729,25 +9728,24 @@ TEST(AlterSchema_lint_2020_12, enum_without_const) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "enum": [1, 2, 3] + "type": "string" })JSON"); EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_2020_12, const_in_enum_4) { +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_4) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [{}], "properties": { "foo": { - "const": 1, - "enum": [1, 2, 3] + "enum": [] } } })JSON"); @@ -9758,12 +9756,12 @@ TEST(AlterSchema_lint_2020_12, const_in_enum_4) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [{}], "properties": { "foo": { - "const": 1 + "not": true } } })JSON"); @@ -9771,16 +9769,23 @@ TEST(AlterSchema_lint_2020_12, const_in_enum_4) { EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_2020_12, const_in_enum_edge_case_preserves_siblings) { +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_5) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://example.com/schemas/my-schema", - "description": "Edge case schema", + "title": "Example", + "description": "Example schema", "examples": [{}], - "title": "Edge Case Schema", - "x-custom-annotation": "should not be deleted", - "const": 1, - "enum": [1, 2, 3] + "properties": { + "foo": { + "enum": [] + }, + "bar": { + "enum": [] + }, + "baz": { + "enum": [1, 2] + } + } })JSON"); LINT_AND_FIX(document, result, traces); @@ -9789,57 +9794,335 @@ TEST(AlterSchema_lint_2020_12, const_in_enum_edge_case_preserves_siblings) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://example.com/schemas/my-schema", - "description": "Edge case schema", + "title": "Example", + "description": "Example schema", "examples": [{}], - "title": "Edge Case Schema", - "x-custom-annotation": "should not be deleted", - "const": 1 + "properties": { + "foo": { + "not": true + }, + "bar": { + "not": true + }, + "baz": { + "enum": [1, 2] + } + } })JSON"); EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_2020_12, const_not_in_enum_1) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_6) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", + "examples": [[]], + "prefixItems": [ + { + "enum": [] + }, + { + "type": "string" + } + ] + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Example", + "description": "Example schema", + "examples": [[]], + "prefixItems": [ + { + "not": true + }, + { + "type": "string" + } + ] + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_7) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Example", + "description": "Example schema", + "examples": [[]], + "properties": { + "arr": { + "type": "array", + "items": { + "x-note": "placeholder", + "enum": [] + } + } + } + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Example", + "description": "Example schema", + "examples": [[]], + "properties": { + "arr": { + "type": "array", + "items": { + "x-note": "placeholder", + "not": true + } + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_8) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": 1, - "enum": [2, 3] + "enum": {} })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Example", + "description": "Example schema", + "examples": [1], + "enum": {} + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_9) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Example", + "description": "Example schema", + "examples": [{}], + "properties": { + "foo": { + "not": { + "enum": [] + } + } + } + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Example", + "description": "Example schema", + "examples": [{}], + "properties": { + "foo": { + "not": { + "not": true + } + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_10) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "A": { + "enum": [] + } + }, + "$ref": "#/$defs/A" + })JSON"); + + LINT_AND_FIX(document, result, traces); EXPECT_FALSE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "", "const_not_in_enum", - "Do not set the `const` and `enum` keyword at the same " - "time, mainly when their values diverge", - false); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "A": { + "enum": [] + } + }, + "$ref": "#/$defs/A" + })JSON"); + + EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_2020_12, const_not_in_enum_2) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_11) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Test", - "description": "Test description", + "$defs": { + "A": { + "enum": [], + "$defs": { + "inner": { "type": "string" } + } + } + }, + "$ref": "#/$defs/A/$defs/inner" + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "A": { + "enum": [], + "$defs": { + "inner": { "type": "string" } + } + } + }, + "$ref": "#/$defs/A/$defs/inner" + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_12) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "A": { + "$id": "https://example.com/schemas/A", + "enum": [] + } + }, + "$ref": "https://example.com/schemas/A" + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "A": { + "$id": "https://example.com/schemas/A", + "enum": [] + } + }, + "$ref": "https://example.com/schemas/A" + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_13) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Example", + "description": "Example schema", "examples": [{}], "properties": { "foo": { - "const": 1, - "enum": [2, 3] + "x-note": "placeholder", + "enum": [] } } })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Example", + "description": "Example schema", + "examples": [{}], + "properties": { + "foo": { + "x-note": "placeholder", + "not": true + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_14) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { "type": "string" }, + "enum": [] + })JSON"); + + LINT_AND_FIX(document, result, traces); EXPECT_FALSE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "const_not_in_enum", - "Do not set the `const` and `enum` keyword at the same " - "time, mainly when their values diverge", - false); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { "type": "string" }, + "enum": [] + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_15) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "A": { + "enum": [], + "additionalProperties": { "type": "string" } + } + }, + "$ref": "#/$defs/A/additionalProperties" + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "A": { + "enum": [], + "additionalProperties": { "type": "string" } + } + }, + "$ref": "#/$defs/A/additionalProperties" + })JSON"); + + EXPECT_EQ(document, expected); } diff --git a/test/alterschema/alterschema_lint_draft3_test.cc b/test/alterschema/alterschema_lint_draft3_test.cc index 69aab7efc..014c80b4c 100644 --- a/test/alterschema/alterschema_lint_draft3_test.cc +++ b/test/alterschema/alterschema_lint_draft3_test.cc @@ -81,6 +81,36 @@ TEST(AlterSchema_lint_draft3, enum_with_type_4) { EXPECT_EQ(document, expected); } +TEST(AlterSchema_lint_draft3, forbid_empty_enum_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "Example", + "description": "Example schema", + "properties": { + "foo": { + "enum": [] + } + } + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "Example", + "description": "Example schema", + "properties": { + "foo": { + "enum": [] + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + TEST(AlterSchema_lint_draft3, non_applicable_enum_validation_keywords_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-03/schema#", diff --git a/test/alterschema/alterschema_lint_draft4_test.cc b/test/alterschema/alterschema_lint_draft4_test.cc index 6c62d61a6..20964d6c1 100644 --- a/test/alterschema/alterschema_lint_draft4_test.cc +++ b/test/alterschema/alterschema_lint_draft4_test.cc @@ -1812,7 +1812,7 @@ TEST(AlterSchema_lint_draft4, not_false_4) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-04/schema#", "type": "string", - "not": true + "not": {} })JSON"); LINT_AND_FIX(document, result, traces); @@ -1822,7 +1822,7 @@ TEST(AlterSchema_lint_draft4, not_false_4) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-04/schema#", "type": "string", - "not": true + "not": {} })JSON"); EXPECT_EQ(document, expected); @@ -2604,3 +2604,99 @@ TEST(AlterSchema_lint_draft4, EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_draft4, forbid_empty_enum_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Example", + "description": "Example schema", + "enum": [] + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Example", + "description": "Example schema", + "not": {} + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft4, forbid_empty_enum_2) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Example", + "description": "Example schema", + "enum": [1, 2] + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Example", + "description": "Example schema", + "enum": [1, 2] + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft4, forbid_empty_enum_3) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Example", + "description": "Example schema", + "type": "string" + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Example", + "description": "Example schema", + "type": "string" + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft4, forbid_empty_enum_4) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Example", + "description": "Example schema", + "properties": { + "foo": { + "enum": [] + } + } + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Example", + "description": "Example schema", + "properties": { + "foo": { + "not": {} + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} diff --git a/test/alterschema/alterschema_lint_draft6_test.cc b/test/alterschema/alterschema_lint_draft6_test.cc index b83ee9909..11cbd43a2 100644 --- a/test/alterschema/alterschema_lint_draft6_test.cc +++ b/test/alterschema/alterschema_lint_draft6_test.cc @@ -2930,14 +2930,13 @@ TEST(AlterSchema_lint_draft6, empty_object_as_true_1) { EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_draft6, const_in_enum_1) { +TEST(AlterSchema_lint_draft6, forbid_empty_enum_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": 1, - "enum": [1, 2, 3] + "enum": [] })JSON"); LINT_AND_FIX(document, result, traces); @@ -2946,22 +2945,22 @@ TEST(AlterSchema_lint_draft6, const_in_enum_1) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": 1 + "not": true })JSON"); EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_draft6, const_without_enum) { +TEST(AlterSchema_lint_draft6, forbid_empty_enum_2) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": "foo" + "enum": [1, 2] })JSON"); LINT_AND_FIX(document, result, traces); @@ -2970,22 +2969,22 @@ TEST(AlterSchema_lint_draft6, const_without_enum) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": "foo" + "enum": [1, 2] })JSON"); EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_draft6, enum_without_const) { +TEST(AlterSchema_lint_draft6, forbid_empty_enum_3) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "enum": [1, 2, 3] + "type": "string" })JSON"); LINT_AND_FIX(document, result, traces); @@ -2994,25 +2993,24 @@ TEST(AlterSchema_lint_draft6, enum_without_const) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "enum": [1, 2, 3] + "type": "string" })JSON"); EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_draft6, const_in_enum_4) { +TEST(AlterSchema_lint_draft6, forbid_empty_enum_4) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [{}], "properties": { "foo": { - "const": 1, - "enum": [1, 2, 3] + "enum": [] } } })JSON"); @@ -3023,88 +3021,15 @@ TEST(AlterSchema_lint_draft6, const_in_enum_4) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [{}], "properties": { "foo": { - "const": 1 + "not": true } } })JSON"); EXPECT_EQ(document, expected); } - -TEST(AlterSchema_lint_draft6, const_in_enum_edge_case_preserves_siblings) { - sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ - "$schema": "http://json-schema.org/draft-06/schema#", - "$id": "https://example.com/schemas/my-schema", - "description": "Edge case schema", - "examples": [{}], - "title": "Edge Case Schema", - "x-custom-annotation": "should not be deleted", - "const": 1, - "enum": [1, 2, 3] - })JSON"); - - LINT_AND_FIX(document, result, traces); - - EXPECT_TRUE(result.first); - - const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ - "$schema": "http://json-schema.org/draft-06/schema#", - "$id": "https://example.com/schemas/my-schema", - "description": "Edge case schema", - "examples": [{}], - "title": "Edge Case Schema", - "x-custom-annotation": "should not be deleted", - "const": 1 - })JSON"); - - EXPECT_EQ(document, expected); -} - -TEST(AlterSchema_lint_draft6, const_not_in_enum_1) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ - "$schema": "http://json-schema.org/draft-06/schema#", - "title": "Test", - "description": "Test description", - "examples": [1], - "const": 1, - "enum": [2, 3] - })JSON"); - - LINT_WITHOUT_FIX(document, result, traces); - - EXPECT_FALSE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "", "const_not_in_enum", - "Do not set the `const` and `enum` keyword at the same " - "time, mainly when their values diverge", - false); -} - -TEST(AlterSchema_lint_draft6, const_not_in_enum_2) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ - "$schema": "http://json-schema.org/draft-06/schema#", - "title": "Test", - "description": "Test description", - "examples": [{}], - "properties": { - "foo": { - "const": 1, - "enum": [2, 3] - } - } - })JSON"); - - LINT_WITHOUT_FIX(document, result, traces); - - EXPECT_FALSE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "const_not_in_enum", - "Do not set the `const` and `enum` keyword at the same " - "time, mainly when their values diverge", - false); -} diff --git a/test/alterschema/alterschema_lint_draft7_test.cc b/test/alterschema/alterschema_lint_draft7_test.cc index 434573a5f..c888faed5 100644 --- a/test/alterschema/alterschema_lint_draft7_test.cc +++ b/test/alterschema/alterschema_lint_draft7_test.cc @@ -3508,14 +3508,13 @@ TEST(AlterSchema_lint_draft7, false); } -TEST(AlterSchema_lint_draft7, const_in_enum_1) { +TEST(AlterSchema_lint_draft7, forbid_empty_enum_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": 1, - "enum": [1, 2, 3] + "enum": [] })JSON"); LINT_AND_FIX(document, result, traces); @@ -3524,22 +3523,22 @@ TEST(AlterSchema_lint_draft7, const_in_enum_1) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": 1 + "not": true })JSON"); EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_draft7, const_without_enum) { +TEST(AlterSchema_lint_draft7, forbid_empty_enum_2) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": "foo" + "enum": [1, 2] })JSON"); LINT_AND_FIX(document, result, traces); @@ -3548,22 +3547,22 @@ TEST(AlterSchema_lint_draft7, const_without_enum) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": "foo" + "enum": [1, 2] })JSON"); EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_draft7, enum_without_const) { +TEST(AlterSchema_lint_draft7, forbid_empty_enum_3) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "enum": [1, 2, 3] + "type": "string" })JSON"); LINT_AND_FIX(document, result, traces); @@ -3572,25 +3571,24 @@ TEST(AlterSchema_lint_draft7, enum_without_const) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [1], - "enum": [1, 2, 3] + "type": "string" })JSON"); EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_draft7, const_in_enum_4) { +TEST(AlterSchema_lint_draft7, forbid_empty_enum_4) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [{}], "properties": { "foo": { - "const": 1, - "enum": [1, 2, 3] + "enum": [] } } })JSON"); @@ -3601,88 +3599,15 @@ TEST(AlterSchema_lint_draft7, const_in_enum_4) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Test", - "description": "Test description", + "title": "Example", + "description": "Example schema", "examples": [{}], "properties": { "foo": { - "const": 1 + "not": true } } })JSON"); EXPECT_EQ(document, expected); } - -TEST(AlterSchema_lint_draft7, const_in_enum_edge_case_preserves_siblings) { - sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://example.com/schemas/my-schema", - "description": "Edge case schema", - "examples": [{}], - "title": "Edge Case Schema", - "x-custom-annotation": "should not be deleted", - "const": 1, - "enum": [1, 2, 3] - })JSON"); - - LINT_AND_FIX(document, result, traces); - - EXPECT_TRUE(result.first); - - const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://example.com/schemas/my-schema", - "description": "Edge case schema", - "examples": [{}], - "title": "Edge Case Schema", - "x-custom-annotation": "should not be deleted", - "const": 1 - })JSON"); - - EXPECT_EQ(document, expected); -} - -TEST(AlterSchema_lint_draft7, const_not_in_enum_1) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Test", - "description": "Test description", - "examples": [1], - "const": 1, - "enum": [2, 3] - })JSON"); - - LINT_WITHOUT_FIX(document, result, traces); - - EXPECT_FALSE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "", "const_not_in_enum", - "Do not set the `const` and `enum` keyword at the same " - "time, mainly when their values diverge", - false); -} - -TEST(AlterSchema_lint_draft7, const_not_in_enum_2) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Test", - "description": "Test description", - "examples": [{}], - "properties": { - "foo": { - "const": 1, - "enum": [2, 3] - } - } - })JSON"); - - LINT_WITHOUT_FIX(document, result, traces); - - EXPECT_FALSE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "const_not_in_enum", - "Do not set the `const` and `enum` keyword at the same " - "time, mainly when their values diverge", - false); -}