robotcode-robot 0.69.0__tar.gz → 0.71.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/PKG-INFO +2 -2
  2. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/pyproject.toml +1 -1
  3. robotcode_robot-0.71.0/src/robotcode/robot/__version__.py +1 -0
  4. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/diagnostics/document_cache_helper.py +7 -6
  5. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/diagnostics/entities.py +22 -2
  6. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/diagnostics/imports_manager.py +13 -19
  7. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/diagnostics/library_doc.py +64 -52
  8. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/diagnostics/namespace.py +175 -118
  9. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/diagnostics/namespace_analyzer.py +53 -5
  10. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/diagnostics/workspace_config.py +6 -0
  11. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/utils/markdownformatter.py +4 -8
  12. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/utils/stubs.py +2 -4
  13. robotcode_robot-0.69.0/src/robotcode/robot/__version__.py +0 -1
  14. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/.gitignore +0 -0
  15. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/LICENSE.txt +0 -0
  16. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/README.md +0 -0
  17. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/__init__.py +0 -0
  18. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/config/__init__.py +0 -0
  19. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/config/loader.py +0 -0
  20. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/config/model.py +0 -0
  21. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/config/utils.py +0 -0
  22. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/diagnostics/__init__.py +0 -0
  23. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/diagnostics/errors.py +0 -0
  24. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/diagnostics/model_helper.py +0 -0
  25. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/py.typed +0 -0
  26. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/utils/__init__.py +0 -0
  27. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/utils/ast.py +0 -0
  28. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/utils/match.py +0 -0
  29. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/utils/robot_path.py +0 -0
  30. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/utils/variables.py +0 -0
  31. {robotcode_robot-0.69.0 → robotcode_robot-0.71.0}/src/robotcode/robot/utils/visitor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: robotcode-robot
3
- Version: 0.69.0
3
+ Version: 0.71.0
4
4
  Summary: Support classes for RobotCode for handling Robot Framework projects.
5
5
  Project-URL: Homepage, https://robotcode.io
6
6
  Project-URL: Donate, https://github.com/sponsors/d-biehl
@@ -26,7 +26,7 @@ Classifier: Topic :: Utilities
26
26
  Classifier: Typing :: Typed
27
27
  Requires-Python: >=3.8
28
28
  Requires-Dist: platformdirs<4.2.0,>=3.2.0
29
- Requires-Dist: robotcode-core==0.69.0
29
+ Requires-Dist: robotcode-core==0.71.0
30
30
  Requires-Dist: robotframework>=4.1.0
31
31
  Requires-Dist: tomli>=1.1.0; python_version < '3.11'
32
32
  Description-Content-Type: text/markdown
@@ -29,7 +29,7 @@ dependencies = [
29
29
  "robotframework>=4.1.0",
30
30
  "tomli>=1.1.0; python_version < '3.11'",
31
31
  "platformdirs>=3.2.0,<4.2.0",
32
- "robotcode-core==0.69.0",
32
+ "robotcode-core==0.71.0",
33
33
  ]
34
34
  dynamic = ["version"]
35
35
 
@@ -0,0 +1 @@
1
+ __version__ = "0.71.0"
@@ -33,7 +33,7 @@ from ..utils import get_robot_version
33
33
  from ..utils.stubs import Languages
34
34
  from .imports_manager import ImportsManager
35
35
  from .namespace import DocumentType, Namespace
36
- from .workspace_config import CacheConfig, RobotConfig
36
+ from .workspace_config import AnalysisRobotConfig, CacheConfig, RobotConfig
37
37
 
38
38
 
39
39
  class UnknownFileTypeError(Exception):
@@ -59,9 +59,9 @@ class DocumentsCacheHelper:
59
59
  self._imports_managers_lock = threading.RLock()
60
60
  self._imports_managers: weakref.WeakKeyDictionary[WorkspaceFolder, ImportsManager] = weakref.WeakKeyDictionary()
61
61
  self._default_imports_manager: Optional[ImportsManager] = None
62
- self._workspace_languages: weakref.WeakKeyDictionary[
63
- WorkspaceFolder, Optional[Languages]
64
- ] = weakref.WeakKeyDictionary()
62
+ self._workspace_languages: weakref.WeakKeyDictionary[WorkspaceFolder, Optional[Languages]] = (
63
+ weakref.WeakKeyDictionary()
64
+ )
65
65
 
66
66
  def get_languages_for_document(self, document_or_uri: Union[TextDocument, Uri, str]) -> Optional[Languages]:
67
67
  if get_robot_version() < (6, 0):
@@ -416,8 +416,7 @@ class DocumentsCacheHelper:
416
416
  return self.__get_namespace_for_document_type(document, DocumentType.GENERAL)
417
417
 
418
418
  @event
419
- def namespace_invalidated(sender, namespace: Namespace) -> None:
420
- ...
419
+ def namespace_invalidated(sender, namespace: Namespace) -> None: ...
421
420
 
422
421
  def __invalidate_namespace(self, sender: Namespace) -> None:
423
422
  document = sender.document
@@ -475,6 +474,7 @@ class DocumentsCacheHelper:
475
474
  *robot_config.variable_files,
476
475
  ]
477
476
 
477
+ analysis_config = self.workspace.get_configuration(AnalysisRobotConfig, root_uri)
478
478
  return ImportsManager(
479
479
  self.documents_manager,
480
480
  self.file_watcher_manager,
@@ -485,6 +485,7 @@ class DocumentsCacheHelper:
485
485
  environment,
486
486
  cache_config.ignored_libraries,
487
487
  cache_config.ignored_variables,
488
+ analysis_config.global_library_search_order,
488
489
  cache_base_path,
489
490
  )
