robotcode-robot 0.94.0__py3-none-any.whl → 0.95.1__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.
@@ -21,9 +21,8 @@ from typing import (
21
21
 
22
22
  from robot.errors import VariableError
23
23
  from robot.parsing.lexer.tokens import Token
24
- from robot.utils.escaping import split_from_equals, unescape
24
+ from robot.utils.escaping import unescape
25
25
  from robot.variables.finders import NOT_FOUND, NumberFinder
26
- from robot.variables.search import contains_variable, search_variable
27
26
  from robotcode.core.lsp.types import Position
28
27
 
29
28
  from ..utils import get_robot_version
@@ -35,6 +34,7 @@ from ..utils.ast import (
35
34
  whitespace_at_begin_of_token,
36
35
  whitespace_from_begin_of_token,
37
36
  )
37
+ from ..utils.variables import contains_variable, search_variable, split_from_equals
38
38
  from .entities import (
39
39
  LibraryEntry,
40
40
  VariableDefinition,
@@ -210,10 +210,14 @@ class ModelHelper:
210
210
  position: Position,
211
211
  analyse_run_keywords: bool = True,
212
212
  ) -> Optional[Tuple[Optional[KeywordDoc], Token]]:
213
- keyword_doc = namespace.find_keyword(keyword_name, raise_keyword_error=False)
213
+ finder = namespace.get_finder()
214
+ keyword_doc = finder.find_keyword(keyword_name, raise_keyword_error=False)
214
215
  if keyword_doc is None:
215
216
  return None
216
217
 
218
+ if finder.result_bdd_prefix:
219
+ keyword_token = ModelHelper.strip_bdd_prefix(namespace, keyword_token)
220
+
217
221
  if position.is_in_range(range_from_token(keyword_token)):
218
222
  return keyword_doc, keyword_token
219
223
 
@@ -661,12 +665,12 @@ class ModelHelper:
661
665
 
662
666
  @classmethod
663
667
  def get_keyword_definition_at_token(cls, library_doc: LibraryDoc, token: Token) -> Optional[KeywordDoc]:
664
- return cls.get_keyword_definition_at_line(library_doc, token.value, token.lineno)
668
+ return cls.get_keyword_definition_at_line(library_doc, token.lineno)
665
669
 
666
670
  @classmethod
667
- def get_keyword_definition_at_line(cls, library_doc: LibraryDoc, value: str, line: int) -> Optional[KeywordDoc]:
671
+ def get_keyword_definition_at_line(cls, library_doc: LibraryDoc, line: int) -> Optional[KeywordDoc]:
668
672
  return next(
669
- (k for k in library_doc.keywords.iter_all(value) if k.line_no == line),
673
+ (k for k in library_doc.keywords.keywords if k.line_no == line),
670
674
  None,
671
675
  )
672
676
 
@@ -1,7 +1,6 @@
1
1
  import ast
2
2
  import enum
3
3
  import itertools
4
- import time
5
4
  import weakref
6
5
  from collections import OrderedDict, defaultdict
7
6
  from concurrent.futures import CancelledError
@@ -29,11 +28,6 @@ from robot.parsing.model.statements import ResourceImport as RobotResourceImport
29
28
  from robot.parsing.model.statements import (
30
29
  VariablesImport as RobotVariablesImport,
31
30
  )
32
- from robot.variables.search import (
33
- is_scalar_assign,
34
- is_variable,
35
- search_variable,
36
- )
37
31
  from robotcode.core.concurrent import RLock
38
32
  from robotcode.core.event import event
39
33
  from robotcode.core.lsp.types import (
@@ -58,7 +52,12 @@ from ..utils.ast import (
58
52
  tokenize_variables,
59
53
  )
60
54
  from ..utils.stubs import Languages
61
- from ..utils.variables import BUILTIN_VARIABLES
55
+ from ..utils.variables import (
56
+ BUILTIN_VARIABLES,
57
+ is_scalar_assign,
58
+ is_variable,
59
+ search_variable,
60
+ )
62
61
  from ..utils.visitor import Visitor
63
62
  from .entities import (
64
63
  ArgumentDefinition,
@@ -894,6 +893,7 @@ class Namespace:
894
893
  self._namespaces[KeywordMatcher(v.alias or v.name or v.import_name, is_namespace=True)].append(v)
895
894
  for v in (self.get_resources()).values():
896
895
  self._namespaces[KeywordMatcher(v.alias or v.name or v.import_name, is_namespace=True)].append(v)
896
+
897
897
  return self._namespaces
898
898
 
899
899
  def get_resources(self) -> Dict[str, ResourceEntry]:
@@ -1794,38 +1794,27 @@ class Namespace:
1794
1794
 
1795
1795
  libdoc = self.get_library_doc()
1796
1796
 
1797
- for doc in itertools.chain(
1797
+ yield from itertools.chain(
1798
1798
  self.get_imported_keywords(),
1799
1799
  libdoc.keywords if libdoc is not None else [],
1800
- ):
1801
- yield doc
1800
+ )
1802
1801
 
1803
1802
  @_logger.call
1804
1803
  def get_keywords(self) -> List[KeywordDoc]:
1805
1804
  with self._keywords_lock:
1806
1805
  if self._keywords is None:
1807
- current_time = time.monotonic()
1808
- self._logger.debug("start collecting keywords")
1809
- try:
1810
- i = 0
1811
1806
 
1812
- self.ensure_initialized()
1807
+ i = 0
1813
1808
 
1814
- result: Dict[KeywordMatcher, KeywordDoc] = {}
1809
+ self.ensure_initialized()
1815
1810
 
1816
- for doc in self.iter_all_keywords():
1817
- i += 1
1818
- result[doc.matcher] = doc
1811
+ result: Dict[KeywordMatcher, KeywordDoc] = {}
1819
1812
 
1820
- self._keywords = list(result.values())
1821
- except BaseException:
1822
- self._logger.debug("Canceled collecting keywords ")
1823
- raise
1824
- else:
1825
- self._logger.debug(
1826
- lambda: f"end collecting {len(self._keywords) if self._keywords else 0}"
1827
- f" keywords in {time.monotonic() - current_time}s analyze {i} keywords"
1828
- )
1813
+ for doc in self.iter_all_keywords():
1814
+ i += 1
1815
+ result[doc.matcher] = doc
1816
+
1817
+ self._keywords = list(result.values())
1829
1818
 
1830
1819
  return self._keywords
1831
1820
 
@@ -8,7 +8,7 @@ from dataclasses import dataclass
8
8
  from io import StringIO
9
9
  from pathlib import Path
10
10
  from tokenize import TokenError, generate_tokens
11
- from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Set, Tuple, Union, cast
11
+ from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional, Set, Tuple, Union, cast
12
12
 
13
13
  from robot.errors import VariableError
14
14
  from robot.parsing.lexer.tokens import Token
@@ -28,9 +28,8 @@ from robot.parsing.model.statements import (
28
28
  Variable,
29
29
  VariablesImport,
30
30
  )
31
- from robot.utils.escaping import split_from_equals, unescape
31
+ from robot.utils.escaping import unescape
32
32
  from robot.variables.finders import NOT_FOUND, NumberFinder
33
- from robot.variables.search import contains_variable, is_scalar_assign, is_variable, search_variable
34
33
  from robotcode.core.concurrent import check_current_task_canceled
35
34
  from robotcode.core.lsp.types import (
36
35
  CodeDescription,
@@ -43,6 +42,7 @@ from robotcode.core.lsp.types import (
43
42
  Range,
44
43
  )
45
44
  from robotcode.core.uri import Uri
45
+ from robotcode.core.utils.logging import LoggingDescriptor
46
46
 
47
47
  from ..utils import get_robot_version
48
48
  from ..utils.ast import (
@@ -53,11 +53,13 @@ from ..utils.ast import (
53
53
  strip_variable_token,
54
54
  tokenize_variables,
55
55
  )
56
+ from ..utils.variables import contains_variable, is_scalar_assign, is_variable, search_variable, split_from_equals
56
57
  from ..utils.visitor import Visitor
57
58
  from .entities import (
58
59
  ArgumentDefinition,
59
60
  EnvironmentVariableDefinition,
60
61
  GlobalVariableDefinition,
62
+ InvalidVariableError,
61
63
  LibraryEntry,
62
64
  LocalVariableDefinition,
63
65
  TestVariableDefinition,
@@ -94,6 +96,9 @@ class AnalyzerResult:
94
96
 
95
97
 
96
98
  class NamespaceAnalyzer(Visitor):
99
+
100
+ _logger = LoggingDescriptor()
101
+
97
102
  def __init__(
98
103
  self,
99
104
  model: ast.AST,
@@ -126,8 +131,11 @@ class NamespaceAnalyzer(Visitor):
126
131
  self._overridden_variables: Dict[VariableDefinition, VariableDefinition] = {}
127
132
 
128
133
  self._in_setting = False
134
+ self._in_block_setting = False
129
135
 
130
136
  self._suite_variables = self._variables.copy()
137
+ self._block_variables: Optional[Dict[VariableMatcher, VariableDefinition]] = None
138
+ self._end_block_handlers: Optional[List[Callable[[], None]]] = None
131
139
 
132
140
  def run(self) -> AnalyzerResult:
133
141
  self._diagnostics = []
@@ -146,10 +154,11 @@ class NamespaceAnalyzer(Visitor):
146
154
  except BaseException as e:
147
155
  self._append_diagnostics(
148
156
  range_from_node(self._model),
149
- message=f"Fatal: can't analyze namespace '{e}')",
157
+ message=f"Fatal: can't analyze namespace '{e}'.",
150
158
  severity=DiagnosticSeverity.ERROR,
151
159
  code=type(e).__qualname__,
152
160
  )
161
+ self._logger.exception(e)
153
162
 
154
163
  return AnalyzerResult(
155
164
  self._diagnostics,
@@ -379,6 +388,15 @@ class NamespaceAnalyzer(Visitor):
379
388
  finally:
380
389
  self._in_setting = False
381
390
 
391
+ def _visit_block_settings_statement(
392
+ self, node: Statement, severity: DiagnosticSeverity = DiagnosticSeverity.ERROR
393
+ ) -> None:
394
+ self._in_block_setting = True
395
+ try:
396
+ self._visit_settings_statement(node, severity)
397
+ finally:
398
+ self._in_block_setting = False
399
+
382
400
  def _analyze_token_expression_variables(
383
401
  self, token: Token, severity: DiagnosticSeverity = DiagnosticSeverity.ERROR
384
402
  ) -> None:
@@ -476,7 +494,7 @@ class NamespaceAnalyzer(Visitor):
476
494
  range=range_from_token(var_token),
477
495
  message=f"Environment variable '{var.name}' not found.",
478
496
  severity=severity,
479
- code=Error.ENVIROMMENT_VARIABLE_NOT_FOUND,
497
+ code=Error.ENVIRONMENT_VARIABLE_NOT_FOUND,
480
498
  )
481
499
 
482
500
  if var.type == VariableDefinitionType.ENVIRONMENT_VARIABLE:
@@ -531,6 +549,20 @@ class NamespaceAnalyzer(Visitor):
531
549
  )
532
550
  )
533
551
 
552
+ KEYWORDS_WITH_EXPRESSIONS = [
553
+ "BuiltIn.Evaluate",
554
+ "BuiltIn.Should Be True",
555
+ "BuiltIn.Should Not Be True",
556
+ "BuiltIn.Skip If",
557
+ "BuiltIn.Continue For Loop If",
558
+ "BuiltIn.Exit For Loop If",
559
+ "BuiltIn.Return From Keyword If",
560
+ "BuiltIn.Run Keyword And Return If",
561
+ "BuiltIn.Pass Execution If",
562
+ "BuiltIn.Run Keyword If",
563
+ "BuiltIn.Run Keyword Unless",
564
+ ]
565
+
534
566
  def _analyze_keyword_call(
535
567
  self,
536
568
  node: ast.AST,
@@ -552,13 +584,11 @@ class NamespaceAnalyzer(Visitor):
552
584
  if not allow_variables and not is_not_variable_token(keyword_token):
553
585
  return None
554
586
 
555
- result = self._finder.find_keyword(keyword, raise_keyword_error=False, handle_bdd_style=False)
587
+ result = self._finder.find_keyword(keyword, raise_keyword_error=False)
556
588
 
557
- if result is None:
589
+ if result is not None and self._finder.result_bdd_prefix:
558
590
  keyword_token = ModelHelper.strip_bdd_prefix(self._namespace, keyword_token)
559
591
 
560
- result = self._finder.find_keyword(keyword, raise_keyword_error=False)
561
-
562
592
  kw_range = range_from_token(keyword_token)
563
593
 
564
594
  if keyword:
@@ -710,19 +740,7 @@ class NamespaceAnalyzer(Visitor):
710
740
  )
711
741
 
712
742
  if result is not None:
713
- if result.longname in [
714
- "BuiltIn.Evaluate",
715
- "BuiltIn.Should Be True",
716
- "BuiltIn.Should Not Be True",
717
- "BuiltIn.Skip If",
718
- "BuiltIn.Continue For Loop If",
719
- "BuiltIn.Exit For Loop If",
720
- "BuiltIn.Return From Keyword If",
721
- "BuiltIn.Run Keyword And Return If",
722
- "BuiltIn.Pass Execution If",
723
- "BuiltIn.Run Keyword If",
724
- "BuiltIn.Run Keyword Unless",
725
- ]:
743
+ if result.longname in self.KEYWORDS_WITH_EXPRESSIONS:
726
744
  tokens = argument_tokens
727
745
  if tokens and (token := tokens[0]):
728
746
  self._analyze_token_expression_variables(token)
@@ -896,7 +914,29 @@ class NamespaceAnalyzer(Visitor):
896
914
 
897
915
  if keyword_token is not None and keyword_token.value and keyword_token.value.upper() not in ("", "NONE"):
898
916
  self._analyze_token_variables(keyword_token)
899
- self._analyze_statement_variables(node)
917
+ self._visit_block_settings_statement(node)
918
+
919
+ self._analyze_keyword_call(
920
+ node,
921
+ keyword_token,
922
+ [e for e in node.get_tokens(Token.ARGUMENT)],
923
+ allow_variables=True,
924
+ ignore_errors_if_contains_variables=True,
925
+ )
926
+
927
+ def visit_Teardown(self, node: Fixture) -> None: # noqa: N802
928
+ keyword_token = node.get_token(Token.NAME)
929
+
930
+ # TODO: calculate possible variables in NAME
931
+
932
+ if keyword_token is not None and keyword_token.value and keyword_token.value.upper() not in ("", "NONE"):
933
+
934
+ def _handler() -> None:
935
+ self._analyze_token_variables(keyword_token)
936
+ self._analyze_statement_variables(node)
937
+
938
+ if self._end_block_handlers is not None:
939
+ self._end_block_handlers.append(_handler)
900
940
 
901
941
  self._analyze_keyword_call(
902
942
  node,
@@ -984,9 +1024,15 @@ class NamespaceAnalyzer(Visitor):
984
1024
  self._current_testcase_or_keyword_name = node.name
985
1025
  old_variables = self._variables
986
1026
  self._variables = self._variables.copy()
1027
+ self._end_block_handlers = []
987
1028
  try:
988
1029
  self.generic_visit(node)
1030
+
1031
+ for handler in self._end_block_handlers:
1032
+ handler()
1033
+
989
1034
  finally:
1035
+ self._end_block_handlers = None
990
1036
  self._variables = old_variables
991
1037
  self._current_testcase_or_keyword_name = None
992
1038
  self._template = None
@@ -1029,13 +1075,21 @@ class NamespaceAnalyzer(Visitor):
1029
1075
  self._current_testcase_or_keyword_name = node.name
1030
1076
  old_variables = self._variables
1031
1077
  self._variables = self._variables.copy()
1078
+ self._end_block_handlers = []
1032
1079
  try:
1033
1080
  arguments = next((v for v in node.body if isinstance(v, Arguments)), None)
1034
1081
  if arguments is not None:
1035
1082
  self._visit_Arguments(arguments)
1083
+ self._block_variables = self._variables.copy()
1036
1084
 
1037
1085
  self.generic_visit(node)
1086
+
1087
+ for handler in self._end_block_handlers:
1088
+ handler()
1089
+
1038
1090
  finally:
1091
+ self._end_block_handlers = None
1092
+ self._block_variables = None
1039
1093
  self._variables = old_variables
1040
1094
  self._current_testcase_or_keyword_name = None
1041
1095
  self._current_keyword_doc = None
@@ -1132,7 +1186,7 @@ class NamespaceAnalyzer(Visitor):
1132
1186
  )
1133
1187
  )
1134
1188
 
1135
- except VariableError:
1189
+ except (VariableError, InvalidVariableError):
1136
1190
  pass
1137
1191
 
1138
1192
  def _analyze_assign_statement(self, node: Statement) -> None:
@@ -1172,7 +1226,7 @@ class NamespaceAnalyzer(Visitor):
1172
1226
  )
1173
1227
  )
1174
1228
 
1175
- except VariableError:
1229
+ except (VariableError, InvalidVariableError):
1176
1230
  pass
1177
1231
 
1178
1232
  def visit_InlineIfHeader(self, node: Statement) -> None: # noqa: N802
@@ -1186,7 +1240,7 @@ class NamespaceAnalyzer(Visitor):
1186
1240
  variables = node.get_tokens(Token.VARIABLE)
1187
1241
  for variable in variables:
1188
1242
  variable_token = self._get_variable_token(variable)
1189
- if variable_token is not None:
1243
+ if variable_token is not None and is_variable(variable_token.value):
1190
1244
  existing_var = self._find_variable(variable_token.value)
1191
1245
 
1192
1246
  if existing_var is None or existing_var.type not in [
@@ -1248,7 +1302,7 @@ class NamespaceAnalyzer(Visitor):
1248
1302
  source=self._namespace.source,
1249
1303
  )
1250
1304
 
1251
- except VariableError:
1305
+ except (VariableError, InvalidVariableError):
1252
1306
  pass
1253
1307
 
1254
1308
  def _format_template(self, template: str, arguments: Tuple[str, ...]) -> Tuple[str, Tuple[str, ...]]:
@@ -1351,7 +1405,7 @@ class NamespaceAnalyzer(Visitor):
1351
1405
  self._visit_settings_statement(node, DiagnosticSeverity.HINT)
1352
1406
 
1353
1407
  def visit_Timeout(self, node: Statement) -> None: # noqa: N802
1354
- self._analyze_statement_variables(node, DiagnosticSeverity.HINT)
1408
+ self._visit_block_settings_statement(node)
1355
1409
 
1356
1410
  def visit_SingleValue(self, node: Statement) -> None: # noqa: N802
1357
1411
  self._visit_settings_statement(node, DiagnosticSeverity.HINT)
@@ -1399,19 +1453,35 @@ class NamespaceAnalyzer(Visitor):
1399
1453
  code=Error.DEPRECATED_HEADER,
1400
1454
  )
1401
1455
 
1402
- def visit_ReturnSetting(self, node: Statement) -> None: # noqa: N802
1403
- self._analyze_statement_variables(node)
1456
+ if get_robot_version() >= (7, 0):
1404
1457
 
1405
- if get_robot_version() >= (7, 0):
1406
- token = node.get_token(Token.RETURN_SETTING)
1407
- if token is not None and token.error:
1408
- self._append_diagnostics(
1409
- range=range_from_node_or_token(node, token),
1410
- message=token.error,
1411
- severity=DiagnosticSeverity.WARNING,
1412
- tags=[DiagnosticTag.DEPRECATED],
1413
- code=Error.DEPRECATED_RETURN_SETTING,
1414
- )
1458
+ def visit_ReturnSetting(self, node: Statement) -> None: # noqa: N802
1459
+
1460
+ def _handler() -> None:
1461
+ self._analyze_statement_variables(node)
1462
+
1463
+ if self._end_block_handlers is not None:
1464
+ self._end_block_handlers.append(_handler)
1465
+
1466
+ if get_robot_version() >= (7, 0):
1467
+ token = node.get_token(Token.RETURN_SETTING)
1468
+ if token is not None and token.error:
1469
+ self._append_diagnostics(
1470
+ range=range_from_node_or_token(node, token),
1471
+ message=token.error,
1472
+ severity=DiagnosticSeverity.WARNING,
1473
+ tags=[DiagnosticTag.DEPRECATED],
1474
+ code=Error.DEPRECATED_RETURN_SETTING,
1475
+ )
1476
+
1477
+ else:
1478
+
1479
+ def visit_Return(self, node: Statement) -> None: # noqa: N802
1480
+ def _handler() -> None:
1481
+ self._analyze_statement_variables(node)
1482
+
1483
+ if self._end_block_handlers is not None:
1484
+ self._end_block_handlers.append(_handler)
1415
1485
 
1416
1486
  def _check_import_name(self, value: Optional[str], node: ast.AST, type: str) -> None:
1417
1487
  if not value:
@@ -1540,11 +1610,18 @@ class NamespaceAnalyzer(Visitor):
1540
1610
  default_value=default_value or None,
1541
1611
  )
