]>
crepu.dev Git - config.git/blob - djavu-asus/elpy/rpc-venv/lib/python3.11/site-packages/jedi/api/helpers.py
5 from collections
import namedtuple
6 from textwrap
import dedent
7 from itertools
import chain
8 from functools
import wraps
9 from inspect
import Parameter
11 from parso
.python
.parser
import Parser
12 from parso
.python
import tree
14 from jedi
.inference
.base_value
import NO_VALUES
15 from jedi
.inference
.syntax_tree
import infer_atom
16 from jedi
.inference
.helpers
import infer_call_of_leaf
17 from jedi
.inference
.compiled
import get_string_value_set
18 from jedi
.cache
import signature_time_cache
, memoize_method
19 from jedi
.parser_utils
import get_parent_scope
22 CompletionParts
= namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
25 def _start_match(string
, like_name
):
26 return string
.startswith(like_name
)
29 def _fuzzy_match(string
, like_name
):
30 if len(like_name
) <= 1:
31 return like_name
in string
32 pos
= string
.find(like_name
[0])
34 return _fuzzy_match(string
[pos
+ 1:], like_name
[1:])
38 def match(string
, like_name
, fuzzy
=False):
40 return _fuzzy_match(string
, like_name
)
42 return _start_match(string
, like_name
)
45 def 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 ''),
53 def 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'):
64 return leaf
.value
[:position
[1] - leaf
.start_pos
[1]]
67 def _get_code(code_lines
, start_pos
, end_pos
):
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]:]
77 class OnErrorLeaf(Exception):
83 def _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()
90 return '' # At the beginning of the file.
92 is_after_newline
= leaf
.type == 'newline'
93 while leaf
.type == 'newline':
94 leaf
= leaf
.get_previous_leaf()
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.
103 # Error leafs cannot be parsed, completion in strings is also
105 raise OnErrorLeaf(leaf
)
109 if user_stmt
.parent
.type in ('file_input', 'suite', 'simple_stmt'):
111 user_stmt
= user_stmt
.parent
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).
119 # This is basically getting the relevant lines.
120 return _get_code(code_lines
, user_stmt
.get_start_pos_of_prefix(), position
)
123 def get_stack_at_position(grammar
, code_lines
, leaf
, pos
):
125 Returns the possible node names (e.g. import_from, xor_test or yield_stmt).
127 class EndMarkerReached(Exception):
130 def tokenize_without_endmarker(code
):
131 # TODO This is for now not an official parso API that exists purely
133 tokens
= grammar
._tokenize
(code
)
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()
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
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
154 p
= Parser(grammar
._pgen
_grammar
, error_recovery
=True)
156 p
.parse(tokens
=tokenize_without_endmarker(code
))
157 except EndMarkerReached
:
160 "This really shouldn't happen. There's a bug in Jedi:\n%s"
161 % list(tokenize_without_endmarker(code
))
165 def infer(inference_state
, context
, leaf
):
166 if leaf
.type == 'name':
167 return inference_state
.infer(context
, leaf
)
170 definitions
= NO_VALUES
171 if parent
.type == 'atom':
173 definitions
= context
.infer_node(leaf
.parent
)
174 elif parent
.type == 'trailer':
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
)
185 def filter_follow_imports(names
, follow_builtin_imports
=False):
188 new_names
= list(filter_follow_imports(
190 follow_builtin_imports
=follow_builtin_imports
,
192 found_builtin
= False
193 if follow_builtin_imports
:
194 for new_name
in new_names
:
195 if new_name
.start_pos
is None:
207 def __init__(self
, bracket_leaf
, children
, position
):
208 self
.bracket_leaf
= bracket_leaf
209 self
._children
= children
210 self
._position
= position
214 return _get_index_and_key(self
._children
, self
._position
)[0]
217 def keyword_name_str(self
):
218 return _get_index_and_key(self
._children
, self
._position
)[1]
221 def _list_arguments(self
):
222 return list(_iter_arguments(self
._children
, self
._position
))
224 def calculate_index(self
, param_names
):
228 args
= self
._list
_arguments
()
236 for i
, (star_count
, key_start
, had_equal
) in enumerate(args
):
237 is_kwarg |
= had_equal |
(star_count
== 2)
239 pass # For now do nothing, we don't know what's in there here.
241 if i
+ 1 != len(args
): # Not last
243 used_names
.add(key_start
)
245 positional_count
+= 1
247 for i
, param_name
in enumerate(param_names
):
248 kind
= param_name
.get_kind()
251 if kind
== Parameter
.VAR_POSITIONAL
:
253 if kind
in (Parameter
.POSITIONAL_OR_KEYWORD
, Parameter
.POSITIONAL_ONLY
):
254 if i
== positional_count
:
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
):
265 if param_name
.string_name
== key_start
:
268 if param_name
.string_name
.startswith(key_start
):
271 if kind
== Parameter
.VAR_KEYWORD
:
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
:
280 def count_positional_arguments(self
):
282 for star_count
, key_start
, had_equal
in self
._list
_arguments
()[:-1]:
283 if star_count
or key_start
:
289 def _iter_arguments(nodes
, position
):
290 def remove_after_pos(name
):
291 if name
.type != 'name':
293 return name
.value
[:position
[1] - name
.start_pos
[1]]
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
)
301 previous_node_yielded
= False
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]
309 if second
.start_pos
< position
and first
.type == 'name':
310 yield 0, first
.value
, True
312 yield 0, remove_after_pos(first
), False
313 elif first
in ('*', '**'):
314 yield len(first
.value
), remove_after_pos(second
), False
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
323 elif node
.type == 'testlist_star_expr':
324 for n
in node
.children
[::2]:
325 if n
.type == 'star_expr':
328 yield stars_seen
, remove_after_pos(n
), False
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
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
346 # Just ignore the star that is probably a syntax error.
349 if not previous_node_yielded
:
350 if nodes_before
[-1].type == 'name':
351 yield stars_seen
, remove_after_pos(nodes_before
[-1]), False
353 yield stars_seen
, '', False
356 def _get_index_and_key(nodes
, position
):
358 Returns the amount of commas and the keyword argument string.
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
)
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
372 key_str
= nodes_before
[-2].value
374 return nodes_before
.count(','), key_str
377 def _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()
387 if name
.type == 'name' or name
.parent
.type in ('trailer', 'atom'):
388 return CallDetails(element
, children
+ additional_children
, position
)
391 def 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
395 if leaf
.start_pos
>= position
:
396 # Whitespace / comments after the leaf count towards the previous leaf.
397 leaf
= leaf
.get_previous_leaf()
401 # Now that we know where we are in the syntax tree, we start to look at
402 # parents for possible function definitions.
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.
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
417 if result
is not None:
420 additional_children
[0:0] = n
.children
422 additional_children
.insert(0, n
)
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()
436 node
.children
[0] if node
.type == 'trailer' else node
.children
[2],
446 @signature_time_cache("call_signatures_validity")
447 def 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
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
)
456 module_path
= context
.get_root_context().py__file__()
457 if module_path
is None:
458 yield None # Don't cache!
460 yield (module_path
, before_bracket
, bracket_leaf
.start_pos
)
464 bracket_leaf
.get_previous_leaf(),
468 def validate_line_column(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.')
475 line_string
= self
._code
_lines
[line
- 1]
476 line_len
= len(line_string
)
477 if line_string
.endswith('\r\n'):
479 elif line_string
.endswith('\n'):
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
)
491 def get_module_names(module
, all_scopes
, definitions
=True, references
=False):
493 Returns a dictionary with name parts as keys and their call paths as
496 def def_ref_filter(name
):
497 is_def
= name
.is_definition()
498 return definitions
and is_def
or references
and not is_def
500 names
= list(chain
.from_iterable(module
.get_used_names().values()))
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.
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)
514 names
= [n
for n
in names
if is_module_scope_name(n
)]
515 return filter(def_ref_filter
, names
)
518 def split_search_string(name
):
519 type, _
, dotted_names
= name
.rpartition(' ')
522 return type, dotted_names
.split('.')