Skip to content

Commit 552c753

Browse files
committed
lint: add oneof_min_branches rule
Signed-off-by: AcE <kintan0108@gmail.com>
1 parent 8163bac commit 552c753

8 files changed

Lines changed: 306 additions & 2 deletions

src/alterschema/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT blaze NAME alterschema
150150
linter/items_array_default.h
151151
linter/items_schema_default.h
152152
linter/multiple_of_default.h
153+
linter/oneof_min_branches.h
153154
linter/pattern_properties_default.h
154155
linter/portable_anchor_names.h
155156
linter/properties_default.h

src/alterschema/alterschema.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ auto WALK_UP_IN_PLACE_APPLICATORS(const JSON &root, const SchemaFrame &frame,
262262
#include "linter/items_array_default.h"
263263
#include "linter/items_schema_default.h"
264264
#include "linter/multiple_of_default.h"
265+
#include "linter/oneof_min_branches.h"
265266
#include "linter/pattern_non_ecma_regex.h"
266267
#include "linter/pattern_properties_default.h"
267268
#include "linter/pattern_properties_non_ecma_regex.h"
@@ -482,6 +483,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void {
482483
bundle.add<UnsatisfiableMinProperties>();
483484
bundle.add<EnumToConst>();
484485
bundle.add<ForbidEmptyEnum>();
486+
bundle.add<OneofMinBranches>();
485487
bundle.add<TopLevelTitle>();
486488
bundle.add<TopLevelDescription>();
487489
bundle.add<TopLevelExamples>();
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
class OneofMinBranches final : public SchemaTransformRule {
2+
public:
3+
using mutates = std::false_type;
4+
using reframe_after_transform = std::false_type;
5+
OneofMinBranches()
6+
: SchemaTransformRule{
7+
"oneof_min_branches",
8+
"The `oneOf` keyword should have at least 2 branches to be "
9+
"meaningful"} {};
10+
[[nodiscard]] auto
11+
condition(const sourcemeta::core::JSON &schema,
12+
const sourcemeta::core::JSON &,
13+
const sourcemeta::blaze::Vocabularies &vocabularies,
14+
const sourcemeta::blaze::SchemaFrame &,
15+
const sourcemeta::blaze::SchemaFrame::Location &,
16+
const sourcemeta::blaze::SchemaWalker &,
17+
const sourcemeta::blaze::SchemaResolver &, const bool) const
18+
-> SchemaTransformRule::Result override {
19+
ONLY_CONTINUE_IF(vocabularies.contains_any(
20+
{Vocabularies::Known::JSON_Schema_2020_12_Applicator,
21+
Vocabularies::Known::JSON_Schema_2019_09_Applicator,
22+
Vocabularies::Known::JSON_Schema_Draft_7,
23+
Vocabularies::Known::JSON_Schema_Draft_6,
24+
Vocabularies::Known::JSON_Schema_Draft_4}));
25+
ONLY_CONTINUE_IF(schema.is_object());
26+
const auto *oneof_value{schema.try_at("oneOf")};
27+
ONLY_CONTINUE_IF(oneof_value && oneof_value->is_array());
28+
ONLY_CONTINUE_IF(oneof_value->size() < 2);
29+
return APPLIES_TO_KEYWORDS("oneOf");
30+
}
31+
};

test/alterschema/alterschema_lint_2019_09_test.cc

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5982,3 +5982,57 @@ TEST(AlterSchema_lint_2019_09,
59825982
EXPECT_EQ(sourcemeta::core::to_string(outcome.locations.at(1)),
59835983
"/patternProperties/[[:digit:]]");
59845984
}
5985+
5986+
TEST(AlterSchema_lint_2019_09, oneof_min_branches_1) {
5987+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
5988+
"$schema": "https://json-schema.org/draft/2019-09/schema",
5989+
"title": "My Schema",
5990+
"description": "A schema",
5991+
"examples": [ "hello" ],
5992+
"oneOf": [ { "type": "string", "minLength": 5 }, { "type": "string", "maxLength": 3 } ]
5993+
})JSON");
5994+
5995+
LINT_WITHOUT_FIX(document, result, traces);
5996+
5997+
EXPECT_TRUE(result.first);
5998+
EXPECT_EQ(traces.size(), 0);
5999+
}
6000+
6001+
TEST(AlterSchema_lint_2019_09, oneof_min_branches_2) {
6002+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
6003+
"$schema": "https://json-schema.org/draft/2019-09/schema",
6004+
"title": "My Schema",
6005+
"description": "A schema",
6006+
"examples": [ "hello" ],
6007+
"oneOf": [ { "type": "string" } ]
6008+
})JSON");
6009+
6010+
LINT_WITHOUT_FIX(document, result, traces);
6011+
6012+
EXPECT_FALSE(result.first);
6013+
EXPECT_EQ(traces.size(), 1);
6014+
EXPECT_LINT_TRACE(traces, 0, "", "oneof_min_branches",
6015+
"The `oneOf` keyword should have at least 2 branches to "
6016+
"be meaningful",
6017+
false);
6018+
}
6019+
6020+
TEST(AlterSchema_lint_2019_09, oneof_min_branches_3) {
6021+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
6022+
"$schema": "https://json-schema.org/draft/2019-09/schema",
6023+
"title": "My Schema",
6024+
"description": "A schema",
6025+
"examples": [ "hello" ],
6026+
"not": { "oneOf": [] }
6027+
})JSON");
6028+
6029+
LINT_WITHOUT_FIX(document, result, traces);
6030+
6031+
EXPECT_FALSE(result.first);
6032+
EXPECT_EQ(traces.size(), 1);
6033+
EXPECT_LINT_TRACE(traces, 0, "/not", "oneof_min_branches",
6034+
"The `oneOf` keyword should have at least 2 branches to "
6035+
"be meaningful",
6036+
false);
6037+
}
6038+

