Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions scripts/snippets/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,20 @@ class ConditionPageValidation:
tags: tuple[IfVersionTag | ElseTag, ...]
conditions: tuple[IfVersionCondition, ...]
diagnostics: tuple[Diagnostic, ...]


@dataclass(frozen=True)
class ValidatedSnippet:
tag: SnippetTag
source: (
ImmutableSourceReference
| PullRequestSnippetSource
| LocalSourceReference
)
condition: IfVersionCondition | None


@dataclass(frozen=True)
class SnippetPageValidation:
snippets: tuple[ValidatedSnippet, ...]
diagnostics: tuple[Diagnostic, ...]
96 changes: 94 additions & 2 deletions scripts/snippets/page_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,38 @@
from pathlib import Path

from .diagnostics import (
candidate_condition_diagnostics,
directive_syntax_diagnostic,
if_version_attribute_diagnostics,
if_version_structure_diagnostics,
local_source_policy_diagnostic,
snippet_attribute_diagnostics,
snippet_source_attribute_diagnostics,
snippet_source_safety_diagnostics,
)
from .model import (
ConditionPageValidation,
IfVersionTag,
SnippetPageValidation,
ValidatedSnippet,
)
from .model import ConditionPageValidation, IfVersionTag
from .registry import RepositoryRegistry
from .syntax import DirectiveSyntaxError, parse_if_version_tags
from .semantics import (
map_snippet_condition_contexts,
resolve_snippet_source_attributes,
validate_candidate_condition,
validate_snippet_basic_attributes,
)
from .syntax import (
DirectiveSyntaxError,
parse_if_version_tags,
parse_snippet_tags,
)
from .validation import (
validate_if_version_attributes,
validate_if_version_structure,
validate_local_source_policy,
validate_snippet_source_safety,
)


Expand Down Expand Up @@ -48,3 +70,73 @@ def validate_condition_page(
conditions=tuple(conditions),
diagnostics=tuple(diagnostics),
)


def validate_snippet_page(
text: str,
*,
path: Path,
registry: RepositoryRegistry,
conditions: ConditionPageValidation,
allow_local: bool = False,
) -> SnippetPageValidation:
"""Parse and validate snippets against prevalidated condition context."""

try:
tags = parse_snippet_tags(text)
except DirectiveSyntaxError as error:
return SnippetPageValidation(
snippets=(),
diagnostics=(directive_syntax_diagnostic(path, error),),
)

diagnostics = []
snippets = []
contexts = map_snippet_condition_contexts(
tags, conditions.tags, conditions.conditions
)
for context in contexts:
tag = context.snippet
tag_diagnostics = list(
snippet_attribute_diagnostics(
path, validate_snippet_basic_attributes(tag)
)
)
source_result = resolve_snippet_source_attributes(tag)
tag_diagnostics.extend(
snippet_source_attribute_diagnostics(path, source_result.issues)
)
source = source_result.source
if source is not None:
tag_diagnostics.extend(
snippet_source_safety_diagnostics(
path,
validate_snippet_source_safety(
source, span=tag.span, registry=registry
),
)
)
local_issues = validate_local_source_policy(
source, span=tag.span, allow_local=allow_local
)
tag_diagnostics.extend(
local_source_policy_diagnostic(path, issue)
for issue in local_issues
)
tag_diagnostics.extend(
candidate_condition_diagnostics(
path, validate_candidate_condition(source, context)
)
)
diagnostics.extend(tag_diagnostics)
if source is not None and not tag_diagnostics:
snippets.append(
ValidatedSnippet(
tag=tag,
source=source,
condition=context.condition,
)
)
return SnippetPageValidation(
snippets=tuple(snippets), diagnostics=tuple(diagnostics)
)
98 changes: 98 additions & 0 deletions tests/test_snippet_page_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from __future__ import annotations

from pathlib import Path

from scripts.snippets.model import SnippetPageValidation
from scripts.snippets.page_validation import (
validate_condition_page,
validate_snippet_page,
)
from scripts.snippets.registry import (
RepositoryConfig,
RepositoryRegistry,
RepositoryVisibility,
)

PATH = Path("docs-main/validator.source.mdx")
REGISTRY = RepositoryRegistry(
(
RepositoryConfig(
name="canton-network/splice",
url="https://github.com/canton-network/splice",
default_branch="main",
visibility=RepositoryVisibility.PUBLIC,
),
)
)
COMMIT = "2c941ea9e834d7602d388f3271c0f864025ea756"


def validate(
text: str, *, allow_local: bool = False
) -> SnippetPageValidation:
conditions = validate_condition_page(text, path=PATH, registry=REGISTRY)
return validate_snippet_page(
text,
path=PATH,
registry=REGISTRY,
conditions=conditions,
allow_local=allow_local,
)


def test_returns_validated_immutable_snippet() -> None:
result = validate(
f'<Snippet source="https://github.com/canton-network/splice/blob/{COMMIT}/file.yaml" '
'language="yaml" />'
)

assert result.diagnostics == ()
assert len(result.snippets) == 1
assert result.snippets[0].condition is None


def test_returns_candidate_matching_condition() -> None:
result = validate(
"""<IfVersion repository="https://github.com/canton-network/splice" containsPullRequest={6123}>
<Snippet source="https://github.com/canton-network/splice/pull/6123" path="file.yaml" language="yaml" />
</IfVersion>"""
)

assert result.diagnostics == ()
assert result.snippets[0].condition is not None


def test_reports_candidate_identity_mismatch() -> None:
result = validate(
"""<IfVersion repository="https://github.com/canton-network/splice" containsPullRequest={6123}>
<Snippet source="https://github.com/canton-network/splice/pull/6124" path="file.yaml" language="yaml" />
</IfVersion>"""
)

assert "SNIP028" in {
diagnostic.code for diagnostic in result.diagnostics
}


def test_reports_local_ref_remediation_unless_preview_enabled() -> None:
text = (
'<Snippet source="local://canton-network/splice/file.yaml" '
'language="yaml" />'
)

committed = validate(text)
preview = validate(text, allow_local=True)

assert committed.diagnostics[0].code == "SNIP007"
remediation = committed.diagnostics[0].remediation
assert remediation is not None
assert "snippets:resolve-local" in remediation
assert preview.diagnostics == ()


def test_reports_snippet_syntax_error() -> None:
result = validate('<Snippet source="one">')

assert [diagnostic.code for diagnostic in result.diagnostics] == [
"SNIP014"
]