Skip to content

Commit 64acf24

Browse files
authored
Linter: add rule to remove default multipleOf: 1 (#1804)
### Implementation **Rule ID**: multiple_of_default **Description**: "Setting multipleOf to 1 does not add any further constraint" **Condition**: Detects multipleOf: 1 (integer) or multipleOf: 1.0 (real) in JSON Schema objects **Transform**: Removes the redundant multipleOf property **Dialects**: Supports draft4, draft6, draft7, 2019-09, and 2020-12 - Registered the rule in the Readability mode (not common rules) since it removes redundant default values - Properly categorized alongside other Default rules like AdditionalPropertiesDefault - Comprehensive Test Coverage: Added 16 test cases across all supported JSON Schema drafts: ### Test cases: Positive cases: multipleOf: 1 and multipleOf: 1.0 removal Negative cases: No change for non-1 values (multipleOf: 2, multipleOf: 0.5) Edge cases: String values ("1"), zero (0), negative (-1) - all correctly preserved Context preservation: Tests with various other keywords to ensure proper behavior --------- Signed-off-by: karan-palan <karanpalan007@gmail.com>
1 parent 3a031c0 commit 64acf24

File tree

8 files changed

+372
-0
lines changed

8 files changed

+372
-0
lines changed

src/extension/alterschema/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema
3838
linter/dependent_required_default.h
3939
linter/items_array_default.h
4040
linter/items_schema_default.h
41+
linter/multiple_of_default.h
4142
linter/pattern_properties_default.h
4243
linter/properties_default.h
4344
linter/unevaluated_items_default.h

src/extension/alterschema/alterschema.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ static auto every_item_is_boolean(const T &container) -> bool {
7373
#include "linter/maximum_real_for_integer.h"
7474
#include "linter/min_contains_without_contains.h"
7575
#include "linter/minimum_real_for_integer.h"
76+
#include "linter/multiple_of_default.h"
7677
#include "linter/non_applicable_type_specific_keywords.h"
7778
#include "linter/pattern_properties_default.h"
7879
#include "linter/properties_default.h"
@@ -141,6 +142,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode)
141142
bundle.add<DependentRequiredDefault>();
142143
bundle.add<ItemsArrayDefault>();
143144
bundle.add<ItemsSchemaDefault>();
145+
bundle.add<MultipleOfDefault>();
144146
bundle.add<PatternPropertiesDefault>();
145147
bundle.add<PropertiesDefault>();
146148
bundle.add<UnevaluatedItemsDefault>();
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
class MultipleOfDefault final : public SchemaTransformRule {
2+
public:
3+
MultipleOfDefault()
4+
: SchemaTransformRule{
5+
"multiple_of_default",
6+
"Setting `multipleOf` to 1 does not add any further constraint"} {};
7+
8+
[[nodiscard]] auto
9+
condition(const sourcemeta::core::JSON &schema,
10+
const sourcemeta::core::JSON &,
11+
const sourcemeta::core::Vocabularies &vocabularies,
12+
const sourcemeta::core::SchemaFrame &,
13+
const sourcemeta::core::SchemaFrame::Location &,
14+
const sourcemeta::core::SchemaWalker &,
15+
const sourcemeta::core::SchemaResolver &) const
16+
-> sourcemeta::core::SchemaTransformRule::Result override {
17+
return contains_any(
18+
vocabularies,
19+
{"https://json-schema.org/draft/2020-12/vocab/validation",
20+
"https://json-schema.org/draft/2019-09/vocab/validation",
21+
"http://json-schema.org/draft-07/schema#",
22+
"http://json-schema.org/draft-06/schema#",
23+
"http://json-schema.org/draft-04/schema#"}) &&
24+
schema.is_object() && schema.defines("multipleOf") &&
25+
((schema.at("multipleOf").is_integer() &&
26+
schema.at("multipleOf").to_integer() == 1) ||
27+
(schema.at("multipleOf").is_real() &&
28+
schema.at("multipleOf").to_real() == 1.0));
29+
}
30+
31+
auto transform(JSON &schema) const -> void override {
32+
schema.erase("multipleOf");
33+
}
34+
};

test/alterschema/alterschema_lint_2019_09_test.cc

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,3 +1680,73 @@ TEST(AlterSchema_lint_2019_09, unnecessary_allof_ref_wrapper_5) {
16801680

16811681
EXPECT_EQ(document, expected);
16821682
}
1683+
1684+
TEST(AlterSchema_lint_2019_09, multiple_of_default_1) {
1685+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
1686+
"$schema": "https://json-schema.org/draft/2019-09/schema",
1687+
"type": "integer",
1688+
"multipleOf": 1
1689+
})JSON");
1690+
1691+
LINT_AND_FIX_FOR_READABILITY(document);
1692+
1693+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
1694+
"$schema": "https://json-schema.org/draft/2019-09/schema",
1695+
"type": "integer"
1696+
})JSON");
1697+
1698+
EXPECT_EQ(document, expected);
1699+
}
1700+
1701+
TEST(AlterSchema_lint_2019_09, multiple_of_default_2) {
1702+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
1703+
"$schema": "https://json-schema.org/draft/2019-09/schema",
1704+
"multipleOf": 1.0,
1705+
"contains": { "type": "string" }
1706+
})JSON");
1707+
1708+
LINT_AND_FIX_FOR_READABILITY(document);
1709+
1710+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
1711+
"$schema": "https://json-schema.org/draft/2019-09/schema",
1712+
"contains": { "type": "string" }
1713+
})JSON");
1714+
1715+
EXPECT_EQ(document, expected);
1716+
}
1717+
1718+
TEST(AlterSchema_lint_2019_09, multiple_of_default_3) {
1719+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
1720+
"$schema": "https://json-schema.org/draft/2019-09/schema",
1721+
"multipleOf": 1.2,
1722+
"type": "number"
1723+
})JSON");
1724+
1725+
LINT_AND_FIX_FOR_READABILITY(document);
1726+
1727+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
1728+
"$schema": "https://json-schema.org/draft/2019-09/schema",
1729+
"multipleOf": 1.2,
1730+
"type": "number"
1731+
})JSON");
1732+
1733+
EXPECT_EQ(document, expected);
1734+
}
1735+
1736+
TEST(AlterSchema_lint_2019_09, multiple_of_default_no_change_1) {
1737+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
1738+
"$schema": "https://json-schema.org/draft/2019-09/schema",
1739+
"type": "integer",
1740+
"multipleOf": 2
1741+
})JSON");
1742+
1743+
LINT_AND_FIX_FOR_READABILITY(document);
1744+
1745+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
1746+
"$schema": "https://json-schema.org/draft/2019-09/schema",
1747+
"type": "integer",
1748+
"multipleOf": 2
1749+
})JSON");
1750+
1751+
EXPECT_EQ(document, expected);
1752+
}

