Skip to content

Commit

Permalink
[WIP] Port exiting linter rules from JSON Toolkit
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <[email protected]>
  • Loading branch information
jviotti committed Aug 23, 2024
1 parent 0c9ea7d commit d3cd12d
Show file tree
Hide file tree
Showing 21 changed files with 559 additions and 1 deletion.
7 changes: 6 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ include(vendor/noa/cmake/noa.cmake)

# Options
option(ALTERSCHEMA_ENGINE "Build the Alterschema Engine library" ON)
option(ALTERSCHEMA_LINTER "Build the Alterschema Linter library" ON)
option(ALTERSCHEMA_TESTS "Build the Alterschema tests" OFF)
option(ALTERSCHEMA_DOCS "Build the Alterschema docs" OFF)
option(ALTERSCHEMA_INSTALL "Install the Alterschema library" ON)
Expand All @@ -19,11 +20,15 @@ if(ALTERSCHEMA_INSTALL)
# TODO
endif()

if(ALTERSCHEMA_ENGINE)
if(ALTERSCHEMA_ENGINE OR ALTERSCHEMA_LINTER)
find_package(JSONToolkit REQUIRED)
add_subdirectory(src/engine)
endif()

if(ALTERSCHEMA_LINTER)
add_subdirectory(src/linter)
endif()

if(ALTERSCHEMA_ADDRESS_SANITIZER)
noa_sanitizer(TYPE address)
elseif(ALTERSCHEMA_UNDEFINED_SANITIZER)
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ configure: .always
-DCMAKE_BUILD_TYPE:STRING=$(PRESET) \
-DCMAKE_COMPILE_WARNING_AS_ERROR:BOOL=ON \
-DALTERSCHEMA_ENGINE:BOOL=ON \
-DALTERSCHEMA_LINTER:BOOL=ON \
-DALTERSCHEMA_TESTS:BOOL=ON \
-DBUILD_SHARED_LIBS:BOOL=$(SHARED)

Expand Down
27 changes: 27 additions & 0 deletions src/linter/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
noa_library(NAMESPACE sourcemeta PROJECT alterschema NAME linter
FOLDER "Alterschema/Linter"
SOURCES linter.cc
# Rules
additional_properties_default.h
const_with_type.h
content_media_type_without_encoding.h
content_schema_default.h
content_schema_without_media_type.h
else_without_if.h
enum_to_const.h
enum_with_type.h
items_array_default.h
items_schema_default.h
max_contains_without_contains.h
min_contains_without_contains.h
single_type_array.h
then_without_if.h
unevaluated_items_default.h
unevaluated_properties_default.h)

if(ALTERSCHEMA_INSTALL)
noa_library_install(NAMESPACE sourcemeta PROJECT alterschema NAME linter)
endif()

target_link_libraries(sourcemeta_alterschema_linter PUBLIC
sourcemeta::alterschema::engine)
32 changes: 32 additions & 0 deletions src/linter/additional_properties_default.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class AdditionalPropertiesDefault final : public Rule {
public:
AdditionalPropertiesDefault()
: Rule{"additional_properties_default",
"Setting the `additionalProperties` keyword to the true schema "
"does not add any further constraint"} {};

[[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/applicator",
"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#",
"http://json-schema.org/draft-02/hyper-schema#",
"http://json-schema.org/draft-01/hyper-schema#"}) &&
schema.is_object() && schema.defines("additionalProperties") &&
((schema.at("additionalProperties").is_boolean() &&
schema.at("additionalProperties").to_boolean()) ||
(schema.at("additionalProperties").is_object() &&
schema.at("additionalProperties").empty()));
}

auto transform(Transformer &transformer) const -> void override {
transformer.erase("additionalProperties");
}
};
25 changes: 25 additions & 0 deletions src/linter/const_with_type.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class ConstWithType final : public Rule {
public:
ConstWithType()
: Rule{"const_with_type",
"Setting `type` alongside `const` is considered an anti-pattern, "
"as the constant already implies its respective type"} {};

[[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",
"http://json-schema.org/draft-07/schema#",
"http://json-schema.org/draft-06/schema#"}) &&
schema.is_object() && schema.defines("type") &&
schema.defines("const");
}

auto transform(Transformer &transformer) const -> void override {
transformer.erase("type");
}
};
23 changes: 23 additions & 0 deletions src/linter/content_media_type_without_encoding.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class ContentMediaTypeWithoutEncoding final : public Rule {
public:
ContentMediaTypeWithoutEncoding()
: Rule{"content_media_type_without_encoding",
"The `contentMediaType` keyword is meaningless "
"without the presence of the `contentEncoding` keyword"} {};

[[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/content",
"https://json-schema.org/draft/2019-09/vocab/content",
"http://json-schema.org/draft-07/schema#"}) &&
schema.is_object() && schema.defines("contentMediaType") &&
!schema.defines("contentEncoding");
}

