Skip to content

Commit 6cbd7f9

Browse files
authored
Add a linter rule that validates the default keyword (#397)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent 6d37617 commit 6cbd7f9

5 files changed

Lines changed: 322 additions & 2 deletions

File tree

src/linter/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
sourcemeta_library(NAMESPACE sourcemeta PROJECT blaze NAME linter
22
FOLDER "Blaze/Linter"
3-
SOURCES valid_examples.cc)
3+
SOURCES
4+
valid_default.cc
5+
valid_examples.cc)
46

57
if(BLAZE_INSTALL)
68
sourcemeta_library_install(NAMESPACE sourcemeta PROJECT blaze NAME linter)

src/linter/include/sourcemeta/blaze/linter.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,37 @@ class SOURCEMETA_BLAZE_LINTER_EXPORT ValidExamples final
5050
#endif
5151
};
5252

53+
/// @ingroup linter
54+
///
55+
/// Check that the `default` keyword consists of an instance that matches the
56+
/// corresponding schema.
57+
class SOURCEMETA_BLAZE_LINTER_EXPORT ValidDefault final
58+
: public sourcemeta::core::SchemaTransformRule {
59+
public:
60+
ValidDefault(Compiler compiler);
61+
[[nodiscard]] auto condition(const sourcemeta::core::JSON &,
62+
const sourcemeta::core::JSON &,
63+
const sourcemeta::core::Vocabularies &,
64+
const sourcemeta::core::SchemaFrame &,
65+
const sourcemeta::core::SchemaFrame::Location &,
66+
const sourcemeta::core::SchemaWalker &,
67+
const sourcemeta::core::SchemaResolver &) const
68+
-> bool override;
69+
auto transform(sourcemeta::core::JSON &) const -> void override;
70+
71+
private:
72+
// Exporting symbols that depends on the standard C++ library is considered
73+
// safe.
74+
// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN
75+
#if defined(_MSC_VER)
76+
#pragma warning(disable : 4251)
77+
#endif
78+
const Compiler compiler_;
79+
#if defined(_MSC_VER)
80+
#pragma warning(default : 4251)
81+
#endif
82+
};
83+
5384
} // namespace sourcemeta::blaze
5485

5586
#endif

src/linter/valid_default.cc

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#include <sourcemeta/blaze/evaluator.h>
2+
#include <sourcemeta/blaze/linter.h>
3+
4+
namespace sourcemeta::blaze {
5+
6+
ValidDefault::ValidDefault(Compiler compiler)
7+
: sourcemeta::core::SchemaTransformRule{"blaze/valid_default",
8+
"Only set a `default` valid that "
9+
"validates against the schema"},
10+
compiler_{std::move(compiler)} {};
11+
12+
auto ValidDefault::condition(
13+
const sourcemeta::core::JSON &schema, const sourcemeta::core::JSON &root,
14+
const sourcemeta::core::Vocabularies &vocabularies,
15+
const sourcemeta::core::SchemaFrame &,
16+
const sourcemeta::core::SchemaFrame::Location &location,
17+
const sourcemeta::core::SchemaWalker &walker,
18+
const sourcemeta::core::SchemaResolver &resolver) const -> bool {
19+
// Technically, the `default` keyword goes back to Draft 1, but Blaze
20+
// only supports Draft 4 and later
21+
if (!vocabularies.contains(
22+
"https://json-schema.org/draft/2020-12/vocab/meta-data") &&
23+
!vocabularies.contains(
24+
"https://json-schema.org/draft/2019-09/vocab/meta-data") &&
25+
!vocabularies.contains("http://json-schema.org/draft-07/schema#") &&
26+
!vocabularies.contains("http://json-schema.org/draft-06/schema#") &&
27+
!vocabularies.contains("http://json-schema.org/draft-04/schema#")) {
28+
return false;
29+
}
30+
31+
if (!schema.defines("default")) {
32+
return false;
33+
}
34+
35+
const auto subschema{sourcemeta::core::wrap(root, location.pointer, resolver,
36+
location.dialect)};
37+
const auto schema_template{compile(subschema, walker, resolver,
38+
this->compiler_, Mode::FastValidation,
39+
location.dialect)};
40+
41+
Evaluator evaluator;
42+
return !evaluator.validate(schema_template, schema.at("default"));
43+
}
44+
45+
auto ValidDefault::transform(sourcemeta::core::JSON &schema) const -> void {
46+
schema.erase("default");
47+
}
48+
49+
} // namespace sourcemeta::blaze

