diff options
Diffstat (limited to 'crates/ra_ide/src')
-rw-r--r-- | crates/ra_ide/src/completion/complete_snippet.rs | 8 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/completion_context.rs | 10 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/presentation.rs | 31 | ||||
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 182 | ||||
-rw-r--r-- | crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs | 171 | ||||
-rw-r--r-- | crates/ra_ide/src/display/short_label.rs | 12 | ||||
-rw-r--r-- | crates/ra_ide/src/folding_ranges.rs | 23 | ||||
-rw-r--r-- | crates/ra_ide/src/goto_definition.rs | 36 | ||||
-rw-r--r-- | crates/ra_ide/src/hover.rs | 88 | ||||
-rw-r--r-- | crates/ra_ide/src/lib.rs | 10 | ||||
-rw-r--r-- | crates/ra_ide/src/references.rs | 4 | ||||
-rw-r--r-- | crates/ra_ide/src/references/rename.rs | 2 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting.rs | 150 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/injection.rs | 5 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/tests.rs | 71 |
15 files changed, 575 insertions, 228 deletions
diff --git a/crates/ra_ide/src/completion/complete_snippet.rs b/crates/ra_ide/src/completion/complete_snippet.rs index 28d8f7876..4368e4eec 100644 --- a/crates/ra_ide/src/completion/complete_snippet.rs +++ b/crates/ra_ide/src/completion/complete_snippet.rs | |||
@@ -36,7 +36,7 @@ pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionConte | |||
36 | snippet( | 36 | snippet( |
37 | ctx, | 37 | ctx, |
38 | cap, | 38 | cap, |
39 | "Test module", | 39 | "tmod (Test module)", |
40 | "\ | 40 | "\ |
41 | #[cfg(test)] | 41 | #[cfg(test)] |
42 | mod tests { | 42 | mod tests { |
@@ -54,7 +54,7 @@ mod tests { | |||
54 | snippet( | 54 | snippet( |
55 | ctx, | 55 | ctx, |
56 | cap, | 56 | cap, |
57 | "Test function", | 57 | "tfn (Test function)", |
58 | "\ | 58 | "\ |
59 | #[test] | 59 | #[test] |
60 | fn ${1:feature}() { | 60 | fn ${1:feature}() { |
@@ -106,10 +106,10 @@ mod tests { | |||
106 | } | 106 | } |
107 | "#, | 107 | "#, |
108 | expect![[r#" | 108 | expect![[r#" |
109 | sn Test function | ||
110 | sn Test module | ||
111 | sn macro_rules | 109 | sn macro_rules |
112 | sn pub(crate) | 110 | sn pub(crate) |
111 | sn tfn (Test function) | ||
112 | sn tmod (Test module) | ||
113 | "#]], | 113 | "#]], |
114 | ) | 114 | ) |
115 | } | 115 | } |
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index 6b03b30bb..4aa761148 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -27,7 +27,7 @@ pub(crate) struct CompletionContext<'a> { | |||
27 | pub(super) scope: SemanticsScope<'a>, | 27 | pub(super) scope: SemanticsScope<'a>, |
28 | pub(super) db: &'a RootDatabase, | 28 | pub(super) db: &'a RootDatabase, |
29 | pub(super) config: &'a CompletionConfig, | 29 | pub(super) config: &'a CompletionConfig, |
30 | pub(super) offset: TextSize, | 30 | pub(super) position: FilePosition, |
31 | /// The token before the cursor, in the original file. | 31 | /// The token before the cursor, in the original file. |
32 | pub(super) original_token: SyntaxToken, | 32 | pub(super) original_token: SyntaxToken, |
33 | /// The token before the cursor, in the macro-expanded file. | 33 | /// The token before the cursor, in the macro-expanded file. |
@@ -117,7 +117,7 @@ impl<'a> CompletionContext<'a> { | |||
117 | config, | 117 | config, |
118 | original_token, | 118 | original_token, |
119 | token, | 119 | token, |
120 | offset: position.offset, | 120 | position, |
121 | krate, | 121 | krate, |
122 | expected_type: None, | 122 | expected_type: None, |
123 | name_ref_syntax: None, | 123 | name_ref_syntax: None, |
@@ -209,7 +209,7 @@ impl<'a> CompletionContext<'a> { | |||
209 | mark::hit!(completes_if_prefix_is_keyword); | 209 | mark::hit!(completes_if_prefix_is_keyword); |
210 | self.original_token.text_range() | 210 | self.original_token.text_range() |
211 | } else { | 211 | } else { |
212 | TextRange::empty(self.offset) | 212 | TextRange::empty(self.position.offset) |
213 | } | 213 | } |
214 | } | 214 | } |
215 | 215 | ||
@@ -379,8 +379,8 @@ impl<'a> CompletionContext<'a> { | |||
379 | self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); | 379 | self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); |
380 | self.has_type_args = segment.generic_arg_list().is_some(); | 380 | self.has_type_args = segment.generic_arg_list().is_some(); |
381 | 381 | ||
382 | #[allow(deprecated)] | 382 | let hygiene = hir::Hygiene::new(self.db, self.position.file_id.into()); |
383 | if let Some(path) = hir::Path::from_ast(path.clone()) { | 383 | if let Some(path) = hir::Path::from_src(path.clone(), &hygiene) { |
384 | if let Some(path_prefix) = path.qualifier() { | 384 | if let Some(path_prefix) = path.qualifier() { |
385 | self.path_prefix = Some(path_prefix); | 385 | self.path_prefix = Some(path_prefix); |
386 | return; | 386 | return; |
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs index 9a94ff476..59f1b1424 100644 --- a/crates/ra_ide/src/completion/presentation.rs +++ b/crates/ra_ide/src/completion/presentation.rs | |||
@@ -2,8 +2,8 @@ | |||
2 | //! It also handles scoring (sorting) completions. | 2 | //! It also handles scoring (sorting) completions. |
3 | 3 | ||
4 | use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; | 4 | use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; |
5 | use itertools::Itertools; | ||
5 | use ra_syntax::ast::NameOwner; | 6 | use ra_syntax::ast::NameOwner; |
6 | use stdx::SepBy; | ||
7 | use test_utils::mark; | 7 | use test_utils::mark; |
8 | 8 | ||
9 | use crate::{ | 9 | use crate::{ |
@@ -289,16 +289,16 @@ impl Completions { | |||
289 | .map(|field| (field.name(ctx.db), field.signature_ty(ctx.db))); | 289 | .map(|field| (field.name(ctx.db), field.signature_ty(ctx.db))); |
290 | let variant_kind = variant.kind(ctx.db); | 290 | let variant_kind = variant.kind(ctx.db); |
291 | let detail = match variant_kind { | 291 | let detail = match variant_kind { |
292 | StructKind::Tuple | StructKind::Unit => detail_types | 292 | StructKind::Tuple | StructKind::Unit => format!( |
293 | .map(|(_, t)| t.display(ctx.db).to_string()) | 293 | "({})", |
294 | .sep_by(", ") | 294 | detail_types.map(|(_, t)| t.display(ctx.db).to_string()).format(", ") |
295 | .surround_with("(", ")") | 295 | ), |
296 | .to_string(), | 296 | StructKind::Record => format!( |
297 | StructKind::Record => detail_types | 297 | "{{ {} }}", |
298 | .map(|(n, t)| format!("{}: {}", n, t.display(ctx.db).to_string())) | 298 | detail_types |
299 | .sep_by(", ") | 299 | .map(|(n, t)| format!("{}: {}", n, t.display(ctx.db).to_string())) |
300 | .surround_with("{ ", " }") | 300 | .format(", ") |
301 | .to_string(), | 301 | ), |
302 | }; | 302 | }; |
303 | let mut res = CompletionItem::new( | 303 | let mut res = CompletionItem::new( |
304 | CompletionKind::Reference, | 304 | CompletionKind::Reference, |
@@ -412,11 +412,10 @@ impl Builder { | |||
412 | self = self.trigger_call_info(); | 412 | self = self.trigger_call_info(); |
413 | let snippet = match (ctx.config.add_call_argument_snippets, params) { | 413 | let snippet = match (ctx.config.add_call_argument_snippets, params) { |
414 | (true, Params::Named(params)) => { | 414 | (true, Params::Named(params)) => { |
415 | let function_params_snippet = params | 415 | let function_params_snippet = |
416 | .iter() | 416 | params.iter().enumerate().format_with(", ", |(index, param_name), f| { |
417 | .enumerate() | 417 | f(&format_args!("${{{}:{}}}", index + 1, param_name)) |
418 | .map(|(index, param_name)| format!("${{{}:{}}}", index + 1, param_name)) | 418 | }); |
419 | .sep_by(", "); | ||
420 | format!("{}({})$0", name, function_params_snippet) | 419 | format!("{}({})$0", name, function_params_snippet) |
421 | } | 420 | } |
422 | _ => { | 421 | _ => { |
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index dd8a7ffd9..1046d7ab3 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -6,22 +6,21 @@ | |||
6 | 6 | ||
7 | use std::cell::RefCell; | 7 | use std::cell::RefCell; |
8 | 8 | ||
9 | use hir::{ | 9 | use hir::{diagnostics::DiagnosticSinkBuilder, Semantics}; |
10 | diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSinkBuilder}, | ||
11 | HasSource, HirDisplay, Semantics, VariantDef, | ||
12 | }; | ||
13 | use itertools::Itertools; | 10 | use itertools::Itertools; |
14 | use ra_db::SourceDatabase; | 11 | use ra_db::SourceDatabase; |
15 | use ra_ide_db::RootDatabase; | 12 | use ra_ide_db::RootDatabase; |
16 | use ra_prof::profile; | 13 | use ra_prof::profile; |
17 | use ra_syntax::{ | 14 | use ra_syntax::{ |
18 | algo, | 15 | ast::{self, AstNode}, |
19 | ast::{self, edit::IndentLevel, make, AstNode}, | ||
20 | SyntaxNode, TextRange, T, | 16 | SyntaxNode, TextRange, T, |
21 | }; | 17 | }; |
22 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 18 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
23 | 19 | ||
24 | use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceFileEdit}; | 20 | use crate::{Diagnostic, FileId, Fix, SourceFileEdit}; |
21 | |||
22 | mod diagnostics_with_fix; | ||
23 | use diagnostics_with_fix::DiagnosticWithFix; | ||
25 | 24 | ||
26 | #[derive(Debug, Copy, Clone)] | 25 | #[derive(Debug, Copy, Clone)] |
27 | pub enum Severity { | 26 | pub enum Severity { |
@@ -54,71 +53,16 @@ pub(crate) fn diagnostics( | |||
54 | let res = RefCell::new(res); | 53 | let res = RefCell::new(res); |
55 | let mut sink = DiagnosticSinkBuilder::new() | 54 | let mut sink = DiagnosticSinkBuilder::new() |
56 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { | 55 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { |
57 | let original_file = d.source().file_id.original_file(db); | 56 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); |
58 | let fix = Fix::new( | ||
59 | "Create module", | ||
60 | FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() } | ||
61 | .into(), | ||
62 | ); | ||
63 | res.borrow_mut().push(Diagnostic { | ||
64 | range: sema.diagnostics_range(d).range, | ||
65 | message: d.message(), | ||
66 | severity: Severity::Error, | ||
67 | fix: Some(fix), | ||
68 | }) | ||
69 | }) | 57 | }) |
70 | .on::<hir::diagnostics::MissingFields, _>(|d| { | 58 | .on::<hir::diagnostics::MissingFields, _>(|d| { |
71 | // Note that although we could add a diagnostics to | 59 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); |
72 | // fill the missing tuple field, e.g : | ||
73 | // `struct A(usize);` | ||
74 | // `let a = A { 0: () }` | ||
75 | // but it is uncommon usage and it should not be encouraged. | ||
76 | let fix = if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { | ||
77 | None | ||
78 | } else { | ||
79 | let mut field_list = d.ast(db); | ||
80 | for f in d.missed_fields.iter() { | ||
81 | let field = | ||
82 | make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); | ||
83 | field_list = field_list.append_field(&field); | ||
84 | } | ||
85 | |||
86 | let edit = { | ||
87 | let mut builder = TextEditBuilder::default(); | ||
88 | algo::diff(&d.ast(db).syntax(), &field_list.syntax()) | ||
89 | .into_text_edit(&mut builder); | ||
90 | builder.finish() | ||
91 | }; | ||
92 | Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into())) | ||
93 | }; | ||
94 | |||
95 | res.borrow_mut().push(Diagnostic { | ||
96 | range: sema.diagnostics_range(d).range, | ||
97 | message: d.message(), | ||
98 | severity: Severity::Error, | ||
99 | fix, | ||
100 | }) | ||
101 | }) | 60 | }) |
102 | .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { | 61 | .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { |
103 | let node = d.ast(db); | 62 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); |
104 | let replacement = format!("Ok({})", node.syntax()); | ||
105 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); | ||
106 | let source_change = SourceFileEdit { file_id, edit }.into(); | ||
107 | let fix = Fix::new("Wrap with ok", source_change); | ||
108 | res.borrow_mut().push(Diagnostic { | ||
109 | range: sema.diagnostics_range(d).range, | ||
110 | message: d.message(), | ||
111 | severity: Severity::Error, | ||
112 | fix: Some(fix), | ||
113 | }) | ||
114 | }) | 63 | }) |
115 | .on::<hir::diagnostics::NoSuchField, _>(|d| { | 64 | .on::<hir::diagnostics::NoSuchField, _>(|d| { |
116 | res.borrow_mut().push(Diagnostic { | 65 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); |
117 | range: sema.diagnostics_range(d).range, | ||
118 | message: d.message(), | ||
119 | severity: Severity::Error, | ||
120 | fix: missing_struct_field_fix(&sema, file_id, d), | ||
121 | }) | ||
122 | }) | 66 | }) |
123 | // Only collect experimental diagnostics when they're enabled. | 67 | // Only collect experimental diagnostics when they're enabled. |
124 | .filter(|diag| !diag.is_experimental() || enable_experimental) | 68 | .filter(|diag| !diag.is_experimental() || enable_experimental) |
@@ -126,7 +70,7 @@ pub(crate) fn diagnostics( | |||
126 | .build(|d| { | 70 | .build(|d| { |
127 | res.borrow_mut().push(Diagnostic { | 71 | res.borrow_mut().push(Diagnostic { |
128 | message: d.message(), | 72 | message: d.message(), |
129 | range: sema.diagnostics_range(d).range, | 73 | range: sema.diagnostics_display_range(d).range, |
130 | severity: Severity::Error, | 74 | severity: Severity::Error, |
131 | fix: None, | 75 | fix: None, |
132 | }) | 76 | }) |
@@ -139,77 +83,12 @@ pub(crate) fn diagnostics( | |||
139 | res.into_inner() | 83 | res.into_inner() |
140 | } | 84 | } |
141 | 85 | ||
142 | fn missing_struct_field_fix( | 86 | fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { |
143 | sema: &Semantics<RootDatabase>, | 87 | Diagnostic { |
144 | usage_file_id: FileId, | 88 | range: sema.diagnostics_display_range(d).range, |
145 | d: &hir::diagnostics::NoSuchField, | 89 | message: d.message(), |
146 | ) -> Option<Fix> { | 90 | severity: Severity::Error, |
147 | let record_expr = sema.ast(d); | 91 | fix: d.fix(&sema), |
148 | |||
149 | let record_lit = ast::RecordExpr::cast(record_expr.syntax().parent()?.parent()?)?; | ||
150 | let def_id = sema.resolve_variant(record_lit)?; | ||
151 | let module; | ||
152 | let def_file_id; | ||
153 | let record_fields = match VariantDef::from(def_id) { | ||
154 | VariantDef::Struct(s) => { | ||
155 | module = s.module(sema.db); | ||
156 | let source = s.source(sema.db); | ||
157 | def_file_id = source.file_id; | ||
158 | let fields = source.value.field_list()?; | ||
159 | record_field_list(fields)? | ||
160 | } | ||
161 | VariantDef::Union(u) => { | ||
162 | module = u.module(sema.db); | ||
163 | let source = u.source(sema.db); | ||
164 | def_file_id = source.file_id; | ||
165 | source.value.record_field_list()? | ||
166 | } | ||
167 | VariantDef::EnumVariant(e) => { | ||
168 | module = e.module(sema.db); | ||
169 | let source = e.source(sema.db); | ||
170 | def_file_id = source.file_id; | ||
171 | let fields = source.value.field_list()?; | ||
172 | record_field_list(fields)? | ||
173 | } | ||
174 | }; | ||
175 | let def_file_id = def_file_id.original_file(sema.db); | ||
176 | |||
177 | let new_field_type = sema.type_of_expr(&record_expr.expr()?)?; | ||
178 | if new_field_type.is_unknown() { | ||
179 | return None; | ||
180 | } | ||
181 | let new_field = make::record_field_def( | ||
182 | record_expr.field_name()?, | ||
183 | make::type_ref(&new_field_type.display_source_code(sema.db, module.into()).ok()?), | ||
184 | ); | ||
185 | |||
186 | let last_field = record_fields.fields().last()?; | ||
187 | let last_field_syntax = last_field.syntax(); | ||
188 | let indent = IndentLevel::from_node(last_field_syntax); | ||
189 | |||
190 | let mut new_field = new_field.to_string(); | ||
191 | if usage_file_id != def_file_id { | ||
192 | new_field = format!("pub(crate) {}", new_field); | ||
193 | } | ||
194 | new_field = format!("\n{}{}", indent, new_field); | ||
195 | |||
196 | let needs_comma = !last_field_syntax.to_string().ends_with(','); | ||
197 | if needs_comma { | ||
198 | new_field = format!(",{}", new_field); | ||
199 | } | ||
200 | |||
201 | let source_change = SourceFileEdit { | ||
202 | file_id: def_file_id, | ||
203 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), | ||
204 | }; | ||
205 | let fix = Fix::new("Create field", source_change.into()); | ||
206 | return Some(fix); | ||
207 | |||
208 | fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { | ||
209 | match field_def_list { | ||
210 | ast::FieldList::RecordFieldList(it) => Some(it), | ||
211 | ast::FieldList::TupleFieldList(_) => None, | ||
212 | } | ||
213 | } | 92 | } |
214 | } | 93 | } |
215 | 94 | ||
@@ -220,24 +99,25 @@ fn check_unnecessary_braces_in_use_statement( | |||
220 | ) -> Option<()> { | 99 | ) -> Option<()> { |
221 | let use_tree_list = ast::UseTreeList::cast(node.clone())?; | 100 | let use_tree_list = ast::UseTreeList::cast(node.clone())?; |
222 | if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { | 101 | if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { |
223 | let range = use_tree_list.syntax().text_range(); | 102 | let use_range = use_tree_list.syntax().text_range(); |
224 | let edit = | 103 | let edit = |
225 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree) | 104 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree) |
226 | .unwrap_or_else(|| { | 105 | .unwrap_or_else(|| { |
227 | let to_replace = single_use_tree.syntax().text().to_string(); | 106 | let to_replace = single_use_tree.syntax().text().to_string(); |
228 | let mut edit_builder = TextEditBuilder::default(); | 107 | let mut edit_builder = TextEditBuilder::default(); |
229 | edit_builder.delete(range); | 108 | edit_builder.delete(use_range); |
230 | edit_builder.insert(range.start(), to_replace); | 109 | edit_builder.insert(use_range.start(), to_replace); |
231 | edit_builder.finish() | 110 | edit_builder.finish() |
232 | }); | 111 | }); |
233 | 112 | ||
234 | acc.push(Diagnostic { | 113 | acc.push(Diagnostic { |
235 | range, | 114 | range: use_range, |
236 | message: "Unnecessary braces in use statement".to_string(), | 115 | message: "Unnecessary braces in use statement".to_string(), |
237 | severity: Severity::WeakWarning, | 116 | severity: Severity::WeakWarning, |
238 | fix: Some(Fix::new( | 117 | fix: Some(Fix::new( |
239 | "Remove unnecessary braces", | 118 | "Remove unnecessary braces", |
240 | SourceFileEdit { file_id, edit }.into(), | 119 | SourceFileEdit { file_id, edit }.into(), |
120 | use_range, | ||
241 | )), | 121 | )), |
242 | }); | 122 | }); |
243 | } | 123 | } |
@@ -252,8 +132,7 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | |||
252 | if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] { | 132 | if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] { |
253 | let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); | 133 | let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); |
254 | let end = use_tree_list_node.text_range().end(); | 134 | let end = use_tree_list_node.text_range().end(); |
255 | let range = TextRange::new(start, end); | 135 | return Some(TextEdit::delete(TextRange::new(start, end))); |
256 | return Some(TextEdit::delete(range)); | ||
257 | } | 136 | } |
258 | None | 137 | None |
259 | } | 138 | } |
@@ -276,13 +155,15 @@ fn check_struct_shorthand_initialization( | |||
276 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); | 155 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); |
277 | let edit = edit_builder.finish(); | 156 | let edit = edit_builder.finish(); |
278 | 157 | ||
158 | let field_range = record_field.syntax().text_range(); | ||
279 | acc.push(Diagnostic { | 159 | acc.push(Diagnostic { |
280 | range: record_field.syntax().text_range(), | 160 | range: field_range, |
281 | message: "Shorthand struct initialization".to_string(), | 161 | message: "Shorthand struct initialization".to_string(), |
282 | severity: Severity::WeakWarning, | 162 | severity: Severity::WeakWarning, |
283 | fix: Some(Fix::new( | 163 | fix: Some(Fix::new( |
284 | "Use struct shorthand initialization", | 164 | "Use struct shorthand initialization", |
285 | SourceFileEdit { file_id, edit }.into(), | 165 | SourceFileEdit { file_id, edit }.into(), |
166 | field_range, | ||
286 | )), | 167 | )), |
287 | }); | 168 | }); |
288 | } | 169 | } |
@@ -302,7 +183,7 @@ mod tests { | |||
302 | /// Takes a multi-file input fixture with annotated cursor positions, | 183 | /// Takes a multi-file input fixture with annotated cursor positions, |
303 | /// and checks that: | 184 | /// and checks that: |
304 | /// * a diagnostic is produced | 185 | /// * a diagnostic is produced |
305 | /// * this diagnostic touches the input cursor position | 186 | /// * this diagnostic fix trigger range touches the input cursor position |
306 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied | 187 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied |
307 | fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { | 188 | fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { |
308 | let after = trim_indent(ra_fixture_after); | 189 | let after = trim_indent(ra_fixture_after); |
@@ -320,10 +201,10 @@ mod tests { | |||
320 | 201 | ||
321 | assert_eq_text!(&after, &actual); | 202 | assert_eq_text!(&after, &actual); |
322 | assert!( | 203 | assert!( |
323 | diagnostic.range.start() <= file_position.offset | 204 | fix.fix_trigger_range.start() <= file_position.offset |
324 | && diagnostic.range.end() >= file_position.offset, | 205 | && fix.fix_trigger_range.end() >= file_position.offset, |
325 | "diagnostic range {:?} does not touch cursor position {:?}", | 206 | "diagnostic fix range {:?} does not touch cursor position {:?}", |
326 | diagnostic.range, | 207 | fix.fix_trigger_range, |
327 | file_position.offset | 208 | file_position.offset |
328 | ); | 209 | ); |
329 | } | 210 | } |
@@ -640,6 +521,7 @@ fn test_fn() { | |||
640 | ], | 521 | ], |
641 | is_snippet: false, | 522 | is_snippet: false, |
642 | }, | 523 | }, |
524 | fix_trigger_range: 0..8, | ||
643 | }, | 525 | }, |
644 | ), | 526 | ), |
645 | }, | 527 | }, |
diff --git a/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs b/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs new file mode 100644 index 000000000..f7c73773f --- /dev/null +++ b/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs | |||
@@ -0,0 +1,171 @@ | |||
1 | //! Provides a way to attach fixes to the diagnostics. | ||
2 | //! The same module also has all curret custom fixes for the diagnostics implemented. | ||
3 | use crate::Fix; | ||
4 | use ast::{edit::IndentLevel, make}; | ||
5 | use hir::{ | ||
6 | db::AstDatabase, | ||
7 | diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule}, | ||
8 | HasSource, HirDisplay, Semantics, VariantDef, | ||
9 | }; | ||
10 | use ra_db::FileId; | ||
11 | use ra_ide_db::{ | ||
12 | source_change::{FileSystemEdit, SourceFileEdit}, | ||
13 | RootDatabase, | ||
14 | }; | ||
15 | use ra_syntax::{algo, ast, AstNode}; | ||
16 | use ra_text_edit::{TextEdit, TextEditBuilder}; | ||
17 | |||
18 | /// A [Diagnostic] that potentially has a fix available. | ||
19 | /// | ||
20 | /// [Diagnostic]: hir::diagnostics::Diagnostic | ||
21 | pub trait DiagnosticWithFix: Diagnostic { | ||
22 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix>; | ||
23 | } | ||
24 | |||
25 | impl DiagnosticWithFix for UnresolvedModule { | ||
26 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | ||
27 | let root = sema.db.parse_or_expand(self.file)?; | ||
28 | let unresolved_module = self.decl.to_node(&root); | ||
29 | Some(Fix::new( | ||
30 | "Create module", | ||
31 | FileSystemEdit::CreateFile { | ||
32 | anchor: self.file.original_file(sema.db), | ||
33 | dst: self.candidate.clone(), | ||
34 | } | ||
35 | .into(), | ||
36 | unresolved_module.syntax().text_range(), | ||
37 | )) | ||
38 | } | ||
39 | } | ||
40 | |||
41 | impl DiagnosticWithFix for NoSuchField { | ||
42 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | ||
43 | let root = sema.db.parse_or_expand(self.file)?; | ||
44 | missing_record_expr_field_fix( | ||
45 | &sema, | ||
46 | self.file.original_file(sema.db), | ||
47 | &self.field.to_node(&root), | ||
48 | ) | ||
49 | } | ||
50 | } | ||
51 | |||
52 | impl DiagnosticWithFix for MissingFields { | ||
53 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | ||
54 | // Note that although we could add a diagnostics to | ||
55 | // fill the missing tuple field, e.g : | ||
56 | // `struct A(usize);` | ||
57 | // `let a = A { 0: () }` | ||
58 | // but it is uncommon usage and it should not be encouraged. | ||
59 | if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { | ||
60 | return None; | ||
61 | } | ||
62 | |||
63 | let root = sema.db.parse_or_expand(self.file)?; | ||
64 | let old_field_list = self.field_list_parent.to_node(&root).record_expr_field_list()?; | ||
65 | let mut new_field_list = old_field_list.clone(); | ||
66 | for f in self.missed_fields.iter() { | ||
67 | let field = | ||
68 | make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); | ||
69 | new_field_list = new_field_list.append_field(&field); | ||
70 | } | ||
71 | |||
72 | let edit = { | ||
73 | let mut builder = TextEditBuilder::default(); | ||
74 | algo::diff(&old_field_list.syntax(), &new_field_list.syntax()) | ||
75 | .into_text_edit(&mut builder); | ||
76 | builder.finish() | ||
77 | }; | ||
78 | Some(Fix::new( | ||
79 | "Fill struct fields", | ||
80 | SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(), | ||
81 | sema.original_range(&old_field_list.syntax()).range, | ||
82 | )) | ||
83 | } | ||
84 | } | ||
85 | |||
86 | impl DiagnosticWithFix for MissingOkInTailExpr { | ||
87 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | ||
88 | let root = sema.db.parse_or_expand(self.file)?; | ||
89 | let tail_expr = self.expr.to_node(&root); | ||
90 | let tail_expr_range = tail_expr.syntax().text_range(); | ||
91 | let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax())); | ||
92 | let source_change = | ||
93 | SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(); | ||
94 | Some(Fix::new("Wrap with ok", source_change, tail_expr_range)) | ||
95 | } | ||
96 | } | ||
97 | |||
98 | fn missing_record_expr_field_fix( | ||
99 | sema: &Semantics<RootDatabase>, | ||
100 | usage_file_id: FileId, | ||
101 | record_expr_field: &ast::RecordExprField, | ||
102 | ) -> Option<Fix> { | ||
103 | let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?; | ||
104 | let def_id = sema.resolve_variant(record_lit)?; | ||
105 | let module; | ||
106 | let def_file_id; | ||
107 | let record_fields = match VariantDef::from(def_id) { | ||
108 | VariantDef::Struct(s) => { | ||
109 | module = s.module(sema.db); | ||
110 | let source = s.source(sema.db); | ||
111 | def_file_id = source.file_id; | ||
112 | let fields = source.value.field_list()?; | ||
113 | record_field_list(fields)? | ||
114 | } | ||
115 | VariantDef::Union(u) => { | ||
116 | module = u.module(sema.db); | ||
117 | let source = u.source(sema.db); | ||
118 | def_file_id = source.file_id; | ||
119 | source.value.record_field_list()? | ||
120 | } | ||
121 | VariantDef::EnumVariant(e) => { | ||
122 | module = e.module(sema.db); | ||
123 | let source = e.source(sema.db); | ||
124 | def_file_id = source.file_id; | ||
125 | let fields = source.value.field_list()?; | ||
126 | record_field_list(fields)? | ||
127 | } | ||
128 | }; | ||
129 | let def_file_id = def_file_id.original_file(sema.db); | ||
130 | |||
131 | let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?; | ||
132 | if new_field_type.is_unknown() { | ||
133 | return None; | ||
134 | } | ||
135 | let new_field = make::record_field( | ||
136 | record_expr_field.field_name()?, | ||
137 | make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?), | ||
138 | ); | ||
139 | |||
140 | let last_field = record_fields.fields().last()?; | ||
141 | let last_field_syntax = last_field.syntax(); | ||
142 | let indent = IndentLevel::from_node(last_field_syntax); | ||
143 | |||
144 | let mut new_field = new_field.to_string(); | ||
145 | if usage_file_id != def_file_id { | ||
146 | new_field = format!("pub(crate) {}", new_field); | ||
147 | } | ||
148 | new_field = format!("\n{}{}", indent, new_field); | ||
149 | |||
150 | let needs_comma = !last_field_syntax.to_string().ends_with(','); | ||
151 | if needs_comma { | ||
152 | new_field = format!(",{}", new_field); | ||
153 | } | ||
154 | |||
155 | let source_change = SourceFileEdit { | ||
156 | file_id: def_file_id, | ||
157 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), | ||
158 | }; | ||
159 | return Some(Fix::new( | ||
160 | "Create field", | ||
161 | source_change.into(), | ||
162 | record_expr_field.syntax().text_range(), | ||
163 | )); | ||
164 | |||
165 | fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { | ||
166 | match field_def_list { | ||
167 | ast::FieldList::RecordFieldList(it) => Some(it), | ||
168 | ast::FieldList::TupleFieldList(_) => None, | ||
169 | } | ||
170 | } | ||
171 | } | ||
diff --git a/crates/ra_ide/src/display/short_label.rs b/crates/ra_ide/src/display/short_label.rs index 0fdf8e9a5..010c34705 100644 --- a/crates/ra_ide/src/display/short_label.rs +++ b/crates/ra_ide/src/display/short_label.rs | |||
@@ -47,6 +47,12 @@ impl ShortLabel for ast::Module { | |||
47 | } | 47 | } |
48 | } | 48 | } |
49 | 49 | ||
50 | impl ShortLabel for ast::SourceFile { | ||
51 | fn short_label(&self) -> Option<String> { | ||
52 | None | ||
53 | } | ||
54 | } | ||
55 | |||
50 | impl ShortLabel for ast::TypeAlias { | 56 | impl ShortLabel for ast::TypeAlias { |
51 | fn short_label(&self) -> Option<String> { | 57 | fn short_label(&self) -> Option<String> { |
52 | short_label_from_node(self, "type ") | 58 | short_label_from_node(self, "type ") |
@@ -55,7 +61,11 @@ impl ShortLabel for ast::TypeAlias { | |||
55 | 61 | ||
56 | impl ShortLabel for ast::Const { | 62 | impl ShortLabel for ast::Const { |
57 | fn short_label(&self) -> Option<String> { | 63 | fn short_label(&self) -> Option<String> { |
58 | short_label_from_ty(self, self.ty(), "const ") | 64 | let mut new_buf = short_label_from_ty(self, self.ty(), "const ")?; |
65 | if let Some(expr) = self.body() { | ||
66 | format_to!(new_buf, " = {}", expr.syntax()); | ||
67 | } | ||
68 | Some(new_buf) | ||
59 | } | 69 | } |
60 | } | 70 | } |
61 | 71 | ||
diff --git a/crates/ra_ide/src/folding_ranges.rs b/crates/ra_ide/src/folding_ranges.rs index 903c34996..0fbc9babd 100644 --- a/crates/ra_ide/src/folding_ranges.rs +++ b/crates/ra_ide/src/folding_ranges.rs | |||
@@ -85,7 +85,8 @@ fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> { | |||
85 | COMMENT => Some(FoldKind::Comment), | 85 | COMMENT => Some(FoldKind::Comment), |
86 | USE => Some(FoldKind::Imports), | 86 | USE => Some(FoldKind::Imports), |
87 | ARG_LIST | PARAM_LIST => Some(FoldKind::ArgList), | 87 | ARG_LIST | PARAM_LIST => Some(FoldKind::ArgList), |
88 | RECORD_FIELD_LIST | 88 | ASSOC_ITEM_LIST |
89 | | RECORD_FIELD_LIST | ||
89 | | RECORD_PAT_FIELD_LIST | 90 | | RECORD_PAT_FIELD_LIST |
90 | | RECORD_EXPR_FIELD_LIST | 91 | | RECORD_EXPR_FIELD_LIST |
91 | | ITEM_LIST | 92 | | ITEM_LIST |
@@ -337,6 +338,26 @@ fn main() <fold block>{ | |||
337 | } | 338 | } |
338 | 339 | ||
339 | #[test] | 340 | #[test] |
341 | fn test_folds_structs() { | ||
342 | check( | ||
343 | r#" | ||
344 | struct Foo <fold block>{ | ||
345 | }</fold> | ||
346 | "#, | ||
347 | ); | ||
348 | } | ||
349 | |||
350 | #[test] | ||
351 | fn test_folds_traits() { | ||
352 | check( | ||
353 | r#" | ||
354 | trait Foo <fold block>{ | ||
355 | }</fold> | ||
356 | "#, | ||
357 | ); | ||
358 | } | ||
359 | |||
360 | #[test] | ||
340 | fn test_folds_macros() { | 361 | fn test_folds_macros() { |
341 | check( | 362 | check( |
342 | r#" | 363 | r#" |
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index 4e3f428fa..45389fd23 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use hir::Semantics; | 1 | use hir::Semantics; |
2 | use ra_ide_db::{ | 2 | use ra_ide_db::{ |
3 | defs::{classify_name, classify_name_ref, NameClass}, | 3 | defs::{classify_name, classify_name_ref}, |
4 | symbol_index, RootDatabase, | 4 | symbol_index, RootDatabase, |
5 | }; | 5 | }; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
@@ -40,10 +40,7 @@ pub(crate) fn goto_definition( | |||
40 | reference_definition(&sema, &name_ref).to_vec() | 40 | reference_definition(&sema, &name_ref).to_vec() |
41 | }, | 41 | }, |
42 | ast::Name(name) => { | 42 | ast::Name(name) => { |
43 | let def = match classify_name(&sema, &name)? { | 43 | let def = classify_name(&sema, &name)?.definition(sema.db); |
44 | NameClass::Definition(def) | NameClass::ConstReference(def) => def, | ||
45 | NameClass::FieldShorthand { local: _, field } => field, | ||
46 | }; | ||
47 | let nav = def.try_to_nav(sema.db)?; | 44 | let nav = def.try_to_nav(sema.db)?; |
48 | vec![nav] | 45 | vec![nav] |
49 | }, | 46 | }, |
@@ -86,8 +83,7 @@ pub(crate) fn reference_definition( | |||
86 | ) -> ReferenceResult { | 83 | ) -> ReferenceResult { |
87 | let name_kind = classify_name_ref(sema, name_ref); | 84 | let name_kind = classify_name_ref(sema, name_ref); |
88 | if let Some(def) = name_kind { | 85 | if let Some(def) = name_kind { |
89 | let def = def.definition(); | 86 | let def = def.definition(sema.db); |
90 | |||
91 | return match def.try_to_nav(sema.db) { | 87 | return match def.try_to_nav(sema.db) { |
92 | Some(nav) => ReferenceResult::Exact(nav), | 88 | Some(nav) => ReferenceResult::Exact(nav), |
93 | None => ReferenceResult::Approximate(Vec::new()), | 89 | None => ReferenceResult::Approximate(Vec::new()), |
@@ -134,6 +130,32 @@ mod tests { | |||
134 | } | 130 | } |
135 | 131 | ||
136 | #[test] | 132 | #[test] |
133 | fn goto_def_for_extern_crate() { | ||
134 | check( | ||
135 | r#" | ||
136 | //- /main.rs | ||
137 | extern crate std<|>; | ||
138 | //- /std/lib.rs | ||
139 | // empty | ||
140 | //^ file | ||
141 | "#, | ||
142 | ) | ||
143 | } | ||
144 | |||
145 | #[test] | ||
146 | fn goto_def_for_renamed_extern_crate() { | ||
147 | check( | ||
148 | r#" | ||
149 | //- /main.rs | ||
150 | extern crate std as abc<|>; | ||
151 | //- /std/lib.rs | ||
152 | // empty | ||
153 | //^ file | ||
154 | "#, | ||
155 | ) | ||
156 | } | ||
157 | |||
158 | #[test] | ||
137 | fn goto_def_in_items() { | 159 | fn goto_def_in_items() { |
138 | check( | 160 | check( |
139 | r#" | 161 | r#" |
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index aa48cb412..f66f62bfb 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -85,8 +85,8 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
85 | let node = token.parent(); | 85 | let node = token.parent(); |
86 | let definition = match_ast! { | 86 | let definition = match_ast! { |
87 | match node { | 87 | match node { |
88 | ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition()), | 88 | ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)), |
89 | ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition()), | 89 | ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)), |
90 | _ => None, | 90 | _ => None, |
91 | } | 91 | } |
92 | }; | 92 | }; |
@@ -304,7 +304,10 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> { | |||
304 | let docs = Documentation::from_ast(&it).map(Into::into); | 304 | let docs = Documentation::from_ast(&it).map(Into::into); |
305 | hover_markup(docs, it.short_label(), mod_path) | 305 | hover_markup(docs, it.short_label(), mod_path) |
306 | } | 306 | } |
307 | _ => None, | 307 | ModuleSource::SourceFile(it) => { |
308 | let docs = Documentation::from_ast(&it).map(Into::into); | ||
309 | hover_markup(docs, it.short_label(), mod_path) | ||
310 | } | ||
308 | }, | 311 | }, |
309 | ModuleDef::Function(it) => from_def_source(db, it, mod_path), | 312 | ModuleDef::Function(it) => from_def_source(db, it, mod_path), |
310 | ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it, mod_path), | 313 | ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it, mod_path), |
@@ -509,6 +512,37 @@ fn main() { } | |||
509 | } | 512 | } |
510 | 513 | ||
511 | #[test] | 514 | #[test] |
515 | fn hover_shows_fn_doc() { | ||
516 | check( | ||
517 | r#" | ||
518 | /// # Example | ||
519 | /// ``` | ||
520 | /// # use std::path::Path; | ||
521 | /// # | ||
522 | /// foo(Path::new("hello, world!")) | ||
523 | /// ``` | ||
524 | pub fn foo<|>(_: &Path) {} | ||
525 | |||
526 | fn main() { } | ||
527 | "#, | ||
528 | expect![[r#" | ||
529 | *foo* | ||
530 | ```rust | ||
531 | pub fn foo(_: &Path) | ||
532 | ``` | ||
533 | ___ | ||
534 | |||
535 | # Example | ||
536 | ``` | ||
537 | # use std::path::Path; | ||
538 | # | ||
539 | foo(Path::new("hello, world!")) | ||
540 | ``` | ||
541 | "#]], | ||
542 | ); | ||
543 | } | ||
544 | |||
545 | #[test] | ||
512 | fn hover_shows_struct_field_info() { | 546 | fn hover_shows_struct_field_info() { |
513 | // Hovering over the field when instantiating | 547 | // Hovering over the field when instantiating |
514 | check( | 548 | check( |
@@ -556,16 +590,16 @@ fn main() { | |||
556 | #[test] | 590 | #[test] |
557 | fn hover_const_static() { | 591 | fn hover_const_static() { |
558 | check( | 592 | check( |
559 | r#"const foo<|>: u32 = 0;"#, | 593 | r#"const foo<|>: u32 = 123;"#, |
560 | expect![[r#" | 594 | expect![[r#" |
561 | *foo* | 595 | *foo* |
562 | ```rust | 596 | ```rust |
563 | const foo: u32 | 597 | const foo: u32 = 123 |
564 | ``` | 598 | ``` |
565 | "#]], | 599 | "#]], |
566 | ); | 600 | ); |
567 | check( | 601 | check( |
568 | r#"static foo<|>: u32 = 0;"#, | 602 | r#"static foo<|>: u32 = 456;"#, |
569 | expect![[r#" | 603 | expect![[r#" |
570 | *foo* | 604 | *foo* |
571 | ```rust | 605 | ```rust |
@@ -800,7 +834,7 @@ fn main() { | |||
800 | expect![[r#" | 834 | expect![[r#" |
801 | *C* | 835 | *C* |
802 | ```rust | 836 | ```rust |
803 | const C: u32 | 837 | const C: u32 = 1 |
804 | ``` | 838 | ``` |
805 | "#]], | 839 | "#]], |
806 | ) | 840 | ) |
@@ -1107,6 +1141,46 @@ fn bar() { fo<|>o(); } | |||
1107 | } | 1141 | } |
1108 | 1142 | ||
1109 | #[test] | 1143 | #[test] |
1144 | fn test_hover_extern_crate() { | ||
1145 | check( | ||
1146 | r#" | ||
1147 | //- /main.rs | ||
1148 | extern crate st<|>d; | ||
1149 | //- /std/lib.rs | ||
1150 | //! Standard library for this test | ||
1151 | //! | ||
1152 | //! Printed? | ||
1153 | //! abc123 | ||
1154 | "#, | ||
1155 | expect![[r#" | ||
1156 | *std* | ||
1157 | Standard library for this test | ||
1158 | |||
1159 | Printed? | ||
1160 | abc123 | ||
1161 | "#]], | ||
1162 | ); | ||
1163 | check( | ||
1164 | r#" | ||
1165 | //- /main.rs | ||
1166 | extern crate std as ab<|>c; | ||
1167 | //- /std/lib.rs | ||
1168 | //! Standard library for this test | ||
1169 | //! | ||
1170 | //! Printed? | ||
1171 | //! abc123 | ||
1172 | "#, | ||
1173 | expect![[r#" | ||
1174 | *abc* | ||
1175 | Standard library for this test | ||
1176 | |||
1177 | Printed? | ||
1178 | abc123 | ||
1179 | "#]], | ||
1180 | ); | ||
1181 | } | ||
1182 | |||
1183 | #[test] | ||
1110 | fn test_hover_mod_with_same_name_as_function() { | 1184 | fn test_hover_mod_with_same_name_as_function() { |
1111 | check( | 1185 | check( |
1112 | r#" | 1186 | r#" |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 0fede0d87..89fcb6f17 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -112,13 +112,19 @@ pub struct Diagnostic { | |||
112 | pub struct Fix { | 112 | pub struct Fix { |
113 | pub label: String, | 113 | pub label: String, |
114 | pub source_change: SourceChange, | 114 | pub source_change: SourceChange, |
115 | /// Allows to trigger the fix only when the caret is in the range given | ||
116 | pub fix_trigger_range: TextRange, | ||
115 | } | 117 | } |
116 | 118 | ||
117 | impl Fix { | 119 | impl Fix { |
118 | pub fn new(label: impl Into<String>, source_change: SourceChange) -> Self { | 120 | pub fn new( |
121 | label: impl Into<String>, | ||
122 | source_change: SourceChange, | ||
123 | fix_trigger_range: TextRange, | ||
124 | ) -> Self { | ||
119 | let label = label.into(); | 125 | let label = label.into(); |
120 | assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.')); | 126 | assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.')); |
121 | Self { label, source_change } | 127 | Self { label, source_change, fix_trigger_range } |
122 | } | 128 | } |
123 | } | 129 | } |
124 | 130 | ||
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index cf456630a..453985de3 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs | |||
@@ -130,13 +130,13 @@ fn find_name( | |||
130 | opt_name: Option<ast::Name>, | 130 | opt_name: Option<ast::Name>, |
131 | ) -> Option<RangeInfo<Definition>> { | 131 | ) -> Option<RangeInfo<Definition>> { |
132 | if let Some(name) = opt_name { | 132 | if let Some(name) = opt_name { |
133 | let def = classify_name(sema, &name)?.definition(); | 133 | let def = classify_name(sema, &name)?.definition(sema.db); |
134 | let range = name.syntax().text_range(); | 134 | let range = name.syntax().text_range(); |
135 | return Some(RangeInfo::new(range, def)); | 135 | return Some(RangeInfo::new(range, def)); |
136 | } | 136 | } |
137 | let name_ref = | 137 | let name_ref = |
138 | sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; | 138 | sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; |
139 | let def = classify_name_ref(sema, &name_ref)?.definition(); | 139 | let def = classify_name_ref(sema, &name_ref)?.definition(sema.db); |
140 | let range = name_ref.syntax().text_range(); | 140 | let range = name_ref.syntax().text_range(); |
141 | Some(RangeInfo::new(range, def)) | 141 | Some(RangeInfo::new(range, def)) |
142 | } | 142 | } |
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index 3cd6c815b..c8d80fcf7 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs | |||
@@ -157,7 +157,7 @@ fn rename_to_self( | |||
157 | } | 157 | } |
158 | let first_param = params.params().next()?; | 158 | let first_param = params.params().next()?; |
159 | let mutable = match first_param.ty() { | 159 | let mutable = match first_param.ty() { |
160 | Some(ast::Type::ReferenceType(rt)) => rt.mut_token().is_some(), | 160 | Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(), |
161 | _ => return None, // not renaming other types | 161 | _ => return None, // not renaming other types |
162 | }; | 162 | }; |
163 | 163 | ||
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index 32f34ef10..c10e15db8 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -4,7 +4,7 @@ mod injection; | |||
4 | #[cfg(test)] | 4 | #[cfg(test)] |
5 | mod tests; | 5 | mod tests; |
6 | 6 | ||
7 | use hir::{Name, Semantics}; | 7 | use hir::{Name, Semantics, VariantDef}; |
8 | use ra_ide_db::{ | 8 | use ra_ide_db::{ |
9 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, | 9 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, |
10 | RootDatabase, | 10 | RootDatabase, |
@@ -455,6 +455,18 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | |||
455 | Some(TextRange::new(range_start, range_end)) | 455 | Some(TextRange::new(range_start, range_end)) |
456 | } | 456 | } |
457 | 457 | ||
458 | fn is_possibly_unsafe(name_ref: &ast::NameRef) -> bool { | ||
459 | name_ref | ||
460 | .syntax() | ||
461 | .parent() | ||
462 | .and_then(|parent| { | ||
463 | ast::FieldExpr::cast(parent.clone()) | ||
464 | .map(|_| true) | ||
465 | .or_else(|| ast::RecordPatField::cast(parent).map(|_| true)) | ||
466 | }) | ||
467 | .unwrap_or(false) | ||
468 | } | ||
469 | |||
458 | fn highlight_element( | 470 | fn highlight_element( |
459 | sema: &Semantics<RootDatabase>, | 471 | sema: &Semantics<RootDatabase>, |
460 | bindings_shadow_count: &mut FxHashMap<Name, u32>, | 472 | bindings_shadow_count: &mut FxHashMap<Name, u32>, |
@@ -483,11 +495,21 @@ fn highlight_element( | |||
483 | }; | 495 | }; |
484 | 496 | ||
485 | match name_kind { | 497 | match name_kind { |
498 | Some(NameClass::ExternCrate(_)) => HighlightTag::Module.into(), | ||
486 | Some(NameClass::Definition(def)) => { | 499 | Some(NameClass::Definition(def)) => { |
487 | highlight_name(db, def) | HighlightModifier::Definition | 500 | highlight_name(sema, db, def, None, false) | HighlightModifier::Definition |
501 | } | ||
502 | Some(NameClass::ConstReference(def)) => highlight_name(sema, db, def, None, false), | ||
503 | Some(NameClass::FieldShorthand { field, .. }) => { | ||
504 | let mut h = HighlightTag::Field.into(); | ||
505 | if let Definition::Field(field) = field { | ||
506 | if let VariantDef::Union(_) = field.parent_def(db) { | ||
507 | h |= HighlightModifier::Unsafe; | ||
508 | } | ||
509 | } | ||
510 | |||
511 | h | ||
488 | } | 512 | } |
489 | Some(NameClass::ConstReference(def)) => highlight_name(db, def), | ||
490 | Some(NameClass::FieldShorthand { .. }) => HighlightTag::Field.into(), | ||
491 | None => highlight_name_by_syntax(name) | HighlightModifier::Definition, | 513 | None => highlight_name_by_syntax(name) | HighlightModifier::Definition, |
492 | } | 514 | } |
493 | } | 515 | } |
@@ -498,8 +520,10 @@ fn highlight_element( | |||
498 | } | 520 | } |
499 | NAME_REF => { | 521 | NAME_REF => { |
500 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); | 522 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); |
523 | let possibly_unsafe = is_possibly_unsafe(&name_ref); | ||
501 | match classify_name_ref(sema, &name_ref) { | 524 | match classify_name_ref(sema, &name_ref) { |
502 | Some(name_kind) => match name_kind { | 525 | Some(name_kind) => match name_kind { |
526 | NameRefClass::ExternCrate(_) => HighlightTag::Module.into(), | ||
503 | NameRefClass::Definition(def) => { | 527 | NameRefClass::Definition(def) => { |
504 | if let Definition::Local(local) = &def { | 528 | if let Definition::Local(local) = &def { |
505 | if let Some(name) = local.name(db) { | 529 | if let Some(name) = local.name(db) { |
@@ -508,11 +532,13 @@ fn highlight_element( | |||
508 | binding_hash = Some(calc_binding_hash(&name, *shadow_count)) | 532 | binding_hash = Some(calc_binding_hash(&name, *shadow_count)) |
509 | } | 533 | } |
510 | }; | 534 | }; |
511 | highlight_name(db, def) | 535 | highlight_name(sema, db, def, Some(name_ref), possibly_unsafe) |
512 | } | 536 | } |
513 | NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(), | 537 | NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(), |
514 | }, | 538 | }, |
515 | None if syntactic_name_ref_highlighting => highlight_name_ref_by_syntax(name_ref), | 539 | None if syntactic_name_ref_highlighting => { |
540 | highlight_name_ref_by_syntax(name_ref, sema) | ||
541 | } | ||
516 | None => HighlightTag::UnresolvedReference.into(), | 542 | None => HighlightTag::UnresolvedReference.into(), |
517 | } | 543 | } |
518 | } | 544 | } |
@@ -540,13 +566,24 @@ fn highlight_element( | |||
540 | } | 566 | } |
541 | } | 567 | } |
542 | p if p.is_punct() => match p { | 568 | p if p.is_punct() => match p { |
543 | T![::] | T![->] | T![=>] | T![&] | T![..] | T![=] | T![@] => { | 569 | T![&] => { |
544 | HighlightTag::Operator.into() | 570 | let h = HighlightTag::Operator.into(); |
571 | let is_unsafe = element | ||
572 | .parent() | ||
573 | .and_then(ast::RefExpr::cast) | ||
574 | .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr)) | ||
575 | .unwrap_or(false); | ||
576 | if is_unsafe { | ||
577 | h | HighlightModifier::Unsafe | ||
578 | } else { | ||
579 | h | ||
580 | } | ||
545 | } | 581 | } |
582 | T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] => HighlightTag::Operator.into(), | ||
546 | T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { | 583 | T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { |
547 | HighlightTag::Macro.into() | 584 | HighlightTag::Macro.into() |
548 | } | 585 | } |
549 | T![*] if element.parent().and_then(ast::PointerType::cast).is_some() => { | 586 | T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => { |
550 | HighlightTag::Keyword.into() | 587 | HighlightTag::Keyword.into() |
551 | } | 588 | } |
552 | T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { | 589 | T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { |
@@ -623,6 +660,18 @@ fn highlight_element( | |||
623 | HighlightTag::SelfKeyword.into() | 660 | HighlightTag::SelfKeyword.into() |
624 | } | 661 | } |
625 | } | 662 | } |
663 | T![ref] => element | ||
664 | .parent() | ||
665 | .and_then(ast::IdentPat::cast) | ||
666 | .and_then(|ident_pat| { | ||
667 | if sema.is_unsafe_ident_pat(&ident_pat) { | ||
668 | Some(HighlightModifier::Unsafe) | ||
669 | } else { | ||
670 | None | ||
671 | } | ||
672 | }) | ||
673 | .map(|modifier| h | modifier) | ||
674 | .unwrap_or(h), | ||
626 | _ => h, | 675 | _ => h, |
627 | } | 676 | } |
628 | } | 677 | } |
@@ -652,16 +701,40 @@ fn is_child_of_impl(element: &SyntaxElement) -> bool { | |||
652 | } | 701 | } |
653 | } | 702 | } |
654 | 703 | ||
655 | fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { | 704 | fn highlight_name( |
705 | sema: &Semantics<RootDatabase>, | ||
706 | db: &RootDatabase, | ||
707 | def: Definition, | ||
708 | name_ref: Option<ast::NameRef>, | ||
709 | possibly_unsafe: bool, | ||
710 | ) -> Highlight { | ||
656 | match def { | 711 | match def { |
657 | Definition::Macro(_) => HighlightTag::Macro, | 712 | Definition::Macro(_) => HighlightTag::Macro, |
658 | Definition::Field(_) => HighlightTag::Field, | 713 | Definition::Field(field) => { |
714 | let mut h = HighlightTag::Field.into(); | ||
715 | if possibly_unsafe { | ||
716 | if let VariantDef::Union(_) = field.parent_def(db) { | ||
717 | h |= HighlightModifier::Unsafe; | ||
718 | } | ||
719 | } | ||
720 | |||
721 | return h; | ||
722 | } | ||
659 | Definition::ModuleDef(def) => match def { | 723 | Definition::ModuleDef(def) => match def { |
660 | hir::ModuleDef::Module(_) => HighlightTag::Module, | 724 | hir::ModuleDef::Module(_) => HighlightTag::Module, |
661 | hir::ModuleDef::Function(func) => { | 725 | hir::ModuleDef::Function(func) => { |
662 | let mut h = HighlightTag::Function.into(); | 726 | let mut h = HighlightTag::Function.into(); |
663 | if func.is_unsafe(db) { | 727 | if func.is_unsafe(db) { |
664 | h |= HighlightModifier::Unsafe; | 728 | h |= HighlightModifier::Unsafe; |
729 | } else { | ||
730 | let is_unsafe = name_ref | ||
731 | .and_then(|name_ref| name_ref.syntax().parent()) | ||
732 | .and_then(ast::MethodCallExpr::cast) | ||
733 | .map(|method_call_expr| sema.is_unsafe_method_call(method_call_expr)) | ||
734 | .unwrap_or(false); | ||
735 | if is_unsafe { | ||
736 | h |= HighlightModifier::Unsafe; | ||
737 | } | ||
665 | } | 738 | } |
666 | return h; | 739 | return h; |
667 | } | 740 | } |
@@ -677,6 +750,7 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { | |||
677 | let mut h = Highlight::new(HighlightTag::Static); | 750 | let mut h = Highlight::new(HighlightTag::Static); |
678 | if s.is_mut(db) { | 751 | if s.is_mut(db) { |
679 | h |= HighlightModifier::Mutable; | 752 | h |= HighlightModifier::Mutable; |
753 | h |= HighlightModifier::Unsafe; | ||
680 | } | 754 | } |
681 | return h; | 755 | return h; |
682 | } | 756 | } |
@@ -724,7 +798,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | |||
724 | tag.into() | 798 | tag.into() |
725 | } | 799 | } |
726 | 800 | ||
727 | fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight { | 801 | fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight { |
728 | let default = HighlightTag::UnresolvedReference; | 802 | let default = HighlightTag::UnresolvedReference; |
729 | 803 | ||
730 | let parent = match name.syntax().parent() { | 804 | let parent = match name.syntax().parent() { |
@@ -732,9 +806,36 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight { | |||
732 | _ => return default.into(), | 806 | _ => return default.into(), |
733 | }; | 807 | }; |
734 | 808 | ||
735 | let tag = match parent.kind() { | 809 | match parent.kind() { |
736 | METHOD_CALL_EXPR => HighlightTag::Function, | 810 | METHOD_CALL_EXPR => { |
737 | FIELD_EXPR => HighlightTag::Field, | 811 | let mut h = Highlight::new(HighlightTag::Function); |
812 | let is_unsafe = ast::MethodCallExpr::cast(parent) | ||
813 | .map(|method_call_expr| sema.is_unsafe_method_call(method_call_expr)) | ||
814 | .unwrap_or(false); | ||
815 | if is_unsafe { | ||
816 | h |= HighlightModifier::Unsafe; | ||
817 | } | ||
818 | |||
819 | h | ||
820 | } | ||
821 | FIELD_EXPR => { | ||
822 | let h = HighlightTag::Field; | ||
823 | let is_union = ast::FieldExpr::cast(parent) | ||
824 | .and_then(|field_expr| { | ||
825 | let field = sema.resolve_field(&field_expr)?; | ||
826 | Some(if let VariantDef::Union(_) = field.parent_def(sema.db) { | ||
827 | true | ||
828 | } else { | ||
829 | false | ||
830 | }) | ||
831 | }) | ||
832 | .unwrap_or(false); | ||
833 | if is_union { | ||
834 | h | HighlightModifier::Unsafe | ||
835 | } else { | ||
836 | h.into() | ||
837 | } | ||
838 | } | ||
738 | PATH_SEGMENT => { | 839 | PATH_SEGMENT => { |
739 | let path = match parent.parent().and_then(ast::Path::cast) { | 840 | let path = match parent.parent().and_then(ast::Path::cast) { |
740 | Some(it) => it, | 841 | Some(it) => it, |
@@ -758,18 +859,15 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight { | |||
758 | }; | 859 | }; |
759 | 860 | ||
760 | match parent.kind() { | 861 | match parent.kind() { |
761 | CALL_EXPR => HighlightTag::Function, | 862 | CALL_EXPR => HighlightTag::Function.into(), |
762 | _ => { | 863 | _ => if name.text().chars().next().unwrap_or_default().is_uppercase() { |
763 | if name.text().chars().next().unwrap_or_default().is_uppercase() { | 864 | HighlightTag::Struct.into() |
764 | HighlightTag::Struct | 865 | } else { |
765 | } else { | 866 | HighlightTag::Constant |
766 | HighlightTag::Constant | ||
767 | } | ||
768 | } | 867 | } |
868 | .into(), | ||
769 | } | 869 | } |
770 | } | 870 | } |
771 | _ => default, | 871 | _ => default.into(), |
772 | }; | 872 | } |
773 | |||
774 | tag.into() | ||
775 | } | 873 | } |
diff --git a/crates/ra_ide/src/syntax_highlighting/injection.rs b/crates/ra_ide/src/syntax_highlighting/injection.rs index 8665b480f..6046643ef 100644 --- a/crates/ra_ide/src/syntax_highlighting/injection.rs +++ b/crates/ra_ide/src/syntax_highlighting/injection.rs | |||
@@ -4,8 +4,8 @@ use std::{collections::BTreeMap, convert::TryFrom}; | |||
4 | 4 | ||
5 | use ast::{HasQuotes, HasStringValue}; | 5 | use ast::{HasQuotes, HasStringValue}; |
6 | use hir::Semantics; | 6 | use hir::Semantics; |
7 | use itertools::Itertools; | ||
7 | use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; | 8 | use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; |
8 | use stdx::SepBy; | ||
9 | 9 | ||
10 | use crate::{ | 10 | use crate::{ |
11 | call_info::ActiveParameter, Analysis, Highlight, HighlightModifier, HighlightTag, | 11 | call_info::ActiveParameter, Analysis, Highlight, HighlightModifier, HighlightTag, |
@@ -129,8 +129,7 @@ pub(super) fn extract_doc_comments( | |||
129 | 129 | ||
130 | line[pos..].to_owned() | 130 | line[pos..].to_owned() |
131 | }) | 131 | }) |
132 | .sep_by("\n") | 132 | .join("\n"); |
133 | .to_string(); | ||
134 | 133 | ||
135 | if doctest.is_empty() { | 134 | if doctest.is_empty() { |
136 | return None; | 135 | return None; |
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index 87a6e2523..a8087635a 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs | |||
@@ -9,6 +9,9 @@ use crate::{mock_analysis::single_file, FileRange, TextRange}; | |||
9 | fn test_highlighting() { | 9 | fn test_highlighting() { |
10 | check_highlighting( | 10 | check_highlighting( |
11 | r#" | 11 | r#" |
12 | use inner::{self as inner_mod}; | ||
13 | mod inner {} | ||
14 | |||
12 | #[derive(Clone, Debug)] | 15 | #[derive(Clone, Debug)] |
13 | struct Foo { | 16 | struct Foo { |
14 | pub x: i32, | 17 | pub x: i32, |
@@ -272,19 +275,64 @@ fn test_unsafe_highlighting() { | |||
272 | r#" | 275 | r#" |
273 | unsafe fn unsafe_fn() {} | 276 | unsafe fn unsafe_fn() {} |
274 | 277 | ||
278 | union Union { | ||
279 | a: u32, | ||
280 | b: f32, | ||
281 | } | ||
282 | |||
275 | struct HasUnsafeFn; | 283 | struct HasUnsafeFn; |
276 | 284 | ||
277 | impl HasUnsafeFn { | 285 | impl HasUnsafeFn { |
278 | unsafe fn unsafe_method(&self) {} | 286 | unsafe fn unsafe_method(&self) {} |
279 | } | 287 | } |
280 | 288 | ||
289 | struct TypeForStaticMut { | ||
290 | a: u8 | ||
291 | } | ||
292 | |||
293 | static mut global_mut: TypeForStaticMut = TypeForStaticMut { a: 0 }; | ||
294 | |||
295 | #[repr(packed)] | ||
296 | struct Packed { | ||
297 | a: u16, | ||
298 | } | ||
299 | |||
300 | trait DoTheAutoref { | ||
301 | fn calls_autoref(&self); | ||
302 | } | ||
303 | |||
304 | impl DoTheAutoref for u16 { | ||
305 | fn calls_autoref(&self) {} | ||
306 | } | ||
307 | |||
281 | fn main() { | 308 | fn main() { |
282 | let x = &5 as *const usize; | 309 | let x = &5 as *const _ as *const usize; |
310 | let u = Union { b: 0 }; | ||
283 | unsafe { | 311 | unsafe { |
312 | // unsafe fn and method calls | ||
284 | unsafe_fn(); | 313 | unsafe_fn(); |
314 | let b = u.b; | ||
315 | match u { | ||
316 | Union { b: 0 } => (), | ||
317 | Union { a } => (), | ||
318 | } | ||
285 | HasUnsafeFn.unsafe_method(); | 319 | HasUnsafeFn.unsafe_method(); |
286 | let y = *(x); | 320 | |
287 | let z = -x; | 321 | // unsafe deref |
322 | let y = *x; | ||
323 | |||
324 | // unsafe access to a static mut | ||
325 | let a = global_mut.a; | ||
326 | |||
327 | // unsafe ref of packed fields | ||
328 | let packed = Packed { a: 0 }; | ||
329 | let a = &packed.a; | ||
330 | let ref a = packed.a; | ||
331 | let Packed { ref a } = packed; | ||
332 | let Packed { a: ref _a } = packed; | ||
333 | |||
334 | // unsafe auto ref of packed field | ||
335 | packed.a.calls_autoref(); | ||
288 | } | 336 | } |
289 | } | 337 | } |
290 | "# | 338 | "# |
@@ -370,6 +418,23 @@ macro_rules! noop { | |||
370 | ); | 418 | ); |
371 | } | 419 | } |
372 | 420 | ||
421 | #[test] | ||
422 | fn test_extern_crate() { | ||
423 | check_highlighting( | ||
424 | r#" | ||
425 | //- /main.rs | ||
426 | extern crate std; | ||
427 | extern crate alloc as abc; | ||
428 | //- /std/lib.rs | ||
429 | pub struct S; | ||
430 | //- /alloc/lib.rs | ||
431 | pub struct A | ||
432 | "#, | ||
433 | expect_file!["crates/ra_ide/test_data/highlight_extern_crate.html"], | ||
434 | false, | ||
435 | ); | ||
436 | } | ||
437 | |||
373 | /// Highlights the code given by the `ra_fixture` argument, renders the | 438 | /// Highlights the code given by the `ra_fixture` argument, renders the |
374 | /// result as HTML, and compares it with the HTML file given as `snapshot`. | 439 | /// result as HTML, and compares it with the HTML file given as `snapshot`. |
375 | /// Note that the `snapshot` file is overwritten by the rendered HTML. | 440 | /// Note that the `snapshot` file is overwritten by the rendered HTML. |