robotcode-robot 0.93.0__py3-none-any.whl → 0.94.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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