robotcode-robot 2.8.0.dev631__py3-none-any.whl → 2.8.0.dev677__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__ = "2.8.0-dev.631"
1
+ __version__ = "2.8.0-dev.677"
@@ -3398,47 +3398,54 @@ class RobotConfig(RobotExtendBaseProfile):
3398
3398
  else:
3399
3399
  verbose_callback("No profiles selected.")
3400
3400
 
3401
+ # whether a selected profile is enabled; None when its condition has an error, which is reported only once
3402
+ enabled_states: Dict[str, Optional[bool]] = {}
3403
+
3404
+ def is_enabled(name: str) -> Optional[bool]:
3405
+ if name not in enabled_states:
3406
+ try:
3407
+ enabled_states[name] = selected_profiles[name][0].is_enabled()
3408
+ except EvaluationError as e:
3409
+ message = f"Error evaluating 'enabled' condition for profile '{name}': {e}"
3410
+
3411
+ if error_callback is None:
3412
+ raise ValueError(message) from e
3413
+
3414
+ error_callback(message)
3415
+ enabled_states[name] = None
3416
+ return enabled_states[name]
3417
+
3418
+ # a profile is used when it is enabled and selected itself, or when one of the profiles that inherit it is
3419
+ # used, so that a disabled profile brings none of the profiles it inherits from along, at any depth
3420
+ used_states: Dict[str, bool] = {}
3421
+
3422
+ def is_used(name: str) -> bool:
3423
+ if name not in used_states:
3424
+ parents = selected_profiles[name][1]
3425
+ used_states[name] = bool(is_enabled(name)) and (
3426
+ parents is None or any(is_used(parent_name) for parent_name, _ in parents)
3427
+ )
3428
+ return used_states[name]
3429
+
3401
3430
  for profile_name, (profile, parent_profiles) in sorted(
3402
3431
  selected_profiles.items(), key=lambda x: x[1][0].precedence or 0
3403
3432
  ):
3404
- try:
3405
- if not profile.is_enabled():
3406
- if verbose_callback:
3407
- verbose_callback(f'Skipping profile "{profile_name}" because it\'s disabled.')
3408
- continue
3409
- except EvaluationError as e:
3410
- message = f"Error evaluating 'enabled' condition for profile '{profile_name}': {e}"
3411
-
3412
- if error_callback is None:
3413
- raise ValueError(message) from e
3414
-
3415
- error_callback(message)
3433
+ profile_enabled = is_enabled(profile_name)
3434
+ if profile_enabled is None:
3416
3435
  continue
3417
3436
 
3418
- if parent_profiles is not None:
3419
- disabled_profiles = []
3420
- skip = False
3421
- for parent_profile in parent_profiles:
3422
- try:
3423
- if not parent_profile[1].is_enabled():
3424
- disabled_profiles.append(parent_profile[0])
3425
- except EvaluationError as e:
3426
- message = f"Error evaluating 'enabled' condition for profile '{parent_profile[0]}': {e}"
3427
-
3428
- if error_callback is None:
3429
- raise ValueError(message) from e
3430
-
3431
- error_callback(message)
3432
- skip = True
3433
- break
3437
+ if not profile_enabled:
3438
+ if verbose_callback:
3439
+ verbose_callback(f'Skipping profile "{profile_name}" because it\'s disabled.')
3440
+ continue
3434
3441
 
3435
- if skip or len(disabled_profiles) == len(parent_profiles):
3436
- if verbose_callback:
3437
- verbose_callback(
3438
- f"Skipping profile inherited '{profile_name}' because no parent is enabled."
3439
- f" Disabled profiles: {', '.join(disabled_profiles)}."
3440
- )
3441
- continue
3442
+ if not is_used(profile_name):
3443
+ if verbose_callback:
3444
+ verbose_callback(
3445
+ f"Skipping inherited profile '{profile_name}' because none of the profiles that inherit it"
3446
+ f" is used: {', '.join(sorted(name for name, _ in parent_profiles or ()))}."
3447
+ )
3448
+ continue
3442
3449
 
3443
3450
  if verbose_callback:
3444
3451
  verbose_callback(f'Using profile "{profile_name}".')
@@ -1,4 +1,4 @@
1
- from dataclasses import dataclass
1
+ from dataclasses import dataclass, field
2
2
  from typing import TYPE_CHECKING, Dict, List, Optional, Set
3
3
 
4
4
  from robotcode.core.lsp.types import Diagnostic, Location, Range
@@ -25,3 +25,7 @@ class AnalyzerResult:
25
25
  testcase_metadata_references: Dict[str, Set[Location]]
26
26
  scope_tree: ScopeTree
27
27
  semantic_model: Optional["SemanticModel"] = None
28
+ # the environment variables the analysis read, which the namespace cache compares when it restores the analysis:
29
+ # whether each one checked without a default value is set, and the hash of each one whose value the analysis used
30
+ environment_set: Dict[str, bool] = field(default_factory=dict)
31
+ environment_values: Dict[str, Optional[str]] = field(default_factory=dict)
@@ -640,6 +640,16 @@ class DocumentsCacheHelper:
640
640
  )
641
641
  return None
642
642
 
