diff --git a/scripts/snippets/model.py b/scripts/snippets/model.py
index c720c458f..9eb1b6984 100644
--- a/scripts/snippets/model.py
+++ b/scripts/snippets/model.py
@@ -173,3 +173,9 @@ class SnippetAttributeIssue:
rule: SnippetAttributeRule
span: Span
message: str
+
+
+@dataclass(frozen=True)
+class SnippetConditionContext:
+ snippet: SnippetTag
+ condition: IfVersionCondition | None
diff --git a/scripts/snippets/semantics.py b/scripts/snippets/semantics.py
index 1539bdddf..102d577d4 100644
--- a/scripts/snippets/semantics.py
+++ b/scripts/snippets/semantics.py
@@ -1,12 +1,16 @@
from __future__ import annotations
from .model import (
+ ElseTag,
+ IfVersionCondition,
+ IfVersionTag,
ImmutableSourceReference,
LocalSourceReference,
PullRequestSnippetSource,
PullRequestSourceReference,
SnippetAttributeIssue,
SnippetAttributeRule,
+ SnippetConditionContext,
SnippetSourceAttributeIssue,
SnippetSourceAttributeRule,
SnippetSourceAttributeValidation,
@@ -136,3 +140,35 @@ def validate_snippet_basic_attributes(
)
)
return tuple(issues)
+
+
+def map_snippet_condition_contexts(
+ snippets: tuple[SnippetTag, ...],
+ conditional_tags: tuple[IfVersionTag | ElseTag, ...],
+ conditions: tuple[IfVersionCondition, ...],
+) -> tuple[SnippetConditionContext, ...]:
+ """Associate snippets with their innermost enclosing IfVersion condition."""
+
+ conditions_by_start = {
+ condition.span.start: condition for condition in conditions
+ }
+ events = sorted(
+ (*snippets, *conditional_tags), key=lambda event: event.span.start
+ )
+ stack: list[IfVersionCondition | None] = []
+ contexts: list[SnippetConditionContext] = []
+ for event in events:
+ if isinstance(event, IfVersionTag):
+ if event.closing:
+ if stack:
+ stack.pop()
+ else:
+ stack.append(conditions_by_start.get(event.span.start))
+ elif isinstance(event, SnippetTag):
+ contexts.append(
+ SnippetConditionContext(
+ snippet=event,
+ condition=stack[-1] if stack else None,
+ )
+ )
+ return tuple(contexts)
diff --git a/tests/test_snippet_condition_context.py b/tests/test_snippet_condition_context.py
new file mode 100644
index 000000000..768f05c9b
--- /dev/null
+++ b/tests/test_snippet_condition_context.py
@@ -0,0 +1,75 @@
+from __future__ import annotations
+
+from scripts.snippets.model import IfVersionCondition, IfVersionTag
+from scripts.snippets.references import parse_github_repository_url
+from scripts.snippets.semantics import map_snippet_condition_contexts
+from scripts.snippets.syntax import (
+ parse_if_version_tags,
+ parse_snippet_tags,
+)
+
+
+def conditions(text: str) -> tuple[IfVersionCondition, ...]:
+ result: list[IfVersionCondition] = []
+ for tag in parse_if_version_tags(text):
+ if not isinstance(tag, IfVersionTag) or tag.closing:
+ continue
+ repository_value = tag.attribute("repository")
+ candidate = tag.attribute("containsPullRequest")
+ assert isinstance(repository_value, str)
+ repository = parse_github_repository_url(repository_value)
+ assert repository is not None
+ assert isinstance(candidate, int)
+ result.append(
+ IfVersionCondition(
+ repository=repository,
+ contains_pull_request=candidate,
+ span=tag.span,
+ )
+ )
+ return tuple(result)
+
+
+def contexts(text: str):
+ return map_snippet_condition_contexts(
+ parse_snippet_tags(text),
+ parse_if_version_tags(text),
+ conditions(text),
+ )
+
+
+def test_maps_unconditional_snippet_to_no_condition() -> None:
+ result = contexts('')
+
+ assert len(result) == 1
+ assert result[0].condition is None
+
+
+def test_maps_both_branches_to_enclosing_condition() -> None:
+ result = contexts(
+ """
+
+
+
+
+"""
+ )
+
+ assert [context.condition.contains_pull_request for context in result] == [
+ 12,
+ 12,
+ ]
+
+
+def test_maps_nested_snippet_to_innermost_condition() -> None:
+ result = contexts(
+ """
+
+
+
+"""
+ )
+
+ assert result[0].condition is not None
+ assert result[0].condition.repository == "example/inner"
+ assert result[0].condition.contains_pull_request == 34