Skip to content

Commit 21efb22

Browse files
Add GitHub annotations format for --output
1 parent 72c413d commit 21efb22

File tree

3 files changed

+104
-1
lines changed

3 files changed

+104
-1
lines changed

mypy/error_formatter.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,20 @@ def report_error(self, error: "MypyError") -> str:
3434
)
3535

3636

37-
OUTPUT_CHOICES = {"json": JSONFormatter()}
37+
class GitHubFormatter(ErrorFormatter):
38+
"""Formatter for GitHub Actions output format."""
39+
40+
def report_error(self, error: "MypyError") -> str:
41+
"""Prints out the errors as GitHub Actions annotations."""
42+
command = "error" if error.severity == "error" else "notice"
43+
code = f"(`{error.errorcode.code}`) " if error.errorcode is not None else ""
44+
45+
result = f"::{command} file={error.file_path},line={error.line},col={error.column}::{code}{error.message}"
46+
if len(error.hints) > 0:
47+
# TODO: Add hints to the output?
48+
pass
49+
50+
return result
51+
52+
53+
OUTPUT_CHOICES = {"json": JSONFormatter(), "github": GitHubFormatter()}

mypy/test/testoutput.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,46 @@ def test_output_json(testcase: DataDrivenTestCase) -> None:
5656
normalized_output = [line.replace(test_temp_dir + json_os_separator, "") for line in output]
5757

5858
assert normalized_output == testcase.output
59+
60+
61+
class OutputGitHubsuite(DataSuite):
62+
files = ["outputgithub.test"]
63+
64+
def run_case(self, testcase: DataDrivenTestCase) -> None:
65+
test_output_github(testcase)
66+
67+
68+
def test_output_github(testcase: DataDrivenTestCase) -> None:
69+
"""Run Mypy in a subprocess, and ensure that `--output=github` works as intended."""
70+
mypy_cmdline = ["--output=github"]
71+
mypy_cmdline.append(f"--python-version={'.'.join(map(str, PYTHON3_VERSION))}")
72+
73+
# Write the program to a file.
74+
program_path = os.path.join(test_temp_dir, "main")
75+
mypy_cmdline.append(program_path)
76+
with open(program_path, "w", encoding="utf8") as file:
77+
for s in testcase.input:
78+
file.write(f"{s}\n")
79+
80+
output = []
81+
# Type check the program.
82+
out, err, returncode = api.run(mypy_cmdline)
83+
# split lines, remove newlines, and remove directory of test case
84+
for line in (out + err).rstrip("\n").splitlines():
85+
if line.startswith(test_temp_dir + os.sep):
86+
output.append(line[len(test_temp_dir + os.sep) :].rstrip("\r\n"))
87+
else:
88+
output.append(line.rstrip("\r\n"))
89+
90+
if returncode > 1:
91+
output.append("!!! Mypy crashed !!!")
92+
93+
# Remove temp file.
94+
os.remove(program_path)
95+
96+
# JSON encodes every `\` character into `\\`, so we need to remove `\\` from windows paths
97+
# and `/` from POSIX paths
98+
json_os_separator = os.sep.replace("\\", "\\\\")
99+
normalized_output = [line.replace(test_temp_dir + json_os_separator, "") for line in output]
100+
101+
assert normalized_output == testcase.output

test-data/unit/outputgithub.test

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
-- Test cases for `--output=json`.
2+
-- These cannot be run by the usual unit test runner because of the backslashes
3+
-- in the output, which get normalized to forward slashes by the test suite on
4+
-- Windows.
5+
6+
[case testOutputGitHubNoIssues]
7+
# flags: --output=github
8+
def foo() -> None:
9+
pass
10+
11+
foo()
12+
[out]
13+
14+
[case testOutputGitHubSimple]
15+
# flags: --output=github
16+
def foo() -> None:
17+
pass
18+
19+
foo(1)
20+
[out]
21+
::error file=main,line=5,col=0::(`call-arg`) Too many arguments for "foo"
22+
23+
[case testOutputGitHubWithHint]
24+
# flags: --output=json
25+
from typing import Optional, overload
26+
27+
@overload
28+
def foo() -> None: ...
29+
@overload
30+
def foo(x: int) -> None: ...
31+
32+
def foo(x: Optional[int] = None) -> None:
33+
...
34+
35+
reveal_type(foo)
36+
37+
foo('42')
38+
39+
def bar() -> None: ...
40+
bar('42')
41+
[out]
42+
::notice file=main,line=12,col=12::(`misc`) Revealed type is "Overload(def (), def (x: builtins.int))"
43+
::error file=main,line=14,col=0::(`call-overload`) No overload variant of "foo" matches argument type "str"
44+
::error file=main,line=17,col=0::(`call-arg`) Too many arguments for "bar"

0 commit comments

Comments
 (0)