643
+ # from_data resolved the imports again with the current Python path and environment variables. A namespace
644
+ # with a failed import is never stored and the dependency keys name the files found, so a failed import or
645
+ # other keys mean that an import finds another file or none now.
646
+ if result.failed_imports or set(result.dependency_metas or {}) != set(meta.dependency_fingerprints):
647
+ self._logger.debug(
648
+ lambda: f"Cache miss for {source}: the imports find other files now, re-analyzing",
649
+ context_name="cache",
650
+ )
651
+ return None
652
+
643
653
  self._logger.debug(
644
654
  lambda: f"Loaded namespace from disk cache for {source}",
645
655
  context_name="import",
@@ -52,10 +52,6 @@ class Import(SourceEntity):
52
52
  name: Optional[str]
53
53
  name_token: Optional[Token]
54
54
 
55
- @property
56
- def hint_key(self) -> str:
57
- return f"{self.name}\0{self.source or ''}\0{self.line_no}"
58
-
59
55
  @property
60
56
  def range(self) -> Range:
61
57
  return Range(
@@ -360,6 +356,26 @@ class VariablesEntry(LibraryEntry):
360
356
  )
361
357
 
362
358
 
359
+ def library_entry_key(entry: LibraryEntry) -> str:
360
+ """The key of a library entry in the namespace disk cache: of a
361
+ `namespace_references` entry in `NamespaceData` and of a `lib_entry` in
362
+ a pickled semantic model.
363
+
364
+ Like `LibraryEntry.__hash__`, it includes the import position, so it tells
365
+ apart the entries of one library: the implicitly imported BuiltIn and an
366
+ explicit import of it, or a library imported by a resource file and the
367
+ same library imported directly.
368
+ """
369
+
370
+ def position(r: Range) -> str:
371
+ return f"{r.start.line}.{r.start.character}-{r.end.line}.{r.end.character}"
372
+
373
+ return (
374
+ f"{type(entry).__name__}:{entry.name}:{entry.import_name}:{entry.args!r}:{entry.alias or ''}"
375
+ f":{entry.import_source or ''}:{position(entry.import_range)}:{position(entry.alias_range)}"
376
+ )
377
+
378
+
363
379
  @dataclass(slots=True)
364
380
  class TestCaseDefinition(SourceEntity):
365
381
  name: str
@@ -9,7 +9,6 @@ The resolver takes imports + a VariableScope and returns ResolvedImports
9
9
  containing all resolved entries and collected diagnostics.
10
10
  """
11
11
 
12
- import os
13
12
  from dataclasses import dataclass, field
14
13
  from pathlib import Path
15
14
  from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
@@ -129,7 +128,6 @@ class ImportResolver:
129
128
  "_scope",
130
129
  "_sentinel",
131
130
  "_source",
132
- "_source_hints",
133
131
  "_source_id",
134
132
  "_variables",
135
133
  "_variables_dirty",
@@ -159,14 +157,11 @@ class ImportResolver:
159
157
  self._diagnostics: List[Diagnostic] = []
160
158
  self._variables: Dict[str, Any] = {}
161
159
  self._variables_dirty = True
162
- self._source_hints: Dict[str, str] = {}
163
160
  self._dependency_metas: Dict[str, Optional[Any]] = {}
164
161
  self._failed_imports: List[FailedImport] = []
165
162
 
166
- def resolve(self, imports: Iterable[Import], *, source_hints: Optional[Dict[str, str]] = None) -> ResolvedImports:
163
+ def resolve(self, imports: Iterable[Import]) -> ResolvedImports:
167
164
  """Resolve all imports and return the result."""
168
- if source_hints:
169
- self._source_hints = source_hints
170
165
  self._refresh_variables()
171
166
  self._import_default_libraries()
172
167
  self._handle_imports(imports, self._base_dir, top_level=True)
@@ -542,19 +537,11 @@ class ImportResolver:
542
537
  ) -> None:
543
538
  assert imp.name is not None
544
539
 
545
- # Look up cached source hint to skip find_resource() filesystem search
546
- source_hint: Optional[str] = None
547
- if self._source_hints:
548
- hint = self._source_hints.get(imp.hint_key)
549
- if hint is not None and os.path.isfile(hint):
550
- source_hint = hint
551
-
552
540
  resource_doc, meta = self._imports_manager.get_resource_doc_for_resource_import_with_meta(
553
541
  imp.name,
554
542
  base_dir,
555
543
  sentinel=self._sentinel,
556
544
  variables=self._variables,
557
- source=source_hint,
558
545
  )
559
546
  if resource_doc.source is not None:
560
547
  # setdefault: on re-imports of the same resource the meta captured
@@ -59,7 +59,7 @@ from ..__version__ import __version__
59
59
  from ..utils import RF_VERSION
60
60
  from ..utils.robot_path import find_file_ex
61
61
  from ..utils.stubs import Languages
62
- from ..utils.variables import contains_variable
62
+ from ..utils.variables import contains_variable, environment_value_hash
63
63
  from .data_cache import CACHE_DIR_NAME, CacheSection, build_cache_dir
64
64
  from .data_cache import SqliteDataCache as DefaultDataCache
65
65
  from .entities import (
@@ -722,6 +722,10 @@ class NamespaceMetaData:
722
722
  config_fingerprint: Any
723
723
  dependency_fingerprints: Dict[str, Any] = field(default_factory=dict)
724
724
  semantic_model_enabled: bool = False
725
+ # the environment variables the analysis read: whether each one checked without a default value was set, and the
726
+ # hash of each one whose value it used (see environment_value_hash)
727
+ environment_set: Dict[str, bool] = field(default_factory=dict)
728
+ environment_values: Dict[str, Optional[str]] = field(default_factory=dict)
725
729
 
726
730
 
727
731
  class ImportsManager:
@@ -808,7 +812,6 @@ class ImportsManager:
808
812
  self._config_fingerprint: Any = (
809
813
  tuple(sorted(self.cmd_variables.items())),
810
814
  tuple(self.cmd_variable_files),
811
- tuple(sorted((k, v) for k, v in self._environment.items() if k not in os.environ)),
812
815
  tuple(self.global_library_search_order),
813
816
  self._languages_fingerprint,
814
817
  )
@@ -895,8 +898,9 @@ class ImportsManager:
895
898
  """Cached configuration snapshot.
896
899
 
897
900
  Computed once at init from cmd_variables, cmd_variable_files,
898
- environment, and global_library_search_order. Changes are
899
- detected via direct ``==`` comparison.
901
+ global_library_search_order and the language settings. Changes are
902
+ detected via direct ``==`` comparison. Environment variables are
903
+ not part of it: each namespace keeps the ones its analysis read.
900
904
  """
901
905
  return self._config_fingerprint
902
906
 
@@ -949,13 +953,15 @@ class ImportsManager:
949
953
  config_fingerprint=self.config_fingerprint,
950
954
  dependency_fingerprints=dict(dependency_metas),
951
955
  semantic_model_enabled=semantic_model_enabled,
956
+ environment_set=dict(namespace.environment_set),
957
+ environment_values=dict(namespace.environment_values),
952
958
  )
953
959
 
954
960
  def validate_namespace_meta(self, meta: NamespaceMetaData, source_disk_info: Optional[DiskInfo]) -> bool:
955
961
  """Check whether a cached NamespaceMetaData is fresh for the given content.
956
962
 
957
963
  Performs a 2-level validation:
958
- Level 1 (fast): source_disk_info, config_fingerprint
964
+ Level 1 (fast): source_disk_info, config_fingerprint, the environment variables the analysis read
959
965
  Level 2 (dependency check): each dependency fingerprint
960
966
 
961
967
  Validation is against the *effective* content: the source is compared
@@ -990,6 +996,25 @@ class ImportsManager:
990
996
  )
991
997
  return False
992
998
 
999
+ # the analysis reads os.environ itself, so it does not matter where a variable was set
1000
+ for env_name, was_set in meta.environment_set.items():
1001
+ if (os.environ.get(env_name) is not None) != was_set:
1002
+ name = env_name
1003
+ self._logger.debug(
1004
+ lambda: f"Cache miss for {source}: environment variable {name!r} was set or unset",
1005
+ context_name="cache",
1006
+ )
1007
+ return False
1008
+
1009
+ for env_name, value_hash in meta.environment_values.items():
1010
+ if environment_value_hash(os.environ.get(env_name)) != value_hash:
1011
+ name = env_name
1012
+ self._logger.debug(
1013
+ lambda: f"Cache miss for {source}: environment variable {name!r} changed",
1014
+ context_name="cache",
1015
+ )
1016
+ return False
1017
+
993
1018
  # Level 2: dependency checks — direct comparison, no hashing
