vyper-language-server 0.2.2__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.
- vyper_language_server/__init__.py +1 -0
- vyper_language_server/__main__.py +30 -0
- vyper_language_server/analyzer/__init__.py +0 -0
- vyper_language_server/ast.py +390 -0
- vyper_language_server/debounce.py +19 -0
- vyper_language_server/grammar/__init__.py +0 -0
- vyper_language_server/grammar/grammar.lark +323 -0
- vyper_language_server/handlers/completion.py +201 -0
- vyper_language_server/handlers/hover.py +102 -0
- vyper_language_server/handlers/signatures.py +99 -0
- vyper_language_server/logging.py +20 -0
- vyper_language_server/main.py +186 -0
- vyper_language_server/navigation.py +184 -0
- vyper_language_server/utils.py +216 -0
- vyper_language_server-0.2.2.dist-info/METADATA +156 -0
- vyper_language_server-0.2.2.dist-info/RECORD +19 -0
- vyper_language_server-0.2.2.dist-info/WHEEL +4 -0
- vyper_language_server-0.2.2.dist-info/entry_points.txt +2 -0
- vyper_language_server-0.2.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .main import server # noqa: F401
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from .main import server
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
parser = argparse.ArgumentParser(
|
|
7
|
+
description="Start the server with specified protocol and options."
|
|
8
|
+
)
|
|
9
|
+
parser.add_argument("--stdio", action="store_true", help="Use stdio protocol")
|
|
10
|
+
parser.add_argument(
|
|
11
|
+
"--tcp",
|
|
12
|
+
nargs=2,
|
|
13
|
+
metavar=("HOST", "PORT"),
|
|
14
|
+
help="Use TCP with specified host and port",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
args = parser.parse_args()
|
|
18
|
+
|
|
19
|
+
if args.tcp:
|
|
20
|
+
print("Starting server with TCP")
|
|
21
|
+
host, port = args.tcp
|
|
22
|
+
server.start_tcp(host=host, port=int(port))
|
|
23
|
+
else:
|
|
24
|
+
print("Starting server with stdio protocol")
|
|
25
|
+
# Default to stdio if --tcp is not provided
|
|
26
|
+
server.start_io()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Optional, List
|
|
4
|
+
from lsprotocol.types import Diagnostic, Position
|
|
5
|
+
from pygls.workspace import Document
|
|
6
|
+
from vyper.ast import VyperNode, nodes
|
|
7
|
+
from vyper.compiler import CompilerData
|
|
8
|
+
from vyper.compiler.input_bundle import FilesystemInputBundle
|
|
9
|
+
from vyper.compiler.phases import DEFAULT_CONTRACT_PATH
|
|
10
|
+
from vyper.semantics.types import StructT
|
|
11
|
+
from vyper.semantics.types.user import FlagT
|
|
12
|
+
from vyper.exceptions import VyperException
|
|
13
|
+
from vyper.cli.vyper_compile import get_search_paths
|
|
14
|
+
import warnings
|
|
15
|
+
import re
|
|
16
|
+
|
|
17
|
+
from vyper_language_server.utils import (
|
|
18
|
+
create_diagnostic_warning,
|
|
19
|
+
diagnostic_from_exception,
|
|
20
|
+
working_directory_for_document,
|
|
21
|
+
document_to_fileinput,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger("vyper-language-server")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
pattern_text = r"(.+) will be deprecated in a future release, use (.+) instead\."
|
|
28
|
+
deprecation_pattern = re.compile(pattern_text)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AST:
|
|
32
|
+
ast_data = None
|
|
33
|
+
ast_data_annotated = None
|
|
34
|
+
|
|
35
|
+
custom_type_node_types = (nodes.StructDef, nodes.FlagDef)
|
|
36
|
+
|
|
37
|
+
# Module Data
|
|
38
|
+
functions = {}
|
|
39
|
+
variables = {}
|
|
40
|
+
flags = {}
|
|
41
|
+
structs = {}
|
|
42
|
+
|
|
43
|
+
# Import Data
|
|
44
|
+
imports = {}
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def from_node(cls, node: VyperNode):
|
|
48
|
+
ast = cls()
|
|
49
|
+
ast.ast_data = node
|
|
50
|
+
ast.ast_data_annotated = node
|
|
51
|
+
return ast
|
|
52
|
+
|
|
53
|
+
def _load_import_data(self):
|
|
54
|
+
ast = self.ast_data_annotated
|
|
55
|
+
if ast is None:
|
|
56
|
+
return
|
|
57
|
+
import_nodes = ast.get_descendants((nodes.ImportFrom, nodes.Import))
|
|
58
|
+
node: nodes.ImportFrom | nodes.Import
|
|
59
|
+
imports = {}
|
|
60
|
+
for node in import_nodes:
|
|
61
|
+
import_info = node._metadata.get("import_info")
|
|
62
|
+
if import_info:
|
|
63
|
+
alias = import_info.alias
|
|
64
|
+
# Handle module imports (vyper files)
|
|
65
|
+
if hasattr(import_info, "parsed") and isinstance(
|
|
66
|
+
import_info.parsed, nodes.Module
|
|
67
|
+
):
|
|
68
|
+
# Get the module type from the parsed AST
|
|
69
|
+
parsed_module = import_info.parsed
|
|
70
|
+
if (
|
|
71
|
+
hasattr(parsed_module, "_metadata")
|
|
72
|
+
and "type" in parsed_module._metadata
|
|
73
|
+
):
|
|
74
|
+
imports[alias] = parsed_module._metadata["type"]
|
|
75
|
+
# Handle interface imports (json/vyi files)
|
|
76
|
+
elif hasattr(import_info, "parsed"):
|
|
77
|
+
imports[alias] = import_info.parsed
|
|
78
|
+
|
|
79
|
+
self.imports = imports
|
|
80
|
+
|
|
81
|
+
def _load_module_data(self):
|
|
82
|
+
ast = self.ast_data_annotated
|
|
83
|
+
if ast is None:
|
|
84
|
+
return
|
|
85
|
+
self.functions = ast._metadata["type"].functions
|
|
86
|
+
self.variables = ast._metadata["type"].variables
|
|
87
|
+
|
|
88
|
+
flagt_list = [
|
|
89
|
+
FlagT.from_FlagDef(node) for node in ast._metadata["type"].flag_defs
|
|
90
|
+
]
|
|
91
|
+
self.flags = {flagt.name: flagt for flagt in flagt_list}
|
|
92
|
+
|
|
93
|
+
structt_list = [
|
|
94
|
+
StructT.from_StructDef(node) for node in ast._metadata["type"].struct_defs
|
|
95
|
+
]
|
|
96
|
+
self.structs = {structt.name: structt for structt in structt_list}
|
|
97
|
+
|
|
98
|
+
def update_ast(self, doc: Document) -> List[Diagnostic]:
|
|
99
|
+
diagnostics = self.build_ast(doc)
|
|
100
|
+
return diagnostics
|
|
101
|
+
|
|
102
|
+
def build_ast(self, doc: Document | str) -> List[Diagnostic]:
|
|
103
|
+
if isinstance(doc, str):
|
|
104
|
+
doc = Document(uri=str(DEFAULT_CONTRACT_PATH), source=doc)
|
|
105
|
+
uri_parent_path = working_directory_for_document(doc)
|
|
106
|
+
search_paths = get_search_paths([str(uri_parent_path)])
|
|
107
|
+
fileinput = document_to_fileinput(doc)
|
|
108
|
+
compiler_data = CompilerData(
|
|
109
|
+
fileinput, input_bundle=FilesystemInputBundle(search_paths)
|
|
110
|
+
)
|
|
111
|
+
diagnostics = []
|
|
112
|
+
replacements = {}
|
|
113
|
+
warnings.simplefilter("always")
|
|
114
|
+
with warnings.catch_warnings(record=True) as w:
|
|
115
|
+
try:
|
|
116
|
+
# unforunately we need this deep copy so the ast doesnt change
|
|
117
|
+
# out from under us when folding stuff happens
|
|
118
|
+
self.ast_data = copy.deepcopy(compiler_data.vyper_module)
|
|
119
|
+
self.ast_data_annotated = compiler_data.annotated_vyper_module
|
|
120
|
+
|
|
121
|
+
self._load_module_data()
|
|
122
|
+
self._load_import_data()
|
|
123
|
+
|
|
124
|
+
except VyperException as e:
|
|
125
|
+
# make message string include class name
|
|
126
|
+
message = f"{e.__class__.__name__}: {e}"
|
|
127
|
+
if e.lineno is not None and e.col_offset is not None:
|
|
128
|
+
diagnostics.append(diagnostic_from_exception(e))
|
|
129
|
+
if e.annotations:
|
|
130
|
+
for a in e.annotations:
|
|
131
|
+
diagnostics.append(
|
|
132
|
+
diagnostic_from_exception(a, message=message)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
for warning in w:
|
|
136
|
+
m = deprecation_pattern.match(str(warning.message))
|
|
137
|
+
if not m:
|
|
138
|
+
continue
|
|
139
|
+
deprecated = m.group(1)
|
|
140
|
+
replacement = m.group(2)
|
|
141
|
+
replacements[deprecated] = replacement
|
|
142
|
+
|
|
143
|
+
# Iterate over doc.lines and find all deprecated values
|
|
144
|
+
for i, line in enumerate(doc.lines):
|
|
145
|
+
for deprecated, replacement in replacements.items():
|
|
146
|
+
for match in re.finditer(re.escape(deprecated), line):
|
|
147
|
+
character_start = match.start()
|
|
148
|
+
character_end = match.end()
|
|
149
|
+
diagnostic_message = (
|
|
150
|
+
f"{deprecated} is deprecated. Please use {replacement} instead."
|
|
151
|
+
)
|
|
152
|
+
diagnostics.append(
|
|
153
|
+
create_diagnostic_warning(
|
|
154
|
+
line_num=i,
|
|
155
|
+
character_start=character_start,
|
|
156
|
+
character_end=character_end,
|
|
157
|
+
message=diagnostic_message,
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return diagnostics
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def best_ast(self):
|
|
165
|
+
if self.ast_data_annotated:
|
|
166
|
+
return self.ast_data_annotated
|
|
167
|
+
elif self.ast_data:
|
|
168
|
+
return self.ast_data
|
|
169
|
+
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
def get_descendants(self, *args, **kwargs):
|
|
173
|
+
if self.best_ast is None:
|
|
174
|
+
return []
|
|
175
|
+
return self.best_ast.get_descendants(*args, **kwargs)
|
|
176
|
+
|
|
177
|
+
def get_top_level_nodes(self, *args, **kwargs):
|
|
178
|
+
if self.best_ast is None:
|
|
179
|
+
return []
|
|
180
|
+
return self.best_ast.get_children(*args, **kwargs)
|
|
181
|
+
|
|
182
|
+
def get_enums(self) -> List[str]:
|
|
183
|
+
# return [node.name for node in self.get_descendants(nodes.FlagDef)]
|
|
184
|
+
return list(self.flags.keys())
|
|
185
|
+
|
|
186
|
+
def get_structs(self) -> List[str]:
|
|
187
|
+
# return [node.name for node in self.get_descendants(nodes.StructDef)]
|
|
188
|
+
return list(self.structs.keys())
|
|
189
|
+
|
|
190
|
+
def get_events(self) -> List[str]:
|
|
191
|
+
return [node.name for node in self.get_descendants(nodes.EventDef)]
|
|
192
|
+
|
|
193
|
+
def get_user_defined_types(self):
|
|
194
|
+
return [node.name for node in self.get_descendants(self.custom_type_node_types)]
|
|
195
|
+
|
|
196
|
+
def get_constants(self):
|
|
197
|
+
# NOTE: Constants should be fetched from self.ast_data, they are missing
|
|
198
|
+
# from self.ast_data_unfolded and self.ast_data_folded
|
|
199
|
+
# NOTE: This may no longer be the case with the new AST format
|
|
200
|
+
if self.ast_data is None:
|
|
201
|
+
return []
|
|
202
|
+
|
|
203
|
+
return [
|
|
204
|
+
node.target.id
|
|
205
|
+
for node in self.ast_data.get_children(
|
|
206
|
+
nodes.VariableDecl, {"is_constant": True}
|
|
207
|
+
)
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
def get_enum_variants(self, enum: str):
|
|
211
|
+
enum_node = self.find_type_declaration_node_for_name(enum)
|
|
212
|
+
if enum_node is None:
|
|
213
|
+
return []
|
|
214
|
+
|
|
215
|
+
return [node.value.id for node in enum_node.get_children(nodes.Expr)]
|
|
216
|
+
|
|
217
|
+
def get_struct_fields(self, struct: str):
|
|
218
|
+
struct_node = self.find_type_declaration_node_for_name(struct)
|
|
219
|
+
if struct_node is None:
|
|
220
|
+
return []
|
|
221
|
+
|
|
222
|
+
return [node.target.id for node in struct_node.get_children(nodes.AnnAssign)]
|
|
223
|
+
|
|
224
|
+
def get_state_variables(self):
|
|
225
|
+
return [node.target.id for node in self.get_state_variables_as_vardecls()]
|
|
226
|
+
|
|
227
|
+
def get_state_variables_as_vardecls(self):
|
|
228
|
+
# NOTE: The state variables should be fetched from self.ast_data, they are
|
|
229
|
+
# missing from self.ast_data_unfolded and self.ast_data_folded when constants
|
|
230
|
+
if self.ast_data is None:
|
|
231
|
+
return []
|
|
232
|
+
|
|
233
|
+
return self.ast_data.get_descendants(nodes.VariableDecl)
|
|
234
|
+
|
|
235
|
+
def get_internal_function_nodes(self):
|
|
236
|
+
function_nodes = self.get_descendants(nodes.FunctionDef)
|
|
237
|
+
internal_nodes = []
|
|
238
|
+
|
|
239
|
+
for node in function_nodes:
|
|
240
|
+
for decorator in node.decorator_list:
|
|
241
|
+
if isinstance(decorator, nodes.Name) and decorator.id == "internal":
|
|
242
|
+
internal_nodes.append(node)
|
|
243
|
+
|
|
244
|
+
return internal_nodes
|
|
245
|
+
|
|
246
|
+
def get_internal_functions(self):
|
|
247
|
+
internal_fn_names = [k for k, v in self.functions.items() if v.is_internal]
|
|
248
|
+
return internal_fn_names
|
|
249
|
+
|
|
250
|
+
def find_nodes_referencing_internal_function(self, function: str):
|
|
251
|
+
return self.get_descendants(
|
|
252
|
+
nodes.Call, {"func.attr": function, "func.value.id": "self"}
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
def find_nodes_referencing_state_variable(self, variable: str):
|
|
256
|
+
return self.get_descendants(
|
|
257
|
+
nodes.Attribute, {"value.id": "self", "attr": variable}
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def find_nodes_referencing_constant(self, constant: str):
|
|
261
|
+
name_nodes = self.get_descendants(nodes.Name, {"id": constant})
|
|
262
|
+
return [
|
|
263
|
+
node
|
|
264
|
+
for node in name_nodes
|
|
265
|
+
if not isinstance(node.get_ancestor(), nodes.VariableDecl)
|
|
266
|
+
]
|
|
267
|
+
|
|
268
|
+
def get_attributes_for_symbol(self, symbol: str):
|
|
269
|
+
node = self.find_type_declaration_node_for_name(symbol)
|
|
270
|
+
if node is None:
|
|
271
|
+
return []
|
|
272
|
+
|
|
273
|
+
if isinstance(node, nodes.StructDef):
|
|
274
|
+
return self.get_struct_fields(symbol)
|
|
275
|
+
elif isinstance(node, nodes.FlagDef):
|
|
276
|
+
return self.get_enum_variants(symbol)
|
|
277
|
+
|
|
278
|
+
return []
|
|
279
|
+
|
|
280
|
+
def find_function_declaration_node_for_name(
|
|
281
|
+
self, function: str
|
|
282
|
+
) -> Optional[nodes.FunctionDef]:
|
|
283
|
+
for node in self.get_descendants(nodes.FunctionDef):
|
|
284
|
+
name_match = node.name == function
|
|
285
|
+
not_interface_declaration = not isinstance(
|
|
286
|
+
node.get_ancestor(), nodes.InterfaceDef
|
|
287
|
+
)
|
|
288
|
+
if name_match and not_interface_declaration:
|
|
289
|
+
return node
|
|
290
|
+
|
|
291
|
+
return None
|
|
292
|
+
|
|
293
|
+
def find_state_variable_declaration_node_for_name(self, variable: str):
|
|
294
|
+
# NOTE: The state variables should be fetched from self.ast_data, they are
|
|
295
|
+
# missing from self.ast_data_unfolded and self.ast_data_folded when constants
|
|
296
|
+
if self.ast_data is None:
|
|
297
|
+
return None
|
|
298
|
+
|
|
299
|
+
for node in self.ast_data.get_descendants(nodes.VariableDecl):
|
|
300
|
+
if node.target.id == variable:
|
|
301
|
+
return node
|
|
302
|
+
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
def find_type_declaration_node_for_name(self, symbol: str):
|
|
306
|
+
searchable_types = self.custom_type_node_types + (nodes.EventDef,)
|
|
307
|
+
for node in self.get_descendants(searchable_types):
|
|
308
|
+
if node.name == symbol:
|
|
309
|
+
return node
|
|
310
|
+
if isinstance(node, nodes.FlagDef):
|
|
311
|
+
for variant in node.get_children(nodes.Expr):
|
|
312
|
+
if variant.value.id == symbol:
|
|
313
|
+
return variant
|
|
314
|
+
|
|
315
|
+
return None
|
|
316
|
+
|
|
317
|
+
def find_nodes_referencing_enum(self, enum: str):
|
|
318
|
+
return_nodes = []
|
|
319
|
+
|
|
320
|
+
for node in self.get_descendants(nodes.AnnAssign, {"annotation.id": enum}):
|
|
321
|
+
return_nodes.append(node)
|
|
322
|
+
for node in self.get_descendants(nodes.Attribute, {"value.id": enum}):
|
|
323
|
+
return_nodes.append(node)
|
|
324
|
+
for node in self.get_descendants(nodes.VariableDecl, {"annotation.id": enum}):
|
|
325
|
+
return_nodes.append(node)
|
|
326
|
+
for node in self.get_descendants(nodes.FunctionDef, {"returns.id": enum}):
|
|
327
|
+
return_nodes.append(node)
|
|
328
|
+
|
|
329
|
+
return return_nodes
|
|
330
|
+
|
|
331
|
+
def find_nodes_referencing_enum_variant(self, enum: str, variant: str):
|
|
332
|
+
return self.get_descendants(
|
|
333
|
+
nodes.Attribute, {"attr": variant, "value.id": enum}
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def find_nodes_referencing_struct(self, struct: str):
|
|
337
|
+
return_nodes = []
|
|
338
|
+
|
|
339
|
+
for node in self.get_descendants(nodes.AnnAssign, {"annotation.id": struct}):
|
|
340
|
+
return_nodes.append(node)
|
|
341
|
+
for node in self.get_descendants(nodes.Call, {"func.id": struct}):
|
|
342
|
+
return_nodes.append(node)
|
|
343
|
+
for node in self.get_descendants(nodes.VariableDecl, {"annotation.id": struct}):
|
|
344
|
+
return_nodes.append(node)
|
|
345
|
+
for node in self.get_descendants(nodes.FunctionDef, {"returns.id": struct}):
|
|
346
|
+
return_nodes.append(node)
|
|
347
|
+
|
|
348
|
+
return return_nodes
|
|
349
|
+
|
|
350
|
+
def find_top_level_node_at_pos(self, pos: Position) -> Optional[VyperNode]:
|
|
351
|
+
nodes = self.get_top_level_nodes()
|
|
352
|
+
for node in nodes:
|
|
353
|
+
if node.lineno <= pos.line and pos.line <= node.end_lineno:
|
|
354
|
+
return node
|
|
355
|
+
|
|
356
|
+
# return node with highest lineno if no node found
|
|
357
|
+
if nodes:
|
|
358
|
+
# sort
|
|
359
|
+
nodes.sort(key=lambda x: x.lineno, reverse=True)
|
|
360
|
+
return nodes[0]
|
|
361
|
+
|
|
362
|
+
return None
|
|
363
|
+
|
|
364
|
+
def find_nodes_referencing_symbol(self, symbol: str):
|
|
365
|
+
# this only runs on subtrees
|
|
366
|
+
return_nodes = []
|
|
367
|
+
|
|
368
|
+
for node in self.get_descendants(nodes.Name, {"id": symbol}):
|
|
369
|
+
parent = node.get_ancestor()
|
|
370
|
+
if isinstance(parent, nodes.Dict):
|
|
371
|
+
# skip struct key names
|
|
372
|
+
if symbol not in [key.id for key in parent.keys]:
|
|
373
|
+
return_nodes.append(node)
|
|
374
|
+
elif isinstance(parent, nodes.AnnAssign):
|
|
375
|
+
if node.id == parent.target.id:
|
|
376
|
+
# lhs of variable declaration
|
|
377
|
+
continue
|
|
378
|
+
else:
|
|
379
|
+
return_nodes.append(node)
|
|
380
|
+
else:
|
|
381
|
+
return_nodes.append(node)
|
|
382
|
+
|
|
383
|
+
return return_nodes
|
|
384
|
+
|
|
385
|
+
def find_node_declaring_symbol(self, symbol: str):
|
|
386
|
+
for node in self.get_descendants((nodes.AnnAssign, nodes.VariableDecl)):
|
|
387
|
+
if node.target.id == symbol:
|
|
388
|
+
return node
|
|
389
|
+
|
|
390
|
+
return None
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Debouncer:
|
|
5
|
+
def __init__(self, wait):
|
|
6
|
+
self.wait = wait
|
|
7
|
+
self.timer = None
|
|
8
|
+
self.lock = threading.Lock()
|
|
9
|
+
|
|
10
|
+
def debounce(self, func):
|
|
11
|
+
def debounced(*args, **kwargs):
|
|
12
|
+
with self.lock:
|
|
13
|
+
if self.timer is not None:
|
|
14
|
+
self.timer.cancel() # Cancel the existing timer if there is one
|
|
15
|
+
# Create a new timer that will call func with the latest arguments
|
|
16
|
+
self.timer = threading.Timer(self.wait, lambda: func(*args, **kwargs))
|
|
17
|
+
self.timer.start()
|
|
18
|
+
|
|
19
|
+
return debounced
|
|
File without changes
|