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

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