490
491
 
@@ -173,7 +173,9 @@ class VariableMatcher:
173
173
  class VariableDefinitionType(Enum):
174
174
  VARIABLE = "suite variable"
175
175
  LOCAL_VARIABLE = "local variable"
176
+ TEST_VARIABLE = "test variable"
176
177
  ARGUMENT = "argument"
178
+ GLOBAL_VARIABLE = "global variable"
177
179
  COMMAND_LINE_VARIABLE = "global variable [command line]"
178
180
  BUILTIN_VARIABLE = "builtin variable"
179
181
  IMPORTED_VARIABLE = "suite variable [imported]"
@@ -218,6 +220,15 @@ class VariableDefinition(SourceEntity):
218
220
  )
219
221
 
220
222
 
223
+ @dataclass
224
+ class TestVariableDefinition(VariableDefinition):
225
+ type: VariableDefinitionType = VariableDefinitionType.TEST_VARIABLE
226
+
227
+ @single_call
228
+ def __hash__(self) -> int:
229
+ return hash((type(self), self.name, self.type, self.range, self.source))
230
+
231
+
221
232
  @dataclass
222
233
  class LocalVariableDefinition(VariableDefinition):
223
234
  type: VariableDefinitionType = VariableDefinitionType.LOCAL_VARIABLE
@@ -227,6 +238,15 @@ class LocalVariableDefinition(VariableDefinition):
227
238
  return hash((type(self), self.name, self.type, self.range, self.source))
228
239
 
229
240
 
241
+ @dataclass
242
+ class GlobalVariableDefinition(VariableDefinition):
243
+ type: VariableDefinitionType = VariableDefinitionType.GLOBAL_VARIABLE
244
+
245
+ @single_call
246
+ def __hash__(self) -> int:
247
+ return hash((type(self), self.name, self.type, self.range, self.source))
248
+
249
+
230
250
  @dataclass
231
251
  class BuiltInVariableDefinition(VariableDefinition):
232
252
  type: VariableDefinitionType = VariableDefinitionType.BUILTIN_VARIABLE
@@ -238,13 +258,13 @@ class BuiltInVariableDefinition(VariableDefinition):
238
258
 
239
259
 
240
260
  @dataclass
241
- class CommandLineVariableDefinition(VariableDefinition):
261
+ class CommandLineVariableDefinition(GlobalVariableDefinition):
242
262
  type: VariableDefinitionType = VariableDefinitionType.COMMAND_LINE_VARIABLE
243
263
  resolvable: bool = True
244
264
 
245
265
  @single_call
246
266
  def __hash__(self) -> int:
247
- return hash((type(self), self.name, self.type))
267
+ return hash((type(self), self.name, self.type, self.range, self.source))
248
268
 
249
269
 
250
270
  @dataclass
@@ -119,8 +119,7 @@ class _ImportEntry(ABC):
119
119
  self.file_watchers = []
120
120
 
121
121
  @abstractmethod
122
- def check_file_changed(self, changes: List[FileEvent]) -> Optional[FileChangeType]:
123
- ...
122
+ def check_file_changed(self, changes: List[FileEvent]) -> Optional[FileChangeType]: ...
124
123
 
125
124
  @final
126
125
  def invalidate(self) -> None:
@@ -128,16 +127,13 @@ class _ImportEntry(ABC):
128
127
  self._invalidate()
129
128
 
130
129
  @abstractmethod
131
- def _invalidate(self) -> None:
132
- ...
130
+ def _invalidate(self) -> None: ...
133
131
 
134
132
  @abstractmethod
135
- def _update(self) -> None:
136
- ...
133
+ def _update(self) -> None: ...
137
134
 
138
135
  @abstractmethod
139
- def is_valid(self) -> bool:
140
- ...
136
+ def is_valid(self) -> bool: ...
141
137
 
142
138
 
143
139
  class _LibrariesEntry(_ImportEntry):
@@ -211,9 +207,7 @@ class _LibrariesEntry(_ImportEntry):
211
207
  source_or_origin = (
212
208
  self._lib_doc.source
213
209
  if self._lib_doc.source is not None
214
- else self._lib_doc.module_spec.origin
215
- if self._lib_doc.module_spec is not None
216
- else None
210
+ else self._lib_doc.module_spec.origin if self._lib_doc.module_spec is not None else None
217
211
  )
218
212
 
219
213
  # we are a module, so add the module path into file watchers
@@ -495,6 +489,7 @@ class ImportsManager:
495
489
  environment: Optional[Dict[str, str]],
496
490
  ignored_libraries: List[str],
497
491
  ignored_variables: List[str],
492
+ global_library_search_order: List[str],
498
493
  cache_base_path: Optional[Path],
499
494
  ) -> None:
500
495
  super().__init__()
@@ -536,6 +531,9 @@ class ImportsManager:
536
531
 
537
532
  self.ignored_libraries_patters = [Pattern(s) for s in ignored_libraries]
538
533
  self.ignored_variables_patters = [Pattern(s) for s in ignored_variables]
534
+
535
+ self.global_library_search_order = global_library_search_order
536
+
539
537
  self._libaries_lock = threading.RLock()
540
538
  self._libaries: OrderedDict[_LibrariesEntryKey, _LibrariesEntry] = OrderedDict()
541
539
  self._resources_lock = threading.RLock()
