diff --git a/src/rule/scope.rs b/src/rule/scope.rs index f469898..d3947bd 100644 --- a/src/rule/scope.rs +++ b/src/rule/scope.rs @@ -15,6 +15,10 @@ pub struct Scope { /// Options represents the options of the rule. /// If the option is empty, it means that no scope is allowed. options: Vec, + + /// Optional scope. + /// If true, even if the scope is not present, it is allowed. + optional: bool, } /// Scope represents the scope rule. @@ -37,7 +41,7 @@ impl Rule for Scope { fn validate(&self, message: &Message) -> Option { match &message.scope { None => { - if self.options.is_empty() { + if self.options.is_empty() || self.optional { return None; } } @@ -64,6 +68,7 @@ impl Default for Scope { fn default() -> Self { Self { level: Some(Self::LEVEL), + optional: false, options: vec![], } } @@ -235,5 +240,54 @@ mod tests { "scope invalid is not allowed. Only [\"api\", \"web\"] are allowed".to_string() ); } + + #[test] + fn test_optional_scope_with_non_empty_scope() { + let rule = Scope { + options: vec!["api".to_string(), "web".to_string()], + optional: true, + ..Default::default() + }; + + let message = Message { + body: None, + description: None, + footers: None, + r#type: Some("feat".to_string()), + raw: "feat(invalid): broadcast $destroy event on scope destruction".to_string(), + scope: Some("invalid".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, + "scope invalid is not allowed. Only [\"api\", \"web\"] are allowed".to_string() + ); + } + + #[test] + fn test_optional_scope_with_empty_scope() { + let rule = Scope { + options: vec!["api".to_string(), "web".to_string()], + optional: true, + ..Default::default() + }; + + let message = Message { + body: None, + description: None, + footers: None, + r#type: Some("feat".to_string()), + raw: "feat: broadcast $destroy event on scope destruction".to_string(), + scope: None, + subject: None, + }; + + let violation = rule.validate(&message); + assert!(violation.is_none()); + } } } diff --git a/web/src/content/docs/rules/scope.md b/web/src/content/docs/rules/scope.md index 0ff1301..d11af2a 100644 --- a/web/src/content/docs/rules/scope.md +++ b/web/src/content/docs/rules/scope.md @@ -3,7 +3,9 @@ title: Scope description: Allowlist for scopes --- -* Default: `ignore` +* Default: + * Level: `ignore` + * Optional: `false` In this example, we assumed that you have a project with the following scopes: @@ -42,6 +44,19 @@ rules: - web ``` +### Optional scopes `api` and `web` + +```yaml +rules: + scope: + level: error + optional: true + options: + - api +``` + +With this configuration, `feat(api): xxx` and `feat: xxx` are valid commits. + ### Disallow all scopes ```yaml