test/alterschema/alterschema_lint_2020_12_test.cc

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10211,9 +10211,13 @@ TEST(AlterSchema_lint_2020_12, object_oneof_required_not_required_5) {
1021110211
LINT_WITHOUT_FIX(document, result, traces);
1021210212

1021310213
EXPECT_FALSE(result.first);
10214-
EXPECT_EQ(traces.size(), 1);
10214+
EXPECT_EQ(traces.size(), 2);
10215+
EXPECT_LINT_TRACE(traces, 0, "", "oneof_min_branches",
10216+
"The `oneOf` keyword should have at least 2 branches to be "
10217+
"meaningful",
10218+
false);
1021510219
EXPECT_LINT_TRACE(
10216-
traces, 0, "/oneOf/0/items/not", "required_properties_in_properties",
10220+
traces, 1, "/oneOf/0/items/not", "required_properties_in_properties",
1021710221
"Every property listed in the `required` keyword must be explicitly "
1021810222
"defined using the `properties` keyword",
1021910223
true);
@@ -13583,3 +13587,57 @@ TEST(AlterSchema_lint_2020_12, embedded_custom_metaschema) {
1358313587

1358413588
EXPECT_EQ(document, expected);
1358513589
}
13590+
13591+
TEST(AlterSchema_lint_2020_12, oneof_min_branches_1) {
13592+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
13593+
"$schema": "https://json-schema.org/draft/2020-12/schema",
13594+
"title": "My Schema",
13595+
"description": "A schema",
13596+
"examples": [ "hello" ],
13597+
"oneOf": [ { "type": "string", "minLength": 5 }, { "type": "string", "maxLength": 3 } ]
13598+
})JSON");
13599+
13600+
LINT_WITHOUT_FIX(document, result, traces);
13601+
13602+
EXPECT_TRUE(result.first);
13603+
EXPECT_EQ(traces.size(), 0);
13604+
}
13605+
13606+
TEST(AlterSchema_lint_2020_12, oneof_min_branches_2) {
13607+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
13608+
"$schema": "https://json-schema.org/draft/2020-12/schema",
13609+
"title": "My Schema",
13610+
"description": "A schema",
13611+
"examples": [ "hello" ],
13612+
"oneOf": [ { "type": "string" } ]
13613+
})JSON");
13614+
13615+
LINT_WITHOUT_FIX(document, result, traces);
13616+
13617+
EXPECT_FALSE(result.first);
13618+
EXPECT_EQ(traces.size(), 1);
13619+
EXPECT_LINT_TRACE(traces, 0, "", "oneof_min_branches",
13620+
"The `oneOf` keyword should have at least 2 branches to "
13621+
"be meaningful",
13622+
false);
13623+
}
13624+
13625+
TEST(AlterSchema_lint_2020_12, oneof_min_branches_3) {
13626+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
13627+
"$schema": "https://json-schema.org/draft/2020-12/schema",
13628+
"title": "My Schema",
13629+
"description": "A schema",
13630+
"examples": [ "hello" ],
13631+
"not": { "oneOf": [] }
13632+
})JSON");
13633+
13634+
LINT_WITHOUT_FIX(document, result, traces);
13635+
13636+
EXPECT_FALSE(result.first);
13637+
EXPECT_EQ(traces.size(), 1);
13638+
EXPECT_LINT_TRACE(traces, 0, "/not", "oneof_min_branches",
13639+
"The `oneOf` keyword should have at least 2 branches to "
13640+
"be meaningful",
13641+
false);
13642+
}
13643+

