Skip to content

Commit cb932c0

Browse files
committed
refactor: split discussion selection helpers
Separate discussion comment loading and ID repair from selection rules so the CLI can evolve lookup behavior without mixing file parsing logic. Made-with: Cursor
1 parent dcb1875 commit cb932c0

File tree

4 files changed

+107
-92
lines changed

4 files changed

+107
-92
lines changed

TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
- [x] `src/commands/misc/feedback/command.rs`: separate file loading/ID normalization from store persistence.
8989
- [x] `src/commands/misc/feedback/apply.rs`: split acceptance/rejection counters from store mutation helpers.
9090
- [x] `src/commands/misc/discussion/command.rs`: separate the interactive loop from single-shot execution.
91-
- [ ] `src/commands/misc/discussion/selection.rs`: split file loading/ID repair from selection rules.
91+
- [x] `src/commands/misc/discussion/selection.rs`: split file loading/ID repair from selection rules.
9292
- [ ] `src/commands/misc/changelog.rs`: evaluate splitting changelog collection from output formatting.
9393
- [x] `src/commands/eval/command.rs`: separate CLI option prep, fixture execution, and report lifecycle.
9494
- [x] `src/commands/feedback_eval/command.rs`: separate input loading from report/output orchestration.
Lines changed: 6 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,7 @@
1-
use anyhow::Result;
2-
use std::path::Path;
1+
#[path = "selection/load.rs"]
2+
mod load;
3+
#[path = "selection/rules.rs"]
4+
mod rules;
35

