Files
roboco/tests/unit/conventions/test_custom_languages.py
T

85 lines
3.0 KiB
Python
Raw Normal View History

2026-06-30 08:08:35 +02:00
"""Custom-rule language scoping (#129 / #32).
A custom rule scoped to a language the validator never reports (a typo like
``pyhton``) silently never fires — the operator gets no signal that their
rule is dead. The fix surfaces each unrecognized tag as a ``warn`` finding on
the conventions file (visible in the findings feed + QA evidence, never
block-level so it cannot strand the gate). The ``tsx`` -> ``typescript``
dialect relation is intentional and stays (#32 BY-DESIGN): a ``typescript``
-rule must fire on a ``.tsx`` file.
"""
from __future__ import annotations
from roboco.conventions import runner
from roboco.conventions.custom import (
_DIALECT_OF,
_KNOWN_LANGUAGES,
_rule_applies,
unrecognized_rule_languages,
)
from roboco.conventions.runner import run
from roboco.foundation.policy.conventions.models import (
ConventionsStandard,
CustomRule,
)
def _rule(languages: list[str], *, rid: str = "r1") -> CustomRule:
return CustomRule(
id=rid,
pattern="TODO",
languages=languages,
level="warn",
message="m",
)
def test_known_languages_cover_the_validator_tags() -> None:
# The runner tags .py -> python, .ts -> typescript, .tsx -> tsx.
assert frozenset({"python", "typescript", "tsx"}) == _KNOWN_LANGUAGES
def test_unrecognized_rule_languages_flags_a_typo() -> None:
assert unrecognized_rule_languages(_rule(["pyhton"])) == ["pyhton"]
def test_unrecognized_rule_languages_clean_for_known_and_unscoped() -> None:
assert unrecognized_rule_languages(_rule(["python"])) == []
assert unrecognized_rule_languages(_rule(["typescript", "tsx"])) == []
# An unscoped rule applies to everything — never unrecognized.
assert unrecognized_rule_languages(_rule([])) == []
def test_run_emits_warn_finding_for_unknown_language_tag() -> None:
standard = ConventionsStandard(custom=[_rule(["pyhton"], rid="no-todos")])
findings = run(".", [], standard)
scope = [f for f in findings if f.rule == "custom_language_scope"]
assert len(scope) == 1
assert scope[0].level == "warn"
assert "no-todos" in scope[0].message
assert "pyhton" in scope[0].message
assert scope[0].file == ".roboco/conventions.yml"
def test_run_clean_when_custom_rules_use_known_languages() -> None:
standard = ConventionsStandard(
custom=[_rule(["typescript"], rid="r"), _rule([], rid="unscoped")]
)
findings = run(".", [], standard)
assert not [f for f in findings if f.rule == "custom_language_scope"]
def test_tsx_is_a_typescript_dialect_by_design() -> None:
"""#32: the one-directional tsx -> typescript relation is intentional."""
assert _DIALECT_OF == {"tsx": "typescript"}
# A typescript-scoped rule applies to a tsx file; a tsx-only rule does not
# apply to plain typescript.
assert _rule_applies(["typescript"], "tsx") is True
assert _rule_applies(["tsx"], "typescript") is False
def test_runner_module_exposes_run() -> None:
# Sanity: the once-per-run validation hook lives on ``run``.
assert callable(runner.run)