test/alterschema/alterschema_lint_draft4_test.cc

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3704,3 +3704,53 @@ TEST(AlterSchema_lint_draft4,
37043704
EXPECT_EQ(sourcemeta::core::to_string(outcome.locations.at(1)),
37053705
"/patternProperties/[[:digit:]]");
37063706
}
3707+
3708+
TEST(AlterSchema_lint_draft4, oneof_min_branches_1) {
3709+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
3710+
"$schema": "http://json-schema.org/draft-04/schema#",
3711+
"title": "My Schema",
3712+
"description": "A schema",
3713+
"oneOf": [ { "type": "string", "minLength": 5 }, { "type": "string", "maxLength": 3 } ]
3714+
})JSON");
3715+
3716+
LINT_WITHOUT_FIX(document, result, traces);
3717+
3718+
EXPECT_TRUE(result.first);
3719+
EXPECT_EQ(traces.size(), 0);
3720+
}
3721+
3722+
TEST(AlterSchema_lint_draft4, oneof_min_branches_2) {
3723+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
3724+
"$schema": "http://json-schema.org/draft-04/schema#",
3725+
"title": "My Schema",
3726+
"description": "A schema",
3727+
"oneOf": [ { "type": "string" } ]
3728+
})JSON");
3729+
3730+
LINT_WITHOUT_FIX(document, result, traces);
3731+
3732+
EXPECT_FALSE(result.first);
3733+
EXPECT_EQ(traces.size(), 1);
3734+
EXPECT_LINT_TRACE(traces, 0, "", "oneof_min_branches",
3735+
"The `oneOf` keyword should have at least 2 branches to "
3736+
"be meaningful",
3737+
false);
3738+
}
3739+
3740+
TEST(AlterSchema_lint_draft4, oneof_min_branches_3) {
3741+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
3742+
"$schema": "http://json-schema.org/draft-04/schema#",
3743+
"title": "My Schema",
3744+
"description": "A schema",
3745+
"oneOf": []
3746+
})JSON");
3747+
3748+
LINT_WITHOUT_FIX(document, result, traces);
3749+
3750+
EXPECT_FALSE(result.first);
3751+
EXPECT_EQ(traces.size(), 1);
3752+
EXPECT_LINT_TRACE(traces, 0, "", "oneof_min_branches",
3753+
"The `oneOf` keyword should have at least 2 branches to "
3754+
"be meaningful",
3755+
false);
3756+
}

