Skip to content

Conversation

xieyongbin
Copy link
Contributor

@xieyongbin xieyongbin commented Aug 20, 2025

Why

I've tested the tool and plan to implement it in our company.
However there are some logical errors which blocked us to use it.

This commit to fix (type|scope|body)_max-length rules report incorrect message.

fix #440
fix #441
fix #442

Signed-off-by: xieyongbin xieyongbin@126.com

Summary by CodeRabbit

  • Refactor
    • Simplified internal validation for message body, scope, and type length checks to reduce nesting and improve consistency.
    • No change to behavior, outputs, or public interfaces—users will see identical results.
    • Improves code maintainability and readiness for future enhancements.
    • No user action required.

This commit to fix follow rules report error message:
- type
  ```shell
  $> echo -n "add new flag" | commitlint
  type is longer than 4 characters
  ```

  Config rules see below:
  ```yaml
  rules:
  type-max-length:
    level: error
    length: 4
  ```

- scope
  ```shell
  $> echo -n "feat: add new flag" | commitlint
  scope is longer than 5 characters
  ```

  Config rules see below:
  ```yaml
  rules:
  scope-max-length:
    level: error
    length: 5
  ```

- body
  ```shell
  $> echo -n "feat(cli): add new flag" | commitlint
  body is longer than 72 characters
  ```

 Config rules see below
 ```yaml
 rules:
  body-empty:
    level: ignore
  body-max-length:
    level: error
    length: 72
 ```

Signed-off-by: xieyongbin <xieyongbin@126.com>
Copy link

coderabbitai bot commented Aug 20, 2025

Walkthrough

Refactors three rule validators to replace match on optional fields with if-let Some(...) guards; preserves length-check logic and violation construction; no public API changes. (49 words)

Changes

Cohort / File(s) Summary
Max-length rule refactors
cli/src/rule/body_max_length.rs, cli/src/rule/scope_max_length.rs, cli/src/rule/type_max_length.rs
Replace match on optional commit fields with if let Some(...) guards; keep len() >= length violation logic and Violation construction; return None when field absent; no signature/API changes.

Sequence Diagram(s)

(omitted — changes are localized control-flow simplifications without new external interactions)

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Assessment against linked issues

