Skip to content

Commit b636d9a

Browse files
fix: Merging of missing values in json (#40)
Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
1 parent cb116b8 commit b636d9a

File tree

4 files changed

+208
-18
lines changed

4 files changed

+208
-18
lines changed

src/nitpick/plugins/json.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,33 @@ def report(self, violation: ViolationEnum, blender: JsonDict, change: BaseDoc |
8787
if not change:
8888
return
8989
if blender:
90-
blender.update(flatten_quotes(change.as_object))
90+
if violation == SharedViolations.MISSING_VALUES:
91+
self._update_missing_values(blender, change)
92+
else:
93+
blender.update(flatten_quotes(change.as_object))
9194
self.dirty = True
9295
yield self.reporter.make_fuss(violation, change.reformatted, prefix="", fixed=self.autofix)
9396

97+
def _update_missing_values(self, blender: JsonDict, change: BaseDoc | None): # pylint: disable=no-self-use
98+
"""Update the missing values in the blender, depending on the type of each key's value."""
99+
flattened_changes = flatten_quotes(change.as_object)
100+
for key, change_value in flattened_changes.items():
101+
if key in blender:
102+
blender_value = blender[key]
103+
# Check if both are of the same type
104+
if isinstance(blender_value, list) and isinstance(change_value, list):
105+
# For arrays, append values without duplicates
106+
blender[key].extend(change_value)
107+
elif isinstance(blender_value, dict) and isinstance(change_value, dict):
108+
# For dictionaries, update recursively
109+
blender_value.update(change_value)
110+
else:
111+
# For different types or scalar values, simply replace
112+
blender[key] = change_value
113+
else:
114+
# Key doesn't exist in blender, simply add it
115+
blender[key] = change_value
116+
94117
@property
95118
def initial_contents(self) -> str:
96119
"""Suggest the initial content for this missing file."""

tests/test_json.py

Lines changed: 181 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,8 @@ def test_suggest_initial_contents(tmp_path, datadir):
1717
style = ["package-json"]
1818
"""
1919
).api_check_then_fix(
20-
Fuss(
21-
True,
22-
JAVASCRIPT_PACKAGE_JSON,
23-
341,
24-
" was not found. Create it with this content:",
25-
expected_package_json,
26-
)
27-
).assert_file_contents(
28-
JAVASCRIPT_PACKAGE_JSON, expected_package_json
29-
).api_check_then_fix()
20+
Fuss(True, JAVASCRIPT_PACKAGE_JSON, 341, " was not found. Create it with this content:", expected_package_json)
21+
).assert_file_contents(JAVASCRIPT_PACKAGE_JSON, expected_package_json).api_check_then_fix()
3022

3123

3224
def test_missing_different_values_with_contains_json_with_contains_keys(tmp_path, datadir):
@@ -70,9 +62,7 @@ def test_missing_different_values_with_contains_json_with_contains_keys(tmp_path
7062
}
7163
""",
7264
),
73-
).assert_file_contents(
74-
JAVASCRIPT_PACKAGE_JSON, expected_package_json
75-
).api_check_then_fix()
65+
).assert_file_contents(JAVASCRIPT_PACKAGE_JSON, expected_package_json).api_check_then_fix()
7666

7767

