From b330d8074527e49e490b710f72353518642e7ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez-Mondrag=C3=B3n?= Date: Fri, 13 Sep 2024 16:27:10 -0600 Subject: [PATCH] Add GitHub annotations format for `--output` --- mypy/error_formatter.py | 18 ++++++++++++- mypy/test/testoutput.py | 39 ++++++++++++++++++++++++++++ test-data/unit/outputgithub.test | 44 ++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 test-data/unit/outputgithub.test diff --git a/mypy/error_formatter.py b/mypy/error_formatter.py index ffc6b6747596..d03a6a366f5f 100644 --- a/mypy/error_formatter.py +++ b/mypy/error_formatter.py @@ -34,4 +34,20 @@ def report_error(self, error: "MypyError") -> str: ) -OUTPUT_CHOICES = {"json": JSONFormatter()} +class GitHubFormatter(ErrorFormatter): + """Formatter for GitHub Actions output format.""" + + def report_error(self, error: "MypyError") -> str: + """Prints out the errors as GitHub Actions annotations.""" + command = "error" if error.severity == "error" else "notice" + code = f"(`{error.errorcode.code}`) " if error.errorcode is not None else "" + + result = f"::{command} file={error.file_path},line={error.line},col={error.column}::{code}{error.message}" + if len(error.hints) > 0: + # TODO: Add hints to the output? + pass + + return result + + +OUTPUT_CHOICES = {"json": JSONFormatter(), "github": GitHubFormatter()} diff --git a/mypy/test/testoutput.py b/mypy/test/testoutput.py index 41f6881658c8..204bd61b2ab4 100644 --- a/mypy/test/testoutput.py +++ b/mypy/test/testoutput.py @@ -56,3 +56,42 @@ def test_output_json(testcase: DataDrivenTestCase) -> None: normalized_output = [line.replace(test_temp_dir + json_os_separator, "") for line in output] assert normalized_output == testcase.output + + +class OutputGitHubsuite(DataSuite): + files = ["outputgithub.test"] + + def run_case(self, testcase: DataDrivenTestCase) -> None: + test_output_github(testcase) + + +def test_output_github(testcase: DataDrivenTestCase) -> None: + """Run Mypy in a subprocess, and ensure that `--output=github` works as intended.""" + mypy_cmdline = ["--output=github"] + mypy_cmdline.append(f"--python-version={'.'.join(map(str, PYTHON3_VERSION))}") + + # Write the program to a file. + program_path = os.path.join(test_temp_dir, "main") + mypy_cmdline.append(program_path) + with open(program_path, "w", encoding="utf8") as file: + for s in testcase.input: + file.write(f"{s}\n") + + output = [] + # Type check the program. + out, err, returncode = api.run(mypy_cmdline) + # split lines, remove newlines, and remove directory of test case + for line in (out + err).rstrip("\n").splitlines(): + if line.startswith(test_temp_dir + os.sep): + output.append(line[len(test_temp_dir + os.sep) :].rstrip("\r\n")) + else: + output.append(line.rstrip("\r\n")) + + if returncode > 1: + output.append("!!! Mypy crashed !!!") + + # Remove temp file. + os.remove(program_path) + + normalized_output = [line.replace(test_temp_dir + os.sep, "") for line in output] + assert normalized_output == testcase.output diff --git a/test-data/unit/outputgithub.test b/test-data/unit/outputgithub.test new file mode 100644 index 000000000000..c2eeef837211 --- /dev/null +++ b/test-data/unit/outputgithub.test @@ -0,0 +1,44 @@ +-- Test cases for `--output=json`. +-- These cannot be run by the usual unit test runner because of the backslashes +-- in the output, which get normalized to forward slashes by the test suite on +-- Windows. + +[case testOutputGitHubNoIssues] +# flags: --output=github +def foo() -> None: + pass + +foo() +[out] + +[case testOutputGitHubSimple] +# flags: --output=github +def foo() -> None: + pass + +foo(1) +[out] +::error file=main,line=5,col=0::(`call-arg`) Too many arguments for "foo" + +[case testOutputGitHubWithHint] +# flags: --output=json +from typing import Optional, overload + +@overload +def foo() -> None: ... +@overload +def foo(x: int) -> None: ... + +def foo(x: Optional[int] = None) -> None: + ... + +reveal_type(foo) + +foo('42') + +def bar() -> None: ... +bar('42') +[out] +::notice file=main,line=12,col=12::(`misc`) Revealed type is "Overload(def (), def (x: builtins.int))" +::error file=main,line=14,col=0::(`call-overload`) No overload variant of "foo" matches argument type "str" +::error file=main,line=17,col=0::(`call-arg`) Too many arguments for "bar"