robotcode-robot 2.8.0.dev548__tar.gz → 2.8.0.dev670__tar.gz
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.
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/PKG-INFO +2 -2
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/pyproject.toml +1 -1
- robotcode_robot-2.8.0.dev670/src/robotcode/robot/__version__.py +1 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/config/model.py +42 -35
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/entities.py +20 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/library_doc.py +6 -3
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/namespace.py +8 -20
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/namespace_analyzer.py +31 -12
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/semantic_analyzer/analyzer.py +65 -12
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/semantic_analyzer/enums.py +1 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/semantic_analyzer/model.py +41 -1
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/semantic_analyzer/nodes.py +49 -1
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/semantic_analyzer/serialization.py +25 -11
- robotcode_robot-2.8.0.dev548/src/robotcode/robot/__version__.py +0 -1
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/.gitignore +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/README.md +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/__init__.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/config/__init__.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/config/loader.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/config/utils.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/__init__.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/analyzer_result.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/data_cache.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/diagnostic_rules.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/diagnostics_modifier.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/document_cache_helper.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/errors.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/import_resolver.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/imports_manager.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/keyword_finder.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/model_helper.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/project_index.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/scope_tree.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/semantic_analyzer/__init__.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/semantic_analyzer/json_dump.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/semantic_analyzer/run_keyword.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/semantic_analyzer/variable_tokenizer.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/variable_scope.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/diagnostics/workspace_config.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/py.typed +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/__init__.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/ast.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/markdown_docs.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/markdownformatter.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/match.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/robot_patching.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/robot_path.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/stubs.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/variables.py +0 -0
- {robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/visitor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: robotcode-robot
|
|
3
|
-
Version: 2.8.0-dev.
|
|
3
|
+
Version: 2.8.0-dev.670
|
|
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.
|
|
30
|
+
Requires-Dist: robotcode-core==2.8.0-dev.670
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.8.0-dev.670"
|
{robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/config/model.py
RENAMED
|
@@ -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
|
-
|
|
3405
|
-
|
|
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
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
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
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
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}".')
|
|
@@ -360,6 +360,26 @@ class VariablesEntry(LibraryEntry):
|
|
|
360
360
|
)
|
|
361
361
|
|
|
362
362
|
|
|
363
|
+
def library_entry_key(entry: LibraryEntry) -> str:
|
|
364
|
+
"""The key of a library entry in the namespace disk cache: of a
|
|
365
|
+
`namespace_references` entry in `NamespaceData` and of a `lib_entry` in
|
|
366
|
+
a pickled semantic model.
|
|
367
|
+
|
|
368
|
+
Like `LibraryEntry.__hash__`, it includes the import position, so it tells
|
|
369
|
+
apart the entries of one library: the implicitly imported BuiltIn and an
|
|
370
|
+
explicit import of it, or a library imported by a resource file and the
|
|
371
|
+
same library imported directly.
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
def position(r: Range) -> str:
|
|
375
|
+
return f"{r.start.line}.{r.start.character}-{r.end.line}.{r.end.character}"
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
f"{type(entry).__name__}:{entry.name}:{entry.import_name}:{entry.args!r}:{entry.alias or ''}"
|
|
379
|
+
f":{entry.import_source or ''}:{position(entry.import_range)}:{position(entry.alias_range)}"
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
|
|
363
383
|
@dataclass(slots=True)
|
|
364
384
|
class TestCaseDefinition(SourceEntity):
|
|
365
385
|
name: str
|
|
@@ -1930,13 +1930,16 @@ class LibraryDoc:
|
|
|
1930
1930
|
def _get_title_markdown(self, header_level: int) -> str:
|
|
1931
1931
|
result = f"#{'#' * header_level} {(self.type.capitalize()) if self.type else 'Unknown'} *{self.name}*\n\n"
|
|
1932
1932
|
|
|
1933
|
-
|
|
1933
|
+
# resource and suite files show GLOBAL, as in Libdoc; variable files have no scope in Robot Framework
|
|
1934
|
+
scope = self.scope if self.type != "VARIABLES" else ""
|
|
1935
|
+
|
|
1936
|
+
if self.version or scope:
|
|
1934
1937
|
result += "| | |\n| :--- | :--- |\n"
|
|
1935
1938
|
|
|
1936
1939
|
if self.version:
|
|
1937
1940
|
result += f"| **Library Version:** | {self.version} |\n"
|
|
1938
|
-
if
|
|
1939
|
-
result += f"| **Library Scope:** | {
|
|
1941
|
+
if scope:
|
|
1942
|
+
result += f"| **Library Scope:** | {scope} |\n"
|
|
1940
1943
|
|
|
1941
1944
|
result += "\n\n"
|
|
1942
1945
|
|
|
@@ -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
|
|
@@ -147,24 +148,6 @@ class NamespaceData:
|
|
|
147
148
|
variable_definitions: Dict[str, VariableDefinition] = field(default_factory=dict)
|
|
148
149
|
|
|
149
150
|
|
|
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
151
|
class Namespace:
|
|
169
152
|
"""Data container holding all results of a namespace build.
|
|
170
153
|
|
|
@@ -516,7 +499,7 @@ class Namespace:
|
|
|
516
499
|
"""
|
|
517
500
|
# Build namespace_references key from LibraryEntry identity
|
|
518
501
|
ns_refs: Dict[str, Set[Location]] = {
|
|
519
|
-
|
|
502
|
+
library_entry_key(entry): locs for entry, locs in self._namespace_references.items()
|
|
520
503
|
}
|
|
521
504
|
|
|
522
505
|
# Collect all referenced variable definitions for stable_id → object lookup
|
|
@@ -637,6 +620,11 @@ class Namespace:
|
|
|
637
620
|
for kw in library_doc.keywords:
|
|
638
621
|
kw_by_id[kw.stable_id] = kw
|
|
639
622
|
|
|
623
|
+
# the init of a variables file, which the import statement of the semantic model refers to
|
|
624
|
+
for variables_entry in resolved.variables_imports.values():
|
|
625
|
+
for kw in variables_entry.library_doc.inits:
|
|
626
|
+
kw_by_id[kw.stable_id] = kw
|
|
627
|
+
|
|
640
628
|
# Variables from scope layers (command_line, own, imported, builtin)
|
|
641
629
|
for var in scope.iter_all():
|
|
642
630
|
var_by_id[var.stable_id] = var
|
|
@@ -685,7 +673,7 @@ class Namespace:
|
|
|
685
673
|
# the other maps add the implicit default libraries and the imports of
|
|
686
674
|
# resource files.
|
|
687
675
|
all_entries: Dict[str, LibraryEntry] = {
|
|
688
|
-
|
|
676
|
+
library_entry_key(entry): entry
|
|
689
677
|
for entry in itertools.chain(
|
|
690
678
|
resolved.libraries.values(),
|
|
691
679
|
resolved.resources.values(),
|
|
@@ -1458,7 +1458,10 @@ class NamespaceAnalyzer(Visitor):
|
|
|
1458
1458
|
ignore_errors=True,
|
|
1459
1459
|
)
|
|
1460
1460
|
|
|
1461
|
-
if
|
|
1461
|
+
if (
|
|
1462
|
+
not matcher.is_assign(allow_assign_mark=True, allow_nested=True, allow_items=True)
|
|
1463
|
+
or matcher.name is None
|
|
1464
|
+
):
|
|
1462
1465
|
return
|
|
1463
1466
|
|
|
1464
1467
|
if RF_VERSION >= (7, 0) and contains_variable(matcher.base, "$@&%"):
|
|
@@ -1496,6 +1499,19 @@ class NamespaceAnalyzer(Visitor):
|
|
|
1496
1499
|
stripped_name_token = strip_variable_token(assign_token, matcher=matcher, parse_type=True)
|
|
1497
1500
|
|
|
1498
1501
|
if matcher.items:
|
|
1502
|
+
# Robot Framework resolves the variables in the index; the search without types keeps a type
|
|
1503
|
+
# in the base, so the index starts after it
|
|
1504
|
+
raw = search_variable(matcher.string, ignore_errors=True)
|
|
1505
|
+
items_start = raw.start + len(raw.identifier) + len(raw.base) + 2
|
|
1506
|
+
items_token = Token(
|
|
1507
|
+
Token.ARGUMENT,
|
|
1508
|
+
matcher.string[items_start : raw.end],
|
|
1509
|
+
assign_token.lineno,
|
|
1510
|
+
assign_token.col_offset + items_start,
|
|
1511
|
+
)
|
|
1512
|
+
for var_token, var in self._iter_variables_from_token(items_token):
|
|
1513
|
+
self._handle_find_variable_result(var_token, var)
|
|
1514
|
+
|
|
1499
1515
|
existing_var = self._find_variable(name)
|
|
1500
1516
|
if existing_var is None:
|
|
1501
1517
|
self._handle_find_variable_result(
|
|
@@ -1611,18 +1627,21 @@ class NamespaceAnalyzer(Visitor):
|
|
|
1611
1627
|
if not matcher.is_scalar_assign():
|
|
1612
1628
|
return
|
|
1613
1629
|
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1630
|
+
existing_var = next(
|
|
1631
|
+
(
|
|
1632
|
+
v
|
|
1633
|
+
for k, v in self._variables.items()
|
|
1634
|
+
if k == matcher
|
|
1635
|
+
and v.type in [VariableDefinitionType.ARGUMENT, VariableDefinitionType.LOCAL_VARIABLE]
|
|
1636
|
+
),
|
|
1637
|
+
None,
|
|
1638
|
+
)
|
|
1639
|
+
if existing_var is not None:
|
|
1640
|
+
# EXCEPT AS sets the existing variable again, like a FOR variable of that name
|
|
1641
|
+
self._variable_references[existing_var].add(
|
|
1642
|
+
Location(self._document_uri, range_from_token(strip_variable_token(variable_token)))
|
|
1623
1643
|
)
|
|
1624
|
-
|
|
1625
|
-
):
|
|
1644
|
+
else:
|
|
1626
1645
|
var_def = LocalVariableDefinition(
|
|
1627
1646
|
name=variable_token.value,
|
|
1628
1647
|
name_token=strip_variable_token(variable_token),
|
|
@@ -405,6 +405,8 @@ class SemanticAnalyzer(Visitor):
|
|
|
405
405
|
)
|
|
406
406
|
self._logger.exception(e)
|
|
407
407
|
|
|
408
|
+
self._mark_argument_uses()
|
|
409
|
+
|
|
408
410
|
# Build indexes
|
|
409
411
|
self._semantic_model.build_index()
|
|
410
412
|
|
|
@@ -423,6 +425,38 @@ class SemanticAnalyzer(Visitor):
|
|
|
423
425
|
semantic_model=self._semantic_model,
|
|
424
426
|
)
|
|
425
427
|
|
|
428
|
+
def _mark_argument_uses(self) -> None:
|
|
429
|
+
"""Mark every variable token whose name the analysis resolved to an argument from `[Arguments]`, the same
|
|
430
|
+
resolution Find References and Rename use; embedded arguments stay unmarked."""
|
|
431
|
+
starts = {
|
|
432
|
+
(location.range.start.line, location.range.start.character)
|
|
433
|
+
for var, locations in self._variable_references.items()
|
|
434
|
+
if isinstance(var, ArgumentDefinition) and not isinstance(var, EmbeddedArgumentDefinition)
|
|
435
|
+
for location in locations
|
|
436
|
+
}
|
|
437
|
+
if starts:
|
|
438
|
+
for stmt in self._semantic_model.statements:
|
|
439
|
+
self._mark_argument_uses_in_statement(stmt, starts)
|
|
440
|
+
|
|
441
|
+
def _mark_argument_uses_in_statement(self, stmt: SemanticStatement, starts: Set[Tuple[int, int]]) -> None:
|
|
442
|
+
for token in stmt.tokens:
|
|
443
|
+
self._mark_argument_uses_in_token(token, starts)
|
|
444
|
+
# the inner calls of a Run Keyword variant have tokens of their own
|
|
445
|
+
if isinstance(stmt, RunKeywordCallStatement):
|
|
446
|
+
for inner in stmt.inner_calls:
|
|
447
|
+
self._mark_argument_uses_in_statement(inner, starts)
|
|
448
|
+
|
|
449
|
+
def _mark_argument_uses_in_token(self, token: SemanticToken, starts: Set[Tuple[int, int]]) -> None:
|
|
450
|
+
for sub in token.sub_tokens or ():
|
|
451
|
+
self._mark_argument_uses_in_token(sub, starts)
|
|
452
|
+
# a reference covers the name of the variable, its base
|
|
453
|
+
if (
|
|
454
|
+
token.kind is TokenKind.VARIABLE
|
|
455
|
+
and sub.kind is TokenKind.VARIABLE_BASE
|
|
456
|
+
and (sub.range.start.line, sub.range.start.character) in starts
|
|
457
|
+
):
|
|
458
|
+
token.modifiers = (token.modifiers or frozenset()) | {TokenModifier.ARGUMENT}
|
|
459
|
+
|
|
426
460
|
# --- Semantic Model helpers ---
|
|
427
461
|
|
|
428
462
|
# Statement kinds that act as the block-header for control-flow blocks.
|
|
@@ -3176,7 +3210,10 @@ class SemanticAnalyzer(Visitor):
|
|
|
3176
3210
|
ignore_errors=True,
|
|
3177
3211
|
)
|
|
3178
3212
|
|
|
3179
|
-
if
|
|
3213
|
+
if (
|
|
3214
|
+
not matcher.is_assign(allow_assign_mark=True, allow_nested=True, allow_items=True)
|
|
3215
|
+
or matcher.name is None
|
|
3216
|
+
):
|
|
3180
3217
|
return
|
|
3181
3218
|
|
|
3182
3219
|
if RF_VERSION >= (7, 0) and contains_variable(matcher.base, "$@&%"):
|
|
@@ -3220,6 +3257,19 @@ class SemanticAnalyzer(Visitor):
|
|
|
3220
3257
|
stripped_name_token = strip_variable_token(assign_token, matcher=matcher, parse_type=True)
|
|
3221
3258
|
|
|
3222
3259
|
if matcher.items:
|
|
3260
|
+
# Robot Framework resolves the variables in the index; the search without types keeps a type
|
|
3261
|
+
# in the base, so the index starts after it
|
|
3262
|
+
raw = search_variable(matcher.string, ignore_errors=True)
|
|
3263
|
+
items_start = raw.start + len(raw.identifier) + len(raw.base) + 2
|
|
3264
|
+
items_token = Token(
|
|
3265
|
+
Token.ARGUMENT,
|
|
3266
|
+
matcher.string[items_start : raw.end],
|
|
3267
|
+
assign_token.lineno,
|
|
3268
|
+
assign_token.col_offset + items_start,
|
|
3269
|
+
)
|
|
3270
|
+
for var_token, var in self._iter_variables_from_token(items_token):
|
|
3271
|
+
self._handle_find_variable_result(var_token, var)
|
|
3272
|
+
|
|
3223
3273
|
existing_var = self._find_variable(name)
|
|
3224
3274
|
if existing_var is None:
|
|
3225
3275
|
self._handle_find_variable_result(
|
|
@@ -3468,18 +3518,21 @@ class SemanticAnalyzer(Visitor):
|
|
|
3468
3518
|
if not matcher.is_scalar_assign():
|
|
3469
3519
|
return
|
|
3470
3520
|
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3521
|
+
existing_var = next(
|
|
3522
|
+
(
|
|
3523
|
+
v
|
|
3524
|
+
for k, v in self._variables.items()
|
|
3525
|
+
if k == matcher
|
|
3526
|
+
and v.type in [VariableDefinitionType.ARGUMENT, VariableDefinitionType.LOCAL_VARIABLE]
|
|
3527
|
+
),
|
|
3528
|
+
None,
|
|
3529
|
+
)
|
|
3530
|
+
if existing_var is not None:
|
|
3531
|
+
# EXCEPT AS sets the existing variable again, like a FOR variable of that name
|
|
3532
|
+
self._variable_references[existing_var].add(
|
|
3533
|
+
Location(self._document_uri, range_from_token(strip_variable_token(variable_token)))
|
|
3480
3534
|
)
|
|
3481
|
-
|
|
3482
|
-
):
|
|
3535
|
+
else:
|
|
3483
3536
|
var_def = LocalVariableDefinition(
|
|
3484
3537
|
name=variable_token.value,
|
|
3485
3538
|
name_token=strip_variable_token(variable_token),
|
|
@@ -194,6 +194,7 @@ class TokenModifier(Enum):
|
|
|
194
194
|
EMBEDDED = "embedded"
|
|
195
195
|
DECLARATION = "declaration"
|
|
196
196
|
DOCUMENTATION = "documentation"
|
|
197
|
+
ARGUMENT = "argument" # a variable that names an argument from the keyword's [Arguments], not an embedded one
|
|
197
198
|
|
|
198
199
|
|
|
199
200
|
class ForFlavor(Enum):
|
|
@@ -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
|
|
9
|
-
are live objects that cannot survive
|
|
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.
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
43
|
-
|
|
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
|
-
|
|
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 +0,0 @@
|
|
|
1
|
-
__version__ = "2.8.0-dev.548"
|
|
File without changes
|
|
File without changes
|
{robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/__init__.py
RENAMED
|
File without changes
|
{robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/config/__init__.py
RENAMED
|
File without changes
|
{robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/config/loader.py
RENAMED
|
File without changes
|
{robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/config/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/__init__.py
RENAMED
|
File without changes
|
{robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/ast.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/match.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/stubs.py
RENAMED
|
File without changes
|
{robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/variables.py
RENAMED
|
File without changes
|
{robotcode_robot-2.8.0.dev548 → robotcode_robot-2.8.0.dev670}/src/robotcode/robot/utils/visitor.py
RENAMED
|
File without changes
|