robotcode-robot 0.93.1__py3-none-any.whl → 0.94.0__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/diagnostics/document_cache_helper.py +11 -6
- robotcode/robot/diagnostics/entities.py +2 -2
- robotcode/robot/diagnostics/errors.py +6 -1
- robotcode/robot/diagnostics/imports_manager.py +77 -37
- robotcode/robot/diagnostics/keyword_finder.py +458 -0
- robotcode/robot/diagnostics/library_doc.py +20 -14
- robotcode/robot/diagnostics/model_helper.py +42 -27
- robotcode/robot/diagnostics/namespace.py +142 -552
- robotcode/robot/diagnostics/namespace_analyzer.py +952 -462
- robotcode/robot/utils/markdownformatter.py +8 -11
- robotcode/robot/utils/visitor.py +7 -13
- {robotcode_robot-0.93.1.dist-info → robotcode_robot-0.94.0.dist-info}/METADATA +2 -2
- robotcode_robot-0.94.0.dist-info/RECORD +31 -0
- robotcode_robot-0.93.1.dist-info/RECORD +0 -30
- {robotcode_robot-0.93.1.dist-info → robotcode_robot-0.94.0.dist-info}/WHEEL +0 -0
- {robotcode_robot-0.93.1.dist-info → robotcode_robot-0.94.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -0,0 +1,458 @@
|
|
1
|
+
from itertools import chain
|
2
|
+
from typing import TYPE_CHECKING, Dict, Iterable, Iterator, List, NamedTuple, Optional, Sequence, Tuple
|
3
|
+
|
4
|
+
from robot.libraries import STDLIBS
|
5
|
+
from robotcode.core.lsp.types import (
|
6
|
+
DiagnosticSeverity,
|
7
|
+
)
|
8
|
+
|
9
|
+
from ..utils import get_robot_version
|
10
|
+
from ..utils.match import eq_namespace
|
11
|
+
from .entities import (
|
12
|
+
LibraryEntry,
|
13
|
+
ResourceEntry,
|
14
|
+
)
|
15
|
+
from .errors import Error
|
16
|
+
from .library_doc import (
|
17
|
+
KeywordDoc,
|
18
|
+
KeywordError,
|
19
|
+
LibraryDoc,
|
20
|
+
)
|
21
|
+
|
22
|
+
if TYPE_CHECKING:
|
23
|
+
from .namespace import Namespace
|
24
|
+
|
25
|
+
|
26
|
+
class DiagnosticsEntry(NamedTuple):
|
27
|
+
message: str
|
28
|
+
severity: DiagnosticSeverity
|
29
|
+
code: Optional[str] = None
|
30
|
+
|
31
|
+
|
32
|
+
class CancelSearchError(Exception):
|
33
|
+
pass
|
34
|
+
|
35
|
+
|
36
|
+
DEFAULT_BDD_PREFIXES = {"Given ", "When ", "Then ", "And ", "But "}
|
37
|
+
|
38
|
+
|
39
|
+
class KeywordFinder:
|
40
|
+
def __init__(self, namespace: "Namespace", library_doc: LibraryDoc) -> None:
|
41
|
+
self.namespace = namespace
|
42
|
+
self.self_library_doc = library_doc
|
43
|
+
|
44
|
+
self.diagnostics: List[DiagnosticsEntry] = []
|
45
|
+
self.multiple_keywords_result: Optional[List[KeywordDoc]] = None
|
46
|
+
self._cache: Dict[
|
47
|
+
Tuple[Optional[str], bool],
|
48
|
+
Tuple[
|
49
|
+
Optional[KeywordDoc],
|
50
|
+
List[DiagnosticsEntry],
|
51
|
+
Optional[List[KeywordDoc]],
|
52
|
+
],
|
53
|
+
] = {}
|
54
|
+
self.handle_bdd_style = True
|
55
|
+
self._all_keywords: Optional[List[LibraryEntry]] = None
|
56
|
+
self._resource_keywords: Optional[List[ResourceEntry]] = None
|
57
|
+
self._library_keywords: Optional[List[LibraryEntry]] = None
|
58
|
+
|
59
|
+
def reset_diagnostics(self) -> None:
|
60
|
+
self.diagnostics = []
|
61
|
+
self.multiple_keywords_result = None
|
62
|
+
|
63
|
+
def find_keyword(
|
64
|
+
self,
|
65
|
+
name: Optional[str],
|
66
|
+
*,
|
67
|
+
raise_keyword_error: bool = False,
|
68
|
+
handle_bdd_style: bool = True,
|
69
|
+
) -> Optional[KeywordDoc]:
|
70
|
+
try:
|
71
|
+
self.reset_diagnostics()
|
72
|
+
|
73
|
+
self.handle_bdd_style = handle_bdd_style
|
74
|
+
|
75
|
+
cached = self._cache.get((name, self.handle_bdd_style), None)
|
76
|
+
|
77
|
+
if cached is not None:
|
78
|
+
self.diagnostics = cached[1]
|
79
|
+
self.multiple_keywords_result = cached[2]
|
80
|
+
return cached[0]
|
81
|
+
|
82
|
+
try:
|
83
|
+
result = self._find_keyword(name)
|
84
|
+
if result is None:
|
85
|
+
self.diagnostics.append(
|
86
|
+
DiagnosticsEntry(
|
87
|
+
f"No keyword with name '{name}' found.",
|
88
|
+
DiagnosticSeverity.ERROR,
|
89
|
+
Error.KEYWORD_NOT_FOUND,
|
90
|
+
)
|
91
|
+
)
|
92
|
+
except KeywordError as e:
|
93
|
+
if e.multiple_keywords:
|
94
|
+
self._add_to_multiple_keywords_result(e.multiple_keywords)
|
95
|
+
|
96
|
+
if raise_keyword_error:
|
97
|
+
raise
|
98
|
+
|
99
|
+
result = None
|
100
|
+
self.diagnostics.append(DiagnosticsEntry(str(e), DiagnosticSeverity.ERROR, Error.KEYWORD_ERROR))
|
101
|
+
|
102
|
+
self._cache[(name, self.handle_bdd_style)] = (
|
103
|
+
result,
|
104
|
+
self.diagnostics,
|
105
|
+
self.multiple_keywords_result,
|
106
|
+
)
|
107
|
+
|
108
|
+
return result
|
109
|
+
except CancelSearchError:
|
110
|
+
return None
|
111
|
+
|
112
|
+
def _find_keyword(self, name: Optional[str]) -> Optional[KeywordDoc]:
|
113
|
+
if not name:
|
114
|
+
self.diagnostics.append(
|
115
|
+
DiagnosticsEntry(
|
116
|
+
"Keyword name cannot be empty.",
|
117
|
+
DiagnosticSeverity.ERROR,
|
118
|
+
Error.KEYWORD_ERROR,
|
119
|
+
)
|
120
|
+
)
|
121
|
+
raise CancelSearchError
|
122
|
+
if not isinstance(name, str):
|
123
|
+
self.diagnostics.append( # type: ignore
|
124
|
+
DiagnosticsEntry(
|
125
|
+
"Keyword name must be a string.",
|
126
|
+
DiagnosticSeverity.ERROR,
|
127
|
+
Error.KEYWORD_ERROR,
|
128
|
+
)
|
129
|
+
)
|
130
|
+
raise CancelSearchError
|
131
|
+
|
132
|
+
result = self._get_keyword_from_self(name)
|
133
|
+
if not result and "." in name:
|
134
|
+
result = self._get_explicit_keyword(name)
|
135
|
+
|
136
|
+
if not result:
|
137
|
+
result = self._get_implicit_keyword(name)
|
138
|
+
|
139
|
+
if not result and self.handle_bdd_style:
|
140
|
+
return self._get_bdd_style_keyword(name)
|
141
|
+
|
142
|
+
return result
|
143
|
+
|
144
|
+
def _get_keyword_from_self(self, name: str) -> Optional[KeywordDoc]:
|
145
|
+
if get_robot_version() >= (6, 0):
|
146
|
+
found: List[Tuple[Optional[LibraryEntry], KeywordDoc]] = [
|
147
|
+
(None, v) for v in self.self_library_doc.keywords.iter_all(name)
|
148
|
+
]
|
149
|
+
if len(found) > 1:
|
150
|
+
found = self._select_best_matches(found)
|
151
|
+
if len(found) > 1:
|
152
|
+
self.diagnostics.append(
|
153
|
+
DiagnosticsEntry(
|
154
|
+
self._create_multiple_keywords_found_message(name, found, implicit=False),
|
155
|
+
DiagnosticSeverity.ERROR,
|
156
|
+
Error.MULTIPLE_KEYWORDS,
|
157
|
+
)
|
158
|
+
)
|
159
|
+
raise CancelSearchError
|
160
|
+
|
161
|
+
if len(found) == 1:
|
162
|
+
# TODO warning if keyword found is defined in resource and suite
|
163
|
+
return found[0][1]
|
164
|
+
|
165
|
+
return None
|
166
|
+
|
167
|
+
try:
|
168
|
+
return self.self_library_doc.keywords.get(name, None)
|
169
|
+
except KeywordError as e:
|
170
|
+
self.diagnostics.append(DiagnosticsEntry(str(e), DiagnosticSeverity.ERROR, Error.KEYWORD_ERROR))
|
171
|
+
raise CancelSearchError from e
|
172
|
+
|
173
|
+
def _yield_owner_and_kw_names(self, full_name: str) -> Iterator[Tuple[str, ...]]:
|
174
|
+
tokens = full_name.split(".")
|
175
|
+
for i in range(1, len(tokens)):
|
176
|
+
yield ".".join(tokens[:i]), ".".join(tokens[i:])
|
177
|
+
|
178
|
+
def _get_explicit_keyword(self, name: str) -> Optional[KeywordDoc]:
|
179
|
+
found: List[Tuple[Optional[LibraryEntry], KeywordDoc]] = []
|
180
|
+
for owner_name, kw_name in self._yield_owner_and_kw_names(name):
|
181
|
+
found.extend(self.find_keywords(owner_name, kw_name))
|
182
|
+
|
183
|
+
if get_robot_version() >= (6, 0) and len(found) > 1:
|
184
|
+
found = self._select_best_matches(found)
|
185
|
+
|
186
|
+
if len(found) > 1:
|
187
|
+
self.diagnostics.append(
|
188
|
+
DiagnosticsEntry(
|
189
|
+
self._create_multiple_keywords_found_message(name, found, implicit=False),
|
190
|
+
DiagnosticSeverity.ERROR,
|
191
|
+
Error.MULTIPLE_KEYWORDS,
|
192
|
+
)
|
193
|
+
)
|
194
|
+
raise CancelSearchError
|
195
|
+
|
196
|
+
return found[0][1] if found else None
|
197
|
+
|
198
|
+
def find_keywords(self, owner_name: str, name: str) -> List[Tuple[LibraryEntry, KeywordDoc]]:
|
199
|
+
if self._all_keywords is None:
|
200
|
+
self._all_keywords = list(
|
201
|
+
chain(
|
202
|
+
self.namespace._libraries.values(),
|
203
|
+
self.namespace._resources.values(),
|
204
|
+
)
|
205
|
+
)
|
206
|
+
|
207
|
+
if get_robot_version() >= (6, 0):
|
208
|
+
result: List[Tuple[LibraryEntry, KeywordDoc]] = []
|
209
|
+
for v in self._all_keywords:
|
210
|
+
if eq_namespace(v.alias or v.name, owner_name):
|
211
|
+
result.extend((v, kw) for kw in v.library_doc.keywords.iter_all(name))
|
212
|
+
return result
|
213
|
+
|
214
|
+
result = []
|
215
|
+
for v in self._all_keywords:
|
216
|
+
if eq_namespace(v.alias or v.name, owner_name):
|
217
|
+
kw = v.library_doc.keywords.get(name, None)
|
218
|
+
if kw is not None:
|
219
|
+
result.append((v, kw))
|
220
|
+
return result
|
221
|
+
|
222
|
+
def _add_to_multiple_keywords_result(self, kw: Iterable[KeywordDoc]) -> None:
|
223
|
+
if self.multiple_keywords_result is None:
|
224
|
+
self.multiple_keywords_result = list(kw)
|
225
|
+
else:
|
226
|
+
self.multiple_keywords_result.extend(kw)
|
227
|
+
|
228
|
+
def _create_multiple_keywords_found_message(
|
229
|
+
self,
|
230
|
+
name: str,
|
231
|
+
found: Sequence[Tuple[Optional[LibraryEntry], KeywordDoc]],
|
232
|
+
implicit: bool = True,
|
233
|
+
) -> str:
|
234
|
+
self._add_to_multiple_keywords_result([k for _, k in found])
|
235
|
+
|
236
|
+
if any(e[1].is_embedded for e in found):
|
237
|
+
error = f"Multiple keywords matching name '{name}' found"
|
238
|
+
else:
|
239
|
+
error = f"Multiple keywords with name '{name}' found"
|
240
|
+
|
241
|
+
if implicit:
|
242
|
+
error += ". Give the full name of the keyword you want to use"
|
243
|
+
|
244
|
+
names = sorted(f"{e[1].name if e[0] is None else f'{e[0].alias or e[0].name}.{e[1].name}'}" for e in found)
|
245
|
+
return "\n ".join([f"{error}:", *names])
|
246
|
+
|
247
|
+
def _get_implicit_keyword(self, name: str) -> Optional[KeywordDoc]:
|
248
|
+
result = self._get_keyword_from_resource_files(name)
|
249
|
+
if not result:
|
250
|
+
return self._get_keyword_from_libraries(name)
|
251
|
+
return result
|
252
|
+
|
253
|
+
def _prioritize_same_file_or_public(
|
254
|
+
self, entries: List[Tuple[Optional[LibraryEntry], KeywordDoc]]
|
255
|
+
) -> List[Tuple[Optional[LibraryEntry], KeywordDoc]]:
|
256
|
+
matches = [h for h in entries if h[1].source == self.namespace.source]
|
257
|
+
if matches:
|
258
|
+
return matches
|
259
|
+
|
260
|
+
matches = [handler for handler in entries if not handler[1].is_private()]
|
261
|
+
|
262
|
+
return matches or entries
|
263
|
+
|
264
|
+
def _select_best_matches(
|
265
|
+
self, entries: List[Tuple[Optional[LibraryEntry], KeywordDoc]]
|
266
|
+
) -> List[Tuple[Optional[LibraryEntry], KeywordDoc]]:
|
267
|
+
normal = [hand for hand in entries if not hand[1].is_embedded]
|
268
|
+
if normal:
|
269
|
+
return normal
|
270
|
+
|
271
|
+
matches = [hand for hand in entries if not self._is_worse_match_than_others(hand, entries)]
|
272
|
+
return matches or entries
|
273
|
+
|
274
|
+
def _is_worse_match_than_others(
|
275
|
+
self,
|
276
|
+
candidate: Tuple[Optional[LibraryEntry], KeywordDoc],
|
277
|
+
alternatives: List[Tuple[Optional[LibraryEntry], KeywordDoc]],
|
278
|
+
) -> bool:
|
279
|
+
for other in alternatives:
|
280
|
+
if (
|
281
|
+
candidate[1] is not other[1]
|
282
|
+
and self._is_better_match(other, candidate)
|
283
|
+
and not self._is_better_match(candidate, other)
|
284
|
+
):
|
285
|
+
return True
|
286
|
+
return False
|
287
|
+
|
288
|
+
def _is_better_match(
|
289
|
+
self,
|
290
|
+
candidate: Tuple[Optional[LibraryEntry], KeywordDoc],
|
291
|
+
other: Tuple[Optional[LibraryEntry], KeywordDoc],
|
292
|
+
) -> bool:
|
293
|
+
return (
|
294
|
+
other[1].matcher.embedded_arguments.match(candidate[1].name) is not None
|
295
|
+
and candidate[1].matcher.embedded_arguments.match(other[1].name) is None
|
296
|
+
)
|
297
|
+
|
298
|
+
def _get_keyword_from_resource_files(self, name: str) -> Optional[KeywordDoc]:
|
299
|
+
if self._resource_keywords is None:
|
300
|
+
self._resource_keywords = list(chain(self.namespace._resources.values()))
|
301
|
+
|
302
|
+
if get_robot_version() >= (6, 0):
|
303
|
+
found: List[Tuple[Optional[LibraryEntry], KeywordDoc]] = []
|
304
|
+
for v in self._resource_keywords:
|
305
|
+
r = v.library_doc.keywords.get_all(name)
|
306
|
+
if r:
|
307
|
+
found.extend([(v, k) for k in r])
|
308
|
+
else:
|
309
|
+
found = []
|
310
|
+
for k in self._resource_keywords:
|
311
|
+
s = k.library_doc.keywords.get(name, None)
|
312
|
+
if s is not None:
|
313
|
+
found.append((k, s))
|
314
|
+
|
315
|
+
if not found:
|
316
|
+
return None
|
317
|
+
|
318
|
+
if get_robot_version() >= (6, 0):
|
319
|
+
if len(found) > 1:
|
320
|
+
found = self._prioritize_same_file_or_public(found)
|
321
|
+
|
322
|
+
if len(found) > 1:
|
323
|
+
found = self._select_best_matches(found)
|
324
|
+
|
325
|
+
if len(found) > 1:
|
326
|
+
found = self._get_keyword_based_on_search_order(found)
|
327
|
+
|
328
|
+
else:
|
329
|
+
if len(found) > 1:
|
330
|
+
found = self._get_keyword_based_on_search_order(found)
|
331
|
+
|
332
|
+
if len(found) == 1:
|
333
|
+
return found[0][1]
|
334
|
+
|
335
|
+
self.diagnostics.append(
|
336
|
+
DiagnosticsEntry(
|
337
|
+
self._create_multiple_keywords_found_message(name, found),
|
338
|
+
DiagnosticSeverity.ERROR,
|
339
|
+
Error.MULTIPLE_KEYWORDS,
|
340
|
+
)
|
341
|
+
)
|
342
|
+
raise CancelSearchError
|
343
|
+
|
344
|
+
def _get_keyword_based_on_search_order(
|
345
|
+
self, entries: List[Tuple[Optional[LibraryEntry], KeywordDoc]]
|
346
|
+
) -> List[Tuple[Optional[LibraryEntry], KeywordDoc]]:
|
347
|
+
for libname in self.namespace.search_order:
|
348
|
+
for e in entries:
|
349
|
+
if e[0] is not None and eq_namespace(libname, e[0].alias or e[0].name):
|
350
|
+
return [e]
|
351
|
+
|
352
|
+
return entries
|
353
|
+
|
354
|
+
def _get_keyword_from_libraries(self, name: str) -> Optional[KeywordDoc]:
|
355
|
+
if self._library_keywords is None:
|
356
|
+
self._library_keywords = list(chain(self.namespace._libraries.values()))
|
357
|
+
|
358
|
+
if get_robot_version() >= (6, 0):
|
359
|
+
found: List[Tuple[Optional[LibraryEntry], KeywordDoc]] = []
|
360
|
+
for v in self._library_keywords:
|
361
|
+
r = v.library_doc.keywords.get_all(name)
|
362
|
+
if r:
|
363
|
+
found.extend([(v, k) for k in r])
|
364
|
+
else:
|
365
|
+
found = []
|
366
|
+
|
367
|
+
for k in self._library_keywords:
|
368
|
+
s = k.library_doc.keywords.get(name, None)
|
369
|
+
if s is not None:
|
370
|
+
found.append((k, s))
|
371
|
+
|
372
|
+
if not found:
|
373
|
+
return None
|
374
|
+
|
375
|
+
if get_robot_version() >= (6, 0):
|
376
|
+
if len(found) > 1:
|
377
|
+
found = self._select_best_matches(found)
|
378
|
+
if len(found) > 1:
|
379
|
+
found = self._get_keyword_based_on_search_order(found)
|
380
|
+
else:
|
381
|
+
if len(found) > 1:
|
382
|
+
found = self._get_keyword_based_on_search_order(found)
|
383
|
+
if len(found) == 2:
|
384
|
+
found = self._filter_stdlib_runner(*found)
|
385
|
+
|
386
|
+
if len(found) == 1:
|
387
|
+
return found[0][1]
|
388
|
+
|
389
|
+
self.diagnostics.append(
|
390
|
+
DiagnosticsEntry(
|
391
|
+
self._create_multiple_keywords_found_message(name, found),
|
392
|
+
DiagnosticSeverity.ERROR,
|
393
|
+
Error.MULTIPLE_KEYWORDS,
|
394
|
+
)
|
395
|
+
)
|
396
|
+
raise CancelSearchError
|
397
|
+
|
398
|
+
def _filter_stdlib_runner(
|
399
|
+
self,
|
400
|
+
entry1: Tuple[Optional[LibraryEntry], KeywordDoc],
|
401
|
+
entry2: Tuple[Optional[LibraryEntry], KeywordDoc],
|
402
|
+
) -> List[Tuple[Optional[LibraryEntry], KeywordDoc]]:
|
403
|
+
stdlibs_without_remote = STDLIBS - {"Remote"}
|
404
|
+
if entry1[0] is not None and entry1[0].name in stdlibs_without_remote:
|
405
|
+
standard, custom = entry1, entry2
|
406
|
+
elif entry2[0] is not None and entry2[0].name in stdlibs_without_remote:
|
407
|
+
standard, custom = entry2, entry1
|
408
|
+
else:
|
409
|
+
return [entry1, entry2]
|
410
|
+
|
411
|
+
self.diagnostics.append(
|
412
|
+
DiagnosticsEntry(
|
413
|
+
self._create_custom_and_standard_keyword_conflict_warning_message(custom, standard),
|
414
|
+
DiagnosticSeverity.WARNING,
|
415
|
+
Error.CONFLICTING_LIBRARY_KEYWORDS,
|
416
|
+
)
|
417
|
+
)
|
418
|
+
|
419
|
+
return [custom]
|
420
|
+
|
421
|
+
def _create_custom_and_standard_keyword_conflict_warning_message(
|
422
|
+
self,
|
423
|
+
custom: Tuple[Optional[LibraryEntry], KeywordDoc],
|
424
|
+
standard: Tuple[Optional[LibraryEntry], KeywordDoc],
|
425
|
+
) -> str:
|
426
|
+
custom_with_name = standard_with_name = ""
|
427
|
+
if custom[0] is not None and custom[0].alias is not None:
|
428
|
+
custom_with_name = " imported as '%s'" % custom[0].alias
|
429
|
+
if standard[0] is not None and standard[0].alias is not None:
|
430
|
+
standard_with_name = " imported as '%s'" % standard[0].alias
|
431
|
+
return (
|
432
|
+
f"Keyword '{standard[1].name}' found both from a custom test library "
|
433
|
+
f"'{'' if custom[0] is None else custom[0].name}'{custom_with_name} "
|
434
|
+
f"and a standard library '{standard[1].name}'{standard_with_name}. "
|
435
|
+
f"The custom keyword is used. To select explicitly, and to get "
|
436
|
+
f"rid of this warning, use either "
|
437
|
+
f"'{'' if custom[0] is None else custom[0].alias or custom[0].name}.{custom[1].name}' "
|
438
|
+
f"or '{'' if standard[0] is None else standard[0].alias or standard[0].name}.{standard[1].name}'."
|
439
|
+
)
|
440
|
+
|
441
|
+
def _get_bdd_style_keyword(self, name: str) -> Optional[KeywordDoc]:
|
442
|
+
if get_robot_version() < (6, 0):
|
443
|
+
lower = name.lower()
|
444
|
+
for prefix in ["given ", "when ", "then ", "and ", "but "]:
|
445
|
+
if lower.startswith(prefix):
|
446
|
+
return self._find_keyword(name[len(prefix) :])
|
447
|
+
return None
|
448
|
+
|
449
|
+
parts = name.split()
|
450
|
+
if len(parts) < 2:
|
451
|
+
return None
|
452
|
+
for index in range(1, len(parts)):
|
453
|
+
prefix = " ".join(parts[:index]).title()
|
454
|
+
if prefix.title() in (
|
455
|
+
self.namespace.languages.bdd_prefixes if self.namespace.languages is not None else DEFAULT_BDD_PREFIXES
|
456
|
+
):
|
457
|
+
return self._find_keyword(" ".join(parts[index:]))
|
458
|
+
return None
|
@@ -74,6 +74,7 @@ from robotcode.robot.diagnostics.entities import (
|
|
74
74
|
)
|
75
75
|
from robotcode.robot.utils import get_robot_version
|
76
76
|
from robotcode.robot.utils.ast import (
|
77
|
+
cached_isinstance,
|
77
78
|
get_variable_token,
|
78
79
|
range_from_token,
|
79
80
|
strip_variable_token,
|
@@ -247,7 +248,7 @@ class KeywordMatcher:
|
|
247
248
|
return self._embedded_arguments
|
248
249
|
|
249
250
|
def __eq__(self, o: object) -> bool:
|
250
|
-
if
|
251
|
+
if cached_isinstance(o, KeywordMatcher):
|
251
252
|
if self._is_namespace != o._is_namespace:
|
252
253
|
return False
|
253
254
|
|
@@ -256,7 +257,7 @@ class KeywordMatcher:
|
|
256
257
|
|
257
258
|
o = o.name
|
258
259
|
|
259
|
-
if not
|
260
|
+
if not cached_isinstance(o, str):
|
260
261
|
return False
|
261
262
|
|
262
263
|
if self.embedded_arguments:
|
@@ -907,7 +908,8 @@ class KeywordStore:
|
|
907
908
|
return self.__matchers
|
908
909
|
|
909
910
|
def __getitem__(self, key: str) -> KeywordDoc:
|
910
|
-
|
911
|
+
key_matcher = KeywordMatcher(key)
|
912
|
+
items = [(k, v) for k, v in self._matchers.items() if k == key_matcher]
|
911
913
|
|
912
914
|
if not items:
|
913
915
|
raise KeyError
|
@@ -933,8 +935,10 @@ class KeywordStore:
|
|
933
935
|
multiple_keywords=[v for _, v in items],
|
934
936
|
)
|
935
937
|
|
936
|
-
def __contains__(self,
|
937
|
-
|
938
|
+
def __contains__(self, _x: object) -> bool:
|
939
|
+
if not isinstance(_x, KeywordMatcher):
|
940
|
+
_x = KeywordMatcher(str(_x))
|
941
|
+
return any(k == _x for k in self._matchers.keys())
|
938
942
|
|
939
943
|
def __len__(self) -> int:
|
940
944
|
return len(self.keywords)
|
@@ -961,7 +965,11 @@ class KeywordStore:
|
|
961
965
|
return default
|
962
966
|
|
963
967
|
def get_all(self, key: str) -> List[KeywordDoc]:
|
964
|
-
return
|
968
|
+
return list(self.iter_all(key))
|
969
|
+
|
970
|
+
def iter_all(self, key: str) -> Iterable[KeywordDoc]:
|
971
|
+
key_matcher = KeywordMatcher(key)
|
972
|
+
yield from (v for k, v in self._matchers.items() if k == key_matcher)
|
965
973
|
|
966
974
|
|
967
975
|
@dataclass
|
@@ -1540,7 +1548,6 @@ def resolve_robot_variables(
|
|
1540
1548
|
command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
|
1541
1549
|
variables: Optional[Dict[str, Optional[Any]]] = None,
|
1542
1550
|
) -> Any:
|
1543
|
-
|
1544
1551
|
result: Variables = _get_default_variables().copy()
|
1545
1552
|
|
1546
1553
|
for k, v in {
|
@@ -1554,14 +1561,16 @@ def resolve_robot_variables(
|
|
1554
1561
|
result[k1] = v1
|
1555
1562
|
|
1556
1563
|
if variables is not None:
|
1557
|
-
vars = [
|
1564
|
+
vars = [
|
1565
|
+
_Variable(k, v) for k, v in variables.items() if v is not None and not cached_isinstance(v, NativeValue)
|
1566
|
+
]
|
1558
1567
|
if get_robot_version() < (7, 0):
|
1559
1568
|
result.set_from_variable_table(vars)
|
1560
1569
|
else:
|
1561
1570
|
result.set_from_variable_section(vars)
|
1562
1571
|
|
1563
1572
|
for k2, v2 in variables.items():
|
1564
|
-
if
|
1573
|
+
if cached_isinstance(v2, NativeValue):
|
1565
1574
|
result[k2] = v2.value
|
1566
1575
|
|
1567
1576
|
result.resolve_delayed()
|
@@ -1576,7 +1585,6 @@ def resolve_variable(
|
|
1576
1585
|
command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
|
1577
1586
|
variables: Optional[Dict[str, Optional[Any]]] = None,
|
1578
1587
|
) -> Any:
|
1579
|
-
|
1580
1588
|
_update_env(working_dir)
|
1581
1589
|
|
1582
1590
|
if contains_variable(name, "$@&%"):
|
@@ -2645,8 +2653,6 @@ def complete_variables_import(
|
|
2645
2653
|
def get_model_doc(
|
2646
2654
|
model: ast.AST,
|
2647
2655
|
source: str,
|
2648
|
-
model_type: str = "RESOURCE",
|
2649
|
-
scope: str = "GLOBAL",
|
2650
2656
|
append_model_errors: bool = True,
|
2651
2657
|
) -> LibraryDoc:
|
2652
2658
|
errors: List[Error] = []
|
@@ -2777,8 +2783,8 @@ def get_model_doc(
|
|
2777
2783
|
libdoc = LibraryDoc(
|
2778
2784
|
name=lib.name or "",
|
2779
2785
|
doc=lib.doc,
|
2780
|
-
type=
|
2781
|
-
scope=
|
2786
|
+
type="RESOURCE",
|
2787
|
+
scope="GLOBAL",
|
2782
2788
|
source=source,
|
2783
2789
|
line_no=1,
|
2784
2790
|
errors=errors,
|