@@ -661,20 +659,16 @@ class ImportsManager:
661
659
  return self._resolvable_command_line_variables
662
660
 
663
661
  @event
664
- def libraries_changed(sender, libraries: List[LibraryDoc]) -> None:
665
- ...
662
+ def libraries_changed(sender, libraries: List[LibraryDoc]) -> None: ...
666
663
 
667
664
  @event
668
- def resources_changed(sender, resources: List[LibraryDoc]) -> None:
669
- ...
665
+ def resources_changed(sender, resources: List[LibraryDoc]) -> None: ...
670
666
 
671
667
  @event
672
- def variables_changed(sender, variables: List[LibraryDoc]) -> None:
673
- ...
668
+ def variables_changed(sender, variables: List[LibraryDoc]) -> None: ...
674
669
 
675
670
  @event
676
- def imports_changed(sender, uri: DocumentUri) -> None:
677
- ...
671
+ def imports_changed(sender, uri: DocumentUri) -> None: ...
678
672
 
679
673
  def possible_imports_modified(self, sender: Any, uri: DocumentUri) -> None:
680
674
  self.imports_changed(self, uri)
@@ -397,11 +397,15 @@ class ArgumentInfo:
397
397
  name=robot_arg.name,
398
398
  default_value=robot_arg_repr(robot_arg),
399
399
  str_repr=str(arg),
400
- types=robot_arg.types_reprs
401
- if get_robot_version() < (7, 0)
402
- else ([str(robot_arg.type)] if not robot_arg.type.is_union else [str(t) for t in robot_arg.type.nested])
403
- if robot_arg.type
404
- else None,
400
+ types=(
401
+ robot_arg.types_reprs
402
+ if get_robot_version() < (7, 0)
403
+ else (
404
+ ([str(robot_arg.type)] if not robot_arg.type.is_union else [str(t) for t in robot_arg.type.nested])
405
+ if robot_arg.type
406
+ else None
407
+ )
408
+ ),
405
409
  kind=KeywordArgumentKind[robot_arg.kind],
406
410
  required=robot_arg.required,
407
411
  )
@@ -1273,9 +1277,9 @@ def get_module_spec(module_name: str) -> Optional[ModuleSpec]:
1273
1277
  return ModuleSpec( # type: ignore
1274
1278
  name=result.name,
1275
1279
  origin=result.origin,
1276
- submodule_search_locations=list(result.submodule_search_locations)
1277
- if result.submodule_search_locations
1278
- else None,
1280
+ submodule_search_locations=(
1281
+ list(result.submodule_search_locations) if result.submodule_search_locations else None
1282
+ ),
1279
1283
  member_name=member_name,
1280
1284
  )
1281
1285
  return None
