From 43b69a8119816d42627b10acd4c55f503d3ad4b9 Mon Sep 17 00:00:00 2001 From: karan-palan Date: Thu, 7 Aug 2025 00:39:23 +0530 Subject: [PATCH 1/7] create rule to to add syntactical sugar when then is set to false Signed-off-by: karan-palan --- src/extension/alterschema/CMakeLists.txt | 3 +- src/extension/alterschema/alterschema.cc | 2 ++ src/extension/alterschema/linter/then_false.h | 29 +++++++++++++++++++ .../alterschema_lint_2019_09_test.cc | 29 +++++++++++++++++++ .../alterschema_lint_2020_12_test.cc | 29 +++++++++++++++++++ .../alterschema_lint_draft7_test.cc | 29 +++++++++++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 src/extension/alterschema/linter/then_false.h diff --git a/src/extension/alterschema/CMakeLists.txt b/src/extension/alterschema/CMakeLists.txt index 7f48dfb8f..89d445aad 100644 --- a/src/extension/alterschema/CMakeLists.txt +++ b/src/extension/alterschema/CMakeLists.txt @@ -65,7 +65,8 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema linter/then_without_if.h linter/property_names_type_default.h linter/property_names_default.h - linter/draft_ref_siblings.h) + linter/draft_ref_siblings.h + linter/then_false.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 ebcc812d0..30e3f27a5 100644 --- a/src/extension/alterschema/alterschema.cc +++ b/src/extension/alterschema/alterschema.cc @@ -75,6 +75,7 @@ contains_any(const Vocabularies &container, #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" @@ -106,6 +107,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) 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..df2e6ad38 --- /dev/null +++ b/src/extension/alterschema/linter/then_false.h @@ -0,0 +1,29 @@ +class ThenFalse final : public SchemaTransformRule { +public: + ThenFalse() + : SchemaTransformRule{ + "then_false", + "`if: S, then: false` is logically equivalent to `not: S`"} {}; + + [[nodiscard]] auto + condition(const JSON &schema, const JSON &, const Vocabularies &vocabularies, + const SchemaFrame &, const SchemaFrame::Location &, + const SchemaWalker &, const SchemaResolver &) const + -> SchemaTransformRule::Result override { + 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.at("then").is_boolean() && + !schema.at("then").to_boolean(); + } + + auto transform(JSON &schema) const -> void override { + const auto if_schema = schema.at("if"); + schema.erase("if"); + schema.erase("then"); + schema.assign("not", if_schema); + } +}; \ No newline at end of file diff --git a/test/alterschema/alterschema_lint_2019_09_test.cc b/test/alterschema/alterschema_lint_2019_09_test.cc index 8679a5c65..17ab59f32 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -2349,3 +2349,32 @@ TEST(AlterSchema_lint_2019_09, non_applicable_type_specific_keywords_3) { 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); +} diff --git a/test/alterschema/alterschema_lint_2020_12_test.cc b/test/alterschema/alterschema_lint_2020_12_test.cc index 367292cb0..04e4c8be3 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -2583,3 +2583,32 @@ TEST(AlterSchema_lint_2020_12, non_applicable_type_specific_keywords_3) { 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); +} diff --git a/test/alterschema/alterschema_lint_draft7_test.cc b/test/alterschema/alterschema_lint_draft7_test.cc index 0c87deaed..f456dcbdd 100644 --- a/test/alterschema/alterschema_lint_draft7_test.cc +++ b/test/alterschema/alterschema_lint_draft7_test.cc @@ -1890,3 +1890,32 @@ TEST(AlterSchema_lint_draft7, non_applicable_type_specific_keywords_3) { 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); +} From 5af1cce876817e232e070aa6c78a359965c3fb59 Mon Sep 17 00:00:00 2001 From: karan-palan Date: Thu, 7 Aug 2025 01:09:19 +0530 Subject: [PATCH 2/7] add newline Signed-off-by: karan-palan --- src/extension/alterschema/linter/then_false.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension/alterschema/linter/then_false.h b/src/extension/alterschema/linter/then_false.h index df2e6ad38..f8cc3e3a2 100644 --- a/src/extension/alterschema/linter/then_false.h +++ b/src/extension/alterschema/linter/then_false.h @@ -26,4 +26,4 @@ class ThenFalse final : public SchemaTransformRule { schema.erase("then"); schema.assign("not", if_schema); } -}; \ No newline at end of file +}; From 04ff1fde12f7f30f51559916ee3402bc6538362e Mon Sep 17 00:00:00 2001 From: karan-palan Date: Mon, 11 Aug 2025 17:07:01 +0530 Subject: [PATCH 3/7] check for then and add more tests Signed-off-by: karan-palan --- src/extension/alterschema/linter/then_false.h | 43 +++-- .../alterschema_lint_2019_09_test.cc | 147 ++++++++++++++++++ .../alterschema_lint_2020_12_test.cc | 147 ++++++++++++++++++ .../alterschema_lint_draft7_test.cc | 147 ++++++++++++++++++ 4 files changed, 474 insertions(+), 10 deletions(-) diff --git a/src/extension/alterschema/linter/then_false.h b/src/extension/alterschema/linter/then_false.h index f8cc3e3a2..6b1bc7cbf 100644 --- a/src/extension/alterschema/linter/then_false.h +++ b/src/extension/alterschema/linter/then_false.h @@ -10,20 +10,43 @@ class ThenFalse final : public SchemaTransformRule { const SchemaFrame &, const SchemaFrame::Location &, const SchemaWalker &, const SchemaResolver &) const -> SchemaTransformRule::Result override { - 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.at("then").is_boolean() && - !schema.at("then").to_boolean(); + 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#"})) { + return false; + } + + if (!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"))) { + return false; + } + + if (schema.defines("else") && + (!schema.at("else").is_boolean() || !schema.at("else").to_boolean())) { + return false; + } + + if (schema.at("if").is_object()) { + for (const auto &entry : schema.at("if").as_object()) { + if (schema.defines(entry.first)) { + return false; + } + } + } + + return true; } auto transform(JSON &schema) const -> void override { - const auto if_schema = schema.at("if"); + auto if_schema = schema.at("if"); schema.erase("if"); schema.erase("then"); - schema.assign("not", if_schema); + if (schema.defines("else")) { + 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 17ab59f32..b3c20b1d8 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -2378,3 +2378,150 @@ TEST(AlterSchema_lint_2019_09, then_false_1) { 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", + "not": true + })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", + "not": false + })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 04e4c8be3..03407b2a8 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -2612,3 +2612,150 @@ TEST(AlterSchema_lint_2020_12, then_false_1) { 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", + "not": true + })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", + "not": false + })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 f456dcbdd..48e31a274 100644 --- a/test/alterschema/alterschema_lint_draft7_test.cc +++ b/test/alterschema/alterschema_lint_draft7_test.cc @@ -1919,3 +1919,150 @@ TEST(AlterSchema_lint_draft7, then_false_1) { 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#", + "not": true + })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#", + "not": false + })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); +} From 704b199c386bf3d85f76f1563af0a46b8b06b91b Mon Sep 17 00:00:00 2001 From: karan-palan Date: Mon, 18 Aug 2025 11:19:56 +0530 Subject: [PATCH 4/7] use std:all_of Signed-off-by: karan-palan --- src/extension/alterschema/linter/then_false.h | 49 +++++++------------ 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/src/extension/alterschema/linter/then_false.h b/src/extension/alterschema/linter/then_false.h index 6b1bc7cbf..69bce4f1c 100644 --- a/src/extension/alterschema/linter/then_false.h +++ b/src/extension/alterschema/linter/then_false.h @@ -10,43 +10,30 @@ class ThenFalse final : public SchemaTransformRule { const SchemaFrame &, const SchemaFrame::Location &, const SchemaWalker &, const SchemaResolver &) const -> SchemaTransformRule::Result override { - 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#"})) { - return false; - } - - if (!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"))) { - return false; - } - - if (schema.defines("else") && - (!schema.at("else").is_boolean() || !schema.at("else").to_boolean())) { - return false; - } - - if (schema.at("if").is_object()) { - for (const auto &entry : schema.at("if").as_object()) { - if (schema.defines(entry.first)) { - return false; - } - } - } - - return true; + 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") && is_schema(schema.at("then")) && + schema.at("then").is_boolean() && !schema.at("then").to_boolean() && + is_schema(schema.at("if")) && + (!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); + })); } auto transform(JSON &schema) const -> void override { auto if_schema = schema.at("if"); schema.erase("if"); schema.erase("then"); - if (schema.defines("else")) { - schema.erase("else"); - } + schema.erase("else"); schema.assign("not", std::move(if_schema)); } }; From 39c1dba83c6a2326b57604406d10aabe0e7aae56 Mon Sep 17 00:00:00 2001 From: karan-palan Date: Thu, 21 Aug 2025 18:56:10 +0530 Subject: [PATCH 5/7] chore: remove default not from tests Signed-off-by: karan-palan --- test/alterschema/alterschema_lint_2019_09_test.cc | 3 +-- test/alterschema/alterschema_lint_2020_12_test.cc | 3 +-- test/alterschema/alterschema_lint_draft7_test.cc | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/test/alterschema/alterschema_lint_2019_09_test.cc b/test/alterschema/alterschema_lint_2019_09_test.cc index c9816bd48..7399dc85f 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -2812,8 +2812,7 @@ TEST(AlterSchema_lint_2019_09, then_false_4) { 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": false + "$schema": "https://json-schema.org/draft/2019-09/schema" })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 ca9443163..5a162899c 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -2990,8 +2990,7 @@ TEST(AlterSchema_lint_2020_12, then_false_4) { 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": false + "$schema": "https://json-schema.org/draft/2020-12/schema" })JSON"); EXPECT_EQ(document, expected); diff --git a/test/alterschema/alterschema_lint_draft7_test.cc b/test/alterschema/alterschema_lint_draft7_test.cc index 182b238ff..1180dc729 100644 --- a/test/alterschema/alterschema_lint_draft7_test.cc +++ b/test/alterschema/alterschema_lint_draft7_test.cc @@ -2190,8 +2190,7 @@ TEST(AlterSchema_lint_draft7, then_false_4) { 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": false + "$schema": "http://json-schema.org/draft-07/schema#" })JSON"); EXPECT_EQ(document, expected); From ea2c18a6629b38e51fb981a5f18abfde728d533a Mon Sep 17 00:00:00 2001 From: karan-palan Date: Fri, 22 Aug 2025 03:27:00 +0530 Subject: [PATCH 6/7] when if is set to true, ignore the not Signed-off-by: karan-palan --- src/extension/alterschema/linter/then_false.h | 5 ++++- test/alterschema/alterschema_lint_2019_09_test.cc | 3 ++- test/alterschema/alterschema_lint_2020_12_test.cc | 3 ++- test/alterschema/alterschema_lint_draft7_test.cc | 3 ++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/extension/alterschema/linter/then_false.h b/src/extension/alterschema/linter/then_false.h index 69bce4f1c..7b991c4af 100644 --- a/src/extension/alterschema/linter/then_false.h +++ b/src/extension/alterschema/linter/then_false.h @@ -3,7 +3,9 @@ class ThenFalse final : public SchemaTransformRule { ThenFalse() : SchemaTransformRule{ "then_false", - "`if: S, then: false` is logically equivalent to `not: S`"} {}; + "`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, @@ -19,6 +21,7 @@ class ThenFalse final : public SchemaTransformRule { 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() || diff --git a/test/alterschema/alterschema_lint_2019_09_test.cc b/test/alterschema/alterschema_lint_2019_09_test.cc index 7399dc85f..92c1936dc 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -2796,7 +2796,8 @@ TEST(AlterSchema_lint_2019_09, then_false_3) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", - "not": true + "if": true, + "then": 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 5a162899c..c0f43c0ab 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -2974,7 +2974,8 @@ TEST(AlterSchema_lint_2020_12, then_false_3) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "not": true + "if": true, + "then": 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 1180dc729..a669af832 100644 --- a/test/alterschema/alterschema_lint_draft7_test.cc +++ b/test/alterschema/alterschema_lint_draft7_test.cc @@ -2174,7 +2174,8 @@ TEST(AlterSchema_lint_draft7, then_false_3) { const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", - "not": true + "if": true, + "then": false })JSON"); EXPECT_EQ(document, expected); From e6e3ae0229acd3ff5145afa13cdbc8f65b23003c Mon Sep 17 00:00:00 2001 From: karan-palan Date: Fri, 22 Aug 2025 22:20:18 +0530 Subject: [PATCH 7/7] chore: fix reordering Signed-off-by: karan-palan --- src/extension/alterschema/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extension/alterschema/CMakeLists.txt b/src/extension/alterschema/CMakeLists.txt index 6aea67ce7..68be2d2c0 100644 --- a/src/extension/alterschema/CMakeLists.txt +++ b/src/extension/alterschema/CMakeLists.txt @@ -67,10 +67,10 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema linter/property_names_default.h linter/draft_ref_siblings.h linter/definitions_to_defs.h + linter/then_false.h # Strict - strict/required_properties_in_properties.h - linter/then_false.h) + strict/required_properties_in_properties.h) if(SOURCEMETA_CORE_INSTALL) sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME alterschema)