Skip to content

Commit 496e23d

Browse files
committed
for pretty-format-json add option: --empty-object-with-newline
1 parent 61b5f7e commit 496e23d

File tree

4 files changed

+82
-1
lines changed

4 files changed

+82
-1
lines changed

pre_commit_hooks/pretty_format_json.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,23 @@
33
import argparse
44
import json
55
import sys
6+
import re
7+
68
from difflib import unified_diff
79
from typing import Mapping
810
from typing import Sequence
911

12+
def _insert_linebreaks(json_str) -> str:
13+
# (?P<spaces>\s*) seems to capture the \n. Hence, there is no need for it in the substitution string
14+
return re.sub(r'\n(?P<spaces>\s*)(?P<json_key>.*): {}(?P<delim>,??)', '\n\g<spaces>\g<json_key>: {\n\g<spaces>}\g<delim>', json_str)
1015

1116
def _get_pretty_format(
1217
contents: str,
1318
indent: str,
1419
ensure_ascii: bool = True,
1520
sort_keys: bool = True,
1621
top_keys: Sequence[str] = (),
22+
empty_object_with_newline: bool = False,
1723
) -> str:
1824
def pairs_first(pairs: Sequence[tuple[str, str]]) -> Mapping[str, str]:
1925
before = [pair for pair in pairs if pair[0] in top_keys]
@@ -27,6 +33,8 @@ def pairs_first(pairs: Sequence[tuple[str, str]]) -> Mapping[str, str]:
2733
indent=indent,
2834
ensure_ascii=ensure_ascii,
2935
)
36+
if empty_object_with_newline:
37+
json_pretty = _insert_linebreaks(json_pretty)
3038
return f'{json_pretty}\n'
3139

3240

@@ -96,6 +104,13 @@ def main(argv: Sequence[str] | None = None) -> int:
96104
default=[],
97105
help='Ordered list of keys to keep at the top of JSON hashes',
98106
)
107+
parser.add_argument(
108+
'--empty-object-with-newline',
109+
action='store_true',
110+
dest='empty_object_with_newline',
111+
default=False,
112+
help='Format empty JSON objects to have a linebreak, also activates --no-sort-keys',
113+
)
99114
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
100115
args = parser.parse_args(argv)
101116

@@ -106,9 +121,12 @@ def main(argv: Sequence[str] | None = None) -> int:
106121
contents = f.read()
107122

108123
try:
124+
if args.empty_object_with_newline:
125+
args.no_sort_keys = True
109126
pretty_contents = _get_pretty_format(
110127
contents, args.indent, ensure_ascii=not args.no_ensure_ascii,
111128
sort_keys=not args.no_sort_keys, top_keys=args.top_keys,
129+
empty_object_with_newline=args.empty_object_with_newline
112130
)
113131
except ValueError:
114132
print(
@@ -118,13 +136,15 @@ def main(argv: Sequence[str] | None = None) -> int:
118136
return 1
119137

120138
if contents != pretty_contents:
139+
status = 1
121140
if args.autofix:
122141
_autofix(json_file, pretty_contents)
142+
if args.empty_object_with_newline:
143+
status = 0
123144
else:
124145
diff_output = get_diff(contents, pretty_contents, json_file)
125146
sys.stdout.buffer.write(diff_output.encode())
126147

127-
status = 1
128148

129149
return status
130150

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"empty": {
3+
},
4+
"inhabited": {
5+
"person": {
6+
"name": "Roger",
7+
"nested_empty_with_comma": {
8+
},
9+
"array": [
10+
{
11+
"nested_empty_in_array": {
12+
}
13+
}
14+
]
15+
}
16+
}
17+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"empty": {},
3+
"inhabited": {
4+
"person": {
5+
"name": "Roger",
6+
"nested_empty_with_comma": {},
7+
"array": [
8+
{
9+
"nested_empty_in_array": {}
10+
}
11+
]
12+
}
13+
}
14+
}

tests/pretty_format_json_test.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import shutil
55

66
import pytest
7+
import filecmp
78

89
from pre_commit_hooks.pretty_format_json import main
910
from pre_commit_hooks.pretty_format_json import parse_num_to_int
@@ -137,3 +138,32 @@ def test_diffing_output(capsys):
137138
assert actual_retval == expected_retval
138139
assert actual_out == expected_out
139140
assert actual_err == ''
141+
142+
143+
def test_empty_object_with_newline(tmpdir):
144+
# same line objects shoud trigger with --empty-object-with-newline switch
145+
sameline = get_resource_path("empty_object_json_sameline.json")
146+
ret = main(["--empty-object-with-newline", str(sameline)])
147+
assert ret == 1
148+
149+
multiline = get_resource_path("empty_object_json_multiline.json")
150+
to_be_formatted_sameline = tmpdir.join(
151+
"not_pretty_formatted_empty_object_json_sameline.json"
152+
)
153+
shutil.copyfile(str(sameline), str(to_be_formatted_sameline))
154+
155+
# file has empty object with newline => expect fail with default settings
156+
ret = main([str(multiline)])
157+
assert ret == 1
158+
159+
# now launch the autofix with empty object with newline support on that file
160+
ret = main(
161+
["--autofix", "--empty-object-with-newline", str(to_be_formatted_sameline)]
162+
)
163+
# it should have formatted it and don't raise an error code
164+
assert ret == 0
165+
166+
# file was formatted (shouldn't trigger linter with --empty-object-with-newline switch)
167+
ret = main(["--empty-object-with-newline", str(to_be_formatted_sameline)])
168+
assert ret == 0
169+
assert filecmp.cmp(to_be_formatted_sameline, multiline)

0 commit comments

Comments
 (0)