@@ -1396,9 +1400,11 @@ def get_message_and_traceback_from_exception_text(
1396
1400
 
1397
1401
  return MessageAndTraceback(
1398
1402
  message=splitted[0].strip(),
1399
- traceback=[SourceAndLineInfo(t.group(1), int(t.group(2))) for t in __RE_TRACEBACK.finditer(splitted[1])]
1400
- if len(splitted) > 1
1401
- else [],
1403
+ traceback=(
1404
+ [SourceAndLineInfo(t.group(1), int(t.group(2))) for t in __RE_TRACEBACK.finditer(splitted[1])]
1405
+ if len(splitted) > 1
1406
+ else []
1407
+ ),
1402
1408
  )
1403
1409
 
1404
1410
 
@@ -1723,20 +1729,20 @@ def get_library_doc(
1723
1729
  except BaseException as e:
1724
1730
  return LibraryDoc(
1725
1731
  name=name,
1726
- source=source or module_spec.origin
1727
- if module_spec is not None and module_spec.origin
1728
- else import_name
1729
- if is_library_by_path(import_name)
1730
- else None,
1732
+ source=(
1733
+ source or module_spec.origin
1734
+ if module_spec is not None and module_spec.origin
1735
+ else import_name if is_library_by_path(import_name) else None
1736
+ ),
1731
1737
  module_spec=module_spec,
1732
1738
  errors=[
1733
1739
  error_from_exception(
1734
1740
  e,
1735
- source or module_spec.origin
1736
- if module_spec is not None and module_spec.origin
1737
- else import_name
1738
- if is_library_by_path(import_name)
1739
- else None,
1741
+ (
1742
+ source or module_spec.origin
1743
+ if module_spec is not None and module_spec.origin
1744
+ else import_name if is_library_by_path(import_name) else None
1745
+ ),
1740
1746
  1 if source is not None or module_spec is not None and module_spec.origin is not None else None,
1741
1747
  )
1742
1748
  ],
@@ -1798,11 +1804,13 @@ def get_library_doc(
1798
1804
  libdoc = LibraryDoc(
1799
1805
  name=library_name,
1800
1806
  source=real_source,
1801
- module_spec=module_spec
1802
- if module_spec is not None
1803
- and module_spec.origin != real_source
1804
- and module_spec.submodule_search_locations is None
1805
- else None,
1807
+ module_spec=(
1808
+ module_spec
1809
+ if module_spec is not None
1810
+ and module_spec.origin != real_source
1811
+ and module_spec.submodule_search_locations is None
1812
+ else None
1813
+ ),
1806
1814
  python_path=sys.path,
1807
1815
  line_no=lib.lineno if lib is not None else -1,
1808
1816
  doc=str(lib.doc) if lib is not None else "",
@@ -1921,18 +1929,22 @@ def get_library_doc(
1921
1929
  is_registered_run_keyword=RUN_KW_REGISTER.is_run_keyword(libdoc.name, kw[0].name),
1922
1930
  args_to_process=get_args_to_process(libdoc.name, kw[0].name),
1923
1931
  deprecated=kw[0].deprecated,
1924
- arguments_spec=ArgumentSpec.from_robot_argument_spec(
1925
- kw[1].arguments if get_robot_version() < (7, 0) else kw[1].args
1926
- )
1927
- if not kw[1].is_error_handler
1928
- else None,
1932
+ arguments_spec=(
1933
+ ArgumentSpec.from_robot_argument_spec(
1934
+ kw[1].arguments if get_robot_version() < (7, 0) else kw[1].args
1935
+ )
1936
+ if not kw[1].is_error_handler
1937
+ else None
1938
+ ),
1929
1939
  return_type=(
1930
- str(kw[1].args.return_type)
1931
- if kw[1].args.return_type is not None and kw[1].args.return_type != type(None)
1940
+ (
1941
+ str(kw[1].args.return_type)
1942
+ if kw[1].args.return_type is not None and kw[1].args.return_type != type(None)
1943
+ else None
1944
+ )
1945
+ if get_robot_version() >= (7, 0)
1932
1946
  else None
1933
- )
1934
- if get_robot_version() >= (7, 0)
1935
- else None,
1947
+ ),
1936
1948
  )
1937
1949
  for kw in keyword_docs
1938
1950
  ],
@@ -2273,9 +2285,9 @@ def get_variables_doc(
2273
2285
  source=source or (module_spec.origin if module_spec is not None else None) or "",
2274
2286
  name=name if get_robot_version() < (7, 0) else f"${{{name}}}",
2275
2287
  name_token=None,
2276
- value=NativeValue(value)
2277
- if value is None or isinstance(value, (int, float, bool, str))
2278
- else None,
2288
+ value=(
2289
+ NativeValue(value) if value is None or isinstance(value, (int, float, bool, str)) else None
2290
+ ),
2279
2291
  has_value=value is None or isinstance(value, (int, float, bool, str)),
2280
2292
  value_is_native=value is None or isinstance(value, (int, float, bool, str)),
2281
2293
  )
@@ -2287,11 +2299,11 @@ def get_variables_doc(
2287
2299
  libdoc.errors = [
2288
2300
  error_from_exception(
2289
2301
  e,
2290
- source or module_spec.origin
2291
- if module_spec is not None and module_spec.origin
2292
- else import_name
2293
- if is_variables_by_path(import_name)
2294
- else None,
2302
+ (
2303
+ source or module_spec.origin
2304
+ if module_spec is not None and module_spec.origin
2305
+ else import_name if is_variables_by_path(import_name) else None
2306
+ ),
2295
2307
  1 if source is not None or module_spec is not None and module_spec.origin is not None else None,
2296
2308
  )
2297
2309
  ]
@@ -2307,11 +2319,11 @@ def get_variables_doc(
2307
2319
  errors=[
2308
2320
  error_from_exception(
2309
2321
  e,
2310
- source or module_spec.origin
2311
- if module_spec is not None and module_spec.origin
2312
- else import_name
2313
- if is_variables_by_path(import_name)
2314
- else None,
2322
+ (
2323
+ source or module_spec.origin
2324
+ if module_spec is not None and module_spec.origin
2325
+ else import_name if is_variables_by_path(import_name) else None
2326
+ ),
2315
2327
  1 if source is not None or module_spec is not None and module_spec.origin is not None else None,
2316
2328
  )
2317
2329
  ],
@@ -2788,9 +2800,9 @@ def get_model_doc(
2788
2800
  is_embedded=is_embedded_keyword(kw[0].name),
2789
2801
  errors=get_kw_errors(kw[1]),
2790
2802
  is_error_handler=isinstance(kw[1], UserErrorHandler),
2791
- error_handler_message=str(cast(UserErrorHandler, kw[1]).error)
2792
- if isinstance(kw[1], UserErrorHandler)
2793
- else None,
2803
+ error_handler_message=(
2804
+ str(cast(UserErrorHandler, kw[1]).error) if isinstance(kw[1], UserErrorHandler) else None
2805
+ ),
2794
2806
  arguments_spec=ArgumentSpec.from_robot_argument_spec(
2795
2807
  kw[1].arguments if get_robot_version() < (7, 0) else kw[1].args
2796
2808
  ),
@@ -20,6 +20,7 @@ from typing import (
20
20
  Set,
21
21
  Tuple,
22
22
  Union,
23
+ cast,
23
24
  )
24
25
 
25
26
  from robot.errors import VariableError
@@ -75,6 +76,7 @@ from .entities import (
75
76
  BuiltInVariableDefinition,
76
77
  CommandLineVariableDefinition,
77
78
  EnvironmentVariableDefinition,
79
+ GlobalVariableDefinition,
78
80
  Import,
79
81
  InvalidVariableError,
80
82
  LibraryEntry,
@@ -82,6 +84,7 @@ from .entities import (
82
84
  LocalVariableDefinition,
83
85
  ResourceEntry,
84
86
  ResourceImport,
87
+ TestVariableDefinition,
85
88
  VariableDefinition,
86
89
  VariableMatcher,
87
90
  VariablesEntry,
@@ -182,18 +185,21 @@ class BlockVariableVisitor(Visitor):
182
185
  def __init__(
183
186
  self,
184
187
  library_doc: LibraryDoc,
188
+ global_variables: List[VariableDefinition],
185
189
  source: str,
186
190
  position: Optional[Position] = None,
187
191
  in_args: bool = True,
188
192
  ) -> None:
189
193
  super().__init__()
190
194
  self.library_doc = library_doc
195
+ self.global_variables = global_variables
191
196
  self.source = source
192
197
  self.position = position
193
198
  self.in_args = in_args
194
199
 
195
200
  self._results: Dict[str, VariableDefinition] = {}
196
201
  self.current_kw_doc: Optional[KeywordDoc] = None
202
+ self._var_statements_vars: List[VariableDefinition] = []
197
203
 
198
204
  def get(self, model: ast.AST) -> List[VariableDefinition]:
199
205
  self._results = {}
@@ -384,15 +390,32 @@ class BlockVariableVisitor(Visitor):
384
390
  )
385
391
 
386
392
  def visit_Var(self, node: Statement) -> None: # noqa: N802
393
+ from robot.parsing.model.statements import Var
394
+
387
395
  variable = node.get_token(Token.VARIABLE)
388
396
  if variable is None:
389
397
  return
390
398
  try:
391
- if not is_variable(variable.value):
399
+ var_name = variable.value
400
+ if var_name.endswith("="):
401
+ var_name = var_name[:-1].rstrip()
402
+
403
+ if not is_variable(var_name):
392
404
  return
393
405
 
394
- self._results[variable.value] = LocalVariableDefinition(
395
- name=variable.value,
406
+ scope = cast(Var, node).scope
407
+
408
+ if scope in ("SUITE",):
409
+ var_type = VariableDefinition
410
+ elif scope in ("TEST", "TASK"):
411
+ var_type = TestVariableDefinition
412
+ elif scope in ("GLOBAL",):
413
+ var_type = GlobalVariableDefinition
414
+ else:
415
+ var_type = LocalVariableDefinition
416
+
417
+ var = var_type(
418
+ name=var_name,
396
419
  name_token=strip_variable_token(variable),
397
420
  line_no=variable.lineno,
398
421
  col_offset=variable.col_offset,
@@ -401,6 +424,16 @@ class BlockVariableVisitor(Visitor):
401
424
  source=self.source,
402
425
  )
403
426
 
427
+ self._var_statements_vars.append(var)
428
+
429
+ if var_name not in self._results or type(self._results[var_name]) != type(var):
430
+ if isinstance(var, LocalVariableDefinition) or not any(
431
+ l for l in self.global_variables if l.matcher == var.matcher
432
+ ):
433
+ self._results[var_name] = var
434
+ else:
435
+ self._results.pop(var_name, None)
436
+
404
437
  except VariableError:
405
438
  pass
406
439
 
@@ -433,16 +466,16 @@ class ImportVisitor(Visitor):
433
466
  alias_token=alias_token,
434
467
  line_no=node.lineno,
435
468
  col_offset=node.col_offset,
436
- end_line_no=last_data_token.lineno
437
- if last_data_token is not None
438
- else node.end_lineno
439
- if node.end_lineno is not None
440
- else -1,
441
- end_col_offset=last_data_token.end_col_offset
442
- if last_data_token is not None
443
- else node.end_col_offset
444
- if node.end_col_offset is not None
445
- else -1,
469
+ end_line_no=(
470
+ last_data_token.lineno
471
+ if last_data_token is not None
472
+ else node.end_lineno if node.end_lineno is not None else -1
473
+ ),
474
+ end_col_offset=(
475
+ last_data_token.end_col_offset
476
+ if last_data_token is not None
477
+ else node.end_col_offset if node.end_col_offset is not None else -1
478
+ ),
446
479
  source=self.source,
447
480
  )
448
481
  )
@@ -458,16 +491,16 @@ class ImportVisitor(Visitor):
458
491
  name_token=name if name is not None else None,
459
492
  line_no=node.lineno,
460
493
  col_offset=node.col_offset,
461
- end_line_no=last_data_token.lineno
462
- if last_data_token is not None
463
- else node.end_lineno
464
- if node.end_lineno is not None
465
- else -1,
466
- end_col_offset=last_data_token.end_col_offset
467
- if last_data_token is not None
468
- else node.end_col_offset
469
- if node.end_col_offset is not None
470
- else -1,
494
+ end_line_no=(
495
+ last_data_token.lineno
496
+ if last_data_token is not None
497
+ else node.end_lineno if node.end_lineno is not None else -1
498
+ ),
499
+ end_col_offset=(
500
+ last_data_token.end_col_offset
501
+ if last_data_token is not None
502
+ else node.end_col_offset if node.end_col_offset is not None else -1
503
+ ),
471
504
  source=self.source,
472
505
  )
473
506
  )
@@ -484,16 +517,16 @@ class ImportVisitor(Visitor):
484
517
  args=node.args,
485
518
  line_no=node.lineno,
486
519
  col_offset=node.col_offset,
487
- end_line_no=last_data_token.lineno
488
- if last_data_token is not None
489
- else node.end_lineno
490
- if node.end_lineno is not None
491
- else -1,
492
- end_col_offset=last_data_token.end_col_offset
493
- if last_data_token is not None
494
- else node.end_col_offset
495
- if node.end_col_offset is not None
496
- else -1,
520
+ end_line_no=(
521
+ last_data_token.lineno
522
+ if last_data_token is not None
523
+ else node.end_lineno if node.end_lineno is not None else -1
524
+ ),
525
+ end_col_offset=(
526
+ last_data_token.end_col_offset
527
+ if last_data_token is not None
528
+ else node.end_col_offset if node.end_col_offset is not None else -1
529
+ ),
497
530
  source=self.source,
498
531
  )
499
532
  )
@@ -563,7 +596,7 @@ class Namespace:
563
596
  self._keywords_lock = RLock(default_timeout=120, name="Namespace.keywords")
564
597
 
565
598
  # TODO: how to get the search order from model
566
- self.search_order: Tuple[str, ...] = ()
599
+ self._search_order: Optional[Tuple[str, ...]] = None
567
600
 
568
601
  self._finder: Optional[KeywordFinder] = None
569
602
 
@@ -577,25 +610,28 @@ class Namespace:
577
610
  self._ignored_lines: Optional[List[int]] = None
578
611
 
579
612
  @event
580
- def has_invalidated(sender) -> None:
581
- ...
613
+ def has_invalidated(sender) -> None: ...
582
614
 
583
615
  @event
584
- def has_initialized(sender) -> None:
585
- ...
616
+ def has_initialized(sender) -> None: ...
586
617
 
587
618
  @event
588
- def has_imports_changed(sender) -> None:
589
- ...
619
+ def has_imports_changed(sender) -> None: ...
590
620
 
591
621
  @event
592
- def has_analysed(sender) -> None:
593
- ...
622
+ def has_analysed(sender) -> None: ...
594
623
 
595
624
  @property
596
625
  def document(self) -> Optional[TextDocument]:
597
626
  return self._document() if self._document is not None else None
598
627
 
628
+ @property
629
+ def search_order(self) -> Tuple[str, ...]:
630
+ if self._search_order is None:
631
+ return tuple(self.imports_manager.global_library_search_order)
632
+
633
+ return self._search_order
634
+
599
635
  def imports_changed(self, sender: Any, uri: DocumentUri) -> None:
600
636
  # TODO: optimise this by checking our imports
601
637
  if self.document is not None:
@@ -926,15 +962,18 @@ class Namespace:
926
962
  for var in chain(
927
963
  *[
928
964
  (
929
- BlockVariableVisitor(
930
- self.get_library_doc(),
931
- self.source,
932
- position,
933
- isinstance(test_or_keyword_nodes[-1], Arguments) if nodes else False,
934
- ).get(test_or_keyword)
965
+ (
966
+ BlockVariableVisitor(
967
+ self.get_library_doc(),
968
+ self.get_global_variables(),
969
+ self.source,
970
+ position,
971
+ isinstance(test_or_keyword_nodes[-1], Arguments) if nodes else False,
972
+ ).get(test_or_keyword)
973
+ )
974
+ if test_or_keyword is not None
975
+ else []
935
976
  )
936
- if test_or_keyword is not None
937
- else []
938
977
  ],
939
978
  self.get_global_variables(),
940
979
  ):
@@ -1064,17 +1103,19 @@ class Namespace:
1064
1103
  message="Possible circular import.",
1065
1104
  severity=DiagnosticSeverity.INFORMATION,
1066
1105
  source=DIAGNOSTICS_SOURCE_NAME,
1067
- related_information=[
1068
- DiagnosticRelatedInformation(
1069
- location=Location(
1070
- str(Uri.from_path(value.source)),
1071
- value.range,
1072
- ),
1073
- message=f"'{Path(self.source).name}' is also imported here.",
1074
- )
1075
- ]
1076
- if value.source
1077
- else None,
1106
+ related_information=(
1107
+ [
1108
+ DiagnosticRelatedInformation(
1109
+ location=Location(
1110
+ str(Uri.from_path(value.source)),
1111
+ value.range,
1112
+ ),
1113
+ message=f"'{Path(self.source).name}' is also imported here.",
1114
+ )
1115
+ ]
1116
+ if value.source
1117
+ else None
1118
+ ),
1078
1119
  code=Error.POSSIBLE_CIRCULAR_IMPORT,
1079
1120
  )
1080
1121
  else:
@@ -1140,20 +1181,24 @@ class Namespace:
1140
1181
  uri=str(Uri.from_path(err.source)),
1141
1182
  range=Range(
1142
1183
  start=Position(
1143
- line=err.line_no - 1
1144
- if err.line_no is not None
1145
- else max(
1146
- result.library_doc.line_no,
1147
- 0,
1184
+ line=(
1185
+ err.line_no - 1
1186
+ if err.line_no is not None
1187
+ else max(
1188
+ result.library_doc.line_no,
1189
+ 0,
1190
+ )
1148
1191
  ),
1149
1192
  character=0,
1150
1193
  ),
1151
1194
  end=Position(
1152
- line=err.line_no - 1
1153
- if err.line_no is not None
1154
- else max(
1155
- result.library_doc.line_no,
1156
- 0,
1195
+ line=(
1196
+ err.line_no - 1
1197
+ if err.line_no is not None
1198
+ else max(
1199
+ result.library_doc.line_no,
1200
+ 0,
1201
+ )
1157
1202
  ),
1158
1203
  character=0,
1159
1204
  ),
@@ -1261,17 +1306,21 @@ class Namespace:
1261
1306
  message=f"Resource {entry} already imported.",
1262
1307
  severity=DiagnosticSeverity.INFORMATION,
1263
1308
  source=DIAGNOSTICS_SOURCE_NAME,
1264
- related_information=[
1265
- DiagnosticRelatedInformation(
1266
- location=Location(
1267
- uri=str(Uri.from_path(already_imported_resources.import_source)),
1268
- range=already_imported_resources.import_range,
1269
- ),
1270
- message="",
1271
- )
1272
- ]
1273
- if already_imported_resources.import_source
1274
- else None,
1309
+ related_information=(
1310
+ [
1311
+ DiagnosticRelatedInformation(
1312
+ location=Location(
1313
+ uri=str(
1314
+ Uri.from_path(already_imported_resources.import_source)
1315
+ ),
1316
+ range=already_imported_resources.import_range,
1317
+ ),
1318
+ message="",
1319
+ )
1320
+ ]
1321
+ if already_imported_resources.import_source
1322
+ else None
1323
+ ),
1275
1324
  code=Error.RESOURCE_ALREADY_IMPORTED,
1276
1325
  )
1277
1326
 
@@ -1293,17 +1342,19 @@ class Namespace:
1293
1342
  message=f'Variables "{entry}" already imported.',
1294
1343
  severity=DiagnosticSeverity.INFORMATION,
1295
1344
  source=DIAGNOSTICS_SOURCE_NAME,
1296
- related_information=[
1297
- DiagnosticRelatedInformation(
1298
- location=Location(
1299
- uri=str(Uri.from_path(already_imported_variables[0].import_source)),
1300
- range=already_imported_variables[0].import_range,
1301
- ),
1302
- message="",
1303
- )
1304
- ]
1305
- if already_imported_variables[0].import_source
1306
- else None,
1345
+ related_information=(
1346
+ [
1347
+ DiagnosticRelatedInformation(
1348
+ location=Location(
1349
+ uri=str(Uri.from_path(already_imported_variables[0].import_source)),
1350
+ range=already_imported_variables[0].import_range,
1351
+ ),
1352
+ message="",
1353
+ )
1354
+ ]
1355
+ if already_imported_variables[0].import_source
1356
+ else None
1357
+ ),
1307
1358
  code=Error.VARIABLES_ALREADY_IMPORTED,
1308
1359
  )
1309
1360
 
@@ -1318,17 +1369,19 @@ class Namespace:
1318
1369
  ' because it would override the "BuiltIn" library.',
1319
1370
  severity=DiagnosticSeverity.INFORMATION,
1320
1371
  source=DIAGNOSTICS_SOURCE_NAME,
1321
- related_information=[
1322
- DiagnosticRelatedInformation(
1323
- location=Location(
1324
- uri=str(Uri.from_path(entry.import_source)),
1325
- range=entry.import_range,
1326
- ),
1327
- message="",
1328
- )
1329
- ]
1330
- if entry.import_source
1331
- else None,
1372
+ related_information=(
1373
+ [
1374
+ DiagnosticRelatedInformation(
1375
+ location=Location(
1376
+ uri=str(Uri.from_path(entry.import_source)),
1377
+ range=entry.import_range,
1378
+ ),
1379
+ message="",
1380
+ )
1381
+ ]
1382
+ if entry.import_source
1383
+ else None
1384
+ ),
1332
1385
  code=Error.LIBRARY_OVERRIDES_BUILTIN,
1333
1386
  )
1334
1387
  continue
@@ -1347,17 +1400,19 @@ class Namespace:
1347
1400
  message=f'Library "{entry}" already imported.',
1348
1401
  severity=DiagnosticSeverity.INFORMATION,
1349
1402
  source=DIAGNOSTICS_SOURCE_NAME,
1350
- related_information=[
1351
- DiagnosticRelatedInformation(
1352
- location=Location(
1353
- uri=str(Uri.from_path(already_imported_library[0].import_source)),
1354
- range=already_imported_library[0].import_range,
1355
- ),
1356
- message="",
1357
- )
1358
- ]
1359
- if already_imported_library[0].import_source
1360
- else None,
1403
+ related_information=(
1404
+ [
1405
+ DiagnosticRelatedInformation(
1406
+ location=Location(
1407
+ uri=str(Uri.from_path(already_imported_library[0].import_source)),
1408
+ range=already_imported_library[0].import_range,
1409
+ ),
1410
+ message="",
1411
+ )
1412
+ ]
1413
+ if already_imported_library[0].import_source
1414
+ else None
1415
+ ),
1361
1416
  code=Error.LIBRARY_ALREADY_IMPORTED,
1362
1417
  )