994
1019
  for key, saved_value in meta.dependency_fingerprints.items():
995
1020
  if key.startswith("lib:"):
@@ -2211,10 +2236,8 @@ class ImportsManager:
2211
2236
  base_dir: str,
2212
2237
  sentinel: Any = None,
2213
2238
  variables: Optional[Dict[str, Any]] = None,
2214
- *,
2215
- source: Optional[str] = None,
2216
2239
  ) -> _ResourcesEntry:
2217
- source = source or self.find_resource(name, base_dir, variables=variables)
2240
+ source = self.find_resource(name, base_dir, variables=variables)
2218
2241
  normalized_source = os.path.normpath(os.path.abspath(source))
2219
2242
 
2220
2243
  entry_key = _ResourcesEntryKey(normalized_source)
@@ -2238,12 +2261,10 @@ class ImportsManager:
2238
2261
  base_dir: str,
2239
2262
  sentinel: Any = None,
2240
2263
  variables: Optional[Dict[str, Any]] = None,
2241
- *,
2242
- source: Optional[str] = None,
2243
2264
  ) -> Tuple["Namespace", LibraryDoc]:
2244
2265
  with self._logger.measure_time(lambda: f"getting namespace and libdoc for {name}", context_name="import"):
2245
2266
  with self._logger.measure_time(lambda: f"getting resource entry {name}", context_name="import"):
2246
- entry = self._get_entry_for_resource_import(name, base_dir, sentinel, variables, source=source)
2267
+ entry = self._get_entry_for_resource_import(name, base_dir, sentinel, variables)
2247
2268
 
2248
2269
  with self._logger.measure_time(lambda: f"getting namespace {name}", context_name="import"):
2249
2270
  namespace = entry.get_namespace()
@@ -2258,11 +2279,9 @@ class ImportsManager:
2258
2279
  base_dir: str,
2259
2280
  sentinel: Any = None,
2260
2281
  variables: Optional[Dict[str, Any]] = None,
2261
- *,
2262
- source: Optional[str] = None,
2263
2282
  ) -> Tuple[ResourceDoc, Optional[RobotFileMeta]]:
2264
2283
  with self._logger.measure_time(lambda: f"getting resource doc for {name}", context_name="import"):
2265
- entry = self._get_entry_for_resource_import(name, base_dir, sentinel, variables, source=source)
2284
+ entry = self._get_entry_for_resource_import(name, base_dir, sentinel, variables)
2266
2285
 
2267
2286
  return entry.get_resource_doc_with_meta()
2268
2287
 
@@ -2272,12 +2291,8 @@ class ImportsManager:
2272
2291
  base_dir: str,
2273
2292
  sentinel: Any = None,
2274
2293
  variables: Optional[Dict[str, Any]] = None,
2275
- *,
2276
- source: Optional[str] = None,
2277
2294
  ) -> ResourceDoc:
2278
- return self.get_resource_doc_for_resource_import_with_meta(name, base_dir, sentinel, variables, source=source)[
2279
- 0
2280
- ]
2295
+ return self.get_resource_doc_for_resource_import_with_meta(name, base_dir, sentinel, variables)[0]
2281
2296
 
2282
2297
  def complete_library_import(
2283
2298
  self,
@@ -44,6 +44,7 @@ from .entities import (
44
44
  VariableDefinition,
45
45
  VariablesEntry,
46
46
  VariablesImport,
47
+ library_entry_key,
47
48
  )
48
49
  from .errors import DIAGNOSTICS_SOURCE_NAME
49
50
  from .import_resolver import FailedImport, ImportResolver
@@ -132,12 +133,6 @@ class NamespaceData:
132
133
  # --- ScopeTree (local scopes only, file_scope is reconstructed) ---
133
134
  local_scopes: List[LocalScope] = field(default_factory=list)
134
135
 
135
- # --- Resolved resource import sources ---
136
- # Maps (import_name\0source_file) → resolved absolute path for resources.
137
- # Used as source hints in from_data() to skip find_resource() filesystem
138
- # lookups during import re-resolution.
139
- resolved_resource_sources: Dict[str, str] = field(default_factory=dict)
140
-
141
136
  # --- Authoritative variable definitions (stable_id → VariableDefinition) ---
142
137
  # Stores the exact VariableDefinition objects that were referenced during
143
138
  # analysis. Used as fallback in from_data() when the re-parsed resource doc
@@ -147,24 +142,6 @@ class NamespaceData:
147
142
  variable_definitions: Dict[str, VariableDefinition] = field(default_factory=dict)
148
143
 
149
144
 
150
- def _namespace_reference_key(entry: LibraryEntry) -> str:
151
- """The key of a `namespace_references` entry in `NamespaceData`.
152
-
153
- Like `LibraryEntry.__hash__`, it includes the import position, so it tells
154
- apart the entries of one library: the implicitly imported BuiltIn and an
155
- explicit import of it, or a library imported by a resource file and the
156
- same library imported directly.
157
- """
158
-
159
- def position(r: Range) -> str:
160
- return f"{r.start.line}.{r.start.character}-{r.end.line}.{r.end.character}"
161
-
162
- return (
163
- f"{type(entry).__name__}:{entry.name}:{entry.import_name}:{entry.args!r}:{entry.alias or ''}"
164
- f":{entry.import_source or ''}:{position(entry.import_range)}:{position(entry.alias_range)}"
165
- )
166
-
167
-
168
145
  class Namespace:
169
146
  """Data container holding all results of a namespace build.
170
147
 
@@ -206,6 +183,8 @@ class Namespace:
206
183
  semantic_model: Optional["SemanticModel"] = None,
207
184
  dependency_metas: Optional[Dict[str, Optional[Any]]] = None,
208
185
  failed_imports: Optional[List[FailedImport]] = None,
186
+ environment_set: Optional[Dict[str, bool]] = None,
187
+ environment_values: Optional[Dict[str, Optional[str]]] = None,
209
188
  ) -> None:
210
189
  self.imports_manager = imports_manager
211
190
  self.source = source
@@ -236,6 +215,8 @@ class Namespace:
236
215
  self._semantic_model = semantic_model
237
216
  self._dependency_metas = dependency_metas
238
217
  self._failed_imports = failed_imports or []
218
+ self._environment_set = environment_set or {}
219
+ self._environment_values = environment_values or {}
239
220
 
240
221
  # Lazy-computed caches
241
222
  self._namespaces: Optional[Dict[KeywordMatcher, List[LibraryEntry]]] = None
@@ -261,6 +242,16 @@ class Namespace:
261
242
  """The imports whose resolution raised or whose library or variables documentation has errors."""
262
243
  return self._failed_imports
263
244
 
245
+ @property
246
+ def environment_set(self) -> Dict[str, bool]:
247
+ """Whether each environment variable the analysis checked without a default value was set."""
248
+ return self._environment_set
249
+
250
+ @property
251
+ def environment_values(self) -> Dict[str, Optional[str]]:
252
+ """The hash of each environment variable whose value the analysis used, see `environment_value_hash`."""
253
+ return self._environment_values
254
+
264
255
  @property
265
256
  def document(self) -> Optional[TextDocument]:
266
257
  return self._document() if self._document is not None else None
@@ -516,7 +507,7 @@ class Namespace:
516
507
  """
517
508
  # Build namespace_references key from LibraryEntry identity
518
509
  ns_refs: Dict[str, Set[Location]] = {
519
- _namespace_reference_key(entry): locs for entry, locs in self._namespace_references.items()
510
+ library_entry_key(entry): locs for entry, locs in self._namespace_references.items()
520
511
  }
521
512
 
522
513
  # Collect all referenced variable definitions for stable_id → object lookup
@@ -555,12 +546,6 @@ class Namespace:
555
546
  else:
556
547
  var_assigns_merged[sid] = set(ranges)
557
548
 
558
- # Build resolved resource source hints for from_data() optimization
559
- resolved_res_sources: Dict[str, str] = {}
560
- for imp, entry in self._import_entries.items():
561
- if isinstance(entry, ResourceEntry) and entry.library_doc.source:
562
- resolved_res_sources[imp.hint_key] = entry.library_doc.source
563
-
564
549
  return NamespaceData(
565
550
  source=self.source,
566
551
  source_id=str(self.source_id) if self.source_id else None,
@@ -580,7 +565,6 @@ class Namespace:
580
565
  metadata_references={k: set(v) for k, v in self._metadata_references.items()},
581
566
  testcase_metadata_references={k: set(v) for k, v in self._testcase_metadata_references.items()},
582
567
  local_scopes=list(self._scope_tree.local_scopes),
583
- resolved_resource_sources=resolved_res_sources,
584
568
  variable_definitions=all_var_defs,
585
569
  )
586
570
 
@@ -612,7 +596,7 @@ class Namespace:
612
596
  )