test/linter/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
sourcemeta_googletest(NAMESPACE sourcemeta PROJECT blaze NAME linter
22
FOLDER "Blaze/Linter"
3-
SOURCES linter_valid_examples_test.cc)
3+
SOURCES
4+
linter_valid_default_test.cc
5+
linter_valid_examples_test.cc)
46

57
target_link_libraries(sourcemeta_blaze_linter_unit
68
PRIVATE sourcemeta::core::json)
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <sourcemeta/blaze/linter.h>
4+
5+
#include <sourcemeta/core/json.h>
6+
#include <sourcemeta/core/jsonschema.h>
7+
8+
TEST(Linter, valid_default_1) {
9+
sourcemeta::core::SchemaTransformer bundle;
10+
bundle.add<sourcemeta::blaze::ValidDefault>(
11+
sourcemeta::blaze::default_schema_compiler);
12+
13+
auto schema{sourcemeta::core::parse_json(R"JSON({
14+
"$schema": "https://json-schema.org/draft/2020-12/schema",
15+
"type": "string"
16+
})JSON")};
17+
18+
const auto result =
19+
bundle.apply(schema, sourcemeta::core::schema_official_walker,
20+
sourcemeta::core::schema_official_resolver);
21+
22+
EXPECT_TRUE(result);
23+
24+
const auto expected{sourcemeta::core::parse_json(R"JSON({
25+
"$schema": "https://json-schema.org/draft/2020-12/schema",
26+
"type": "string"
27+
})JSON")};
28+
29+
EXPECT_EQ(schema, expected);
30+
}
31+
32+
TEST(Linter, valid_default_2) {
33+
sourcemeta::core::SchemaTransformer bundle;
34+
bundle.add<sourcemeta::blaze::ValidDefault>(
35+
sourcemeta::blaze::default_schema_compiler);
36+
37+
auto schema{sourcemeta::core::parse_json(R"JSON({
38+
"$schema": "https://json-schema.org/draft/2020-12/schema",
39+
"default": { "foo": "bar" },
40+
"properties": {
41+
"foo": {
42+
"type": "string",
43+
"default": "baz"
44+
}
45+
}
46+
})JSON")};
47+
48+
const auto result =
49+
bundle.apply(schema, sourcemeta::core::schema_official_walker,
50+
sourcemeta::core::schema_official_resolver);
51+
52+
EXPECT_TRUE(result);
53+
54+
const auto expected{sourcemeta::core::parse_json(R"JSON({
55+
"$schema": "https://json-schema.org/draft/2020-12/schema",
56+
"default": { "foo": "bar" },
57+
"properties": {
58+
"foo": {
59+
"type": "string",
60+
"default": "baz"
61+
}
62+
}
63+
})JSON")};
64+
65+
EXPECT_EQ(schema, expected);
66+
}
67+
68+
TEST(Linter, valid_default_3) {
69+
sourcemeta::core::SchemaTransformer bundle;
70+
bundle.add<sourcemeta::blaze::ValidDefault>(
71+
sourcemeta::blaze::default_schema_compiler);
72+
73+
auto schema{sourcemeta::core::parse_json(R"JSON({
74+
"$schema": "https://json-schema.org/draft/2020-12/schema",
75+
"default": { "foo": 1 },
76+
"properties": {
77+
"foo": {
78+
"type": "string",
79+
"default": true
80+
}
81+
}
82+
})JSON")};
83+
84+
const auto result =
85+
bundle.apply(schema, sourcemeta::core::schema_official_walker,
86+
sourcemeta::core::schema_official_resolver);
87+
88+
EXPECT_TRUE(result);
89+
90+
const auto expected{sourcemeta::core::parse_json(R"JSON({
91+
"$schema": "https://json-schema.org/draft/2020-12/schema",
92+
"properties": {
93+
"foo": {
94+
"type": "string"
95+
}
96+
}
97+
})JSON")};
98+
99+
EXPECT_EQ(schema, expected);
100+
}
101+
102+
TEST(Linter, valid_default_4) {
103+
sourcemeta::core::SchemaTransformer bundle;
104+
bundle.add<sourcemeta::blaze::ValidDefault>(
105+
sourcemeta::blaze::default_schema_compiler);
106+
107+
auto schema{sourcemeta::core::parse_json(R"JSON({
108+
"$schema": "https://json-schema.org/draft/2019-09/schema",
109+
"default": { "foo": 1 },
110+
"properties": {
111+
"foo": {
112+
"type": "string",
113+
"default": true
114+
}
115+
}
116+
})JSON")};
117+
118+
const auto result =
119+
bundle.apply(schema, sourcemeta::core::schema_official_walker,
120+
sourcemeta::core::schema_official_resolver);
121+
122+
EXPECT_TRUE(result);
123+
124+
const auto expected{sourcemeta::core::parse_json(R"JSON({
125+
"$schema": "https://json-schema.org/draft/2019-09/schema",
126+
"properties": {
127+
"foo": {
128+
"type": "string"
129+
}
130+
}
131+
})JSON")};
132+
133+
EXPECT_EQ(schema, expected);
134+
}
135+
136+
TEST(Linter, valid_default_5) {
137+
sourcemeta::core::SchemaTransformer bundle;
138+
bundle.add<sourcemeta::blaze::ValidDefault>(
139+
sourcemeta::blaze::default_schema_compiler);
140+
141+
auto schema{sourcemeta::core::parse_json(R"JSON({
142+
"$schema": "http://json-schema.org/draft-07/schema#",
143+
"default": { "foo": 1 },
144+
"properties": {
145+
"foo": {
146+
"type": "string",
147+
"default": true
148+
}
149+
}
150+
})JSON")};
151+
152+
const auto result =
153+
bundle.apply(schema, sourcemeta::core::schema_official_walker,
154+
sourcemeta::core::schema_official_resolver);
155+
156+
EXPECT_TRUE(result);
157+
158+
const auto expected{sourcemeta::core::parse_json(R"JSON({
159+
"$schema": "http://json-schema.org/draft-07/schema#",
160+
"properties": {
161+
"foo": {
162+
"type": "string"
163+
}
164+
}
165+
})JSON")};
166+
167+
EXPECT_EQ(schema, expected);
168+
}
169+
170+
TEST(Linter, valid_default_6) {
171+
sourcemeta::core::SchemaTransformer bundle;
172+
bundle.add<sourcemeta::blaze::ValidDefault>(
173+
sourcemeta::blaze::default_schema_compiler);
174+
175+
auto schema{sourcemeta::core::parse_json(R"JSON({
176+
"$schema": "http://json-schema.org/draft-06/schema#",
177+
"default": { "foo": 1 },
178+
"properties": {
179+
"foo": {
180+
"type": "string",
181+
"default": true
182+
}
183+
}
184+
})JSON")};
185+
186+
const auto result =
187+
bundle.apply(schema, sourcemeta::core::schema_official_walker,
188+
sourcemeta::core::schema_official_resolver);
189+
190+
EXPECT_TRUE(result);
191+
192+
const auto expected{sourcemeta::core::parse_json(R"JSON({
193+
"$schema": "http://json-schema.org/draft-06/schema#",
194+
"properties": {
195+
"foo": {
196+
"type": "string"
197+
}
198+
}
199+
})JSON")};
200+
201+
EXPECT_EQ(schema, expected);
202+
}
203+
204+
TEST(Linter, valid_default_7) {
205+
sourcemeta::core::SchemaTransformer bundle;
206+
bundle.add<sourcemeta::blaze::ValidDefault>(
207+
sourcemeta::blaze::default_schema_compiler);
208+
209+
auto schema{sourcemeta::core::parse_json(R"JSON({
210+
"$schema": "http://json-schema.org/draft-04/schema#",
211+
"default": { "foo": 1 },
212+
"properties": {
213+
"foo": {
214+
"type": "string",
215+
"default": true
216+
}
217+
}
218+
})JSON")};
219+
220+
const auto result =
221+
bundle.apply(schema, sourcemeta::core::schema_official_walker,
222+
sourcemeta::core::schema_official_resolver);
223+
224+
EXPECT_TRUE(result);
225+
226+
const auto expected{sourcemeta::core::parse_json(R"JSON({
227+
"$schema": "http://json-schema.org/draft-04/schema#",
228+
"properties": {
229+
"foo": {
230+
"type": "string"
231+
}
232+
}
233+
})JSON")};
234+
235+
EXPECT_EQ(schema, expected);
236+
}

0 commit comments

Comments
 (0)