diff options
Diffstat (limited to 'crates/ra_ide_api')
-rw-r--r-- | crates/ra_ide_api/src/diagnostics.rs | 351 |
1 files changed, 298 insertions, 53 deletions
diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs index 53d95fb4c..069092528 100644 --- a/crates/ra_ide_api/src/diagnostics.rs +++ b/crates/ra_ide_api/src/diagnostics.rs | |||
@@ -1,65 +1,310 @@ | |||
1 | use itertools::Itertools; | ||
1 | use hir::{Problem, source_binder}; | 2 | use hir::{Problem, source_binder}; |
2 | use ra_ide_api_light::Severity; | 3 | use ra_ide_api_light::Severity; |
3 | use ra_db::SourceDatabase; | 4 | use ra_db::SourceDatabase; |
5 | use ra_syntax::{ | ||
6 | Location, SourceFile, SyntaxKind, TextRange, SyntaxNode, | ||
7 | ast::{self, AstNode}, | ||
4 | 8 | ||
5 | use crate::{Diagnostic, FileId, FileSystemEdit, SourceChange, db::RootDatabase}; | 9 | }; |
10 | use ra_text_edit::{TextEdit, TextEditBuilder}; | ||
11 | |||
12 | use crate::{Diagnostic, FileId, FileSystemEdit, SourceChange, SourceFileEdit, db::RootDatabase}; | ||
6 | 13 | ||
7 | pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> { | 14 | pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> { |
8 | let syntax = db.parse(file_id); | 15 | let source_file = db.parse(file_id); |
9 | 16 | let mut res = Vec::new(); | |
10 | let mut res = ra_ide_api_light::diagnostics(&syntax) | 17 | |
11 | .into_iter() | 18 | syntax_errors(&mut res, &source_file); |
12 | .map(|d| Diagnostic { | 19 | |
13 | range: d.range, | 20 | for node in source_file.syntax().descendants() { |
14 | message: d.msg, | 21 | check_unnecessary_braces_in_use_statement(&mut res, file_id, node); |
15 | severity: d.severity, | 22 | check_struct_shorthand_initialization(&mut res, file_id, node); |
16 | fix: d.fix.map(|fix| SourceChange::from_local_edit(file_id, fix)), | 23 | } |
17 | }) | 24 | |
18 | .collect::<Vec<_>>(); | ||
19 | if let Some(m) = source_binder::module_from_file_id(db, file_id) { | 25 | if let Some(m) = source_binder::module_from_file_id(db, file_id) { |
20 | for (name_node, problem) in m.problems(db) { | 26 | check_module(&mut res, db, file_id, m); |
21 | let source_root = db.file_source_root(file_id); | 27 | }; |
22 | let diag = match problem { | 28 | res |
23 | Problem::UnresolvedModule { candidate } => { | 29 | } |
24 | let create_file = | 30 | |
25 | FileSystemEdit::CreateFile { source_root, path: candidate.clone() }; | 31 | fn syntax_errors(acc: &mut Vec<Diagnostic>, source_file: &SourceFile) { |
26 | let fix = SourceChange { | 32 | fn location_to_range(location: Location) -> TextRange { |
27 | label: "create module".to_string(), | 33 | match location { |
28 | source_file_edits: Vec::new(), | 34 | Location::Offset(offset) => TextRange::offset_len(offset, 1.into()), |
29 | file_system_edits: vec![create_file], | 35 | Location::Range(range) => range, |
36 | } | ||
37 | } | ||
38 | |||
39 | acc.extend(source_file.errors().into_iter().map(|err| Diagnostic { | ||
40 | range: location_to_range(err.location()), | ||
41 | message: format!("Syntax Error: {}", err), | ||
42 | severity: Severity::Error, | ||
43 | fix: None, | ||
44 | })); | ||
45 | } | ||
46 | |||
47 | fn check_unnecessary_braces_in_use_statement( | ||
48 | acc: &mut Vec<Diagnostic>, | ||
49 | file_id: FileId, | ||
50 | node: &SyntaxNode, | ||
51 | ) -> Option<()> { | ||
52 | let use_tree_list = ast::UseTreeList::cast(node)?; | ||
53 | if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { | ||
54 | let range = use_tree_list.syntax().range(); | ||
55 | let edit = | ||
56 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(single_use_tree) | ||
57 | .unwrap_or_else(|| { | ||
58 | let to_replace = single_use_tree.syntax().text().to_string(); | ||
59 | let mut edit_builder = TextEditBuilder::default(); | ||
60 | edit_builder.delete(range); | ||
61 | edit_builder.insert(range.start(), to_replace); | ||
62 | edit_builder.finish() | ||
63 | }); | ||
64 | |||
65 | acc.push(Diagnostic { | ||
66 | range, | ||
67 | message: format!("Unnecessary braces in use statement"), | ||
68 | severity: Severity::WeakWarning, | ||
69 | fix: Some(SourceChange { | ||
70 | label: "Remove unnecessary braces".to_string(), | ||
71 | source_file_edits: vec![SourceFileEdit { file_id, edit }], | ||
72 | file_system_edits: Vec::new(), | ||
73 | cursor_position: None, | ||
74 | }), | ||
75 | }); | ||
76 | } | ||
77 | |||
78 | Some(()) | ||
79 | } | ||
80 | |||
81 | fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | ||
82 | single_use_tree: &ast::UseTree, | ||
83 | ) -> Option<TextEdit> { | ||
84 | let use_tree_list_node = single_use_tree.syntax().parent()?; | ||
85 | if single_use_tree.path()?.segment()?.syntax().first_child()?.kind() == SyntaxKind::SELF_KW { | ||
86 | let start = use_tree_list_node.prev_sibling()?.range().start(); | ||
87 | let end = use_tree_list_node.range().end(); | ||
88 | let range = TextRange::from_to(start, end); | ||
89 | let mut edit_builder = TextEditBuilder::default(); | ||
90 | edit_builder.delete(range); | ||
91 | return Some(edit_builder.finish()); | ||
92 | } | ||
93 | None | ||
94 | } | ||
95 | |||
96 | fn check_struct_shorthand_initialization( | ||
97 | acc: &mut Vec<Diagnostic>, | ||
98 | file_id: FileId, | ||
99 | node: &SyntaxNode, | ||
100 | ) -> Option<()> { | ||
101 | let struct_lit = ast::StructLit::cast(node)?; | ||
102 | let named_field_list = struct_lit.named_field_list()?; | ||
103 | for named_field in named_field_list.fields() { | ||
104 | if let (Some(name_ref), Some(expr)) = (named_field.name_ref(), named_field.expr()) { | ||
105 | let field_name = name_ref.syntax().text().to_string(); | ||
106 | let field_expr = expr.syntax().text().to_string(); | ||
107 | if field_name == field_expr { | ||
108 | let mut edit_builder = TextEditBuilder::default(); | ||
109 | edit_builder.delete(named_field.syntax().range()); | ||
110 | edit_builder.insert(named_field.syntax().range().start(), field_name); | ||
111 | let edit = edit_builder.finish(); | ||
112 | |||
113 | acc.push(Diagnostic { | ||
114 | range: named_field.syntax().range(), | ||
115 | message: format!("Shorthand struct initialization"), | ||
116 | severity: Severity::WeakWarning, | ||
117 | fix: Some(SourceChange { | ||
118 | label: "use struct shorthand initialization".to_string(), | ||
119 | source_file_edits: vec![SourceFileEdit { file_id, edit }], | ||
120 | file_system_edits: Vec::new(), | ||
30 | cursor_position: None, | 121 | cursor_position: None, |
31 | }; | 122 | }), |
32 | Diagnostic { | 123 | }); |
33 | range: name_node.range(), | 124 | } |
34 | message: "unresolved module".to_string(), | 125 | } |
35 | severity: Severity::Error, | 126 | } |
36 | fix: Some(fix), | 127 | Some(()) |
37 | } | 128 | } |
129 | |||
130 | fn check_module( | ||
131 | acc: &mut Vec<Diagnostic>, | ||
132 | db: &RootDatabase, | ||
133 | file_id: FileId, | ||
134 | module: hir::Module, | ||
135 | ) { | ||
136 | let source_root = db.file_source_root(file_id); | ||
137 | for (name_node, problem) in module.problems(db) { | ||
138 | let diag = match problem { | ||
139 | Problem::UnresolvedModule { candidate } => { | ||
140 | let create_file = | ||
141 | FileSystemEdit::CreateFile { source_root, path: candidate.clone() }; | ||
142 | let fix = SourceChange { | ||
143 | label: "create module".to_string(), | ||
144 | source_file_edits: Vec::new(), | ||
145 | file_system_edits: vec![create_file], | ||
146 | cursor_position: None, | ||
147 | }; | ||
148 | Diagnostic { | ||
149 | range: name_node.range(), | ||
150 | message: "unresolved module".to_string(), | ||
151 | severity: Severity::Error, | ||
152 | fix: Some(fix), | ||
38 | } | 153 | } |
39 | Problem::NotDirOwner { move_to, candidate } => { | 154 | } |
40 | let move_file = FileSystemEdit::MoveFile { | 155 | Problem::NotDirOwner { move_to, candidate } => { |
41 | src: file_id, | 156 | let move_file = FileSystemEdit::MoveFile { |
42 | dst_source_root: source_root, | 157 | src: file_id, |
43 | dst_path: move_to.clone(), | 158 | dst_source_root: source_root, |
44 | }; | 159 | dst_path: move_to.clone(), |
45 | let create_file = | 160 | }; |
46 | FileSystemEdit::CreateFile { source_root, path: move_to.join(candidate) }; | 161 | let create_file = |
47 | let fix = SourceChange { | 162 | FileSystemEdit::CreateFile { source_root, path: move_to.join(candidate) }; |
48 | label: "move file and create module".to_string(), | 163 | let fix = SourceChange { |
49 | source_file_edits: Vec::new(), | 164 | label: "move file and create module".to_string(), |
50 | file_system_edits: vec![move_file, create_file], | 165 | source_file_edits: Vec::new(), |
51 | cursor_position: None, | 166 | file_system_edits: vec![move_file, create_file], |
52 | }; | 167 | cursor_position: None, |
53 | Diagnostic { | 168 | }; |
54 | range: name_node.range(), | 169 | Diagnostic { |
55 | message: "can't declare module at this location".to_string(), | 170 | range: name_node.range(), |
56 | severity: Severity::Error, | 171 | message: "can't declare module at this location".to_string(), |
57 | fix: Some(fix), | 172 | severity: Severity::Error, |
58 | } | 173 | fix: Some(fix), |
59 | } | 174 | } |
60 | }; | 175 | } |
61 | res.push(diag) | 176 | }; |
177 | acc.push(diag) | ||
178 | } | ||
179 | } | ||
180 | |||
181 | #[cfg(test)] | ||
182 | mod tests { | ||
183 | use test_utils::assert_eq_text; | ||
184 | |||
185 | use super::*; | ||
186 | |||
187 | type DiagnosticChecker = fn(&mut Vec<Diagnostic>, FileId, &SyntaxNode) -> Option<()>; | ||
188 | |||
189 | fn check_not_applicable(code: &str, func: DiagnosticChecker) { | ||
190 | let file = SourceFile::parse(code); | ||
191 | let mut diagnostics = Vec::new(); | ||
192 | for node in file.syntax().descendants() { | ||
193 | func(&mut diagnostics, FileId(0), node); | ||
62 | } | 194 | } |
63 | }; | 195 | assert!(diagnostics.is_empty()); |
64 | res | 196 | } |
197 | |||
198 | fn check_apply(before: &str, after: &str, func: DiagnosticChecker) { | ||
199 | let file = SourceFile::parse(before); | ||
200 | let mut diagnostics = Vec::new(); | ||
201 | for node in file.syntax().descendants() { | ||
202 | func(&mut diagnostics, FileId(0), node); | ||
203 | } | ||
204 | let diagnostic = | ||
205 | diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); | ||
206 | let mut fix = diagnostic.fix.unwrap(); | ||
207 | let edit = fix.source_file_edits.pop().unwrap().edit; | ||
208 | let actual = edit.apply(&before); | ||
209 | assert_eq_text!(after, &actual); | ||
210 | } | ||
211 | |||
212 | #[test] | ||
213 | fn test_check_unnecessary_braces_in_use_statement() { | ||
214 | check_not_applicable( | ||
215 | " | ||
216 | use a; | ||
217 | use a::{c, d::e}; | ||
218 | ", | ||
219 | check_unnecessary_braces_in_use_statement, | ||
220 | ); | ||
221 | check_apply("use {b};", "use b;", check_unnecessary_braces_in_use_statement); | ||
222 | check_apply("use a::{c};", "use a::c;", check_unnecessary_braces_in_use_statement); | ||
223 | check_apply("use a::{self};", "use a;", check_unnecessary_braces_in_use_statement); | ||
224 | check_apply( | ||
225 | "use a::{c, d::{e}};", | ||
226 | "use a::{c, d::e};", | ||
227 | check_unnecessary_braces_in_use_statement, | ||
228 | ); | ||
229 | } | ||
230 | |||
231 | #[test] | ||
232 | fn test_check_struct_shorthand_initialization() { | ||
233 | check_not_applicable( | ||
234 | r#" | ||
235 | struct A { | ||
236 | a: &'static str | ||
237 | } | ||
238 | |||
239 | fn main() { | ||
240 | A { | ||
241 | a: "hello" | ||
242 | } | ||
243 | } | ||
244 | "#, | ||
245 | check_struct_shorthand_initialization, | ||
246 | ); | ||
247 | |||
248 | check_apply( | ||
249 | r#" | ||
250 | struct A { | ||
251 | a: &'static str | ||
252 | } | ||
253 | |||
254 | fn main() { | ||
255 | let a = "haha"; | ||
256 | A { | ||
257 | a: a | ||
258 | } | ||
259 | } | ||
260 | "#, | ||
261 | r#" | ||
262 | struct A { | ||
263 | a: &'static str | ||
264 | } | ||
265 | |||
266 | fn main() { | ||
267 | let a = "haha"; | ||
268 | A { | ||
269 | a | ||
270 | } | ||
271 | } | ||
272 | "#, | ||
273 | check_struct_shorthand_initialization, | ||
274 | ); | ||
275 | |||
276 | check_apply( | ||
277 | r#" | ||
278 | struct A { | ||
279 | a: &'static str, | ||
280 | b: &'static str | ||
281 | } | ||
282 | |||
283 | fn main() { | ||
284 | let a = "haha"; | ||
285 | let b = "bb"; | ||
286 | A { | ||
287 | a: a, | ||
288 | b | ||
289 | } | ||
290 | } | ||
291 | "#, | ||
292 | r#" | ||
293 | struct A { | ||
294 | a: &'static str, | ||
295 | b: &'static str | ||
296 | } | ||
297 | |||
298 | fn main() { | ||
299 | let a = "haha"; | ||
300 | let b = "bb"; | ||
301 | A { | ||
302 | a, | ||
303 | b | ||
304 | } | ||
305 | } | ||
306 | "#, | ||
307 | check_struct_shorthand_initialization, | ||
308 | ); | ||
309 | } | ||
65 | } | 310 | } |