1363
1418
 
@@ -1661,9 +1716,11 @@ class Namespace:
1661
1716
  self._analyzed = not canceled
1662
1717
 
1663
1718
  self._logger.debug(
1664
- lambda: f"end analyzed {self.document} succeed in {time.monotonic() - start_time}s"
1665
- if self._analyzed
1666
- else f"end analyzed {self.document} failed in {time.monotonic() - start_time}s"
1719
+ lambda: (
1720
+ f"end analyzed {self.document} succeed in {time.monotonic() - start_time}s"
1721
+ if self._analyzed
1722
+ else f"end analyzed {self.document} failed in {time.monotonic() - start_time}s"
1723
+ )
1667
1724
  )
1668
1725
 
1669
1726
  self.has_analysed(self)
@@ -5,8 +5,9 @@ import itertools
5
5
  import os
6
6
  from collections import defaultdict
7
7
  from dataclasses import dataclass
8
- from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union
8
+ from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Type, Union
9
9
 
10
+ import robot.parsing.model.statements
10
11
  from robot.parsing.lexer.tokens import Token
11
12
  from robot.parsing.model.blocks import Keyword, TestCase
12
13
  from robot.parsing.model.statements import (
@@ -206,11 +207,60 @@ class NamespaceAnalyzer(Visitor, ModelHelper):
206
207
  if var_def not in self._variable_references:
207
208
  self._variable_references[var_def] = set()
208
209
 
210
+ def visit_Var(self, node: Statement) -> None: # noqa: N802
211
+ name_token = node.get_token(Token.VARIABLE)
212
+ if name_token is None:
213
+ return
214
+
215
+ name = name_token.value
216
+
217
+ if name is not None:
218
+ match = search_variable(name, ignore_errors=True)
219
+ if not match.is_assign(allow_assign_mark=True):
220
+ return
221
+
222
+ if name.endswith("="):
223
+ name = name[:-1].rstrip()
224
+
225
+ r = range_from_token(
226
+ strip_variable_token(
227
+ Token(
228
+ name_token.type,
229
+ name,
230
+ name_token.lineno,
231
+ name_token.col_offset,
232
+ name_token.error,
233
+ )
234
+ )
235
+ )
236
+ # r.start.character = 0
237
+ # r.end.character = 0
238
+
239
+ var_def = self.namespace.find_variable(
240
+ name,
241
+ skip_commandline_variables=False,
242
+ nodes=self.node_stack,
243
+ position=range_from_token(node.get_token(Token.VAR)).start,
244
+ ignore_error=True,
245
+ )
246
+ if var_def is not None:
247
+ if var_def.name_range != r:
248
+ if self.namespace.document is not None:
249
+ self._variable_references[var_def].add(Location(self.namespace.document.document_uri, r))
250
+ else:
251
+ if self.namespace.document is not None:
252
+ self._variable_references[var_def] = set()
253
+
209
254
  def generic_visit(self, node: ast.AST) -> None:
210
255
  check_current_task_canceled()
211
256
 
212
257
  super().generic_visit(node)
213
258
 
259
+ if get_robot_version() < (7, 0):
260
+ variable_statements: Tuple[Type[Any], ...] = (Variable,)
261
+ else:
262
+ variable_statements = (Variable, robot.parsing.model.statements.Var)
263
+
214
264
  def visit(self, node: ast.AST) -> None:
215
265
  check_current_task_canceled()
216
266
 
@@ -231,7 +281,7 @@ class NamespaceAnalyzer(Visitor, ModelHelper):
231
281
  for token1 in (
232
282
  t
233
283
  for t in node.tokens
234
- if not (isinstance(node, Variable) and t.type == Token.VARIABLE)
284
+ if not (isinstance(node, self.variable_statements) and t.type == Token.VARIABLE)
235
285
  and t.error is None
236
286
  and contains_variable(t.value, "$@&%")
237
287
  ):
@@ -505,9 +555,7 @@ class NamespaceAnalyzer(Visitor, ModelHelper):
505
555
  Uri.from_path(
506
556
  err.source
507
557
  if err.source is not None
508
- else result.source
509
- if result.source is not None
510
- else "/<unknown>"
558
+ else result.source if result.source is not None else "/<unknown>"
511
559
  )
512
560
  ),
