Skip to content
Merged
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
19 changes: 19 additions & 0 deletions src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,25 @@ Link: Hello";
assert_eq!(f.get("Link"), Some(&"Hello".to_string()));
}

#[test]
fn test_footer_with_multiline_body_parse_commit_message() {
let input = "feat(cli): add dummy option

Hello, there!
I'm from Japan!

Link: Hello";
let (subject, body, footer) = parse_commit_message(input);

let mut f = HashMap::new();
f.insert("Link".to_string(), "Hello".to_string());
assert_eq!(subject, "feat(cli): add dummy option");
assert_eq!(body, Some("Hello, there!
I'm from Japan!".to_string()));
assert!(footer.is_some());
assert_eq!(f.get("Link"), Some(&"Hello".to_string()));
}

#[test]
fn test_multiple_footers_parse_commit_message() {
let input = "feat(cli): add dummy option
Expand Down
12 changes: 9 additions & 3 deletions src/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ use serde::{Deserialize, Serialize};
use self::{
body_empty::BodyEmpty, body_max_length::BodyMaxLength, description_empty::DescriptionEmpty,
description_format::DescriptionFormat, description_max_length::DescriptionMaxLength,
r#type::Type, scope::Scope, scope_empty::ScopeEmpty, scope_format::ScopeFormat,
scope_max_length::ScopeMaxLength, subject_empty::SubjectEmpty, type_empty::TypeEmpty,
type_format::TypeFormat, type_max_length::TypeMaxLength,
footers_empty::FootersEmpty, r#type::Type, scope::Scope, scope_empty::ScopeEmpty,
scope_format::ScopeFormat, scope_max_length::ScopeMaxLength, subject_empty::SubjectEmpty,
type_empty::TypeEmpty, type_format::TypeFormat, type_max_length::TypeMaxLength,
};

pub mod body_empty;
pub mod body_max_length;
pub mod description_empty;
pub mod description_format;
pub mod description_max_length;
pub mod footers_empty;
pub mod scope;
pub mod scope_empty;
pub mod scope_format;
Expand Down Expand Up @@ -50,6 +51,10 @@ pub struct Rules {
#[serde(skip_serializing_if = "Option::is_none")]
pub description_max_length: Option<DescriptionMaxLength>,

#[serde(rename = "footers-empty")]
#[serde(skip_serializing_if = "Option::is_none")]
pub footers_empty: Option<FootersEmpty>,

#[serde(rename = "scope")]
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<Scope>,
Expand Down Expand Up @@ -190,6 +195,7 @@ impl Default for Rules {
description_empty: DescriptionEmpty::default().into(),
description_format: None,
description_max_length: None,
footers_empty: None,
scope: None,
scope_empty: None,
scope_format: None,
Expand Down
95 changes: 95 additions & 0 deletions src/rule/footers_empty.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use crate::{message::Message, result::Violation, rule::Rule};
use serde::{Deserialize, Serialize};

use super::Level;

/// FootersEmpty represents the footer-empty rule.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FootersEmpty {
/// Level represents the level of the rule.
///
// Note that currently the default literal is not supported.
// See: https://github.com/serde-rs/serde/issues/368
level: Option<Level>,
}

/// FooterEmpty represents the footer-empty rule.
impl Rule for FootersEmpty {
const NAME: &'static str = "footers-empty";
const LEVEL: Level = Level::Error;

fn message(&self, _message: &Message) -> String {
"footers are empty".to_string()
}

fn validate(&self, message: &Message) -> Option<Violation> {
if message.footers.is_none() {
return Some(Violation {
level: self.level.unwrap_or(Self::LEVEL),
message: self.message(message),
});
}

None
}
}

/// Default implementation of FooterEmpty.
impl Default for FootersEmpty {
fn default() -> Self {
Self {
level: Some(Self::LEVEL),
}
}
}

#[cfg(test)]
mod tests {
use std::collections::HashMap;

use super::*;

#[test]
fn test_non_empty_footer() {
let rule = FootersEmpty::default();

let mut f = HashMap::new();
f.insert("Link".to_string(), "hello".to_string());

let message = Message {
body: Some("Hello world".to_string()),
description: Some("broadcast $destroy event on scope destruction".to_string()),
footers: Some(f),
r#type: Some("feat".to_string()),
raw: "feat(scope): broadcast $destroy event on scope destruction

Hello world

Link: hello"
.to_string(),
scope: Some("scope".to_string()),
subject: Some("feat(scope): broadcast $destroy event on scope destruction".to_string()),
};

assert!(rule.validate(&message).is_none());
}

#[test]
fn test_empty_footer() {
let rule = FootersEmpty::default();
let message = Message {
body: None,
description: None,
footers: None,
r#type: Some("feat".to_string()),
raw: "feat(scope): broadcast $destroy event on scope destruction".to_string(),
scope: Some("scope".to_string()),
subject: None,
};

let violation = rule.validate(&message);
assert!(violation.is_some());
assert_eq!(violation.clone().unwrap().level, Level::Error);
assert_eq!(violation.unwrap().message, "footers are empty".to_string());
}
Comment on lines +46 to +94
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding more test cases.

The tests cover basic cases. Consider adding tests for edge cases, such as when footers is an empty map.

#[test]
fn test_empty_footer_map() {
    let rule = FootersEmpty::default();
    let message = Message {
        body: None,
        description: None,
        footers: Some(HashMap::new()),
        r#type: Some("feat".to_string()),
        raw: "feat(scope): broadcast $destroy event on scope destruction".to_string(),
        scope: Some("scope".to_string()),
        subject: None,
    };

    let violation = rule.validate(&message);
    assert!(violation.is_some());
    assert_eq!(violation.clone().unwrap().level, Level::Error);
    assert_eq!(violation.unwrap().message, "footers are empty".to_string());
}

}
30 changes: 30 additions & 0 deletions web/src/content/docs/rules/footers-empty.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
title: Footers Empty
description: Check if the footers exists
---

* Default: `error`

## ❌ Bad

```console
feat(cli): user logout handler
```

## ✅ Good

```console
feat(cli): add new flag

Link: https://keisukeyamashita.github.io/commitlint-rs/
```

## Example

### Footers must exist

```yaml
rules:
footers-empty:
level: error
```