|
17 | 17 | from pylsp.config.config import Config
|
18 | 18 | from typing import Optional, Dict, Any, IO, List
|
19 | 19 | import atexit
|
| 20 | +import collections |
20 | 21 |
|
21 | 22 | line_pattern: str = r"((?:^[a-z]:)?[^:]+):(?:(\d+):)?(?:(\d+):)? (\w+): (.*)"
|
22 | 23 |
|
|
26 | 27 |
|
27 | 28 | tmpFile: Optional[IO[str]] = None
|
28 | 29 |
|
| 30 | +# In non-live-mode the file contents aren't updated. |
| 31 | +# Returning an empty diagnostic clears the diagnostic result, |
| 32 | +# so store a cache of last diagnostics for each file a-la the pylint plugin, |
| 33 | +# so we can return some potentially-stale diagnostics. |
| 34 | +# https://github.com/python-lsp/python-lsp-server/blob/v1.0.1/pylsp/plugins/pylint_lint.py#L55-L62 |
| 35 | +last_diagnostics: Dict[str, List] = collections.defaultdict(list) |
| 36 | + |
29 | 37 |
|
30 | 38 | def parse_line(
|
31 | 39 | line: str, document: Optional[Document] = None
|
@@ -115,33 +123,73 @@ def pylsp_lint(
|
115 | 123 |
|
116 | 124 | """
|
117 | 125 | settings = config.plugin_settings("mypy-ls")
|
| 126 | + log.info( |
| 127 | + "lint settings = %s document.path = %s is_saved = %s", |
| 128 | + settings, |
| 129 | + document.path, |
| 130 | + is_saved, |
| 131 | + ) |
| 132 | + |
118 | 133 | live_mode = settings.get("live_mode", True)
|
119 |
| - args = ["--incremental", "--show-column-numbers", "--follow-imports", "silent"] |
| 134 | + dmypy = settings.get("dmypy", False) |
| 135 | + |
| 136 | + if dmypy and live_mode: |
| 137 | + # dmypy can only be efficiently run on files that have been saved, see: |
| 138 | + # https://github.com/python/mypy/issues/9309 |
| 139 | + log.warning("live_mode is not supported with dmypy, disabling") |
| 140 | + live_mode = False |
| 141 | + |
| 142 | + args = ["--show-column-numbers"] |
120 | 143 |
|
121 | 144 | global tmpFile
|
122 | 145 | if live_mode and not is_saved and tmpFile:
|
| 146 | + log.info("live_mode tmpFile = %s", live_mode) |
123 | 147 | tmpFile = open(tmpFile.name, "w")
|
124 | 148 | tmpFile.write(document.source)
|
125 | 149 | tmpFile.close()
|
126 | 150 | args.extend(["--shadow-file", document.path, tmpFile.name])
|
127 |
| - elif not is_saved: |
128 |
| - return [] |
| 151 | + elif not is_saved and document.path in last_diagnostics: |
| 152 | + # On-launch the document isn't marked as saved, so fall through and run |
| 153 | + # the diagnostics anyway even if the file contents may be out of date. |
| 154 | + log.info( |
| 155 | + "non-live, returning cached diagnostics len(cached) = %s", |
| 156 | + last_diagnostics[document.path], |
| 157 | + ) |
| 158 | + return last_diagnostics[document.path] |
129 | 159 |
|
130 | 160 | if mypyConfigFile:
|
131 | 161 | args.append("--config-file")
|
132 | 162 | args.append(mypyConfigFile)
|
| 163 | + |
133 | 164 | args.append(document.path)
|
| 165 | + |
134 | 166 | if settings.get("strict", False):
|
135 | 167 | args.append("--strict")
|
136 | 168 |
|
137 |
| - report, errors, _ = mypy_api.run(args) |
| 169 | + if not dmypy: |
| 170 | + args.extend(["--incremental", "--follow-imports", "silent"]) |
| 171 | + |
| 172 | + log.info("executing mypy args = %s", args) |
| 173 | + report, errors, _ = mypy_api.run(args) |
| 174 | + else: |
| 175 | + args = ["run", "--"] + args |
| 176 | + |
| 177 | + log.info("executing dmypy args = %s", args) |
| 178 | + report, errors, _ = mypy_api.run_dmypy(args) |
| 179 | + |
| 180 | + log.debug("report:\n%s", report) |
| 181 | + log.debug("errors:\n%s", errors) |
138 | 182 |
|
139 | 183 | diagnostics = []
|
140 | 184 | for line in report.splitlines():
|
| 185 | + log.debug("parsing: line = %r", line) |
141 | 186 | diag = parse_line(line, document)
|
142 | 187 | if diag:
|
143 | 188 | diagnostics.append(diag)
|
144 | 189 |
|
| 190 | + log.info("mypy-ls len(diagnostics) = %s", len(diagnostics)) |
| 191 | + |
| 192 | + last_diagnostics[document.path] = diagnostics |
145 | 193 | return diagnostics
|
146 | 194 |
|
147 | 195 |
|
@@ -181,20 +229,28 @@ def init(workspace: str) -> Dict[str, str]:
|
181 | 229 |
|
182 | 230 | """
|
183 | 231 | # On windows the path contains \\ on linux it contains / all the code works with /
|
| 232 | + log.info("init workspace = %s", workspace) |
184 | 233 | workspace = workspace.replace("\\", "/")
|
| 234 | + |
185 | 235 | configuration = {}
|
186 | 236 | path = findConfigFile(workspace, "mypy-ls.cfg")
|
187 | 237 | if path:
|
188 | 238 | with open(path) as file:
|
189 | 239 | configuration = eval(file.read())
|
| 240 | + |
190 | 241 | global mypyConfigFile
|
191 | 242 | mypyConfigFile = findConfigFile(workspace, "mypy.ini")
|
| 243 | + if not mypyConfigFile: |
| 244 | + mypyConfigFile = findConfigFile(workspace, ".mypy.ini") |
| 245 | + |
192 | 246 | if ("enabled" not in configuration or configuration["enabled"]) and (
|
193 | 247 | "live_mode" not in configuration or configuration["live_mode"]
|
194 | 248 | ):
|
195 | 249 | global tmpFile
|
196 | 250 | tmpFile = tempfile.NamedTemporaryFile("w", delete=False)
|
197 | 251 | tmpFile.close()
|
| 252 | + |
| 253 | + log.info("mypyConfigFile = %s configuration = %s", mypyConfigFile, configuration) |
198 | 254 | return configuration
|
199 | 255 |
|
200 | 256 |
|
|
0 commit comments