diff options
author | gfreezy <[email protected]> | 2018-12-30 13:31:01 +0000 |
---|---|---|
committer | gfreezy <[email protected]> | 2018-12-30 14:26:59 +0000 |
commit | a3520bf01dd4709ea1c9694f91cbb72d1eac3924 (patch) | |
tree | c2422f89d57a46e213760835638f97107f73f2e1 /crates/ra_editor/src/diagnostics.rs | |
parent | 4a3d6aa26a1c71dadc91f37f9d5f8662b51c138a (diff) |
implement struct shorthand initialization diagnostic
Diffstat (limited to 'crates/ra_editor/src/diagnostics.rs')
-rw-r--r-- | crates/ra_editor/src/diagnostics.rs | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/crates/ra_editor/src/diagnostics.rs b/crates/ra_editor/src/diagnostics.rs new file mode 100644 index 000000000..d1667fd7c --- /dev/null +++ b/crates/ra_editor/src/diagnostics.rs | |||
@@ -0,0 +1,266 @@ | |||
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_text_edit::{ | ||
11 | TextEdit, | ||
12 | TextEditBuilder, | ||
13 | }; | ||
14 | |||
15 | use crate::{ | ||
16 | Diagnostic, | ||
17 | LocalEdit, | ||
18 | Severity, | ||
19 | }; | ||
20 | |||
21 | pub fn diagnostics(file: &SourceFileNode) -> Vec<Diagnostic> { | ||
22 | fn location_to_range(location: Location) -> TextRange { | ||
23 | match location { | ||
24 | Location::Offset(offset) => TextRange::offset_len(offset, 1.into()), | ||
25 | Location::Range(range) => range, | ||
26 | } | ||
27 | } | ||
28 | |||
29 | let mut errors: Vec<Diagnostic> = file | ||
30 | .errors() | ||
31 | .into_iter() | ||
32 | .map(|err| Diagnostic { | ||
33 | range: location_to_range(err.location()), | ||
34 | msg: format!("Syntax Error: {}", err), | ||
35 | severity: Severity::Error, | ||
36 | fix: None, | ||
37 | }) | ||
38 | .collect(); | ||
39 | |||
40 | errors.extend(check_unnecessary_braces_in_use_statement(file)); | ||
41 | errors.extend(check_struct_shorthand_initialization(file)); | ||
42 | errors | ||
43 | } | ||
44 | |||
45 | fn check_unnecessary_braces_in_use_statement(file: &SourceFileNode) -> Vec<Diagnostic> { | ||
46 | let mut diagnostics = Vec::new(); | ||
47 | for use_tree_list in file | ||
48 | .syntax() | ||
49 | .descendants() | ||
50 | .filter_map(ast::UseTreeList::cast) | ||
51 | { | ||
52 | if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { | ||
53 | let range = use_tree_list.syntax().range(); | ||
54 | let edit = | ||
55 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(single_use_tree) | ||
56 | .unwrap_or_else(|| { | ||
57 | let to_replace = single_use_tree.syntax().text().to_string(); | ||
58 | let mut edit_builder = TextEditBuilder::new(); | ||
59 | edit_builder.delete(range); | ||
60 | edit_builder.insert(range.start(), to_replace); | ||
61 | edit_builder.finish() | ||
62 | }); | ||
63 | |||
64 | diagnostics.push(Diagnostic { | ||
65 | range, | ||
66 | msg: format!("Unnecessary braces in use statement"), | ||
67 | severity: Severity::WeakWarning, | ||
68 | fix: Some(LocalEdit { | ||
69 | label: "Remove unnecessary braces".to_string(), | ||
70 | edit, | ||
71 | cursor_position: None, | ||
72 | }), | ||
73 | }) | ||
74 | } | ||
75 | } | ||
76 | |||
77 | diagnostics | ||
78 | } | ||
79 | |||
80 | fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | ||
81 | single_use_tree: ast::UseTree, | ||
82 | ) -> Option<TextEdit> { | ||
83 | let use_tree_list_node = single_use_tree.syntax().parent()?; | ||
84 | if single_use_tree | ||
85 | .path()? | ||
86 | .segment()? | ||
87 | .syntax() | ||
88 | .first_child()? | ||
89 | .kind() | ||
90 | == SyntaxKind::SELF_KW | ||
91 | { | ||
92 | let start = use_tree_list_node.prev_sibling()?.range().start(); | ||
93 | let end = use_tree_list_node.range().end(); | ||
94 | let range = TextRange::from_to(start, end); | ||
95 | let mut edit_builder = TextEditBuilder::new(); | ||
96 | edit_builder.delete(range); | ||
97 | return Some(edit_builder.finish()); | ||
98 | } | ||
99 | None | ||
100 | } | ||
101 | |||
102 | fn check_struct_shorthand_initialization(file: &SourceFileNode) -> Vec<Diagnostic> { | ||
103 | let mut diagnostics = Vec::new(); | ||
104 | for struct_lit in file.syntax().descendants().filter_map(ast::StructLit::cast) { | ||
105 | if let Some(named_field_list) = struct_lit.named_field_list() { | ||
106 | for named_field in named_field_list.fields() { | ||
107 | if let (Some(name_ref), Some(expr)) = (named_field.name_ref(), named_field.expr()) { | ||
108 | let field_name = name_ref.syntax().text().to_string(); | ||
109 | let field_expr = expr.syntax().text().to_string(); | ||
110 | if field_name == field_expr { | ||
111 | let mut edit_builder = TextEditBuilder::new(); | ||
112 | edit_builder.delete(named_field.syntax().range()); | ||
113 | edit_builder.insert(named_field.syntax().range().start(), field_name); | ||
114 | let edit = edit_builder.finish(); | ||
115 | |||
116 | diagnostics.push(Diagnostic { | ||
117 | range: named_field.syntax().range(), | ||
118 | msg: format!("Shorthand struct initialization"), | ||
119 | severity: Severity::WeakWarning, | ||
120 | fix: Some(LocalEdit { | ||
121 | label: "use struct shorthand initialization".to_string(), | ||
122 | edit, | ||
123 | cursor_position: None, | ||
124 | }), | ||
125 | }); | ||
126 | } | ||
127 | } | ||
128 | } | ||
129 | } | ||
130 | } | ||
131 | diagnostics | ||
132 | } | ||
133 | |||
134 | #[cfg(test)] | ||
135 | mod tests { | ||
136 | use crate::test_utils::assert_eq_text; | ||
137 | |||
138 | use super::*; | ||
139 | |||
140 | fn check_not_applicable(code: &str, func: fn(file: &SourceFileNode) -> Vec<Diagnostic>) { | ||
141 | let file = SourceFileNode::parse(code); | ||
142 | let diagnostics = func(&file); | ||
143 | assert!(diagnostics.is_empty()); | ||
144 | } | ||
145 | |||
146 | fn check_apply(before: &str, after: &str, func: fn(file: &SourceFileNode) -> Vec<Diagnostic>) { | ||
147 | let file = SourceFileNode::parse(before); | ||
148 | let diagnostic = func(&file) | ||
149 | .pop() | ||
150 | .unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); | ||
151 | let fix = diagnostic.fix.unwrap(); | ||
152 | let actual = fix.edit.apply(&before); | ||
153 | assert_eq_text!(after, &actual); | ||
154 | } | ||
155 | |||
156 | #[test] | ||
157 | fn test_check_unnecessary_braces_in_use_statement() { | ||
158 | check_not_applicable( | ||
159 | " | ||
160 | use a; | ||
161 | use a::{c, d::e}; | ||
162 | ", | ||
163 | check_unnecessary_braces_in_use_statement, | ||
164 | ); | ||
165 | check_apply( | ||
166 | "use {b};", | ||
167 | "use b;", | ||
168 | check_unnecessary_braces_in_use_statement, | ||
169 | ); | ||
170 | check_apply( | ||
171 | "use a::{c};", | ||
172 | "use a::c;", | ||
173 | check_unnecessary_braces_in_use_statement, | ||
174 | ); | ||
175 | check_apply( | ||
176 | "use a::{self};", | ||
177 | "use a;", | ||
178 | check_unnecessary_braces_in_use_statement, | ||
179 | ); | ||
180 | check_apply( | ||
181 | "use a::{c, d::{e}};", | ||
182 | "use a::{c, d::e};", | ||
183 | check_unnecessary_braces_in_use_statement, | ||
184 | ); | ||
185 | } | ||
186 | |||
187 | #[test] | ||
188 | fn test_check_struct_shorthand_initialization() { | ||
189 | check_not_applicable( | ||
190 | r#" | ||
191 | struct A { | ||
192 | a: &'static str | ||
193 | } | ||
194 | |||
195 | fn main() { | ||
196 | A { | ||
197 | a: "hello" | ||
198 | } | ||
199 | } | ||
200 | "#, | ||
201 | check_struct_shorthand_initialization, | ||
202 | ); | ||
203 | |||
204 | check_apply( | ||
205 | r#" | ||
206 | struct A { | ||
207 | a: &'static str | ||
208 | } | ||
209 | |||
210 | fn main() { | ||
211 | let a = "haha"; | ||
212 | A { | ||
213 | a: a | ||
214 | } | ||
215 | } | ||
216 | "#, | ||
217 | r#" | ||
218 | struct A { | ||
219 | a: &'static str | ||
220 | } | ||
221 | |||
222 | fn main() { | ||
223 | let a = "haha"; | ||
224 | A { | ||
225 | a | ||
226 | } | ||
227 | } | ||
228 | "#, | ||
229 | check_struct_shorthand_initialization, | ||
230 | ); | ||
231 | |||
232 | check_apply( | ||
233 | r#" | ||
234 | struct A { | ||
235 | a: &'static str, | ||
236 | b: &'static str | ||
237 | } | ||
238 | |||
239 | fn main() { | ||
240 | let a = "haha"; | ||
241 | let b = "bb"; | ||
242 | A { | ||
243 | a: a, | ||
244 | b | ||
245 | } | ||
246 | } | ||
247 | "#, | ||
248 | r#" | ||
249 | struct A { | ||
250 | a: &'static str, | ||
251 | b: &'static str | ||
252 | } | ||
253 | |||
254 | fn main() { | ||
255 | let a = "haha"; | ||
256 | let b = "bb"; | ||
257 | A { | ||
258 | a, | ||
259 | b | ||
260 | } | ||
261 | } | ||
262 | "#, | ||
263 | check_struct_shorthand_initialization, | ||
264 | ); | ||
265 | } | ||
266 | } | ||