test/alterschema/alterschema_lint_2020_12_test.cc

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1842,3 +1842,73 @@ TEST(AlterSchema_lint_2020_12, unnecessary_allof_ref_wrapper_5) {
18421842

18431843
EXPECT_EQ(document, expected);
18441844
}
1845+
1846+
TEST(AlterSchema_lint_2020_12, multiple_of_default_1) {
1847+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
1848+
"$schema": "https://json-schema.org/draft/2020-12/schema",
1849+
"type": "integer",
1850+
"multipleOf": 1
1851+
})JSON");
1852+
1853+
LINT_AND_FIX_FOR_READABILITY(document);
1854+
1855+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
1856+
"$schema": "https://json-schema.org/draft/2020-12/schema",
1857+
"type": "integer"
1858+
})JSON");
1859+
1860+
EXPECT_EQ(document, expected);
1861+
}
1862+
1863+
TEST(AlterSchema_lint_2020_12, multiple_of_default_2) {
1864+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
1865+
"$schema": "https://json-schema.org/draft/2020-12/schema",
1866+
"multipleOf": 1.0,
1867+
"unevaluatedProperties": false
1868+
})JSON");
1869+
1870+
LINT_AND_FIX_FOR_READABILITY(document);
1871+
1872+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
1873+
"$schema": "https://json-schema.org/draft/2020-12/schema",
1874+
"unevaluatedProperties": false
1875+
})JSON");
1876+
1877+
EXPECT_EQ(document, expected);
1878+
}
1879+
1880+
TEST(AlterSchema_lint_2020_12, multiple_of_default_no_change_string_value) {
1881+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
1882+
"$schema": "https://json-schema.org/draft/2020-12/schema",
1883+
"type": "integer",
1884+
"multipleOf": "1"
1885+
})JSON");
1886+
1887+
LINT_AND_FIX_FOR_READABILITY(document);
1888+
1889+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
1890+
"$schema": "https://json-schema.org/draft/2020-12/schema",
1891+
"type": "integer",
1892+
"multipleOf": "1"
1893+
})JSON");
1894+
1895+
EXPECT_EQ(document, expected);
1896+
}
1897+
1898+
TEST(AlterSchema_lint_2020_12, multiple_of_default_no_change_numeric_value) {
1899+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
1900+
"$schema": "https://json-schema.org/draft/2020-12/schema",
1901+
"type": "number",
1902+
"multipleOf": 2.5
1903+
})JSON");
1904+
1905+
LINT_AND_FIX_FOR_READABILITY(document);
1906+
1907+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
1908+
"$schema": "https://json-schema.org/draft/2020-12/schema",
1909+
"type": "number",
1910+
"multipleOf": 2.5
1911+
})JSON");
1912+
1913+
EXPECT_EQ(document, expected);
1914+
}

