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.
@@ -5,7 +5,6 @@ import time
5
5
  import weakref
6
6
  from collections import OrderedDict, defaultdict
7
7
  from concurrent.futures import CancelledError
8
- from itertools import chain
9
8
  from pathlib import Path
10
9
  from typing import (
11
10
  Any,
@@ -15,7 +14,6 @@ from typing import (
15
14
  List,
16
15
  NamedTuple,
17
16
  Optional,
18
- Sequence,
19
17
  Set,
20
18
  Tuple,
21
19
  Union,
@@ -23,7 +21,6 @@ from typing import (
23
21
  )
24
22
 
25
23
  from robot.errors import VariableError
26
- from robot.libraries import STDLIBS
27
24
  from robot.parsing.lexer.tokens import Token
28
25
  from robot.parsing.model.blocks import Keyword, SettingSection, TestCase, VariableSection
29
26
  from robot.parsing.model.statements import Arguments, Setup, Statement, Timeout
@@ -54,21 +51,18 @@ from robotcode.core.text_document import TextDocument
54
51
  from robotcode.core.uri import Uri
55
52
  from robotcode.core.utils.logging import LoggingDescriptor
56
53
 
57
- from ..utils import get_robot_version
58
54
  from ..utils.ast import (
59
55
  range_from_node,
60
56
  range_from_token,
61
57
  strip_variable_token,
62
58
  tokenize_variables,
63
59
  )
64
- from ..utils.match import eq_namespace
65
60
  from ..utils.stubs import Languages
66
61
  from ..utils.variables import BUILTIN_VARIABLES
67
62
  from ..utils.visitor import Visitor
68
63
  from .entities import (
69
64
  ArgumentDefinition,
70
65
  BuiltInVariableDefinition,
71
- CommandLineVariableDefinition,
72
66
  EnvironmentVariableDefinition,
73
67
  GlobalVariableDefinition,
74
68
  Import,
@@ -86,15 +80,16 @@ from .entities import (
86
80
  )
87
81
  from .errors import DIAGNOSTICS_SOURCE_NAME, Error
88
82
  from .imports_manager import ImportsManager
83
+ from .keyword_finder import KeywordFinder
89
84
  from .library_doc import (
90
85
  BUILTIN_LIBRARY_NAME,
91
86
  DEFAULT_LIBRARIES,
92
87
  KeywordDoc,
93
- KeywordError,
94
88
  KeywordMatcher,
95
89
  LibraryDoc,
96
90
  resolve_robot_variables,
97
91
  )
92
+ from .namespace_analyzer import NamespaceAnalyzer
98
93
 
99
94
 
100
95
  class DiagnosticsError(Exception):
@@ -174,12 +169,14 @@ class VariablesVisitor(Visitor):
174
169
 
175
170
 
176
171
  class VariableVisitorBase(Visitor):
172
+
177
173
  def __init__(
178
174
  self,
179
175
  namespace: "Namespace",
180
176
  nodes: Optional[List[ast.AST]] = None,
181
177
  position: Optional[Position] = None,
182
178
  in_args: bool = True,
179
+ resolved_variables: Any = None,
183
180
  ) -> None:
184
181
  super().__init__()
185
182
  self.namespace = namespace
@@ -190,6 +187,7 @@ class VariableVisitorBase(Visitor):
190
187
  self._results: Dict[str, VariableDefinition] = {}
191
188
  self.current_kw_doc: Optional[KeywordDoc] = None
192
189
  self.current_kw: Optional[Keyword] = None
190
+ self._resolved_variables: Any = resolved_variables
193
191
 
194
192
  def get_variable_token(self, token: Token) -> Optional[Token]:
195
193
  return next(
@@ -344,12 +342,15 @@ class BlockVariableVisitor(OnlyArgumentsVisitor):
344
342
  pass
345
343
 
346
344
  def _get_var_name(self, original: str, position: Position, require_assign: bool = True) -> Optional[str]:
347
- robot_variables = resolve_robot_variables(
348
- str(self.namespace.imports_manager.root_folder),
349
- str(Path(self.namespace.source).parent) if self.namespace.source else ".",
350
- self.namespace.imports_manager.get_resolvable_command_line_variables(),
351
- variables=self.namespace.get_resolvable_variables(),
352
- )
345
+ if self._resolved_variables is None:
346
+ self._resolved_variables = resolve_robot_variables(
347
+ str(self.namespace.imports_manager.root_folder),
348
+ str(Path(self.namespace.source).parent) if self.namespace.source else ".",
349
+ self.namespace.imports_manager.get_resolvable_command_line_variables(),
350
+ variables=self.namespace.get_resolvable_variables(),
351
+ )
352
+
353
+ robot_variables = self._resolved_variables
353
354
 
354
355
  try:
355
356
  replaced = robot_variables.replace_string(original)
@@ -680,8 +681,9 @@ class Namespace:
680
681
  self._namespaces: Optional[Dict[KeywordMatcher, List[LibraryEntry]]] = None
681
682
  self._libraries_matchers: Optional[Dict[KeywordMatcher, LibraryEntry]] = None
682
683
  self._resources: Dict[str, ResourceEntry] = OrderedDict()
684
+ self._resources_files: Dict[str, ResourceEntry] = OrderedDict()
683
685
  self._resources_matchers: Optional[Dict[KeywordMatcher, ResourceEntry]] = None
684
- self._variables: Dict[str, VariablesEntry] = OrderedDict()
686
+ self._variables_imports: Dict[str, VariablesEntry] = OrderedDict()
685
687
  self._initialized = False
686
688
  self._invalid = False
687
689
  self._initialize_lock = RLock(default_timeout=120, name="Namespace.initialize")
@@ -694,15 +696,23 @@ class Namespace:
694
696
  self._own_variables: Optional[List[VariableDefinition]] = None
695
697
  self._own_variables_lock = RLock(default_timeout=120, name="Namespace.own_variables")
696
698
  self._global_variables: Optional[List[VariableDefinition]] = None
697
- self._global_variables_dict: Optional[Dict[VariableMatcher, VariableDefinition]] = None
698
699
  self._global_variables_lock = RLock(default_timeout=120, name="Namespace.global_variables")
700
+ self._global_variables_dict: Optional[Dict[VariableMatcher, VariableDefinition]] = None
699
701
  self._global_variables_dict_lock = RLock(default_timeout=120, name="Namespace.global_variables_dict")
700
702
 
703
+ self._imported_variables: Optional[List[VariableDefinition]] = None
704
+ self._imported_variables_lock = RLock(default_timeout=120, name="Namespace._imported_variables_lock")
705
+
701
706
  self._global_resolvable_variables: Optional[Dict[str, Any]] = None
702
707
  self._global_resolvable_variables_lock = RLock(
703
708
  default_timeout=120, name="Namespace._global_resolvable_variables_lock"
704
709
  )
705
710
 
711
+ self._global_resolved_variables: Any = None
712
+ self._global_resolved_variables_lock = RLock(
713
+ default_timeout=120, name="Namespace._global_resolvabled_variables_lock"
714
+ )
715
+
706
716
  self._suite_variables: Optional[Dict[str, Any]] = None
707
717
  self._suite_variables_lock = RLock(default_timeout=120, name="Namespace.global_variables")
708
718
 
@@ -729,8 +739,6 @@ class Namespace:
729
739
 
730
740
  self._in_initialize = False
731
741
 
732
- self._ignored_lines: Optional[List[int]] = None
733
-
734
742
  @event
735
743
  def has_invalidated(sender) -> None: ...
736
744
 
@@ -740,19 +748,14 @@ class Namespace:
740
748
  @event
741
749
  def has_analysed(sender) -> None: ...
742
750
 
743
- @event
744
- def library_import_changed(sender) -> None: ...
745
-
746
- @event
747
- def resource_import_changed(sender) -> None: ...
748
-
749
- @event
750
- def variables_import_changed(sender) -> None: ...
751
-
752
751
  @property
753
752
  def document(self) -> Optional[TextDocument]:
754
753
  return self._document() if self._document is not None else None
755
754
 
755
+ @property
756
+ def document_uri(self) -> str:
757
+ return self.document.document_uri if self.document is not None else str(Uri.from_path(self.source))
758
+
756
759
  @property
757
760
  def search_order(self) -> Tuple[str, ...]:
758
761
  if self._search_order is None:
@@ -802,7 +805,7 @@ class Namespace:
802
805
  invalidate = False
803
806
 
804
807
  for p in variables:
805
- if any(e for e in self._variables.values() if e.library_doc.source == p.source):
808
+ if any(e for e in self._variables_imports.values() if e.library_doc.source == p.source):
806
809
  invalidate = True
807
810
  break
808
811
 
@@ -898,10 +901,10 @@ class Namespace:
898
901
 
899
902
  return self._resources
900
903
 
901
- def get_imported_variables(self) -> Dict[str, VariablesEntry]:
904
+ def get_variables_imports(self) -> Dict[str, VariablesEntry]:
902
905
  self.ensure_initialized()
903
906
 
904
- return self._variables
907
+ return self._variables_imports
905
908
 
906
909
  @_logger.call
907
910
  def get_library_doc(self) -> LibraryDoc:
@@ -910,7 +913,6 @@ class Namespace:
910
913
  self._library_doc = self.imports_manager.get_libdoc_from_model(
911
914
  self.model,
912
915
  self.source,
913
- model_type="RESOURCE",
914
916
  append_model_errors=self.document_type is not None and self.document_type == DocumentType.RESOURCE,
915
917
  )
916
918
 
@@ -928,38 +930,38 @@ class Namespace:
928
930
  def ensure_initialized(self) -> bool:
929
931
  with self._initialize_lock:
930
932
  if not self._initialized:
933
+ with self._logger.measure_time(
934
+ lambda: f"Initialize Namespace for {self.source}", context_name="import"
935
+ ):
936
+ succeed = False
937
+ try:
938
+ imports = self.get_imports()
931
939
 
932
- succeed = False
933
- try:
934
- self._logger.debug(lambda: f"initialize {self.document}")
935
-
936
- imports = self.get_imports()
937
-
938
- variables = self.get_suite_variables()
940
+ variables = self.get_suite_variables()
939
941
 
940
- self._import_default_libraries(variables)
941
- self._import_imports(
942
- imports,
943
- str(Path(self.source).parent),
944
- top_level=True,
945
- variables=variables,
946
- )
942
+ self._import_default_libraries(variables)
943
+ self._import_imports(
944
+ imports,
945
+ str(Path(self.source).parent),
946
+ top_level=True,
947
+ variables=variables,
948
+ )
947
949
 
948
- self._reset_global_variables()
950
+ self._reset_global_variables()
949
951
 
950
- self._initialized = True
951
- succeed = True
952
+ self._initialized = True
953
+ succeed = True
952
954
 
953
- except BaseException:
954
- if self.document is not None:
955
- self.document.remove_data(Namespace)
956
- self.document.remove_data(Namespace.DataEntry)
955
+ except BaseException:
956
+ if self.document is not None:
957
+ self.document.remove_data(Namespace)
958
+ self.document.remove_data(Namespace.DataEntry)
957
959
 
958
- self._invalidate()
959
- raise
960
+ self._invalidate()
961
+ raise
960
962
 
961
- if succeed:
962
- self.has_initialized(self)
963
+ if succeed:
964
+ self.has_initialized(self)
963
965
 
964
966
  return self._initialized
965
967
 
@@ -986,10 +988,10 @@ class Namespace:
986
988
 
987
989
  return self._own_variables
988
990
 
989
- _builtin_variables: Optional[List[BuiltInVariableDefinition]] = None
991
+ _builtin_variables: Optional[List[VariableDefinition]] = None
990
992
 
991
993
  @classmethod
992
- def get_builtin_variables(cls) -> List[BuiltInVariableDefinition]:
994
+ def get_builtin_variables(cls) -> List[VariableDefinition]:
993
995
  if cls._builtin_variables is None:
994
996
  cls._builtin_variables = [BuiltInVariableDefinition(0, 0, 0, 0, "", n, None) for n in BUILTIN_VARIABLES]
995
997
 
@@ -1001,11 +1003,14 @@ class Namespace:
1001
1003
 
1002
1004
  def _reset_global_variables(self) -> None:
1003
1005
  with self._global_variables_lock, self._global_variables_dict_lock, self._suite_variables_lock:
1004
- with self._global_resolvable_variables_lock:
1005
- self._global_variables = None
1006
- self._global_variables_dict = None
1007
- self._suite_variables = None
1008
- self._global_resolvable_variables = None
1006
+ with self._global_resolvable_variables_lock, self._global_resolved_variables_lock:
1007
+ with self._imported_variables_lock:
1008
+ self._global_variables = None
1009
+ self._global_variables_dict = None
1010
+ self._suite_variables = None
1011
+ self._global_resolvable_variables = None
1012
+ self._global_resolved_variables = None
1013
+ self._imported_variables = None
1009
1014
 
1010
1015
  def get_global_variables(self) -> List[VariableDefinition]:
1011
1016
  with self._global_variables_lock:
@@ -1015,7 +1020,7 @@ class Namespace:
1015
1020
  self.get_command_line_variables(),
1016
1021
  self.get_own_variables(),
1017
1022
  *(e.variables for e in self._resources.values()),
1018
- *(e.variables for e in self._variables.values()),
1023
+ *(e.variables for e in self._variables_imports.values()),
1019
1024
  self.get_builtin_variables(),
1020
1025
  )
1021
1026
  )
@@ -1037,8 +1042,6 @@ class Namespace:
1037
1042
  skip_local_variables: bool = False,
1038
1043
  skip_global_variables: bool = False,
1039
1044
  ) -> Iterator[Tuple[VariableMatcher, VariableDefinition]]:
1040
- yielded: Dict[VariableMatcher, VariableDefinition] = {}
1041
-
1042
1045
  test_or_keyword = None
1043
1046
  test_or_keyword_nodes = None
1044
1047
 
@@ -1056,43 +1059,41 @@ class Namespace:
1056
1059
  isinstance(test_or_keyword_nodes[-1], (Arguments, Setup, Timeout)) if test_or_keyword_nodes else False
1057
1060
  )
