linthis/interactive/
quickfix.rs1use crate::utils::types::{LintIssue, RunResult, Severity};
24use std::fs::File;
25use std::io::Write;
26use std::path::Path;
27
28pub fn generate_quickfix(issues: &[LintIssue]) -> String {
30 issues
31 .iter()
32 .map(format_issue_quickfix)
33 .collect::<Vec<_>>()
34 .join("\n")
35}
36
37pub fn generate_quickfix_from_result(result: &RunResult) -> String {
39 generate_quickfix(&result.issues)
40}
41
42fn format_issue_quickfix(issue: &LintIssue) -> String {
46 let file = issue.file_path.display();
47 let line = issue.line;
48 let col = issue.column.unwrap_or(1);
49
50 let severity = match issue.severity {
52 Severity::Error => "error",
53 Severity::Warning => "warning",
54 Severity::Info => "info",
55 };
56
57 let message = if let Some(ref code) = issue.code {
59 format!("{} ({}) [{}]", issue.message, code, severity)
60 } else {
61 format!("{} [{}]", issue.message, severity)
62 };
63
64 let message = message.replace('\n', " ").replace('\r', "");
66
67 format!("{}:{}:{}:{}", file, line, col, message)
68}
69
70pub fn write_quickfix_file(issues: &[LintIssue], path: &Path) -> super::InteractiveResult<()> {
80 use super::InteractiveError;
81
82 let content = generate_quickfix(issues);
83
84 let mut file = File::create(path).map_err(|e| {
85 InteractiveError::QuickfixWrite(format!("Failed to create file: {}", e))
86 })?;
87
88 file.write_all(content.as_bytes()).map_err(|e| {
89 InteractiveError::QuickfixWrite(format!("Failed to write content: {}", e))
90 })?;
91
92 if !content.is_empty() && !content.ends_with('\n') {
94 file.write_all(b"\n").map_err(|e| {
95 InteractiveError::QuickfixWrite(format!("Failed to write newline: {}", e))
96 })?;
97 }
98
99 Ok(())
100}
101
102pub fn default_quickfix_path() -> std::path::PathBuf {
104 std::path::PathBuf::from(".linthis").join("quickfix.txt")
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use std::path::PathBuf;
111
112 fn make_issue(file: &str, line: usize, col: Option<usize>, severity: Severity, msg: &str, code: Option<&str>) -> LintIssue {
113 let mut issue = LintIssue::new(
114 PathBuf::from(file),
115 line,
116 msg.to_string(),
117 severity,
118 );
119 if let Some(c) = col {
120 issue = issue.with_column(c);
121 }
122 if let Some(code) = code {
123 issue = issue.with_code(code.to_string());
124 }
125 issue
126 }
127
128 #[test]
129 fn test_format_issue_quickfix_basic() {
130 let issue = make_issue("src/main.rs", 42, Some(10), Severity::Error, "unused variable", Some("W0612"));
131 let formatted = format_issue_quickfix(&issue);
132 assert_eq!(formatted, "src/main.rs:42:10:unused variable (W0612) [error]");
133 }
134
135 #[test]
136 fn test_format_issue_quickfix_no_column() {
137 let issue = make_issue("test.py", 100, None, Severity::Warning, "line too long", Some("E501"));
138 let formatted = format_issue_quickfix(&issue);
139 assert_eq!(formatted, "test.py:100:1:line too long (E501) [warning]");
140 }
141
142 #[test]
143 fn test_format_issue_quickfix_no_code() {
144 let issue = make_issue("file.cpp", 5, Some(1), Severity::Info, "consider using const", None);
145 let formatted = format_issue_quickfix(&issue);
146 assert_eq!(formatted, "file.cpp:5:1:consider using const [info]");
147 }
148
149 #[test]
150 fn test_generate_quickfix_multiple() {
151 let issues = vec![
152 make_issue("a.rs", 1, Some(1), Severity::Error, "error 1", Some("E001")),
153 make_issue("b.rs", 2, Some(5), Severity::Warning, "warning 1", Some("W001")),
154 ];
155 let output = generate_quickfix(&issues);
156 let lines: Vec<&str> = output.lines().collect();
157 assert_eq!(lines.len(), 2);
158 assert!(lines[0].starts_with("a.rs:1:1:"));
159 assert!(lines[1].starts_with("b.rs:2:5:"));
160 }
161
162 #[test]
163 fn test_generate_quickfix_empty() {
164 let issues: Vec<LintIssue> = vec![];
165 let output = generate_quickfix(&issues);
166 assert!(output.is_empty());
167 }
168
169 #[test]
170 fn test_format_issue_quickfix_escapes_newlines() {
171 let issue = make_issue("test.rs", 1, Some(1), Severity::Error, "line1\nline2\rline3", None);
172 let formatted = format_issue_quickfix(&issue);
173 assert!(!formatted.contains('\n'));
174 assert!(!formatted.contains('\r'));
175 }
176}