ricecoder_lsp/diagnostics/
rust_rules.rs1use super::DiagnosticsResult;
4use crate::types::{Diagnostic, DiagnosticSeverity, Position, Range};
5
6pub fn generate_rust_diagnostics(code: &str) -> DiagnosticsResult<Vec<Diagnostic>> {
8 let mut diagnostics = Vec::new();
9
10 diagnostics.extend(check_unused_imports(code));
12
13 diagnostics.extend(check_unreachable_code(code));
15
16 diagnostics.extend(check_naming_conventions(code));
18
19 Ok(diagnostics)
20}
21
22fn check_unused_imports(code: &str) -> Vec<Diagnostic> {
24 let mut diagnostics = Vec::new();
25
26 for (line_num, line) in code.lines().enumerate() {
27 if line.trim().starts_with("use ") && line.contains("::") {
29 let parts: Vec<&str> = line.split("::").collect();
31 if let Some(last_part) = parts.last() {
32 let imported_name = last_part.trim().trim_end_matches(';').trim_end_matches(',');
33
34 let occurrences = code
36 .lines()
37 .enumerate()
38 .filter(|(i, l)| *i != line_num && l.contains(imported_name))
39 .count();
40
41 if occurrences == 0 && !imported_name.is_empty() {
43 let range = Range::new(
44 Position::new(line_num as u32, 0),
45 Position::new(line_num as u32, line.len() as u32),
46 );
47
48 let diagnostic = Diagnostic::new(
49 range,
50 DiagnosticSeverity::Warning,
51 format!("Unused import: `{}`", imported_name),
52 );
53
54 diagnostics.push(diagnostic);
55 }
56 }
57 }
58 }
59
60 diagnostics
61}
62
63fn check_unreachable_code(code: &str) -> Vec<Diagnostic> {
65 let mut diagnostics = Vec::new();
66
67 for (line_num, line) in code.lines().enumerate() {
68 let trimmed = line.trim();
69
70 if trimmed.starts_with("return")
72 || trimmed.starts_with("panic!")
73 || trimmed.starts_with("unreachable!")
74 {
75 if let Some(pos) = line.find("return") {
77 let after_return = &line[pos + 6..].trim();
78 if !after_return.is_empty()
79 && !after_return.starts_with(';')
80 && !after_return.starts_with("(")
81 {
82 let range = Range::new(
83 Position::new(line_num as u32, (pos + 6) as u32),
84 Position::new(line_num as u32, line.len() as u32),
85 );
86
87 let diagnostic = Diagnostic::new(
88 range,
89 DiagnosticSeverity::Warning,
90 "Unreachable code after return statement".to_string(),
91 );
92
93 diagnostics.push(diagnostic);
94 }
95 }
96 }
97 }
98
99 diagnostics
100}
101
102fn check_naming_conventions(code: &str) -> Vec<Diagnostic> {
104 let mut diagnostics = Vec::new();
105
106 for (line_num, line) in code.lines().enumerate() {
107 if line.contains("fn ") {
109 if let Some(fn_pos) = line.find("fn ") {
110 let after_fn = &line[fn_pos + 3..];
111 if let Some(paren_pos) = after_fn.find('(') {
112 let fn_name = after_fn[..paren_pos].trim();
113
114 if fn_name.contains(|c: char| c.is_uppercase()) {
116 let range = Range::new(
117 Position::new(line_num as u32, (fn_pos + 3) as u32),
118 Position::new(line_num as u32, (fn_pos + 3 + fn_name.len()) as u32),
119 );
120
121 let diagnostic = Diagnostic::new(
122 range,
123 DiagnosticSeverity::Hint,
124 format!("Function name `{}` should be in snake_case", fn_name),
125 );
126
127 diagnostics.push(diagnostic);
128 }
129 }
130 }
131 }
132
133 if line.contains("const ") {
135 if let Some(const_pos) = line.find("const ") {
136 let after_const = &line[const_pos + 6..];
137 if let Some(colon_pos) = after_const.find(':') {
138 let const_name = after_const[..colon_pos].trim();
139
140 if !const_name
142 .chars()
143 .all(|c| c.is_uppercase() || c == '_' || c.is_numeric())
144 {
145 let range = Range::new(
146 Position::new(line_num as u32, (const_pos + 6) as u32),
147 Position::new(
148 line_num as u32,
149 (const_pos + 6 + const_name.len()) as u32,
150 ),
151 );
152
153 let diagnostic = Diagnostic::new(
154 range,
155 DiagnosticSeverity::Hint,
156 format!("Constant name `{}` should be in UPPER_CASE", const_name),
157 );
158
159 diagnostics.push(diagnostic);
160 }
161 }
162 }
163 }
164 }
165
166 diagnostics
167}