|
| 1 | +import json |
1 | 2 | from multiprocessing import Process, Queue |
2 | | -from typing import TYPE_CHECKING, Any, Callable, Optional |
3 | | - |
4 | | -from mypy import api |
| 3 | +from typing import ( |
| 4 | + TYPE_CHECKING, |
| 5 | + Any, |
| 6 | + Callable, |
| 7 | + Optional, |
| 8 | +) |
| 9 | + |
| 10 | +import mypy.api |
| 11 | +import mypy.build |
| 12 | +import mypy.errors |
| 13 | +import mypy.fscache |
| 14 | +import mypy.main |
5 | 15 |
|
6 | 16 | from prospector.finder import FileFinder |
7 | 17 | from prospector.message import Location, Message |
8 | 18 | from prospector.tools import ToolBase |
9 | | - |
10 | | -__all__ = ("MypyTool",) |
11 | | - |
12 | 19 | from prospector.tools.exceptions import BadToolConfig |
13 | 20 |
|
14 | 21 | if TYPE_CHECKING: |
@@ -58,9 +65,10 @@ def _run_in_subprocess( |
58 | 65 | class MypyTool(ToolBase): |
59 | 66 | def __init__(self, *args: Any, **kwargs: Any) -> None: |
60 | 67 | super().__init__(*args, **kwargs) |
61 | | - self.checker = api |
| 68 | + self.checker = mypy.api |
62 | 69 | self.options = ["--show-column-numbers", "--no-error-summary"] |
63 | 70 | self.use_dmypy = False |
| 71 | + self.fscache = mypy.fscache.FileSystemCache() |
64 | 72 |
|
65 | 73 | def configure(self, prospector_config: "ProspectorConfig", _: Any) -> None: |
66 | 74 | options = prospector_config.tool_options("mypy") |
@@ -90,18 +98,48 @@ def configure(self, prospector_config: "ProspectorConfig", _: Any) -> None: |
90 | 98 | raise BadToolConfig("mypy", f"The option {name} has an unsupported value type: {type(value)}") |
91 | 99 |
|
92 | 100 | def run(self, found_files: FileFinder) -> list[Message]: |
93 | | - paths = [str(path) for path in found_files.python_modules] |
94 | | - paths.extend(self.options) |
| 101 | + args = [str(path) for path in found_files.python_modules] |
| 102 | + args.extend(self.options) |
95 | 103 | if self.use_dmypy: |
96 | 104 | # Due to dmypy messing with stdout/stderr we call it in a separate |
97 | 105 | # process |
98 | 106 | q: Queue[str] = Queue(1) |
99 | | - p = Process(target=_run_in_subprocess, args=(q, self.checker.run_dmypy, ["run", "--"] + paths)) |
| 107 | + p = Process(target=_run_in_subprocess, args=(q, self.checker.run_dmypy, ["run", "--"] + args)) |
100 | 108 | p.start() |
101 | 109 | result = q.get() |
102 | 110 | p.join() |
103 | | - else: |
104 | | - result = self.checker.run(paths) |
105 | | - report, _ = result[0], result[1:] # noqa |
106 | 111 |
|
107 | | - return [format_message(message) for message in report.splitlines()] |
| 112 | + report, _ = result[0], result[1:] # noqa |
| 113 | + return [format_message(message) for message in report.splitlines()] |
| 114 | + else: |
| 115 | + return self._run_std(args) |
| 116 | + |
| 117 | + def _run_std(self, args: list[str]) -> list[Message]: |
| 118 | + sources, options = mypy.main.process_options(args, fscache=self.fscache) |
| 119 | + options.output = "json" |
| 120 | + res = mypy.build.build(sources, options, fscache=self.fscache) |
| 121 | + |
| 122 | + messages = [] |
| 123 | + for mypy_json in res.errors: |
| 124 | + mypy_message = json.loads(mypy_json) |
| 125 | + message = f"{mypy_message['message']}." |
| 126 | + if mypy_message.get("hint", ""): |
| 127 | + message = f"{message} {mypy_message['hint']}." |
| 128 | + code = mypy_message["code"] |
| 129 | + messages.append( |
| 130 | + Message( |
| 131 | + "mypy", |
| 132 | + code=code, |
| 133 | + location=Location( |
| 134 | + path=mypy_message["file"], |
| 135 | + module=None, |
| 136 | + function=None, |
| 137 | + line=mypy_message["line"], |
| 138 | + character=mypy_message["column"], |
| 139 | + ), |
| 140 | + message=message, |
| 141 | + doc_url=f"{mypy.errors.BASE_RTD_URL}-{code}", |
| 142 | + ) |
| 143 | + ) |
| 144 | + |
| 145 | + return messages |
0 commit comments