test/alterschema/alterschema_lint_draft6_test.cc

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3812,3 +3812,57 @@ TEST(AlterSchema_lint_draft6,
38123812
EXPECT_EQ(sourcemeta::core::to_string(outcome.locations.at(1)),
38133813
"/patternProperties/[[:digit:]]");
38143814
}
3815+
3816+
TEST(AlterSchema_lint_draft6, oneof_min_branches_1) {
3817+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
3818+
"$schema": "http://json-schema.org/draft-06/schema#",
3819+
"title": "My Schema",
3820+
"description": "A schema",
3821+
"examples": [ "hello" ],
3822+
"oneOf": [ { "type": "string", "minLength": 5 }, { "type": "string", "maxLength": 3 } ]
3823+
})JSON");
3824+
3825+
LINT_WITHOUT_FIX(document, result, traces);
3826+
3827+
EXPECT_TRUE(result.first);
3828+
EXPECT_EQ(traces.size(), 0);
3829+
}
3830+
3831+
TEST(AlterSchema_lint_draft6, oneof_min_branches_2) {
3832+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
3833+
"$schema": "http://json-schema.org/draft-06/schema#",
3834+
"title": "My Schema",
3835+
"description": "A schema",
3836+
"examples": [ "hello" ],
3837+
"oneOf": [ { "type": "string" } ]
3838+
})JSON");
3839+
3840+
LINT_WITHOUT_FIX(document, result, traces);
3841+
3842+
EXPECT_FALSE(result.first);
3843+
EXPECT_EQ(traces.size(), 1);
3844+
EXPECT_LINT_TRACE(traces, 0, "", "oneof_min_branches",
3845+
"The `oneOf` keyword should have at least 2 branches to "
3846+
"be meaningful",
3847+
false);
3848+
}
3849+
3850+
TEST(AlterSchema_lint_draft6, oneof_min_branches_3) {
3851+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
3852+
"$schema": "http://json-schema.org/draft-06/schema#",
3853+
"title": "My Schema",
3854+
"description": "A schema",
3855+
"examples": [ "hello" ],
3856+
"not": { "oneOf": [] }
3857+
})JSON");
3858+
3859+
LINT_WITHOUT_FIX(document, result, traces);
3860+
3861+
EXPECT_FALSE(result.first);
3862+
EXPECT_EQ(traces.size(), 1);
3863+
EXPECT_LINT_TRACE(traces, 0, "/not", "oneof_min_branches",
3864+
"The `oneOf` keyword should have at least 2 branches to "
3865+
"be meaningful",
3866+
false);
3867+
}
3868+

test/alterschema/alterschema_lint_draft7_test.cc

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5352,3 +5352,57 @@ TEST(AlterSchema_lint_draft7,
53525352
EXPECT_EQ(sourcemeta::core::to_string(outcome.locations.at(1)),
53535353
"/patternProperties/[[:digit:]]");
53545354
}
5355+
5356+
TEST(AlterSchema_lint_draft7, oneof_min_branches_1) {
5357+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
5358+
"$schema": "http://json-schema.org/draft-07/schema#",
5359+
"title": "My Schema",
5360+
"description": "A schema",
5361+
"examples": [ "hello" ],
5362+
"oneOf": [ { "type": "string", "minLength": 5 }, { "type": "string", "maxLength": 3 } ]
5363+
})JSON");
5364+
5365+
LINT_WITHOUT_FIX(document, result, traces);
5366+
5367+
EXPECT_TRUE(result.first);
5368+
EXPECT_EQ(traces.size(), 0);
5369+
}
5370+
5371+
TEST(AlterSchema_lint_draft7, oneof_min_branches_2) {
5372+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
5373+
"$schema": "http://json-schema.org/draft-07/schema#",
5374+
"title": "My Schema",
5375+
"description": "A schema",
5376+
"examples": [ "hello" ],
5377+
"oneOf": [ { "type": "string" } ]
5378+
})JSON");
5379+
5380+
LINT_WITHOUT_FIX(document, result, traces);
5381+
5382+
EXPECT_FALSE(result.first);
5383+
EXPECT_EQ(traces.size(), 1);
5384+
EXPECT_LINT_TRACE(traces, 0, "", "oneof_min_branches",
5385+
"The `oneOf` keyword should have at least 2 branches to "
5386+
"be meaningful",
5387+
false);
5388+
}
5389+
5390+
TEST(AlterSchema_lint_draft7, oneof_min_branches_3) {
5391+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
5392+
"$schema": "http://json-schema.org/draft-07/schema#",
5393+
"title": "My Schema",
5394+
"description": "A schema",
5395+
"examples": [ "hello" ],
5396+
"not": { "oneOf": [] }
5397+
})JSON");
5398+
5399+
LINT_WITHOUT_FIX(document, result, traces);
5400+
5401+
EXPECT_FALSE(result.first);
5402+
EXPECT_EQ(traces.size(), 1);
5403+
EXPECT_LINT_TRACE(traces, 0, "/not", "oneof_min_branches",
5404+
"The `oneOf` keyword should have at least 2 branches to "
5405+
"be meaningful",
5406+
false);
5407+
}
5408+

0 commit comments

Comments
 (0)