1058
1061
 
1059
- for var in chain(
1060
- *[
1062
+ yield from (
1063
+ (var.matcher, var)
1064
+ for var in itertools.chain(
1061
1065
  (
1062
1066
  (
1063
1067
  (OnlyArgumentsVisitor if only_args else BlockVariableVisitor)(
1064
- self,
1065
- nodes,
1066
- position,
1067
- in_args,
1068
+ self, nodes, position, in_args, resolved_variables=self.get_global_resolved_variables()
1068
1069
  ).get(test_or_keyword)
1069
1070
  )
1070
1071
  if test_or_keyword is not None and not skip_local_variables
1071
1072
  else []
1072
- )
1073
- ],
1074
- self.get_global_variables() if not skip_global_variables else [],
1075
- ):
1076
- if var.matcher not in yielded:
1077
- if skip_commandline_variables and isinstance(var, CommandLineVariableDefinition):
1078
- continue
1073
+ ),
1074
+ [] if skip_global_variables or skip_commandline_variables else self.get_command_line_variables(),
1075
+ [] if skip_global_variables else self.get_own_variables(),
1076
+ [] if skip_global_variables else self.get_imported_variables(),
1077
+ [] if skip_global_variables else self.get_builtin_variables(),
1078
+ )
1079
+ )
1079
1080
 
