From a398ee27d5e56eb6328f26d13356eb4ce0048635 Mon Sep 17 00:00:00 2001 From: karan-palan Date: Fri, 22 Aug 2025 18:05:21 +0530 Subject: [PATCH 1/4] feat: Improve ValidExamples linter to selectively remove invalid examples Signed-off-by: karan-palan --- src/linter/include/sourcemeta/blaze/linter.h | 4 ++ src/linter/valid_examples.cc | 40 +++++++++++-- test/linter/linter_valid_examples_test.cc | 62 ++++++++++++++++++-- 3 files changed, 96 insertions(+), 10 deletions(-) diff --git a/src/linter/include/sourcemeta/blaze/linter.h b/src/linter/include/sourcemeta/blaze/linter.h index 3f4e1b110..0e85d9127 100644 --- a/src/linter/include/sourcemeta/blaze/linter.h +++ b/src/linter/include/sourcemeta/blaze/linter.h @@ -8,6 +8,9 @@ #include #include +#include // std::size_t +#include // std::set + /// @defgroup linter Linter /// @brief A set of JSON Schema linter extensions powered by Blaze /// @@ -45,6 +48,7 @@ class SOURCEMETA_BLAZE_LINTER_EXPORT ValidExamples final #pragma warning(disable : 4251) #endif const Compiler compiler_; + mutable std::set invalid_indices_; #if defined(_MSC_VER) #pragma warning(default : 4251) #endif diff --git a/src/linter/valid_examples.cc b/src/linter/valid_examples.cc index c85bf6756..075afa4e8 100644 --- a/src/linter/valid_examples.cc +++ b/src/linter/valid_examples.cc @@ -10,6 +10,7 @@ #include // std::size_t #include // std::ref, std::cref +#include // std::set #include // std::ostringstream #include // std::move @@ -20,7 +21,7 @@ ValidExamples::ValidExamples(Compiler compiler) SchemaTransformRule{"blaze/valid_examples", "Only include instances in the `examples` array " "that validate against the schema"}, - compiler_{std::move(compiler)} {}; + compiler_{std::move(compiler)}, invalid_indices_{} {}; auto ValidExamples::condition( const sourcemeta::core::JSON &schema, const sourcemeta::core::JSON &root, @@ -73,26 +74,53 @@ auto ValidExamples::condition( Evaluator evaluator; std::size_t cursor{0}; + this->invalid_indices_.clear(); + std::ostringstream collected_messages; + for (const auto &example : schema.at("examples").as_array()) { const std::string ref{"$ref"}; SimpleOutput output{example, {std::cref(ref)}}; const auto result{ evaluator.validate(schema_template, example, std::ref(output))}; if (!result) { - std::ostringstream message; - message << "Invalid example instance at index " << cursor << "\n"; - output.stacktrace(message, " "); - return message.str(); + this->invalid_indices_.insert(cursor); + collected_messages << "Invalid example instance at index " << cursor + << "\n"; + output.stacktrace(collected_messages, " "); } cursor += 1; } + if (!this->invalid_indices_.empty()) { + return collected_messages.str(); + } + return false; } auto ValidExamples::transform(sourcemeta::core::JSON &schema) const -> void { - schema.erase("examples"); + if (!schema.is_object() || !schema.defines("examples") || + !schema.at("examples").is_array() || this->invalid_indices_.empty()) { + return; + } + + auto &examples_array = schema.at("examples"); + auto new_examples = sourcemeta::core::JSON::make_array(); + + std::size_t index = 0; + for (const auto &example : examples_array.as_array()) { + if (this->invalid_indices_.find(index) == this->invalid_indices_.end()) { + new_examples.push_back(example); + } + index++; + } + + if (new_examples.empty()) { + schema.erase("examples"); + } else { + schema.assign("examples", std::move(new_examples)); + } } } // namespace sourcemeta::blaze diff --git a/test/linter/linter_valid_examples_test.cc b/test/linter/linter_valid_examples_test.cc index c318dfcf5..3cfa27e7b 100644 --- a/test/linter/linter_valid_examples_test.cc +++ b/test/linter/linter_valid_examples_test.cc @@ -262,7 +262,8 @@ TEST(Linter, valid_examples_3) { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "foo": { - "type": "string" + "type": "string", + "examples": [ "foo", "baz" ] } } })JSON")}; @@ -296,7 +297,8 @@ TEST(Linter, valid_examples_4) { "$schema": "https://json-schema.org/draft/2019-09/schema", "properties": { "foo": { - "type": "string" + "type": "string", + "examples": [ "foo", "baz" ] } } })JSON")}; @@ -330,7 +332,8 @@ TEST(Linter, valid_examples_5) { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { "foo": { - "type": "string" + "type": "string", + "examples": [ "foo", "baz" ] } } })JSON")}; @@ -364,7 +367,8 @@ TEST(Linter, valid_examples_6) { "$schema": "http://json-schema.org/draft-06/schema#", "properties": { "foo": { - "type": "string" + "type": "string", + "examples": [ "foo", "baz" ] } } })JSON")}; @@ -683,3 +687,53 @@ TEST(Linter, valid_examples_15) { EXPECT_EQ(schema, expected); } +TEST(Linter, valid_examples_mixed_valid_invalid) { + sourcemeta::core::SchemaTransformer bundle; + bundle.add( + sourcemeta::blaze::default_schema_compiler); + + auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "examples": [ "valid1", 123, "valid2", true, "valid3" ] + })JSON")}; + + const auto result = bundle.apply( + schema, sourcemeta::core::schema_official_walker, + sourcemeta::core::schema_official_resolver, transformer_callback_error); + + EXPECT_TRUE(result); + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "examples": [ "valid1", "valid2", "valid3" ] + })JSON")}; + + EXPECT_EQ(schema, expected); +} + +TEST(Linter, valid_examples_all_invalid_removes_property) { + sourcemeta::core::SchemaTransformer bundle; + bundle.add( + sourcemeta::blaze::default_schema_compiler); + + auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "examples": [ 123, true, 456 ] + })JSON")}; + + const auto result = bundle.apply( + schema, sourcemeta::core::schema_official_walker, + sourcemeta::core::schema_official_resolver, transformer_callback_error); + + EXPECT_TRUE(result); + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string" + })JSON")}; + + EXPECT_EQ(schema, expected); +} From 8f1c6162926ccec57d0d8e87b9a41562131a2a08 Mon Sep 17 00:00:00 2001 From: karan-palan Date: Fri, 19 Sep 2025 00:21:32 +0530 Subject: [PATCH 2/4] use vector, iterator based erase, reverse iteration, clean constructor and remove unecessary checks Signed-off-by: karan-palan --- src/linter/include/sourcemeta/blaze/linter.h | 4 +-- src/linter/valid_examples.cc | 36 ++++++++------------ 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/linter/include/sourcemeta/blaze/linter.h b/src/linter/include/sourcemeta/blaze/linter.h index e9f56f19b..885430381 100644 --- a/src/linter/include/sourcemeta/blaze/linter.h +++ b/src/linter/include/sourcemeta/blaze/linter.h @@ -9,7 +9,7 @@ #include #include // std::size_t -#include // std::set +#include // std::vector /// @defgroup linter Linter /// @brief A set of JSON Schema linter extensions powered by Blaze @@ -50,7 +50,7 @@ class SOURCEMETA_BLAZE_LINTER_EXPORT ValidExamples final #pragma warning(disable : 4251) #endif const Compiler compiler_; - mutable std::set invalid_indices_; + mutable std::vector invalid_indices_; #if defined(_MSC_VER) #pragma warning(default : 4251) #endif diff --git a/src/linter/valid_examples.cc b/src/linter/valid_examples.cc index e5325cbd0..7cbd1ea30 100644 --- a/src/linter/valid_examples.cc +++ b/src/linter/valid_examples.cc @@ -5,11 +5,12 @@ #include +#include // std::find #include // std::size_t #include // std::ref, std::cref -#include // std::set #include // std::ostringstream #include // std::move +#include // std::vector namespace sourcemeta::blaze { @@ -18,7 +19,7 @@ ValidExamples::ValidExamples(Compiler compiler) SchemaTransformRule{"blaze/valid_examples", "Only include instances in the `examples` array " "that validate against the schema"}, - compiler_{std::move(compiler)}, invalid_indices_{} {}; + compiler_{std::move(compiler)} {}; auto ValidExamples::condition( const sourcemeta::core::JSON &schema, const sourcemeta::core::JSON &root, @@ -81,7 +82,7 @@ auto ValidExamples::condition( const auto result{ evaluator.validate(schema_template, example, std::ref(output))}; if (!result) { - this->invalid_indices_.insert(cursor); + this->invalid_indices_.push_back(cursor); collected_messages << "Invalid example instance at index " << cursor << "\n"; output.stacktrace(collected_messages, " "); @@ -91,7 +92,7 @@ auto ValidExamples::condition( } if (!this->invalid_indices_.empty()) { - std::size_t first_invalid = *this->invalid_indices_.begin(); + std::size_t first_invalid = this->invalid_indices_.front(); return {{{"examples", first_invalid}}, collected_messages.str()}; } @@ -101,27 +102,20 @@ auto ValidExamples::condition( auto ValidExamples::transform( sourcemeta::core::JSON &schema, const sourcemeta::core::SchemaTransformRule::Result &) const -> void { - if (!schema.is_object() || !schema.defines("examples") || - !schema.at("examples").is_array() || this->invalid_indices_.empty()) { - return; - } - - auto &examples_array = schema.at("examples"); - auto new_examples = sourcemeta::core::JSON::make_array(); - - std::size_t index = 0; - for (const auto &example : examples_array.as_array()) { - if (this->invalid_indices_.find(index) == this->invalid_indices_.end()) { - new_examples.push_back(example); + auto &examples = schema.at("examples"); + + for (std::vector::const_reverse_iterator it = + this->invalid_indices_.crbegin(); + it != this->invalid_indices_.crend(); ++it) { + const auto index = *it; + if (index < examples.size()) { + examples.erase(examples.as_array().cbegin() + + static_cast(index)); } - index++; } - if (new_examples.empty()) { + if (examples.size() == 0) { schema.erase("examples"); - } else { - schema.assign("examples", std::move(new_examples)); } } - } // namespace sourcemeta::blaze From bac66f632c8728abcdbd2c151e3b8cf1b45fd1c8 Mon Sep 17 00:00:00 2001 From: karan-palan Date: Fri, 19 Sep 2025 00:22:51 +0530 Subject: [PATCH 3/4] chore: add newline in tests Signed-off-by: karan-palan --- test/linter/linter_valid_examples_test.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/test/linter/linter_valid_examples_test.cc b/test/linter/linter_valid_examples_test.cc index c414779e5..2a740b7af 100644 --- a/test/linter/linter_valid_examples_test.cc +++ b/test/linter/linter_valid_examples_test.cc @@ -711,6 +711,7 @@ TEST(Linter, valid_examples_15) { EXPECT_EQ(schema, expected); } + TEST(Linter, valid_examples_mixed_valid_invalid) { sourcemeta::core::SchemaTransformer bundle; bundle.add( From 93747d4f22ee71131d5ba6f1c0e7b8c510a30d4e Mon Sep 17 00:00:00 2001 From: karan-palan Date: Mon, 13 Oct 2025 10:20:16 +0530 Subject: [PATCH 4/4] chore: try ci fix Signed-off-by: karan-palan --- src/linter/valid_examples.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/linter/valid_examples.cc b/src/linter/valid_examples.cc index 7cbd1ea30..ed2d40537 100644 --- a/src/linter/valid_examples.cc +++ b/src/linter/valid_examples.cc @@ -8,6 +8,7 @@ #include // std::find #include // std::size_t #include // std::ref, std::cref +#include // std::ranges::reverse_view #include // std::ostringstream #include // std::move #include // std::vector @@ -104,10 +105,7 @@ auto ValidExamples::transform( const sourcemeta::core::SchemaTransformRule::Result &) const -> void { auto &examples = schema.at("examples"); - for (std::vector::const_reverse_iterator it = - this->invalid_indices_.crbegin(); - it != this->invalid_indices_.crend(); ++it) { - const auto index = *it; + for (const auto index : std::ranges::reverse_view(this->invalid_indices_)) { if (index < examples.size()) { examples.erase(examples.as_array().cbegin() + static_cast(index));