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