1080
- yielded[var.matcher] = var
1081
+ def get_imported_variables(self) -> List[VariableDefinition]:
1082
+ with self._imported_variables_lock:
1083
+ if self._imported_variables is None:
1084
+ self._imported_variables = list(
1085
+ itertools.chain(
1086
+ *(e.variables for e in self._resources.values()),
1087
+ *(e.variables for e in self._variables_imports.values()),
1088
+ ),
1089
+ )
1081
1090
 
1082
- yield var.matcher, var
1091
+ return self._imported_variables
1083
1092
 
1084
1093
  def get_suite_variables(self) -> Dict[str, Any]:
1085
1094
  with self._suite_variables_lock:
1086
- vars = {}
1087
-
1088
- def check_var(var: VariableDefinition) -> bool:
1089
- if var.matcher in vars:
1090
- return False
1091
- vars[var.matcher] = var
1092
-
1093
- return var.has_value
1094
-
1095
- self._suite_variables = {v.name: v.value for v in filter(check_var, self.get_global_variables())}
1095
+ if self._suite_variables is None:
1096
+ self._suite_variables = {v.name: v.value for v in reversed(self.get_global_variables())}
1096
1097
 
1097
1098
  return self._suite_variables
1098
1099
 
@@ -1117,6 +1118,19 @@ class Namespace:
1117
1118
  }