613
597
 
614
598
  resolver = ImportResolver(imports_manager, data.source, scope, sentinel=sentinel)
615
- resolved = resolver.resolve(data.imports, source_hints=data.resolved_resource_sources)
599
+ resolved = resolver.resolve(data.imports)
616
600
 
617
601
  # Add imported variables to scope (needed for variable lookup)
618
602
  for resource_entry in resolved.resources.values():
@@ -637,6 +621,11 @@ class Namespace:
637
621
  for kw in library_doc.keywords:
638
622
  kw_by_id[kw.stable_id] = kw
639
623
 
624
+ # the init of a variables file, which the import statement of the semantic model refers to
625
+ for variables_entry in resolved.variables_imports.values():
626
+ for kw in variables_entry.library_doc.inits:
627
+ kw_by_id[kw.stable_id] = kw
628
+
640
629
  # Variables from scope layers (command_line, own, imported, builtin)
641
630
  for var in scope.iter_all():
642
631
  var_by_id[var.stable_id] = var
@@ -685,7 +674,7 @@ class Namespace:
685
674
  # the other maps add the implicit default libraries and the imports of
686
675
  # resource files.
687
676
  all_entries: Dict[str, LibraryEntry] = {
688
- _namespace_reference_key(entry): entry
677
+ library_entry_key(entry): entry
689
678
  for entry in itertools.chain(
690
679
  resolved.libraries.values(),
691
680
  resolved.resources.values(),
@@ -905,4 +894,6 @@ class NamespaceBuilder:
905
894
  semantic_model=analyzer_result.semantic_model,
906
895
  dependency_metas=resolved.dependency_metas,
907
896
  failed_imports=resolved.failed_imports,
897
+ environment_set=analyzer_result.environment_set,
898
+ environment_values=analyzer_result.environment_values,
908
899
  )
@@ -61,6 +61,7 @@ from ..utils.variables import (
61
61
  InvalidVariableError,
62
62
  VariableMatcher,
63
63
  contains_variable,
64
+ environment_value_hash,
64
65
  is_number_literal,
65
66
  replace_curdir_in_variable_values,
66
67
  search_variable,
@@ -145,6 +146,8 @@ class NamespaceAnalyzer(Visitor):
145
146
  self._testcase_tag_references: Dict[str, Set[Location]] = defaultdict(set)
146
147
  self._metadata_references: Dict[str, Set[Location]] = defaultdict(set)
147
148
  self._testcase_metadata_references: Dict[str, Set[Location]] = defaultdict(set)
149
+ self._environment_set: Dict[str, bool] = {}
150
+ self._environment_values: Dict[str, Optional[str]] = {}
148
151
 
149
152
  # Phase 1+2 results (set by resolve())
150
153
  self._library_doc: ResourceDoc = cast(ResourceDoc, None) # set by resolve()
@@ -245,6 +248,8 @@ class NamespaceAnalyzer(Visitor):
245
248
  self._metadata_references,
246
249
  self._testcase_metadata_references,
247
250
  self._scope_builder.build(self._variable_scope),
251
+ environment_set=self._environment_set,
252
+ environment_values=self._environment_values,
248
253
  )
249
254
 
250
255
  def _visit_VariableSection(self, node: VariableSection) -> None: # noqa: N802
@@ -673,7 +678,10 @@ class NamespaceAnalyzer(Visitor):
673
678
  and cast(EnvironmentVariableDefinition, var).default_value is None
674
679
  ):
675
680
  env_name = var.name[2:-1]