7868
def test_missing_different_values_with_contains_json_without_contains_keys(tmp_path, datadir):
@@ -124,9 +114,184 @@ def test_missing_different_values_with_contains_json_without_contains_keys(tmp_p
124114
}
125115
""",
126116
),
127-
).assert_file_contents(
128-
"my.json", datadir / "3-expected.json"
129-
).api_check_then_fix()
117+
).assert_file_contents("my.json", datadir / "3-expected.json").api_check_then_fix()
118+
119+
120+
def test_missing_value_with_doubly_single_quoted_dotted_key_should_be_added_and_kept_flatten(tmp_path):
121+
"""A dotted key wrapped in double single quotes should be added and preserved without getting unflatten."""
122+
ProjectMock(tmp_path).style(
123+
"""
124+
["my.json".contains_json]
125+
"''some.dotted.key''" = \"\"\"
126+
"some_value"
127+
\"\"\"
128+
"""
129+
).save_file("my.json", """{ "project_name": "my-project" }""").api_fix().assert_file_contents(
130+
"my.json",
131+
"""
132+
{
133+
"project_name": "my-project",
134+
"some.dotted.key": "some_value"
135+
}
136+
""",
137+
)
138+
139+
140+
def test_missing_value_with_dotted_key_should_be_added_and_unflatten(tmp_path):
141+
"""A dotted key should be added and unflatten."""
142+
ProjectMock(tmp_path).style(
143+
"""
144+
["my.json".contains_json]
145+
"some.dotted.key" = \"\"\"
146+
"some_value"
147+
\"\"\"
148+
"""
149+
).save_file("my.json", """{ "project_name": "my-project" }""").api_fix().assert_file_contents(
150+
"my.json",
151+
"""
152+
{
153+
"project_name": "my-project",
154+
"some": {
155+
"dotted": {
156+
"key": "some_value"
157+
}
158+
}
159+
}
160+
""",
161+
)
162+
163+
164+
def test_missing_values_in_array_should_be_appended(tmp_path):
165+
"""Test missing values in array should be appended."""
166+
ProjectMock(tmp_path).style(
167+
"""
168+
["my.json".contains_json]
169+
"some_key" = \"\"\"
170+
[
171+
"val_1",
172+
"val_2",
173+
"val_3"
174+
]
175+
\"\"\"
176+
"""
177+
).save_file(
178+
"my.json",
179+
"""
180+
{
181+
"project_name": "my-project",
182+
"some_key": [
183+
"val_1"
184+
]
185+
}
186+
""",
187+
).api_fix().assert_file_contents(
188+
"my.json",
189+
"""
190+
{
191+
"project_name": "my-project",
192+
"some_key": [
193+
"val_1",
194+
"val_2",
195+
"val_3"
196+
]
197+
}
198+
""",
199+
)
200+
201+
202+
def test_different_values_in_dict_should_be_updated(tmp_path):
203+
"""Test different values in dict should be appended."""
204+
ProjectMock(tmp_path).style(
205+
"""
206+
["my.json".contains_json]
207+
"some_key" = \"\"\"
208+
{
209+
"key_1": "val_1_updated",
210+
"key_2": "val_2",
211+
"key_3": {
212+
"k": "v"
213+
}
214+
}
215+
\"\"\"
216+
"""
217+
).save_file(
218+
"my.json",
219+
"""
220+
{
221+
"project_name": "my-project",
222+
"some_key": {
223+
"key_1": "val_1"
224+
}
225+
}
226+
""",
227+
).api_fix().assert_file_contents(
228+
"my.json",
229+
"""
230+
{
231+
"project_name": "my-project",
232+
"some_key": {
233+
"key_1": "val_1_updated",
234+
"key_2": "val_2",
235+
"key_3": {
236+
"k": "v"
237+
}
238+
}
239+
}
240+
""",
241+
)
242+
243+
244+
def test_missing_values_in_nested_dict_and_array_should_also_be_updated_and_appended(tmp_path):
245+
"""Test missing values in nested dict and array should also be updated and appended."""
246+
ProjectMock(tmp_path).style(
247+
"""
248+
["my.json".contains_json]
249+
"some_key" = \"\"\"
250+
{
251+
"key_dict": {
252+
"k": "v"
253+
},
254+
"key_array": [
255+
"val_2",
256+
"val_3"
257+
]
258+
}
259+
\"\"\"
260+
"""
261+
).save_file(
262+
"my.json",
263+
"""
264+
{
265+
"project_name": "my-project",
266+
"some_key": {
267+
"key_dict": {
268+
"existing_k": "existing_v"
269+
},
270+
"key_array": [
271+
"val_1"
272+
]
273+
}
274+
}
275+
""",
276+
).api_fix().assert_file_contents(
277+
"my.json",
278+
"""
279+
{
280+
"project_name": "my-project",
281+
"some_key": {
282+
"key_array": [
283+
"val_1",
284+
"val_2",
285+
"val_3"
286+
],
287+
"key_dict": {
288+
"existing_k": "existing_v",
289+
"k": "v"
290+
}
291+
}
292+
}
293+
""",
294+
)
130295

131296

132297
def test_invalid_json(tmp_path, datadir):

tests/test_json/2-actual-package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
"version": "0.0.1",
44
"something": "else",
55
"commitlint": {
6-
"extends": ["wrong-plugin-should-be-replaced", "another-wrong-plugin"]
6+
"extends": ["some-plugin-should-be-kept", "another-plugin-should-be-kept"]
77
}
88
}

tests/test_json/2-expected-package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"commitlint": {
33
"extends": [
4+
"some-plugin-should-be-kept",
5+
"another-plugin-should-be-kept",
46
"@commitlint/config-conventional"
57
]
68
},

0 commit comments

Comments
 (0)