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.
@@ -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