Skip to content

Commit dc4e96b

Browse files
feat(cli): add footers empty rule (#327)
* feat(cli): add footers empty rule Signed-off-by: KeisukeYamashita <[email protected]> * feat(web): add footers-empty rule page Signed-off-by: KeisukeYamashita <[email protected]> --------- Signed-off-by: KeisukeYamashita <[email protected]>
1 parent f7e3f93 commit dc4e96b

File tree

4 files changed

+153
-3
lines changed

4 files changed

+153
-3
lines changed

src/git.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,25 @@ Link: Hello";
175175
assert_eq!(f.get("Link"), Some(&"Hello".to_string()));
176176
}
177177

178+
#[test]
179+
fn test_footer_with_multiline_body_parse_commit_message() {
180+
let input = "feat(cli): add dummy option
181+
182+
Hello, there!
183+
I'm from Japan!
184+
185+
Link: Hello";
186+
let (subject, body, footer) = parse_commit_message(input);
187+
188+
let mut f = HashMap::new();
189+
f.insert("Link".to_string(), "Hello".to_string());
190+
assert_eq!(subject, "feat(cli): add dummy option");
191+
assert_eq!(body, Some("Hello, there!
192+
I'm from Japan!".to_string()));
193+
assert!(footer.is_some());
194+
assert_eq!(f.get("Link"), Some(&"Hello".to_string()));
195+
}
196+
178197
#[test]
179198
fn test_multiple_footers_parse_commit_message() {
180199
let input = "feat(cli): add dummy option

src/rule.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,17 @@ use serde::{Deserialize, Serialize};
66
use self::{
77
body_empty::BodyEmpty, body_max_length::BodyMaxLength, description_empty::DescriptionEmpty,
88
description_format::DescriptionFormat, description_max_length::DescriptionMaxLength,
9-
r#type::Type, scope::Scope, scope_empty::ScopeEmpty, scope_format::ScopeFormat,
10-
scope_max_length::ScopeMaxLength, subject_empty::SubjectEmpty, type_empty::TypeEmpty,
11-
type_format::TypeFormat, type_max_length::TypeMaxLength,
9+
footers_empty::FootersEmpty, r#type::Type, scope::Scope, scope_empty::ScopeEmpty,
10+
scope_format::ScopeFormat, scope_max_length::ScopeMaxLength, subject_empty::SubjectEmpty,
11+
type_empty::TypeEmpty, type_format::TypeFormat, type_max_length::TypeMaxLength,
1212
};
1313

1414
pub mod body_empty;
1515
pub mod body_max_length;
1616
pub mod description_empty;
1717
pub mod description_format;
1818
pub mod description_max_length;
19+
pub mod footers_empty;
1920
pub mod scope;
2021
pub mod scope_empty;
2122
pub mod scope_format;
@@ -50,6 +51,10 @@ pub struct Rules {
5051
#[serde(skip_serializing_if = "Option::is_none")]
5152
pub description_max_length: Option<DescriptionMaxLength>,
5253

54+
#[serde(rename = "footers-empty")]
55+
#[serde(skip_serializing_if = "Option::is_none")]
56+
pub footers_empty: Option<FootersEmpty>,
57+
5358
#[serde(rename = "scope")]
5459
#[serde(skip_serializing_if = "Option::is_none")]
5560
pub scope: Option<Scope>,
@@ -190,6 +195,7 @@ impl Default for Rules {
190195
description_empty: DescriptionEmpty::default().into(),
191196
description_format: None,
192197
description_max_length: None,
198+
footers_empty: None,
193199
scope: None,
194200
scope_empty: None,
195201
scope_format: None,

src/rule/footers_empty.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use crate::{message::Message, result::Violation, rule::Rule};
2+
use serde::{Deserialize, Serialize};
3+
4+
use super::Level;
5+
6+
/// FootersEmpty represents the footer-empty rule.
7+
#[derive(Clone, Debug, Deserialize, Serialize)]
8+
pub struct FootersEmpty {
9+
/// Level represents the level of the rule.
10+
///
11+
// Note that currently the default literal is not supported.
12+
// See: https://github.com/serde-rs/serde/issues/368
13+
level: Option<Level>,
14+
}
15+
16+
/// FooterEmpty represents the footer-empty rule.
17+
impl Rule for FootersEmpty {
18+
const NAME: &'static str = "footers-empty";
19+
const LEVEL: Level = Level::Error;
20+
21+
fn message(&self, _message: &Message) -> String {
22+
"footers are empty".to_string()
23+
}
24+
25+
fn validate(&self, message: &Message) -> Option<Violation> {
26+
if message.footers.is_none() {
27+
return Some(Violation {
28+
level: self.level.unwrap_or(Self::LEVEL),
29+
message: self.message(message),
30+
});
31+
}
32+
33+
None
34+
}
35+
}
36+
37+
/// Default implementation of FooterEmpty.
38+
impl Default for FootersEmpty {
39+
fn default() -> Self {
40+
Self {
41+
level: Some(Self::LEVEL),
42+
}
43+
}
44+
}
45+
46+
#[cfg(test)]
47+
mod tests {
48+
use std::collections::HashMap;
49+
50+
use super::*;
51+
52+
#[test]
53+
fn test_non_empty_footer() {
54+
let rule = FootersEmpty::default();
55+
56+
let mut f = HashMap::new();
57+
f.insert("Link".to_string(), "hello".to_string());
58+
59+
let message = Message {
60+
body: Some("Hello world".to_string()),
61+
description: Some("broadcast $destroy event on scope destruction".to_string()),
62+
footers: Some(f),
63+
r#type: Some("feat".to_string()),
64+
raw: "feat(scope): broadcast $destroy event on scope destruction
65+
66+
Hello world
67+
68+
Link: hello"
69+
.to_string(),
70+
scope: Some("scope".to_string()),
71+
subject: Some("feat(scope): broadcast $destroy event on scope destruction".to_string()),
72+
};
73+
74+
assert!(rule.validate(&message).is_none());
75+
}
76+
77+
#[test]
78+
fn test_empty_footer() {
79+
let rule = FootersEmpty::default();
80+
let message = Message {
81+
body: None,
82+
description: None,
83+
footers: None,
84+
r#type: Some("feat".to_string()),
85+
raw: "feat(scope): broadcast $destroy event on scope destruction".to_string(),
86+
scope: Some("scope".to_string()),
87+
subject: None,
88+
};
89+
90+
let violation = rule.validate(&message);
91+
assert!(violation.is_some());
92+
assert_eq!(violation.clone().unwrap().level, Level::Error);
93+
assert_eq!(violation.unwrap().message, "footers are empty".to_string());
94+
}
95+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
title: Footers Empty
3+
description: Check if the footers exists
4+
---
5+
6+
* Default: `error`
7+
8+
## ❌ Bad
9+
10+
```console
11+
feat(cli): user logout handler
12+
```
13+
14+
## ✅ Good
15+
16+
```console
17+
feat(cli): add new flag
18+
19+
Link: https://keisukeyamashita.github.io/commitlint-rs/
20+
```
21+
22+
## Example
23+
24+
### Footers must exist
25+
26+
```yaml
27+
rules:
28+
footers-empty:
29+
level: error
30+
```

0 commit comments

Comments
 (0)