robotcode-robot 0.68.2__py3-none-any.whl → 0.68.5__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,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