-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Error format support, and JSON output option #11396
Changes from 47 commits
393820c
282bd28
ccda5b0
c849a77
fd2feab
b188001
51c1acc
9177dab
bc5ceac
9d29ab0
bd6d48d
ba8d17f
a2bc04d
35974e4
1e5ec91
723219f
2228c0a
33d81b0
63001ea
d27be7e
efe5c5d
1872ae6
3abc9cb
6c9ab11
627ed8e
e425cbe
47f1b07
e00ad4a
c1fb6a2
aafe3aa
fae3215
89ad1d3
7a3f736
6d46f75
e71a372
8cca203
79e16a8
8bf4890
5899f26
880b8f3
0aafadf
4cab249
7fe71c3
ad8f1d6
e2fd45e
4b03c5c
e0e6896
a0dc6d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
"""Defines the different custom formats in which mypy can output.""" | ||
|
||
import json | ||
from abc import ABC, abstractmethod | ||
from typing import TYPE_CHECKING | ||
|
||
if TYPE_CHECKING: | ||
from mypy.errors import MypyError | ||
|
||
|
||
class ErrorFormatter(ABC): | ||
"""Base class to define how errors are formatted before being printed.""" | ||
|
||
@abstractmethod | ||
def report_error(self, error: "MypyError") -> str: | ||
raise NotImplementedError | ||
|
||
|
||
class JSONFormatter(ErrorFormatter): | ||
"""Formatter for basic JSON output format.""" | ||
|
||
def report_error(self, error: "MypyError") -> str: | ||
"""Prints out the errors as simple, static JSON lines.""" | ||
return json.dumps( | ||
{ | ||
"file": error.file_path, | ||
"line": error.line, | ||
"column": error.column, | ||
"message": error.message, | ||
"hint": None if len(error.hints) == 0 else "\n".join(error.hints), | ||
"code": None if error.errorcode is None else error.errorcode.code, | ||
"is_note": error.is_note, | ||
} | ||
) | ||
|
||
|
||
OUTPUT_CHOICES = {"json": JSONFormatter()} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
"""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. | ||
""" | ||
|
||
from __future__ import annotations | ||
|
||
import os | ||
import os.path | ||
|
||
from mypy import api | ||
from mypy.defaults import PYTHON3_VERSION | ||
from mypy.test.config import test_temp_dir | ||
from mypy.test.data import DataDrivenTestCase, DataSuite | ||
|
||
|
||
class OutputJSONsuite(DataSuite): | ||
files = ["outputjson.test"] | ||
|
||
def run_case(self, testcase: DataDrivenTestCase) -> None: | ||
test_output_json(testcase) | ||
|
||
|
||
def test_output_json(testcase: DataDrivenTestCase) -> None: | ||
"""Runs Mypy in a subprocess, and ensures that `--output=json` works as intended.""" | ||
mypy_cmdline = ["--output=json"] | ||
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) | ||
|
||
# JSON encodes every `\` character into `\\`, so we need to remove `\\` from windows paths | ||
# and `/` from POSIX paths | ||
json_os_separator = os.sep.replace("\\", "\\\\") | ||
normalized_output = [line.replace(test_temp_dir + json_os_separator, "") for line in output] | ||
|
||
assert normalized_output == testcase.output |
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 testOutputJsonNoIssues] | ||
# flags: --output=json | ||
def foo() -> None: | ||
pass | ||
|
||
foo() | ||
[out] | ||
|
||
[case testOutputJsonSimple] | ||
# flags: --output=json | ||
def foo() -> None: | ||
pass | ||
|
||
foo(1) | ||
[out] | ||
{"file": "main", "line": 5, "column": 0, "message": "Too many arguments for \"foo\"", "hint": null, "code": "call-arg", "is_note": false} | ||
|
||
[case testOutputJsonWithHint] | ||
# 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') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a test case with |
||
[out] | ||
{"file": "main", "line": 12, "column": 12, "message": "Revealed type is \"Overload(def (), def (x: builtins.int))\"", "hint": null, "code": "misc", "is_note": true} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we change this to |
||
{"file": "main", "line": 14, "column": 0, "message": "No overload variant of \"foo\" matches argument type \"str\"", "hint": "Possible overload variants:\n def foo() -> None\n def foo(x: int) -> None", "code": "call-overload", "is_note": false} | ||
{"file": "main", "line": 17, "column": 0, "message": "Too many arguments for \"bar\"", "hint": null, "code": "call-arg", "is_note": false} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This module should define a dictionary
str -> ErrorFormatter
that can be used inbuild.py
.