From 496e23dea64790a2285f0e4c9b76b57fac00365e Mon Sep 17 00:00:00 2001 From: Andreas Scheucher Date: Sun, 13 Aug 2023 20:01:12 +0200 Subject: [PATCH] for pretty-format-json add option: --empty-object-with-newline --- pre_commit_hooks/pretty_format_json.py | 22 +++++++++++++- .../empty_object_json_multiline.json | 17 +++++++++++ .../resources/empty_object_json_sameline.json | 14 +++++++++ tests/pretty_format_json_test.py | 30 +++++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 testing/resources/empty_object_json_multiline.json create mode 100644 testing/resources/empty_object_json_sameline.json diff --git a/pre_commit_hooks/pretty_format_json.py b/pre_commit_hooks/pretty_format_json.py index 627a11c..d9956fe 100644 --- a/pre_commit_hooks/pretty_format_json.py +++ b/pre_commit_hooks/pretty_format_json.py @@ -3,10 +3,15 @@ from __future__ import annotations import argparse import json import sys +import re + from difflib import unified_diff from typing import Mapping from typing import Sequence +def _insert_linebreaks(json_str) -> str: + # (?P\s*) seems to capture the \n. Hence, there is no need for it in the substitution string + return re.sub(r'\n(?P\s*)(?P.*): {}(?P,??)', '\n\g\g: {\n\g}\g', json_str) def _get_pretty_format( contents: str, @@ -14,6 +19,7 @@ def _get_pretty_format( ensure_ascii: bool = True, sort_keys: bool = True, top_keys: Sequence[str] = (), + empty_object_with_newline: bool = False, ) -> str: def pairs_first(pairs: Sequence[tuple[str, str]]) -> Mapping[str, str]: before = [pair for pair in pairs if pair[0] in top_keys] @@ -27,6 +33,8 @@ def _get_pretty_format( indent=indent, ensure_ascii=ensure_ascii, ) + if empty_object_with_newline: + json_pretty = _insert_linebreaks(json_pretty) return f'{json_pretty}\n' @@ -96,6 +104,13 @@ def main(argv: Sequence[str] | None = None) -> int: default=[], help='Ordered list of keys to keep at the top of JSON hashes', ) + parser.add_argument( + '--empty-object-with-newline', + action='store_true', + dest='empty_object_with_newline', + default=False, + help='Format empty JSON objects to have a linebreak, also activates --no-sort-keys', + ) parser.add_argument('filenames', nargs='*', help='Filenames to fix') args = parser.parse_args(argv) @@ -106,9 +121,12 @@ def main(argv: Sequence[str] | None = None) -> int: contents = f.read() try: + if args.empty_object_with_newline: + args.no_sort_keys = True pretty_contents = _get_pretty_format( contents, args.indent, ensure_ascii=not args.no_ensure_ascii, sort_keys=not args.no_sort_keys, top_keys=args.top_keys, + empty_object_with_newline=args.empty_object_with_newline ) except ValueError: print( @@ -118,13 +136,15 @@ def main(argv: Sequence[str] | None = None) -> int: return 1 if contents != pretty_contents: + status = 1 if args.autofix: _autofix(json_file, pretty_contents) + if args.empty_object_with_newline: + status = 0 else: diff_output = get_diff(contents, pretty_contents, json_file) sys.stdout.buffer.write(diff_output.encode()) - status = 1 return status diff --git a/testing/resources/empty_object_json_multiline.json b/testing/resources/empty_object_json_multiline.json new file mode 100644 index 0000000..318434b --- /dev/null +++ b/testing/resources/empty_object_json_multiline.json @@ -0,0 +1,17 @@ +{ + "empty": { + }, + "inhabited": { + "person": { + "name": "Roger", + "nested_empty_with_comma": { + }, + "array": [ + { + "nested_empty_in_array": { + } + } + ] + } + } +} diff --git a/testing/resources/empty_object_json_sameline.json b/testing/resources/empty_object_json_sameline.json new file mode 100644 index 0000000..8e5b1e2 --- /dev/null +++ b/testing/resources/empty_object_json_sameline.json @@ -0,0 +1,14 @@ +{ + "empty": {}, + "inhabited": { + "person": { + "name": "Roger", + "nested_empty_with_comma": {}, + "array": [ + { + "nested_empty_in_array": {} + } + ] + } + } +} diff --git a/tests/pretty_format_json_test.py b/tests/pretty_format_json_test.py index 5ded724..31f3955 100644 --- a/tests/pretty_format_json_test.py +++ b/tests/pretty_format_json_test.py @@ -4,6 +4,7 @@ import os import shutil import pytest +import filecmp from pre_commit_hooks.pretty_format_json import main from pre_commit_hooks.pretty_format_json import parse_num_to_int @@ -137,3 +138,32 @@ def test_diffing_output(capsys): assert actual_retval == expected_retval assert actual_out == expected_out assert actual_err == '' + + +def test_empty_object_with_newline(tmpdir): + # same line objects shoud trigger with --empty-object-with-newline switch + sameline = get_resource_path("empty_object_json_sameline.json") + ret = main(["--empty-object-with-newline", str(sameline)]) + assert ret == 1 + + multiline = get_resource_path("empty_object_json_multiline.json") + to_be_formatted_sameline = tmpdir.join( + "not_pretty_formatted_empty_object_json_sameline.json" + ) + shutil.copyfile(str(sameline), str(to_be_formatted_sameline)) + + # file has empty object with newline => expect fail with default settings + ret = main([str(multiline)]) + assert ret == 1 + + # now launch the autofix with empty object with newline support on that file + ret = main( + ["--autofix", "--empty-object-with-newline", str(to_be_formatted_sameline)] + ) + # it should have formatted it and don't raise an error code + assert ret == 0 + + # file was formatted (shouldn't trigger linter with --empty-object-with-newline switch) + ret = main(["--empty-object-with-newline", str(to_be_formatted_sameline)]) + assert ret == 0 + assert filecmp.cmp(to_be_formatted_sameline, multiline)