Skip to content

Commit e6f208d

Browse files
tomasr8asottile
authored andcommitted
Pretty-print JSON output
1 parent 4bc2e03 commit e6f208d

File tree

7 files changed

+179
-9
lines changed

7 files changed

+179
-9
lines changed

.github/workflows/main.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ jobs:
1717
- os: ubuntu-latest
1818
python: 3.9
1919
toxenv: pre-commit
20+
- os: ubuntu-latest
21+
python: 3.9
22+
toxenv: py
2023
runs-on: ${{ matrix.os }}
2124
steps:
2225
- uses: actions/checkout@v2

README.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
Flake8-JSON
22
===========
33

4-
This is a plugin for Flake8 that will format the output as JSON. By default,
5-
the output is **not** pretty-printed. We would love to add that as a separate
6-
formatter option, though.
4+
This is a plugin for Flake8 that will format the output as JSON. The output of
5+
the default JSON formatter is not pretty-printed. If you'd like the output to
6+
be pretty-printed, use json-pretty instead.
77

88
CodeClimate support is also offered through this plugin as of v20.12.0
99

@@ -23,6 +23,10 @@ Usage
2323
2424
flake8 --format=json ...
2525
26+
.. code-block:: bash
27+
28+
flake8 --format=json-pretty ...
29+
2630
.. code-block:: bash
2731
2832
flake8 --format=codeclimate ...

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ where = src/
3434
[options.entry_points]
3535
flake8.report =
3636
json = flake8_json_reporter.reporters:DefaultJSON
37+
json-pretty = flake8_json_reporter.reporters:FormattedJSON
3738
codeclimate = flake8_json_reporter.reporters:CodeClimateJSON
3839

3940
[bdist_wheel]

src/flake8_json_reporter/reporters.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Module containing all of the JSON reporters for Flake8."""
22
import hashlib
33
import json
4+
import textwrap
45

56
from flake8.formatting import base
67

@@ -70,6 +71,48 @@ def format(self, violation):
7071
self.reported_errors_count += 1
7172

7273

74+
def _indent(text, indent):
75+
return textwrap.indent(text, " " * indent)
76+
77+
78+
class FormattedJSON(DefaultJSON):
79+
"""Pretty-printing JSON formatter."""
80+
81+
def stop(self):
82+
"""Override the default to finish printing JSON."""
83+
if self.files_reported_count > 0:
84+
self.write_line("\n")
85+
self.write_line("}\n")
86+
87+
def beginning(self, filename):
88+
"""We're starting a new file."""
89+
if self.files_reported_count > 0:
90+
self.write_line(",\n")
91+
self.write_line(f" {json.dumps(filename)}: [")
92+
else:
93+
self.write_line(f"\n {json.dumps(filename)}: [")
94+
self.reported_errors_count = 0
95+
96+
def finished(self, filename):
97+
"""We've finished processing a file."""
98+
self.files_reported_count += 1
99+
if self.reported_errors_count > 0:
100+
self.write_line("\n")
101+
self.write_line(" ]")
102+
else:
103+
self.write_line("]")
104+
105+
def format(self, violation):
106+
"""Format a violation."""
107+
formatted = json.dumps(self.dictionary_from(violation), indent=2)
108+
formatted = _indent(formatted, indent=4)
109+
if self.reported_errors_count > 0:
110+
self.write_line(",")
111+
self.write_line("\n")
112+
self.write_line(formatted)
113+
self.reported_errors_count += 1
114+
115+
73116
class CodeClimateJSON(base.BaseFormatter):
74117
"""Formatter for CodeClimate JSON."""
75118

tests/__init__.py

Whitespace-only changes.

tests/flake8_json_reporter_test.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
from argparse import Namespace
2+
3+
import pytest
4+
from flake8.violation import Violation
5+
6+
from flake8_json_reporter.reporters import FormattedJSON
7+
8+
9+
@pytest.fixture
10+
def formatter():
11+
"""Return a ``FormattedJSON`` instance."""
12+
options = Namespace(output_file=None, color=False, tee=False)
13+
formatter = FormattedJSON(options)
14+
return formatter
15+
16+
17+
@pytest.fixture
18+
def violation():
19+
return Violation(
20+
code="E222",
21+
filename="main.py",
22+
line_number=42,
23+
column_number=4,
24+
text="multiple spaces after operator",
25+
physical_line="x = 1",
26+
)
27+
28+
29+
def run(formatter, violations):
30+
formatter.start()
31+
for filename in violations:
32+
formatter.beginning(filename)
33+
for violation in violations[filename]:
34+
formatter.format(violation)
35+
formatter.finished(filename)
36+
formatter.stop()
37+
38+
39+
def test_no_files(capsys, formatter):
40+
run(formatter, {})
41+
stdout, _ = capsys.readouterr()
42+
assert stdout == "{}\n"
43+
44+
45+
def test_single_file_no_violations(capsys, formatter):
46+
run(formatter, {"main.py": []})
47+
stdout, _ = capsys.readouterr()
48+
expected = """\
49+
{
50+
"main.py": []
51+
}
52+
"""
53+
assert stdout == expected
54+
55+
56+
def test_multiple_files_no_violations(capsys, formatter):
57+
run(formatter, {"main.py": [], "__init__.py": []})
58+
stdout, _ = capsys.readouterr()
59+
expected = """\
60+
{
61+
"main.py": [],
62+
"__init__.py": []
63+
}
64+
"""
65+
assert stdout == expected
66+
67+
68+
def test_single_file_single_violation(capsys, formatter, violation):
69+
run(formatter, {"main.py": [violation]})
70+
stdout, _ = capsys.readouterr()
71+
expected = """\
72+
{
73+
"main.py": [
74+
{
75+
"code": "E222",
76+
"filename": "main.py",
77+
"line_number": 42,
78+
"column_number": 4,
79+
"text": "multiple spaces after operator",
80+
"physical_line": "x = 1"
81+
}
82+
]
83+
}
84+
"""
85+
assert stdout == expected
86+
87+
88+
def test_single_file_multiple_violations(capsys, formatter, violation):
89+
run(formatter, {"main.py": [violation] * 3})
90+
stdout, _ = capsys.readouterr()
91+
expected = """\
92+
{
93+
"main.py": [
94+
{
95+
"code": "E222",
96+
"filename": "main.py",
97+
"line_number": 42,
98+
"column_number": 4,
99+
"text": "multiple spaces after operator",
100+
"physical_line": "x = 1"
101+
},
102+
{
103+
"code": "E222",
104+
"filename": "main.py",
105+
"line_number": 42,
106+
"column_number": 4,
107+
"text": "multiple spaces after operator",
108+
"physical_line": "x = 1"
109+
},
110+
{
111+
"code": "E222",
112+
"filename": "main.py",
113+
"line_number": 42,
114+
"column_number": 4,
115+
"text": "multiple spaces after operator",
116+
"physical_line": "x = 1"
117+
}
118+
]
119+
}
120+
"""
121+
assert stdout == expected

tox.ini

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
[tox]
22
minversion = 2.3.1
3-
envlist = flake8,pre-commit
3+
envlist = py,flake8,pre-commit
44

55
[testenv]
66
deps =
7+
flake8
8+
pytest
79
commands =
8-
9-
[testenv:venv]
10-
deps =
11-
.
12-
commands = {posargs}
10+
pytest tests {posargs}
1311

1412
# Linters
1513
[testenv:flake8]

0 commit comments

Comments
 (0)