diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-12-30 15:07:27 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-12-30 15:07:27 +0000 |
commit | 0f95d8523e0646aac8fb69c61f7b0d7a8efe42bb (patch) | |
tree | cdbd8fd24c5d9b187da0f8da069f5e270205d6a6 | |
parent | 55b57227e469c84f7888c538e9644d230cd449ca (diff) | |
parent | e1ef205a1915442229638dbb296de4466ee3d3da (diff) |
Merge #365
365: implement struct shorthand initialization diagnostic r=matklad a=gfreezy
Co-authored-by: gfreezy <[email protected]>
-rw-r--r-- | crates/ra_editor/src/diagnostics.rs | 276 | ||||
-rw-r--r-- | crates/ra_editor/src/lib.rs | 114 |
2 files changed, 278 insertions, 112 deletions
diff --git a/crates/ra_editor/src/diagnostics.rs b/crates/ra_editor/src/diagnostics.rs new file mode 100644 index 000000000..1b336cfe2 --- /dev/null +++ b/crates/ra_editor/src/diagnostics.rs | |||
@@ -0,0 +1,276 @@ | |||
1 | use itertools::Itertools; | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | ast::{self, AstNode}, | ||
5 | Location, | ||
6 | SourceFileNode, | ||
7 | SyntaxKind, | ||
8 | TextRange, | ||
9 | }; | ||
10 | use ra_syntax::SyntaxNodeRef; | ||
11 | use ra_text_edit::{ | ||
12 | TextEdit, | ||
13 | TextEditBuilder, | ||
14 | }; | ||
15 | |||
16 | use crate::{ | ||
17 | Diagnostic, | ||
18 | LocalEdit, | ||
19 | Severity, | ||
20 | }; | ||
21 | |||
22 | pub fn diagnostics(file: &SourceFileNode) -> Vec<Diagnostic> { | ||
23 | fn location_to_range(location: Location) -> TextRange { | ||
24 | match location { | ||
25 | Location::Offset(offset) => TextRange::offset_len(offset, 1.into()), | ||
26 | Location::Range(range) => range, | ||
27 | } | ||
28 | } | ||
29 | |||
30 | let mut errors: Vec<Diagnostic> = file | ||
31 | .errors() | ||
32 | .into_iter() | ||
33 | .map(|err| Diagnostic { | ||
34 | range: location_to_range(err.location()), | ||
35 | msg: format!("Syntax Error: {}", err), | ||
36 | severity: Severity::Error, | ||
37 | fix: None, | ||
38 | }) | ||
39 | .collect(); | ||
40 | |||
41 | for node in file.syntax().descendants() { | ||
42 | check_unnecessary_braces_in_use_statement(&mut errors, node); | ||
43 | check_struct_shorthand_initialization(&mut errors, node); | ||
44 | } | ||
45 | |||
46 | errors | ||
47 | } | ||
48 | |||
49 | fn check_unnecessary_braces_in_use_statement( | ||
50 | acc: &mut Vec<Diagnostic>, | ||
51 | node: SyntaxNodeRef, | ||
52 | ) -> Option<()> { | ||
53 | let use_tree_list = ast::UseTreeList::cast(node)?; | ||
54 | if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { | ||
55 | let range = use_tree_list.syntax().range(); | ||
56 | let edit = | ||
57 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(single_use_tree) | ||
58 | .unwrap_or_else(|| { | ||
59 | let to_replace = single_use_tree.syntax().text().to_string(); | ||
60 | let mut edit_builder = TextEditBuilder::new(); | ||
61 | edit_builder.delete(range); | ||
62 | edit_builder.insert(range.start(), to_replace); | ||
63 | edit_builder.finish() | ||
64 | }); | ||
65 | |||
66 | acc.push(Diagnostic { | ||
67 | range, | ||
68 | msg: format!("Unnecessary braces in use statement"), | ||
69 | severity: Severity::WeakWarning, | ||
70 | fix: Some(LocalEdit { | ||
71 | label: "Remove unnecessary braces".to_string(), | ||
72 | edit, | ||
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 | ||
86 | .path()? | ||
87 | .segment()? | ||
88 | .syntax() | ||
89 | .first_child()? | ||
90 | .kind() | ||
91 | == SyntaxKind::SELF_KW | ||
92 | { | ||
93 | let start = use_tree_list_node.prev_sibling()?.range().start(); | ||
94 | let end = use_tree_list_node.range().end(); | ||
95 | let range = TextRange::from_to(start, end); | ||
96 | let mut edit_builder = TextEditBuilder::new(); | ||
97 | edit_builder.delete(range); | ||
98 | return Some(edit_builder.finish()); | ||
99 | } | ||
100 | None | ||
101 | } | ||
102 | |||
103 | fn check_struct_shorthand_initialization( | ||
104 | acc: &mut Vec<Diagnostic>, | ||
105 | node: SyntaxNodeRef, | ||
106 | ) -> Option<()> { | ||
107 | let struct_lit = ast::StructLit::cast(node)?; | ||
108 | let named_field_list = struct_lit.named_field_list()?; | ||
109 | for named_field in named_field_list.fields() { | ||
110 | if let (Some(name_ref), Some(expr)) = (named_field.name_ref(), named_field.expr()) { | ||
111 | let field_name = name_ref.syntax().text().to_string(); | ||
112 | let field_expr = expr.syntax().text().to_string(); | ||
113 | if field_name == field_expr { | ||
114 | let mut edit_builder = TextEditBuilder::new(); | ||
115 | edit_builder.delete(named_field.syntax().range()); | ||
116 | edit_builder.insert(named_field.syntax().range().start(), field_name); | ||
117 | let edit = edit_builder.finish(); | ||
118 | |||
119 | acc.push(Diagnostic { | ||
120 | range: named_field.syntax().range(), | ||
121 | msg: format!("Shorthand struct initialization"), | ||
122 | severity: Severity::WeakWarning, | ||
123 | fix: Some(LocalEdit { | ||
124 | label: "use struct shorthand initialization".to_string(), | ||
125 | edit, | ||
126 | cursor_position: None, | ||
127 | }), | ||
128 | }); | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | Some(()) | ||
133 | } | ||
134 | |||
135 | #[cfg(test)] | ||
136 | mod tests { | ||
137 | use crate::test_utils::assert_eq_text; | ||
138 | |||
139 | use super::*; | ||
140 | |||
141 | type DiagnosticChecker = fn(&mut Vec<Diagnostic>, SyntaxNodeRef) -> Option<()>; | ||
142 | |||
143 | fn check_not_applicable(code: &str, func: DiagnosticChecker) { | ||
144 | let file = SourceFileNode::parse(code); | ||
145 | let mut diagnostics = Vec::new(); | ||
146 | for node in file.syntax().descendants() { | ||
147 | func(&mut diagnostics, node); | ||
148 | } | ||
149 | assert!(diagnostics.is_empty()); | ||
150 | } | ||
151 | |||
152 | fn check_apply(before: &str, after: &str, func: DiagnosticChecker) { | ||
153 | let file = SourceFileNode::parse(before); | ||
154 | let mut diagnostics = Vec::new(); | ||
155 | for node in file.syntax().descendants() { | ||
156 | func(&mut diagnostics, node); | ||
157 | } | ||
158 | let diagnostic = diagnostics | ||
159 | .pop() | ||
160 | .unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); | ||
161 | let fix = diagnostic.fix.unwrap(); | ||
162 | let actual = fix.edit.apply(&before); | ||
163 | assert_eq_text!(after, &actual); | ||
164 | } | ||
165 | |||
166 | #[test] | ||
167 | fn test_check_unnecessary_braces_in_use_statement() { | ||
168 | check_not_applicable( | ||
169 | " | ||
170 | use a; | ||
171 | use a::{c, d::e}; | ||
172 | ", | ||
173 | check_unnecessary_braces_in_use_statement, | ||
174 | ); | ||
175 | check_apply( | ||
176 | "use {b};", | ||
177 | "use b;", | ||
178 | check_unnecessary_braces_in_use_statement, | ||
179 | ); | ||
180 | check_apply( | ||
181 | "use a::{c};", | ||
182 | "use a::c;", | ||
183 | check_unnecessary_braces_in_use_statement, | ||
184 | ); | ||
185 | check_apply( | ||
186 | "use a::{self};", | ||
187 | "use a;", | ||
188 | check_unnecessary_braces_in_use_statement, | ||
189 | ); | ||
190 | check_apply( | ||
191 | "use a::{c, d::{e}};", | ||
192 | "use a::{c, d::e};", | ||
193 | check_unnecessary_braces_in_use_statement, | ||
194 | ); | ||
195 | } | ||
196 | |||
197 | #[test] | ||
198 | fn test_check_struct_shorthand_initialization() { | ||
199 | check_not_applicable( | ||
200 | r#" | ||
201 | struct A { | ||
202 | a: &'static str | ||
203 | } | ||
204 | |||
205 | fn main() { | ||
206 | A { | ||
207 | a: "hello" | ||
208 | } | ||
209 | } | ||
210 | "#, | ||
211 | check_struct_shorthand_initialization, | ||
212 | ); | ||
213 | |||
214 | check_apply( | ||
215 | r#" | ||
216 | struct A { | ||
217 | a: &'static str | ||
218 | } | ||
219 | |||
220 | fn main() { | ||
221 | let a = "haha"; | ||
222 | A { | ||
223 | a: a | ||
224 | } | ||
225 | } | ||
226 | "#, | ||
227 | r#" | ||
228 | struct A { | ||
229 | a: &'static str | ||
230 | } | ||
231 | |||
232 | fn main() { | ||
233 | let a = "haha"; | ||
234 | A { | ||
235 | a | ||
236 | } | ||
237 | } | ||
238 | "#, | ||
239 | check_struct_shorthand_initialization, | ||
240 | ); | ||
241 | |||
242 | check_apply( | ||
243 | r#" | ||
244 | struct A { | ||
245 | a: &'static str, | ||
246 | b: &'static str | ||
247 | } | ||
248 | |||
249 | fn main() { | ||
250 | let a = "haha"; | ||
251 | let b = "bb"; | ||
252 | A { | ||
253 | a: a, | ||
254 | b | ||
255 | } | ||
256 | } | ||
257 | "#, | ||
258 | r#" | ||
259 | struct A { | ||
260 | a: &'static str, | ||
261 | b: &'static str | ||
262 | } | ||
263 | |||
264 | fn main() { | ||
265 | let a = "haha"; | ||
266 | let b = "bb"; | ||
267 | A { | ||
268 | a, | ||
269 | b | ||
270 | } | ||
271 | } | ||
272 | "#, | ||
273 | check_struct_shorthand_initialization, | ||
274 | ); | ||
275 | } | ||
276 | } | ||
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs index 9043026c1..a65637d52 100644 --- a/crates/ra_editor/src/lib.rs +++ b/crates/ra_editor/src/lib.rs | |||
@@ -7,6 +7,7 @@ mod symbols; | |||
7 | #[cfg(test)] | 7 | #[cfg(test)] |
8 | mod test_utils; | 8 | mod test_utils; |
9 | mod typing; | 9 | mod typing; |
10 | mod diagnostics; | ||
10 | 11 | ||
11 | pub use self::{ | 12 | pub use self::{ |
12 | code_actions::{add_derive, add_impl, flip_comma, introduce_variable, make_pub_crate, LocalEdit}, | 13 | code_actions::{add_derive, add_impl, flip_comma, introduce_variable, make_pub_crate, LocalEdit}, |
@@ -16,17 +17,16 @@ pub use self::{ | |||
16 | line_index_utils::translate_offset_with_edit, | 17 | line_index_utils::translate_offset_with_edit, |
17 | symbols::{file_structure, file_symbols, FileSymbol, StructureNode}, | 18 | symbols::{file_structure, file_symbols, FileSymbol, StructureNode}, |
18 | typing::{join_lines, on_enter, on_eq_typed}, | 19 | typing::{join_lines, on_enter, on_eq_typed}, |
20 | diagnostics::diagnostics | ||
19 | }; | 21 | }; |
20 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 22 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
21 | use ra_syntax::{ | 23 | use ra_syntax::{ |
22 | algo::find_leaf_at_offset, | 24 | algo::find_leaf_at_offset, |
23 | ast::{self, AstNode, NameOwner}, | 25 | ast::{self, AstNode, NameOwner}, |
24 | SourceFileNode, | 26 | SourceFileNode, |
25 | Location, | ||
26 | SyntaxKind::{self, *}, | 27 | SyntaxKind::{self, *}, |
27 | SyntaxNodeRef, TextRange, TextUnit, Direction, | 28 | SyntaxNodeRef, TextRange, TextUnit, Direction, |
28 | }; | 29 | }; |
29 | use itertools::Itertools; | ||
30 | use rustc_hash::FxHashSet; | 30 | use rustc_hash::FxHashSet; |
31 | 31 | ||
32 | #[derive(Debug)] | 32 | #[derive(Debug)] |
@@ -129,87 +129,6 @@ pub fn highlight(file: &SourceFileNode) -> Vec<HighlightedRange> { | |||
129 | res | 129 | res |
130 | } | 130 | } |
131 | 131 | ||
132 | pub fn diagnostics(file: &SourceFileNode) -> Vec<Diagnostic> { | ||
133 | fn location_to_range(location: Location) -> TextRange { | ||
134 | match location { | ||
135 | Location::Offset(offset) => TextRange::offset_len(offset, 1.into()), | ||
136 | Location::Range(range) => range, | ||
137 | } | ||
138 | } | ||
139 | |||
140 | let mut errors: Vec<Diagnostic> = file | ||
141 | .errors() | ||
142 | .into_iter() | ||
143 | .map(|err| Diagnostic { | ||
144 | range: location_to_range(err.location()), | ||
145 | msg: format!("Syntax Error: {}", err), | ||
146 | severity: Severity::Error, | ||
147 | fix: None, | ||
148 | }) | ||
149 | .collect(); | ||
150 | |||
151 | let warnings = check_unnecessary_braces_in_use_statement(file); | ||
152 | |||
153 | errors.extend(warnings); | ||
154 | errors | ||
155 | } | ||
156 | |||
157 | fn check_unnecessary_braces_in_use_statement(file: &SourceFileNode) -> Vec<Diagnostic> { | ||
158 | let mut diagnostics = Vec::new(); | ||
159 | for node in file.syntax().descendants() { | ||
160 | if let Some(use_tree_list) = ast::UseTreeList::cast(node) { | ||
161 | if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { | ||
162 | let range = use_tree_list.syntax().range(); | ||
163 | let edit = text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | ||
164 | single_use_tree, | ||
165 | ) | ||
166 | .unwrap_or_else(|| { | ||
167 | let to_replace = single_use_tree.syntax().text().to_string(); | ||
168 | let mut edit_builder = TextEditBuilder::new(); | ||
169 | edit_builder.delete(range); | ||
170 | edit_builder.insert(range.start(), to_replace); | ||
171 | edit_builder.finish() | ||
172 | }); | ||
173 | |||
174 | diagnostics.push(Diagnostic { | ||
175 | range: range, | ||
176 | msg: format!("Unnecessary braces in use statement"), | ||
177 | severity: Severity::WeakWarning, | ||
178 | fix: Some(LocalEdit { | ||
179 | label: "Remove unnecessary braces".to_string(), | ||
180 | edit: edit, | ||
181 | cursor_position: None, | ||
182 | }), | ||
183 | }) | ||
184 | } | ||
185 | } | ||
186 | } | ||
187 | |||
188 | diagnostics | ||
189 | } | ||
190 | |||
191 | fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | ||
192 | single_use_tree: ast::UseTree, | ||
193 | ) -> Option<TextEdit> { | ||
194 | let use_tree_list_node = single_use_tree.syntax().parent()?; | ||
195 | if single_use_tree | ||
196 | .path()? | ||
197 | .segment()? | ||
198 | .syntax() | ||
199 | .first_child()? | ||
200 | .kind() | ||
201 | == SyntaxKind::SELF_KW | ||
202 | { | ||
203 | let start = use_tree_list_node.prev_sibling()?.range().start(); | ||
204 | let end = use_tree_list_node.range().end(); | ||
205 | let range = TextRange::from_to(start, end); | ||
206 | let mut edit_builder = TextEditBuilder::new(); | ||
207 | edit_builder.delete(range); | ||
208 | return Some(edit_builder.finish()); | ||
209 | } | ||
210 | None | ||
211 | } | ||
212 | |||
213 | pub fn syntax_tree(file: &SourceFileNode) -> String { | 132 | pub fn syntax_tree(file: &SourceFileNode) -> String { |
214 | ::ra_syntax::utils::dump_tree(file.syntax()) | 133 | ::ra_syntax::utils::dump_tree(file.syntax()) |
215 | } | 134 | } |
@@ -310,33 +229,4 @@ fn test_foo() {} | |||
310 | do_check("struct Foo { a: i32, }<|>", "struct Foo <|>{ a: i32, }"); | 229 | do_check("struct Foo { a: i32, }<|>", "struct Foo <|>{ a: i32, }"); |
311 | } | 230 | } |
312 | 231 | ||
313 | #[test] | ||
314 | fn test_check_unnecessary_braces_in_use_statement() { | ||
315 | fn check_not_applicable(code: &str) { | ||
316 | let file = SourceFileNode::parse(code); | ||
317 | let diagnostics = check_unnecessary_braces_in_use_statement(&file); | ||
318 | assert!(diagnostics.is_empty()); | ||
319 | } | ||
320 | |||
321 | fn check_apply(before: &str, after: &str) { | ||
322 | let file = SourceFileNode::parse(before); | ||
323 | let diagnostic = check_unnecessary_braces_in_use_statement(&file) | ||
324 | .pop() | ||
325 | .unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); | ||
326 | let fix = diagnostic.fix.unwrap(); | ||
327 | let actual = fix.edit.apply(&before); | ||
328 | assert_eq_text!(after, &actual); | ||
329 | } | ||
330 | |||
331 | check_not_applicable( | ||
332 | " | ||
333 | use a; | ||
334 | use a::{c, d::e}; | ||
335 | ", | ||
336 | ); | ||
337 | check_apply("use {b};", "use b;"); | ||
338 | check_apply("use a::{c};", "use a::c;"); | ||
339 | check_apply("use a::{self};", "use a;"); | ||
340 | check_apply("use a::{c, d::{e}};", "use a::{c, d::e};"); | ||
341 | } | ||
342 | } | 232 | } |