test/alterschema/alterschema_lint_draft4_test.cc

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,3 +761,126 @@ TEST(AlterSchema_lint_draft4, unnecessary_allof_ref_wrapper_1) {
761761

762762
EXPECT_EQ(document, expected);
763763
}
764+
765+
TEST(AlterSchema_lint_draft4, multiple_of_default_1) {
766+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
767+
"$schema": "http://json-schema.org/draft-04/schema#",
768+
"type": "integer",
769+
"multipleOf": 1
770+
})JSON");
771+
772+
LINT_AND_FIX_FOR_READABILITY(document);
773+
774+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
775+
"$schema": "http://json-schema.org/draft-04/schema#",
776+
"type": "integer"
777+
})JSON");
778+
779+
EXPECT_EQ(document, expected);
780+
}
781+
782+
TEST(AlterSchema_lint_draft4, multiple_of_default_2) {
783+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
784+
"$schema": "http://json-schema.org/draft-04/schema#",
785+
"type": "number",
786+
"multipleOf": 1.0
787+
})JSON");
788+
789+
LINT_AND_FIX_FOR_READABILITY(document);
790+
791+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
792+
"$schema": "http://json-schema.org/draft-04/schema#",
793+
"type": "number"
794+
})JSON");
795+
796+
EXPECT_EQ(document, expected);
797+
}
798+
799+
TEST(AlterSchema_lint_draft4, multiple_of_default_3) {
800+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
801+
"$schema": "http://json-schema.org/draft-04/schema#",
802+
"multipleOf": 1,
803+
"minimum": 0
804+
})JSON");
805+
806+
LINT_AND_FIX_FOR_READABILITY(document);
807+
808+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
809+
"$schema": "http://json-schema.org/draft-04/schema#",
810+
"minimum": 0
811+
})JSON");
812+
813+
EXPECT_EQ(document, expected);
814+
}
815+
816+
TEST(AlterSchema_lint_draft4, multiple_of_default_no_change_1) {
817+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
818+
"$schema": "http://json-schema.org/draft-04/schema#",
819+
"type": "integer",
820+
"multipleOf": 2
821+
})JSON");
822+
823+
LINT_AND_FIX_FOR_READABILITY(document);
824+
825+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
826+
"$schema": "http://json-schema.org/draft-04/schema#",
827+
"type": "integer",
828+
"multipleOf": 2
829+
})JSON");
830+
831+
EXPECT_EQ(document, expected);
832+
}
833+
834+
TEST(AlterSchema_lint_draft4, multiple_of_default_no_change_2) {
835+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
836+
"$schema": "http://json-schema.org/draft-04/schema#",
837+
"type": "number",
838+
"multipleOf": 0.5
839+
})JSON");
840+
841+
LINT_AND_FIX_FOR_READABILITY(document);
842+
843+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
844+
"$schema": "http://json-schema.org/draft-04/schema#",
845+
"type": "number",
846+
"multipleOf": 0.5
847+
})JSON");
848+
849+
EXPECT_EQ(document, expected);
850+
}
851+
852+
TEST(AlterSchema_lint_draft4, multiple_of_default_no_change_zero) {
853+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
854+
"$schema": "http://json-schema.org/draft-04/schema#",
855+
"type": "number",
856+
"multipleOf": 0
857+
})JSON");
858+
859+
LINT_AND_FIX_FOR_READABILITY(document);
860+
861+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
862+
"$schema": "http://json-schema.org/draft-04/schema#",
863+
"type": "number",
864+
"multipleOf": 0
865+
})JSON");
866+
867+
EXPECT_EQ(document, expected);
868+
}
869+
870+
TEST(AlterSchema_lint_draft4, multiple_of_default_no_change_negative) {
871+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
872+
"$schema": "http://json-schema.org/draft-04/schema#",
873+
"type": "number",
874+
"multipleOf": -1
875+
})JSON");
876+
877+
LINT_AND_FIX_FOR_READABILITY(document);
878+
879+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
880+
"$schema": "http://json-schema.org/draft-04/schema#",
881+
"type": "number",
882+
"multipleOf": -1
883+
})JSON");
884+
885+
EXPECT_EQ(document, expected);
886+
}

test/alterschema/alterschema_lint_draft6_test.cc

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,3 +1112,37 @@ TEST(AlterSchema_lint_draft6, unnecessary_allof_ref_wrapper_1) {
11121112

11131113
EXPECT_EQ(document, expected);
11141114
}
1115+
1116+
TEST(AlterSchema_lint_draft6, multiple_of_default_1) {
1117+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
1118+
"$schema": "http://json-schema.org/draft-06/schema#",
1119+
"type": "integer",
1120+
"multipleOf": 1
1121+
})JSON");
1122+
1123+
LINT_AND_FIX_FOR_READABILITY(document);
1124+
1125+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
1126+
"$schema": "http://json-schema.org/draft-06/schema#",
1127+
"type": "integer"
1128+
})JSON");
1129+
1130+
EXPECT_EQ(document, expected);
1131+
}
1132+
1133+
TEST(AlterSchema_lint_draft6, multiple_of_default_2) {
1134+
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
1135+
"$schema": "http://json-schema.org/draft-06/schema#",
1136+
"multipleOf": 1.0,
1137+
"maximum": 100
1138+
})JSON");
1139+
1140+
LINT_AND_FIX_FOR_READABILITY(document);
1141+
1142+
const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
1143+
"$schema": "http://json-schema.org/draft-06/schema#",
1144+
"maximum": 100
1145+
})JSON");
1146+
1147+
EXPECT_EQ(document, expected);
1148+
}

0 commit comments

Comments
 (0)