Skip to content

Commit d3cd12d

Browse files
committed
[WIP] Port exiting linter rules from JSON Toolkit
Signed-off-by: Juan Cruz Viotti <[email protected]>
1 parent 0c9ea7d commit d3cd12d

21 files changed

+559
-1
lines changed

CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ include(vendor/noa/cmake/noa.cmake)
77

88
# Options
99
option(ALTERSCHEMA_ENGINE "Build the Alterschema Engine library" ON)
10+
option(ALTERSCHEMA_LINTER "Build the Alterschema Linter library" ON)
1011
option(ALTERSCHEMA_TESTS "Build the Alterschema tests" OFF)
1112
option(ALTERSCHEMA_DOCS "Build the Alterschema docs" OFF)
1213
option(ALTERSCHEMA_INSTALL "Install the Alterschema library" ON)
@@ -19,11 +20,15 @@ if(ALTERSCHEMA_INSTALL)
1920
# TODO
2021
endif()
2122

22-
if(ALTERSCHEMA_ENGINE)
23+
if(ALTERSCHEMA_ENGINE OR ALTERSCHEMA_LINTER)
2324
find_package(JSONToolkit REQUIRED)
2425
add_subdirectory(src/engine)
2526
endif()
2627

28+
if(ALTERSCHEMA_LINTER)
29+
add_subdirectory(src/linter)
30+
endif()
31+
2732
if(ALTERSCHEMA_ADDRESS_SANITIZER)
2833
noa_sanitizer(TYPE address)
2934
elseif(ALTERSCHEMA_UNDEFINED_SANITIZER)

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ configure: .always
1313
-DCMAKE_BUILD_TYPE:STRING=$(PRESET) \
1414
-DCMAKE_COMPILE_WARNING_AS_ERROR:BOOL=ON \
1515
-DALTERSCHEMA_ENGINE:BOOL=ON \
16+
-DALTERSCHEMA_LINTER:BOOL=ON \
1617
-DALTERSCHEMA_TESTS:BOOL=ON \
1718
-DBUILD_SHARED_LIBS:BOOL=$(SHARED)
1819

src/linter/CMakeLists.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
noa_library(NAMESPACE sourcemeta PROJECT alterschema NAME linter
2+
FOLDER "Alterschema/Linter"
3+
SOURCES linter.cc
4+
# Rules
5+
additional_properties_default.h
6+
const_with_type.h
7+
content_media_type_without_encoding.h
8+
content_schema_default.h
9+
content_schema_without_media_type.h
10+
else_without_if.h
11+
enum_to_const.h
12+
enum_with_type.h
13+
items_array_default.h
14+
items_schema_default.h
15+
max_contains_without_contains.h
16+
min_contains_without_contains.h
17+
single_type_array.h
18+
then_without_if.h
19+
unevaluated_items_default.h
20+
unevaluated_properties_default.h)
21+
22+
if(ALTERSCHEMA_INSTALL)
23+
noa_library_install(NAMESPACE sourcemeta PROJECT alterschema NAME linter)
24+
endif()
25+
26+
target_link_libraries(sourcemeta_alterschema_linter PUBLIC
27+
sourcemeta::alterschema::engine)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
class AdditionalPropertiesDefault final : public Rule {
2+
public:
3+
AdditionalPropertiesDefault()
4+
: Rule{"additional_properties_default",
5+
"Setting the `additionalProperties` keyword to the true schema "
6+
"does not add any further constraint"} {};
7+
8+
[[nodiscard]] auto
9+
condition(const sourcemeta::jsontoolkit::JSON &schema, const std::string &,
10+
const std::set<std::string> &vocabularies,
11+
const sourcemeta::jsontoolkit::Pointer &) const -> bool override {
12+
return contains_any(
13+
vocabularies,
14+
{"https://json-schema.org/draft/2020-12/vocab/applicator",
15+
"https://json-schema.org/draft/2019-09/vocab/applicator",
16+
"http://json-schema.org/draft-07/schema#",
17+
"http://json-schema.org/draft-06/schema#",
18+
"http://json-schema.org/draft-04/schema#",
19+
"http://json-schema.org/draft-03/schema#",
20+
"http://json-schema.org/draft-02/hyper-schema#",
21+
"http://json-schema.org/draft-01/hyper-schema#"}) &&
22+
schema.is_object() && schema.defines("additionalProperties") &&
23+
((schema.at("additionalProperties").is_boolean() &&
24+
schema.at("additionalProperties").to_boolean()) ||
25+
(schema.at("additionalProperties").is_object() &&
26+
schema.at("additionalProperties").empty()));
27+
}
28+
29+
auto transform(Transformer &transformer) const -> void override {
30+
transformer.erase("additionalProperties");
31+
}
32+
};

