cargo_quality/differ/
generator.rs1use std::fs;
5
6use masterror::AppResult;
7
8use super::types::{DiffEntry, FileDiff};
9use crate::{
10 analyzer::Analyzer,
11 error::{IoError, ParseError}
12};
13
14pub fn generate_diff(file_path: &str, analyzers: &[Box<dyn Analyzer>]) -> AppResult<FileDiff> {
34 let content = fs::read_to_string(file_path).map_err(IoError::from)?;
35 let ast = syn::parse_file(&content).map_err(ParseError::from)?;
36
37 let mut file_diff = FileDiff::new(file_path.to_string());
38
39 for analyzer in analyzers {
40 let result = analyzer.analyze(&ast, &content)?;
41
42 for issue in result.issues {
43 if issue.line == 0 || !issue.fix.is_available() {
44 continue;
45 }
46
47 let original_content = content
48 .lines()
49 .nth(issue.line.saturating_sub(1))
50 .unwrap_or("");
51
52 let (modified_line, import) =
53 if let Some((import, pattern, replacement)) = issue.fix.as_import() {
54 let modified = original_content.replace(pattern, replacement);
55 (modified, Some(import.to_string()))
56 } else if let Some(simple) = issue.fix.as_simple() {
57 (simple.to_string(), None)
58 } else {
59 continue;
60 };
61
62 let entry = DiffEntry {
63 line: issue.line,
64 analyzer: analyzer.name().to_string(),
65 original: original_content.to_string(),
66 modified: modified_line,
67 description: issue.message,
68 import
69 };
70
71 file_diff.add_entry(entry);
72 }
73 }
74
75 Ok(file_diff)
76}
77
78#[cfg(test)]
79mod tests {
80 use tempfile::TempDir;
81
82 use super::*;
83 use crate::analyzers::get_analyzers;
84
85 #[test]
86 fn test_generate_diff_integration() {
87 let temp_dir = TempDir::new().unwrap();
88 let file_path = temp_dir.path().join("test.rs");
89 std::fs::write(
90 &file_path,
91 "fn main() { let x = std::fs::read_to_string(\"f\"); }"
92 )
93 .unwrap();
94
95 let analyzers = get_analyzers();
96 let result = generate_diff(file_path.to_str().unwrap(), &analyzers);
97
98 assert!(result.is_ok());
99 }
100
101 #[test]
102 fn test_generate_diff_no_issues() {
103 let temp_dir = TempDir::new().unwrap();
104 let file_path = temp_dir.path().join("test.rs");
105 std::fs::write(&file_path, "fn main() {}").unwrap();
106
107 let analyzers = get_analyzers();
108 let result = generate_diff(file_path.to_str().unwrap(), &analyzers);
109
110 assert!(result.is_ok());
111 }
112
113 #[test]
114 fn test_generate_diff_invalid_syntax() {
115 let temp_dir = TempDir::new().unwrap();
116 let file_path = temp_dir.path().join("test.rs");
117 std::fs::write(&file_path, "fn main() { invalid syntax +++").unwrap();
118
119 let analyzers = get_analyzers();
120 let result = generate_diff(file_path.to_str().unwrap(), &analyzers);
121
122 assert!(result.is_err());
123 }
124
125 #[test]
126 fn test_path_import_included_in_diff() {
127 let temp_dir = TempDir::new().unwrap();
128 let file_path = temp_dir.path().join("test.rs");
129 std::fs::write(
130 &file_path,
131 "fn main() { let x = std::fs::read_to_string(\"f\"); }"
132 )
133 .unwrap();
134
135 let analyzers = get_analyzers();
136 let result = generate_diff(file_path.to_str().unwrap(), &analyzers).unwrap();
137
138 assert!(
139 result.entries.iter().any(|e| e.analyzer == "path_import"),
140 "path_import should be included in diff with suggestions"
141 );
142 }
143
144 #[test]
145 fn test_format_args_excluded_from_diff_without_suggestion() {
146 let temp_dir = TempDir::new().unwrap();
147 let file_path = temp_dir.path().join("test.rs");
148 std::fs::write(
149 &file_path,
150 "fn main() { println!(\"Hello {}\", \"world\"); }"
151 )
152 .unwrap();
153
154 let analyzers = get_analyzers();
155 let result = generate_diff(file_path.to_str().unwrap(), &analyzers).unwrap();
156
157 for entry in &result.entries {
158 assert_ne!(entry.analyzer, "format_args");
159 }
160 }
161}