1118
1119
  return self._global_resolvable_variables
1119
1120
 
1121
+ def get_global_resolved_variables(
1122
+ self,
1123
+ ) -> Any:
1124
+ with self._global_resolved_variables_lock:
1125
+ if self._global_resolved_variables is None:
1126
+ self._global_resolved_variables = resolve_robot_variables(
1127
+ str(self.imports_manager.root_folder),
1128
+ str(Path(self.source).parent) if self.source else ".",
1129
+ self.imports_manager.get_resolvable_command_line_variables(),
1130
+ variables=self.get_resolvable_variables(),
1131
+ )
1132
+ return self._global_resolved_variables
1133
+
1120
1134
  def get_variable_matchers(
1121
1135
  self,
1122
1136
  nodes: Optional[List[ast.AST]] = None,
@@ -1154,15 +1168,24 @@ class Namespace:
1154
1168
  try:
1155
1169
  matcher = VariableMatcher(name)
1156
1170
 
1157
- for m, v in self.yield_variables(
1158
- nodes,
1159
- position,
1160
- skip_commandline_variables=skip_commandline_variables,
1161
- skip_local_variables=skip_local_variables,
1162
- skip_global_variables=True,
1163
- ):
1164
- if matcher == m:
1165
- return v
1171
+ if nodes:
1172
+ result = next(
1173
+ (
1174
+ v
1175
+ for m, v in self.yield_variables(
1176
+ nodes,
1177
+ position,
1178
+ skip_commandline_variables=skip_commandline_variables,
1179
+ skip_local_variables=skip_local_variables,
1180
+ skip_global_variables=True,
1181
+ )
1182
+ if matcher == m
1183
+ ),
1184
+ None,
1185
+ )
1186
+
1187
+ if result is not None:
1188
+ return result
1166
1189
 
1167
1190
  result = self.get_global_variables_dict().get(matcher, None)
1168
1191
  if matcher is not None:
@@ -1223,6 +1246,10 @@ class Namespace:
1223
1246
 
1224
1247
  source = self.imports_manager.find_resource(value.name, base_dir, variables=variables)
1225
1248
 
1249
+ if source in self._resources_files:
1250
+ self._logger.debug(lambda: f"Resource '{value.name}' already imported.", context_name="import")
1251
+ return None
1252
+
1226
1253
  if self.source == source:
1227
1254
  if parent_import:
1228
1255
  self.append_diagnostics(
@@ -1256,6 +1283,9 @@ class Namespace:
1256
1283
 
1257
1284
  self._import_entries[value] = result
1258
1285
 
1286
+ if result.variables:
1287
+ self._reset_global_variables()
1288
+
1259
1289
  if top_level and (
1260
1290
  not result.library_doc.errors
1261
1291
  and top_level
@@ -1286,6 +1316,9 @@ class Namespace:
1286
1316
  result.import_source = value.source
1287
1317
 
1288
1318
  self._import_entries[value] = result
1319
+
1320
+ if result.variables:
1321
+ self._reset_global_variables()
1289
1322
  else:
1290
1323
  raise DiagnosticsError("Unknown import type.")
1291
1324
 
@@ -1385,8 +1418,6 @@ class Namespace:
1385
1418
  else None
1386
1419
  ),
1387
1420
  )
1388
- finally:
1389
- self._reset_global_variables()
1390
1421
 
1391
1422
  return result
1392
1423
 
@@ -1423,13 +1454,11 @@ class Namespace:
1423
1454
  if entry is not None:
1424
1455
  if isinstance(entry, ResourceEntry):
1425
1456
  assert entry.library_doc.source is not None
1426
- already_imported_resources = next(
1427
- (e for e in self._resources.values() if e.library_doc.source == entry.library_doc.source),
1428
- None,
1429
- )
1457
+ allread_imported_resource = self._resources_files.get(entry.library_doc.source, None)
1430
1458
 
1431
- if already_imported_resources is None and entry.library_doc.source != self.source:
1459
+ if allread_imported_resource is None and entry.library_doc.source != self.source:
1432
1460
  self._resources[entry.import_name] = entry
1461
+ self._resources_files[entry.library_doc.source] = entry
1433
1462
  if entry.variables:
1434
1463
  variables = self.get_suite_variables()
1435
1464
 
@@ -1463,9 +1492,7 @@ class Namespace:
1463
1492
  source=DIAGNOSTICS_SOURCE_NAME,
1464
1493
  code=Error.RECURSIVE_IMPORT,
1465
1494
  )
