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.
- robotcode/robot/__version__.py +1 -1
- robotcode/robot/diagnostics/data_cache.py +83 -0
- robotcode/robot/diagnostics/entities.py +3 -3
- robotcode/robot/diagnostics/errors.py +1 -1
- robotcode/robot/diagnostics/imports_manager.py +100 -99
- robotcode/robot/diagnostics/keyword_finder.py +69 -43
- robotcode/robot/diagnostics/library_doc.py +197 -151
- robotcode/robot/diagnostics/model_helper.py +10 -6
- robotcode/robot/diagnostics/namespace.py +17 -28
- robotcode/robot/diagnostics/namespace_analyzer.py +120 -43
- robotcode/robot/utils/ast.py +0 -7
- robotcode/robot/utils/match.py +2 -2
- robotcode/robot/utils/robot_path.py +15 -16
- robotcode/robot/utils/stubs.py +1 -19
- robotcode/robot/utils/variables.py +35 -0
- robotcode/robot/utils/visitor.py +0 -27
- {robotcode_robot-0.94.0.dist-info → robotcode_robot-0.95.1.dist-info}/METADATA +2 -2
- robotcode_robot-0.95.1.dist-info/RECORD +32 -0
- robotcode_robot-0.94.0.dist-info/RECORD +0 -31
- {robotcode_robot-0.94.0.dist-info → robotcode_robot-0.95.1.dist-info}/WHEEL +0 -0
- {robotcode_robot-0.94.0.dist-info → robotcode_robot-0.95.1.dist-info}/licenses/LICENSE.txt +0 -0
@@ -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
|
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
|
-
|
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.
|
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,
|
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.
|
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
|
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
|
-
|
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
|
-
|
1807
|
+
i = 0
|
1813
1808
|
|
1814
|
-
|
1809
|
+
self.ensure_initialized()
|
1815
1810
|
|
1816
|
-
|
1817
|
-
i += 1
|
1818
|
-
result[doc.matcher] = doc
|
1811
|
+
result: Dict[KeywordMatcher, KeywordDoc] = {}
|
1819
1812
|
|
1820
|
-
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
1824
|
-
|
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
|
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.
|
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
|
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.
|
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.
|
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
|
-
|
1403
|
-
self._analyze_statement_variables(node)
|
1456
|
+
if get_robot_version() >= (7, 0):
|
1404
1457
|
|
1405
|
-
|
1406
|
-
|
1407
|
-
|
1408
|
-
self.
|
1409
|
-
|
1410
|
-
|
1411
|
-
|
1412
|
-
|
1413
|
-
|
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 =
|
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
|
-
|
1619
|
+
try:
|
1620
|
+
matcher = VariableMatcher(name)
|
1546
1621
|
|
1547
|
-
|
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("$"):
|
robotcode/robot/utils/ast.py
CHANGED
@@ -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):
|
robotcode/robot/utils/match.py
CHANGED
@@ -3,13 +3,13 @@ from functools import lru_cache
|
|
3
3
|
_transform_table = str.maketrans("", "", "_ ")
|
4
4
|
|
5
5
|
|
6
|
-
@lru_cache(maxsize=
|
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=
|
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
|
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())
|
robotcode/robot/utils/stubs.py
CHANGED
@@ -1,22 +1,4 @@
|
|
1
|
-
from
|
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))
|
robotcode/robot/utils/visitor.py
CHANGED
@@ -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
|
|