Skip to content

Commit

Permalink
Add GitHub annotations format for --output
Browse files Browse the repository at this point in the history
  • Loading branch information
edgarrmondragon committed Sep 27, 2024
1 parent 26a77f9 commit fcdfa8f
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 7 deletions.
2 changes: 1 addition & 1 deletion docs/source/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ Optional arguments

Show program's version number and exit.

.. option:: -O FORMAT, --output FORMAT {json}
.. option:: -O {json,github}, --output {json,github}

Set a custom output format.

Expand Down
26 changes: 25 additions & 1 deletion mypy/error_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,28 @@ 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"
title = f"Mypy ({error.errorcode.code})" if error.errorcode is not None else "Mypy"

message = f"{error.message}."

if error.hints:
message += "%0A%0A"
message += "%0A".join(error.hints)

return (
f"::{command} "
f"file={error.file_path},"
f"line={error.line},"
f"col={error.column},"
f"title={title}"
f"::{message}"
)


OUTPUT_CHOICES = {"json": JSONFormatter(), "github": GitHubFormatter()}
6 changes: 1 addition & 5 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,11 +530,7 @@ def add_invertible_flag(
)

general_group.add_argument(
"-O",
"--output",
metavar="FORMAT",
help="Set a custom output format",
choices=OUTPUT_CHOICES,
"-O", "--output", help="Set a custom output format", choices=OUTPUT_CHOICES
)

config_group = parser.add_argument_group(
Expand Down
39 changes: 39 additions & 0 deletions mypy/test/testoutput.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
44 changes: 44 additions & 0 deletions test-data/unit/outputgithub.test
Original file line number Diff line number Diff line change
@@ -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,title=Mypy (call-arg)::Too many arguments for "foo".

[case testOutputGitHubWithHint]
# flags: --output=github
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,title=Mypy (misc)::Revealed type is "Overload(def (), def (x: builtins.int))".
::error file=main,line=14,col=0,title=Mypy (call-overload)::No overload variant of "foo" matches argument type "str".%0A%0APossible overload variants:%0A def foo() -> None%0A def foo(x: int) -> None
::error file=main,line=17,col=0,title=Mypy (call-arg)::Too many arguments for "bar".

0 comments on commit fcdfa8f

Please sign in to comment.