diff --git a/src/extension/alterschema/CMakeLists.txt b/src/extension/alterschema/CMakeLists.txt index 7f48dfb8f..fa97c0d04 100644 --- a/src/extension/alterschema/CMakeLists.txt +++ b/src/extension/alterschema/CMakeLists.txt @@ -62,6 +62,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema linter/modern_official_dialect_with_empty_fragment.h linter/then_empty.h linter/else_empty.h + linter/else_false.h linter/then_without_if.h linter/property_names_type_default.h linter/property_names_default.h diff --git a/src/extension/alterschema/alterschema.cc b/src/extension/alterschema/alterschema.cc index ebcc812d0..4aec7366a 100644 --- a/src/extension/alterschema/alterschema.cc +++ b/src/extension/alterschema/alterschema.cc @@ -52,6 +52,7 @@ contains_any(const Vocabularies &container, #include "linter/duplicate_enum_values.h" #include "linter/duplicate_required_values.h" #include "linter/else_empty.h" +#include "linter/else_false.h" #include "linter/else_without_if.h" #include "linter/enum_to_const.h" #include "linter/enum_with_type.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/else_false.h b/src/extension/alterschema/linter/else_false.h new file mode 100644 index 000000000..a616fb083 --- /dev/null +++ b/src/extension/alterschema/linter/else_false.h @@ -0,0 +1,54 @@ +class ElseFalse final : public SchemaTransformRule { +public: + ElseFalse() + : SchemaTransformRule{"else_false", + "`if: S, else: false` collapses to just `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("else") && is_schema(schema.at("else")) && + schema.at("else").is_boolean() && !schema.at("else").to_boolean() && + is_schema(schema.at("if")) && + (!schema.defines("then") || (schema.at("then").is_boolean() && + schema.at("then").to_boolean())) && + (!schema.at("if").is_object() || [&schema](const JSON &if_schema) { + for (const auto &entry : if_schema.as_object()) { + if (schema.defines(entry.first)) { + return false; + } + } + return true; + }(schema.at("if"))); + } + + auto transform(JSON &schema) const -> void override { + auto if_schema = schema.at("if"); + schema.erase("if"); + schema.erase("else"); + schema.erase("then"); + + if (if_schema.is_object()) { + for (auto &entry : if_schema.as_object()) { + schema.assign(entry.first, entry.second); + } + } else if (if_schema.is_boolean() && !if_schema.to_boolean()) { + auto metadata = JSON::make_object(); + for (const auto &entry : schema.as_object()) { + if (entry.first.starts_with("$")) { + metadata.assign(entry.first, entry.second); + } + } + schema = std::move(metadata); + schema.assign("not", JSON::make_object()); + } + } +}; diff --git a/test/alterschema/alterschema_lint_2019_09_test.cc b/test/alterschema/alterschema_lint_2019_09_test.cc index 8679a5c65..d7037efae 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -2349,3 +2349,224 @@ TEST(AlterSchema_lint_2019_09, non_applicable_type_specific_keywords_3) { EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_2019_09, else_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 + } + } + }, + "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", + "properties": { + "flag": { + "const": true + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, else_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": true, + "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", + "properties": { + "flag": { + "const": true + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, else_false_3) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": true, + "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" + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, else_false_4) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": 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", + "not": {} + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, else_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" } + } + }, + "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", + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "if": { + "properties": { + "name": { "type": "number" } + } + }, + "else": false + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, else_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 } + } + }, + "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", + "properties": { + "flag": { "const": true } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, else_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); +} + +TEST(AlterSchema_lint_2019_09, else_false_8) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://example.com/schema", + "$anchor": "root", + "$comment": "Test schema with metadata", + "if": 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", + "$id": "https://example.com/schema", + "$anchor": "root", + "$comment": "Test schema with metadata", + "not": {} + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, else_false_9) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://example.com/complex", + "$anchor": "main", + "$ref": "#/$defs/base", + "$defs": { + "base": { "type": "string" } + }, + "if": 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", + "$id": "https://example.com/complex", + "$anchor": "main", + "$ref": "#/$defs/base", + "$defs": { + "base": { "type": "string" } + }, + "not": {} + })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..c836f9d7e 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -2583,3 +2583,224 @@ TEST(AlterSchema_lint_2020_12, non_applicable_type_specific_keywords_3) { EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_2020_12, else_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 + } + } + }, + "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", + "properties": { + "flag": { + "const": true + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, else_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": true, + "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", + "properties": { + "flag": { + "const": true + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, else_false_3) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": true, + "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" + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, else_false_4) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": 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", + "not": {} + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, else_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" } + } + }, + "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", + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "if": { + "properties": { + "name": { "type": "number" } + } + }, + "else": false + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, else_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 } + } + }, + "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", + "properties": { + "flag": { "const": true } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, else_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); +} + +TEST(AlterSchema_lint_2020_12, else_false_8) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/schema", + "$anchor": "root", + "$comment": "Test schema with metadata", + "if": 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", + "$id": "https://example.com/schema", + "$anchor": "root", + "$comment": "Test schema with metadata", + "not": {} + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, else_false_9) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/complex", + "$anchor": "main", + "$ref": "#/$defs/base", + "$defs": { + "base": { "type": "string" } + }, + "if": 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", + "$id": "https://example.com/complex", + "$anchor": "main", + "$ref": "#/$defs/base", + "$defs": { + "base": { "type": "string" } + }, + "not": {} + })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..0a3645a67 100644 --- a/test/alterschema/alterschema_lint_draft7_test.cc +++ b/test/alterschema/alterschema_lint_draft7_test.cc @@ -1890,3 +1890,172 @@ TEST(AlterSchema_lint_draft7, non_applicable_type_specific_keywords_3) { EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_draft7, else_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 + } + } + }, + "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#", + "properties": { + "flag": { + "const": true + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, else_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": true, + "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#", + "properties": { + "flag": { + "const": true + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, else_false_3) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "if": true, + "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#" + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, else_false_4) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "if": 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#", + "not": {} + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, else_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" } + } + }, + "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#", + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "if": { + "properties": { + "name": { "type": "number" } + } + }, + "else": false + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, else_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 } + } + }, + "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#", + "properties": { + "flag": { "const": true } + } + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, else_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); +}