auto transform(Transformer &transformer) const -> void override {
transformer.erase("contentMediaType");
}
};
26 changes: 26 additions & 0 deletions src/linter/content_schema_default.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class ContentSchemaDefault final : public Rule {
public:
ContentSchemaDefault()
: Rule{"content_schema_default",
"Setting the `contentSchema` keyword to the true schema "
"does not add any further constraint"} {};

[[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/content",
"https://json-schema.org/draft/2019-09/vocab/content"}) &&
schema.is_object() && schema.defines("contentSchema") &&
((schema.at("contentSchema").is_boolean() &&
schema.at("contentSchema").to_boolean()) ||
(schema.at("contentSchema").is_object() &&
schema.at("contentSchema").empty()));
}

auto transform(Transformer &transformer) const -> void override {
transformer.erase("contentSchema");
}
};
23 changes: 23 additions & 0 deletions src/linter/content_schema_without_media_type.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class ContentSchemaWithoutMediaType final : public Rule {
public:
ContentSchemaWithoutMediaType()
: Rule{"content_schema_without_media_type",
"The `contentSchema` keyword is meaningless without the presence "
"of the `contentMediaType` keyword"} {};

[[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/content",
"https://json-schema.org/draft/2019-09/vocab/content"}) &&
schema.is_object() && schema.defines("contentSchema") &&
!schema.defines("contentMediaType");
}

auto transform(Transformer &transformer) const -> void override {
transformer.erase("contentSchema");
}
};
23 changes: 23 additions & 0 deletions src/linter/else_without_if.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class ElseWithoutIf final : public Rule {
public:
ElseWithoutIf()
: Rule{"else_without_if", "The `else` keyword is meaningless "
"without the presence of the `if` keyword"} {};

[[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/applicator",
"https://json-schema.org/draft/2019-09/vocab/applicator",
"http://json-schema.org/draft-07/schema#"}) &&
schema.is_object() && schema.defines("else") &&
!schema.defines("if");
}

auto transform(Transformer &transformer) const -> void override {
transformer.erase("else");
}
};
26 changes: 26 additions & 0 deletions src/linter/enum_to_const.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class EnumToConst final : public Rule {
public:
EnumToConst()
: Rule("enum_to_const",
"An `enum` of a single value can be expressed as `const`") {};

[[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",
"http://json-schema.org/draft-07/schema#",
"http://json-schema.org/draft-06/schema#"}) &&
schema.is_object() && !schema.defines("const") &&
schema.defines("enum") && schema.at("enum").is_array() &&
schema.at("enum").size() == 1;
}

auto transform(Transformer &transformer) const -> void override {
transformer.assign("const", transformer.schema().at("enum").front());
transformer.erase("enum");
}
};
30 changes: 30 additions & 0 deletions src/linter/enum_with_type.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class EnumWithType final : public Rule {
public:
EnumWithType()
: Rule{
"enum_with_type",
"Setting `type` alongside `enum` is considered an anti-pattern, as "
"the enumeration choices already imply their respective types"} {};

[[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",
"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.defines("enum");
}

auto transform(Transformer &transformer) const -> void override {
transformer.erase("type");
}
};
31 changes: 31 additions & 0 deletions src/linter/include/sourcemeta/alterschema/linter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#ifndef SOURCEMETA_ALTERSCHEMA_LINTER_H_
#define SOURCEMETA_ALTERSCHEMA_LINTER_H_

#include "linter_export.h"

#include <sourcemeta/alterschema/engine.h>

namespace sourcemeta::alterschema {

/// @ingroup linter
/// The category of a built-in transformation rule
enum class LinterCategory {
/// Rules that make use of newer features within the same dialect
Modernize,

/// Rules that detect common anti-patterns
AntiPattern,

/// Rules that simplify the given schema
Simplify,

/// Rules that remove schema redundancies
Redundant
};

/// Add a set of built-in linter rules given a category
auto lint(Bundle &bundle, const LinterCategory category) -> void;

} // namespace sourcemeta::alterschema

#endif
28 changes: 28 additions & 0 deletions src/linter/items_array_default.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class ItemsArrayDefault final : public Rule {
public:
ItemsArrayDefault()
: Rule{"items_array_default",
"Setting the `items` keyword to the empty array "
"does not add any further constraint"} {};

[[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/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#",
"http://json-schema.org/draft-02/hyper-schema#",
"http://json-schema.org/draft-01/hyper-schema#"}) &&
schema.is_object() && schema.defines("items") &&
schema.at("items").is_array() && schema.at("items").empty();
}

auto transform(Transformer &transformer) const -> void override {
transformer.erase("items");
}
};
31 changes: 31 additions & 0 deletions src/linter/items_schema_default.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class ItemsSchemaDefault final : public Rule {
public:
ItemsSchemaDefault()
: Rule{"items_schema_default",
"Setting the `items` keyword to the true schema "
"does not add any further constraint"} {};

[[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/applicator",
"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#",
"http://json-schema.org/draft-02/hyper-schema#",
"http://json-schema.org/draft-01/hyper-schema#"}) &&
schema.is_object() && schema.defines("items") &&
((schema.at("items").is_boolean() &&
schema.at("items").to_boolean()) ||
(schema.at("items").is_object() && schema.at("items").empty()));
}

auto transform(Transformer &transformer) const -> void override {
transformer.erase("items");
}
};
Loading

0 comments on commit d3cd12d

Please sign in to comment.