diff --git a/mypy/inspections.py b/mypy/inspections.py index cb695a80eef2..e3fad033d904 100644 --- a/mypy/inspections.py +++ b/mypy/inspections.py @@ -219,13 +219,6 @@ def __init__( # Module for which inspection was requested. self.module: State | None = None - def parse_location(self, location: str) -> tuple[str, list[int]]: - if location.count(":") not in [2, 4]: - raise ValueError("Format should be file:line:column[:end_line:end_column]") - parts = location.split(":") - module, *rest = parts - return module, [int(p) for p in rest] - def reload_module(self, state: State) -> None: """Reload given module while temporary exporting types.""" old = self.fg_manager.manager.options.export_types @@ -581,7 +574,7 @@ def run_inspection( This can be re-used by various simple inspections. """ try: - file, pos = self.parse_location(location) + file, pos = parse_location(location) except ValueError as err: return {"error": str(err)} @@ -623,3 +616,18 @@ def get_definition(self, location: str) -> dict[str, object]: result["out"] = f"No name or member expressions at {location}" result["status"] = 1 return result + + +def parse_location(location: str) -> tuple[str, list[int]]: + if location.count(":") < 2: + raise ValueError("Format should be file:line:column[:end_line:end_column]") + parts = location.rsplit(":", maxsplit=2) + start, *rest = parts + # Note: we must allow drive prefix like `C:` on Windows. + if start.count(":") < 2: + return start, [int(p) for p in rest] + parts = start.rsplit(":", maxsplit=2) + start, *start_rest = parts + if start.count(":") < 2: + return start, [int(p) for p in start_rest + rest] + raise ValueError("Format should be file:line:column[:end_line:end_column]") diff --git a/mypy/test/testutil.py b/mypy/test/testutil.py index 89184b11a826..571e4d0b11f2 100644 --- a/mypy/test/testutil.py +++ b/mypy/test/testutil.py @@ -3,6 +3,7 @@ import os from unittest import TestCase, mock +from mypy.inspections import parse_location from mypy.util import get_terminal_width @@ -15,3 +16,7 @@ def test_get_terminal_size_in_pty_defaults_to_80(self) -> None: with mock.patch.object(os, "get_terminal_size", return_value=ret): with mock.patch.dict(os.environ, values=mock_environ, clear=True): assert get_terminal_width() == 80 + + def test_parse_location_windows(self) -> None: + assert parse_location(r"C:\test.py:1:1") == (r"C:\test.py", [1, 1]) + assert parse_location(r"C:\test.py:1:1:1:1") == (r"C:\test.py", [1, 1, 1, 1]) diff --git a/test-data/unit/daemon.test b/test-data/unit/daemon.test index 18a03a92207d..61dc345e2c49 100644 --- a/test-data/unit/daemon.test +++ b/test-data/unit/daemon.test @@ -372,6 +372,9 @@ foo.py:3: error: Incompatible types in assignment (expression has type "str", va $ dmypy inspect foo:1 Format should be file:line:column[:end_line:end_column] == Return code: 2 +$ dmypy inspect foo:1:2:3 +Source file is not a Python file +== Return code: 2 $ dmypy inspect foo.py:1:2:a:b invalid literal for int() with base 10: 'a' == Return code: 2