robotcode-robot 0.69.0__py3-none-any.whl → 0.71.0__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.
@@ -1 +1 @@
1
- __version__ = "0.69.0"
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,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
@@ -1,29 +1,29 @@
1
1
  robotcode/robot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- robotcode/robot/__version__.py,sha256=KqW3RShDvR3XpTHqOOZs4x2eNNCNE3rl8jGUOBXzcw0,23
2
+ robotcode/robot/__version__.py,sha256=mNejnumd8tVE1XGXnMJ_BwjG-FRuO9U5tz0dQVo3UpU,23
3
3
  robotcode/robot/py.typed,sha256=bWew9mHgMy8LqMu7RuqQXFXLBxh2CRx0dUbSx-3wE48,27
4
4
  robotcode/robot/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  robotcode/robot/config/loader.py,sha256=RvFjU3fu5U4VlTSVDa0uUzZNAnpVdZwIy_4a0sXh6d0,5777
6
6
  robotcode/robot/config/model.py,sha256=ZfzBA3iMqH2F7hWRmvRxOIiWj_ryyn6iSnjmA4ETuF4,82380
7
7
  robotcode/robot/config/utils.py,sha256=mNNE8Uq5U78_OPwhOdZjtt1HufczyEHogGMB0azRcC4,2651
8
8
  robotcode/robot/diagnostics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- robotcode/robot/diagnostics/document_cache_helper.py,sha256=mXzKTJV5QBJT2nxYBLyxIemQ4MNjxFS8st5OmUyZzck,19857
10
- robotcode/robot/diagnostics/entities.py,sha256=iC7EfRCLePF-fWxgWtA9zRQ46DPAw7X8qZkhzfMV5qI,10443
9
+ robotcode/robot/diagnostics/document_cache_helper.py,sha256=LRMMF6eVHpOw0RvgG1GqeUxjfgXDeguy5Mh13-JVZ80,20019
10
+ robotcode/robot/diagnostics/entities.py,sha256=m_AXtaMGrkk7f7oVOQc90-gA21y-sHiMbsxxI_YrmWc,11084
11
11
  robotcode/robot/diagnostics/errors.py,sha256=VavgWYuHoW5sTT16j2rl9hxMhWxBKNSFsNmHWPzARQQ,1413
12
- robotcode/robot/diagnostics/imports_manager.py,sha256=sGVHDi2KHot3dlkjLOyMXQyvjRMpEAB_qND-Pjh1c-Q,53213
13
- robotcode/robot/diagnostics/library_doc.py,sha256=34yzfIs3oOYip81CC2oChCjApOHnDYEGwHSbyQj-Hwo,97227
12
+ robotcode/robot/diagnostics/imports_manager.py,sha256=HR71GvdjJd1uwcV9ARWVd6bkDBxVUwmojSIhK8-ZcCo,53246
13
+ robotcode/robot/diagnostics/library_doc.py,sha256=0m5VT0eBuFBwEDET65rnex-sDipGP-s0g3KUNS7kwD0,97659
14
14
  robotcode/robot/diagnostics/model_helper.py,sha256=wkh2ltduJkl8YPU1UVltgrpRAgLZEAOqgfSsaSF9X54,29858
15
- robotcode/robot/diagnostics/namespace.py,sha256=n40pdkH5MZwwKalcnfXnzakwZTDPFZWKeW99bgNLue0,83765
16
- robotcode/robot/diagnostics/namespace_analyzer.py,sha256=pLAXNPhe2ll21ckkqJ5luK6ZQtEMylKq2aqzE2NwsPs,46766
17
- robotcode/robot/diagnostics/workspace_config.py,sha256=UtewdaT0ToexuvwDj5Q8Yk6NlUzHpe7ewJnxpOcVAg8,1560
15
+ robotcode/robot/diagnostics/namespace.py,sha256=8n45qHM7WVqkpOh8IzcIl5_E85d5ozUBrVMWerL1x2A,86395
16
+ robotcode/robot/diagnostics/namespace_analyzer.py,sha256=I1svp-nOB9MslnHPMlb6VD24E64MbRLNAV95H0x3od4,48500
17
+ robotcode/robot/diagnostics/workspace_config.py,sha256=WbHH8R3KSX-ryPgUYgBnR6gPMpTST2o98Mqyp4p6liw,1729
18
18
  robotcode/robot/utils/__init__.py,sha256=OjNPMn_XSnfaMCyKd8Kmq6vlRt6mIGlzW4qiiD3ykUg,447
19
19
  robotcode/robot/utils/ast.py,sha256=N7PobxXjpPWwv6UBa-GBn5wn6RAsiRm8unP6UZt0P6M,10193
20
- robotcode/robot/utils/markdownformatter.py,sha256=h4s18lQ8auHLxsMKYIgJQ2p5d_K_PTaJuCtG-xdS-cg,11653
20
+ robotcode/robot/utils/markdownformatter.py,sha256=IVVnCYJLpX8-sK73n3AjtjBVirB-Xa3U2ZudAtFUOD0,11621
21
21
  robotcode/robot/utils/match.py,sha256=jxKXVpv0SHw_LxsDc1vgOxSGGtcV_9eO9cOVj4MAgIo,527
22
22
  robotcode/robot/utils/robot_path.py,sha256=qKBh1cEnReBBLKkWu4gB9EzM-scAwE4xJc1m6v2LRN0,1786
23
- robotcode/robot/utils/stubs.py,sha256=eFVNqonOehFn2-_2SFuO9Aa8OLb5jnRlkfxO5rG5HMI,764
23
+ robotcode/robot/utils/stubs.py,sha256=6-DMI_CQVJHDgG13t-zINKGCRb_Q7MQPm0_AkfhAEvE,748
24
24
  robotcode/robot/utils/variables.py,sha256=fEl8S37lb_mD4hn2MZRAlkiuLGBjAOeZVK0r2o2CfPw,742
25
25
  robotcode/robot/utils/visitor.py,sha256=uYLqEhGPmzWKWI3SSrmCaYMwtKvNShvbiPZ4b3FavX8,3241
26
- robotcode_robot-0.69.0.dist-info/METADATA,sha256=SJrdXvmh4sOPLXADpy_PzkPXmkOMxTSpbd3Jmbk44RE,2209
27
- robotcode_robot-0.69.0.dist-info/WHEEL,sha256=mRYSEL3Ih6g5a_CVMIcwiF__0Ae4_gLYh01YFNwiq1k,87
28
- robotcode_robot-0.69.0.dist-info/licenses/LICENSE.txt,sha256=B05uMshqTA74s-0ltyHKI6yoPfJ3zYgQbvcXfDVGFf8,10280
29
- robotcode_robot-0.69.0.dist-info/RECORD,,
26
+ robotcode_robot-0.71.0.dist-info/METADATA,sha256=_lW_qkoCAOKjsAUYHLivzJsmuIgIX6VmsnER4BgHzgs,2209
27
+ robotcode_robot-0.71.0.dist-info/WHEEL,sha256=TJPnKdtrSue7xZ_AVGkp9YXcvDrobsjBds1du3Nx6dc,87
28
+ robotcode_robot-0.71.0.dist-info/licenses/LICENSE.txt,sha256=B05uMshqTA74s-0ltyHKI6yoPfJ3zYgQbvcXfDVGFf8,10280
29
+ robotcode_robot-0.71.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.21.0
2
+ Generator: hatchling 1.21.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any