]> crepu.dev Git - config.git/blame_incremental - djavu-asus/emacs/elpy/rpc-venv/lib/python3.11/site-packages/jedi/api/helpers.py
Reorganización de directorios
[config.git] / djavu-asus / emacs / elpy / rpc-venv / lib / python3.11 / site-packages / jedi / api / helpers.py
... / ...
CommitLineData
1"""
2Helpers for the API
3"""
4import re
5from collections import namedtuple
6from textwrap import dedent
7from itertools import chain
8from functools import wraps
9from inspect import Parameter
10
11from parso.python.parser import Parser
12from parso.python import tree
13
14from jedi.inference.base_value import NO_VALUES
15from jedi.inference.syntax_tree import infer_atom
16from jedi.inference.helpers import infer_call_of_leaf
17from jedi.inference.compiled import get_string_value_set
18from jedi.cache import signature_time_cache, memoize_method
19from jedi.parser_utils import get_parent_scope
20
21
22CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
23
24
25def _start_match(string, like_name):
26 return string.startswith(like_name)
27
28
29def _fuzzy_match(string, like_name):
30 if len(like_name) <= 1:
31 return like_name in string
32 pos = string.find(like_name[0])
33 if pos >= 0:
34 return _fuzzy_match(string[pos + 1:], like_name[1:])
35 return False
36
37
38def match(string, like_name, fuzzy=False):
39 if fuzzy:
40 return _fuzzy_match(string, like_name)
41 else:
42 return _start_match(string, like_name)
43
44
45def sorted_definitions(defs):
46 # Note: `or ''` below is required because `module_path` could be
47 return sorted(defs, key=lambda x: (str(x.module_path or ''),
48 x.line or 0,
49 x.column or 0,
50 x.name))
51
52
53def get_on_completion_name(module_node, lines, position):
54 leaf = module_node.get_leaf_for_position(position)
55 if leaf is None or leaf.type in ('string', 'error_leaf'):
56 # Completions inside strings are a bit special, we need to parse the
57 # string. The same is true for comments and error_leafs.
58 line = lines[position[0] - 1]
59 # The first step of completions is to get the name
60 return re.search(r'(?!\d)\w+$|$', line[:position[1]]).group(0)
61 elif leaf.type not in ('name', 'keyword'):
62 return ''
63
64 return leaf.value[:position[1] - leaf.start_pos[1]]
65
66
67def _get_code(code_lines, start_pos, end_pos):
68 # Get relevant lines.
69 lines = code_lines[start_pos[0] - 1:end_pos[0]]
70 # Remove the parts at the end of the line.
71 lines[-1] = lines[-1][:end_pos[1]]
72 # Remove first line indentation.
73 lines[0] = lines[0][start_pos[1]:]
74 return ''.join(lines)
75
76
77class OnErrorLeaf(Exception):
78 @property
79 def error_leaf(self):
80 return self.args[0]
81
82
83def _get_code_for_stack(code_lines, leaf, position):
84 # It might happen that we're on whitespace or on a comment. This means
85 # that we would not get the right leaf.
86 if leaf.start_pos >= position:
87 # If we're not on a comment simply get the previous leaf and proceed.
88 leaf = leaf.get_previous_leaf()
89 if leaf is None:
90 return '' # At the beginning of the file.
91
92 is_after_newline = leaf.type == 'newline'
93 while leaf.type == 'newline':
94 leaf = leaf.get_previous_leaf()
95 if leaf is None:
96 return ''
97
98 if leaf.type == 'error_leaf' or leaf.type == 'string':
99 if leaf.start_pos[0] < position[0]:
100 # On a different line, we just begin anew.
101 return ''
102
103 # Error leafs cannot be parsed, completion in strings is also
104 # impossible.
105 raise OnErrorLeaf(leaf)
106 else:
107 user_stmt = leaf
108 while True:
109 if user_stmt.parent.type in ('file_input', 'suite', 'simple_stmt'):
110 break
111 user_stmt = user_stmt.parent
112
113 if is_after_newline:
114 if user_stmt.start_pos[1] > position[1]:
115 # This means that it's actually a dedent and that means that we
116 # start without value (part of a suite).
117 return ''
118
119 # This is basically getting the relevant lines.
120 return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position)
121
122
123def get_stack_at_position(grammar, code_lines, leaf, pos):
124 """
125 Returns the possible node names (e.g. import_from, xor_test or yield_stmt).
126 """
127 class EndMarkerReached(Exception):
128 pass
129
130 def tokenize_without_endmarker(code):
131 # TODO This is for now not an official parso API that exists purely
132 # for Jedi.
133 tokens = grammar._tokenize(code)
134 for token in tokens:
135 if token.string == safeword:
136 raise EndMarkerReached()
137 elif token.prefix.endswith(safeword):
138 # This happens with comments.
139 raise EndMarkerReached()
140 elif token.string.endswith(safeword):
141 yield token # Probably an f-string literal that was not finished.
142 raise EndMarkerReached()
143 else:
144 yield token
145
146 # The code might be indedented, just remove it.
147 code = dedent(_get_code_for_stack(code_lines, leaf, pos))
148 # We use a word to tell Jedi when we have reached the start of the
149 # completion.
150 # Use Z as a prefix because it's not part of a number suffix.
151 safeword = 'ZZZ_USER_WANTS_TO_COMPLETE_HERE_WITH_JEDI'
152 code = code + ' ' + safeword
153
154 p = Parser(grammar._pgen_grammar, error_recovery=True)
155 try:
156 p.parse(tokens=tokenize_without_endmarker(code))
157 except EndMarkerReached:
158 return p.stack
159 raise SystemError(
160 "This really shouldn't happen. There's a bug in Jedi:\n%s"
161 % list(tokenize_without_endmarker(code))
162 )
163
164
165def infer(inference_state, context, leaf):
166 if leaf.type == 'name':
167 return inference_state.infer(context, leaf)
168
169 parent = leaf.parent
170 definitions = NO_VALUES
171 if parent.type == 'atom':
172 # e.g. `(a + b)`
173 definitions = context.infer_node(leaf.parent)
174 elif parent.type == 'trailer':
175 # e.g. `a()`
176 definitions = infer_call_of_leaf(context, leaf)
177 elif isinstance(leaf, tree.Literal):
178 # e.g. `"foo"` or `1.0`
179 return infer_atom(context, leaf)
180 elif leaf.type in ('fstring_string', 'fstring_start', 'fstring_end'):
181 return get_string_value_set(inference_state)
182 return definitions
183
184
185def filter_follow_imports(names, follow_builtin_imports=False):
186 for name in names:
187 if name.is_import():
188 new_names = list(filter_follow_imports(
189 name.goto(),
190 follow_builtin_imports=follow_builtin_imports,
191 ))
192 found_builtin = False
193 if follow_builtin_imports:
194 for new_name in new_names:
195 if new_name.start_pos is None:
196 found_builtin = True
197
198 if found_builtin:
199 yield name
200 else:
201 yield from new_names
202 else:
203 yield name
204
205
206class CallDetails:
207 def __init__(self, bracket_leaf, children, position):
208 self.bracket_leaf = bracket_leaf
209 self._children = children
210 self._position = position
211
212 @property
213 def index(self):
214 return _get_index_and_key(self._children, self._position)[0]
215
216 @property
217 def keyword_name_str(self):
218 return _get_index_and_key(self._children, self._position)[1]
219
220 @memoize_method
221 def _list_arguments(self):
222 return list(_iter_arguments(self._children, self._position))
223
224 def calculate_index(self, param_names):
225 positional_count = 0
226 used_names = set()
227 star_count = -1
228 args = self._list_arguments()
229 if not args:
230 if param_names:
231 return 0
232 else:
233 return None
234
235 is_kwarg = False
236 for i, (star_count, key_start, had_equal) in enumerate(args):
237 is_kwarg |= had_equal | (star_count == 2)
238 if star_count:
239 pass # For now do nothing, we don't know what's in there here.
240 else:
241 if i + 1 != len(args): # Not last
242 if had_equal:
243 used_names.add(key_start)
244 else:
245 positional_count += 1
246
247 for i, param_name in enumerate(param_names):
248 kind = param_name.get_kind()
249
250 if not is_kwarg:
251 if kind == Parameter.VAR_POSITIONAL:
252 return i
253 if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
254 if i == positional_count:
255 return i
256
257 if key_start is not None and not star_count == 1 or star_count == 2:
258 if param_name.string_name not in used_names \
259 and (kind == Parameter.KEYWORD_ONLY
260 or kind == Parameter.POSITIONAL_OR_KEYWORD
261 and positional_count <= i):
262 if star_count:
263 return i
264 if had_equal:
265 if param_name.string_name == key_start:
266 return i
267 else:
268 if param_name.string_name.startswith(key_start):
269 return i
270
271 if kind == Parameter.VAR_KEYWORD:
272 return i
273 return None
274
275 def iter_used_keyword_arguments(self):
276 for star_count, key_start, had_equal in list(self._list_arguments()):
277 if had_equal and key_start:
278 yield key_start
279
280 def count_positional_arguments(self):
281 count = 0
282 for star_count, key_start, had_equal in self._list_arguments()[:-1]:
283 if star_count or key_start:
284 break
285 count += 1
286 return count
287
288
289def _iter_arguments(nodes, position):
290 def remove_after_pos(name):
291 if name.type != 'name':
292 return None
293 return name.value[:position[1] - name.start_pos[1]]
294
295 # Returns Generator[Tuple[star_count, Optional[key_start: str], had_equal]]
296 nodes_before = [c for c in nodes if c.start_pos < position]
297 if nodes_before[-1].type == 'arglist':
298 yield from _iter_arguments(nodes_before[-1].children, position)
299 return
300
301 previous_node_yielded = False
302 stars_seen = 0
303 for i, node in enumerate(nodes_before):
304 if node.type == 'argument':
305 previous_node_yielded = True
306 first = node.children[0]
307 second = node.children[1]
308 if second == '=':
309 if second.start_pos < position and first.type == 'name':
310 yield 0, first.value, True
311 else:
312 yield 0, remove_after_pos(first), False
313 elif first in ('*', '**'):
314 yield len(first.value), remove_after_pos(second), False
315 else:
316 # Must be a Comprehension
317 first_leaf = node.get_first_leaf()
318 if first_leaf.type == 'name' and first_leaf.start_pos >= position:
319 yield 0, remove_after_pos(first_leaf), False
320 else:
321 yield 0, None, False
322 stars_seen = 0
323 elif node.type == 'testlist_star_expr':
324 for n in node.children[::2]:
325 if n.type == 'star_expr':
326 stars_seen = 1
327 n = n.children[1]
328 yield stars_seen, remove_after_pos(n), False
329 stars_seen = 0
330 # The count of children is even if there's a comma at the end.
331 previous_node_yielded = bool(len(node.children) % 2)
332 elif isinstance(node, tree.PythonLeaf) and node.value == ',':
333 if not previous_node_yielded:
334 yield stars_seen, '', False
335 stars_seen = 0
336 previous_node_yielded = False
337 elif isinstance(node, tree.PythonLeaf) and node.value in ('*', '**'):
338 stars_seen = len(node.value)
339 elif node == '=' and nodes_before[-1]:
340 previous_node_yielded = True
341 before = nodes_before[i - 1]
342 if before.type == 'name':
343 yield 0, before.value, True
344 else:
345 yield 0, None, False
346 # Just ignore the star that is probably a syntax error.
347 stars_seen = 0
348
349 if not previous_node_yielded:
350 if nodes_before[-1].type == 'name':
351 yield stars_seen, remove_after_pos(nodes_before[-1]), False
352 else:
353 yield stars_seen, '', False
354
355
356def _get_index_and_key(nodes, position):
357 """
358 Returns the amount of commas and the keyword argument string.
359 """
360 nodes_before = [c for c in nodes if c.start_pos < position]
361 if nodes_before[-1].type == 'arglist':
362 return _get_index_and_key(nodes_before[-1].children, position)
363
364 key_str = None
365
366 last = nodes_before[-1]
367 if last.type == 'argument' and last.children[1] == '=' \
368 and last.children[1].end_pos <= position:
369 # Checked if the argument
370 key_str = last.children[0].value
371 elif last == '=':
372 key_str = nodes_before[-2].value
373
374 return nodes_before.count(','), key_str
375
376
377def _get_signature_details_from_error_node(node, additional_children, position):
378 for index, element in reversed(list(enumerate(node.children))):
379 # `index > 0` means that it's a trailer and not an atom.
380 if element == '(' and element.end_pos <= position and index > 0:
381 # It's an error node, we don't want to match too much, just
382 # until the parentheses is enough.
383 children = node.children[index:]
384 name = element.get_previous_leaf()
385 if name is None:
386 continue
387 if name.type == 'name' or name.parent.type in ('trailer', 'atom'):
388 return CallDetails(element, children + additional_children, position)
389
390
391def get_signature_details(module, position):
392 leaf = module.get_leaf_for_position(position, include_prefixes=True)
393 # It's easier to deal with the previous token than the next one in this
394 # case.
395 if leaf.start_pos >= position:
396 # Whitespace / comments after the leaf count towards the previous leaf.
397 leaf = leaf.get_previous_leaf()
398 if leaf is None:
399 return None
400
401 # Now that we know where we are in the syntax tree, we start to look at
402 # parents for possible function definitions.
403 node = leaf.parent
404 while node is not None:
405 if node.type in ('funcdef', 'classdef', 'decorated', 'async_stmt'):
406 # Don't show signatures if there's stuff before it that just
407 # makes it feel strange to have a signature.
408 return None
409
410 additional_children = []
411 for n in reversed(node.children):
412 if n.start_pos < position:
413 if n.type == 'error_node':
414 result = _get_signature_details_from_error_node(
415 n, additional_children, position
416 )
417 if result is not None:
418 return result
419
420 additional_children[0:0] = n.children
421 continue
422 additional_children.insert(0, n)
423
424 # Find a valid trailer
425 if node.type == 'trailer' and node.children[0] == '(' \
426 or node.type == 'decorator' and node.children[2] == '(':
427 # Additionally we have to check that an ending parenthesis isn't
428 # interpreted wrong. There are two cases:
429 # 1. Cursor before paren -> The current signature is good
430 # 2. Cursor after paren -> We need to skip the current signature
431 if not (leaf is node.children[-1] and position >= leaf.end_pos):
432 leaf = node.get_previous_leaf()
433 if leaf is None:
434 return None
435 return CallDetails(
436 node.children[0] if node.type == 'trailer' else node.children[2],
437 node.children,
438 position
439 )
440
441 node = node.parent
442
443 return None
444
445
446@signature_time_cache("call_signatures_validity")
447def cache_signatures(inference_state, context, bracket_leaf, code_lines, user_pos):
448 """This function calculates the cache key."""
449 line_index = user_pos[0] - 1
450
451 before_cursor = code_lines[line_index][:user_pos[1]]
452 other_lines = code_lines[bracket_leaf.start_pos[0]:line_index]
453 whole = ''.join(other_lines + [before_cursor])
454 before_bracket = re.match(r'.*\(', whole, re.DOTALL)
455
456 module_path = context.get_root_context().py__file__()
457 if module_path is None:
458 yield None # Don't cache!
459 else:
460 yield (module_path, before_bracket, bracket_leaf.start_pos)
461 yield infer(
462 inference_state,
463 context,
464 bracket_leaf.get_previous_leaf(),
465 )
466
467
468def validate_line_column(func):
469 @wraps(func)
470 def wrapper(self, line=None, column=None, *args, **kwargs):
471 line = max(len(self._code_lines), 1) if line is None else line
472 if not (0 < line <= len(self._code_lines)):
473 raise ValueError('`line` parameter is not in a valid range.')
474
475 line_string = self._code_lines[line - 1]
476 line_len = len(line_string)
477 if line_string.endswith('\r\n'):
478 line_len -= 2
479 elif line_string.endswith('\n'):
480 line_len -= 1
481
482 column = line_len if column is None else column
483 if not (0 <= column <= line_len):
484 raise ValueError('`column` parameter (%d) is not in a valid range '
485 '(0-%d) for line %d (%r).' % (
486 column, line_len, line, line_string))
487 return func(self, line, column, *args, **kwargs)
488 return wrapper
489
490
491def get_module_names(module, all_scopes, definitions=True, references=False):
492 """
493 Returns a dictionary with name parts as keys and their call paths as
494 values.
495 """
496 def def_ref_filter(name):
497 is_def = name.is_definition()
498 return definitions and is_def or references and not is_def
499
500 names = list(chain.from_iterable(module.get_used_names().values()))
501 if not all_scopes:
502 # We have to filter all the names that don't have the module as a
503 # parent_scope. There's None as a parent, because nodes in the module
504 # node have the parent module and not suite as all the others.
505 # Therefore it's important to catch that case.
506
507 def is_module_scope_name(name):
508 parent_scope = get_parent_scope(name)
509 # async functions have an extra wrapper. Strip it.
510 if parent_scope and parent_scope.type == 'async_stmt':
511 parent_scope = parent_scope.parent
512 return parent_scope in (module, None)
513
514 names = [n for n in names if is_module_scope_name(n)]
515 return filter(def_ref_filter, names)
516
517
518def split_search_string(name):
519 type, _, dotted_names = name.rpartition(' ')
520 if type == 'def':
521 type = 'function'
522 return type, dotted_names.split('.')