deepnote-python-lsp-server 0.0.0__py2.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.
Files changed (43) hide show
  1. deepnote_python_lsp_server-0.0.0.dist-info/METADATA +263 -0
  2. deepnote_python_lsp_server-0.0.0.dist-info/RECORD +43 -0
  3. deepnote_python_lsp_server-0.0.0.dist-info/WHEEL +6 -0
  4. deepnote_python_lsp_server-0.0.0.dist-info/entry_points.txt +24 -0
  5. deepnote_python_lsp_server-0.0.0.dist-info/licenses/LICENSE +22 -0
  6. deepnote_python_lsp_server-0.0.0.dist-info/top_level.txt +1 -0
  7. pylsp/__init__.py +33 -0
  8. pylsp/__main__.py +132 -0
  9. pylsp/_utils.py +325 -0
  10. pylsp/config/__init__.py +2 -0
  11. pylsp/config/config.py +202 -0
  12. pylsp/config/flake8_conf.py +64 -0
  13. pylsp/config/pycodestyle_conf.py +34 -0
  14. pylsp/config/source.py +88 -0
  15. pylsp/hookspecs.py +140 -0
  16. pylsp/lsp.py +115 -0
  17. pylsp/plugins/__init__.py +2 -0
  18. pylsp/plugins/_resolvers.py +132 -0
  19. pylsp/plugins/_rope_task_handle.py +106 -0
  20. pylsp/plugins/autopep8_format.py +95 -0
  21. pylsp/plugins/definition.py +84 -0
  22. pylsp/plugins/flake8_lint.py +243 -0
  23. pylsp/plugins/folding.py +210 -0
  24. pylsp/plugins/highlight.py +36 -0
  25. pylsp/plugins/hover.py +57 -0
  26. pylsp/plugins/jedi_completion.py +318 -0
  27. pylsp/plugins/jedi_rename.py +56 -0
  28. pylsp/plugins/mccabe_lint.py +56 -0
  29. pylsp/plugins/preload_imports.py +38 -0
  30. pylsp/plugins/pycodestyle_lint.py +113 -0
  31. pylsp/plugins/pydocstyle_lint.py +127 -0
  32. pylsp/plugins/pyflakes_lint.py +97 -0
  33. pylsp/plugins/pylint_lint.py +367 -0
  34. pylsp/plugins/references.py +33 -0
  35. pylsp/plugins/rope_autoimport.py +407 -0
  36. pylsp/plugins/rope_completion.py +161 -0
  37. pylsp/plugins/signature.py +81 -0
  38. pylsp/plugins/symbols.py +214 -0
  39. pylsp/plugins/yapf_format.py +199 -0
  40. pylsp/python_lsp.py +921 -0
  41. pylsp/text_edit.py +97 -0
  42. pylsp/uris.py +139 -0
  43. pylsp/workspace.py +730 -0
