deepnote-python-lsp-server 1.13.1rc2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deepnote_python_lsp_server-1.13.1rc2.dist-info/METADATA +264 -0
- deepnote_python_lsp_server-1.13.1rc2.dist-info/RECORD +46 -0
- deepnote_python_lsp_server-1.13.1rc2.dist-info/WHEEL +5 -0
- deepnote_python_lsp_server-1.13.1rc2.dist-info/entry_points.txt +25 -0
- deepnote_python_lsp_server-1.13.1rc2.dist-info/licenses/LICENSE +22 -0
- deepnote_python_lsp_server-1.13.1rc2.dist-info/top_level.txt +1 -0
- pylsp/__init__.py +33 -0
- pylsp/__main__.py +132 -0
- pylsp/_utils.py +418 -0
- pylsp/_version.py +1 -0
- pylsp/config/__init__.py +2 -0
- pylsp/config/config.py +203 -0
- pylsp/config/flake8_conf.py +64 -0
- pylsp/config/pycodestyle_conf.py +34 -0
- pylsp/config/source.py +88 -0
- pylsp/hookspecs.py +150 -0
- pylsp/lsp.py +115 -0
- pylsp/plugins/__init__.py +2 -0
- pylsp/plugins/_resolvers.py +132 -0
- pylsp/plugins/_rope_task_handle.py +107 -0
- pylsp/plugins/autopep8_format.py +95 -0
- pylsp/plugins/definition.py +84 -0
- pylsp/plugins/flake8_lint.py +243 -0
- pylsp/plugins/folding.py +210 -0
- pylsp/plugins/highlight.py +36 -0
- pylsp/plugins/hover.py +59 -0
- pylsp/plugins/jedi_completion.py +337 -0
- pylsp/plugins/jedi_rename.py +56 -0
- pylsp/plugins/mccabe_lint.py +56 -0
- pylsp/plugins/preload_imports.py +72 -0
- pylsp/plugins/pycodestyle_lint.py +113 -0
- pylsp/plugins/pydocstyle_lint.py +127 -0
- pylsp/plugins/pyflakes_lint.py +97 -0
- pylsp/plugins/pylint_lint.py +367 -0
- pylsp/plugins/references.py +33 -0
- pylsp/plugins/rope_autoimport.py +408 -0
- pylsp/plugins/rope_completion.py +161 -0
- pylsp/plugins/signature.py +81 -0
- pylsp/plugins/symbols.py +219 -0
- pylsp/plugins/type_definition.py +38 -0
- pylsp/plugins/yapf_format.py +199 -0
- pylsp/py.typed +0 -0
- pylsp/python_lsp.py +945 -0
- pylsp/text_edit.py +97 -0
- pylsp/uris.py +139 -0
- pylsp/workspace.py +791 -0
pylsp/plugins/hover.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Copyright 2017-2020 Palantir Technologies, Inc.
|
|
2
|
+
# Copyright 2021- Python Language Server Contributors.
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from pylsp import _utils, hookimpl
|
|
7
|
+
|
|
8
|
+
log = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@hookimpl
|
|
12
|
+
def pylsp_hover(config, document, position):
|
|
13
|
+
code_position = _utils.position_to_jedi_linecolumn(document, position)
|
|
14
|
+
definitions = document.jedi_script(use_document_path=True).infer(**code_position)
|
|
15
|
+
word = document.word_at_position(position)
|
|
16
|
+
|
|
17
|
+
# Find first exact matching definition
|
|
18
|
+
definition = next((x for x in definitions if x.name == word), None)
|
|
19
|
+
|
|
20
|
+
# Ensure a definition is used if only one is available
|
|
21
|
+
# even if the word doesn't match. An example of this case is 'np'
|
|
22
|
+
# where 'numpy' doesn't match with 'np'. Same for NumPy ufuncs
|
|
23
|
+
if len(definitions) == 1:
|
|
24
|
+
definition = definitions[0]
|
|
25
|
+
|
|
26
|
+
if not definition:
|
|
27
|
+
return {"contents": ""}
|
|
28
|
+
|
|
29
|
+
hover_capabilities = config.capabilities.get("textDocument", {}).get("hover", {})
|
|
30
|
+
supported_markup_kinds = hover_capabilities.get("contentFormat", ["markdown"])
|
|
31
|
+
preferred_markup_kind = _utils.choose_markup_kind(supported_markup_kinds)
|
|
32
|
+
|
|
33
|
+
doc = _utils.format_docstring(
|
|
34
|
+
definition.docstring(raw=True), preferred_markup_kind
|
|
35
|
+
)["value"]
|
|
36
|
+
|
|
37
|
+
# Find first exact matching signature
|
|
38
|
+
signature = next(
|
|
39
|
+
(
|
|
40
|
+
x.to_string()
|
|
41
|
+
for x in definition.get_signatures()
|
|
42
|
+
if (x.name == word and x.type not in ["module"])
|
|
43
|
+
),
|
|
44
|
+
"",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
contents = []
|
|
48
|
+
if signature:
|
|
49
|
+
contents.append(
|
|
50
|
+
{
|
|
51
|
+
"language": "python",
|
|
52
|
+
"value": signature,
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if doc:
|
|
57
|
+
contents.append(doc)
|
|
58
|
+
|
|
59
|
+
return {"contents": contents or ""}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# Copyright 2017-2020 Palantir Technologies, Inc.
|
|
2
|
+
# Copyright 2021- Python Language Server Contributors.
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
import parso
|
|
8
|
+
|
|
9
|
+
from pylsp import _utils, hookimpl, lsp
|
|
10
|
+
from pylsp.plugins._resolvers import LABEL_RESOLVER, SNIPPET_RESOLVER
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# Map to the LSP type
|
|
15
|
+
# > Valid values for type are ``module``, `` class ``, ``instance``, ``function``,
|
|
16
|
+
# > ``param``, ``path``, ``keyword``, ``property`` and ``statement``.
|
|
17
|
+
# see: https://jedi.readthedocs.io/en/latest/docs/api-classes.html#jedi.api.classes.BaseName.type
|
|
18
|
+
_TYPE_MAP = {
|
|
19
|
+
"module": lsp.CompletionItemKind.Module,
|
|
20
|
+
"namespace": lsp.CompletionItemKind.Module, # to be added in Jedi 0.18+
|
|
21
|
+
"class": lsp.CompletionItemKind.Class,
|
|
22
|
+
"instance": lsp.CompletionItemKind.Reference,
|
|
23
|
+
"function": lsp.CompletionItemKind.Function,
|
|
24
|
+
"param": lsp.CompletionItemKind.Variable,
|
|
25
|
+
"path": lsp.CompletionItemKind.File,
|
|
26
|
+
"keyword": lsp.CompletionItemKind.Keyword,
|
|
27
|
+
"property": lsp.CompletionItemKind.Property, # added in Jedi 0.18
|
|
28
|
+
"statement": lsp.CompletionItemKind.Variable,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
COMPLETION_CACHE = {}
|
|
32
|
+
|
|
33
|
+
# Types of parso nodes for which snippet is not included in the completion
|
|
34
|
+
_IMPORTS = ("import_name", "import_from")
|
|
35
|
+
|
|
36
|
+
# Types of parso node for errors
|
|
37
|
+
_ERRORS = ("error_node",)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@hookimpl
|
|
41
|
+
def pylsp_completions(config, document, position):
|
|
42
|
+
"""Get formatted completions for current code position"""
|
|
43
|
+
settings = config.plugin_settings("jedi_completion", document_path=document.path)
|
|
44
|
+
resolve_eagerly = settings.get("eager", False)
|
|
45
|
+
signature_config = config.settings().get("signature", {})
|
|
46
|
+
|
|
47
|
+
code_position = _utils.position_to_jedi_linecolumn(document, position)
|
|
48
|
+
code_position["fuzzy"] = settings.get("fuzzy", False)
|
|
49
|
+
completions = document.jedi_script(use_document_path=True).complete(**code_position)
|
|
50
|
+
|
|
51
|
+
if not completions:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
completion_capabilities = config.capabilities.get("textDocument", {}).get(
|
|
55
|
+
"completion", {}
|
|
56
|
+
)
|
|
57
|
+
item_capabilities = completion_capabilities.get("completionItem", {})
|
|
58
|
+
snippet_support = item_capabilities.get("snippetSupport")
|
|
59
|
+
supported_markup_kinds = item_capabilities.get("documentationFormat", ["markdown"])
|
|
60
|
+
preferred_markup_kind = _utils.choose_markup_kind(supported_markup_kinds)
|
|
61
|
+
|
|
62
|
+
should_include_params = settings.get("include_params")
|
|
63
|
+
should_include_class_objects = settings.get("include_class_objects", False)
|
|
64
|
+
should_include_function_objects = settings.get("include_function_objects", False)
|
|
65
|
+
|
|
66
|
+
max_to_resolve = settings.get("resolve_at_most", 25)
|
|
67
|
+
modules_to_cache_for = settings.get("cache_for", None)
|
|
68
|
+
if modules_to_cache_for is not None:
|
|
69
|
+
LABEL_RESOLVER.cached_modules = modules_to_cache_for
|
|
70
|
+
SNIPPET_RESOLVER.cached_modules = modules_to_cache_for
|
|
71
|
+
|
|
72
|
+
include_params = (
|
|
73
|
+
snippet_support and should_include_params and use_snippets(document, position)
|
|
74
|
+
)
|
|
75
|
+
include_class_objects = (
|
|
76
|
+
snippet_support
|
|
77
|
+
and should_include_class_objects
|
|
78
|
+
and use_snippets(document, position)
|
|
79
|
+
)
|
|
80
|
+
include_function_objects = (
|
|
81
|
+
snippet_support
|
|
82
|
+
and should_include_function_objects
|
|
83
|
+
and use_snippets(document, position)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
ready_completions = [
|
|
87
|
+
_format_completion(
|
|
88
|
+
c,
|
|
89
|
+
markup_kind=preferred_markup_kind,
|
|
90
|
+
include_params=include_params if c.type in ["class", "function"] else False,
|
|
91
|
+
resolve=resolve_eagerly,
|
|
92
|
+
resolve_label_or_snippet=(i < max_to_resolve),
|
|
93
|
+
snippet_support=snippet_support,
|
|
94
|
+
signature_config=signature_config,
|
|
95
|
+
)
|
|
96
|
+
for i, c in enumerate(completions)
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
# TODO split up once other improvements are merged
|
|
100
|
+
if include_class_objects:
|
|
101
|
+
for i, c in enumerate(completions):
|
|
102
|
+
if c.type == "class":
|
|
103
|
+
completion_dict = _format_completion(
|
|
104
|
+
c,
|
|
105
|
+
markup_kind=preferred_markup_kind,
|
|
106
|
+
include_params=False,
|
|
107
|
+
resolve=resolve_eagerly,
|
|
108
|
+
resolve_label_or_snippet=(i < max_to_resolve),
|
|
109
|
+
snippet_support=snippet_support,
|
|
110
|
+
signature_config=signature_config,
|
|
111
|
+
)
|
|
112
|
+
completion_dict["kind"] = lsp.CompletionItemKind.TypeParameter
|
|
113
|
+
completion_dict["label"] += " object"
|
|
114
|
+
ready_completions.append(completion_dict)
|
|
115
|
+
|
|
116
|
+
if include_function_objects:
|
|
117
|
+
for i, c in enumerate(completions):
|
|
118
|
+
if c.type == "function":
|
|
119
|
+
completion_dict = _format_completion(
|
|
120
|
+
c,
|
|
121
|
+
markup_kind=preferred_markup_kind,
|
|
122
|
+
include_params=False,
|
|
123
|
+
resolve=resolve_eagerly,
|
|
124
|
+
resolve_label_or_snippet=(i < max_to_resolve),
|
|
125
|
+
snippet_support=snippet_support,
|
|
126
|
+
signature_config=signature_config,
|
|
127
|
+
)
|
|
128
|
+
completion_dict["kind"] = lsp.CompletionItemKind.TypeParameter
|
|
129
|
+
completion_dict["label"] += " object"
|
|
130
|
+
ready_completions.append(completion_dict)
|
|
131
|
+
|
|
132
|
+
for completion_dict in ready_completions:
|
|
133
|
+
completion_dict["data"] = {"doc_uri": document.uri}
|
|
134
|
+
|
|
135
|
+
# most recently retrieved completion items, used for resolution
|
|
136
|
+
document.shared_data["LAST_JEDI_COMPLETIONS"] = {
|
|
137
|
+
# label is the only required property; here it is assumed to be unique
|
|
138
|
+
completion["label"]: (completion, data)
|
|
139
|
+
for completion, data in zip(ready_completions, completions)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return ready_completions or None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@hookimpl
|
|
146
|
+
def pylsp_completion_detail(config, item):
|
|
147
|
+
d = COMPLETION_CACHE.get(item)
|
|
148
|
+
if d:
|
|
149
|
+
completion = {
|
|
150
|
+
"label": "", # _label(d),
|
|
151
|
+
"kind": _TYPE_MAP.get(d.type),
|
|
152
|
+
"detail": "", # _detail(d),
|
|
153
|
+
"documentation": _utils.format_docstring(d.docstring()),
|
|
154
|
+
"sortText": "", # _sort_text(d),
|
|
155
|
+
"insertText": d.name,
|
|
156
|
+
}
|
|
157
|
+
return completion
|
|
158
|
+
else:
|
|
159
|
+
log.info("Completion missing")
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@hookimpl
|
|
164
|
+
def pylsp_completion_item_resolve(
|
|
165
|
+
config,
|
|
166
|
+
completion_item,
|
|
167
|
+
document,
|
|
168
|
+
):
|
|
169
|
+
"""Resolve formatted completion for given non-resolved completion"""
|
|
170
|
+
shared_data = document.shared_data["LAST_JEDI_COMPLETIONS"].get(
|
|
171
|
+
completion_item["label"]
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
completion_capabilities = config.capabilities.get("textDocument", {}).get(
|
|
175
|
+
"completion", {}
|
|
176
|
+
)
|
|
177
|
+
item_capabilities = completion_capabilities.get("completionItem", {})
|
|
178
|
+
supported_markup_kinds = item_capabilities.get("documentationFormat", ["markdown"])
|
|
179
|
+
preferred_markup_kind = _utils.choose_markup_kind(supported_markup_kinds)
|
|
180
|
+
|
|
181
|
+
if shared_data:
|
|
182
|
+
completion, data = shared_data
|
|
183
|
+
return _resolve_completion(
|
|
184
|
+
completion,
|
|
185
|
+
data,
|
|
186
|
+
markup_kind=preferred_markup_kind,
|
|
187
|
+
signature_config=config.settings().get("signature", {}),
|
|
188
|
+
)
|
|
189
|
+
return completion_item
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def is_exception_class(name):
|
|
193
|
+
"""
|
|
194
|
+
Determine if a class name is an instance of an Exception.
|
|
195
|
+
|
|
196
|
+
This returns `False` if the name given corresponds with a instance of
|
|
197
|
+
the 'Exception' class, `True` otherwise
|
|
198
|
+
"""
|
|
199
|
+
try:
|
|
200
|
+
return name in [cls.__name__ for cls in Exception.__subclasses__()]
|
|
201
|
+
except AttributeError:
|
|
202
|
+
# Needed in case a class don't uses new-style
|
|
203
|
+
# class definition in Python 2
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def use_snippets(document, position):
|
|
208
|
+
"""
|
|
209
|
+
Determine if it's necessary to return snippets in code completions.
|
|
210
|
+
|
|
211
|
+
This returns `False` if a completion is being requested on an import
|
|
212
|
+
statement, `True` otherwise.
|
|
213
|
+
"""
|
|
214
|
+
line = position["line"]
|
|
215
|
+
lines = document.source.split("\n", line)
|
|
216
|
+
act_lines = [lines[line][: position["character"]]]
|
|
217
|
+
line -= 1
|
|
218
|
+
last_character = ""
|
|
219
|
+
while line > -1:
|
|
220
|
+
act_line = lines[line]
|
|
221
|
+
if (
|
|
222
|
+
act_line.rstrip().endswith("\\")
|
|
223
|
+
or act_line.rstrip().endswith("(")
|
|
224
|
+
or act_line.rstrip().endswith(",")
|
|
225
|
+
):
|
|
226
|
+
act_lines.insert(0, act_line)
|
|
227
|
+
line -= 1
|
|
228
|
+
if act_line.rstrip().endswith("("):
|
|
229
|
+
# Needs to be added to the end of the code before parsing
|
|
230
|
+
# to make it valid, otherwise the node type could end
|
|
231
|
+
# being an 'error_node' for multi-line imports that use '('
|
|
232
|
+
last_character = ")"
|
|
233
|
+
else:
|
|
234
|
+
break
|
|
235
|
+
if "(" in act_lines[-1].strip():
|
|
236
|
+
last_character = ")"
|
|
237
|
+
code = "\n".join(act_lines).rsplit(";", maxsplit=1)[-1].strip() + last_character
|
|
238
|
+
tokens = parso.parse(code)
|
|
239
|
+
expr_type = tokens.children[0].type
|
|
240
|
+
return expr_type not in _IMPORTS and not (expr_type in _ERRORS and "import" in code)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _resolve_completion(completion, d, markup_kind: str, signature_config: dict):
|
|
244
|
+
completion["detail"] = _detail(d)
|
|
245
|
+
try:
|
|
246
|
+
docs = _utils.format_docstring(
|
|
247
|
+
d.docstring(raw=True),
|
|
248
|
+
signatures=[signature.to_string() for signature in d.get_signatures()],
|
|
249
|
+
markup_kind=markup_kind,
|
|
250
|
+
signature_config=signature_config,
|
|
251
|
+
)
|
|
252
|
+
except Exception:
|
|
253
|
+
docs = ""
|
|
254
|
+
completion["documentation"] = docs
|
|
255
|
+
return completion
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _format_completion(
|
|
259
|
+
d,
|
|
260
|
+
markup_kind: str,
|
|
261
|
+
include_params=True,
|
|
262
|
+
resolve=False,
|
|
263
|
+
resolve_label_or_snippet=False,
|
|
264
|
+
snippet_support=False,
|
|
265
|
+
signature_config=None,
|
|
266
|
+
):
|
|
267
|
+
COMPLETION_CACHE[d.name] = d
|
|
268
|
+
completion = {
|
|
269
|
+
"label": _label(d, resolve_label_or_snippet),
|
|
270
|
+
"kind": _TYPE_MAP.get(d.type),
|
|
271
|
+
"sortText": _sort_text(d),
|
|
272
|
+
"insertText": d.name,
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if resolve:
|
|
276
|
+
completion = _resolve_completion(
|
|
277
|
+
completion, d, markup_kind, signature_config=signature_config
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Adjustments for file completions
|
|
281
|
+
if d.type == "path":
|
|
282
|
+
path = os.path.normpath(d.name)
|
|
283
|
+
|
|
284
|
+
# If the completion ends with os.sep, it means it's a directory. So we add os.sep at the end
|
|
285
|
+
# to ease additional file completions.
|
|
286
|
+
if d.name.endswith(os.sep):
|
|
287
|
+
if os.name == "nt":
|
|
288
|
+
path = path + "\\"
|
|
289
|
+
else:
|
|
290
|
+
path = path + "/"
|
|
291
|
+
|
|
292
|
+
# Escape to prevent conflicts with the code snippets grammer
|
|
293
|
+
# See also https://github.com/python-lsp/python-lsp-server/issues/373
|
|
294
|
+
if snippet_support:
|
|
295
|
+
path = path.replace("\\", "\\\\")
|
|
296
|
+
path = path.replace("/", "\\/")
|
|
297
|
+
|
|
298
|
+
completion["insertText"] = path
|
|
299
|
+
|
|
300
|
+
if include_params and not is_exception_class(d.name):
|
|
301
|
+
snippet = _snippet(d, resolve_label_or_snippet)
|
|
302
|
+
completion.update(snippet)
|
|
303
|
+
|
|
304
|
+
return completion
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _label(definition, resolve=False):
|
|
308
|
+
if not resolve:
|
|
309
|
+
return definition.name
|
|
310
|
+
sig = LABEL_RESOLVER.get_or_create(definition)
|
|
311
|
+
if sig:
|
|
312
|
+
return sig
|
|
313
|
+
return definition.name
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _snippet(definition, resolve=False):
|
|
317
|
+
if not resolve:
|
|
318
|
+
return {}
|
|
319
|
+
snippet = SNIPPET_RESOLVER.get_or_create(definition)
|
|
320
|
+
return snippet
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _detail(definition):
|
|
324
|
+
try:
|
|
325
|
+
return definition.parent().full_name or ""
|
|
326
|
+
except AttributeError:
|
|
327
|
+
return definition.full_name or ""
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _sort_text(definition):
|
|
331
|
+
"""Ensure builtins appear at the bottom.
|
|
332
|
+
Description is of format <type>: <module>.<item>
|
|
333
|
+
"""
|
|
334
|
+
|
|
335
|
+
# If its 'hidden', put it next last
|
|
336
|
+
prefix = "z{}" if definition.name.startswith("_") else "a{}"
|
|
337
|
+
return prefix.format(definition.name)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Copyright 2017-2020 Palantir Technologies, Inc.
|
|
2
|
+
# Copyright 2021- Python Language Server Contributors.
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from pylsp import _utils, hookimpl, uris
|
|
7
|
+
|
|
8
|
+
log = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@hookimpl
|
|
12
|
+
def pylsp_rename(config, workspace, document, position, new_name):
|
|
13
|
+
log.debug(
|
|
14
|
+
"Executing rename of %s to %s", document.word_at_position(position), new_name
|
|
15
|
+
)
|
|
16
|
+
kwargs = _utils.position_to_jedi_linecolumn(document, position)
|
|
17
|
+
kwargs["new_name"] = new_name
|
|
18
|
+
try:
|
|
19
|
+
refactoring = document.jedi_script().rename(**kwargs)
|
|
20
|
+
except NotImplementedError as exc:
|
|
21
|
+
raise Exception(
|
|
22
|
+
"No support for renaming in Python 2/3.5 with Jedi. "
|
|
23
|
+
"Consider using the pylsp-rope plugin instead"
|
|
24
|
+
) from exc
|
|
25
|
+
log.debug("Finished rename: %s", refactoring.get_diff())
|
|
26
|
+
changes = []
|
|
27
|
+
|
|
28
|
+
changed_files = refactoring.get_changed_files()
|
|
29
|
+
for file_path, changed_file in changed_files.items():
|
|
30
|
+
uri = uris.from_fs_path(str(file_path))
|
|
31
|
+
doc = workspace.get_maybe_document(uri)
|
|
32
|
+
changes.append(
|
|
33
|
+
{
|
|
34
|
+
"textDocument": {"uri": uri, "version": doc.version if doc else None},
|
|
35
|
+
"edits": [
|
|
36
|
+
{
|
|
37
|
+
"range": {
|
|
38
|
+
"start": {"line": 0, "character": 0},
|
|
39
|
+
"end": {
|
|
40
|
+
"line": _num_lines(changed_file.get_new_code()),
|
|
41
|
+
"character": 0,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
"newText": changed_file.get_new_code(),
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
return {"documentChanges": changes}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _num_lines(file_contents):
|
|
53
|
+
"Count the number of lines in the given string."
|
|
54
|
+
if _utils.get_eol_chars(file_contents):
|
|
55
|
+
return len(file_contents.splitlines())
|
|
56
|
+
return 0
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Copyright 2017-2020 Palantir Technologies, Inc.
|
|
2
|
+
# Copyright 2021- Python Language Server Contributors.
|
|
3
|
+
|
|
4
|
+
import ast
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
import mccabe
|
|
8
|
+
|
|
9
|
+
from pylsp import hookimpl, lsp
|
|
10
|
+
|
|
11
|
+
log = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
THRESHOLD = "threshold"
|
|
14
|
+
DEFAULT_THRESHOLD = 15
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@hookimpl
|
|
18
|
+
def pylsp_lint(config, workspace, document):
|
|
19
|
+
with workspace.report_progress("lint: mccabe"):
|
|
20
|
+
threshold = config.plugin_settings("mccabe", document_path=document.path).get(
|
|
21
|
+
THRESHOLD, DEFAULT_THRESHOLD
|
|
22
|
+
)
|
|
23
|
+
log.debug("Running mccabe lint with threshold: %s", threshold)
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
tree = compile(document.source, document.path, "exec", ast.PyCF_ONLY_AST)
|
|
27
|
+
except SyntaxError:
|
|
28
|
+
# We'll let the other linters point this one out
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
visitor = mccabe.PathGraphingAstVisitor()
|
|
32
|
+
visitor.preorder(tree, visitor)
|
|
33
|
+
|
|
34
|
+
diags = []
|
|
35
|
+
for graph in visitor.graphs.values():
|
|
36
|
+
if graph.complexity() >= threshold:
|
|
37
|
+
diags.append(
|
|
38
|
+
{
|
|
39
|
+
"source": "mccabe",
|
|
40
|
+
"range": {
|
|
41
|
+
"start": {
|
|
42
|
+
"line": graph.lineno - 1,
|
|
43
|
+
"character": graph.column,
|
|
44
|
+
},
|
|
45
|
+
"end": {
|
|
46
|
+
"line": graph.lineno - 1,
|
|
47
|
+
"character": len(document.lines[graph.lineno]),
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
"message": "Cyclomatic complexity too high: %s (threshold %s)"
|
|
51
|
+
% (graph.complexity(), threshold),
|
|
52
|
+
"severity": lsp.DiagnosticSeverity.Warning,
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return diags
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Copyright 2017-2020 Palantir Technologies, Inc.
|
|
2
|
+
# Copyright 2021- Python Language Server Contributors.
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from pylsp import hookimpl
|
|
7
|
+
|
|
8
|
+
log = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
MODULES = [
|
|
11
|
+
"numpy",
|
|
12
|
+
"tensorflow",
|
|
13
|
+
"sklearn",
|
|
14
|
+
"array",
|
|
15
|
+
"binascii",
|
|
16
|
+
"cmath",
|
|
17
|
+
"collections",
|
|
18
|
+
"datetime",
|
|
19
|
+
"errno",
|
|
20
|
+
"exceptions",
|
|
21
|
+
"gc",
|
|
22
|
+
"imageop",
|
|
23
|
+
"imp",
|
|
24
|
+
"itertools",
|
|
25
|
+
"marshal",
|
|
26
|
+
"math",
|
|
27
|
+
"matplotlib",
|
|
28
|
+
"mmap",
|
|
29
|
+
"mpmath",
|
|
30
|
+
"msvcrt",
|
|
31
|
+
"networkx",
|
|
32
|
+
"nose",
|
|
33
|
+
"nt",
|
|
34
|
+
"operator",
|
|
35
|
+
"os",
|
|
36
|
+
"os.path",
|
|
37
|
+
"pandas",
|
|
38
|
+
"parser",
|
|
39
|
+
"scipy",
|
|
40
|
+
"signal",
|
|
41
|
+
"skimage",
|
|
42
|
+
"statsmodels",
|
|
43
|
+
"strop",
|
|
44
|
+
"sympy",
|
|
45
|
+
"sys",
|
|
46
|
+
"thread",
|
|
47
|
+
"time",
|
|
48
|
+
"wx",
|
|
49
|
+
"zlib",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@hookimpl
|
|
54
|
+
def pylsp_settings():
|
|
55
|
+
# Setup default modules to preload, and rope extension modules
|
|
56
|
+
return {
|
|
57
|
+
"plugins": {"preload": {"modules": MODULES}},
|
|
58
|
+
"rope": {"extensionModules": MODULES},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@hookimpl
|
|
63
|
+
def pylsp_initialize(config) -> None:
|
|
64
|
+
for mod_name in config.plugin_settings("preload").get("modules", []):
|
|
65
|
+
try:
|
|
66
|
+
__import__(mod_name)
|
|
67
|
+
log.debug("Preloaded module %s", mod_name)
|
|
68
|
+
except Exception:
|
|
69
|
+
# Catch any exception since not only ImportError can be raised here
|
|
70
|
+
# For example, old versions of NumPy can cause a ValueError.
|
|
71
|
+
# See spyder-ide/spyder#13985
|
|
72
|
+
pass
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Copyright 2017-2020 Palantir Technologies, Inc.
|
|
2
|
+
# Copyright 2021- Python Language Server Contributors.
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
import pycodestyle
|
|
7
|
+
|
|
8
|
+
from pylsp import hookimpl, lsp
|
|
9
|
+
from pylsp._utils import get_eol_chars
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from autopep8 import continued_indentation as autopep8_c_i
|
|
13
|
+
except ImportError:
|
|
14
|
+
pass
|
|
15
|
+
else:
|
|
16
|
+
# Check if autopep8's continued_indentation implementation
|
|
17
|
+
# is overriding pycodestyle's and if so, re-register
|
|
18
|
+
# the check using pycodestyle's implementation as expected
|
|
19
|
+
if autopep8_c_i in pycodestyle._checks["logical_line"]:
|
|
20
|
+
del pycodestyle._checks["logical_line"][autopep8_c_i]
|
|
21
|
+
pycodestyle.register_check(pycodestyle.continued_indentation)
|
|
22
|
+
|
|
23
|
+
log = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@hookimpl
|
|
27
|
+
def pylsp_lint(workspace, document):
|
|
28
|
+
with workspace.report_progress("lint: pycodestyle"):
|
|
29
|
+
config = workspace._config
|
|
30
|
+
settings = config.plugin_settings("pycodestyle", document_path=document.path)
|
|
31
|
+
log.debug("Got pycodestyle settings: %s", settings)
|
|
32
|
+
|
|
33
|
+
opts = {
|
|
34
|
+
"exclude": settings.get("exclude"),
|
|
35
|
+
"filename": settings.get("filename"),
|
|
36
|
+
"hang_closing": settings.get("hangClosing"),
|
|
37
|
+
"ignore": settings.get("ignore"),
|
|
38
|
+
"max_line_length": settings.get("maxLineLength"),
|
|
39
|
+
"indent_size": settings.get("indentSize"),
|
|
40
|
+
"select": settings.get("select"),
|
|
41
|
+
}
|
|
42
|
+
kwargs = {k: v for k, v in opts.items() if v}
|
|
43
|
+
styleguide = pycodestyle.StyleGuide(kwargs)
|
|
44
|
+
|
|
45
|
+
# Use LF to lint file because other line endings can give false positives.
|
|
46
|
+
# See spyder-ide/spyder#19565 for context.
|
|
47
|
+
source = document.source
|
|
48
|
+
eol_chars = get_eol_chars(source)
|
|
49
|
+
if eol_chars in ["\r", "\r\n"]:
|
|
50
|
+
source = source.replace(eol_chars, "\n")
|
|
51
|
+
lines = source.splitlines(keepends=True)
|
|
52
|
+
else:
|
|
53
|
+
lines = document.lines
|
|
54
|
+
|
|
55
|
+
c = pycodestyle.Checker(
|
|
56
|
+
filename=document.path,
|
|
57
|
+
lines=lines,
|
|
58
|
+
options=styleguide.options,
|
|
59
|
+
report=PyCodeStyleDiagnosticReport(styleguide.options),
|
|
60
|
+
)
|
|
61
|
+
c.check_all()
|
|
62
|
+
diagnostics = c.report.diagnostics
|
|
63
|
+
|
|
64
|
+
return diagnostics
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class PyCodeStyleDiagnosticReport(pycodestyle.BaseReport):
|
|
68
|
+
def __init__(self, options) -> None:
|
|
69
|
+
self.diagnostics = []
|
|
70
|
+
super().__init__(options=options)
|
|
71
|
+
|
|
72
|
+
def error(self, line_number, offset, text, check):
|
|
73
|
+
code = text[:4]
|
|
74
|
+
if self._ignore_code(code):
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
# Don't care about expected errors or warnings
|
|
78
|
+
if code in self.expected:
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
# PyCodeStyle will sometimes give you an error the line after the end of the file
|
|
82
|
+
# e.g. no newline at end of file
|
|
83
|
+
# In that case, the end offset should just be some number ~100
|
|
84
|
+
# (because why not? There's nothing to underline anyways)
|
|
85
|
+
err_range = {
|
|
86
|
+
"start": {"line": line_number - 1, "character": offset},
|
|
87
|
+
"end": {
|
|
88
|
+
# FIXME: It's a little naiive to mark until the end of the line, can we not easily do better?
|
|
89
|
+
"line": line_number - 1,
|
|
90
|
+
"character": 100
|
|
91
|
+
if line_number > len(self.lines)
|
|
92
|
+
else len(self.lines[line_number - 1]),
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
diagnostic = {
|
|
96
|
+
"source": "pycodestyle",
|
|
97
|
+
"range": err_range,
|
|
98
|
+
"message": text,
|
|
99
|
+
"code": code,
|
|
100
|
+
# Are style errors really ever errors?
|
|
101
|
+
"severity": _get_severity(code),
|
|
102
|
+
}
|
|
103
|
+
if code.startswith("W6"):
|
|
104
|
+
diagnostic["tags"] = [lsp.DiagnosticTag.Deprecated]
|
|
105
|
+
self.diagnostics.append(diagnostic)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _get_severity(code):
|
|
109
|
+
# Are style errors ever really errors?
|
|
110
|
+
if code[0] == "E" or code[0] == "W":
|
|
111
|
+
return lsp.DiagnosticSeverity.Warning
|
|
112
|
+
# If no severity is specified, why wouldn't this be informational only?
|
|
113
|
+
return lsp.DiagnosticSeverity.Information
|