Skip to content

Commit 0d3f8d8

Browse files
committed
Typing
1 parent a8beefa commit 0d3f8d8

File tree

9 files changed

+252
-1941
lines changed

9 files changed

+252
-1941
lines changed

README.rst

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,21 @@ Installation
1717

1818
Install into the same virtualenv as pyls itself.
1919

20-
``pip install pyls-mypy``
20+
``pip install mypy-ls``
2121

2222
Configuration
2323
-------------
2424

25-
``live_mode`` (default is True) provides type checking as you type. This writes a tempfile every time a check is done.
25+
``live_mode`` (default is True) provides type checking as you type. This writes to a tempfile every time a check is done.
2626

2727
Turning off live_mode means you must save your changes for mypy diagnostics to update correctly.
2828

29-
Depending on your editor, the configuration should be roughly like this:
29+
Depending on your editor, the configuration (found in a file called mypy-ls.cfg in your workspace or a parent directory) should be roughly like this:
3030

3131
::
3232

33-
"pyls":
3433
{
35-
"plugins":
36-
{
37-
"pyls_mypy":
38-
{
39-
"enabled": true,
40-
"live_mode": true
41-
}
42-
}
34+
"enabled": True,
35+
"live_mode": True,
36+
"strict": False
4337
}
File renamed without changes.

mypy_ls/_version.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "0.3.0"

