Skip to content

Commit 41045dc

Browse files
authored
Fix edge cases in pretty formatting (#20809)
This fixes few edge cases I discovered accidentally: * Caret position on imports in try/except gets set at column 1 (before the actual word `import`) * Code lines with a symbol `^` get all red (this is a silly one) * If the exact text `: error:` appears in a code line, it gets highlighted as a mypy error
1 parent a2d715a commit 41045dc

3 files changed

Lines changed: 35 additions & 11 deletions

File tree

mypy/errors.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,9 +1063,10 @@ def format_messages_default(
10631063
if severity == "error" and source_lines and line > 0:
10641064
source_line = source_lines[line - 1]
10651065
source_line_expanded = source_line.expandtabs()
1066-
if column < 0:
1066+
min_column = len(source_line) - len(source_line.lstrip())
1067+
if column < min_column:
10671068
# Something went wrong, take first non-empty column.
1068-
column = len(source_line) - len(source_line.lstrip())
1069+
column = min_column
10691070

10701071
# Shifts column after tab expansion
10711072
column = len(source_line[:column].expandtabs())

mypy/util.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
ENCODING_RE: Final = re.compile(rb"([ \t\v]*#.*(\r\n?|\n))??[ \t\v]*#.*coding[:=][ \t]*([-\w.]+)")
3636

3737
DEFAULT_SOURCE_OFFSET: Final = 4
38+
CODE_START: Final = " " * DEFAULT_SOURCE_OFFSET
3839
DEFAULT_COLUMNS: Final = 80
3940

4041
# At least this number of columns will be shown on each side of
@@ -729,19 +730,27 @@ def style(
729730
start += self.DIM
730731
return start + self.colors[color] + text + self.NORMAL
731732

733+
def is_marker_line(self, line: str) -> bool:
734+
s_line = line.lstrip()
735+
return (
736+
line.startswith(CODE_START)
737+
and s_line.startswith("^")
738+
and set(s_line).issubset({"^", "~"})
739+
)
740+
732741
def fit_in_terminal(
733742
self, messages: list[str], fixed_terminal_width: int | None = None
734743
) -> list[str]:
735744
"""Improve readability by wrapping error messages and trimming source code."""
736745
width = fixed_terminal_width or get_terminal_width()
737746
new_messages = messages.copy()
738747
for i, error in enumerate(messages):
739-
if ": error:" in error:
748+
# TODO: detecting source code highlights through an indent can be surprising.
749+
if not error.startswith(CODE_START) and ": error:" in error:
740750
loc, msg = error.split("error:", maxsplit=1)
741751
msg = soft_wrap(msg, width, first_offset=len(loc) + len("error: "))
742752
new_messages[i] = loc + "error:" + msg
743-
if error.startswith(" " * DEFAULT_SOURCE_OFFSET) and "^" not in error:
744-
# TODO: detecting source code highlights through an indent can be surprising.
753+
elif error.startswith(CODE_START) and not self.is_marker_line(error):
745754
# Restore original error message and error location.
746755
error = error[DEFAULT_SOURCE_OFFSET:]
747756
marker_line = messages[i + 1]
@@ -768,7 +777,12 @@ def fit_in_terminal(
768777

769778
def colorize(self, error: str) -> str:
770779
"""Colorize an output line by highlighting the status and error code."""
771-
if ": error:" in error:
780+
# TODO: detecting source code highlights through an indent can be surprising.
781+
if error.startswith(CODE_START):
782+
if not self.is_marker_line(error):
783+
return self.style(error, "none", dim=True)
784+
return self.style(error, "red")
785+
elif ": error:" in error:
772786
loc, msg = error.split("error:", maxsplit=1)
773787
if self.hide_error_codes:
774788
return (
@@ -790,11 +804,6 @@ def colorize(self, error: str) -> str:
790804
loc, msg = error.split("note:", maxsplit=1)
791805
formatted = self.highlight_quote_groups(self.underline_link(msg))
792806
return loc + self.style("note:", "blue") + formatted
793-
elif error.startswith(" " * DEFAULT_SOURCE_OFFSET):
794-
# TODO: detecting source code highlights through an indent can be surprising.
795-
if "^" not in error:
796-
return self.style(error, "none", dim=True)
797-
return self.style(error, "red")
798807
else:
799808
return error
800809

test-data/unit/cmdline.test

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,20 @@ expected "str"
738738
test_between(1 + 1)
739739
^~~~~~~~~~~~
740740

741+
[case testErrorMarkerSkipsLeftSpace]
742+
# cmd: mypy --pretty --show-error-codes imp.py
743+
[file imp.py]
744+
try:
745+
import foobar
746+
except ValueError:
747+
pass
748+
[out]
749+
imp.py:2: error: Cannot find implementation or library stub for module named
750+
"foobar" [import-not-found]
751+
import foobar
752+
^
753+
imp.py:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
754+
741755
[case testErrorMessageWhenOpenPydFile]
742756
# cmd: mypy a.pyd
743757
[file a.pyd]

0 commit comments

Comments
 (0)