Skip to content

Linter: create rule to remove additional items when items is an object #1805

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 14, 2025
1 change: 1 addition & 0 deletions src/extension/alterschema/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema
linter/unsatisfiable_min_properties.h
linter/content_media_type_without_encoding.h
linter/content_schema_without_media_type.h
linter/additional_items_with_schema_items.h
linter/non_applicable_type_specific_keywords.h
linter/unnecessary_allof_wrapper_modern.h
linter/unnecessary_allof_wrapper_draft.h
Expand Down
2 changes: 2 additions & 0 deletions src/extension/alterschema/alterschema.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ contains_any(const Vocabularies &container,
#include "canonicalizer/type_union_implicit.h"

// Linter
#include "linter/additional_items_with_schema_items.h"
#include "linter/additional_properties_default.h"
#include "linter/const_with_type.h"
#include "linter/content_media_type_without_encoding.h"
Expand Down Expand Up @@ -107,6 +108,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode)
bundle.add<DuplicateEnumValues>();
bundle.add<DuplicateRequiredValues>();
bundle.add<ConstWithType>();
bundle.add<AdditionalItemsWithSchemaItems>();
bundle.add<ExclusiveMaximumNumberAndMaximum>();
bundle.add<ExclusiveMinimumNumberAndMinimum>();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class AdditionalItemsWithSchemaItems final : public SchemaTransformRule {
public:
AdditionalItemsWithSchemaItems()
: SchemaTransformRule{"additional_items_with_schema_items",
"The `additionalItems` keyword is ignored when the "
"`items` keyword is set to a schema"} {};

[[nodiscard]] auto
condition(const sourcemeta::core::JSON &schema,
const sourcemeta::core::JSON &,
const sourcemeta::core::Vocabularies &vocabularies,
const sourcemeta::core::SchemaFrame &,
const sourcemeta::core::SchemaFrame::Location &,
const sourcemeta::core::SchemaWalker &,
const sourcemeta::core::SchemaResolver &) const
-> sourcemeta::core::SchemaTransformRule::Result override {
return contains_any(
vocabularies,
{"https://json-schema.org/draft/2019-09/vocab/applicator",
"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#"}) &&
schema.is_object() && schema.defines("items") &&
schema.defines("additionalItems") && is_schema(schema.at("items"));
}

auto transform(JSON &schema) const -> void override {
schema.erase("additionalItems");
}
};
105 changes: 105 additions & 0 deletions test/alterschema/alterschema_lint_2019_09_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1879,3 +1879,108 @@ TEST(AlterSchema_lint_2019_09, unnecessary_allof_wrapper_properties_4) {

EXPECT_EQ(document, expected);
}

TEST(AlterSchema_lint_2019_09, additional_items_with_schema_items_1) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2019-09/schema",
"items": {
"type": "number"
},
"additionalItems": 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",
"items": {
"type": "number"
}
})JSON");

EXPECT_EQ(document, expected);
}

TEST(AlterSchema_lint_2019_09, additional_items_with_schema_items_2) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2019-09/schema",
"items": {
"unevaluatedProperties": false
},
"additionalItems": {
"type": "string"
}
})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",
"items": {
"unevaluatedProperties": false
}
})JSON");

EXPECT_EQ(document, expected);
}

TEST(AlterSchema_lint_2019_09,
additional_items_with_schema_items_boolean_items_true) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2019-09/schema",
"items": true,
"additionalItems": 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,
additional_items_with_schema_items_boolean_items_false) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2019-09/schema",
"items": false,
"additionalItems": {
"type": "string"
}
})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",
"items": false
})JSON");

EXPECT_EQ(document, expected);
}

TEST(AlterSchema_lint_2019_09, additional_items_with_schema_items_array_items) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2019-09/schema",
"items": [
{ "type": "string" },
{ "type": "number" }
],
"additionalItems": 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",
"items": [
{ "type": "string" },
{ "type": "number" }
],
"additionalItems": false
})JSON");

EXPECT_EQ(document, expected);
}
105 changes: 105 additions & 0 deletions test/alterschema/alterschema_lint_draft3_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -595,3 +595,108 @@ TEST(AlterSchema_lint_draft3, draft_official_dialect_without_empty_fragment_1) {

EXPECT_EQ(document, expected);
}

TEST(AlterSchema_lint_draft3, additional_items_with_schema_items_1) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"$schema": "http://json-schema.org/draft-03/schema#",
"items": {
"type": "number"
},
"additionalItems": 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-03/schema#",
"items": {
"type": "number"
}
})JSON");

EXPECT_EQ(document, expected);
}

TEST(AlterSchema_lint_draft3, additional_items_with_schema_items_2) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"$schema": "http://json-schema.org/draft-03/schema#",
"items": {
"type": "string"
},
"additionalItems": {
"type": "boolean"
}
})JSON");

LINT_AND_FIX_FOR_READABILITY(document);

const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
"$schema": "http://json-schema.org/draft-03/schema#",
"items": {
"type": "string"
}
})JSON");

EXPECT_EQ(document, expected);
}

TEST(AlterSchema_lint_draft3,
additional_items_with_schema_items_boolean_items_true) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"$schema": "http://json-schema.org/draft-03/schema#",
"items": true,
"additionalItems": 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-03/schema#"
})JSON");

EXPECT_EQ(document, expected);
}

TEST(AlterSchema_lint_draft3,
additional_items_with_schema_items_boolean_items_false) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"$schema": "http://json-schema.org/draft-03/schema#",
"items": false,
"additionalItems": {
"type": "string"
}
})JSON");

LINT_AND_FIX_FOR_READABILITY(document);

const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
"$schema": "http://json-schema.org/draft-03/schema#",
"items": false
})JSON");

EXPECT_EQ(document, expected);
}

TEST(AlterSchema_lint_draft3, additional_items_with_schema_items_array_items) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"$schema": "http://json-schema.org/draft-03/schema#",
"items": [
{ "type": "string" },
{ "type": "number" }
],
"additionalItems": 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-03/schema#",
"items": [
{ "type": "string" },
{ "type": "number" }
],
"additionalItems": false
})JSON");

EXPECT_EQ(document, expected);
}
Loading
Loading