1542
1612
 
1543
- vars = self._suite_variables if self._in_setting else self._variables
1613
+ vars = (
1614
+ self._block_variables
1615
+ if self._block_variables and self._in_block_setting
1616
+ else self._suite_variables if self._in_setting else self._variables
1617
+ )
1544
1618
 
1545
- matcher = VariableMatcher(name)
1619
+ try:
1620
+ matcher = VariableMatcher(name)
1546
1621
 
1547
- return vars.get(matcher, None)
1622
+ return vars.get(matcher, None)
1623
+ except (VariableError, InvalidVariableError):
1624
+ return None
1548
1625
 
1549
1626
  def _is_number(self, name: str) -> bool:
1550
1627
  if name.startswith("$"):
@@ -38,13 +38,6 @@ def cached_isinstance(obj: Any, *expected_types: Type[_T]) -> TypeGuard[Union[_T
38
38
  return False
39
39
 
40
40
 
41
- # def cached_isinstance(obj: Any, *expected_types: type) -> bool:
42
- # try:
43
- # return isinstance(obj, expected_types)
44
- # except TypeError:
45
- # return False
46
-
47
-
48
41
  def iter_nodes(node: ast.AST, descendants: bool = True) -> Iterator[ast.AST]:
49
42
  for _field, value in ast.iter_fields(node):
50
43
  if cached_isinstance(value, list):
@@ -3,13 +3,13 @@ from functools import lru_cache
3
3
  _transform_table = str.maketrans("", "", "_ ")
4
4
 
5
5
 
6
- @lru_cache(maxsize=5000)
6
+ @lru_cache(maxsize=None)
7
7
  def normalize(text: str) -> str:
8
8
  # return text.lower().replace("_", "").replace(" ", "")
9
9
  return text.casefold().translate(_transform_table)
10
10
 
11
11
 
12
- @lru_cache(maxsize=5000)
12
+ @lru_cache(maxsize=None)
13
13
  def normalize_namespace(text: str) -> str:
14
14
  return text.lower().replace(" ", "")
15
15
 
@@ -1,22 +1,12 @@
1
- from __future__ import annotations
2
-
3
1
  import sys
4
2
  from os import PathLike
5
3
  from pathlib import Path
6
4
  from typing import Optional, Union
7
5
 
8
6
 
9
- def find_file(
10
- path: Union[Path, PathLike[str], str],
11
- basedir: Union[Path, PathLike[str], str] = ".",
12
- file_type: Optional[str] = None,
13
- ) -> str:
14
- return find_file_ex(path, basedir, file_type)
15
-
16
-
17
7
  def find_file_ex(
18
- path: Union[Path, PathLike[str], str],
19
- basedir: Union[Path, PathLike[str], str] = ".",
8
+ path: Union[Path, "PathLike[str]", str],
9
+ basedir: Union[Path, "PathLike[str]", str] = ".",
20
10
  file_type: Optional[str] = None,
21
11
  ) -> str:
22
12
  from robot.errors import DataError
@@ -25,6 +15,7 @@ def find_file_ex(
25
15
  ret = _find_absolute_path(path) if path.is_absolute() else _find_relative_path(path, basedir)
26
16
  if ret:
27
17
  return str(ret)
18
+
28
19
  default = file_type or "File"
29
20
 
30
21
  file_type = (
@@ -40,15 +31,23 @@ def find_file_ex(
40
31
  raise DataError("%s '%s' does not exist." % (file_type, path))
41
32
 
42
33
 
43
- def _find_absolute_path(path: Union[Path, PathLike[str], str]) -> Optional[str]:
34
+ def find_file(
35
+ path: Union[Path, "PathLike[str]", str],
36
+ basedir: Union[Path, "PathLike[str]", str] = ".",
37
+ file_type: Optional[str] = None,
38
+ ) -> str:
39
+ return find_file_ex(path, basedir, file_type)
40
+
41
+
42
+ def _find_absolute_path(path: Union[Path, "PathLike[str]", str]) -> Optional[str]:
44
43
  if _is_valid_file(path):
45
44
  return str(path)
46
45
  return None
47
46
 
48
47
 
49
48
  def _find_relative_path(
50
- path: Union[Path, PathLike[str], str],
51
- basedir: Union[Path, PathLike[str], str],
49
+ path: Union[Path, "PathLike[str]", str],
50
+ basedir: Union[Path, "PathLike[str]", str],
52
51
  ) -> Optional[str]:
53
52
  for base in [basedir, *sys.path]:
54
53
  if not base:
@@ -65,6 +64,6 @@ def _find_relative_path(
65
64
  return None
66
65
 
67
66
 
68
- def _is_valid_file(path: Union[Path, PathLike[str], str]) -> bool:
67
+ def _is_valid_file(path: Union[Path, "PathLike[str]", str]) -> bool:
69
68
  path = Path(path)
70
69
  return path.is_file() or (path.is_dir() and Path(path, "__init__.py").is_fifo())
@@ -1,22 +1,4 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any, Dict, Iterator, List, Optional, Protocol, Set, runtime_checkable
4
-
5
-
6
- @runtime_checkable
7
- class HasError(Protocol):
8
- error: Optional[str]
9
-
10
-
11
- @runtime_checkable
12
- class HasErrors(Protocol):
13
- errors: Optional[List[str]]
14
-
15
-
16
- @runtime_checkable
17
- class HeaderAndBodyBlock(Protocol):
18
- header: Any
19
- body: List[Any]
1
+ from typing import Any, Dict, Iterator, List, Protocol, Set, runtime_checkable
20
2
 
21
3
 
22
4
  @runtime_checkable
@@ -1,3 +1,13 @@
1
+ import functools
2
+ from typing import Optional, Tuple, cast
3
+
4
+ from robot.utils.escaping import split_from_equals as robot_split_from_equals
5
+ from robot.variables.search import VariableMatch as RobotVariableMatch
6
+ from robot.variables.search import contains_variable as robot_contains_variable
7
+ from robot.variables.search import is_scalar_assign as robot_is_scalar_assign
8
+ from robot.variables.search import is_variable as robot_is_variable
9
+ from robot.variables.search import search_variable as robot_search_variable
10
+
1
11
  BUILTIN_VARIABLES = [
2
12
  "${CURDIR}",
3
13
  "${EMPTY}",
@@ -35,3 +45,28 @@ BUILTIN_VARIABLES = [
35
45
  "${DEBUG_FILE}",
36
46
  "${OUTPUT_DIR}",
37
47
  ]
48
+
49
+
50
+ @functools.lru_cache(maxsize=512)
51
+ def contains_variable(string: str, identifiers: str = "$@&") -> bool:
52
+ return cast(bool, robot_contains_variable(string, identifiers))
53
+
54
+
55
+ @functools.lru_cache(maxsize=512)
56
+ def is_scalar_assign(string: str, allow_assign_mark: bool = False) -> bool:
57
+ return cast(bool, robot_is_scalar_assign(string, allow_assign_mark))
58
+
59
+
60
+ @functools.lru_cache(maxsize=512)
61
+ def is_variable(string: str, identifiers: str = "$@&") -> bool:
62
+ return cast(bool, robot_is_variable(string, identifiers))
63
+
64
+
65
+ @functools.lru_cache(maxsize=512)
66
+ def search_variable(string: str, identifiers: str = "$@&%*", ignore_errors: bool = False) -> RobotVariableMatch:
67
+ return robot_search_variable(string, identifiers, ignore_errors)
68
+
69
+
70
+ @functools.lru_cache(maxsize=512)
71
+ def split_from_equals(string: str) -> Tuple[str, Optional[str]]:
72
+ return cast(Tuple[str, Optional[str]], robot_split_from_equals(string))
@@ -2,7 +2,6 @@ import ast
2
2
  from abc import ABC
3
3
  from typing import (
4
4
  Any,
5
- AsyncIterator,
6
5
  Callable,
7
6
  Dict,
8
7
  Iterator,
@@ -37,32 +36,6 @@ def iter_field_values(node: ast.AST) -> Iterator[Any]:
37
36
  pass
38
37
 
39
38
 
40
- def iter_child_nodes(node: ast.AST) -> Iterator[ast.AST]:
41
- for _name, field in iter_fields(node):
42
- if isinstance(field, ast.AST):
43
- yield field
44
- elif isinstance(field, list):
45
- for item in field:
46
- if isinstance(item, ast.AST):
47
- yield item
48
-
49
-
50
- async def iter_nodes(node: ast.AST) -> AsyncIterator[ast.AST]:
51
- for _name, value in iter_fields(node):
52
- if isinstance(value, list):
53
- for item in value:
54
- if isinstance(item, ast.AST):
55
- yield item
56
- async for n in iter_nodes(item):
57
- yield n
58
-
59
- elif isinstance(value, ast.AST):
60
- yield value
61
-
62
- async for n in iter_nodes(value):
63
- yield n
64
-
65
-
66
39
  class VisitorFinder(ABC):
67
40
  __cls_finder_cache__: Dict[Type[Any], Optional[Callable[..., Any]]]
68
41