|
| 1 | +use itertools::Itertools; |
1 | 2 | use hir::{Problem, source_binder};
|
2 | 3 | use ra_ide_api_light::Severity;
|
3 | 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 | 14 | pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> {
|
8 |
| - let syntax = db.parse(file_id); |
9 |
| - |
10 |
| - let mut res = ra_ide_api_light::diagnostics(&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<_>>(); |
| 15 | + let source_file = db.parse(file_id); |
| 16 | + let mut res = Vec::new(); |
| 17 | + |
| 18 | + syntax_errors(&mut res, &source_file); |
| 19 | + |
| 20 | + for node in source_file.syntax().descendants() { |
| 21 | + check_unnecessary_braces_in_use_statement(&mut res, file_id, node); |
| 22 | + check_struct_shorthand_initialization(&mut res, file_id, node); |
| 23 | + } |
| 24 | + |
19 | 25 | if let Some(m) = source_binder::module_from_file_id(db, file_id) {
|
20 |
| - for (name_node, problem) in m.problems(db) { |
21 |
| - let source_root = db.file_source_root(file_id); |
22 |
| - let diag = match problem { |
23 |
| - Problem::UnresolvedModule { candidate } => { |
24 |
| - let create_file = |
25 |
| - FileSystemEdit::CreateFile { source_root, path: candidate.clone() }; |
26 |
| - let fix = SourceChange { |
27 |
| - label: "create module".to_string(), |
28 |
| - source_file_edits: Vec::new(), |
29 |
| - file_system_edits: vec![create_file], |
| 26 | + check_module(&mut res, db, file_id, m); |
| 27 | + }; |
| 28 | + res |
| 29 | +} |
| 30 | + |
| 31 | +fn syntax_errors(acc: &mut Vec<Diagnostic>, source_file: &SourceFile) { |
| 32 | + fn location_to_range(location: Location) -> TextRange { |
| 33 | + match location { |
| 34 | + Location::Offset(offset) => TextRange::offset_len(offset, 1.into()), |
| 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 | 121 | cursor_position: None,
|
31 |
| - }; |
32 |
| - Diagnostic { |
33 |
| - range: name_node.range(), |
34 |
| - message: "unresolved module".to_string(), |
35 |
| - severity: Severity::Error, |
36 |
| - fix: Some(fix), |
37 |
| - } |
| 122 | + }), |
| 123 | + }); |
| 124 | + } |
| 125 | + } |
| 126 | + } |
| 127 | + Some(()) |
| 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 } => { |
40 |
| - let move_file = FileSystemEdit::MoveFile { |
41 |
| - src: file_id, |
42 |
| - dst_source_root: source_root, |
43 |
| - dst_path: move_to.clone(), |
44 |
| - }; |
45 |
| - let create_file = |
46 |
| - FileSystemEdit::CreateFile { source_root, path: move_to.join(candidate) }; |
47 |
| - let fix = SourceChange { |
48 |
| - label: "move file and create module".to_string(), |
49 |
| - source_file_edits: Vec::new(), |
50 |
| - file_system_edits: vec![move_file, create_file], |
51 |
| - cursor_position: None, |
52 |
| - }; |
53 |
| - Diagnostic { |
54 |
| - range: name_node.range(), |
55 |
| - message: "can't declare module at this location".to_string(), |
56 |
| - severity: Severity::Error, |
57 |
| - fix: Some(fix), |
58 |
| - } |
| 154 | + } |
| 155 | + Problem::NotDirOwner { move_to, candidate } => { |
| 156 | + let move_file = FileSystemEdit::MoveFile { |
| 157 | + src: file_id, |
| 158 | + dst_source_root: source_root, |
| 159 | + dst_path: move_to.clone(), |
| 160 | + }; |
| 161 | + let create_file = |
| 162 | + FileSystemEdit::CreateFile { source_root, path: move_to.join(candidate) }; |
| 163 | + let fix = SourceChange { |
| 164 | + label: "move file and create module".to_string(), |
| 165 | + source_file_edits: Vec::new(), |
| 166 | + file_system_edits: vec![move_file, create_file], |
| 167 | + cursor_position: None, |
| 168 | + }; |
| 169 | + Diagnostic { |
| 170 | + range: name_node.range(), |
| 171 | + message: "can't declare module at this location".to_string(), |
| 172 | + severity: Severity::Error, |
| 173 | + fix: Some(fix), |
59 | 174 | }
|
60 |
| - }; |
61 |
| - res.push(diag) |
| 175 | + } |
| 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 |
| - }; |
64 |
| - res |
| 195 | + assert!(diagnostics.is_empty()); |
| 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 | }
|
0 commit comments