mypy_ls/plugin.py

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
File that contains the pyls plugin mypy-ls.
4+
5+
Created on Fri Jul 10 09:53:57 2020
6+
7+
@author: Richard Kellnberger
8+
"""
9+
import re
10+
import tempfile
11+
import os
12+
import os.path
13+
import logging
14+
from mypy import api as mypy_api
15+
from pyls import hookimpl
16+
from pyls.workspace import Document, Workspace
17+
from pyls.config.config import Config
18+
from typing import Optional, Dict, Any, IO, List
19+
import atexit
20+
21+
line_pattern: str = r"((?:^[a-z]:)?[^:]+):(?:(\d+):)?(?:(\d+):)? (\w+): (.*)"
22+
23+
log = logging.getLogger(__name__)
24+
25+
mypyConfigFile: Optional[str] = None
26+
27+
tmpFile: Optional[IO[str]] = None
28+
29+
30+
def parse_line(line: str, document: Optional[Document] = None) -> Optional[Dict[str, Any]]:
31+
"""
32+
Return a language-server diagnostic from a line of the Mypy error report.
33+
34+
optionally, use the whole document to provide more context on it.
35+
36+
37+
Parameters
38+
----------
39+
line : str
40+
Line of mypy output to be analysed.
41+
document : Optional[Document], optional
42+
Document in wich the line is found. The default is None.
43+
44+
Returns
45+
-------
46+
Optional[Dict[str, Any]]
47+
The dict with the lint data.
48+
49+
"""
50+
result = re.match(line_pattern, line)
51+
if result:
52+
file_path, linenoStr, offsetStr, severity, msg = result.groups()
53+
54+
if file_path != "<string>": # live mode
55+
# results from other files can be included, but we cannot return
56+
# them.
57+
if document and document.path and not document.path.endswith(
58+
file_path):
59+
log.warning("discarding result for %s against %s", file_path,
60+
document.path)
61+
return None
62+
63+
lineno = int(linenoStr or 1) - 1 # 0-based line number
64+
offset = int(offsetStr or 1) - 1 # 0-based offset
65+
errno = 2
66+
if severity == 'error':
67+
errno = 1
68+
diag: Dict[str, Any] = {
69+
'source': 'mypy',
70+
'range': {
71+
'start': {'line': lineno, 'character': offset},
72+
# There may be a better solution, but mypy does not provide end
73+
'end': {'line': lineno, 'character': offset + 1}
74+
},
75+
'message': msg,
76+
'severity': errno
77+
}
78+
if document:
79+
# although mypy does not provide the end of the affected range, we
80+
# can make a good guess by highlighting the word that Mypy flagged
81+
word = document.word_at_position(diag['range']['start'])
82+
if word:
83+
diag['range']['end']['character'] = (
84+
diag['range']['start']['character'] + len(word))
85+
86+
return diag
87+
return None
88+
89+
90+
@hookimpl
91+
def pyls_lint(config: Config, workspace: Workspace, document: Document,
92+
is_saved: bool) -> List[Dict[str, Any]]:
93+
"""
94+
Lints.
95+
96+
Parameters
97+
----------
98+
config : Config
99+
The pyls config.
100+
workspace : Workspace
101+
The pyls workspace.
102+
document : Document
103+
The document to be linted.
104+
is_saved : bool
105+
Weather the document is saved.
106+
107+
Returns
108+
-------
109+
List[Dict[str, Any]]
110+
List of the linting data.
111+
112+
"""
113+
settings = config.plugin_settings('mypy-ls')
114+
live_mode = settings.get('live_mode', True)
115+
args = ['--incremental',
116+
'--show-column-numbers',
117+
'--follow-imports', 'silent']
118+
119+
global tmpFile
120+
if live_mode and not is_saved and tmpFile:
121+
tmpFile = open(tmpFile.name, "w")
122+
tmpFile.write(document.source)
123+
tmpFile.close()
124+
args.extend(['--shadow-file', document.path, tmpFile.name])
125+
elif not is_saved:
126+
return []
127+
128+
if mypyConfigFile:
129+
args.append('--config-file')
130+
args.append(mypyConfigFile)
131+
args.append(document.path)
132+
if settings.get('strict', False):
133+
args.append('--strict')
134+
135+
report, errors, _ = mypy_api.run(args)
136+
137+
diagnostics = []
138+
for line in report.splitlines():
139+
diag = parse_line(line, document)
140+
if diag:
141+
diagnostics.append(diag)
142+
143+
return diagnostics
144+
145+
146+
@hookimpl
147+
def pyls_settings(config: Config) -> Dict[str, Dict[str, Dict[str, str]]]:
148+
"""
149+
Read the settings.
150+
151+
Parameters
152+
----------
153+
config : Config
154+
The pyls config.
155+
156+
Returns
157+
-------
158+
Dict[str, Dict[str, Dict[str, str]]]
159+
The config dict.
160+
161+
"""
162+
configuration = init(config._root_path)
163+
return {"plugins": {"mypy-ls": configuration}}
164+
165+
166+
def init(workspace: str) -> Dict[str, str]:
167+
"""
168+
Find plugin and mypy config files and creates the temp file should it be used.
169+
170+
Parameters
171+
----------
172+
workspace : str
173+
The path to the current workspace.
174+
175+
Returns
176+
-------
177+
Dict[str, str]
178+
The plugin config dict.
179+
180+
"""
181+
configuration = {}
182+
path = findConfigFile(workspace, "mypy-ls.cfg")
183+
if path:
184+
with open(path) as file:
185+
configuration = eval(file.read())
186+
global mypyConfigFile
187+
mypyConfigFile = findConfigFile(workspace, "mypy.ini")
188+
if (("enabled" not in configuration or configuration["enabled"])
189+
and ("live_mode" not in configuration or configuration["live_mode"])):
190+
global tmpFile
191+
tmpFile = tempfile.NamedTemporaryFile('w', delete=False)
192+
tmpFile.close()
193+
return configuration
194+
195+
196+
def findConfigFile(path: str, name: str) -> Optional[str]:
197+
"""
198+
Search for a config file.
199+
200+
Search for a file of a given name from the directory specifyed by path through all parent
201+
directories. The first file found is selected.
202+
203+
Parameters
204+
----------
205+
path : str
206+
The path where the search starts.
207+
name : str
208+
The file to be found.
209+
210+
Returns
211+
-------
212+
Optional[str]
213+
The path where the file has been found or None if no matching file has been found.
214+
215+
"""
216+
while True:
217+
p = f"{path}\\{name}"
218+
if os.path.isfile(p):
219+
return p
220+
else:
221+
loc = path.rfind("\\")
222+
if loc == -1:
223+
return None
224+
path = path[:loc]
225+
226+
227+
@atexit.register
228+
def close() -> None:
229+
"""
230+
Deltes the tempFile should it exist.
231+
232+
Returns
233+
-------
234+
None.
235+
236+
"""
237+
if tmpFile and tmpFile.name:
238+
os.unlink(tmpFile.name)

pyls_mypy/_version.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

pyls_mypy/plugin.py

Lines changed: 0 additions & 104 deletions
This file was deleted.

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ install_requires =
2727

2828

2929
[options.entry_points]
30-
pyls = pyls_mypy = pyls_mypy.plugin
30+
pyls = mypy_ls = mypy_ls.plugin
3131

3232
[options.extras_require]
3333
test =

0 commit comments

Comments
 (0)