Objective Addressed Explanation
Ensure body-max-length ignores empty body; body-empty handles emptiness (#440) Refactor preserves prior behavior; diff does not explicitly change empty-string vs missing-field handling.
Ensure scope-max-length ignores empty scope; scope-empty handles emptiness (#441) Control-flow simplified only; no explicit handling added for empty vs missing scope.
Ensure type-max-length ignores empty type; type-empty handles emptiness (#442) Behavior kept equivalent; unclear if prior incorrect empty-type reporting is resolved.

Out-of-scope changes

No out-of-scope functional changes detected.

Poem

I nibble code beneath the moonlit log,
If-let hops in, match burrows to the bog.
No APIs moved, just cleaner trails I sow,
Length checks still guard where Some strings grow.
A little thump — refactor, onward go! 🥕🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
cli/src/rule/type_max_length.rs (1)

57-75: Tests: add coverage for None/empty and equality-at-limit

Current tests don’t prove the intended fix (no violation when type is None/empty) and don’t cover the equality boundary. Please add:

Example tests to add:

#[test]
fn test_type_none_is_ignored_by_max_length() {
    let rule = TypeMaxLength { length: 4, ..Default::default() };
    let message = Message {
        body: None,
        description: None,
        footers: None,
        r#type: None,
        raw: "".to_string(),
        scope: None,
        subject: None,
    };
    assert!(rule.validate(&message).is_none());
}

#[test]
fn test_type_empty_string_is_ignored_by_max_length_even_when_limit_zero() {
    let rule = TypeMaxLength { length: 0, ..Default::default() };
    let message = Message {
        body: None,
        description: None,
        footers: None,
        r#type: Some("".to_string()),
        raw: "".to_string(),
        scope: None,
        subject: None,
    };
    assert!(rule.validate(&message).is_none());
}

#[test]
fn test_type_equal_to_max_is_ok() {
    let rule = TypeMaxLength { length: 4, ..Default::default() };
    let message = Message {
        body: None,
        description: None,
        footers: None,
        r#type: Some("feat".to_string()),
        raw: "feat: x".to_string(),
        scope: None,
        subject: None,
    };
    assert!(rule.validate(&message).is_none());
}
cli/src/rule/scope_max_length.rs (1)

57-75: Tests: add coverage for None/empty and equality-at-limit

Missing assertions for the scope being None/empty and equals-to-limit.

Example tests to add:

#[test]
fn test_scope_none_is_ignored_by_max_length() {
    let rule = ScopeMaxLength { length: 5, ..Default::default() };
    let message = Message {
        body: None,
        description: None,
        footers: None,
        r#type: Some("feat".to_string()),
        raw: "feat: x".to_string(),
        scope: None,
        subject: None,
    };
    assert!(rule.validate(&message).is_none());
}

#[test]
fn test_scope_empty_string_is_ignored_by_max_length_even_when_limit_zero() {
    let rule = ScopeMaxLength { length: 0, ..Default::default() };
    let message = Message {
        body: None,
        description: None,
        footers: None,
        r#type: Some("feat".to_string()),
        raw: "feat: x".to_string(),
        scope: Some("".to_string()),
        subject: None,
    };
    assert!(rule.validate(&message).is_none());
}

#[test]
fn test_scope_equal_to_max_is_ok() {
    let rule = ScopeMaxLength { length: 5, ..Default::default() };
    let message = Message {
        body: None,
        description: None,
        footers: None,
        r#type: Some("feat".to_string()),
        raw: "feat(scope): x".to_string(),
        scope: Some("scope".to_string()),
        subject: None,
    };
    assert!(rule.validate(&message).is_none());
}
cli/src/rule/body_max_length.rs (1)

57-77: Tests: add coverage for None/empty and equality-at-limit

To prove the bugfix and guard the boundary:

Example tests to add:

#[test]
fn test_body_none_is_ignored_by_max_length() {
    let rule = BodyMaxLength { length: 72, ..Default::default() };
    let message = Message {
        body: None,
        description: None,
        footers: None,
        r#type: Some("feat".to_string()),
        raw: "feat: x".to_string(),
        scope: None,
        subject: None,
    };
    assert!(rule.validate(&message).is_none());
}

#[test]
fn test_body_empty_string_is_ignored_by_max_length_even_when_limit_zero() {
    let rule = BodyMaxLength { length: 0, ..Default::default() };
    let message = Message {
        body: Some("".to_string()),
        description: None,
        footers: None,
        r#type: Some("feat".to_string()),
        raw: "feat: x".to_string(),
        scope: None,
        subject: None,
    };
    assert!(rule.validate(&message).is_none());
}

#[test]
fn test_body_equal_to_max_is_ok() {
    let body = "Hello"; // length 5
    let rule = BodyMaxLength { length: 5, ..Default::default() };
    let message = Message {
        body: Some(body.to_string()),
        description: None,
        footers: None,
        r#type: Some("feat".to_string()),
        raw: format!("feat: {}\n\n{}", "x", body),
        scope: None,
        subject: None,
    };
    assert!(rule.validate(&message).is_none());
}
🧹 Nitpick comments (5)
cli/src/rule/type_max_length.rs (2)

30-35: Optional: Count “characters” not bytes, or reword the message

len() counts bytes, while the message says “characters.” If non-ASCII commit messages are allowed, prefer .chars().count() to match the wording; otherwise, reword to “bytes”.

If you choose to align with characters:

-            if t.len() > self.length {
+            if t.chars().count() > self.length {

Note: This change impacts performance minimally for short strings and avoids misreporting lengths for Unicode.


6-6: Docstring nit: wrong rule name

The comment says “description-max-length” but this struct implements type-max-length.

Apply this diff:

-/// TypeMaxLength represents the description-max-length rule.
+/// TypeMaxLength represents the type-max-length rule.
cli/src/rule/scope_max_length.rs (2)

30-35: Optional: Count “characters” not bytes, or reword the message

Align the implementation with the message wording “characters”.

If aligning:

-            if scope.len() > self.length {
+            if scope.chars().count() > self.length {

6-6: Docstring nit: wrong rule name

The comment mentions “description-max-length” instead of scope-max-length.

Apply this diff:

-/// ScopeMaxLength represents the description-max-length rule.
+/// ScopeMaxLength represents the scope-max-length rule.
cli/src/rule/body_max_length.rs (1)

30-35: Optional: Count “characters” not bytes, or reword the message

len() counts bytes; message says “characters”.

If aligning:

-            if body.len() > self.length {
+            if body.chars().count() > self.length {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between efaf903 and 3cd558b.

📒 Files selected for processing (3)
  • cli/src/rule/body_max_length.rs (1 hunks)
  • cli/src/rule/scope_max_length.rs (1 hunks)
  • cli/src/rule/type_max_length.rs (1 hunks)

Comment on lines +30 to +35
if let Some(body) = &message.body {
if body.len() >= self.length {
return Some(Violation {
level: self.level.unwrap_or(Self::LEVEL),
message: self.message(message),
})
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix boundary condition: use strict “greater than” for max-length checks

Equality with the limit should pass. Using >= causes false positives and can still flag an empty body when limit is 0.

Apply this diff:

-        if let Some(body) = &message.body {
-            if body.len() >= self.length {
+        if let Some(body) = &message.body {
+            if body.len() > self.length {
                 return Some(Violation {
                     level: self.level.unwrap_or(Self::LEVEL),
                     message: self.message(message),
                 });
             }
         }
🤖 Prompt for AI Agents
In cli/src/rule/body_max_length.rs around lines 30 to 35, the max-length check
uses a non-strict comparison (body.len() >= self.length) which incorrectly flags
messages whose body length equals the limit (including empty body when limit is
0); change the comparison to a strict greater-than (body.len() > self.length) so
only lengths strictly greater than the configured max produce a Violation.

Comment on lines +30 to +35
if let Some(scope) = &message.scope {
if scope.len() >= self.length {
return Some(Violation {
level: self.level.unwrap_or(Self::LEVEL),
message: self.message(message),
})
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix boundary condition: use strict “greater than” for max-length checks

Same issue as type: equality with the limit should not violate, and length 0 shouldn’t violate when max is 0.

Apply this diff:

-        if let Some(scope) = &message.scope {
-            if scope.len() >= self.length {
+        if let Some(scope) = &message.scope {
+            if scope.len() > self.length {
                 return Some(Violation {
                     level: self.level.unwrap_or(Self::LEVEL),
                     message: self.message(message),
                 });
             }
         }
🤖 Prompt for AI Agents
In cli/src/rule/scope_max_length.rs around lines 30 to 35, the max-length check
uses >= which incorrectly flags scopes equal to the limit (including the 0
case); change the condition to use strict greater-than so it only violates when
scope.len() > self.length (i.e., replace `if scope.len() >= self.length` with
`if scope.len() > self.length`) and keep the rest of the Violation construction
unchanged.

Comment on lines +30 to +35
if let Some(t) = &message.r#type {
if t.len() >= self.length {
return Some(Violation {
level: self.level.unwrap_or(Self::LEVEL),
message: self.message(message),
})
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix boundary condition: use strict “greater than” for max-length checks

Max-length rules should pass when the length equals the limit. Using >= incorrectly flags values exactly at the configured maximum and also causes empty strings to violate when length is 0. Switch to >.

Apply this diff:

-        if let Some(t) = &message.r#type {
-            if t.len() >= self.length {
+        if let Some(t) = &message.r#type {
+            if t.len() > self.length {
                 return Some(Violation {
                     level: self.level.unwrap_or(Self::LEVEL),
                     message: self.message(message),
                 });
             }
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if let Some(t) = &message.r#type {
if t.len() >= self.length {
return Some(Violation {
level: self.level.unwrap_or(Self::LEVEL),
message: self.message(message),
})
});
if let Some(t) = &message.r#type {
if t.len() > self.length {
return Some(Violation {
level: self.level.unwrap_or(Self::LEVEL),
message: self.message(message),
});
}
}
🤖 Prompt for AI Agents
In cli/src/rule/type_max_length.rs around lines 30 to 35, the max-length check
currently uses >= which incorrectly flags values exactly equal to the limit (and
empty strings when limit is 0); change the conditional to use > so only lengths
strictly greater than self.length produce a Violation, keeping the rest of the
Violation construction intact.

@KeisukeYamashita KeisukeYamashita changed the title fix(rule): report error message when type scope or body empty fix(cli): report error message when type scope or body empty Aug 22, 2025
Copy link
Owner

@KeisukeYamashita KeisukeYamashita left a comment

Choose a reason for hiding this comment

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

Thank you!

@KeisukeYamashita KeisukeYamashita merged commit 428d4c7 into KeisukeYamashita:main Aug 22, 2025
32 of 34 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
cli/src/rule/scope_max_length.rs (1)

30-36: Fix boundary condition: use strict “greater than” for max-length checks

Max length should be inclusive; equality with the limit must not violate. Using >= incorrectly flags scopes whose length equals the configured maximum (including 0). Switch to > to align with the rule’s intent and the linked issues’ objectives.

Apply this diff:

-        if let Some(scope) = &message.scope {
-            if scope.len() >= self.length {
+        if let Some(scope) = &message.scope {
+            if scope.len() > self.length {
                 return Some(Violation {
                     level: self.level.unwrap_or(Self::LEVEL),
                     message: self.message(message),
                 });
             }
         }

Run this script to ensure no remaining >= self.length comparisons exist across max-length rules:

#!/bin/bash
# Verify all max-length rules use strict `>`
rg -nP -C2 --glob 'cli/src/rule/**' $'len\\(\\)\\s*>=\\s*self\\.length|\\.len\\(\\)\\s*>=\\s*self\\.length'
🧹 Nitpick comments (3)
cli/src/rule/scope_max_length.rs (3)

6-6: Correct copy/paste in doc comment

Update the docstring to reference the correct rule name.

-/// ScopeMaxLength represents the description-max-length rule.
+/// ScopeMaxLength represents the scope-max-length rule.

95-118: Add equality and empty-string tests; rename test for clarity

Strengthen tests to lock the intended semantics and avoid regressions:

  • Add a test where scope length equals the limit (no violation).
  • Add a test where scope is the empty string (no violation; emptiness is handled by scope-empty).
  • Rename test_short_scope to better reflect it verifies “exceeds limit”.

Apply this diff:

-    fn test_short_scope() {
+    fn test_scope_exceeds_limit() {
         let rule = ScopeMaxLength {
             length: 3, // Short length for testing
             ..Default::default()
         };
         let message = Message {
             body: None,
             description: Some("feat(scope): I'm long description".to_string()),
             footers: None,
             r#type: Some("feat".to_string()),
             raw: "feat(scope): I'm long description".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,
             format!("scope is longer than {} characters", rule.length)
         );
     }
+
+    #[test]
+    fn test_scope_equal_to_limit_no_violation() {
+        let rule = ScopeMaxLength {
+            length: 5,
+            ..Default::default()
+        };
+        let message = Message {
+            body: None,
+            description: Some("desc".to_string()),
+            footers: None,
+            r#type: Some("feat".to_string()),
+            raw: "feat(scope): desc".to_string(),
+            scope: Some("scope".to_string()), // len == 5
+            subject: Some("feat(scope): desc".to_string()),
+        };
+        assert!(rule.validate(&message).is_none());
+    }
+
+    #[test]
+    fn test_empty_scope_string_no_violation() {
+        let rule = ScopeMaxLength {
+            length: 5,
+            ..Default::default()
+        };
+        let message = Message {
+            body: None,
+            description: Some("desc".to_string()),
+            footers: None,
+            r#type: Some("feat".to_string()),
+            raw: "feat(): desc".to_string(),
+            scope: Some("".to_string()),
+            subject: Some("feat(): desc".to_string()),
+        };
+        assert!(rule.validate(&message).is_none());
+    }

I can mirror these tests in type/body max-length modules if desired.


31-31: Optional: count user-perceived characters, not bytes

String::len() counts bytes; multi-byte Unicode scopes (e.g., emoji) will be penalized earlier than intended. If international input is common, consider counting grapheme clusters.

Example (behind an optional feature):

-            if scope.len() > self.length {
+            #[cfg(feature = "unicode_graphemes")]
+            {
+                use unicode_segmentation::UnicodeSegmentation;
+                if scope.graphemes(true).count() > self.length {
+                    return Some(Violation {
+                        level: self.level.unwrap_or(Self::LEVEL),
+                        message: self.message(message),
+                    });
+                }
+            }
+            #[cfg(not(feature = "unicode_graphemes"))]
+            if scope.len() > self.length {
                 return Some(Violation {
                     level: self.level.unwrap_or(Self::LEVEL),
                     message: self.message(message),
                 });
             }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3cd558b and ed73bbe.

📒 Files selected for processing (3)
  • cli/src/rule/body_max_length.rs (1 hunks)
  • cli/src/rule/scope_max_length.rs (1 hunks)
  • cli/src/rule/type_max_length.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • cli/src/rule/type_max_length.rs
  • cli/src/rule/body_max_length.rs
🧰 Additional context used
🧬 Code graph analysis (1)
cli/src/rule/scope_max_length.rs (1)
cli/src/rule.rs (1)
  • message (220-220)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Report error message when type empty Report error message when scope empty Report error message when body empty
2 participants