513
561
  range=Range(
@@ -48,3 +48,9 @@ class CacheConfig(ConfigBase):
48
48
  save_location: CacheSaveLocation = CacheSaveLocation.WORKSPACE_STORAGE
49
49
  ignored_libraries: List[str] = field(default_factory=list)
50
50
  ignored_variables: List[str] = field(default_factory=list)
51
+
52
+
53
+ @config_section("robotcode.analysis.robot")
54
+ @dataclass
55
+ class AnalysisRobotConfig(ConfigBase):
56
+ global_library_search_order: List[str] = field(default_factory=list)
@@ -17,8 +17,7 @@ class Formatter(ABC):
17
17
  return self._handles(line.strip() if self._strip_lines else line)
18
18
 
19
19
  @abstractmethod
20
- def _handles(self, line: str) -> bool:
21
- ...
20
+ def _handles(self, line: str) -> bool: ...
22
21
 
23
22
  def add(self, line: str) -> None:
24
23
  self._lines.append(line.strip() if self._strip_lines else line)
@@ -29,8 +28,7 @@ class Formatter(ABC):
29
28
  return result
30
29
 
31
30
  @abstractmethod
32
- def format(self, lines: List[str]) -> str:
33
- ...
31
+ def format(self, lines: List[str]) -> str: ...
34
32
 
35
33
 
36
34
  class MarkDownFormatter:
@@ -80,15 +78,13 @@ class SingleLineFormatter(Formatter):
80
78
  return bool(not self._lines and self.match(line))
81
79
 
82
80
  @abstractmethod
83
- def match(self, line: str) -> Optional[re.Match[str]]:
84
- ...
81
+ def match(self, line: str) -> Optional[re.Match[str]]: ...
85
82
 
86
83
  def format(self, lines: List[str]) -> str:
87
84
  return self.format_line(lines[0])
88
85
 
89
86
  @abstractmethod
90
- def format_line(self, line: str) -> str:
91
- ...
87
+ def format_line(self, line: str) -> str: ...
92
88
 
93
89
 
94
90
  class HeaderFormatter(SingleLineFormatter):
@@ -33,8 +33,6 @@ class Languages(Protocol):
33
33
  true_strings: Set[str]
34
34
  false_strings: Set[str]
35
35
 
36
- def add_language(self, name: str) -> None:
37
- ...
36
+ def add_language(self, name: str) -> None: ...
38
37
 
39
- def __iter__(self) -> Iterator[Any]:
40
- ...
38
+ def __iter__(self) -> Iterator[Any]: ...
@@ -1 +0,0 @@
1
- __version__ = "0.69.0"