From 9ae2cd589aadfb40c0232c58ec605aaca91c363d Mon Sep 17 00:00:00 2001 From: Vaibhav mittal Date: Fri, 13 Mar 2026 11:35:53 +0000 Subject: [PATCH 01/10] feat(linter): make forbid_empty_enum auto-fixable by rewriting to false Signed-off-by: Vaibhav mittal --- src/extension/alterschema/CMakeLists.txt | 1 + src/extension/alterschema/alterschema.cc | 2 + .../alterschema/linter/forbid_empty_enum.h | 36 ++++ .../alterschema_lint_2019_09_test.cc | 169 +++--------------- .../alterschema_lint_2020_12_test.cc | 169 +++--------------- .../alterschema_lint_draft4_test.cc | 64 +++++++ .../alterschema_lint_draft6_test.cc | 169 +++--------------- .../alterschema_lint_draft7_test.cc | 169 +++--------------- 8 files changed, 219 insertions(+), 560 deletions(-) create mode 100644 src/extension/alterschema/linter/forbid_empty_enum.h 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..a2ce1ed10 --- /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::false_type; + ForbidEmptyEnum() + : SchemaTransformRule{"forbid_empty_enum", + "An empty `enum` validates nothing and is " + "equivalent to `false`"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::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, + Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_1}) && + schema.is_object() && schema.defines("enum") && + schema.at("enum").is_array() && schema.at("enum").empty()); + return APPLIES_TO_KEYWORDS("enum"); + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.into(JSON{false}); + } +}; diff --git a/test/alterschema/alterschema_lint_2019_09_test.cc b/test/alterschema/alterschema_lint_2019_09_test.cc index 81863c7da..0777dc833 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -4495,171 +4495,62 @@ TEST(AlterSchema_lint_2019_09, empty_object_as_true_1) { EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_2019_09, const_in_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", - "examples": [1], - "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": "https://json-schema.org/draft/2019-09/schema", - "title": "Test", - "description": "Test description", - "examples": [1], - "const": 1 - })JSON"); - - EXPECT_EQ(document, expected); -} - -TEST(AlterSchema_lint_2019_09, const_without_enum) { - sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ +TEST(AlterSchema_lint_2019_09, forbid_empty_enum_1) { + const 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": [] })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/2019-09/schema", - "title": "Test", - "description": "Test description", - "examples": [1], - "const": "foo" - })JSON"); + LINT_WITHOUT_FIX(document, result, traces); - EXPECT_EQ(document, expected); + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "forbid_empty_enum", + "enum must contain at least one value", false); } -TEST(AlterSchema_lint_2019_09, enum_without_const) { - 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], - "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({ +TEST(AlterSchema_lint_2019_09, forbid_empty_enum_2) { + const 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] - })JSON"); - - EXPECT_EQ(document, expected); -} - -TEST(AlterSchema_lint_2019_09, const_in_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", - "examples": [{}], - "properties": { - "foo": { - "const": 1, - "enum": [1, 2, 3] - } - } + "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": "https://json-schema.org/draft/2019-09/schema", - "title": "Test", - "description": "Test description", - "examples": [{}], - "properties": { - "foo": { - "const": 1 - } - } - })JSON"); - - EXPECT_EQ(document, expected); -} - -TEST(AlterSchema_lint_2019_09, const_in_enum_edge_case_preserves_siblings) { - 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] - })JSON"); - - LINT_AND_FIX(document, result, traces); + LINT_WITHOUT_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", - "$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); + EXPECT_EQ(traces.size(), 0); } -TEST(AlterSchema_lint_2019_09, const_not_in_enum_1) { +TEST(AlterSchema_lint_2019_09, forbid_empty_enum_3) { const 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": [2, 3] + "type": "string" })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); + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); } -TEST(AlterSchema_lint_2019_09, const_not_in_enum_2) { +TEST(AlterSchema_lint_2019_09, forbid_empty_enum_4) { const 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": [2, 3] + "enum": [] } } })JSON"); @@ -4668,8 +4559,6 @@ TEST(AlterSchema_lint_2019_09, const_not_in_enum_2) { 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); + EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", + "enum must contain at least one value", false); } diff --git a/test/alterschema/alterschema_lint_2020_12_test.cc b/test/alterschema/alterschema_lint_2020_12_test.cc index 0d99b32c9..f70c8545a 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -9665,171 +9665,62 @@ TEST(AlterSchema_lint_2020_12, object_oneof_required_not_required_6) { true); } -TEST(AlterSchema_lint_2020_12, const_in_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", - "examples": [1], - "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": "https://json-schema.org/draft/2020-12/schema", - "title": "Test", - "description": "Test description", - "examples": [1], - "const": 1 - })JSON"); - - EXPECT_EQ(document, expected); -} - -TEST(AlterSchema_lint_2020_12, const_without_enum) { - sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_1) { + const 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": [] })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": "Test", - "description": "Test description", - "examples": [1], - "const": "foo" - })JSON"); + LINT_WITHOUT_FIX(document, result, traces); - EXPECT_EQ(document, expected); + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "forbid_empty_enum", + "enum must contain at least one value", false); } -TEST(AlterSchema_lint_2020_12, enum_without_const) { - sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Test", - "description": "Test description", - "examples": [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({ +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_2) { + const 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] - })JSON"); - - EXPECT_EQ(document, expected); -} - -TEST(AlterSchema_lint_2020_12, const_in_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", - "examples": [{}], - "properties": { - "foo": { - "const": 1, - "enum": [1, 2, 3] - } - } + "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": "https://json-schema.org/draft/2020-12/schema", - "title": "Test", - "description": "Test description", - "examples": [{}], - "properties": { - "foo": { - "const": 1 - } - } - })JSON"); - - EXPECT_EQ(document, expected); -} - -TEST(AlterSchema_lint_2020_12, const_in_enum_edge_case_preserves_siblings) { - 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", - "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); + LINT_WITHOUT_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", - "$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); + EXPECT_EQ(traces.size(), 0); } -TEST(AlterSchema_lint_2020_12, const_not_in_enum_1) { +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_3) { const 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": [2, 3] + "type": "string" })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); + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); } -TEST(AlterSchema_lint_2020_12, const_not_in_enum_2) { +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_4) { const 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": [2, 3] + "enum": [] } } })JSON"); @@ -9838,8 +9729,6 @@ TEST(AlterSchema_lint_2020_12, const_not_in_enum_2) { 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); + EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", + "enum must contain at least one value", false); } diff --git a/test/alterschema/alterschema_lint_draft4_test.cc b/test/alterschema/alterschema_lint_draft4_test.cc index 6c62d61a6..134f958b3 100644 --- a/test/alterschema/alterschema_lint_draft4_test.cc +++ b/test/alterschema/alterschema_lint_draft4_test.cc @@ -2604,3 +2604,67 @@ TEST(AlterSchema_lint_draft4, EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_draft4, forbid_empty_enum_1) { + const 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_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "forbid_empty_enum", + "enum must contain at least one value", false); +} + +TEST(AlterSchema_lint_draft4, forbid_empty_enum_2) { + const 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_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft4, forbid_empty_enum_3) { + const 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_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft4, forbid_empty_enum_4) { + const 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_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", + "enum must contain at least one value", false); +} diff --git a/test/alterschema/alterschema_lint_draft6_test.cc b/test/alterschema/alterschema_lint_draft6_test.cc index b83ee9909..a13895961 100644 --- a/test/alterschema/alterschema_lint_draft6_test.cc +++ b/test/alterschema/alterschema_lint_draft6_test.cc @@ -2930,171 +2930,62 @@ TEST(AlterSchema_lint_draft6, empty_object_as_true_1) { EXPECT_EQ(document, expected); } -TEST(AlterSchema_lint_draft6, const_in_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", - "examples": [1], - "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#", - "title": "Test", - "description": "Test description", - "examples": [1], - "const": 1 - })JSON"); - - EXPECT_EQ(document, expected); -} - -TEST(AlterSchema_lint_draft6, const_without_enum) { - sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ +TEST(AlterSchema_lint_draft6, forbid_empty_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", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": "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-06/schema#", - "title": "Test", - "description": "Test description", - "examples": [1], - "const": "foo" - })JSON"); + LINT_WITHOUT_FIX(document, result, traces); - EXPECT_EQ(document, expected); + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "forbid_empty_enum", + "enum must contain at least one value", false); } -TEST(AlterSchema_lint_draft6, enum_without_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], - "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({ +TEST(AlterSchema_lint_draft6, forbid_empty_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", + "title": "Example", + "description": "Example schema", "examples": [1], - "enum": [1, 2, 3] - })JSON"); - - EXPECT_EQ(document, expected); -} - -TEST(AlterSchema_lint_draft6, const_in_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", - "examples": [{}], - "properties": { - "foo": { - "const": 1, - "enum": [1, 2, 3] - } - } + "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-06/schema#", - "title": "Test", - "description": "Test description", - "examples": [{}], - "properties": { - "foo": { - "const": 1 - } - } - })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); + LINT_WITHOUT_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); + EXPECT_EQ(traces.size(), 0); } -TEST(AlterSchema_lint_draft6, const_not_in_enum_1) { +TEST(AlterSchema_lint_draft6, forbid_empty_enum_3) { const 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": [2, 3] + "type": "string" })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); + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); } -TEST(AlterSchema_lint_draft6, const_not_in_enum_2) { +TEST(AlterSchema_lint_draft6, forbid_empty_enum_4) { const 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": [2, 3] + "enum": [] } } })JSON"); @@ -3103,8 +2994,6 @@ TEST(AlterSchema_lint_draft6, const_not_in_enum_2) { 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); + EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", + "enum must contain at least one value", false); } diff --git a/test/alterschema/alterschema_lint_draft7_test.cc b/test/alterschema/alterschema_lint_draft7_test.cc index 434573a5f..eae69230b 100644 --- a/test/alterschema/alterschema_lint_draft7_test.cc +++ b/test/alterschema/alterschema_lint_draft7_test.cc @@ -3508,171 +3508,62 @@ TEST(AlterSchema_lint_draft7, false); } -TEST(AlterSchema_lint_draft7, const_in_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", - "examples": [1], - "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#", - "title": "Test", - "description": "Test description", - "examples": [1], - "const": 1 - })JSON"); - - EXPECT_EQ(document, expected); -} - -TEST(AlterSchema_lint_draft7, const_without_enum) { - sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ +TEST(AlterSchema_lint_draft7, forbid_empty_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", + "title": "Example", + "description": "Example schema", "examples": [1], - "const": "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-07/schema#", - "title": "Test", - "description": "Test description", - "examples": [1], - "const": "foo" - })JSON"); + LINT_WITHOUT_FIX(document, result, traces); - EXPECT_EQ(document, expected); + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "forbid_empty_enum", + "enum must contain at least one value", false); } -TEST(AlterSchema_lint_draft7, enum_without_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], - "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({ +TEST(AlterSchema_lint_draft7, forbid_empty_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", + "title": "Example", + "description": "Example schema", "examples": [1], - "enum": [1, 2, 3] - })JSON"); - - EXPECT_EQ(document, expected); -} - -TEST(AlterSchema_lint_draft7, const_in_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", - "examples": [{}], - "properties": { - "foo": { - "const": 1, - "enum": [1, 2, 3] - } - } + "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-07/schema#", - "title": "Test", - "description": "Test description", - "examples": [{}], - "properties": { - "foo": { - "const": 1 - } - } - })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); + LINT_WITHOUT_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); + EXPECT_EQ(traces.size(), 0); } -TEST(AlterSchema_lint_draft7, const_not_in_enum_1) { +TEST(AlterSchema_lint_draft7, forbid_empty_enum_3) { const 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": [2, 3] + "type": "string" })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); + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); } -TEST(AlterSchema_lint_draft7, const_not_in_enum_2) { +TEST(AlterSchema_lint_draft7, forbid_empty_enum_4) { const 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": [2, 3] + "enum": [] } } })JSON"); @@ -3681,8 +3572,6 @@ TEST(AlterSchema_lint_draft7, const_not_in_enum_2) { 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); + EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", + "enum must contain at least one value", false); } From 0bc062ec37ebacd9329437c4611b4179d8f7e674 Mon Sep 17 00:00:00 2001 From: Vaibhav mittal Date: Fri, 13 Mar 2026 11:26:39 +0000 Subject: [PATCH 02/10] test(linter): update forbid_empty_enum tests for 2019-09 and 2020-12 Signed-off-by: Vaibhav mittal --- .../alterschema_lint_2019_09_test.cc | 24 ++- .../alterschema_lint_2020_12_test.cc | 194 +++++++++++++++++- 2 files changed, 209 insertions(+), 9 deletions(-) diff --git a/test/alterschema/alterschema_lint_2019_09_test.cc b/test/alterschema/alterschema_lint_2019_09_test.cc index 0777dc833..5d2eebb8e 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -4509,7 +4509,9 @@ TEST(AlterSchema_lint_2019_09, forbid_empty_enum_1) { EXPECT_FALSE(result.first); EXPECT_EQ(traces.size(), 1); EXPECT_LINT_TRACE(traces, 0, "", "forbid_empty_enum", - "enum must contain at least one value", false); + "An empty `enum` validates nothing and is equivalent to " + "`false`", + true); } TEST(AlterSchema_lint_2019_09, forbid_empty_enum_2) { @@ -4543,7 +4545,7 @@ TEST(AlterSchema_lint_2019_09, forbid_empty_enum_3) { } TEST(AlterSchema_lint_2019_09, forbid_empty_enum_4) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", "title": "Example", "description": "Example schema", @@ -4554,11 +4556,23 @@ TEST(AlterSchema_lint_2019_09, forbid_empty_enum_4) { } } })JSON"); + 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": false + } + })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); - EXPECT_FALSE(result.first); + EXPECT_TRUE(result.first); EXPECT_EQ(traces.size(), 1); EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", - "enum must contain at least one value", false); + "An empty `enum` validates nothing and is equivalent to " + "`false`", + true); + 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 f70c8545a..f75d0952b 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -9679,7 +9679,9 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_1) { EXPECT_FALSE(result.first); EXPECT_EQ(traces.size(), 1); EXPECT_LINT_TRACE(traces, 0, "", "forbid_empty_enum", - "enum must contain at least one value", false); + "An empty `enum` validates nothing and is equivalent to " + "`false`", + true); } TEST(AlterSchema_lint_2020_12, forbid_empty_enum_2) { @@ -9713,7 +9715,40 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_3) { } TEST(AlterSchema_lint_2020_12, forbid_empty_enum_4) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + 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": { + "enum": [] + } + } + })JSON"); + 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": false + } + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", + "An empty `enum` validates nothing and is equivalent to " + "`false`", + true); + EXPECT_EQ(document, expected); +} + +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", "title": "Example", "description": "Example schema", @@ -9721,6 +9756,155 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_4) { "properties": { "foo": { "enum": [] + }, + "bar": { + "enum": [] + }, + "baz": { + "enum": [1, 2] + } + } + })JSON"); + 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": false, + "bar": false, + "baz": { + "enum": [1, 2] + } + } + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 2); + bool has_foo{false}; + bool has_bar{false}; + for (const auto &trace : traces) { + const auto pointer{sourcemeta::core::to_string(std::get<0>(trace))}; + const auto &name{std::get<1>(trace)}; + if (pointer == "/properties/foo" && name == "forbid_empty_enum") { + has_foo = true; + } + if (pointer == "/properties/bar" && name == "forbid_empty_enum") { + has_bar = true; + } + } + EXPECT_TRUE(has_foo); + EXPECT_TRUE(has_bar); + EXPECT_EQ(document, expected); +} + +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": "Example", + "description": "Example schema", + "examples": [[]], + "prefixItems": [ + { + "enum": [] + }, + { + "type": "string" + } + ] + })JSON"); + 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": [ + false, + { + "type": "string" + } + ] + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "/prefixItems/0", "forbid_empty_enum", + "An empty `enum` validates nothing and is equivalent to " + "`false`", + true); + 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"); + 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": false + } + } + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "/properties/arr/items", "forbid_empty_enum", + "An empty `enum` validates nothing and is equivalent to " + "`false`", + true); + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_8) { + const 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], + "enum": {} + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_2020_12, forbid_empty_enum_9) { + const 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"); @@ -9729,6 +9913,8 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_4) { EXPECT_FALSE(result.first); EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", - "enum must contain at least one value", false); + EXPECT_LINT_TRACE(traces, 0, "/properties/foo/not", "forbid_empty_enum", + "An empty `enum` validates nothing and is equivalent to " + "`false`", + true); } From d71d15c479ef6f61d211ddb261e3413d7a9d64e6 Mon Sep 17 00:00:00 2001 From: Vaibhav mittal Date: Fri, 13 Mar 2026 11:26:53 +0000 Subject: [PATCH 03/10] test(linter): update forbid_empty_enum tests for draft4, draft6, and draft7 Signed-off-by: Vaibhav mittal --- .../alterschema_lint_draft4_test.cc | 23 ++++++++++++++---- .../alterschema_lint_draft6_test.cc | 24 +++++++++++++++---- .../alterschema_lint_draft7_test.cc | 24 +++++++++++++++---- 3 files changed, 56 insertions(+), 15 deletions(-) diff --git a/test/alterschema/alterschema_lint_draft4_test.cc b/test/alterschema/alterschema_lint_draft4_test.cc index 134f958b3..9273804f0 100644 --- a/test/alterschema/alterschema_lint_draft4_test.cc +++ b/test/alterschema/alterschema_lint_draft4_test.cc @@ -2618,7 +2618,9 @@ TEST(AlterSchema_lint_draft4, forbid_empty_enum_1) { EXPECT_FALSE(result.first); EXPECT_EQ(traces.size(), 1); EXPECT_LINT_TRACE(traces, 0, "", "forbid_empty_enum", - "enum must contain at least one value", false); + "An empty `enum` validates nothing and is equivalent to " + "`false`", + true); } TEST(AlterSchema_lint_draft4, forbid_empty_enum_2) { @@ -2650,7 +2652,7 @@ TEST(AlterSchema_lint_draft4, forbid_empty_enum_3) { } TEST(AlterSchema_lint_draft4, forbid_empty_enum_4) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-04/schema#", "title": "Example", "description": "Example schema", @@ -2660,11 +2662,22 @@ TEST(AlterSchema_lint_draft4, forbid_empty_enum_4) { } } })JSON"); + 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": false + } + })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); - EXPECT_FALSE(result.first); + EXPECT_TRUE(result.first); EXPECT_EQ(traces.size(), 1); EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", - "enum must contain at least one value", false); + "An empty `enum` validates nothing and is equivalent to " + "`false`", + true); + EXPECT_EQ(document, expected); } diff --git a/test/alterschema/alterschema_lint_draft6_test.cc b/test/alterschema/alterschema_lint_draft6_test.cc index a13895961..361439693 100644 --- a/test/alterschema/alterschema_lint_draft6_test.cc +++ b/test/alterschema/alterschema_lint_draft6_test.cc @@ -2944,7 +2944,9 @@ TEST(AlterSchema_lint_draft6, forbid_empty_enum_1) { EXPECT_FALSE(result.first); EXPECT_EQ(traces.size(), 1); EXPECT_LINT_TRACE(traces, 0, "", "forbid_empty_enum", - "enum must contain at least one value", false); + "An empty `enum` validates nothing and is equivalent to " + "`false`", + true); } TEST(AlterSchema_lint_draft6, forbid_empty_enum_2) { @@ -2978,7 +2980,7 @@ TEST(AlterSchema_lint_draft6, forbid_empty_enum_3) { } TEST(AlterSchema_lint_draft6, forbid_empty_enum_4) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", "title": "Example", "description": "Example schema", @@ -2989,11 +2991,23 @@ TEST(AlterSchema_lint_draft6, forbid_empty_enum_4) { } } })JSON"); + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Example", + "description": "Example schema", + "examples": [{}], + "properties": { + "foo": false + } + })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); - EXPECT_FALSE(result.first); + EXPECT_TRUE(result.first); EXPECT_EQ(traces.size(), 1); EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", - "enum must contain at least one value", false); + "An empty `enum` validates nothing and is equivalent to " + "`false`", + true); + EXPECT_EQ(document, expected); } diff --git a/test/alterschema/alterschema_lint_draft7_test.cc b/test/alterschema/alterschema_lint_draft7_test.cc index eae69230b..cbebc8f96 100644 --- a/test/alterschema/alterschema_lint_draft7_test.cc +++ b/test/alterschema/alterschema_lint_draft7_test.cc @@ -3522,7 +3522,9 @@ TEST(AlterSchema_lint_draft7, forbid_empty_enum_1) { EXPECT_FALSE(result.first); EXPECT_EQ(traces.size(), 1); EXPECT_LINT_TRACE(traces, 0, "", "forbid_empty_enum", - "enum must contain at least one value", false); + "An empty `enum` validates nothing and is equivalent to " + "`false`", + true); } TEST(AlterSchema_lint_draft7, forbid_empty_enum_2) { @@ -3556,7 +3558,7 @@ TEST(AlterSchema_lint_draft7, forbid_empty_enum_3) { } TEST(AlterSchema_lint_draft7, forbid_empty_enum_4) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Example", "description": "Example schema", @@ -3567,11 +3569,23 @@ TEST(AlterSchema_lint_draft7, forbid_empty_enum_4) { } } })JSON"); + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Example", + "description": "Example schema", + "examples": [{}], + "properties": { + "foo": false + } + })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); - EXPECT_FALSE(result.first); + EXPECT_TRUE(result.first); EXPECT_EQ(traces.size(), 1); EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", - "enum must contain at least one value", false); + "An empty `enum` validates nothing and is equivalent to " + "`false`", + true); + EXPECT_EQ(document, expected); } From 464b493a325edb2828da83f6257d06c18d03cdb4 Mon Sep 17 00:00:00 2001 From: Vaibhav mittal Date: Fri, 13 Mar 2026 11:27:10 +0000 Subject: [PATCH 04/10] test(linter): add edge cases and draft3 coverage for forbid_empty_enum Signed-off-by: Vaibhav mittal --- .../alterschema_lint_draft3_test.cc | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/alterschema/alterschema_lint_draft3_test.cc b/test/alterschema/alterschema_lint_draft3_test.cc index 69aab7efc..a970bbc7a 100644 --- a/test/alterschema/alterschema_lint_draft3_test.cc +++ b/test/alterschema/alterschema_lint_draft3_test.cc @@ -81,6 +81,37 @@ 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"); + 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": false + } + })JSON"); + + LINT_AND_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", + "An empty `enum` validates nothing and is equivalent to " + "`false`", + true); + 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#", From ba59f0bc5aa033eda736664a2f011b874f6c1543 Mon Sep 17 00:00:00 2001 From: Vaibhav mittal Date: Fri, 13 Mar 2026 15:17:06 +0000 Subject: [PATCH 05/10] feat(linter): preserve schema structure in forbid_empty_enum autofix Signed-off-by: Vaibhav mittal --- .../alterschema/linter/forbid_empty_enum.h | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/extension/alterschema/linter/forbid_empty_enum.h b/src/extension/alterschema/linter/forbid_empty_enum.h index a2ce1ed10..98713e252 100644 --- a/src/extension/alterschema/linter/forbid_empty_enum.h +++ b/src/extension/alterschema/linter/forbid_empty_enum.h @@ -1,11 +1,11 @@ class ForbidEmptyEnum final : public SchemaTransformRule { public: using mutates = std::true_type; - using reframe_after_transform = std::false_type; + using reframe_after_transform = std::true_type; ForbidEmptyEnum() : SchemaTransformRule{"forbid_empty_enum", "An empty `enum` validates nothing and is " - "equivalent to `false`"} {}; + "equivalent to `not: {}`"} {}; [[nodiscard]] auto condition(const sourcemeta::core::JSON &schema, @@ -21,16 +21,15 @@ class ForbidEmptyEnum final : public SchemaTransformRule { 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, - Vocabularies::Known::JSON_Schema_Draft_3, - Vocabularies::Known::JSON_Schema_Draft_2, - Vocabularies::Known::JSON_Schema_Draft_1}) && + Vocabularies::Known::JSON_Schema_Draft_4}) && schema.is_object() && schema.defines("enum") && - schema.at("enum").is_array() && schema.at("enum").empty()); + !schema.defines("not") && schema.at("enum").is_array() && + schema.at("enum").empty()); return APPLIES_TO_KEYWORDS("enum"); } auto transform(JSON &schema, const Result &) const -> void override { - schema.into(JSON{false}); + schema.erase("enum"); + schema.assign("not", JSON::make_object()); } }; From 4e873fb5272154673a62e565532233f51cd29e48 Mon Sep 17 00:00:00 2001 From: Vaibhav mittal Date: Fri, 13 Mar 2026 15:48:12 +0000 Subject: [PATCH 06/10] test(linter): extend forbid_empty_enum coverage across dialects and reference edge cases Signed-off-by: Vaibhav mittal --- .../alterschema_lint_2019_09_test.cc | 204 ++++++++++-- .../alterschema_lint_2020_12_test.cc | 314 ++++++++++++++---- .../alterschema_lint_draft3_test.cc | 9 +- .../alterschema_lint_draft4_test.cc | 61 ++-- .../alterschema_lint_draft6_test.cc | 60 ++-- .../alterschema_lint_draft7_test.cc | 60 ++-- 6 files changed, 560 insertions(+), 148 deletions(-) diff --git a/test/alterschema/alterschema_lint_2019_09_test.cc b/test/alterschema/alterschema_lint_2019_09_test.cc index 5d2eebb8e..bf6f714f4 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -4496,7 +4496,7 @@ TEST(AlterSchema_lint_2019_09, empty_object_as_true_1) { } TEST(AlterSchema_lint_2019_09, forbid_empty_enum_1) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", "title": "Example", "description": "Example schema", @@ -4504,18 +4504,23 @@ TEST(AlterSchema_lint_2019_09, forbid_empty_enum_1) { "enum": [] })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, "", "forbid_empty_enum", - "An empty `enum` validates nothing and is equivalent to " - "`false`", - true); + 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": [1], + "not": true + })JSON"); + + EXPECT_EQ(document, expected); } TEST(AlterSchema_lint_2019_09, forbid_empty_enum_2) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", "title": "Example", "description": "Example schema", @@ -4523,14 +4528,23 @@ TEST(AlterSchema_lint_2019_09, forbid_empty_enum_2) { "enum": [1, 2] })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 0); + + 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": [1], + "enum": [1, 2] + })JSON"); + + EXPECT_EQ(document, expected); } TEST(AlterSchema_lint_2019_09, forbid_empty_enum_3) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", "title": "Example", "description": "Example schema", @@ -4538,10 +4552,19 @@ TEST(AlterSchema_lint_2019_09, forbid_empty_enum_3) { "type": "string" })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 0); + + 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": [1], + "type": "string" + })JSON"); + + EXPECT_EQ(document, expected); } TEST(AlterSchema_lint_2019_09, forbid_empty_enum_4) { @@ -4562,17 +4585,158 @@ TEST(AlterSchema_lint_2019_09, forbid_empty_enum_4) { "description": "Example schema", "examples": [{}], "properties": { - "foo": false + "foo": { + "not": true + } } })JSON"); LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", - "An empty `enum` validates nothing and is equivalent to " - "`false`", - true); + EXPECT_EQ(document, expected); +} + +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", + "title": "Example", + "description": "Example schema", + "examples": [{}], + "$defs": { + "A": { + "enum": [] + } + }, + "$ref": "#/$defs/A" + })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/2019-09/schema", + "title": "Example", + "description": "Example schema", + "examples": [{}], + "$defs": { + "A": { + "not": true + } + }, + "$ref": "#/$defs/A" + })JSON"); + + EXPECT_EQ(document, expected); +} + +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": "Example", + "description": "Example schema", + "examples": [{}], + "$defs": { + "A": { + "enum": [], + "$defs": { + "inner": { "type": "string" } + } + } + }, + "$ref": "#/$defs/A/$defs/inner" + })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/2019-09/schema", + "title": "Example", + "description": "Example schema", + "examples": [{}], + "$defs": { + "A": { + "$defs": { + "inner": { "type": "string" } + }, + "not": true + } + }, + "$ref": "#/$defs/A/$defs/inner" + })JSON"); + + EXPECT_EQ(document, expected); +} + +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": "Example", + "description": "Example schema", + "examples": [{}], + "$defs": { + "A": { + "$id": "https://example.com/schemas/A", + "enum": [] + } + }, + "$ref": "https://example.com/schemas/A" + })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/2019-09/schema", + "title": "Example", + "description": "Example schema", + "examples": [{}], + "$defs": { + "A": { + "$id": "https://example.com/schemas/A", + "not": true + } + }, + "$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": { + "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/2019-09/schema", + "title": "Example", + "description": "Example schema", + "examples": [{}], + "properties": { + "foo": { + "x-note": "placeholder", + "not": true + } + } + })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 f75d0952b..c441b4972 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -9666,7 +9666,7 @@ TEST(AlterSchema_lint_2020_12, object_oneof_required_not_required_6) { } TEST(AlterSchema_lint_2020_12, forbid_empty_enum_1) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Example", "description": "Example schema", @@ -9674,18 +9674,23 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_1) { "enum": [] })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, "", "forbid_empty_enum", - "An empty `enum` validates nothing and is equivalent to " - "`false`", - true); + 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], + "not": true + })JSON"); + + EXPECT_EQ(document, expected); } TEST(AlterSchema_lint_2020_12, forbid_empty_enum_2) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Example", "description": "Example schema", @@ -9693,14 +9698,23 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_2) { "enum": [1, 2] })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 0); + + 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": [1, 2] + })JSON"); + + EXPECT_EQ(document, expected); } TEST(AlterSchema_lint_2020_12, forbid_empty_enum_3) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Example", "description": "Example schema", @@ -9708,10 +9722,19 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_3) { "type": "string" })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 0); + + 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], + "type": "string" + })JSON"); + + EXPECT_EQ(document, expected); } TEST(AlterSchema_lint_2020_12, forbid_empty_enum_4) { @@ -9732,18 +9755,15 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_4) { "description": "Example schema", "examples": [{}], "properties": { - "foo": false + "foo": { + "not": true + } } })JSON"); LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", - "An empty `enum` validates nothing and is equivalent to " - "`false`", - true); EXPECT_EQ(document, expected); } @@ -9771,8 +9791,12 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_5) { "description": "Example schema", "examples": [{}], "properties": { - "foo": false, - "bar": false, + "foo": { + "not": true + }, + "bar": { + "not": true + }, "baz": { "enum": [1, 2] } @@ -9782,21 +9806,6 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_5) { LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 2); - bool has_foo{false}; - bool has_bar{false}; - for (const auto &trace : traces) { - const auto pointer{sourcemeta::core::to_string(std::get<0>(trace))}; - const auto &name{std::get<1>(trace)}; - if (pointer == "/properties/foo" && name == "forbid_empty_enum") { - has_foo = true; - } - if (pointer == "/properties/bar" && name == "forbid_empty_enum") { - has_bar = true; - } - } - EXPECT_TRUE(has_foo); - EXPECT_TRUE(has_bar); EXPECT_EQ(document, expected); } @@ -9821,7 +9830,9 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_6) { "description": "Example schema", "examples": [[]], "prefixItems": [ - false, + { + "not": true + }, { "type": "string" } @@ -9831,11 +9842,6 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_6) { LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "/prefixItems/0", "forbid_empty_enum", - "An empty `enum` validates nothing and is equivalent to " - "`false`", - true); EXPECT_EQ(document, expected); } @@ -9863,7 +9869,10 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_7) { "properties": { "arr": { "type": "array", - "items": false + "items": { + "x-note": "placeholder", + "not": true + } } } })JSON"); @@ -9871,16 +9880,11 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_7) { LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "/properties/arr/items", "forbid_empty_enum", - "An empty `enum` validates nothing and is equivalent to " - "`false`", - true); EXPECT_EQ(document, expected); } TEST(AlterSchema_lint_2020_12, forbid_empty_enum_8) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Example", "description": "Example schema", @@ -9888,14 +9892,23 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_8) { "enum": {} })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 0); + + 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) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Example", "description": "Example schema", @@ -9909,12 +9922,193 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_9) { } })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, "/properties/foo/not", "forbid_empty_enum", - "An empty `enum` validates nothing and is equivalent to " - "`false`", - true); + 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", + "title": "Example", + "description": "Example schema", + "examples": [{}], + "$defs": { + "A": { + "enum": [] + } + }, + "$ref": "#/$defs/A" + })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": [{}], + "$defs": { + "A": { + "not": true + } + }, + "$ref": "#/$defs/A" + })JSON"); + + EXPECT_EQ(document, expected); +} + +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": "Example", + "description": "Example schema", + "examples": [{}], + "$defs": { + "A": { + "enum": [], + "$defs": { + "inner": { "type": "string" } + } + } + }, + "$ref": "#/$defs/A/$defs/inner" + })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": [{}], + "$defs": { + "A": { + "$defs": { + "inner": { "type": "string" } + }, + "not": true + } + }, + "$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", + "title": "Example", + "description": "Example schema", + "examples": [{}], + "$defs": { + "A": { + "$id": "https://example.com/schemas/A", + "enum": [] + } + }, + "$ref": "https://example.com/schemas/A" + })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": [{}], + "$defs": { + "A": { + "$id": "https://example.com/schemas/A", + "not": true + } + }, + "$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": { + "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": { + "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", + "title": "Example", + "description": "Example schema", + "examples": [{}], + "not": { "type": "string" }, + "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": [{}], + "not": { "type": "string" }, + "enum": [] + })JSON"); + + EXPECT_EQ(document, expected); } diff --git a/test/alterschema/alterschema_lint_draft3_test.cc b/test/alterschema/alterschema_lint_draft3_test.cc index a970bbc7a..b608808d7 100644 --- a/test/alterschema/alterschema_lint_draft3_test.cc +++ b/test/alterschema/alterschema_lint_draft3_test.cc @@ -97,18 +97,15 @@ TEST(AlterSchema_lint_draft3, forbid_empty_enum_1) { "title": "Example", "description": "Example schema", "properties": { - "foo": false + "foo": { + "enum": [] + } } })JSON"); LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", - "An empty `enum` validates nothing and is equivalent to " - "`false`", - true); EXPECT_EQ(document, expected); } diff --git a/test/alterschema/alterschema_lint_draft4_test.cc b/test/alterschema/alterschema_lint_draft4_test.cc index 9273804f0..038954fb5 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); @@ -2606,49 +2606,69 @@ TEST(AlterSchema_lint_draft4, } TEST(AlterSchema_lint_draft4, forbid_empty_enum_1) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + 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_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, "", "forbid_empty_enum", - "An empty `enum` validates nothing and is equivalent to " - "`false`", - true); + 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) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + 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_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 0); + + 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) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + 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_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 0); + + 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) { @@ -2667,17 +2687,14 @@ TEST(AlterSchema_lint_draft4, forbid_empty_enum_4) { "title": "Example", "description": "Example schema", "properties": { - "foo": false + "foo": { + "not": {} + } } })JSON"); LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", - "An empty `enum` validates nothing and is equivalent to " - "`false`", - true); EXPECT_EQ(document, expected); } diff --git a/test/alterschema/alterschema_lint_draft6_test.cc b/test/alterschema/alterschema_lint_draft6_test.cc index 361439693..b66a21057 100644 --- a/test/alterschema/alterschema_lint_draft6_test.cc +++ b/test/alterschema/alterschema_lint_draft6_test.cc @@ -2931,7 +2931,7 @@ TEST(AlterSchema_lint_draft6, empty_object_as_true_1) { } TEST(AlterSchema_lint_draft6, forbid_empty_enum_1) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", "title": "Example", "description": "Example schema", @@ -2939,18 +2939,23 @@ TEST(AlterSchema_lint_draft6, forbid_empty_enum_1) { "enum": [] })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, "", "forbid_empty_enum", - "An empty `enum` validates nothing and is equivalent to " - "`false`", - true); + EXPECT_TRUE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Example", + "description": "Example schema", + "examples": [1], + "not": true + })JSON"); + + EXPECT_EQ(document, expected); } TEST(AlterSchema_lint_draft6, forbid_empty_enum_2) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", "title": "Example", "description": "Example schema", @@ -2958,14 +2963,23 @@ TEST(AlterSchema_lint_draft6, forbid_empty_enum_2) { "enum": [1, 2] })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 0); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Example", + "description": "Example schema", + "examples": [1], + "enum": [1, 2] + })JSON"); + + EXPECT_EQ(document, expected); } TEST(AlterSchema_lint_draft6, forbid_empty_enum_3) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", "title": "Example", "description": "Example schema", @@ -2973,10 +2987,19 @@ TEST(AlterSchema_lint_draft6, forbid_empty_enum_3) { "type": "string" })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 0); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Example", + "description": "Example schema", + "examples": [1], + "type": "string" + })JSON"); + + EXPECT_EQ(document, expected); } TEST(AlterSchema_lint_draft6, forbid_empty_enum_4) { @@ -2997,17 +3020,14 @@ TEST(AlterSchema_lint_draft6, forbid_empty_enum_4) { "description": "Example schema", "examples": [{}], "properties": { - "foo": false + "foo": { + "not": true + } } })JSON"); LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", - "An empty `enum` validates nothing and is equivalent to " - "`false`", - true); EXPECT_EQ(document, expected); } diff --git a/test/alterschema/alterschema_lint_draft7_test.cc b/test/alterschema/alterschema_lint_draft7_test.cc index cbebc8f96..fe231ed98 100644 --- a/test/alterschema/alterschema_lint_draft7_test.cc +++ b/test/alterschema/alterschema_lint_draft7_test.cc @@ -3509,7 +3509,7 @@ TEST(AlterSchema_lint_draft7, } TEST(AlterSchema_lint_draft7, forbid_empty_enum_1) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Example", "description": "Example schema", @@ -3517,18 +3517,23 @@ TEST(AlterSchema_lint_draft7, forbid_empty_enum_1) { "enum": [] })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, "", "forbid_empty_enum", - "An empty `enum` validates nothing and is equivalent to " - "`false`", - true); + EXPECT_TRUE(result.first); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Example", + "description": "Example schema", + "examples": [1], + "not": true + })JSON"); + + EXPECT_EQ(document, expected); } TEST(AlterSchema_lint_draft7, forbid_empty_enum_2) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Example", "description": "Example schema", @@ -3536,14 +3541,23 @@ TEST(AlterSchema_lint_draft7, forbid_empty_enum_2) { "enum": [1, 2] })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 0); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Example", + "description": "Example schema", + "examples": [1], + "enum": [1, 2] + })JSON"); + + EXPECT_EQ(document, expected); } TEST(AlterSchema_lint_draft7, forbid_empty_enum_3) { - const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Example", "description": "Example schema", @@ -3551,10 +3565,19 @@ TEST(AlterSchema_lint_draft7, forbid_empty_enum_3) { "type": "string" })JSON"); - LINT_WITHOUT_FIX(document, result, traces); + LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 0); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Example", + "description": "Example schema", + "examples": [1], + "type": "string" + })JSON"); + + EXPECT_EQ(document, expected); } TEST(AlterSchema_lint_draft7, forbid_empty_enum_4) { @@ -3575,17 +3598,14 @@ TEST(AlterSchema_lint_draft7, forbid_empty_enum_4) { "description": "Example schema", "examples": [{}], "properties": { - "foo": false + "foo": { + "not": true + } } })JSON"); LINT_AND_FIX(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_EQ(traces.size(), 1); - EXPECT_LINT_TRACE(traces, 0, "/properties/foo", "forbid_empty_enum", - "An empty `enum` validates nothing and is equivalent to " - "`false`", - true); EXPECT_EQ(document, expected); } From f8452c6e29f22a2ede8e84ae7225179e5b72fb21 Mon Sep 17 00:00:00 2001 From: Vaibhav mittal Date: Sat, 21 Mar 2026 09:44:59 +0000 Subject: [PATCH 07/10] fix(linter): correct forbid_empty_enum autofix Signed-off-by: Vaibhav mittal --- src/extension/alterschema/linter/forbid_empty_enum.h | 3 +-- test/alterschema/alterschema_lint_2020_12_test.cc | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/extension/alterschema/linter/forbid_empty_enum.h b/src/extension/alterschema/linter/forbid_empty_enum.h index 98713e252..7684132bf 100644 --- a/src/extension/alterschema/linter/forbid_empty_enum.h +++ b/src/extension/alterschema/linter/forbid_empty_enum.h @@ -23,8 +23,7 @@ class ForbidEmptyEnum final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4}) && schema.is_object() && schema.defines("enum") && - !schema.defines("not") && schema.at("enum").is_array() && - schema.at("enum").empty()); + schema.at("enum").is_array() && schema.at("enum").empty()); return APPLIES_TO_KEYWORDS("enum"); } diff --git a/test/alterschema/alterschema_lint_2020_12_test.cc b/test/alterschema/alterschema_lint_2020_12_test.cc index c441b4972..776ab6cd5 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -10106,8 +10106,7 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_14) { "title": "Example", "description": "Example schema", "examples": [{}], - "not": { "type": "string" }, - "enum": [] + "not": true })JSON"); EXPECT_EQ(document, expected); From ede594ed31a7e7ac43850d7b55e594637c613d19 Mon Sep 17 00:00:00 2001 From: Vaibhav mittal Date: Tue, 24 Mar 2026 02:38:10 +0000 Subject: [PATCH 08/10] fix(linter): finalize forbid_empty_enum with correct reference guards and tests Signed-off-by: Vaibhav mittal --- .../alterschema/linter/forbid_empty_enum.h | 14 ++- .../alterschema_lint_2019_09_test.cc | 90 ++++++++++----- .../alterschema_lint_2020_12_test.cc | 105 ++++++++++-------- .../alterschema_lint_draft3_test.cc | 8 +- .../alterschema_lint_draft4_test.cc | 8 +- .../alterschema_lint_draft6_test.cc | 8 +- .../alterschema_lint_draft7_test.cc | 8 +- 7 files changed, 150 insertions(+), 91 deletions(-) diff --git a/src/extension/alterschema/linter/forbid_empty_enum.h b/src/extension/alterschema/linter/forbid_empty_enum.h index 7684132bf..3f8eb25c9 100644 --- a/src/extension/alterschema/linter/forbid_empty_enum.h +++ b/src/extension/alterschema/linter/forbid_empty_enum.h @@ -11,8 +11,8 @@ class ForbidEmptyEnum final : public SchemaTransformRule { condition(const sourcemeta::core::JSON &schema, const sourcemeta::core::JSON &, const sourcemeta::core::Vocabularies &vocabularies, - const sourcemeta::core::SchemaFrame &, - const sourcemeta::core::SchemaFrame::Location &, + 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 { @@ -22,13 +22,15 @@ class ForbidEmptyEnum final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4}) && - schema.is_object() && schema.defines("enum") && - schema.at("enum").is_array() && schema.at("enum").empty()); + 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.erase("enum"); - schema.assign("not", JSON::make_object()); + 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 bf6f714f4..9effb3edc 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -4579,6 +4579,11 @@ TEST(AlterSchema_lint_2019_09, forbid_empty_enum_4) { } } })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/2019-09/schema", "title": "Example", @@ -4591,18 +4596,12 @@ TEST(AlterSchema_lint_2019_09, forbid_empty_enum_4) { } })JSON"); - LINT_AND_FIX(document, result, traces); - - EXPECT_TRUE(result.first); EXPECT_EQ(document, expected); } 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", - "title": "Example", - "description": "Example schema", - "examples": [{}], "$defs": { "A": { "enum": [] @@ -4613,16 +4612,13 @@ TEST(AlterSchema_lint_2019_09, forbid_empty_enum_5) { 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", - "title": "Example", - "description": "Example schema", - "examples": [{}], "$defs": { "A": { - "not": true + "enum": [] } }, "$ref": "#/$defs/A" @@ -4634,9 +4630,6 @@ TEST(AlterSchema_lint_2019_09, forbid_empty_enum_5) { 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": "Example", - "description": "Example schema", - "examples": [{}], "$defs": { "A": { "enum": [], @@ -4650,19 +4643,16 @@ TEST(AlterSchema_lint_2019_09, forbid_empty_enum_6) { 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", - "title": "Example", - "description": "Example schema", - "examples": [{}], "$defs": { "A": { + "enum": [], "$defs": { "inner": { "type": "string" } - }, - "not": true + } } }, "$ref": "#/$defs/A/$defs/inner" @@ -4674,9 +4664,6 @@ TEST(AlterSchema_lint_2019_09, forbid_empty_enum_6) { 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": "Example", - "description": "Example schema", - "examples": [{}], "$defs": { "A": { "$id": "https://example.com/schemas/A", @@ -4688,17 +4675,14 @@ TEST(AlterSchema_lint_2019_09, forbid_empty_enum_7) { 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", - "title": "Example", - "description": "Example schema", - "examples": [{}], "$defs": { "A": { "$id": "https://example.com/schemas/A", - "not": true + "enum": [] } }, "$ref": "https://example.com/schemas/A" @@ -4740,3 +4724,53 @@ TEST(AlterSchema_lint_2019_09, forbid_empty_enum_8) { 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); + + 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 776ab6cd5..43339fed7 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -9749,6 +9749,11 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_4) { } } })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", @@ -9761,9 +9766,6 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_4) { } })JSON"); - LINT_AND_FIX(document, result, traces); - - EXPECT_TRUE(result.first); EXPECT_EQ(document, expected); } @@ -9785,6 +9787,11 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_5) { } } })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", @@ -9803,9 +9810,6 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_5) { } })JSON"); - LINT_AND_FIX(document, result, traces); - - EXPECT_TRUE(result.first); EXPECT_EQ(document, expected); } @@ -9824,6 +9828,11 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_6) { } ] })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", @@ -9839,9 +9848,6 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_6) { ] })JSON"); - LINT_AND_FIX(document, result, traces); - - EXPECT_TRUE(result.first); EXPECT_EQ(document, expected); } @@ -9861,6 +9867,11 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_7) { } } })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", @@ -9877,9 +9888,6 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_7) { } })JSON"); - LINT_AND_FIX(document, result, traces); - - EXPECT_TRUE(result.first); EXPECT_EQ(document, expected); } @@ -9946,9 +9954,6 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_9) { 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", - "title": "Example", - "description": "Example schema", - "examples": [{}], "$defs": { "A": { "enum": [] @@ -9959,16 +9964,13 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_10) { 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/2020-12/schema", - "title": "Example", - "description": "Example schema", - "examples": [{}], "$defs": { "A": { - "not": true + "enum": [] } }, "$ref": "#/$defs/A" @@ -9980,9 +9982,6 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_10) { 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": "Example", - "description": "Example schema", - "examples": [{}], "$defs": { "A": { "enum": [], @@ -9996,19 +9995,16 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_11) { 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/2020-12/schema", - "title": "Example", - "description": "Example schema", - "examples": [{}], "$defs": { "A": { + "enum": [], "$defs": { "inner": { "type": "string" } - }, - "not": true + } } }, "$ref": "#/$defs/A/$defs/inner" @@ -10020,9 +10016,6 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_11) { 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", - "title": "Example", - "description": "Example schema", - "examples": [{}], "$defs": { "A": { "$id": "https://example.com/schemas/A", @@ -10034,17 +10027,14 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_12) { 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/2020-12/schema", - "title": "Example", - "description": "Example schema", - "examples": [{}], "$defs": { "A": { "$id": "https://example.com/schemas/A", - "not": true + "enum": [] } }, "$ref": "https://example.com/schemas/A" @@ -10090,23 +10080,48 @@ TEST(AlterSchema_lint_2020_12, forbid_empty_enum_13) { 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", - "title": "Example", - "description": "Example schema", - "examples": [{}], "not": { "type": "string" }, "enum": [] })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/2020-12/schema", - "title": "Example", - "description": "Example schema", - "examples": [{}], - "not": true + "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 b608808d7..014c80b4c 100644 --- a/test/alterschema/alterschema_lint_draft3_test.cc +++ b/test/alterschema/alterschema_lint_draft3_test.cc @@ -92,6 +92,11 @@ TEST(AlterSchema_lint_draft3, forbid_empty_enum_1) { } } })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", @@ -103,9 +108,6 @@ TEST(AlterSchema_lint_draft3, forbid_empty_enum_1) { } })JSON"); - LINT_AND_FIX(document, result, traces); - - EXPECT_TRUE(result.first); EXPECT_EQ(document, expected); } diff --git a/test/alterschema/alterschema_lint_draft4_test.cc b/test/alterschema/alterschema_lint_draft4_test.cc index 038954fb5..20964d6c1 100644 --- a/test/alterschema/alterschema_lint_draft4_test.cc +++ b/test/alterschema/alterschema_lint_draft4_test.cc @@ -2682,6 +2682,11 @@ TEST(AlterSchema_lint_draft4, forbid_empty_enum_4) { } } })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", @@ -2693,8 +2698,5 @@ TEST(AlterSchema_lint_draft4, forbid_empty_enum_4) { } })JSON"); - LINT_AND_FIX(document, result, traces); - - EXPECT_TRUE(result.first); EXPECT_EQ(document, expected); } diff --git a/test/alterschema/alterschema_lint_draft6_test.cc b/test/alterschema/alterschema_lint_draft6_test.cc index b66a21057..11cbd43a2 100644 --- a/test/alterschema/alterschema_lint_draft6_test.cc +++ b/test/alterschema/alterschema_lint_draft6_test.cc @@ -3014,6 +3014,11 @@ TEST(AlterSchema_lint_draft6, forbid_empty_enum_4) { } } })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#", "title": "Example", @@ -3026,8 +3031,5 @@ TEST(AlterSchema_lint_draft6, forbid_empty_enum_4) { } })JSON"); - LINT_AND_FIX(document, result, traces); - - EXPECT_TRUE(result.first); EXPECT_EQ(document, expected); } diff --git a/test/alterschema/alterschema_lint_draft7_test.cc b/test/alterschema/alterschema_lint_draft7_test.cc index fe231ed98..c888faed5 100644 --- a/test/alterschema/alterschema_lint_draft7_test.cc +++ b/test/alterschema/alterschema_lint_draft7_test.cc @@ -3592,6 +3592,11 @@ TEST(AlterSchema_lint_draft7, forbid_empty_enum_4) { } } })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#", "title": "Example", @@ -3604,8 +3609,5 @@ TEST(AlterSchema_lint_draft7, forbid_empty_enum_4) { } })JSON"); - LINT_AND_FIX(document, result, traces); - - EXPECT_TRUE(result.first); EXPECT_EQ(document, expected); } From d832c4a23f9ed818c77481430486bb86c24b096f Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Fri, 27 Mar 2026 09:48:23 -0400 Subject: [PATCH 09/10] Apply suggestion from @jviotti Signed-off-by: Juan Cruz Viotti --- src/extension/alterschema/linter/forbid_empty_enum.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension/alterschema/linter/forbid_empty_enum.h b/src/extension/alterschema/linter/forbid_empty_enum.h index 3f8eb25c9..30e59c0b7 100644 --- a/src/extension/alterschema/linter/forbid_empty_enum.h +++ b/src/extension/alterschema/linter/forbid_empty_enum.h @@ -5,7 +5,7 @@ class ForbidEmptyEnum final : public SchemaTransformRule { ForbidEmptyEnum() : SchemaTransformRule{"forbid_empty_enum", "An empty `enum` validates nothing and is " - "equivalent to `not: {}`"} {}; + unsatisfiable"} {}; [[nodiscard]] auto condition(const sourcemeta::core::JSON &schema, From 6d343251f4d693517ca845ccf2c45d17470b6da3 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Fri, 27 Mar 2026 09:48:45 -0400 Subject: [PATCH 10/10] Apply suggestion from @jviotti Signed-off-by: Juan Cruz Viotti --- src/extension/alterschema/linter/forbid_empty_enum.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension/alterschema/linter/forbid_empty_enum.h b/src/extension/alterschema/linter/forbid_empty_enum.h index 30e59c0b7..279d20f55 100644 --- a/src/extension/alterschema/linter/forbid_empty_enum.h +++ b/src/extension/alterschema/linter/forbid_empty_enum.h @@ -5,7 +5,7 @@ class ForbidEmptyEnum final : public SchemaTransformRule { ForbidEmptyEnum() : SchemaTransformRule{"forbid_empty_enum", "An empty `enum` validates nothing and is " - unsatisfiable"} {}; + "unsatisfiable"} {}; [[nodiscard]] auto condition(const sourcemeta::core::JSON &schema,