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.
- 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/library_doc.py +3 -2
- 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/ast.py +6 -5
- robotcode/robot/utils/stubs.py +17 -1
- {robotcode_robot-0.68.2.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.2.dist-info/RECORD +0 -22
- {robotcode_robot-0.68.2.dist-info → robotcode_robot-0.68.5.dist-info}/WHEEL +0 -0
- {robotcode_robot-0.68.2.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
|