1466
- elif (
1467
- already_imported_resources is not None and already_imported_resources.library_doc.source
1468
- ):
1495
+ elif allread_imported_resource is not None and allread_imported_resource.library_doc.source:
1469
1496
  self.append_diagnostics(
1470
1497
  range=entry.import_range,
1471
1498
  message=f"Resource {entry} already imported.",
@@ -1475,13 +1502,13 @@ class Namespace:
1475
1502
  [
1476
1503
  DiagnosticRelatedInformation(
1477
1504
  location=Location(
1478
- uri=str(Uri.from_path(already_imported_resources.import_source)),
1479
- range=already_imported_resources.import_range,
1505
+ uri=str(Uri.from_path(allread_imported_resource.import_source)),
1506
+ range=allread_imported_resource.import_range,
1480
1507
  ),
1481
1508
  message="",
1482
1509
  )
1483
1510
  ]
1484
- if already_imported_resources.import_source
1511
+ if allread_imported_resource.import_source
1485
1512
  else None
1486
1513
  ),
1487
1514
  code=Error.RESOURCE_ALREADY_IMPORTED,
@@ -1491,7 +1518,7 @@ class Namespace:
1491
1518
  already_imported_variables = next(
1492
1519
  (
1493
1520
  e
1494
- for e in self._variables.values()
1521
+ for e in self._variables_imports.values()
1495
1522
  if e.library_doc.source == entry.library_doc.source
1496
1523
  and e.alias == entry.alias
1497
1524
  and e.args == entry.args
@@ -1503,7 +1530,7 @@ class Namespace:
1503
1530
  and entry.library_doc is not None
1504
1531
  and entry.library_doc.source_or_origin
1505
1532
  ):
1506
- self._variables[entry.library_doc.source_or_origin] = entry
1533
+ self._variables_imports[entry.library_doc.source_or_origin] = entry
1507
1534
  if entry.variables:
1508
1535
  variables = self.get_suite_variables()
1509
1536
  elif top_level and already_imported_variables and already_imported_variables.library_doc.source:
@@ -1737,7 +1764,7 @@ class Namespace:
1737
1764
  )
1738
1765
 
1739
1766
  @_logger.call
1740
- def get_imported_variables_libdoc(self, name: str, args: Tuple[str, ...] = ()) -> Optional[LibraryDoc]:
1767
+ def get_variables_import_libdoc(self, name: str, args: Tuple[str, ...] = ()) -> Optional[LibraryDoc]:
1741
1768
  self.ensure_initialized()
1742
1769
 
1743
1770
  return next(
@@ -1835,8 +1862,6 @@ class Namespace:
1835
1862
 
1836
1863
  @_logger.call(condition=lambda self: not self._analyzed)
1837
1864
  def analyze(self) -> None:
1838
- from .namespace_analyzer import NamespaceAnalyzer
1839
-
1840
1865
  with self._analyze_lock:
1841
1866
  if not self._analyzed:
1842
1867
  canceled = False
@@ -1907,438 +1932,3 @@ class Namespace:
1907
1932
  raise_keyword_error=raise_keyword_error,
1908
1933
  handle_bdd_style=handle_bdd_style,
1909
1934
  )
