diff --git a/src/extension/alterschema/CMakeLists.txt b/src/extension/alterschema/CMakeLists.txt index 516f81304..8782bfe42 100644 --- a/src/extension/alterschema/CMakeLists.txt +++ b/src/extension/alterschema/CMakeLists.txt @@ -69,6 +69,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema linter/definitions_to_defs.h linter/unknown_keywords_prefix.h linter/non_applicable_enum_validation_keywords.h + linter/then_false.h # Strict strict/required_properties_in_properties.h) diff --git a/src/extension/alterschema/alterschema.cc b/src/extension/alterschema/alterschema.cc index 34010fa0d..eed36f3d6 100644 --- a/src/extension/alterschema/alterschema.cc +++ b/src/extension/alterschema/alterschema.cc @@ -94,6 +94,7 @@ inline auto APPLIES_TO_POINTERS(std::vector &&keywords) #include "linter/property_names_type_default.h" #include "linter/single_type_array.h" #include "linter/then_empty.h" +#include "linter/then_false.h" #include "linter/then_without_if.h" #include "linter/unevaluated_items_default.h" #include "linter/unevaluated_properties_default.h" @@ -130,6 +131,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/then_false.h b/src/extension/alterschema/linter/then_false.h new file mode 100644 index 000000000..20291a777 --- /dev/null +++ b/src/extension/alterschema/linter/then_false.h @@ -0,0 +1,47 @@ +class ThenFalse final : public SchemaTransformRule { +public: + ThenFalse() + : SchemaTransformRule{ + "then_false", + "`Setting the `then` keyword to the false schema is a convoluted " + "way of negating the `if` subschema, which you can more cleanly " + "represent using the `not` keyword"} {}; + + [[nodiscard]] auto + condition(const JSON &schema, const JSON &, const Vocabularies &vocabularies, + const SchemaFrame &, const SchemaFrame::Location &, + const SchemaWalker &, const SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + 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") && + is_schema(schema.at("then")) && schema.at("then").is_boolean() && + !schema.at("then").to_boolean() && is_schema(schema.at("if")) && + !(schema.at("if").is_boolean() && schema.at("if").to_boolean()) && + (!schema.defines("else") || + (schema.at("else").is_boolean() && schema.at("else").to_boolean())) && + (!schema.at("if").is_object() || + std::all_of(schema.at("if").as_object().begin(), + schema.at("if").as_object().end(), + [&schema](const auto &entry) { + return !schema.defines(entry.first); + }))); + + if (schema.defines("else")) { + return APPLIES_TO_KEYWORDS("if", "then", "else"); + } else { + return APPLIES_TO_KEYWORDS("if", "then"); + } + } + + auto transform(JSON &schema, const Result &) const -> void override { + auto if_schema = schema.at("if"); + schema.erase("if"); + schema.erase("then"); + schema.erase("else"); + schema.assign("not", std::move(if_schema)); + } +}; diff --git a/test/alterschema/alterschema_lint_2019_09_test.cc b/test/alterschema/alterschema_lint_2019_09_test.cc index 75a6d0fdb..c8a455e83 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -3275,3 +3275,179 @@ TEST(AlterSchema_lint_2019_09, unknown_keywords_prefix_11) { EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_2019_09, then_false_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": { + "properties": { + "flag": { + "const": true + } + } + }, + "then": false + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": { + "properties": { + "flag": { + "const": true + } + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, then_false_2) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": { + "properties": { + "flag": { + "const": true + } + } + }, + "then": false, + "else": true + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": { + "properties": { + "flag": { + "const": true + } + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, then_false_3) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": true, + "then": false + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": true, + "then": false + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, then_false_4) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": false, + "then": false + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema" + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, then_false_5) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "if": { + "properties": { + "name": { "type": "number" } + } + }, + "then": false + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "if": { + "properties": { + "name": { "type": "number" } + } + }, + "then": false + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, then_false_6) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": { + "properties": { + "flag": { "const": true } + } + }, + "then": false + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": { + "properties": { + "flag": { "const": true } + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, then_false_7) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": { + "type": "object" + }, + "then": false, + "else": false + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": { + "type": "object" + }, + "then": false, + "else": false + })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 17aca18bd..51f31add2 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -3771,3 +3771,179 @@ TEST(AlterSchema_lint_2020_12, EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_2020_12, then_false_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": false + })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", + "not": { + "properties": { + "flag": { + "const": true + } + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, then_false_2) { + 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": false, + "else": true + })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", + "not": { + "properties": { + "flag": { + "const": true + } + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, then_false_3) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": true, + "then": false + })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": true, + "then": false + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, then_false_4) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": false, + "then": false + })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" + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, then_false_5) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "if": { + "properties": { + "name": { "type": "number" } + } + }, + "then": false + })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", + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "if": { + "properties": { + "name": { "type": "number" } + } + }, + "then": false + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, then_false_6) { + 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": false + })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", + "not": { + "properties": { + "flag": { "const": true } + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, then_false_7) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "type": "object" + }, + "then": false, + "else": false + })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": { + "type": "object" + }, + "then": false, + "else": false + })JSON"); + + EXPECT_EQ(document, expected); +} diff --git a/test/alterschema/alterschema_lint_draft7_test.cc b/test/alterschema/alterschema_lint_draft7_test.cc index 1c083b80e..f4ae3a802 100644 --- a/test/alterschema/alterschema_lint_draft7_test.cc +++ b/test/alterschema/alterschema_lint_draft7_test.cc @@ -2636,3 +2636,179 @@ TEST(AlterSchema_lint_draft7, unknown_keywords_prefix_10) { EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_draft7, then_false_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "if": { + "properties": { + "flag": { + "const": true + } + } + }, + "then": false + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "not": { + "properties": { + "flag": { + "const": true + } + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, then_false_2) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "if": { + "properties": { + "flag": { + "const": true + } + } + }, + "then": false, + "else": true + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "not": { + "properties": { + "flag": { + "const": true + } + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, then_false_3) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "if": true, + "then": false + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "if": true, + "then": false + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, then_false_4) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "if": false, + "then": false + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#" + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, then_false_5) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "if": { + "properties": { + "name": { "type": "number" } + } + }, + "then": false + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "if": { + "properties": { + "name": { "type": "number" } + } + }, + "then": false + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, then_false_6) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "if": { + "properties": { + "flag": { "const": true } + } + }, + "then": false + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "not": { + "properties": { + "flag": { "const": true } + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, then_false_7) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "if": { + "type": "object" + }, + "then": false, + "else": false + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "if": { + "type": "object" + }, + "then": false, + "else": false + })JSON"); + + EXPECT_EQ(document, expected); +}