robotcode-robot 0.68.2__py3-none-any.whl → 0.68.5__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,799 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ import re
5
+ import token as python_token
6
+ from io import StringIO
7
+ from tokenize import TokenError, generate_tokens
8
+ from typing import (
9
+ Any,
10
+ Iterator,
11
+ List,
12
+ Optional,
13
+ Sequence,
14
+ Set,
15
+ Tuple,
16
+ Type,
17
+ Union,
18
+ )
19
+
20
+ from robot.parsing.lexer.tokens import Token
21
+ from robot.utils.escaping import split_from_equals, unescape
22
+ from robot.variables.finders import NOT_FOUND, NumberFinder
23
+ from robot.variables.search import contains_variable, search_variable
24
+ from robotcode.core.lsp.types import Position
25
+
26
+ from ..utils import get_robot_version
27
+ from ..utils.ast import (
28
+ iter_over_keyword_names_and_owners,
29
+ range_from_token,
30
+ strip_variable_token,
31
+ tokenize_variables,
32
+ whitespace_at_begin_of_token,
33
+ whitespace_from_begin_of_token,
34
+ )
35
+ from .entities import (
36
+ LibraryEntry,
37
+ VariableDefinition,
38
+ VariableNotFoundDefinition,
39
+ )
40
+ from .library_doc import (
41
+ ArgumentInfo,
42
+ KeywordArgumentKind,
43
+ KeywordDoc,
44
+ LibraryDoc,
45
+ )
46
+ from .namespace import DEFAULT_BDD_PREFIXES, Namespace
47
+
48
+
49
+ class ModelHelper:
50
+ @classmethod
51
+ def get_run_keyword_keyworddoc_and_token_from_position(
52
+ cls,
53
+ keyword_doc: Optional[KeywordDoc],
54
+ argument_tokens: List[Token],
55
+ namespace: Namespace,
56
+ position: Position,
57
+ ) -> Tuple[Optional[Tuple[Optional[KeywordDoc], Token]], List[Token]]:
58
+ if keyword_doc is None or not keyword_doc.is_any_run_keyword():
59
+ return None, argument_tokens
60
+
61
+ if keyword_doc.is_run_keyword() and len(argument_tokens) > 0:
62
+ result = cls.get_keyworddoc_and_token_from_position(
63
+ unescape(argument_tokens[0].value),
64
+ argument_tokens[0],
65
+ argument_tokens[1:],
66
+ namespace,
67
+ position,
68
+ )
69
+ return result, argument_tokens[1:]
70
+
71
+ if keyword_doc.is_run_keyword_with_condition() and len(argument_tokens) > (
72
+ cond_count := keyword_doc.run_keyword_condition_count()
73
+ ):
74
+ result = cls.get_keyworddoc_and_token_from_position(
75
+ unescape(argument_tokens[cond_count].value),
76
+ argument_tokens[cond_count],
77
+ argument_tokens[cond_count + 1 :],
78
+ namespace,
79
+ position,
80
+ )
81
+
82
+ return result, argument_tokens[cond_count + 1 :]
83
+
84
+ if keyword_doc.is_run_keywords():
85
+ has_and = False
86
+ while argument_tokens:
87
+ t = argument_tokens[0]
88
+ argument_tokens = argument_tokens[1:]
89
+ if t.value == "AND":
90
+ continue
91
+
92
+ and_token = next((e for e in argument_tokens if e.value == "AND"), None)
93
+ if and_token is not None:
94
+ args = argument_tokens[: argument_tokens.index(and_token)]
95
+ has_and = True
96
+ else:
97
+ if has_and:
98
+ args = argument_tokens
99
+ else:
100
+ args = []
101
+
102
+ result = cls.get_keyworddoc_and_token_from_position(unescape(t.value), t, args, namespace, position)
103
+ if result is not None and result[0] is not None:
104
+ return result, []
105
+
106
+ if and_token is not None:
107
+ argument_tokens = argument_tokens[argument_tokens.index(and_token) + 1 :]
108
+ elif has_and:
109
+ argument_tokens = []
110
+
111
+ return None, []
112
+
113
+ if keyword_doc.is_run_keyword_if() and len(argument_tokens) > 1:
114
+
115
+ def skip_args() -> None:
116
+ nonlocal argument_tokens
117
+
118
+ while argument_tokens:
119
+ if argument_tokens[0].value in ["ELSE", "ELSE IF"]:
120
+ break
121
+ argument_tokens = argument_tokens[1:]
122
+
123
+ inner_keyword_doc = namespace.find_keyword(argument_tokens[1].value, raise_keyword_error=False)
124
+
125
+ if position.is_in_range(range_from_token(argument_tokens[1])):
126
+ return (inner_keyword_doc, argument_tokens[1]), argument_tokens[2:]
127
+
128
+ argument_tokens = argument_tokens[2:]
129
+
130
+ inner_keyword_doc_and_args = cls.get_run_keyword_keyworddoc_and_token_from_position(
131
+ inner_keyword_doc, argument_tokens, namespace, position
132
+ )
133
+
134
+ if inner_keyword_doc_and_args[0] is not None:
135
+ return inner_keyword_doc_and_args
136
+
137
+ argument_tokens = inner_keyword_doc_and_args[1]
138
+
139
+ skip_args()
140
+
141
+ while argument_tokens:
142
+ if argument_tokens[0].value == "ELSE" and len(argument_tokens) > 1:
143
+ inner_keyword_doc = namespace.find_keyword(unescape(argument_tokens[1].value))
144
+
145
+ if position.is_in_range(range_from_token(argument_tokens[1])):
146
+ return (
147
+ inner_keyword_doc,
148
+ argument_tokens[1],
149
+ ), argument_tokens[2:]
150
+
151
+ argument_tokens = argument_tokens[2:]
152
+
153
+ inner_keyword_doc_and_args = cls.get_run_keyword_keyworddoc_and_token_from_position(
154
+ inner_keyword_doc,
155
+ argument_tokens,
156
+ namespace,
157
+ position,
158
+ )
159
+
160
+ if inner_keyword_doc_and_args[0] is not None:
161
+ return inner_keyword_doc_and_args
162
+
163
+ argument_tokens = inner_keyword_doc_and_args[1]
164
+
165
+ skip_args()
166
+
167
+ break
168
+ if argument_tokens[0].value == "ELSE IF" and len(argument_tokens) > 2:
169
+ inner_keyword_doc = namespace.find_keyword(unescape(argument_tokens[2].value))
170
+
171
+ if position.is_in_range(range_from_token(argument_tokens[2])):
172
+ return (
173
+ inner_keyword_doc,
174
+ argument_tokens[2],
175
+ ), argument_tokens[3:]
176
+
177
+ argument_tokens = argument_tokens[3:]
178
+
179
+ inner_keyword_doc_and_args = cls.get_run_keyword_keyworddoc_and_token_from_position(
180
+ inner_keyword_doc,
181
+ argument_tokens,
182
+ namespace,
183
+ position,
184
+ )
185
+
186
+ if inner_keyword_doc_and_args[0] is not None:
187
+ return inner_keyword_doc_and_args
188
+
189
+ argument_tokens = inner_keyword_doc_and_args[1]
190
+
191
+ skip_args()
192
+ else:
193
+ break
194
+
195
+ return None, argument_tokens
196
+
197
+ @classmethod
198
+ def get_keyworddoc_and_token_from_position(
199
+ cls,
200
+ keyword_name: Optional[str],
201
+ keyword_token: Token,
202
+ argument_tokens: List[Token],
203
+ namespace: Namespace,
204
+ position: Position,
205
+ analyse_run_keywords: bool = True,
206
+ ) -> Optional[Tuple[Optional[KeywordDoc], Token]]:
207
+ keyword_doc = namespace.find_keyword(keyword_name, raise_keyword_error=False)
208
+ if keyword_doc is None:
209
+ return None
210
+
211
+ if position.is_in_range(range_from_token(keyword_token)):
212
+ return keyword_doc, keyword_token
213
+
214
+ if analyse_run_keywords:
215
+ return (
216
+ cls.get_run_keyword_keyworddoc_and_token_from_position(
217
+ keyword_doc, argument_tokens, namespace, position
218
+ )
219
+ )[0]
220
+
221
+ return None
222
+
223
+ @classmethod
224
+ def get_namespace_info_from_keyword_token(
225
+ cls, namespace: Namespace, keyword_token: Token
226
+ ) -> Tuple[Optional[LibraryEntry], Optional[str]]:
227
+ lib_entry: Optional[LibraryEntry] = None
228
+ kw_namespace: Optional[str] = None
229
+
230
+ for lib, keyword in iter_over_keyword_names_and_owners(keyword_token.value):
231
+ if lib is not None:
232
+ lib_entries = next(
233
+ (v for k, v in (namespace.get_namespaces()).items() if k == lib),
234
+ None,
235
+ )
236
+ if lib_entries is not None:
237
+ kw_namespace = lib
238
+ lib_entry = next(
239
+ (v for v in lib_entries if keyword in v.library_doc.keywords),
240
+ lib_entries[0] if lib_entries else None,
241
+ )
242
+ break
243
+
244
+ return lib_entry, kw_namespace
245
+
246
+ __match_extended = re.compile(
247
+ r"""
248
+ (.+?) # base name (group 1)
249
+ ([^\s\w].+) # extended part (group 2)
250
+ """,
251
+ re.UNICODE | re.VERBOSE,
252
+ )
253
+
254
+ @staticmethod
255
+ def iter_expression_variables_from_token(
256
+ expression: Token,
257
+ namespace: Namespace,
258
+ nodes: Optional[List[ast.AST]],
259
+ position: Optional[Position] = None,
260
+ skip_commandline_variables: bool = False,
261
+ return_not_found: bool = False,
262
+ ) -> Iterator[Tuple[Token, VariableDefinition]]:
263
+ variable_started = False
264
+ try:
265
+ for toknum, tokval, (_, tokcol), _, _ in generate_tokens(StringIO(expression.value).readline):
266
+ if variable_started:
267
+ if toknum == python_token.NAME:
268
+ var = namespace.find_variable(
269
+ f"${{{tokval}}}",
270
+ nodes,
271
+ position,
272
+ skip_commandline_variables=skip_commandline_variables,
273
+ ignore_error=True,
274
+ )
275
+ sub_token = Token(
276
+ expression.type,
277
+ tokval,
278
+ expression.lineno,
279
+ expression.col_offset + tokcol,
280
+ expression.error,
281
+ )
282
+ if var is not None:
283
+ yield sub_token, var
284
+ elif return_not_found:
285
+ yield (
286
+ sub_token,
287
+ VariableNotFoundDefinition(
288
+ sub_token.lineno,
289
+ sub_token.col_offset,
290
+ sub_token.lineno,
291
+ sub_token.end_col_offset,
292
+ namespace.source,
293
+ tokval,
294
+ sub_token,
295
+ ),
296
+ )
297
+ variable_started = False
298
+ if tokval == "$":
299
+ variable_started = True
300
+ except TokenError:
301
+ pass
302
+
303
+ @staticmethod
304
+ def remove_index_from_variable_token(
305
+ token: Token,
306
+ ) -> Tuple[Token, Optional[Token]]:
307
+ def escaped(i: int) -> bool:
308
+ return bool(token.value[-i - 3 : -i - 2] == "\\")
309
+
310
+ if token.type != Token.VARIABLE or not token.value.endswith("]"):
311
+ return (token, None)
312
+
313
+ braces = 1
314
+ curly_braces = 0
315
+ index = 0
316
+ for i, c in enumerate(reversed(token.value[:-1])):
317
+ if c == "}" and not escaped(i):
318
+ curly_braces += 1
319
+ elif c == "{" and not escaped(i):
320
+ curly_braces -= 1
321
+ elif c == "]" and curly_braces == 0 and not escaped(i):
322
+ braces += 1
323
+
324
+ if braces == 0:
325
+ index = i
326
+ elif c == "[" and curly_braces == 0 and not escaped(i):
327
+ braces -= 1
328
+
329
+ if braces == 0:
330
+ index = i
331
+
332
+ if braces != 0 or curly_braces != 0:
333
+ return (token, None)
334
+
335
+ value = token.value[: -index - 2]
336
+ var = Token(token.type, value, token.lineno, token.col_offset, token.error) if len(value) > 0 else None
337
+ rest = Token(
338
+ Token.ARGUMENT,
339
+ token.value[-index - 2 :],
340
+ token.lineno,
341
+ token.col_offset + len(value),
342
+ token.error,
343
+ )
344
+
345
+ return (var, rest)
346
+
347
+ @classmethod
348
+ def tokenize_variables(
349
+ cls,
350
+ token: Token,
351
+ identifiers: str = "$@&%",
352
+ ignore_errors: bool = False,
353
+ *,
354
+ extra_types: Optional[Set[str]] = None,
355
+ ) -> Iterator[Token]:
356
+ for t in tokenize_variables(token, identifiers, ignore_errors, extra_types=extra_types):
357
+ if t.type == Token.VARIABLE:
358
+ var, rest = cls.remove_index_from_variable_token(t)
359
+ if var is not None:
360
+ yield var
361
+ if rest is not None:
362
+ yield from cls.tokenize_variables(
363
+ rest,
364
+ identifiers,
365
+ ignore_errors,
366
+ extra_types=extra_types,
367
+ )
368
+ else:
369
+ yield t
370
+
371
+ @classmethod
372
+ def iter_variables_from_token(
373
+ cls,
374
+ token: Token,
375
+ namespace: Namespace,
376
+ nodes: Optional[List[ast.AST]],
377
+ position: Optional[Position] = None,
378
+ skip_commandline_variables: bool = False,
379
+ return_not_found: bool = False,
380
+ ) -> Iterator[Tuple[Token, VariableDefinition]]:
381
+ def is_number(name: str) -> bool:
382
+ if name.startswith("$"):
383
+ finder = NumberFinder()
384
+ return bool(finder.find(name) != NOT_FOUND)
385
+ return False
386
+
387
+ def iter_token(
388
+ to: Token, ignore_errors: bool = False
389
+ ) -> Iterator[Union[Token, Tuple[Token, VariableDefinition]]]:
390
+ for sub_token in cls.tokenize_variables(to, ignore_errors=ignore_errors):
391
+ if sub_token.type == Token.VARIABLE:
392
+ base = sub_token.value[2:-1]
393
+ if base and not (base[0] == "{" and base[-1] == "}"):
394
+ yield sub_token
395
+ elif base:
396
+ for v in cls.iter_expression_variables_from_token(
397
+ Token(
398
+ sub_token.type,
399
+ base[1:-1],
400
+ sub_token.lineno,
401
+ sub_token.col_offset + 3,
402
+ sub_token.error,
403
+ ),
404
+ namespace,
405
+ nodes,
406
+ position,
407
+ skip_commandline_variables=skip_commandline_variables,
408
+ return_not_found=return_not_found,
409
+ ):
410
+ yield v
411
+ elif base == "" and return_not_found:
412
+ yield (
413
+ sub_token,
414
+ VariableNotFoundDefinition(
415
+ sub_token.lineno,
416
+ sub_token.col_offset,
417
+ sub_token.lineno,
418
+ sub_token.end_col_offset,
419
+ namespace.source,
420
+ sub_token.value,
421
+ sub_token,
422
+ ),
423
+ )
424
+ return
425
+
426
+ if contains_variable(base, "$@&%"):
427
+ for sub_token_or_var in iter_token(
428
+ Token(
429
+ to.type,
430
+ base,
431
+ sub_token.lineno,
432
+ sub_token.col_offset + 2,
433
+ ),
434
+ ignore_errors=ignore_errors,
435
+ ):
436
+ if isinstance(sub_token_or_var, Token):
437
+ if sub_token_or_var.type == Token.VARIABLE:
438
+ yield sub_token_or_var
439
+ else:
440
+ yield sub_token_or_var
441
+
442
+ if token.type == Token.VARIABLE and token.value.endswith("="):
443
+ match = search_variable(token.value, ignore_errors=True)
444
+ if not match.is_assign(allow_assign_mark=True):
445
+ return
446
+
447
+ token = Token(
448
+ token.type,
449
+ token.value[:-1].strip(),
450
+ token.lineno,
451
+ token.col_offset,
452
+ token.error,
453
+ )
454
+
455
+ for token_or_var in iter_token(token, ignore_errors=True):
456
+ if isinstance(token_or_var, Token):
457
+ sub_token = token_or_var
458
+ name = sub_token.value
459
+ var = namespace.find_variable(
460
+ name,
461
+ nodes,
462
+ position,
463
+ skip_commandline_variables=skip_commandline_variables,
464
+ ignore_error=True,
465
+ )
466
+ if var is not None:
467
+ yield strip_variable_token(sub_token), var
468
+ continue
469
+
470
+ if is_number(sub_token.value):
471
+ continue
472
+
473
+ if (
474
+ sub_token.type == Token.VARIABLE
475
+ and sub_token.value[:1] in "$@&%"
476
+ and sub_token.value[1:2] == "{"
477
+ and sub_token.value[-1:] == "}"
478
+ ):
479
+ match = cls.__match_extended.match(name[2:-1])
480
+ if match is not None:
481
+ base_name, _ = match.groups()
482
+ name = f"{name[0]}{{{base_name.strip()}}}"
483
+ var = namespace.find_variable(
484
+ name,
485
+ nodes,
486
+ position,
487
+ skip_commandline_variables=skip_commandline_variables,
488
+ ignore_error=True,
489
+ )
490
+ sub_sub_token = Token(
491
+ sub_token.type,
492
+ name,
493
+ sub_token.lineno,
494
+ sub_token.col_offset,
495
+ )
496
+ if var is not None:
497
+ yield strip_variable_token(sub_sub_token), var
498
+ continue
499
+ if is_number(name):
500
+ continue
501
+ elif return_not_found:
502
+ if contains_variable(sub_token.value[2:-1]):
503
+ continue
504
+ else:
505
+ yield (
506
+ strip_variable_token(sub_sub_token),
507
+ VariableNotFoundDefinition(
508
+ sub_sub_token.lineno,
509
+ sub_sub_token.col_offset,
510
+ sub_sub_token.lineno,
511
+ sub_sub_token.end_col_offset,
512
+ namespace.source,
513
+ name,
514
+ sub_sub_token,
515
+ ),
516
+ )
517
+ if return_not_found:
518
+ yield (
519
+ strip_variable_token(sub_token),
520
+ VariableNotFoundDefinition(
521
+ sub_token.lineno,
522
+ sub_token.col_offset,
523
+ sub_token.lineno,
524
+ sub_token.end_col_offset,
525
+ namespace.source,
526
+ sub_token.value,
527
+ sub_token,
528
+ ),
529
+ )
530
+ else:
531
+ yield token_or_var
532
+
533
+ __expression_statement_types: Optional[Tuple[Type[Any]]] = None
534
+
535
+ @classmethod
536
+ def get_expression_statement_types(cls) -> Tuple[Type[Any]]:
537
+ import robot.parsing.model.statements
538
+
539
+ if cls.__expression_statement_types is None:
540
+ cls.__expression_statement_types = (robot.parsing.model.statements.IfHeader,)
541
+
542
+ if get_robot_version() >= (5, 0):
543
+ cls.__expression_statement_types = ( # type: ignore[assignment]
544
+ robot.parsing.model.statements.IfElseHeader,
545
+ robot.parsing.model.statements.WhileHeader,
546
+ )
547
+
548
+ return cls.__expression_statement_types
549
+
550
+ BDD_TOKEN_REGEX = re.compile(r"^(Given|When|Then|And|But)\s", flags=re.IGNORECASE)
551
+ BDD_TOKEN = re.compile(r"^(Given|When|Then|And|But)$", flags=re.IGNORECASE)
552
+
553
+ @classmethod
554
+ def split_bdd_prefix(cls, namespace: Namespace, token: Token) -> Tuple[Optional[Token], Optional[Token]]:
555
+ bdd_token = None
556
+
557
+ parts = token.value.split()
558
+ if len(parts) < 2:
559
+ return None, token
560
+
561
+ for index in range(1, len(parts)):
562
+ prefix = " ".join(parts[:index]).title()
563
+ if prefix in (
564
+ namespace.languages.bdd_prefixes if namespace.languages is not None else DEFAULT_BDD_PREFIXES
565
+ ):
566
+ bdd_len = len(prefix)
567
+ bdd_token = Token(
568
+ token.type,
569
+ token.value[:bdd_len],
570
+ token.lineno,
571
+ token.col_offset,
572
+ token.error,
573
+ )
574
+
575
+ token = Token(
576
+ token.type,
577
+ token.value[bdd_len + 1 :],
578
+ token.lineno,
579
+ token.col_offset + bdd_len + 1,
580
+ token.error,
581
+ )
582
+ break
583
+
584
+ return bdd_token, token
585
+
586
+ @classmethod
587
+ def strip_bdd_prefix(cls, namespace: Namespace, token: Token) -> Token:
588
+ if get_robot_version() < (6, 0):
589
+ bdd_match = cls.BDD_TOKEN_REGEX.match(token.value)
590
+ if bdd_match:
591
+ bdd_len = len(bdd_match.group(1))
592
+
593
+ token = Token(
594
+ token.type,
595
+ token.value[bdd_len + 1 :],
596
+ token.lineno,
597
+ token.col_offset + bdd_len + 1,
598
+ token.error,
599
+ )
600
+ return token
601
+
602
+ parts = token.value.split()
603
+ if len(parts) < 2:
604
+ return token
605
+
606
+ for index in range(1, len(parts)):
607
+ prefix = " ".join(parts[:index]).title()
608
+ if prefix in (
609
+ namespace.languages.bdd_prefixes if namespace.languages is not None else DEFAULT_BDD_PREFIXES
610
+ ):
611
+ bdd_len = len(prefix)
612
+ token = Token(
613
+ token.type,
614
+ token.value[bdd_len + 1 :],
615
+ token.lineno,
616
+ token.col_offset + bdd_len + 1,
617
+ token.error,
618
+ )
619
+ break
620
+
621
+ return token
622
+
623
+ @classmethod
624
+ def is_bdd_token(cls, namespace: Namespace, token: Token) -> bool:
625
+ if get_robot_version() < (6, 0):
626
+ bdd_match = cls.BDD_TOKEN.match(token.value)
627
+ return bool(bdd_match)
628
+
629
+ parts = token.value.split()
630
+
631
+ for index in range(len(parts)):
632
+ prefix = " ".join(parts[: index + 1]).title()
633
+
634
+ if prefix.title() in (
635
+ namespace.languages.bdd_prefixes if namespace.languages is not None else DEFAULT_BDD_PREFIXES
636
+ ):
637
+ return True
638
+
639
+ return False
640
+
641
+ @classmethod
642
+ def get_keyword_definition_at_token(cls, library_doc: LibraryDoc, token: Token) -> Optional[KeywordDoc]:
643
+ return cls.get_keyword_definition_at_line(library_doc, token.value, token.lineno)
644
+
645
+ @classmethod
646
+ def get_keyword_definition_at_line(cls, library_doc: LibraryDoc, value: str, line: int) -> Optional[KeywordDoc]:
647
+ return next(
648
+ (k for k in library_doc.keywords.get_all(value) if k.line_no == line),
649
+ None,
650
+ )
651
+
652
+ def get_argument_info_at_position(
653
+ self,
654
+ keyword_doc: KeywordDoc,
655
+ tokens: Sequence[Token],
656
+ token_at_position: Token,
657
+ position: Position,
658
+ ) -> Tuple[int, Optional[List[ArgumentInfo]], Optional[Token]]:
659
+ argument_index = -1
660
+ named_arg = False
661
+
662
+ kw_arguments = [
663
+ a
664
+ for a in keyword_doc.arguments
665
+ if a.kind
666
+ not in [
667
+ KeywordArgumentKind.POSITIONAL_ONLY_MARKER,
668
+ KeywordArgumentKind.NAMED_ONLY_MARKER,
669
+ ]
670
+ ]
671
+
672
+ token_at_position_index = tokens.index(token_at_position)
673
+
674
+ if (
675
+ token_at_position.type in [Token.EOL, Token.SEPARATOR]
676
+ and token_at_position_index > 2
677
+ and tokens[token_at_position_index - 1].type == Token.CONTINUATION
678
+ and position.character < range_from_token(tokens[token_at_position_index - 1]).end.character + 2
679
+ ):
680
+ return -1, None, None
681
+
682
+ token_at_position_index = tokens.index(token_at_position)
683
+
684
+ argument_token_index = token_at_position_index
685
+ while argument_token_index >= 0 and tokens[argument_token_index].type != Token.ARGUMENT:
686
+ argument_token_index -= 1
687
+
688
+ if (
689
+ token_at_position.type == Token.EOL
690
+ and len(tokens) > 1
691
+ and tokens[argument_token_index - 1].type == Token.CONTINUATION
692
+ ):
693
+ argument_token_index -= 2
694
+ while argument_token_index >= 0 and tokens[argument_token_index].type != Token.ARGUMENT:
695
+ argument_token_index -= 1
696
+
697
+ arguments = [a for a in tokens if a.type == Token.ARGUMENT]
698
+
699
+ argument_token: Optional[Token] = None
700
+
701
+ if argument_token_index >= 0:
702
+ argument_token = tokens[argument_token_index]
703
+ if argument_token is not None and argument_token.type == Token.ARGUMENT:
704
+ argument_index = arguments.index(argument_token)
705
+ else:
706
+ argument_index = 0
707
+ else:
708
+ argument_index = -1
709
+
710
+ if whitespace_at_begin_of_token(token_at_position) > 1:
711
+ r = range_from_token(token_at_position)
712
+
713
+ ws_b = whitespace_from_begin_of_token(token_at_position)
714
+ r.start.character += 2 if ws_b and ws_b[0] != "\t" else 1
715
+
716
+ if position.is_in_range(r, True):
717
+ argument_index += 1
718
+ argument_token = None
719
+
720
+ if argument_token is None:
721
+ r.end.character = r.start.character + whitespace_at_begin_of_token(token_at_position) - 3
722
+ if not position.is_in_range(r, False):
723
+ argument_token_index += 2
724
+ if argument_token_index < len(tokens) and tokens[argument_token_index].type == Token.ARGUMENT:
725
+ argument_token = tokens[argument_token_index]
726
+
727
+ if (
728
+ argument_index < 0
729
+ or argument_token is not None
730
+ and argument_token.type == Token.ARGUMENT
731
+ and argument_token.value.startswith(("@{", "&{"))
732
+ and argument_token.value.endswith("}")
733
+ ):
734
+ return -1, kw_arguments, argument_token
735
+
736
+ if argument_token is not None and argument_token.type == Token.ARGUMENT:
737
+ arg_name_or_value, arg_value = split_from_equals(argument_token.value)
738
+ if arg_value is not None:
739
+ old_argument_index = argument_index
740
+ argument_index = next(
741
+ (
742
+ i
743
+ for i, v in enumerate(kw_arguments)
744
+ if v.name == arg_name_or_value or v.kind == KeywordArgumentKind.VAR_NAMED
745
+ ),
746
+ -1,
747
+ )
748
+
749
+ if argument_index == -1:
750
+ argument_index = old_argument_index
751
+ else:
752
+ named_arg = True
753
+
754
+ if not named_arg and argument_index >= 0:
755
+ need_named = False
756
+ for i, a in enumerate(arguments):
757
+ if i == argument_index:
758
+ break
759
+ arg_name_or_value, arg_value = split_from_equals(a.value)
760
+ if arg_value is not None and any(
761
+ (k for k, v in enumerate(kw_arguments) if v.name == arg_name_or_value)
762
+ ):
763
+ need_named = True
764
+ break
765
+ if arg_name_or_value.startswith(("@{", "&{")) and arg_name_or_value.endswith("}"):
766
+ need_named = True
767
+ break
768
+
769
+ a_index = next(
770
+ (
771
+ i
772
+ for i, v in enumerate(kw_arguments)
773
+ if v.kind
774
+ in [
775
+ KeywordArgumentKind.POSITIONAL_ONLY,
776
+ KeywordArgumentKind.POSITIONAL_OR_NAMED,
777
+ ]
778
+ and i == argument_index
779
+ ),
780
+ -1,
781
+ )
782
+ if a_index >= 0 and not need_named:
783
+ argument_index = a_index
784
+ else:
785
+ if need_named:
786
+ argument_index = next(
787
+ (i for i, v in enumerate(kw_arguments) if v.kind == KeywordArgumentKind.VAR_NAMED),
788
+ -1,
789
+ )
790
+ else:
791
+ argument_index = next(
792
+ (i for i, v in enumerate(kw_arguments) if v.kind == KeywordArgumentKind.VAR_POSITIONAL),
793
+ -1,
794
+ )
795
+
796
+ if argument_index >= len(kw_arguments):
797
+ argument_index = -1
798
+
799
+ return argument_index, kw_arguments, argument_token