Skip to content

Commit

Permalink
Add implicit rules for minItems (#231)
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <[email protected]>
  • Loading branch information
jviotti authored Sep 11, 2024
1 parent f1ca303 commit 3bb509b
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/linter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ noa_library(NAMESPACE sourcemeta PROJECT alterschema NAME linter

# Implicit
implicit/max_contains_covered_by_max_items.h
implicit/min_items_given_min_contains.h
implicit/min_items_implicit.h
implicit/min_length_implicit.h
implicit/min_properties_covered_by_required.h
implicit/min_properties_implicit.h
Expand Down
33 changes: 33 additions & 0 deletions src/linter/implicit/min_items_given_min_contains.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class MinItemsGivenMinContains final : public sourcemeta::alterschema::Rule {
public:
MinItemsGivenMinContains()
: Rule{"min_items_given_min_contains",
"Every array has a minimum size of zero items but may be affected "
"by `minContains`"} {};

[[nodiscard]] auto
condition(const sourcemeta::jsontoolkit::JSON &schema, const std::string &,
const std::set<std::string> &vocabularies,
const sourcemeta::jsontoolkit::Pointer &) const -> bool override {
return contains_any(
vocabularies,
{"https://json-schema.org/draft/2020-12/vocab/validation",
"https://json-schema.org/draft/2019-09/vocab/validation"}) &&
schema.is_object() && schema.defines("type") &&
schema.at("type").is_string() &&
schema.at("type").to_string() == "array" &&
!schema.defines("minItems");
}

auto transform(Transformer &transformer) const -> void override {
if (transformer.schema().defines("contains") &&
transformer.schema().defines("minContains") &&
transformer.schema().at("minContains").is_integer()) {
transformer.assign(
"minItems", sourcemeta::jsontoolkit::JSON{
transformer.schema().at("minContains").to_integer()});
} else {
transformer.assign("minItems", sourcemeta::jsontoolkit::JSON{0});
}
}
};
27 changes: 27 additions & 0 deletions src/linter/implicit/min_items_implicit.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class MinItemsImplicit final : public Rule {
public:
MinItemsImplicit()
: Rule{"min_items_implicit",
"Every array has a minimum size of zero items"} {};

[[nodiscard]] auto
condition(const sourcemeta::jsontoolkit::JSON &schema, const std::string &,
const std::set<std::string> &vocabularies,
const sourcemeta::jsontoolkit::Pointer &) const -> bool override {
return contains_any(vocabularies,
{"http://json-schema.org/draft-07/schema#",
"http://json-schema.org/draft-06/schema#",
"http://json-schema.org/draft-04/schema#",
"http://json-schema.org/draft-03/schema#",
"http://json-schema.org/draft-02/hyper-schema#",
"http://json-schema.org/draft-01/hyper-schema#"}) &&
schema.is_object() && schema.defines("type") &&
schema.at("type").is_string() &&
schema.at("type").to_string() == "array" &&
!schema.defines("minItems");
}

auto transform(Transformer &transformer) const -> void override {
transformer.assign("minItems", sourcemeta::jsontoolkit::JSON{0});
}
};
4 changes: 4 additions & 0 deletions src/linter/linter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ template <typename T> auto every_item_is_boolean(const T &container) -> bool {
#include "redundant/unsatisfiable_min_properties.h"
// Implicit
#include "implicit/max_contains_covered_by_max_items.h"
#include "implicit/min_items_given_min_contains.h"
#include "implicit/min_items_implicit.h"
#include "implicit/min_length_implicit.h"
#include "implicit/min_properties_covered_by_required.h"
#include "implicit/min_properties_implicit.h"
Expand Down Expand Up @@ -219,6 +221,8 @@ auto add(Bundle &bundle, const LinterCategory category) -> void {
break;
case LinterCategory::Implicit:
bundle.add<MaxContainsCoveredByMaxItems>();
bundle.add<MinItemsGivenMinContains>();
bundle.add<MinItemsImplicit>();
bundle.add<MinLengthImplicit>();
bundle.add<MinPropertiesCoveredByRequired>();
bundle.add<MinPropertiesImplicit>();
Expand Down
44 changes: 44 additions & 0 deletions test/linter/2019_09_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1582,6 +1582,7 @@ TEST(Lint_2019_09, max_contains_covered_by_max_items_1) {
"minLength": 0
},
"maxContains": 1,
"minItems": 0,
"maxItems": 1
})JSON");

Expand Down Expand Up @@ -1655,3 +1656,46 @@ TEST(Lint_2019_09, min_properties_implicit_2) {

EXPECT_EQ(document, expected);
}

TEST(Lint_2019_09, min_items_given_min_contains_1) {
sourcemeta::jsontoolkit::JSON document =
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "array",
"contains": { "type": "boolean" },
"minContains": 3
})JSON");

LINT_AND_FIX_FOR_ANALYSIS(document);

const sourcemeta::jsontoolkit::JSON expected =
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "array",
"contains": { "enum": [ false, true ] },
"minContains": 3,
"minItems": 3
})JSON");

EXPECT_EQ(document, expected);
}

TEST(Lint_2019_09, min_items_given_min_contains_2) {
sourcemeta::jsontoolkit::JSON document =
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "array",
"minContains": 3
})JSON");

LINT_AND_FIX_FOR_ANALYSIS(document);

const sourcemeta::jsontoolkit::JSON expected =
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "array",
"minItems": 0
})JSON");

EXPECT_EQ(document, expected);
}
49 changes: 48 additions & 1 deletion test/linter/2020_12_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1672,7 +1672,10 @@ TEST(Lint_2020_12, type_array_to_any_of_3) {
"minLength": 0
}
},
{ "type": "array" },
{
"type": "array",
"minItems": 0
},
{
"type": "string",
"minLength": 0
Expand Down Expand Up @@ -1736,6 +1739,7 @@ TEST(Lint_2020_12, max_contains_covered_by_max_items_1) {
"minLength": 0
},
"maxContains": 1,
"minItems": 0,
"maxItems": 1
})JSON");

Expand Down Expand Up @@ -1809,3 +1813,46 @@ TEST(Lint_2020_12, min_properties_implicit_2) {

EXPECT_EQ(document, expected);
}

TEST(Lint_2020_12, min_items_given_min_contains_1) {
sourcemeta::jsontoolkit::JSON document =
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"contains": { "type": "boolean" },
"minContains": 3
})JSON");

LINT_AND_FIX_FOR_ANALYSIS(document);

const sourcemeta::jsontoolkit::JSON expected =
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"contains": { "enum": [ false, true ] },
"minContains": 3,
"minItems": 3
})JSON");

EXPECT_EQ(document, expected);
}

TEST(Lint_2020_12, min_items_given_min_contains_2) {
sourcemeta::jsontoolkit::JSON document =
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"minContains": 3
})JSON");

LINT_AND_FIX_FOR_ANALYSIS(document);

const sourcemeta::jsontoolkit::JSON expected =
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"minItems": 0
})JSON");

EXPECT_EQ(document, expected);
}

0 comments on commit 3bb509b

Please sign in to comment.