676
- if os.environ.get(env_name, None) is None:
681
+ # the namespace cache compares every read of the environment when it restores the analysis
682
+ env_value = os.environ.get(env_name, None)
683
+ self._environment_set[env_name] = env_value is not None
684
+ if env_value is None:
677
685
  self._append_diagnostics(
678
686
  range=range_from_token(var_token),
679
687
  message=(
@@ -2039,7 +2047,9 @@ class NamespaceAnalyzer(Visitor):
2039
2047
  return name_default
2040
2048
  return False
2041
2049
  env_name, sep, default = env_inner.partition("=")
2050
+ # the namespace cache compares every read of the environment when it restores the analysis
2042
2051
  env_val = os.environ.get(env_name)
2052
+ self._environment_values[env_name] = environment_value_hash(env_val)
2043
2053
  if env_val is not None:
2044
2054
  return env_val
2045
2055
  if sep:
@@ -98,6 +98,7 @@ from ...utils.variables import (
98
98
  InvalidVariableError,
99
99
  VariableMatcher,
100
100
  contains_variable,
101
+ environment_value_hash,
101
102
  is_number_literal,
102
103
  replace_curdir_in_variable_values,
103
104
  search_variable,
@@ -280,6 +281,8 @@ class SemanticAnalyzer(Visitor):
280
281
  self._testcase_tag_references: Dict[str, Set[Location]] = defaultdict(set)
281
282
  self._metadata_references: Dict[str, Set[Location]] = defaultdict(set)
282
283
  self._testcase_metadata_references: Dict[str, Set[Location]] = defaultdict(set)
284
+ self._environment_set: Dict[str, bool] = {}
285
+ self._environment_values: Dict[str, Optional[str]] = {}
283
286
 
284
287
  # Phase 1+2 results (set by resolve())
285
288
  self._library_doc: ResourceDoc = cast(ResourceDoc, None)
@@ -422,6 +425,8 @@ class SemanticAnalyzer(Visitor):
422
425
  self._metadata_references,
423
426
  self._testcase_metadata_references,
424
427
  self._scope_builder.build(self._variable_scope),
428
+ environment_set=self._environment_set,
429
+ environment_values=self._environment_values,
425
430
  semantic_model=self._semantic_model,
426
431
  )
427
432
 
@@ -2004,7 +2009,10 @@ class SemanticAnalyzer(Visitor):
2004
2009
  and cast(EnvironmentVariableDefinition, var).default_value is None
2005
2010
  ):
2006
2011
  env_name = var.name[2:-1]
2007
- if os.environ.get(env_name, None) is None:
2012
+ # the namespace cache compares every read of the environment when it restores the analysis
2013
+ env_value = os.environ.get(env_name, None)
2014
+ self._environment_set[env_name] = env_value is not None
2015
+ if env_value is None:
2008
2016
  self._append_diagnostics(
2009
2017
  range=range_from_token(var_token),
2010
2018
  message=(
@@ -4456,7 +4464,9 @@ class SemanticAnalyzer(Visitor):
4456
4464
  return name_default
4457
4465
  return False
4458
4466
  env_name, sep, default = env_inner.partition("=")
4467
+ # the namespace cache compares every read of the environment when it restores the analysis
4459
4468
  env_val = os.environ.get(env_name)
4469
+ self._environment_values[env_name] = environment_value_hash(env_val)
4460
4470
  if env_val is not None:
4461
4471
  return env_val
4462
4472
  if sep:
@@ -1,10 +1,12 @@
1
1
  from dataclasses import dataclass, field
2
2
  from typing import TYPE_CHECKING, Dict, FrozenSet, List, Optional
3
3
 
4
- from .enums import NodeKind
4
+ from .enums import NodeKind, TokenKind
5
5
  from .nodes import (
6
6
  DefinitionBlock,
7
7
  DefinitionStatement,
8
+ KeywordCallStatement,
9
+ RunKeywordCallStatement,
8
10
  SemanticBlock,
9
11
  SemanticNode,
10
12
  SemanticStatement,
@@ -23,6 +25,13 @@ _SECTION_KINDS: FrozenSet[NodeKind] = frozenset(
23
25
  }
24
26
  )
25
27
 
28
+ # The namespace and the name of a keyword reference; the span from the first to the
29
+ # last of them also covers the "." between. An inner call of a Run Keyword variant
30
+ # carries its name as KEYWORD_INNER.
31
+ _KEYWORD_REFERENCE_KINDS: FrozenSet[TokenKind] = frozenset(
32
+ {TokenKind.NAMESPACE, TokenKind.KEYWORD, TokenKind.KEYWORD_INNER}
33
+ )
34
+
26
35
  if TYPE_CHECKING:
27
36
  from ...utils.variables import VariableMatcher
28
37
  from ..entities import VariableDefinition
@@ -193,6 +202,37 @@ class SemanticModel:
193
202
  SemanticModel._collect_token_path(sub, line, col, path)
194
203
  return
195
204
 
205
+ def keyword_call_at(self, line: int, col: int) -> Optional[KeywordCallStatement]:
206
+ """Get the keyword call whose keyword reference covers a position.
207
+
208
+ The keyword reference is the namespace and the keyword name with the
209
+ "." between them, without a BDD prefix; its end counts as covered, as
210
+ in the legacy check of a keyword token. Besides the call that
211
+ `statement_at()` returns, this finds the calls in the body of an
212
+ inline IF, which are statements of their own on the line, and the
213
+ inner calls of a Run Keyword variant, searched recursively.
214
+ O(n) in statements.
215
+ """
216
+ for stmt in self.statements:
217
+ if isinstance(stmt, KeywordCallStatement) and stmt.line_start <= line <= stmt.line_end:
218
+ call = self._keyword_call_covering(stmt, line, col)
219
+ if call is not None:
220
+ return call
221
+ return None
222
+
223
+ @staticmethod
224
+ def _keyword_call_covering(stmt: KeywordCallStatement, line: int, col: int) -> Optional[KeywordCallStatement]:
225
+ reference = [token for token in stmt.tokens if token.kind in _KEYWORD_REFERENCE_KINDS]
226
+ if reference and reference[0].line == line:
227
+ if reference[0].col_offset <= col <= reference[-1].col_offset + reference[-1].length:
228
+ return stmt
229
+ if isinstance(stmt, RunKeywordCallStatement):
230
+ for inner in stmt.inner_calls:
231
+ call = SemanticModel._keyword_call_covering(inner, line, col)
232
+ if call is not None:
233
+ return call
234
+ return None
235
+
196
236
  def enclosing_definition(self, line: int) -> Optional["DefinitionBlock | DefinitionStatement"]:
197
237
  """Find the enclosing definition (Keyword/TestCase) containing the given line.
198
238
 
@@ -1,8 +1,9 @@
1
1
  from dataclasses import dataclass, field
2
- from typing import TYPE_CHECKING, FrozenSet, List, Optional, Tuple
2
+ from typing import TYPE_CHECKING, Any, Dict, FrozenSet, List, Optional, Tuple
3
3
 
4
4
  from robotcode.core.lsp.types import Position, Range
5
5
 
6
+ from ..entities import library_entry_key
6
7
  from .enums import (
7
8
  ForFlavor,
8
9
  ForZipMode,
@@ -19,6 +20,16 @@ if TYPE_CHECKING:
19
20
  from ..library_doc import ArgumentSpec, KeywordDoc
20
21
 
21
22
 
23
+ def _slot_values(node: object) -> Dict[str, Any]:
24
+ """The values of all slots of a node, the base of a statement's pickled state."""
25
+ return {slot: getattr(node, slot) for cls in type(node).__mro__ for slot in getattr(cls, "__slots__", ())}
26
+
27
+
28
+ def _set_slot_values(node: object, state: Dict[str, Any]) -> None:
29
+ for slot, value in state.items():
30
+ setattr(node, slot, value)
31
+
32
+
22
33
  @dataclass(slots=True)
23
34
  class SemanticToken:
24
35
  """A single resolved token in the Semantic Model.
@@ -136,6 +147,21 @@ class KeywordCallStatement(SemanticStatement):
136
147
  # Variables that receive the return value: ${result}= My Keyword
137
148
  assign_variables: List[SemanticToken] = field(default_factory=list)
138
149
 
150
+ def __getstate__(self) -> Dict[str, Any]:
151
+ # The namespace disk cache stores ids for the keyword and the library
152
+ # entry, which belong to the analysis of the imports;
153
+ # `resolve_references()` maps them back to the restored namespace's
154
+ # objects. Inner calls replace their own references.
155
+ state = _slot_values(self)
156
+ if self.keyword_doc is not None:
157
+ state["keyword_doc"] = self.keyword_doc.stable_id
158
+ if self.lib_entry is not None:
159
+ state["lib_entry"] = library_entry_key(self.lib_entry)
160
+ return state
161
+
162
+ def __setstate__(self, state: Dict[str, Any]) -> None:
163
+ _set_slot_values(self, state)
164
+
139
165
 
140
166
  @dataclass(slots=True)
141
167
  class RunKeywordCallStatement(KeywordCallStatement):
@@ -274,6 +300,18 @@ class ImportStatement(SemanticStatement):
274
300
  # second AST walk + libdoc lookup.
275
301
  init_keyword_doc: Optional["KeywordDoc"] = None
276
302
 
303
+ def __getstate__(self) -> Dict[str, Any]:
304
+ # ids in the namespace disk cache, see `KeywordCallStatement.__getstate__`
305
+ state = _slot_values(self)
306
+ if self.lib_entry is not None:
307
+ state["lib_entry"] = library_entry_key(self.lib_entry)
308
+ if self.init_keyword_doc is not None:
309
+ state["init_keyword_doc"] = self.init_keyword_doc.stable_id
310
+ return state
311
+
312
+ def __setstate__(self, state: Dict[str, Any]) -> None:
313
+ _set_slot_values(self, state)
314
+
277
315
 
278
316
  # --- Settings ---
279
317
 
@@ -328,6 +366,16 @@ class TemplateDataStatement(SemanticStatement):
328
366
 
329
367
  template_keyword_doc: Optional["KeywordDoc"] = None
330
368
 
369
+ def __getstate__(self) -> Dict[str, Any]:
370
+ # an id in the namespace disk cache, see `KeywordCallStatement.__getstate__`
371
+ state = _slot_values(self)
372
+ if self.template_keyword_doc is not None:
373
+ state["template_keyword_doc"] = self.template_keyword_doc.stable_id
374
+ return state
375
+
376
+ def __setstate__(self, state: Dict[str, Any]) -> None:
377
+ _set_slot_values(self, state)
378
+
331
379
 
332
380
  # --- Blocks (structural containers) ---
333
381
 
@@ -5,13 +5,14 @@ stable_id strings back to live objects, and pickle helper utilities.
5
5
 
6
6
  The SemanticModel is designed to be pickle-friendly:
7
7
  - SemanticToken is a pure value object (no live references) - pickles natively
8
- - SemanticStatement subclasses hold keyword_doc/lib_entry references which
9
- are live objects that cannot survive pickle/unpickle across sessions
8
+ - SemanticStatement subclasses hold keyword_doc/lib_entry/init_keyword_doc/
9
+ template_keyword_doc references which are live objects that cannot survive
10
+ pickle/unpickle across sessions
10
11
 
11
12
  For serialization:
12
- 1. Before pickling: replace live object references with stable_id strings
13
- 2. Pickle normally
14
- 3. After unpickling: call resolve_references() to restore live objects
13
+ 1. When pickled, the statements store stable_id strings and library entry
14
+ keys instead of these references (`__getstate__` in nodes.py)
15
+ 2. After unpickling: call resolve_references() to restore live objects
15
16
  """
16
17
 
17
18
  from typing import TYPE_CHECKING, Dict, Optional
@@ -24,6 +25,7 @@ from .nodes import (
24
25
  RunKeywordCallStatement,
25
26
  SemanticBlock,
26
27
  SemanticStatement,
28
+ TemplateDataStatement,
27
29
  )
28
30
 
29
31
  if TYPE_CHECKING:
@@ -39,10 +41,11 @@ def resolve_references(
39
41
  ) -> None:
40
42
  """Resolve stable_id strings back to live objects after unpickling.
41
43
 
42
- After unpickling a SemanticModel, keyword_doc and lib_entry fields
43
- contain stable_id strings instead of live objects. This function
44
+ After unpickling a SemanticModel, the keyword_doc, lib_entry,
45
+ init_keyword_doc and template_keyword_doc fields contain stable_id
46
+ strings and library entry keys instead of live objects. This function
44
47
  walks the model and replaces them with the actual objects from the
45
- provided lookup dicts.
48
+ provided lookup dicts; an id that is not in them resolves to None.
46
49
 
47
50
  Args:
48
51
  model: The unpickled SemanticModel to resolve.
@@ -85,6 +88,9 @@ def _resolve_statement(
85
88
  _resolve_keyword_call(stmt, kw_by_id, entry_by_key)
86
89
  elif isinstance(stmt, ImportStatement):
87
90
  stmt.lib_entry = _resolve_entry(stmt.lib_entry, entry_by_key)
91
+ stmt.init_keyword_doc = _resolve_keyword(stmt.init_keyword_doc, kw_by_id)
92
+ elif isinstance(stmt, TemplateDataStatement):
93
+ stmt.template_keyword_doc = _resolve_keyword(stmt.template_keyword_doc, kw_by_id)
88
94
  elif isinstance(stmt, DefinitionStatement):
89
95
  if isinstance(stmt.arguments_spec, str):
90
96
  # arguments_spec should not be serialized as string,
@@ -98,9 +104,7 @@ def _resolve_keyword_call(
98
104
  entry_by_key: Dict[str, "LibraryEntry"],
99
105
  ) -> None:
100
106
  """Resolve references in a keyword call statement."""
101
- if isinstance(stmt.keyword_doc, str):
102
- stmt.keyword_doc = kw_by_id.get(stmt.keyword_doc) # type: ignore[unreachable]
103
-
107
+ stmt.keyword_doc = _resolve_keyword(stmt.keyword_doc, kw_by_id)
104
108
  stmt.lib_entry = _resolve_entry(stmt.lib_entry, entry_by_key)
105
109
 
106
110
  if isinstance(stmt, RunKeywordCallStatement):
@@ -108,6 +112,16 @@ def _resolve_keyword_call(
108
112
  _resolve_keyword_call(inner, kw_by_id, entry_by_key)
109
113
 
110
114
 
115
+ def _resolve_keyword(
116
+ keyword_doc: Optional["KeywordDoc"],
117
+ kw_by_id: Dict[str, "KeywordDoc"],
118
+ ) -> Optional["KeywordDoc"]:
119
+ """Resolve a single keyword reference."""
120
+ if isinstance(keyword_doc, str):
121
+ return kw_by_id.get(keyword_doc) # type: ignore[unreachable]
122
+ return keyword_doc
123
+
124
+
111
125
  def _resolve_entry(
112
126
  entry: Optional["LibraryEntry"],
113
127
  entry_by_key: Dict[str, "LibraryEntry"],
@@ -1,4 +1,5 @@
1
1
  import functools
2
+ import hashlib
2
3
  from pathlib import Path
3
4
  from typing import Any, Optional, Sequence, Tuple, cast
4
5
 
@@ -275,3 +276,11 @@ def replace_curdir_in_variable_values(values: Sequence[str], source: str) -> Tup
275
276
  """Replace ${CURDIR} in variable values with the escaped parent directory of source."""
276
277
  curdir = str(Path(source).parent).replace("\\", "\\\\")
277
278
  return tuple(s.replace("${CURDIR}", curdir) for s in values)
279
+
280
+
281
+ def environment_value_hash(value: Optional[str]) -> Optional[str]:
282
+ """The SHA-256 of the value of an environment variable, or None when it is not set.
283
+
284
+ What the analysis cache keeps instead of the value, so that no value of the environment ends up in it.
285
+ """
286
+ return hashlib.sha256(value.encode("utf-8", "surrogateescape")).hexdigest() if value is not None else None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: robotcode-robot
3
- Version: 2.8.0-dev.631
3
+ Version: 2.8.0-dev.677
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://opencollective.com/robotcode
@@ -27,7 +27,7 @@ Classifier: Topic :: Utilities
27
27
  Classifier: Typing :: Typed
28
28
  Requires-Python: >=3.10
29
29
  Requires-Dist: platformdirs>=4.3
30
- Requires-Dist: robotcode-core==2.8.0-dev.631
30
+ Requires-Dist: robotcode-core==2.8.0-dev.677
31
31
  Requires-Dist: robotframework>=5.0.0
32
32
  Requires-Dist: tomli>=1.1.0; python_version < '3.11'
33
33
  Description-Content-Type: text/markdown
@@ -1,37 +1,37 @@
1
1
  robotcode/robot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- robotcode/robot/__version__.py,sha256=9SGjqX5uvERIavT5Cjgw0TSLgQ-nKCFaQ13-E6K7MDA,30
2
+ robotcode/robot/__version__.py,sha256=0xyLlet74gxp40f05fA-fjXhAr2XdtEik3UuIJs6yAM,30
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=HLbkZem8JiBuy9Q5naghZddAuMPOn8Tk6UBgN2T3Uyc,9073
6
- robotcode/robot/config/model.py,sha256=I3cMx3jfegK2lEGMln81rEDFbBLFKQ5uST-YwoUKi0I,111744
6
+ robotcode/robot/config/model.py,sha256=i8EH_n_20U7eCNY81nWa7oTFAxKj9iSH5Sxm1vr8bOQ,112151
7
7
  robotcode/robot/config/utils.py,sha256=A8gD5IItxuC7AY5Olcc8tmsSbqCCi_vZPVoK3BqWt_8,4070
8
8
  robotcode/robot/diagnostics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- robotcode/robot/diagnostics/analyzer_result.py,sha256=4LC031CMJWVtKJm_UMYKVLGosFPR81rSAgqU1xs4Adc,1071
9
+ robotcode/robot/diagnostics/analyzer_result.py,sha256=5pHW5Yf3C3rmaDvmM7tP0y_qwqLW3YHhkeVV-qDhxtg,1459
10
10
  robotcode/robot/diagnostics/data_cache.py,sha256=Yi52oQTUBFgcPK0UF3lDAj4mRFqoD_VOtrAUyRWXnrQ,18200
11
11
  robotcode/robot/diagnostics/diagnostic_rules.py,sha256=7b4vbGTEuxCef8AHTshV4XAJe75aR4-lJ5QEDyuZtcE,3558
12
12
  robotcode/robot/diagnostics/diagnostics_modifier.py,sha256=MftxJlUEDsn9ZiOsBDApXC2N-4OjXHSZj1Y9PXXlSAg,11742
13
- robotcode/robot/diagnostics/document_cache_helper.py,sha256=D5X6b3-smbZrlYz7E4b0pGDmMA9hCfQ_Da7cRslhx34,34841
14
- robotcode/robot/diagnostics/entities.py,sha256=P1R1kJPvXNGjjah7c6TVfltCF_u1NAKGpzFnPthw3F8,11215
13
+ robotcode/robot/diagnostics/document_cache_helper.py,sha256=llwyocHMKrSpuooXMuBalwnG6hlY6YgMTIPFq2tHFr8,35462
14
+ robotcode/robot/diagnostics/entities.py,sha256=vE5szceIL-ANqJPnAbqIebg3kLddE9OPhxwBL9iKP-M,11949
15
15
  robotcode/robot/diagnostics/errors.py,sha256=lqHDWrMNy3ZhuEIVxeEhaU0ns8PB6pVTQBfaMqRQ8L4,2733
16
- robotcode/robot/diagnostics/import_resolver.py,sha256=lqbJYm3i62nK-EB1tgQ-RZoUtJ6rkB9JX6AzSN-p9t4,33126
17
- robotcode/robot/diagnostics/imports_manager.py,sha256=SRSidp3KyPXvT7NollGu-nhYmVKon8hw2pXoQog9SlE,89243
16
+ robotcode/robot/diagnostics/import_resolver.py,sha256=DZjUOD3uNxFAwsVUqrCTRhColjz9DRPetAvIb_Ccufs,32588
17
+ robotcode/robot/diagnostics/imports_manager.py,sha256=opemApuCLgPNsnqVuYc9MefqHt42g_7Ozqhrmu_osH0,90401
18
18
  robotcode/robot/diagnostics/keyword_finder.py,sha256=nwTmjvhyu5H7HHzyk8gt-pny2K2xdH2qCNyKXwwXRnk,18998
19
19
  robotcode/robot/diagnostics/library_doc.py,sha256=MYztjgnOmXAPjXn7td651oWats2qDlPrLgCkmwpdSmU,153576
20
20
  robotcode/robot/diagnostics/model_helper.py,sha256=_ySXwerkK5xvjWa82T0DFy8jpxRzcaavTJiAHnk2lXQ,29740
21
- robotcode/robot/diagnostics/namespace.py,sha256=f2A4K2KURxyXr5unBBN622ayoJg34pkePQMbS79WhVA,35914
22
- robotcode/robot/diagnostics/namespace_analyzer.py,sha256=HrYdY6DM2DvR3Jh9wB-RggbxLDgez5CLk_NRSOZQRcQ,103678
21
+ robotcode/robot/diagnostics/namespace.py,sha256=Rcmjk3V0vcY-DUkgjkRyuOdYXurKaTEqXJLc6efYrwY,35489
22
+ robotcode/robot/diagnostics/namespace_analyzer.py,sha256=dGAEbbznPJlFBKN4dLkMDU7w9xc0mYdmRARvRS3Zlwg,104331
23
23
  robotcode/robot/diagnostics/project_index.py,sha256=Oqs7kV279PixVS6rGeigPKXGJYGqJdTDmDBjU7ODDRA,11006
24
24
  robotcode/robot/diagnostics/scope_tree.py,sha256=UREHoFG7NWh6UTWxET1M5Jxz_XBjXaQJg1GUIYPctq0,8348
25
25
  robotcode/robot/diagnostics/variable_scope.py,sha256=bge49DM-1FfQYIeqK2DtBG7JAMm25mazZ_zjh0DoMmg,3096
26
26
  robotcode/robot/diagnostics/workspace_config.py,sha256=GlsR1VnUBHoLBvIZtUXkm1dBDFPT9qoiYZzwZlurmXM,3277
27
27
  robotcode/robot/diagnostics/semantic_analyzer/__init__.py,sha256=ObbJv-S8LDgkWKZtTZjISBodXxyoLdEiOjVYp5aYbT4,1689
28
- robotcode/robot/diagnostics/semantic_analyzer/analyzer.py,sha256=PrWfgnA2AveDaVpXQUQ-pyx5aDeIcQwA7q2b7K39_Uo,205496
28
+ robotcode/robot/diagnostics/semantic_analyzer/analyzer.py,sha256=CkROVL_B3QsSULXkHkRDTddLCR0sswVVxEKCQUYk_qs,206149
29
29
  robotcode/robot/diagnostics/semantic_analyzer/enums.py,sha256=FIf3bHHqYY2etzpNrZTpZxGFsdtBE15lTssI1g8T2ro,8668
30
30
  robotcode/robot/diagnostics/semantic_analyzer/json_dump.py,sha256=2Yq3CLAKF5IEqKeFDoIYn-Ize_1Ucgz82ORkx3wAguY,13836
31
- robotcode/robot/diagnostics/semantic_analyzer/model.py,sha256=YF8txeR5gXzhDLn3Fv3Y8hSo_vItEKkeLE2h3nx9Bmc,18465
32
- robotcode/robot/diagnostics/semantic_analyzer/nodes.py,sha256=lwXK648OLbNknFqAN9LP4Z-2k8tk-UuQqWYX-PZ8xIU,16379
31
+ robotcode/robot/diagnostics/semantic_analyzer/model.py,sha256=TTsrU_aDSC4JwbdypxSaGR79iCN071Y5VUMO8vGm5IE,20476
32
+ robotcode/robot/diagnostics/semantic_analyzer/nodes.py,sha256=z3EM_e7AOQ6r6Ya0iS9e4wpDXDMg5Hk-WljoWBUDOWE,18433
33
33
  robotcode/robot/diagnostics/semantic_analyzer/run_keyword.py,sha256=tfMaq982MaYlwcJViPa6w7eNuruFz1H_TWjrbefsMjA,1876
34
- robotcode/robot/diagnostics/semantic_analyzer/serialization.py,sha256=zswAxVe2X2d1U89DbEZXwhq7OfnUSRyH32xbSd8TaLY,4198
34
+ robotcode/robot/diagnostics/semantic_analyzer/serialization.py,sha256=DA-_gRbo_9D6BbCMeIg6z2_8sNBY6uayOu9y4xhjK7Q,4896
35
35
  robotcode/robot/diagnostics/semantic_analyzer/variable_tokenizer.py,sha256=vBor3GZjvZX33I9yQliUtHZbjDybWn7W0YoSd5EMO_M,33646
36
36
  robotcode/robot/utils/__init__.py,sha256=-A0YeHv7e7WK1SId7Er7Vu6frrDYWopUrcyFlyxLmPY,396
37
37
  robotcode/robot/utils/ast.py,sha256=S9xyiJZee4Y5VcroNrGIIWp8f9sutb_1o4blAlCqzdg,11218
@@ -41,8 +41,8 @@ robotcode/robot/utils/match.py,sha256=KgA6o6TbnQpOT1eAzEjhl_-FZ2XO07xiV0L7IYRzyz
41
41
  robotcode/robot/utils/robot_patching.py,sha256=jXHhnPvZx8hCQq4B_2uwX9j51Be_toWpUoTYYe8HjPo,1463
42
42
  robotcode/robot/utils/robot_path.py,sha256=ObLP8PqamTZAz5oi0eDXE_dnyZwAPXp4TJYm6ugeENs,2185
43
43
  robotcode/robot/utils/stubs.py,sha256=umugZYAyneFNgqRJBRMJPzm0u0B_TH8Sx_y-ykXnxpw,351
44
- robotcode/robot/utils/variables.py,sha256=M_JVe4DxV7ucL_iRW7VOQnSlY6I72NVhRfki4f3zMQY,9430
44
+ robotcode/robot/utils/variables.py,sha256=9WnP3QqcHdq-zioJ_gJXJobw_2Gcto_rGPCUGmOH9YQ,9829
45
45
  robotcode/robot/utils/visitor.py,sha256=nP3O0qh3YYuxR6S8wYJRBFfNwIVgsgohURBlrnFkRYQ,2299
46
- robotcode_robot-2.8.0.dev631.dist-info/METADATA,sha256=EGKTTkjAa-1ACqJS7zvLOKWf6CAloJyQL-jCUAJFrC4,2960
47
- robotcode_robot-2.8.0.dev631.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
48
- robotcode_robot-2.8.0.dev631.dist-info/RECORD,,
46
+ robotcode_robot-2.8.0.dev677.dist-info/METADATA,sha256=-WMG4AXbck6ZGANXnHl578n_RxVj5Y5o7iron1Hu8OU,2960
47
+ robotcode_robot-2.8.0.dev677.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
48
+ robotcode_robot-2.8.0.dev677.dist-info/RECORD,,