diff --git a/relint/__main__.py b/relint/__main__.py index afb162f..ed831d1 100644 --- a/relint/__main__.py +++ b/relint/__main__.py @@ -1,4 +1,5 @@ import argparse +import os import subprocess # nosec import sys import warnings @@ -6,7 +7,13 @@ from rich.progress import track from relint.config import load_config -from relint.parse import lint_file, match_with_diff_changes, parse_diff, print_culprits +from relint.parse import ( + lint_file, + match_with_diff_changes, + parse_diff, + print_culprits, + print_github_actions_output, +) def parse_args(args=None): @@ -90,7 +97,11 @@ def main(args=None): changed_content = parse_diff(output) matches = match_with_diff_changes(changed_content, matches) - exit_code = print_culprits(matches, args) + GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS") == "true" + if GITHUB_ACTIONS: + exit_code = print_github_actions_output(matches, args) + else: + exit_code = print_culprits(matches, args) exit(exit_code) diff --git a/relint/parse.py b/relint/parse.py index 192f3b2..6009887 100644 --- a/relint/parse.py +++ b/relint/parse.py @@ -1,6 +1,7 @@ from __future__ import annotations import collections +import os try: import regex as re @@ -93,6 +94,25 @@ def split_diff_content_by_filename(output: str) -> {str: str}: return content_by_filename +def print_github_actions_output(matches, args): + exit_code = 0 + for filename, test, match, line_number in matches: + exit_code = test.error if exit_code == 0 else exit_code + start_line_no = match.string[: match.start()].count("\n") + 1 + end_line_no = match.string[: match.end()].count("\n") + 1 + col = match.start() - match.string.rfind("\n", 0, match.start()) + col_end = match.end() - match.string.rfind("\n", 0, match.end()) + + print( + f"::{'error' if test.error else 'warning'} file={filename}," + f"line={start_line_no},endLine={end_line_no},col={col},colEnd={col_end}," + f"title={test.name}::{test.hint}".replace( + "\n", "%0A" + ) + ) + return exit_code + + def print_culprits(matches, args): exit_code = 0 messages = [] diff --git a/tests/test_main.py b/tests/test_main.py index b8cfd80..850aa56 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -44,6 +44,25 @@ def test_main_execution_with_error(self, capsys, tmpdir, fixture_dir): assert "❱ 1 # FIXME do something" in out assert exc_info.value.code == 1 + def test_main_execution_with_error__github_workflow_output( + self, monkeypatch, capsys, tmpdir, fixture_dir + ): + monkeypatch.setenv("GITHUB_ACTIONS", "true") + with (fixture_dir / ".relint.yml").open() as fs: + config = fs.read() + tmpdir.join(".relint.yml").write(config) + tmpdir.join("dummy.py").write("# FIXME do something") + with tmpdir.as_cwd(): + with pytest.raises(SystemExit) as exc_info: + main(["dummy.py"]) + + out, _ = capsys.readouterr() + assert ( + "::error file=dummy.py,line=1,endLine=1,col=3,colEnd=8,title=No fixme (warning)::### This is a multiline hint%0AFix it right away!%0A%0AYou can use code blocks too, like Python:%0A%0A" + in out + ) + assert exc_info.value.code == 1 + @pytest.mark.parametrize("args", [[], ["--summarize"]]) def test_main_execution_without_hint(self, args, capsys, tmpdir, fixture_dir): with (fixture_dir / ".relint.yml").open() as fs: