diff options
Diffstat (limited to 'crates/ra_editor/src/lib.rs')
-rw-r--r-- | crates/ra_editor/src/lib.rs | 149 |
1 files changed, 34 insertions, 115 deletions
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs index d9b89155b..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,17 @@ 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, | 28 | SyntaxNodeRef, TextRange, TextUnit, Direction, |
28 | }; | 29 | }; |
29 | use itertools::Itertools; | 30 | use rustc_hash::FxHashSet; |
30 | 31 | ||
31 | #[derive(Debug)] | 32 | #[derive(Debug)] |
32 | pub struct HighlightedRange { | 33 | pub struct HighlightedRange { |
@@ -79,8 +80,13 @@ pub fn matching_brace(file: &SourceFileNode, offset: TextUnit) -> Option<TextUni | |||
79 | } | 80 | } |
80 | 81 | ||
81 | pub fn highlight(file: &SourceFileNode) -> Vec<HighlightedRange> { | 82 | pub fn highlight(file: &SourceFileNode) -> Vec<HighlightedRange> { |
83 | // Visited nodes to handle highlighting priorities | ||
84 | let mut highlighted = FxHashSet::default(); | ||
82 | let mut res = Vec::new(); | 85 | let mut res = Vec::new(); |
83 | for node in file.syntax().descendants() { | 86 | for node in file.syntax().descendants() { |
87 | if highlighted.contains(&node) { | ||
88 | continue; | ||
89 | } | ||
84 | let tag = match node.kind() { | 90 | let tag = match node.kind() { |
85 | COMMENT => "comment", | 91 | COMMENT => "comment", |
86 | STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string", | 92 | STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string", |
@@ -90,7 +96,30 @@ pub fn highlight(file: &SourceFileNode) -> Vec<HighlightedRange> { | |||
90 | INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal", | 96 | INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal", |
91 | LIFETIME => "parameter", | 97 | LIFETIME => "parameter", |
92 | k if k.is_keyword() => "keyword", | 98 | k if k.is_keyword() => "keyword", |
93 | _ => continue, | 99 | _ => { |
100 | if let Some(macro_call) = ast::MacroCall::cast(node) { | ||
101 | if let Some(path) = macro_call.path() { | ||
102 | if let Some(segment) = path.segment() { | ||
103 | if let Some(name_ref) = segment.name_ref() { | ||
104 | highlighted.insert(name_ref.syntax()); | ||
105 | let range_start = name_ref.syntax().range().start(); | ||
106 | let mut range_end = name_ref.syntax().range().end(); | ||
107 | for sibling in path.syntax().siblings(Direction::Next) { | ||
108 | match sibling.kind() { | ||
109 | EXCL | IDENT => range_end = sibling.range().end(), | ||
110 | _ => (), | ||
111 | } | ||
112 | } | ||
113 | res.push(HighlightedRange { | ||
114 | range: TextRange::from_to(range_start, range_end), | ||
115 | tag: "macro", | ||
116 | }) | ||
117 | } | ||
118 | } | ||
119 | } | ||
120 | } | ||
121 | continue; | ||
122 | } | ||
94 | }; | 123 | }; |
95 | res.push(HighlightedRange { | 124 | res.push(HighlightedRange { |
96 | range: node.range(), | 125 | range: node.range(), |
@@ -100,87 +129,6 @@ pub fn highlight(file: &SourceFileNode) -> Vec<HighlightedRange> { | |||
100 | res | 129 | res |
101 | } | 130 | } |
102 | 131 | ||
103 | pub fn diagnostics(file: &SourceFileNode) -> Vec<Diagnostic> { | ||
104 | fn location_to_range(location: Location) -> TextRange { | ||
105 | match location { | ||
106 | Location::Offset(offset) => TextRange::offset_len(offset, 1.into()), | ||
107 | Location::Range(range) => range, | ||
108 | } | ||
109 | } | ||
110 | |||
111 | let mut errors: Vec<Diagnostic> = file | ||
112 | .errors() | ||
113 | .into_iter() | ||
114 | .map(|err| Diagnostic { | ||
115 | range: location_to_range(err.location()), | ||
116 | msg: format!("Syntax Error: {}", err), | ||
117 | severity: Severity::Error, | ||
118 | fix: None, | ||
119 | }) | ||
120 | .collect(); | ||
121 | |||
122 | let warnings = check_unnecessary_braces_in_use_statement(file); | ||
123 | |||
124 | errors.extend(warnings); | ||
125 | errors | ||
126 | } | ||
127 | |||
128 | fn check_unnecessary_braces_in_use_statement(file: &SourceFileNode) -> Vec<Diagnostic> { | ||
129 | let mut diagnostics = Vec::new(); | ||
130 | for node in file.syntax().descendants() { | ||
131 | if let Some(use_tree_list) = ast::UseTreeList::cast(node) { | ||
132 | if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { | ||
133 | let range = use_tree_list.syntax().range(); | ||
134 | let edit = text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | ||
135 | single_use_tree, | ||
136 | ) | ||
137 | .unwrap_or_else(|| { | ||
138 | let to_replace = single_use_tree.syntax().text().to_string(); | ||
139 | let mut edit_builder = TextEditBuilder::new(); | ||
140 | edit_builder.delete(range); | ||
141 | edit_builder.insert(range.start(), to_replace); | ||
142 | edit_builder.finish() | ||
143 | }); | ||
144 | |||
145 | diagnostics.push(Diagnostic { | ||
146 | range: range, | ||
147 | msg: format!("Unnecessary braces in use statement"), | ||
148 | severity: Severity::WeakWarning, | ||
149 | fix: Some(LocalEdit { | ||
150 | label: "Remove unnecessary braces".to_string(), | ||
151 | edit: edit, | ||
152 | cursor_position: None, | ||
153 | }), | ||
154 | }) | ||
155 | } | ||
156 | } | ||
157 | } | ||
158 | |||
159 | diagnostics | ||
160 | } | ||
161 | |||
162 | fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | ||
163 | single_use_tree: ast::UseTree, | ||
164 | ) -> Option<TextEdit> { | ||
165 | let use_tree_list_node = single_use_tree.syntax().parent()?; | ||
166 | if single_use_tree | ||
167 | .path()? | ||
168 | .segment()? | ||
169 | .syntax() | ||
170 | .first_child()? | ||
171 | .kind() | ||
172 | == SyntaxKind::SELF_KW | ||
173 | { | ||
174 | let start = use_tree_list_node.prev_sibling()?.range().start(); | ||
175 | let end = use_tree_list_node.range().end(); | ||
176 | let range = TextRange::from_to(start, end); | ||
177 | let mut edit_builder = TextEditBuilder::new(); | ||
178 | edit_builder.delete(range); | ||
179 | return Some(edit_builder.finish()); | ||
180 | } | ||
181 | None | ||
182 | } | ||
183 | |||
184 | pub fn syntax_tree(file: &SourceFileNode) -> String { | 132 | pub fn syntax_tree(file: &SourceFileNode) -> String { |
185 | ::ra_syntax::utils::dump_tree(file.syntax()) | 133 | ::ra_syntax::utils::dump_tree(file.syntax()) |
186 | } | 134 | } |
@@ -235,7 +183,7 @@ fn main() {} | |||
235 | r#"[HighlightedRange { range: [1; 11), tag: "comment" }, | 183 | r#"[HighlightedRange { range: [1; 11), tag: "comment" }, |
236 | HighlightedRange { range: [12; 14), tag: "keyword" }, | 184 | HighlightedRange { range: [12; 14), tag: "keyword" }, |
237 | HighlightedRange { range: [15; 19), tag: "function" }, | 185 | HighlightedRange { range: [15; 19), tag: "function" }, |
238 | HighlightedRange { range: [29; 36), tag: "text" }, | 186 | HighlightedRange { range: [29; 37), tag: "macro" }, |
239 | HighlightedRange { range: [38; 50), tag: "string" }, | 187 | HighlightedRange { range: [38; 50), tag: "string" }, |
240 | HighlightedRange { range: [52; 54), tag: "literal" }]"#, | 188 | HighlightedRange { range: [52; 54), tag: "literal" }]"#, |
241 | &hls, | 189 | &hls, |
@@ -281,33 +229,4 @@ fn test_foo() {} | |||
281 | do_check("struct Foo { a: i32, }<|>", "struct Foo <|>{ a: i32, }"); | 229 | do_check("struct Foo { a: i32, }<|>", "struct Foo <|>{ a: i32, }"); |
282 | } | 230 | } |
283 | 231 | ||
284 | #[test] | ||
285 | fn test_check_unnecessary_braces_in_use_statement() { | ||
286 | fn check_not_applicable(code: &str) { | ||
287 | let file = SourceFileNode::parse(code); | ||
288 | let diagnostics = check_unnecessary_braces_in_use_statement(&file); | ||
289 | assert!(diagnostics.is_empty()); | ||
290 | } | ||
291 | |||
292 | fn check_apply(before: &str, after: &str) { | ||
293 | let file = SourceFileNode::parse(before); | ||
294 | let diagnostic = check_unnecessary_braces_in_use_statement(&file) | ||
295 | .pop() | ||
296 | .unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); | ||
297 | let fix = diagnostic.fix.unwrap(); | ||
298 | let actual = fix.edit.apply(&before); | ||
299 | assert_eq_text!(after, &actual); | ||
300 | } | ||
301 | |||
302 | check_not_applicable( | ||
303 | " | ||
304 | use a; | ||
305 | use a::{c, d::e}; | ||
306 | ", | ||
307 | ); | ||
308 | check_apply("use {b};", "use b;"); | ||
309 | check_apply("use a::{c};", "use a::c;"); | ||
310 | check_apply("use a::{self};", "use a;"); | ||
311 | check_apply("use a::{c, d::{e}};", "use a::{c, d::e};"); | ||
312 | } | ||
313 | } | 232 | } |