diff --git a/setup.cfg b/setup.cfg index 6c28b90..99521e4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,6 +52,7 @@ flake8.extension = W = flake8.plugins.pycodestyle:pycodestyle_physical flake8.report = default = flake8.formatting.default:Default + github = flake8.formatting.default:GitHub pylint = flake8.formatting.default:Pylint quiet-filename = flake8.formatting.default:FilenameOnly quiet-nothing = flake8.formatting.default:Nothing diff --git a/src/flake8/formatting/default.py b/src/flake8/formatting/default.py index b5d08ff..7ee93bd 100644 --- a/src/flake8/formatting/default.py +++ b/src/flake8/formatting/default.py @@ -78,6 +78,16 @@ class Pylint(SimpleFormatter): error_format = "%(path)s:%(row)d: [%(code)s] %(text)s" +class GitHub(SimpleFormatter): + """GitHub formatter for Flake8.""" + + error_format = ( + "::error title=Flake8 %(code)s,file=%(path)s," + "line=%(row)d,col=%(col)d,endLine=%(row)d,endColumn=%(col)d" + "::%(code)s %(text)s" + ) + + class FilenameOnly(SimpleFormatter): """Only print filenames, e.g., flake8 -q.""" diff --git a/tests/integration/test_main.py b/tests/integration/test_main.py index 68b93cb..311e2fe 100644 --- a/tests/integration/test_main.py +++ b/tests/integration/test_main.py @@ -3,6 +3,7 @@ from __future__ import annotations import json import os +import re import sys from unittest import mock @@ -396,5 +397,8 @@ def test_format_option_help(capsys): cli.main(["--help"]) out, err = capsys.readouterr() - assert "(default, pylint, quiet-filename, quiet-nothing)" in out + assert ( + "(default, github, pylint, quiet-filename, quiet-nothing)" + in re.sub(re.compile(r"\n\s*"), "", out) # noqa: E501 + ) assert err == "" diff --git a/tests/unit/plugins/reporter_test.py b/tests/unit/plugins/reporter_test.py index ff4d97f..29a242a 100644 --- a/tests/unit/plugins/reporter_test.py +++ b/tests/unit/plugins/reporter_test.py @@ -8,6 +8,7 @@ import pytest from flake8.formatting import default from flake8.plugins import finder from flake8.plugins import reporter +from flake8.violation import Violation def _opts(**kwargs): @@ -17,6 +18,18 @@ def _opts(**kwargs): return argparse.Namespace(**kwargs) +@pytest.fixture +def violation(): + return Violation( + code="E501", + filename="test/test1.py", + line_number=1, + column_number=2, + text="line too long (124 > 79 characters)", + physical_line=None, + ) + + @pytest.fixture def reporters(): def _plugin(name, cls): @@ -35,6 +48,7 @@ def reporters(): return { "default": _plugin("default", default.Default), "pylint": _plugin("pylint", default.Pylint), + "github": _plugin("github", default.GitHub), "quiet-filename": _plugin("quiet-filename", default.FilenameOnly), "quiet-nothing": _plugin("quiet-nothing", default.Nothing), } @@ -57,9 +71,27 @@ def test_make_formatter_very_quiet(reporters, quiet): assert isinstance(ret, default.Nothing) -def test_make_formatter_custom(reporters): - ret = reporter.make(reporters, _opts(format="pylint")) - assert isinstance(ret, default.Pylint) +@pytest.mark.parametrize( + "format_input, expected_formatter_class, error_str", + [ + ( + "pylint", + default.Pylint, + "test/test1.py:1: [E501] line too long (124 > 79 characters)", + ), + ( + "github", + default.GitHub, + "::error title=Flake8 E501,file=test/test1.py,line=1,col=2,endLine=1,endColumn=2::E501 line too long (124 > 79 characters)", # noqa: E501 + ), + ], +) +def test_make_formatter_custom( + reporters, violation, format_input, expected_formatter_class, error_str +): + ret = reporter.make(reporters, _opts(format=format_input)) + assert isinstance(ret, expected_formatter_class) + assert ret.format(violation) == error_str def test_make_formatter_format_string(reporters, caplog):