src/linter/const_with_type.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
class ConstWithType final : public Rule {
2+
public:
3+
ConstWithType()
4+
: Rule{"const_with_type",
5+
"Setting `type` alongside `const` is considered an anti-pattern, "
6+
"as the constant already implies its respective type"} {};
7+
8+
[[nodiscard]] auto
9+
condition(const sourcemeta::jsontoolkit::JSON &schema, const std::string &,
10+
const std::set<std::string> &vocabularies,
11+
const sourcemeta::jsontoolkit::Pointer &) const -> bool override {
12+
return contains_any(
13+
vocabularies,
14+
{"https://json-schema.org/draft/2020-12/vocab/validation",
15+
"https://json-schema.org/draft/2019-09/vocab/validation",
16+
"http://json-schema.org/draft-07/schema#",
17+
"http://json-schema.org/draft-06/schema#"}) &&
18+
schema.is_object() && schema.defines("type") &&
19+
schema.defines("const");
20+
}
21+
22+
auto transform(Transformer &transformer) const -> void override {
23+
transformer.erase("type");
24+
}
25+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class ContentMediaTypeWithoutEncoding final : public Rule {
2+
public:
3+
ContentMediaTypeWithoutEncoding()
4+
: Rule{"content_media_type_without_encoding",
5+
"The `contentMediaType` keyword is meaningless "
6+
"without the presence of the `contentEncoding` keyword"} {};
7+
8+
[[nodiscard]] auto
9+
condition(const sourcemeta::jsontoolkit::JSON &schema, const std::string &,
10+
const std::set<std::string> &vocabularies,
11+
const sourcemeta::jsontoolkit::Pointer &) const -> bool override {
12+
return contains_any(vocabularies,
13+
{"https://json-schema.org/draft/2020-12/vocab/content",
14+
"https://json-schema.org/draft/2019-09/vocab/content",
15+
"http://json-schema.org/draft-07/schema#"}) &&
16+
schema.is_object() && schema.defines("contentMediaType") &&
17+
!schema.defines("contentEncoding");
18+
}
19+
20+
auto transform(Transformer &transformer) const -> void override {
21+
transformer.erase("contentMediaType");
22+
}
23+
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
class ContentSchemaDefault final : public Rule {
2+
public:
3+
ContentSchemaDefault()
4+
: Rule{"content_schema_default",
5+
"Setting the `contentSchema` keyword to the true schema "
6+
"does not add any further constraint"} {};
7+
8+
[[nodiscard]] auto
9+
condition(const sourcemeta::jsontoolkit::JSON &schema, const std::string &,
10+
const std::set<std::string> &vocabularies,
11+
const sourcemeta::jsontoolkit::Pointer &) const -> bool override {
12+
return contains_any(
13+
vocabularies,
14+
{"https://json-schema.org/draft/2020-12/vocab/content",
15+
"https://json-schema.org/draft/2019-09/vocab/content"}) &&
16+
schema.is_object() && schema.defines("contentSchema") &&
17+
((schema.at("contentSchema").is_boolean() &&
18+
schema.at("contentSchema").to_boolean()) ||
19+
(schema.at("contentSchema").is_object() &&
20+
schema.at("contentSchema").empty()));
21+
}
22+
23+
auto transform(Transformer &transformer) const -> void override {
24+
transformer.erase("contentSchema");
25+
}
26+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class ContentSchemaWithoutMediaType final : public Rule {
2+
public:
3+
ContentSchemaWithoutMediaType()
4+
: Rule{"content_schema_without_media_type",
5+
"The `contentSchema` keyword is meaningless without the presence "
6+
"of the `contentMediaType` keyword"} {};
7+
8+
[[nodiscard]] auto
9+
condition(const sourcemeta::jsontoolkit::JSON &schema, const std::string &,
10+
const std::set<std::string> &vocabularies,
11+
const sourcemeta::jsontoolkit::Pointer &) const -> bool override {
12+
return contains_any(
13+
vocabularies,
14+
{"https://json-schema.org/draft/2020-12/vocab/content",
15+
"https://json-schema.org/draft/2019-09/vocab/content"}) &&
16+
schema.is_object() && schema.defines("contentSchema") &&
17+
!schema.defines("contentMediaType");
18+
}
19+
20+
auto transform(Transformer &transformer) const -> void override {
21+
transformer.erase("contentSchema");
22+
}
23+
};

src/linter/else_without_if.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class ElseWithoutIf final : public Rule {
2+
public:
3+
ElseWithoutIf()
4+
: Rule{"else_without_if", "The `else` keyword is meaningless "
5+
"without the presence of the `if` keyword"} {};
6+
7+
[[nodiscard]] auto
8+
condition(const sourcemeta::jsontoolkit::JSON &schema, const std::string &,
9+
const std::set<std::string> &vocabularies,
10+
const sourcemeta::jsontoolkit::Pointer &) const -> bool override {
11+
return contains_any(
12+
vocabularies,
13+
{"https://json-schema.org/draft/2020-12/vocab/applicator",
14+
"https://json-schema.org/draft/2019-09/vocab/applicator",
15+
"http://json-schema.org/draft-07/schema#"}) &&
16+
schema.is_object() && schema.defines("else") &&
17+
!schema.defines("if");
18+
}
19+
20+
auto transform(Transformer &transformer) const -> void override {
21+
transformer.erase("else");
22+
}
23+
};

src/linter/enum_to_const.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
class EnumToConst final : public Rule {
2+
public:
3+
EnumToConst()
4+
: Rule("enum_to_const",
5+
"An `enum` of a single value can be expressed as `const`") {};
6+
7+
[[nodiscard]] auto
8+
condition(const sourcemeta::jsontoolkit::JSON &schema, const std::string &,
9+
const std::set<std::string> &vocabularies,
10+
const sourcemeta::jsontoolkit::Pointer &) const -> bool override {
11+
return contains_any(
12+
vocabularies,
13+
{"https://json-schema.org/draft/2020-12/vocab/validation",
14+
"https://json-schema.org/draft/2019-09/vocab/validation",
15+
"http://json-schema.org/draft-07/schema#",
16+
"http://json-schema.org/draft-06/schema#"}) &&
17+
schema.is_object() && !schema.defines("const") &&
18+
schema.defines("enum") && schema.at("enum").is_array() &&
19+
schema.at("enum").size() == 1;
20+
}
21+
22+
auto transform(Transformer &transformer) const -> void override {
23+
transformer.assign("const", transformer.schema().at("enum").front());
24+
transformer.erase("enum");
25+
}
26+
};

0 commit comments

Comments
 (0)