@@ -0,0 +1,318 @@
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
+ code_position = _utils.position_to_jedi_linecolumn(document, position)
46
+
47
+ code_position["fuzzy"] = settings.get("fuzzy", False)
48
+ completions = document.jedi_script(use_document_path=True).complete(**code_position)
49
+
50
+ if not completions:
51
+ return None
52
+
53
+ completion_capabilities = config.capabilities.get("textDocument", {}).get(
54
+ "completion", {}
55
+ )
56
+ item_capabilities = completion_capabilities.get("completionItem", {})
57
+ snippet_support = item_capabilities.get("snippetSupport")
58
+ supported_markup_kinds = item_capabilities.get("documentationFormat", ["markdown"])
59
+ preferred_markup_kind = _utils.choose_markup_kind(supported_markup_kinds)
60
+
61
+ should_include_params = settings.get("include_params")
62
+ should_include_class_objects = settings.get("include_class_objects", False)
63
+ should_include_function_objects = settings.get("include_function_objects", False)
64
+
65
+ max_to_resolve = settings.get("resolve_at_most", 25)
66
+ modules_to_cache_for = settings.get("cache_for", None)
67
+ if modules_to_cache_for is not None:
68
+ LABEL_RESOLVER.cached_modules = modules_to_cache_for
69
+ SNIPPET_RESOLVER.cached_modules = modules_to_cache_for
70
+
71
+ include_params = (
72
+ snippet_support and should_include_params and use_snippets(document, position)
73
+ )
74
+ include_class_objects = (
75
+ snippet_support
76
+ and should_include_class_objects
77
+ and use_snippets(document, position)
78
+ )
79
+ include_function_objects = (
80
+ snippet_support
81
+ and should_include_function_objects
82
+ and use_snippets(document, position)
83
+ )
84
+
85
+ ready_completions = [
86
+ _format_completion(
87
+ c,
88
+ markup_kind=preferred_markup_kind,
89
+ include_params=include_params if c.type in ["class", "function"] else False,
90
+ resolve=resolve_eagerly,
91
+ resolve_label_or_snippet=(i < max_to_resolve),
92
+ snippet_support=snippet_support,
93
+ )
94
+ for i, c in enumerate(completions)
95
+ ]
96
+
97
+ # TODO split up once other improvements are merged
98
+ if include_class_objects:
99
+ for i, c in enumerate(completions):
100
+ if c.type == "class":
101
+ completion_dict = _format_completion(
102
+ c,
103
+ markup_kind=preferred_markup_kind,
104
+ include_params=False,
105
+ resolve=resolve_eagerly,
106
+ resolve_label_or_snippet=(i < max_to_resolve),
107
+ snippet_support=snippet_support,
108
+ )
109
+ completion_dict["kind"] = lsp.CompletionItemKind.TypeParameter
110
+ completion_dict["label"] += " object"
111
+ ready_completions.append(completion_dict)
112
+
113
+ if include_function_objects:
114
+ for i, c in enumerate(completions):
115
+ if c.type == "function":
116
+ completion_dict = _format_completion(
117
+ c,
118
+ markup_kind=preferred_markup_kind,
119
+ include_params=False,
120
+ resolve=resolve_eagerly,
121
+ resolve_label_or_snippet=(i < max_to_resolve),
122
+ snippet_support=snippet_support,
123
+ )
124
+ completion_dict["kind"] = lsp.CompletionItemKind.TypeParameter
125
+ completion_dict["label"] += " object"
126
+ ready_completions.append(completion_dict)
127
+
128
+ for completion_dict in ready_completions:
129
+ completion_dict["data"] = {"doc_uri": document.uri}
130
+
131
+ # most recently retrieved completion items, used for resolution
132
+ document.shared_data["LAST_JEDI_COMPLETIONS"] = {
133
+ # label is the only required property; here it is assumed to be unique
134
+ completion["label"]: (completion, data)
135
+ for completion, data in zip(ready_completions, completions)
136
+ }
137
+
138
+ return ready_completions or None
139
+
140
+ @hookimpl
141
+ def pylsp_completion_detail(config, item):
142
+ d = COMPLETION_CACHE.get(item)
143
+ if d:
144
+ completion = {
145
+ 'label': '', #_label(d),
146
+ 'kind': _TYPE_MAP.get(d.type),
147
+ 'detail': '', #_detail(d),
148
+ 'documentation': _utils.format_docstring(d.docstring()),
149
+ 'sortText': '', #_sort_text(d),
150
+ 'insertText': d.name
151
+ }
152
+ return completion
153
+ else:
154
+ log.info('Completion missing')
155
+ return None
156
+
157
+ @hookimpl
158
+ def pylsp_completion_item_resolve(config, completion_item, document):
159
+ """Resolve formatted completion for given non-resolved completion"""
160
+ shared_data = document.shared_data["LAST_JEDI_COMPLETIONS"].get(
161
+ completion_item["label"]
162
+ )
163
+
164
+ completion_capabilities = config.capabilities.get("textDocument", {}).get(
165
+ "completion", {}
166
+ )
167
+ item_capabilities = completion_capabilities.get("completionItem", {})
168
+ supported_markup_kinds = item_capabilities.get("documentationFormat", ["markdown"])
169
+ preferred_markup_kind = _utils.choose_markup_kind(supported_markup_kinds)
170
+
171
+ if shared_data:
172
+ completion, data = shared_data
173
+ return _resolve_completion(completion, data, markup_kind=preferred_markup_kind)
174
+ return completion_item
175
+
176
+
177
+ def is_exception_class(name):
178
+ """
179
+ Determine if a class name is an instance of an Exception.
180
+
181
+ This returns `False` if the name given corresponds with a instance of
182
+ the 'Exception' class, `True` otherwise
183
+ """
184
+ try:
185
+ return name in [cls.__name__ for cls in Exception.__subclasses__()]
186
+ except AttributeError:
187
+ # Needed in case a class don't uses new-style
188
+ # class definition in Python 2
189
+ return False
190
+
191
+
192
+ def use_snippets(document, position):
193
+ """
194
+ Determine if it's necessary to return snippets in code completions.
195
+
196
+ This returns `False` if a completion is being requested on an import
197
+ statement, `True` otherwise.
198
+ """
199
+ line = position["line"]
200
+ lines = document.source.split("\n", line)
201
+ act_lines = [lines[line][: position["character"]]]
202
+ line -= 1
203
+ last_character = ""
204
+ while line > -1:
205
+ act_line = lines[line]
206
+ if (
207
+ act_line.rstrip().endswith("\\")
208
+ or act_line.rstrip().endswith("(")
209
+ or act_line.rstrip().endswith(",")
210
+ ):
211
+ act_lines.insert(0, act_line)
212
+ line -= 1
213
+ if act_line.rstrip().endswith("("):
214
+ # Needs to be added to the end of the code before parsing
215
+ # to make it valid, otherwise the node type could end
216
+ # being an 'error_node' for multi-line imports that use '('
217
+ last_character = ")"
218
+ else:
219
+ break
220
+ if "(" in act_lines[-1].strip():
221
+ last_character = ")"
222
+ code = "\n".join(act_lines).rsplit(";", maxsplit=1)[-1].strip() + last_character
223
+ tokens = parso.parse(code)
224
+ expr_type = tokens.children[0].type
225
+ return expr_type not in _IMPORTS and not (expr_type in _ERRORS and "import" in code)
226
+
227
+
228
+ def _resolve_completion(completion, d, markup_kind: str):
229
+ completion["detail"] = _detail(d)
230
+ try:
231
+ docs = _utils.format_docstring(
232
+ d.docstring(raw=True),
233
+ signatures=[signature.to_string() for signature in d.get_signatures()],
234
+ markup_kind=markup_kind,
235
+ )
236
+ except Exception:
237
+ docs = ""
238
+ completion["documentation"] = docs
239
+ return completion
240
+
241
+
242
+ def _format_completion(
243
+ d,
244
+ markup_kind: str,
245
+ include_params=True,
246
+ resolve=False,
247
+ resolve_label_or_snippet=False,
248
+ snippet_support=False,
249
+ ):
250
+ COMPLETION_CACHE[d.name] = d
251
+ completion = {
252
+ "label": _label(d, resolve_label_or_snippet),
253
+ "kind": _TYPE_MAP.get(d.type),
254
+ "sortText": _sort_text(d),
255
+ "insertText": d.name,
256
+ }
257
+
258
+ if resolve:
259
+ completion = _resolve_completion(completion, d, markup_kind)
260
+
261
+ # Adjustments for file completions
262
+ if d.type == "path":
263
+ path = os.path.normpath(d.name)
264
+
265
+ # If the completion ends with os.sep, it means it's a directory. So we add os.sep at the end
266
+ # to ease additional file completions.
267
+ if d.name.endswith(os.sep):
268
+ if os.name == "nt":
269
+ path = path + "\\"
270
+ else:
271
+ path = path + "/"
272
+
273
+ # Escape to prevent conflicts with the code snippets grammer
274
+ # See also https://github.com/python-lsp/python-lsp-server/issues/373
275
+ if snippet_support:
276
+ path = path.replace("\\", "\\\\")
277
+ path = path.replace("/", "\\/")
278
+
279
+ completion["insertText"] = path
280
+
281
+ if include_params and not is_exception_class(d.name):
282
+ snippet = _snippet(d, resolve_label_or_snippet)
283
+ completion.update(snippet)
284
+
285
+ return completion
286
+
287
+
288
+ def _label(definition, resolve=False):
289
+ if not resolve:
290
+ return definition.name
291
+ sig = LABEL_RESOLVER.get_or_create(definition)
292
+ if sig:
293
+ return sig
294
+ return definition.name
295
+
296
+
297
+ def _snippet(definition, resolve=False):
298
+ if not resolve:
299
+ return {}
300
+ snippet = SNIPPET_RESOLVER.get_or_create(definition)
301
+ return snippet
302
+
303
+
304
+ def _detail(definition):
305
+ try:
306
+ return definition.parent().full_name or ""
307
+ except AttributeError:
308
+ return definition.full_name or ""
309
+
310
+
311
+ def _sort_text(definition):
312
+ """Ensure builtins appear at the bottom.
313
+ Description is of format <type>: <module>.<item>
314
+ """
315
+
316
+ # If its 'hidden', put it next last
317
+ prefix = "z{}" if definition.name.startswith("_") else "a{}"
318
+ 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,38 @@
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", "tensorflow", "sklearn", "array", "binascii", "cmath", "collections",
12
+ "datetime", "errno", "exceptions", "gc", "imageop", "imp", "itertools",
13
+ "marshal", "math", "matplotlib", "mmap", "mpmath", "msvcrt", "networkx", "nose", "nt",
14
+ "operator", "os", "os.path", "pandas", "parser", "scipy", "signal",
15
+ "skimage", "statsmodels", "strop", "sympy", "sys", "thread", "time", "wx", "zlib"
16
+ ]
17
+
18
+
19
+ @hookimpl
20
+ def pylsp_settings():
21
+ # Setup default modules to preload, and rope extension modules
22
+ return {
23
+ "plugins": {"preload": {"modules": MODULES}},
24
+ "rope": {"extensionModules": MODULES},
25
+ }
26
+
27
+
28
+ @hookimpl
29
+ def pylsp_initialize(config) -> None:
30
+ for mod_name in config.plugin_settings("preload").get("modules", []):
31
+ try:
32
+ __import__(mod_name)
33
+ log.debug("Preloaded module %s", mod_name)
34
+ except Exception:
35
+ # Catch any exception since not only ImportError can be raised here
36
+ # For example, old versions of NumPy can cause a ValueError.
37
+ # See spyder-ide/spyder#13985
38
+ 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