diff --git a/src/extension/alterschema/CMakeLists.txt b/src/extension/alterschema/CMakeLists.txt index 3685bd864..4e4e25880 100644 --- a/src/extension/alterschema/CMakeLists.txt +++ b/src/extension/alterschema/CMakeLists.txt @@ -54,7 +54,8 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema linter/if_without_then_else.h linter/max_contains_without_contains.h linter/min_contains_without_contains.h - linter/then_without_if.h) + linter/then_without_if.h + linter/then_else_empty.h) if(SOURCEMETA_CORE_INSTALL) sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME alterschema) diff --git a/src/extension/alterschema/alterschema.cc b/src/extension/alterschema/alterschema.cc index a455c1e2a..48a88b3bb 100644 --- a/src/extension/alterschema/alterschema.cc +++ b/src/extension/alterschema/alterschema.cc @@ -77,6 +77,7 @@ static auto every_item_is_boolean(const T &container) -> bool { #include "linter/pattern_properties_default.h" #include "linter/properties_default.h" #include "linter/single_type_array.h" +#include "linter/then_else_empty.h" #include "linter/then_without_if.h" #include "linter/unevaluated_items_default.h" #include "linter/unevaluated_properties_default.h" @@ -118,6 +119,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) switch (mode) { case AlterSchemaMode::StaticAnalysis: bundle.add(); + bundle.add(); bundle.add(); bundle.add(); bundle.add(); @@ -135,6 +137,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) bundle.add(); break; case AlterSchemaMode::Readability: + bundle.add(); bundle.add(); bundle.add(); bundle.add(); diff --git a/src/extension/alterschema/linter/if_without_then_else.h b/src/extension/alterschema/linter/if_without_then_else.h index ab99b5040..fef4340b2 100644 --- a/src/extension/alterschema/linter/if_without_then_else.h +++ b/src/extension/alterschema/linter/if_without_then_else.h @@ -15,13 +15,18 @@ class IfWithoutThenElse final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { + const auto trivial_if = [](const sourcemeta::core::JSON &s) { + return s.is_boolean() || (s.is_object() && s.empty()); + }; + return contains_any( vocabularies, {"https://json-schema.org/draft/2020-12/vocab/applicator", "https://json-schema.org/draft/2019-09/vocab/applicator", "http://json-schema.org/draft-07/schema#"}) && schema.is_object() && schema.defines("if") && - !schema.defines("then") && !schema.defines("else"); + !schema.defines("then") && !schema.defines("else") && + trivial_if(schema.at("if")); } auto transform(JSON &schema) const -> void override { schema.erase("if"); } diff --git a/src/extension/alterschema/linter/then_else_empty.h b/src/extension/alterschema/linter/then_else_empty.h new file mode 100644 index 000000000..784ec2e5b --- /dev/null +++ b/src/extension/alterschema/linter/then_else_empty.h @@ -0,0 +1,44 @@ +class ThenElseEmpty final : public SchemaTransformRule { +public: + ThenElseEmpty() + : SchemaTransformRule{"then_else_empty", + "then or else is an empty schema ({} or true) " + "and therefore adds no restriction"} {} + + [[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 { + + const auto empty_object = [](const sourcemeta::core::JSON &s) { + return s.is_object() && s.empty(); + }; + + return contains_any( + vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/applicator", + "https://json-schema.org/draft/2019-09/vocab/applicator", + "http://json-schema.org/draft-07/schema#"}) && + schema.is_object() && schema.defines("if") && + ((schema.defines("then") && empty_object(schema.at("then"))) || + (schema.defines("else") && empty_object(schema.at("else")))); + } + + auto transform(JSON &schema) const -> void override { + const auto empty_object = [](const JSON &s) { + return s.is_object() && s.empty(); + }; + + if (schema.defines("then") && empty_object(schema.at("then"))) { + schema.erase("then"); + } + if (schema.defines("else") && empty_object(schema.at("else"))) { + schema.erase("else"); + } + } +}; \ No newline at end of file diff --git a/test/alterschema/alterschema_lint_2020_12_test.cc b/test/alterschema/alterschema_lint_2020_12_test.cc index 55e4405b9..4093a99c7 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -1842,3 +1842,22 @@ TEST(AlterSchema_lint_2020_12, unnecessary_allof_ref_wrapper_5) { EXPECT_EQ(document, expected); } + +TEST(AlterSchema_Lint_2020_12, empty_then_else_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON( + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { "properties": { "flag": { "const": true } } }, + "then": {} + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON( + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { "properties": { "flag": { "const": true } } } + })JSON"); + + EXPECT_EQ(document, expected); +}