From 3f5cd050b6cb5e124e1663c0e8292ecd231e8295 Mon Sep 17 00:00:00 2001 From: Adam Turner Date: Tue, 3 Feb 2026 22:46:18 +0000 Subject: [PATCH] Emit end line/column in JSON format for span tracking --- mypy/error_formatter.py | 2 ++ mypy/errors.py | 21 ++++++++++++++++++--- test-data/unit/check-incremental.test | 2 +- test-data/unit/outputjson.test | 8 ++++---- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/mypy/error_formatter.py b/mypy/error_formatter.py index ffc6b67475962..ea79affbddd0e 100644 --- a/mypy/error_formatter.py +++ b/mypy/error_formatter.py @@ -26,6 +26,8 @@ def report_error(self, error: "MypyError") -> str: "file": error.file_path, "line": error.line, "column": error.column, + "end_line": error.end_line, + "end_column": error.end_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, diff --git a/mypy/errors.py b/mypy/errors.py index 4cdeb0534f2e8..750b7c746c4b1 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -1480,6 +1480,8 @@ def __init__( file_path: str, line: int, column: int, + end_line: int, + end_column: int, message: str, errorcode: ErrorCode | None, severity: Literal["error", "note"], @@ -1487,6 +1489,8 @@ def __init__( self.file_path = file_path self.line = line self.column = column + self.end_line = end_line + self.end_column = end_column self.message = message self.errorcode = errorcode self.severity = severity @@ -1502,7 +1506,7 @@ def create_errors(error_tuples: list[ErrorTuple]) -> list[MypyError]: latest_error_at_location: dict[_ErrorLocation, MypyError] = {} for error_tuple in error_tuples: - file_path, line, column, _, _, severity, message, errorcode = error_tuple + file_path, line, column, end_line, end_column, severity, message, errorcode = error_tuple if file_path is None: continue @@ -1512,14 +1516,25 @@ def create_errors(error_tuples: list[ErrorTuple]) -> list[MypyError]: error = latest_error_at_location.get(error_location) if error is None: # This is purely a note, with no error correlated to it - error = MypyError(file_path, line, column, message, errorcode, severity="note") + error = MypyError( + file_path, + line, + column, + end_line, + end_column, + message, + errorcode, + severity="note", + ) errors.append(error) continue error.hints.append(message) else: - error = MypyError(file_path, line, column, message, errorcode, severity="error") + error = MypyError( + file_path, line, column, end_line, end_column, message, errorcode, severity="error" + ) errors.append(error) error_location = (file_path, line, column) latest_error_at_location[error_location] = error diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 1698a51a975d9..52e3f8b9aa84c 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -7655,7 +7655,7 @@ def wrong() -> int: [out] main:2: error: Missing return statement [out2] -{"file": "main", "line": 2, "column": 0, "message": "Missing return statement", "hint": null, "code": "return", "severity": "error"} +{"file": "main", "line": 2, "column": 0, "end_line": 4, "end_column": 16, "message": "Missing return statement", "hint": null, "code": "return", "severity": "error"} [case testReExportAllInStubIncremental] from m1 import C diff --git a/test-data/unit/outputjson.test b/test-data/unit/outputjson.test index 43649b7b781d3..51a49c64530ac 100644 --- a/test-data/unit/outputjson.test +++ b/test-data/unit/outputjson.test @@ -18,7 +18,7 @@ def foo() -> None: foo(1) [out] -{"file": "main", "line": 5, "column": 0, "message": "Too many arguments for \"foo\"", "hint": null, "code": "call-arg", "severity": "error"} +{"file": "main", "line": 5, "column": 0, "end_line": 5, "end_column": 6, "message": "Too many arguments for \"foo\"", "hint": null, "code": "call-arg", "severity": "error"} [case testOutputJsonWithHint] # flags: --output=json @@ -39,6 +39,6 @@ foo('42') def bar() -> None: ... bar('42') [out] -{"file": "main", "line": 12, "column": 12, "message": "Revealed type is \"Overload(def (), def (x: builtins.int))\"", "hint": null, "code": "misc", "severity": "note"} -{"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", "severity": "error"} -{"file": "main", "line": 17, "column": 0, "message": "Too many arguments for \"bar\"", "hint": null, "code": "call-arg", "severity": "error"} +{"file": "main", "line": 12, "column": 12, "end_line": 12, "end_column": 15, "message": "Revealed type is \"Overload(def (), def (x: builtins.int))\"", "hint": null, "code": "misc", "severity": "note"} +{"file": "main", "line": 14, "column": 0, "end_line": 14, "end_column": 9, "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", "severity": "error"} +{"file": "main", "line": 17, "column": 0, "end_line": 17, "end_column": 9, "message": "Too many arguments for \"bar\"", "hint": null, "code": "call-arg", "severity": "error"}