Skip to content

Commit f85ce98

Browse files
feat(cli): add optional field for scope (#368)
* feat(cli): add optional field for scope Signed-off-by: KeisukeYamashita <[email protected]> * feat(web): add optional scopes example Signed-off-by: KeisukeYamashita <[email protected]> --------- Signed-off-by: KeisukeYamashita <[email protected]>
1 parent 5ba81cf commit f85ce98

File tree

2 files changed

+71
-2
lines changed

2 files changed

+71
-2
lines changed

src/rule/scope.rs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ pub struct Scope {
1515
/// Options represents the options of the rule.
1616
/// If the option is empty, it means that no scope is allowed.
1717
options: Vec<String>,
18+
19+
/// Optional scope.
20+
/// If true, even if the scope is not present, it is allowed.
21+
optional: bool,
1822
}
1923

2024
/// Scope represents the scope rule.
@@ -37,7 +41,7 @@ impl Rule for Scope {
3741
fn validate(&self, message: &Message) -> Option<Violation> {
3842
match &message.scope {
3943
None => {
40-
if self.options.is_empty() {
44+
if self.options.is_empty() || self.optional {
4145
return None;
4246
}
4347
}
@@ -64,6 +68,7 @@ impl Default for Scope {
6468
fn default() -> Self {
6569
Self {
6670
level: Some(Self::LEVEL),
71+
optional: false,
6772
options: vec![],
6873
}
6974
}
@@ -235,5 +240,54 @@ mod tests {
235240
"scope invalid is not allowed. Only [\"api\", \"web\"] are allowed".to_string()
236241
);
237242
}
243+
244+
#[test]
245+
fn test_optional_scope_with_non_empty_scope() {
246+
let rule = Scope {
247+
options: vec!["api".to_string(), "web".to_string()],
248+
optional: true,
249+
..Default::default()
250+
};
251+
252+
let message = Message {
253+
body: None,
254+
description: None,
255+
footers: None,
256+
r#type: Some("feat".to_string()),
257+
raw: "feat(invalid): broadcast $destroy event on scope destruction".to_string(),
258+
scope: Some("invalid".to_string()),
259+
subject: None,
260+
};
261+
262+
let violation = rule.validate(&message);
263+
assert!(violation.is_some());
264+
assert_eq!(violation.clone().unwrap().level, Level::Error);
265+
assert_eq!(
266+
violation.unwrap().message,
267+
"scope invalid is not allowed. Only [\"api\", \"web\"] are allowed".to_string()
268+
);
269+
}
270+
271+
#[test]
272+
fn test_optional_scope_with_empty_scope() {
273+
let rule = Scope {
274+
options: vec!["api".to_string(), "web".to_string()],
275+
optional: true,
276+
..Default::default()
277+
};
278+
279+
let message = Message {
280+
body: None,
281+
description: None,
282+
footers: None,
283+
r#type: Some("feat".to_string()),
284+
raw: "feat: broadcast $destroy event on scope destruction".to_string(),
285+
scope: None,
286+
subject: None,
287+
};
288+
289+
let violation = rule.validate(&message);
290+
assert!(violation.is_none());
291+
}
238292
}
239293
}

web/src/content/docs/rules/scope.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ title: Scope
33
description: Allowlist for scopes
44
---
55

6-
* Default: `ignore`
6+
* Default:
7+
* Level: `ignore`
8+
* Optional: `false`
79

810
In this example, we assumed that you have a project with the following scopes:
911

@@ -42,6 +44,19 @@ rules:
4244
- web
4345
```
4446
47+
### Optional scopes `api` and `web`
48+
49+
```yaml
50+
rules:
51+
scope:
52+
level: error
53+
optional: true
54+
options:
55+
- api
56+
```
57+
58+
With this configuration, `feat(api): xxx` and `feat: xxx` are valid commits.
59+
4560
### Disallow all scopes
4661

4762
```yaml

0 commit comments

Comments
 (0)