Coverage for src / python_commitlint / rules / base.py: 100%
13 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-04-28 02:54 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-04-28 02:54 +0000
1"""Abstract base class for built-in commitlint rules."""
3from abc import ABC, abstractmethod
5from python_commitlint.core.models import (
6 CommitMessage,
7 RuleConfig,
8 ValidationError,
9)
10from python_commitlint.core.protocols import RuleProtocol
13def config_value_or[T](config: RuleConfig, default: T) -> T:
14 """Return ``config.value`` if it is not ``None``, otherwise ``default``.
16 Avoids the falsy-zero bug of ``config.value or default``: a configured
17 ``value: 0`` is preserved, not silently replaced by the default.
19 Note:
20 ``RuleConfig.value`` is typed ``Any``, so the ``T`` return type is
21 nominally a lie when the YAML stores a value whose Python type
22 doesn't match ``default``. Callers are expected to validate the
23 shape of ``config.value`` themselves (or accept downstream
24 ``TypeError`` if the YAML is malformed).
25 """
26 return config.value if config.value is not None else default
29class BaseRule(RuleProtocol, ABC):
30 """Abstract base for all built-in commitlint rules.
32 Inheriting from :class:`RuleProtocol` couples the ABC and the structural
33 Protocol so they cannot drift independently — adding a method to
34 ``RuleProtocol`` immediately makes ``BaseRule`` (and every subclass)
35 fail to instantiate.
37 Third-party rules need not inherit from ``BaseRule``. Any class that
38 structurally implements :class:`RuleProtocol` (a ``name`` property
39 and a ``validate`` method with the correct signature) can be passed
40 to :meth:`RuleRegistry.register`.
41 """
43 @property
44 @abstractmethod
45 def name(self) -> str:
46 """The rule's stable identifier (e.g. ``type-case``)."""
48 @abstractmethod
49 def validate(
50 self, commit: CommitMessage, config: RuleConfig
51 ) -> ValidationError | None:
52 """Apply the rule to ``commit`` under ``config``.
54 Args:
55 commit: Parsed commit to validate.
56 config: This rule's severity, condition, and optional value.
58 Returns:
59 A :class:`ValidationError` if violated, otherwise ``None``.
60 """
62 def _create_error(
63 self, config: RuleConfig, message: str
64 ) -> ValidationError:
65 """Construct a :class:`ValidationError` carrying this rule's name.
67 Helper for subclasses so they don't repeat the boilerplate of
68 threading ``self.name`` and ``config.severity`` through every
69 violation site.
70 """
71 return ValidationError(
72 rule=self.name,
73 message=message,
74 severity=config.severity,
75 )