Coverage for src / python_commitlint / rules / type_rules.py: 100%

71 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-04-28 02:54 +0000

1"""Rules that validate the commit ``type`` (the token before the colon).""" 

2 

3from python_commitlint.core.enums import CaseType, RuleCondition 

4from python_commitlint.core.exceptions import ConfigurationError 

5from python_commitlint.core.models import ( 

6 CommitMessage, 

7 RuleConfig, 

8 ValidationError, 

9) 

10from python_commitlint.rules.base import BaseRule, config_value_or 

11from python_commitlint.rules.case_validators import CaseValidator 

12 

13 

14class TypeEmptyRule(BaseRule): 

15 """Require or forbid an empty type. Rule name: ``type-empty``.""" 

16 

17 @property 

18 def name(self) -> str: 

19 return "type-empty" 

20 

21 def validate( 

22 self, commit: CommitMessage, config: RuleConfig 

23 ) -> ValidationError | None: 

24 is_empty = not commit.type 

25 should_be_empty = config.condition == RuleCondition.ALWAYS 

26 

27 if is_empty != should_be_empty: 

28 msg = ( 

29 "type may not be empty" 

30 if not should_be_empty 

31 else "type must be empty" 

32 ) 

33 return self._create_error(config, msg) 

34 return None 

35 

36 

37class TypeCaseRule(BaseRule): 

38 """Enforce a single case style on the type. Rule name: ``type-case``.""" 

39 

40 @property 

41 def name(self) -> str: 

42 return "type-case" 

43 

44 def validate( 

45 self, commit: CommitMessage, config: RuleConfig 

46 ) -> ValidationError | None: 

47 if not commit.type or config.value is None: 

48 return None 

49 

50 case_type = CaseType(config.value) 

51 is_valid = CaseValidator.validate(commit.type, case_type) 

52 should_match = config.condition == RuleCondition.ALWAYS 

53 

54 if is_valid != should_match: 

55 return self._create_error(config, f"type must be {config.value}") 

56 return None 

57 

58 

59class TypeEnumRule(BaseRule): 

60 """Restrict the type to (or away from) an allowed list. Rule name: ``type-enum``.""" 

61 

62 @property 

63 def name(self) -> str: 

64 return "type-enum" 

65 

66 def validate( 

67 self, commit: CommitMessage, config: RuleConfig 

68 ) -> ValidationError | None: 

69 if not commit.type: 

70 return None 

71 

72 if config.value is None: 

73 raise ConfigurationError( 

74 f"{self.name}: requires a 'value' (list of allowed types)" 

75 ) 

76 allowed_types = config.value 

77 is_in_enum = commit.type in allowed_types 

78 should_be_in_enum = config.condition == RuleCondition.ALWAYS 

79 

80 if is_in_enum != should_be_in_enum: 

81 msg = ( 

82 f"type must be one of {allowed_types}" 

83 if should_be_in_enum 

84 else f"type must not be one of {allowed_types}" 

85 ) 

86 return self._create_error(config, msg) 

87 return None 

88 

89 

90class TypeMinLengthRule(BaseRule): 

91 """Enforce a minimum character length on the type. Rule name: ``type-min-length``.""" 

92 

93 @property 

94 def name(self) -> str: 

95 return "type-min-length" 

96 

97 def validate( 

98 self, commit: CommitMessage, config: RuleConfig 

99 ) -> ValidationError | None: 

100 if not commit.type: 

101 return None 

102 

103 min_length = config_value_or(config, 0) 

104 is_valid = len(commit.type) >= min_length 

105 should_be_valid = config.condition == RuleCondition.ALWAYS 

106 

107 if is_valid != should_be_valid: 

108 return self._create_error( 

109 config, f"type must be at least {min_length} characters" 

110 ) 

111 return None 

112 

113 

114class TypeMaxLengthRule(BaseRule): 

115 """Enforce a maximum character length on the type. Rule name: ``type-max-length``.""" 

116 

117 @property 

118 def name(self) -> str: 

119 return "type-max-length" 

120 

121 def validate( 

122 self, commit: CommitMessage, config: RuleConfig 

123 ) -> ValidationError | None: 

124 if not commit.type: 

125 return None 

126 

127 max_length = config_value_or(config, float("inf")) 

128 is_valid = len(commit.type) <= max_length 

129 should_be_valid = config.condition == RuleCondition.ALWAYS 

130 

131 if is_valid != should_be_valid: 

132 return self._create_error( 

133 config, f"type must be at most {max_length} characters" 

134 ) 

135 return None