4-
use crate::core;
5-
6-
pub(super) async fn load_discussion_comments(review_path: &Path) -> Result<Vec<core::Comment>> {
7-
let content = tokio::fs::read_to_string(review_path).await?;
8-
let mut comments: Vec<core::Comment> = serde_json::from_str(&content)?;
9-
if comments.is_empty() {
10-
anyhow::bail!("No comments found in {}", review_path.display());
11-
}
12-
13-
for comment in &mut comments {
14-
if comment.id.trim().is_empty() {
15-
comment.id = core::comment::compute_comment_id(
16-
&comment.file_path,
17-
&comment.content,
18-
&comment.category,
19-
);
20-
}
21-
}
22-
23-
Ok(comments)
24-
}
25-
26-
pub(super) fn select_discussion_comment(
27-
comments: &[core::Comment],
28-
comment_id: Option<String>,
29-
comment_index: Option<usize>,
30-
) -> Result<core::Comment> {
31-
if comment_id.is_some() && comment_index.is_some() {
32-
anyhow::bail!("Specify only one of --comment-id or --comment-index");
33-
}
34-
35-
if let Some(id) = comment_id {
36-
let selected = comments
37-
.iter()
38-
.find(|comment| comment.id == id)
39-
.cloned()
40-
.ok_or_else(|| anyhow::anyhow!("Comment id not found: {}", id))?;
41-
return Ok(selected);
42-
}
43-
44-
if let Some(index) = comment_index {
45-
if index == 0 {
46-
anyhow::bail!("comment-index is 1-based");
47-
}
48-
let selected = comments
49-
.get(index - 1)
50-
.cloned()
51-
.ok_or_else(|| anyhow::anyhow!("Comment index out of range: {}", index))?;
52-
return Ok(selected);
53-
}
54-
55-
comments
56-
.first()
57-
.cloned()
58-
.ok_or_else(|| anyhow::anyhow!("No comments available"))
59-
}
60-
61-
#[cfg(test)]
62-
mod tests {
63-
use super::*;
64-
use std::path::PathBuf;
65-
66-
#[test]
67-
fn test_select_discussion_comment_empty_comments() {
68-
let result = select_discussion_comment(&[], None, None);
69-
assert!(result.is_err());
70-
}
71-
72-
#[test]
73-
fn test_select_discussion_comment_defaults_to_first() {
74-
let comment = core::Comment {
75-
id: "cmt_1".to_string(),
76-
file_path: PathBuf::from("test.rs"),
77-
line_number: 1,
78-
content: "test".to_string(),
79-
rule_id: None,
80-
severity: core::comment::Severity::Info,
81-
category: core::comment::Category::BestPractice,
82-
suggestion: None,
83-
confidence: 0.8,
84-
code_suggestion: None,
85-
tags: vec![],
86-
fix_effort: core::comment::FixEffort::Low,
87-
feedback: None,
88-
};
89-
let result = select_discussion_comment(std::slice::from_ref(&comment), None, None).unwrap();
90-
assert_eq!(result.id, "cmt_1");
91-
}
92-
}
6+
pub(super) use load::load_discussion_comments;
7+
pub(super) use rules::select_discussion_comment;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use anyhow::Result;
2+
use std::path::Path;
3+
4+
use crate::core;
5+
6+
pub(in super::super) async fn load_discussion_comments(
7+
review_path: &Path,
8+
) -> Result<Vec<core::Comment>> {
9+
let content = tokio::fs::read_to_string(review_path).await?;
10+
let mut comments: Vec<core::Comment> = serde_json::from_str(&content)?;
11+
if comments.is_empty() {
12+
anyhow::bail!("No comments found in {}", review_path.display());
13+
}
14+
15+
normalize_comment_ids(&mut comments);
16+
Ok(comments)
17+
}
18+
19+
fn normalize_comment_ids(comments: &mut [core::Comment]) {
20+
for comment in comments {
21+
if comment.id.trim().is_empty() {
22+
comment.id = core::comment::compute_comment_id(
23+
&comment.file_path,
24+
&comment.content,
25+
&comment.category,
26+
);
27+
}
28+
}
29+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use anyhow::Result;
2+
3+
use crate::core;
4+
5+
pub(in super::super) fn select_discussion_comment(
6+
comments: &[core::Comment],
7+
comment_id: Option<String>,
8+
comment_index: Option<usize>,
9+
) -> Result<core::Comment> {
10+
if comment_id.is_some() && comment_index.is_some() {
11+
anyhow::bail!("Specify only one of --comment-id or --comment-index");
12+
}
13+
14+
if let Some(id) = comment_id {
15+
let selected = comments
16+
.iter()
17+
.find(|comment| comment.id == id)
18+
.cloned()
19+
.ok_or_else(|| anyhow::anyhow!("Comment id not found: {}", id))?;
20+
return Ok(selected);
21+
}
22+
23+
if let Some(index) = comment_index {
24+
if index == 0 {
25+
anyhow::bail!("comment-index is 1-based");
26+
}
27+
let selected = comments
28+
.get(index - 1)
29+
.cloned()
30+
.ok_or_else(|| anyhow::anyhow!("Comment index out of range: {}", index))?;
31+
return Ok(selected);
32+
}
33+
34+
comments
35+
.first()
36+
.cloned()
37+
.ok_or_else(|| anyhow::anyhow!("No comments available"))
38+
}
39+
40+
#[cfg(test)]
41+
mod tests {
42+
use super::*;
43+
use std::path::PathBuf;
44+
45+
#[test]
46+
fn test_select_discussion_comment_empty_comments() {
47+
let result = select_discussion_comment(&[], None, None);
48+
assert!(result.is_err());
49+
}
50+
51+
#[test]
52+
fn test_select_discussion_comment_defaults_to_first() {
53+
let comment = core::Comment {
54+
id: "cmt_1".to_string(),
55+
file_path: PathBuf::from("test.rs"),
56+
line_number: 1,
57+
content: "test".to_string(),
58+
rule_id: None,
59+
severity: core::comment::Severity::Info,
60+
category: core::comment::Category::BestPractice,
61+
suggestion: None,
62+
confidence: 0.8,
63+
code_suggestion: None,
64+
tags: vec![],
65+
fix_effort: core::comment::FixEffort::Low,
66+
feedback: None,
67+
};
68+
let result = select_discussion_comment(std::slice::from_ref(&comment), None, None).unwrap();
69+
assert_eq!(result.id, "cmt_1");
70+
}
71+
}

0 commit comments

Comments
 (0)