|
17 | 17 | from pyls.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/palantir/python-language-server/blob/0.36.2/pyls/plugins/pylint_lint.py#L52-L59 |
| 35 | +last_diagnostics : Dict[str, List] = collections.defaultdict(list) |
29 | 36 |
|
30 | 37 | def parse_line(line: str, document: Optional[Document] = None) -> Optional[Dict[str, Any]]:
|
31 | 38 | """
|
@@ -111,35 +118,77 @@ def pyls_lint(config: Config, workspace: Workspace, document: Document,
|
111 | 118 |
|
112 | 119 | """
|
113 | 120 | settings = config.plugin_settings('mypy-ls')
|
| 121 | + log.info( |
| 122 | + "lint settings = %s document.path = %s is_saved = %s", |
| 123 | + settings, |
| 124 | + document.path, |
| 125 | + is_saved |
| 126 | + ) |
| 127 | + |
114 | 128 | live_mode = settings.get('live_mode', True)
|
115 |
| - args = ['--incremental', |
116 |
| - '--show-column-numbers', |
117 |
| - '--follow-imports', 'silent'] |
| 129 | + dmypy = settings.get("dmypy", False) |
| 130 | + |
| 131 | + if dmypy and live_mode: |
| 132 | + # dmypy can only be efficiently run on files that have been saved, see: |
| 133 | + # https://github.com/python/mypy/issues/9309 |
| 134 | + log.warning("live_mode is not supported with dmypy, disabling") |
| 135 | + live_mode = False |
| 136 | + |
| 137 | + args = ['--show-column-numbers'] |
118 | 138 |
|
119 | 139 | global tmpFile
|
120 | 140 | if live_mode and not is_saved and tmpFile:
|
| 141 | + log.info("live_mode tmpFile = %s", live_mode) |
121 | 142 | tmpFile = open(tmpFile.name, "w")
|
122 | 143 | tmpFile.write(document.source)
|
123 | 144 | tmpFile.close()
|
124 | 145 | args.extend(['--shadow-file', document.path, tmpFile.name])
|
125 |
| - elif not is_saved: |
126 |
| - return [] |
| 146 | + elif not is_saved and document.path in last_diagnostics: |
| 147 | + # On-launch the document isn't marked as saved, so fall through and run |
| 148 | + # the diagnostics anyway even if the file contents may be out of date. |
| 149 | + log.info( |
| 150 | + "non-live, returning cached diagnostics len(cached) = %s", |
| 151 | + last_diagnostics[document.path] |
| 152 | + ) |
| 153 | + return last_diagnostics[document.path] |
127 | 154 |
|
128 | 155 | if mypyConfigFile:
|
129 | 156 | args.append('--config-file')
|
130 | 157 | args.append(mypyConfigFile)
|
| 158 | + |
131 | 159 | args.append(document.path)
|
| 160 | + |
132 | 161 | if settings.get('strict', False):
|
133 | 162 | args.append('--strict')
|
134 | 163 |
|
135 |
| - report, errors, _ = mypy_api.run(args) |
| 164 | + if not dmypy: |
| 165 | + args.extend([ |
| 166 | + "--incremental", |
| 167 | + "--follow-imports", |
| 168 | + "silent" |
| 169 | + ]) |
| 170 | + |
| 171 | + log.info(f"executing mypy {args=}") |
| 172 | + report, errors, _ = mypy_api.run(args) |
| 173 | + else: |
| 174 | + args = ["run", "--"] + args |
| 175 | + |
| 176 | + log.info(f"executing dmypy {args=}") |
| 177 | + report, errors, _ = mypy_api.run_dmypy(args) |
| 178 | + |
| 179 | + log.debug("report: \n" + report) |
| 180 | + log.debug("errors: \n" + errors) |
136 | 181 |
|
137 | 182 | diagnostics = []
|
138 | 183 | for line in report.splitlines():
|
| 184 | + log.debug(f"parsing: {line=}") |
139 | 185 | diag = parse_line(line, document)
|
140 | 186 | if diag:
|
141 | 187 | diagnostics.append(diag)
|
142 | 188 |
|
| 189 | + logging.info("mypy-ls len(diagnostics) = %s", len(diagnostics)) |
| 190 | + |
| 191 | + last_diagnostics[document.path] = diagnostics |
143 | 192 | return diagnostics
|
144 | 193 |
|
145 | 194 |
|
@@ -179,19 +228,27 @@ def init(workspace: str) -> Dict[str, str]:
|
179 | 228 |
|
180 | 229 | """
|
181 | 230 | # On windows the path contains \\ on linux it contains / all the code works with /
|
| 231 | + log.info(f"init {workspace=}") |
182 | 232 | workspace = workspace.replace("\\", "/")
|
| 233 | + |
183 | 234 | configuration = {}
|
184 | 235 | path = findConfigFile(workspace, "mypy-ls.cfg")
|
185 | 236 | if path:
|
186 | 237 | with open(path) as file:
|
187 | 238 | configuration = eval(file.read())
|
| 239 | + |
188 | 240 | global mypyConfigFile
|
189 | 241 | mypyConfigFile = findConfigFile(workspace, "mypy.ini")
|
| 242 | + if not mypyConfigFile: |
| 243 | + mypyConfigFile = findConfigFile(workspace, ".mypy.ini") |
| 244 | + |
190 | 245 | if (("enabled" not in configuration or configuration["enabled"])
|
191 | 246 | and ("live_mode" not in configuration or configuration["live_mode"])):
|
192 | 247 | global tmpFile
|
193 | 248 | tmpFile = tempfile.NamedTemporaryFile('w', delete=False)
|
194 | 249 | tmpFile.close()
|
| 250 | + |
| 251 | + log.info(f"{mypyConfigFile=} {configuration=}") |
195 | 252 | return configuration
|
196 | 253 |
|
197 | 254 |
|
|
0 commit comments