robotcode-robot 0.68.3__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.
- robotcode/robot/__version__.py +1 -1
- robotcode/robot/config/model.py +63 -51
- robotcode/robot/diagnostics/document_cache_helper.py +523 -0
- robotcode/robot/diagnostics/entities.py +2 -1
- robotcode/robot/diagnostics/errors.py +33 -0
- robotcode/robot/diagnostics/imports_manager.py +1499 -0
- robotcode/robot/diagnostics/model_helper.py +799 -0
- robotcode/robot/diagnostics/namespace.py +2165 -0
- robotcode/robot/diagnostics/namespace_analyzer.py +1121 -0
- robotcode/robot/diagnostics/workspace_config.py +50 -0
- robotcode/robot/utils/stubs.py +17 -1
- {robotcode_robot-0.68.3.dist-info → robotcode_robot-0.68.5.dist-info}/METADATA +2 -2
- robotcode_robot-0.68.5.dist-info/RECORD +29 -0
- robotcode_robot-0.68.3.dist-info/RECORD +0 -22
- {robotcode_robot-0.68.3.dist-info → robotcode_robot-0.68.5.dist-info}/WHEEL +0 -0
- {robotcode_robot-0.68.3.dist-info → robotcode_robot-0.68.5.dist-info}/licenses/LICENSE.txt +0 -0
@@ -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
|