1910
-
1911
-
1912
- class DiagnosticsEntry(NamedTuple):
1913
- message: str
1914
- severity: DiagnosticSeverity
1915
- code: Optional[str] = None
1916
-
1917
-
1918
- class CancelSearchError(Exception):
1919
- pass
1920
-
1921
-
1922
- DEFAULT_BDD_PREFIXES = {"Given ", "When ", "Then ", "And ", "But "}
1923
-
1924
-
1925
- class KeywordFinder:
1926
- def __init__(self, namespace: Namespace, library_doc: LibraryDoc) -> None:
1927
- self.namespace = namespace
1928
- self.self_library_doc = library_doc
1929
-
1930
- self.diagnostics: List[DiagnosticsEntry] = []
1931
- self.multiple_keywords_result: Optional[List[KeywordDoc]] = None
1932
- self._cache: Dict[
1933
- Tuple[Optional[str], bool],
1934
- Tuple[
1935
- Optional[KeywordDoc],
1936
- List[DiagnosticsEntry],
1937
- Optional[List[KeywordDoc]],
1938
- ],
1939
- ] = {}
1940
- self.handle_bdd_style = True
1941
- self._all_keywords: Optional[List[LibraryEntry]] = None
1942
- self._resource_keywords: Optional[List[ResourceEntry]] = None
1943
- self._library_keywords: Optional[List[LibraryEntry]] = None
1944
-
1945
- def reset_diagnostics(self) -> None:
1946
- self.diagnostics = []
1947
- self.multiple_keywords_result = None
1948
-
1949
- def find_keyword(
1950
- self,
1951
- name: Optional[str],
1952
- *,
1953
- raise_keyword_error: bool = False,
1954
- handle_bdd_style: bool = True,
1955
- ) -> Optional[KeywordDoc]:
1956
- try:
1957
- self.reset_diagnostics()
1958
-
1959
- self.handle_bdd_style = handle_bdd_style
1960
-
1961
- cached = self._cache.get((name, self.handle_bdd_style), None)
1962
-
1963
- if cached is not None:
1964
- self.diagnostics = cached[1]
1965
- self.multiple_keywords_result = cached[2]
1966
- return cached[0]
1967
-
1968
- try:
1969
- result = self._find_keyword(name)
1970
- if result is None:
1971
- self.diagnostics.append(
1972
- DiagnosticsEntry(
1973
- f"No keyword with name '{name}' found.",
1974
- DiagnosticSeverity.ERROR,
1975
- Error.KEYWORD_NOT_FOUND,
1976
- )
1977
- )
1978
- except KeywordError as e:
1979
- if e.multiple_keywords:
1980
- self._add_to_multiple_keywords_result(e.multiple_keywords)
1981
-
1982
- if raise_keyword_error:
1983
- raise
1984
-
1985
- result = None
1986
- self.diagnostics.append(DiagnosticsEntry(str(e), DiagnosticSeverity.ERROR, Error.KEYWORD_ERROR))
1987
-
1988
- self._cache[(name, self.handle_bdd_style)] = (
1989
- result,
1990
- self.diagnostics,
1991
- self.multiple_keywords_result,
1992
- )
1993
-
1994
- return result
1995
- except CancelSearchError:
1996
- return None
1997
-
1998
- def _find_keyword(self, name: Optional[str]) -> Optional[KeywordDoc]:
1999
- if not name:
2000
- self.diagnostics.append(
2001
- DiagnosticsEntry(
2002
- "Keyword name cannot be empty.",
2003
- DiagnosticSeverity.ERROR,
2004
- Error.KEYWORD_ERROR,
2005
- )
2006
- )
2007
- raise CancelSearchError
2008
- if not isinstance(name, str):
2009
- self.diagnostics.append( # type: ignore
2010
- DiagnosticsEntry(
2011
- "Keyword name must be a string.",
2012
- DiagnosticSeverity.ERROR,
2013
- Error.KEYWORD_ERROR,
2014
- )
2015
- )
2016
- raise CancelSearchError
2017
-
2018
- result = self._get_keyword_from_self(name)
2019
- if not result and "." in name:
2020
- result = self._get_explicit_keyword(name)
2021
-
2022
- if not result:
2023
- result = self._get_implicit_keyword(name)
2024
-
2025
- if not result and self.handle_bdd_style:
2026
- return self._get_bdd_style_keyword(name)
2027
-
2028
- return result
2029
-
2030
- def _get_keyword_from_self(self, name: str) -> Optional[KeywordDoc]:
2031
- if get_robot_version() >= (6, 0):
2032
- found: List[Tuple[Optional[LibraryEntry], KeywordDoc]] = [
2033
- (None, v) for v in self.self_library_doc.keywords.get_all(name)
2034
- ]
2035
- if len(found) > 1:
2036
- found = self._select_best_matches(found)
2037
- if len(found) > 1:
2038
- self.diagnostics.append(
2039
- DiagnosticsEntry(
2040
- self._create_multiple_keywords_found_message(name, found, implicit=False),
2041
- DiagnosticSeverity.ERROR,
2042
- Error.MULTIPLE_KEYWORDS,
2043
- )
2044
- )
2045
- raise CancelSearchError
2046
-
2047
- if len(found) == 1:
2048
- # TODO warning if keyword found is defined in resource and suite
2049
- return found[0][1]
2050
-
2051
- return None
2052
-
2053
- try:
2054
- return self.self_library_doc.keywords.get(name, None)
2055
- except KeywordError as e:
2056
- self.diagnostics.append(DiagnosticsEntry(str(e), DiagnosticSeverity.ERROR, Error.KEYWORD_ERROR))
2057
- raise CancelSearchError from e
2058
-
2059
- def _yield_owner_and_kw_names(self, full_name: str) -> Iterator[Tuple[str, ...]]:
2060
- tokens = full_name.split(".")
2061
- for i in range(1, len(tokens)):
2062
- yield ".".join(tokens[:i]), ".".join(tokens[i:])
2063
-
2064
- def _get_explicit_keyword(self, name: str) -> Optional[KeywordDoc]:
2065
- found: List[Tuple[Optional[LibraryEntry], KeywordDoc]] = []
2066
- for owner_name, kw_name in self._yield_owner_and_kw_names(name):
2067
- found.extend(self.find_keywords(owner_name, kw_name))
2068
-
2069
- if get_robot_version() >= (6, 0) and len(found) > 1:
2070
- found = self._select_best_matches(found)
2071
-
2072
- if len(found) > 1:
2073
- self.diagnostics.append(
2074
- DiagnosticsEntry(
2075
- self._create_multiple_keywords_found_message(name, found, implicit=False),
2076
- DiagnosticSeverity.ERROR,
2077
- Error.MULTIPLE_KEYWORDS,
2078
- )
2079
- )
2080
- raise CancelSearchError
2081
-
2082
- return found[0][1] if found else None
2083
-
2084
- def find_keywords(self, owner_name: str, name: str) -> List[Tuple[LibraryEntry, KeywordDoc]]:
2085
- if self._all_keywords is None:
2086
- self._all_keywords = list(
2087
- chain(
2088
- self.namespace._libraries.values(),
2089
- self.namespace._resources.values(),
2090
- )
2091
- )
2092
-
2093
- if get_robot_version() >= (6, 0):
2094
- result: List[Tuple[LibraryEntry, KeywordDoc]] = []
2095
- for v in self._all_keywords:
2096
- if eq_namespace(v.alias or v.name, owner_name):
2097
- result.extend((v, kw) for kw in v.library_doc.keywords.get_all(name))
2098
- return result
2099
-
2100
- result = []
2101
- for v in self._all_keywords:
2102
- if eq_namespace(v.alias or v.name, owner_name):
2103
- kw = v.library_doc.keywords.get(name, None)
2104
- if kw is not None:
2105
- result.append((v, kw))
2106
- return result
2107
-
2108
- def _add_to_multiple_keywords_result(self, kw: Iterable[KeywordDoc]) -> None:
2109
- if self.multiple_keywords_result is None:
2110
- self.multiple_keywords_result = list(kw)
2111
- else:
2112
- self.multiple_keywords_result.extend(kw)
2113
-
2114
- def _create_multiple_keywords_found_message(
2115
- self,
2116
- name: str,
2117
- found: Sequence[Tuple[Optional[LibraryEntry], KeywordDoc]],
2118
- implicit: bool = True,
2119
- ) -> str:
2120
- self._add_to_multiple_keywords_result([k for _, k in found])
2121
-
2122
- if any(e[1].is_embedded for e in found):
2123
- error = f"Multiple keywords matching name '{name}' found"
2124
- else:
2125
- error = f"Multiple keywords with name '{name}' found"
2126
-
2127
- if implicit:
2128
- error += ". Give the full name of the keyword you want to use"
2129
-
2130
- 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)
2131
- return "\n ".join([f"{error}:", *names])
2132
-
2133
- def _get_implicit_keyword(self, name: str) -> Optional[KeywordDoc]:
2134
- result = self._get_keyword_from_resource_files(name)
2135
- if not result:
2136
- return self._get_keyword_from_libraries(name)
2137
- return result
2138
-
2139
- def _prioritize_same_file_or_public(
2140
- self, entries: List[Tuple[Optional[LibraryEntry], KeywordDoc]]
2141
- ) -> List[Tuple[Optional[LibraryEntry], KeywordDoc]]:
2142
- matches = [h for h in entries if h[1].source == self.namespace.source]
2143
- if matches:
2144
- return matches
2145
-
2146
- matches = [handler for handler in entries if not handler[1].is_private()]
2147
-
2148
- return matches or entries
2149
-
2150
- def _select_best_matches(
2151
- self, entries: List[Tuple[Optional[LibraryEntry], KeywordDoc]]
2152
- ) -> List[Tuple[Optional[LibraryEntry], KeywordDoc]]:
2153
- normal = [hand for hand in entries if not hand[1].is_embedded]
2154
- if normal:
2155
- return normal
2156
-
2157
- matches = [hand for hand in entries if not self._is_worse_match_than_others(hand, entries)]
2158
- return matches or entries
2159
-
2160
- def _is_worse_match_than_others(
2161
- self,
2162
- candidate: Tuple[Optional[LibraryEntry], KeywordDoc],
2163
- alternatives: List[Tuple[Optional[LibraryEntry], KeywordDoc]],
2164
- ) -> bool:
2165
- for other in alternatives:
2166
- if (
2167
- candidate[1] is not other[1]
2168
- and self._is_better_match(other, candidate)
2169
- and not self._is_better_match(candidate, other)
2170
- ):
2171
- return True
2172
- return False
2173
-
2174
- def _is_better_match(
2175
- self,
2176
- candidate: Tuple[Optional[LibraryEntry], KeywordDoc],
2177
- other: Tuple[Optional[LibraryEntry], KeywordDoc],
2178
- ) -> bool:
2179
- return (
2180
- other[1].matcher.embedded_arguments.match(candidate[1].name) is not None
2181
- and candidate[1].matcher.embedded_arguments.match(other[1].name) is None
2182
- )
2183
-
2184
- def _get_keyword_from_resource_files(self, name: str) -> Optional[KeywordDoc]:
2185
- if self._resource_keywords is None:
2186
- self._resource_keywords = list(chain(self.namespace._resources.values()))
2187
-
2188
- if get_robot_version() >= (6, 0):
2189
- found: List[Tuple[Optional[LibraryEntry], KeywordDoc]] = []
2190
- for v in self._resource_keywords:
2191
- r = v.library_doc.keywords.get_all(name)
2192
- if r:
2193
- found.extend([(v, k) for k in r])
2194
- else:
2195
- found = []
2196
- for k in self._resource_keywords:
2197
- s = k.library_doc.keywords.get(name, None)
2198
- if s is not None:
2199
- found.append((k, s))
2200
-
2201
- if not found:
2202
- return None
2203
-
2204
- if get_robot_version() >= (6, 0):
2205
- if len(found) > 1:
2206
- found = self._prioritize_same_file_or_public(found)
2207
-
2208
- if len(found) > 1:
2209
- found = self._select_best_matches(found)
2210
-
2211
- if len(found) > 1:
2212
- found = self._get_keyword_based_on_search_order(found)
2213
-
2214
- else:
2215
- if len(found) > 1:
2216
- found = self._get_keyword_based_on_search_order(found)
2217
-
2218
- if len(found) == 1:
2219
- return found[0][1]
2220
-
2221
- self.diagnostics.append(
2222
- DiagnosticsEntry(
2223
- self._create_multiple_keywords_found_message(name, found),
2224
- DiagnosticSeverity.ERROR,
2225
- Error.MULTIPLE_KEYWORDS,
2226
- )
2227
- )
2228
- raise CancelSearchError
2229
-
2230
- def _get_keyword_based_on_search_order(
2231
- self, entries: List[Tuple[Optional[LibraryEntry], KeywordDoc]]
2232
- ) -> List[Tuple[Optional[LibraryEntry], KeywordDoc]]:
2233
- for libname in self.namespace.search_order:
2234
- for e in entries:
2235
- if e[0] is not None and eq_namespace(libname, e[0].alias or e[0].name):
2236
- return [e]
2237
-
2238
- return entries
2239
-
2240
- def _get_keyword_from_libraries(self, name: str) -> Optional[KeywordDoc]:
2241
- if self._library_keywords is None:
2242
- self._library_keywords = list(chain(self.namespace._libraries.values()))
2243
-
2244
- if get_robot_version() >= (6, 0):
2245
- found: List[Tuple[Optional[LibraryEntry], KeywordDoc]] = []
2246
- for v in self._library_keywords:
2247
- r = v.library_doc.keywords.get_all(name)
2248
- if r:
2249
- found.extend([(v, k) for k in r])
2250
- else:
2251
- found = []
2252
-
2253
- for k in self._library_keywords:
2254
- s = k.library_doc.keywords.get(name, None)
2255
- if s is not None:
2256
- found.append((k, s))
2257
-
2258
- if not found:
2259
- return None
2260
-
2261
- if get_robot_version() >= (6, 0):
2262
- if len(found) > 1:
2263
- found = self._select_best_matches(found)
2264
- if len(found) > 1:
2265
- found = self._get_keyword_based_on_search_order(found)
2266
- else:
2267
- if len(found) > 1:
2268
- found = self._get_keyword_based_on_search_order(found)
2269
- if len(found) == 2:
2270
- found = self._filter_stdlib_runner(*found)
2271
-
2272
- if len(found) == 1:
2273
- return found[0][1]
2274
-
2275
- self.diagnostics.append(
2276
- DiagnosticsEntry(
2277
- self._create_multiple_keywords_found_message(name, found),
2278
- DiagnosticSeverity.ERROR,
2279
- Error.MULTIPLE_KEYWORDS,
2280
- )
2281
- )
2282
- raise CancelSearchError
2283
-
2284
- def _filter_stdlib_runner(
2285
- self,
2286
- entry1: Tuple[Optional[LibraryEntry], KeywordDoc],
2287
- entry2: Tuple[Optional[LibraryEntry], KeywordDoc],
2288
- ) -> List[Tuple[Optional[LibraryEntry], KeywordDoc]]:
2289
- stdlibs_without_remote = STDLIBS - {"Remote"}
2290
- if entry1[0] is not None and entry1[0].name in stdlibs_without_remote:
2291
- standard, custom = entry1, entry2
2292
- elif entry2[0] is not None and entry2[0].name in stdlibs_without_remote:
2293
- standard, custom = entry2, entry1
2294
- else:
2295
- return [entry1, entry2]
2296
-
2297
- self.diagnostics.append(
2298
- DiagnosticsEntry(
2299
- self._create_custom_and_standard_keyword_conflict_warning_message(custom, standard),
2300
- DiagnosticSeverity.WARNING,
2301
- Error.CONFLICTING_LIBRARY_KEYWORDS,
2302
- )
2303
- )
2304
-
2305
- return [custom]
2306
-
2307
- def _create_custom_and_standard_keyword_conflict_warning_message(
2308
- self,
2309
- custom: Tuple[Optional[LibraryEntry], KeywordDoc],
2310
- standard: Tuple[Optional[LibraryEntry], KeywordDoc],
2311
- ) -> str:
2312
- custom_with_name = standard_with_name = ""
2313
- if custom[0] is not None and custom[0].alias is not None:
2314
- custom_with_name = " imported as '%s'" % custom[0].alias
2315
- if standard[0] is not None and standard[0].alias is not None:
2316
- standard_with_name = " imported as '%s'" % standard[0].alias
2317
- return (
2318
- f"Keyword '{standard[1].name}' found both from a custom test library "
2319
- f"'{'' if custom[0] is None else custom[0].name}'{custom_with_name} "
2320
- f"and a standard library '{standard[1].name}'{standard_with_name}. "
2321
- f"The custom keyword is used. To select explicitly, and to get "
2322
- f"rid of this warning, use either "
2323
- f"'{'' if custom[0] is None else custom[0].alias or custom[0].name}.{custom[1].name}' "
2324
- f"or '{'' if standard[0] is None else standard[0].alias or standard[0].name}.{standard[1].name}'."
2325
- )
2326
-
2327
- def _get_bdd_style_keyword(self, name: str) -> Optional[KeywordDoc]:
2328
- if get_robot_version() < (6, 0):
2329
- lower = name.lower()
2330
- for prefix in ["given ", "when ", "then ", "and ", "but "]:
2331
- if lower.startswith(prefix):
2332
- return self._find_keyword(name[len(prefix) :])
2333
- return None
2334
-
2335
- parts = name.split()
2336
- if len(parts) < 2:
2337
- return None
2338
- for index in range(1, len(parts)):
2339
- prefix = " ".join(parts[:index]).title()
2340
- if prefix.title() in (
2341
- self.namespace.languages.bdd_prefixes if self.namespace.languages is not None else DEFAULT_BDD_PREFIXES
2342
- ):
2343
- return self._find_keyword(" ".join(parts[index:]))
2344
- return None