diff options
Diffstat (limited to 'crates/ide')
41 files changed, 279 insertions, 2605 deletions
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml index 88f3d09d3..0e8447394 100644 --- a/crates/ide/Cargo.toml +++ b/crates/ide/Cargo.toml | |||
@@ -10,7 +10,7 @@ edition = "2018" | |||
10 | doctest = false | 10 | doctest = false |
11 | 11 | ||
12 | [dependencies] | 12 | [dependencies] |
13 | cov-mark = { version = "1.1", features = ["thread-local"] } | 13 | cov-mark = "2.0.0-pre.1" |
14 | either = "1.5.3" | 14 | either = "1.5.3" |
15 | indexmap = "1.4.0" | 15 | indexmap = "1.4.0" |
16 | itertools = "0.10.0" | 16 | itertools = "0.10.0" |
@@ -29,6 +29,7 @@ ide_db = { path = "../ide_db", version = "0.0.0" } | |||
29 | cfg = { path = "../cfg", version = "0.0.0" } | 29 | cfg = { path = "../cfg", version = "0.0.0" } |
30 | profile = { path = "../profile", version = "0.0.0" } | 30 | profile = { path = "../profile", version = "0.0.0" } |
31 | ide_assists = { path = "../ide_assists", version = "0.0.0" } | 31 | ide_assists = { path = "../ide_assists", version = "0.0.0" } |
32 | ide_diagnostics = { path = "../ide_diagnostics", version = "0.0.0" } | ||
32 | ide_ssr = { path = "../ide_ssr", version = "0.0.0" } | 33 | ide_ssr = { path = "../ide_ssr", version = "0.0.0" } |
33 | ide_completion = { path = "../ide_completion", version = "0.0.0" } | 34 | ide_completion = { path = "../ide_completion", version = "0.0.0" } |
34 | 35 | ||
@@ -39,4 +40,3 @@ hir = { path = "../hir", version = "0.0.0" } | |||
39 | [dev-dependencies] | 40 | [dev-dependencies] |
40 | test_utils = { path = "../test_utils" } | 41 | test_utils = { path = "../test_utils" } |
41 | expect-test = "1.1" | 42 | expect-test = "1.1" |
42 | cov-mark = "1.1.0" | ||
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs deleted file mode 100644 index d5c954b8b..000000000 --- a/crates/ide/src/diagnostics.rs +++ /dev/null | |||
@@ -1,722 +0,0 @@ | |||
1 | //! Collects diagnostics & fixits for a single file. | ||
2 | //! | ||
3 | //! The tricky bit here is that diagnostics are produced by hir in terms of | ||
4 | //! macro-expanded files, but we need to present them to the users in terms of | ||
5 | //! original files. So we need to map the ranges. | ||
6 | |||
7 | mod fixes; | ||
8 | mod field_shorthand; | ||
9 | mod unlinked_file; | ||
10 | |||
11 | use std::cell::RefCell; | ||
12 | |||
13 | use hir::{ | ||
14 | db::AstDatabase, | ||
15 | diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, | ||
16 | InFile, Semantics, | ||
17 | }; | ||
18 | use ide_assists::AssistResolveStrategy; | ||
19 | use ide_db::{base_db::SourceDatabase, RootDatabase}; | ||
20 | use itertools::Itertools; | ||
21 | use rustc_hash::FxHashSet; | ||
22 | use syntax::{ | ||
23 | ast::{self, AstNode}, | ||
24 | SyntaxNode, SyntaxNodePtr, TextRange, TextSize, | ||
25 | }; | ||
26 | use text_edit::TextEdit; | ||
27 | use unlinked_file::UnlinkedFile; | ||
28 | |||
29 | use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; | ||
30 | |||
31 | use self::fixes::DiagnosticWithFixes; | ||
32 | |||
33 | #[derive(Debug)] | ||
34 | pub struct Diagnostic { | ||
35 | // pub name: Option<String>, | ||
36 | pub message: String, | ||
37 | pub range: TextRange, | ||
38 | pub severity: Severity, | ||
39 | pub fixes: Option<Vec<Assist>>, | ||
40 | pub unused: bool, | ||
41 | pub code: Option<DiagnosticCode>, | ||
42 | } | ||
43 | |||
44 | impl Diagnostic { | ||
45 | fn error(range: TextRange, message: String) -> Self { | ||
46 | Self { message, range, severity: Severity::Error, fixes: None, unused: false, code: None } | ||
47 | } | ||
48 | |||
49 | fn hint(range: TextRange, message: String) -> Self { | ||
50 | Self { | ||
51 | message, | ||
52 | range, | ||
53 | severity: Severity::WeakWarning, | ||
54 | fixes: None, | ||
55 | unused: false, | ||
56 | code: None, | ||
57 | } | ||
58 | } | ||
59 | |||
60 | fn with_fixes(self, fixes: Option<Vec<Assist>>) -> Self { | ||
61 | Self { fixes, ..self } | ||
62 | } | ||
63 | |||
64 | fn with_unused(self, unused: bool) -> Self { | ||
65 | Self { unused, ..self } | ||
66 | } | ||
67 | |||
68 | fn with_code(self, code: Option<DiagnosticCode>) -> Self { | ||
69 | Self { code, ..self } | ||
70 | } | ||
71 | } | ||
72 | |||
73 | #[derive(Debug, Copy, Clone)] | ||
74 | pub enum Severity { | ||
75 | Error, | ||
76 | WeakWarning, | ||
77 | } | ||
78 | |||
79 | #[derive(Default, Debug, Clone)] | ||
80 | pub struct DiagnosticsConfig { | ||
81 | pub disable_experimental: bool, | ||
82 | pub disabled: FxHashSet<String>, | ||
83 | } | ||
84 | |||
85 | pub(crate) fn diagnostics( | ||
86 | db: &RootDatabase, | ||
87 | config: &DiagnosticsConfig, | ||
88 | resolve: &AssistResolveStrategy, | ||
89 | file_id: FileId, | ||
90 | ) -> Vec<Diagnostic> { | ||
91 | let _p = profile::span("diagnostics"); | ||
92 | let sema = Semantics::new(db); | ||
93 | let parse = db.parse(file_id); | ||
94 | let mut res = Vec::new(); | ||
95 | |||
96 | // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. | ||
97 | res.extend( | ||
98 | parse | ||
99 | .errors() | ||
100 | .iter() | ||
101 | .take(128) | ||
102 | .map(|err| Diagnostic::error(err.range(), format!("Syntax Error: {}", err))), | ||
103 | ); | ||
104 | |||
105 | for node in parse.tree().syntax().descendants() { | ||
106 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); | ||
107 | field_shorthand::check(&mut res, file_id, &node); | ||
108 | } | ||
109 | let res = RefCell::new(res); | ||
110 | let sink_builder = DiagnosticSinkBuilder::new() | ||
111 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { | ||
112 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); | ||
113 | }) | ||
114 | .on::<hir::diagnostics::MissingFields, _>(|d| { | ||
115 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); | ||
116 | }) | ||
117 | .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| { | ||
118 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); | ||
119 | }) | ||
120 | .on::<hir::diagnostics::NoSuchField, _>(|d| { | ||
121 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); | ||
122 | }) | ||
123 | .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| { | ||
124 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); | ||
125 | }) | ||
126 | .on::<hir::diagnostics::IncorrectCase, _>(|d| { | ||
127 | res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); | ||
128 | }) | ||
129 | .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| { | ||
130 | res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); | ||
131 | }) | ||
132 | .on::<hir::diagnostics::InactiveCode, _>(|d| { | ||
133 | // If there's inactive code somewhere in a macro, don't propagate to the call-site. | ||
134 | if d.display_source().file_id.expansion_info(db).is_some() { | ||
135 | return; | ||
136 | } | ||
137 | |||
138 | // Override severity and mark as unused. | ||
139 | res.borrow_mut().push( | ||
140 | Diagnostic::hint( | ||
141 | sema.diagnostics_display_range(d.display_source()).range, | ||
142 | d.message(), | ||
143 | ) | ||
144 | .with_unused(true) | ||
145 | .with_code(Some(d.code())), | ||
146 | ); | ||
147 | }) | ||
148 | .on::<UnlinkedFile, _>(|d| { | ||
149 | // Limit diagnostic to the first few characters in the file. This matches how VS Code | ||
150 | // renders it with the full span, but on other editors, and is less invasive. | ||
151 | let range = sema.diagnostics_display_range(d.display_source()).range; | ||
152 | let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range); | ||
153 | |||
154 | // Override severity and mark as unused. | ||
155 | res.borrow_mut().push( | ||
156 | Diagnostic::hint(range, d.message()) | ||
157 | .with_fixes(d.fixes(&sema, resolve)) | ||
158 | .with_code(Some(d.code())), | ||
159 | ); | ||
160 | }) | ||
161 | .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| { | ||
162 | // Use more accurate position if available. | ||
163 | let display_range = d | ||
164 | .precise_location | ||
165 | .unwrap_or_else(|| sema.diagnostics_display_range(d.display_source()).range); | ||
166 | |||
167 | // FIXME: it would be nice to tell the user whether proc macros are currently disabled | ||
168 | res.borrow_mut() | ||
169 | .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code()))); | ||
170 | }) | ||
171 | .on::<hir::diagnostics::UnresolvedMacroCall, _>(|d| { | ||
172 | let last_path_segment = sema.db.parse_or_expand(d.file).and_then(|root| { | ||
173 | d.node | ||
174 | .to_node(&root) | ||
175 | .path() | ||
176 | .and_then(|it| it.segment()) | ||
177 | .and_then(|it| it.name_ref()) | ||
178 | .map(|it| InFile::new(d.file, SyntaxNodePtr::new(it.syntax()))) | ||
179 | }); | ||
180 | let diagnostics = last_path_segment.unwrap_or_else(|| d.display_source()); | ||
181 | let display_range = sema.diagnostics_display_range(diagnostics).range; | ||
182 | res.borrow_mut() | ||
183 | .push(Diagnostic::error(display_range, d.message()).with_code(Some(d.code()))); | ||
184 | }) | ||
185 | .on::<hir::diagnostics::UnimplementedBuiltinMacro, _>(|d| { | ||
186 | let display_range = sema.diagnostics_display_range(d.display_source()).range; | ||
187 | res.borrow_mut() | ||
188 | .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code()))); | ||
189 | }) | ||
190 | // Only collect experimental diagnostics when they're enabled. | ||
191 | .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) | ||
192 | .filter(|diag| !config.disabled.contains(diag.code().as_str())); | ||
193 | |||
194 | // Finalize the `DiagnosticSink` building process. | ||
195 | let mut sink = sink_builder | ||
196 | // Diagnostics not handled above get no fix and default treatment. | ||
197 | .build(|d| { | ||
198 | res.borrow_mut().push( | ||
199 | Diagnostic::error( | ||
200 | sema.diagnostics_display_range(d.display_source()).range, | ||
201 | d.message(), | ||
202 | ) | ||
203 | .with_code(Some(d.code())), | ||
204 | ); | ||
205 | }); | ||
206 | |||
207 | match sema.to_module_def(file_id) { | ||
208 | Some(m) => m.diagnostics(db, &mut sink), | ||
209 | None => { | ||
210 | sink.push(UnlinkedFile { file_id, node: SyntaxNodePtr::new(&parse.tree().syntax()) }); | ||
211 | } | ||
212 | } | ||
213 | |||
214 | drop(sink); | ||
215 | res.into_inner() | ||
216 | } | ||
217 | |||
218 | fn diagnostic_with_fix<D: DiagnosticWithFixes>( | ||
219 | d: &D, | ||
220 | sema: &Semantics<RootDatabase>, | ||
221 | resolve: &AssistResolveStrategy, | ||
222 | ) -> Diagnostic { | ||
223 | Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) | ||
224 | .with_fixes(d.fixes(&sema, resolve)) | ||
225 | .with_code(Some(d.code())) | ||
226 | } | ||
227 | |||
228 | fn warning_with_fix<D: DiagnosticWithFixes>( | ||
229 | d: &D, | ||
230 | sema: &Semantics<RootDatabase>, | ||
231 | resolve: &AssistResolveStrategy, | ||
232 | ) -> Diagnostic { | ||
233 | Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) | ||
234 | .with_fixes(d.fixes(&sema, resolve)) | ||
235 | .with_code(Some(d.code())) | ||
236 | } | ||
237 | |||
238 | fn check_unnecessary_braces_in_use_statement( | ||
239 | acc: &mut Vec<Diagnostic>, | ||
240 | file_id: FileId, | ||
241 | node: &SyntaxNode, | ||
242 | ) -> Option<()> { | ||
243 | let use_tree_list = ast::UseTreeList::cast(node.clone())?; | ||
244 | if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { | ||
245 | // If there is a comment inside the bracketed `use`, | ||
246 | // assume it is a commented out module path and don't show diagnostic. | ||
247 | if use_tree_list.has_inner_comment() { | ||
248 | return Some(()); | ||
249 | } | ||
250 | |||
251 | let use_range = use_tree_list.syntax().text_range(); | ||
252 | let edit = | ||
253 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree) | ||
254 | .unwrap_or_else(|| { | ||
255 | let to_replace = single_use_tree.syntax().text().to_string(); | ||
256 | let mut edit_builder = TextEdit::builder(); | ||
257 | edit_builder.delete(use_range); | ||
258 | edit_builder.insert(use_range.start(), to_replace); | ||
259 | edit_builder.finish() | ||
260 | }); | ||
261 | |||
262 | acc.push( | ||
263 | Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) | ||
264 | .with_fixes(Some(vec![fix( | ||
265 | "remove_braces", | ||
266 | "Remove unnecessary braces", | ||
267 | SourceChange::from_text_edit(file_id, edit), | ||
268 | use_range, | ||
269 | )])), | ||
270 | ); | ||
271 | } | ||
272 | |||
273 | Some(()) | ||
274 | } | ||
275 | |||
276 | fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | ||
277 | single_use_tree: &ast::UseTree, | ||
278 | ) -> Option<TextEdit> { | ||
279 | let use_tree_list_node = single_use_tree.syntax().parent()?; | ||
280 | if single_use_tree.path()?.segment()?.self_token().is_some() { | ||
281 | let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); | ||
282 | let end = use_tree_list_node.text_range().end(); | ||
283 | return Some(TextEdit::delete(TextRange::new(start, end))); | ||
284 | } | ||
285 | None | ||
286 | } | ||
287 | |||
288 | fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist { | ||
289 | let mut res = unresolved_fix(id, label, target); | ||
290 | res.source_change = Some(source_change); | ||
291 | res | ||
292 | } | ||
293 | |||
294 | fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist { | ||
295 | assert!(!id.contains(' ')); | ||
296 | Assist { | ||
297 | id: AssistId(id, AssistKind::QuickFix), | ||
298 | label: Label::new(label), | ||
299 | group: None, | ||
300 | target, | ||
301 | source_change: None, | ||
302 | } | ||
303 | } | ||
304 | |||
305 | #[cfg(test)] | ||
306 | mod tests { | ||
307 | use expect_test::Expect; | ||
308 | use ide_assists::AssistResolveStrategy; | ||
309 | use stdx::trim_indent; | ||
310 | use test_utils::{assert_eq_text, extract_annotations}; | ||
311 | |||
312 | use crate::{fixture, DiagnosticsConfig}; | ||
313 | |||
314 | /// Takes a multi-file input fixture with annotated cursor positions, | ||
315 | /// and checks that: | ||
316 | /// * a diagnostic is produced | ||
317 | /// * the first diagnostic fix trigger range touches the input cursor position | ||
318 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied | ||
319 | #[track_caller] | ||
320 | pub(crate) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { | ||
321 | check_nth_fix(0, ra_fixture_before, ra_fixture_after); | ||
322 | } | ||
323 | /// Takes a multi-file input fixture with annotated cursor positions, | ||
324 | /// and checks that: | ||
325 | /// * a diagnostic is produced | ||
326 | /// * every diagnostic fixes trigger range touches the input cursor position | ||
327 | /// * that the contents of the file containing the cursor match `after` after each diagnostic fix is applied | ||
328 | pub(crate) fn check_fixes(ra_fixture_before: &str, ra_fixtures_after: Vec<&str>) { | ||
329 | for (i, ra_fixture_after) in ra_fixtures_after.iter().enumerate() { | ||
330 | check_nth_fix(i, ra_fixture_before, ra_fixture_after) | ||
331 | } | ||
332 | } | ||
333 | |||
334 | #[track_caller] | ||
335 | fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) { | ||
336 | let after = trim_indent(ra_fixture_after); | ||
337 | |||
338 | let (analysis, file_position) = fixture::position(ra_fixture_before); | ||
339 | let diagnostic = analysis | ||
340 | .diagnostics( | ||
341 | &DiagnosticsConfig::default(), | ||
342 | AssistResolveStrategy::All, | ||
343 | file_position.file_id, | ||
344 | ) | ||
345 | .unwrap() | ||
346 | .pop() | ||
347 | .unwrap(); | ||
348 | let fix = &diagnostic.fixes.unwrap()[nth]; | ||
349 | let actual = { | ||
350 | let source_change = fix.source_change.as_ref().unwrap(); | ||
351 | let file_id = *source_change.source_file_edits.keys().next().unwrap(); | ||
352 | let mut actual = analysis.file_text(file_id).unwrap().to_string(); | ||
353 | |||
354 | for edit in source_change.source_file_edits.values() { | ||
355 | edit.apply(&mut actual); | ||
356 | } | ||
357 | actual | ||
358 | }; | ||
359 | |||
360 | assert_eq_text!(&after, &actual); | ||
361 | assert!( | ||
362 | fix.target.contains_inclusive(file_position.offset), | ||
363 | "diagnostic fix range {:?} does not touch cursor position {:?}", | ||
364 | fix.target, | ||
365 | file_position.offset | ||
366 | ); | ||
367 | } | ||
368 | /// Checks that there's a diagnostic *without* fix at `$0`. | ||
369 | fn check_no_fix(ra_fixture: &str) { | ||
370 | let (analysis, file_position) = fixture::position(ra_fixture); | ||
371 | let diagnostic = analysis | ||
372 | .diagnostics( | ||
373 | &DiagnosticsConfig::default(), | ||
374 | AssistResolveStrategy::All, | ||
375 | file_position.file_id, | ||
376 | ) | ||
377 | .unwrap() | ||
378 | .pop() | ||
379 | .unwrap(); | ||
380 | assert!(diagnostic.fixes.is_none(), "got a fix when none was expected: {:?}", diagnostic); | ||
381 | } | ||
382 | |||
383 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics | ||
384 | /// apply to the file containing the cursor. | ||
385 | pub(crate) fn check_no_diagnostics(ra_fixture: &str) { | ||
386 | let (analysis, files) = fixture::files(ra_fixture); | ||
387 | let diagnostics = files | ||
388 | .into_iter() | ||
389 | .flat_map(|file_id| { | ||
390 | analysis | ||
391 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) | ||
392 | .unwrap() | ||
393 | }) | ||
394 | .collect::<Vec<_>>(); | ||
395 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); | ||
396 | } | ||
397 | |||
398 | pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) { | ||
399 | let (analysis, file_id) = fixture::file(ra_fixture); | ||
400 | let diagnostics = analysis | ||
401 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) | ||
402 | .unwrap(); | ||
403 | expect.assert_debug_eq(&diagnostics) | ||
404 | } | ||
405 | |||
406 | pub(crate) fn check_diagnostics(ra_fixture: &str) { | ||
407 | let (analysis, file_id) = fixture::file(ra_fixture); | ||
408 | let diagnostics = analysis | ||
409 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) | ||
410 | .unwrap(); | ||
411 | |||
412 | let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); | ||
413 | let actual = diagnostics.into_iter().map(|d| (d.range, d.message)).collect::<Vec<_>>(); | ||
414 | assert_eq!(expected, actual); | ||
415 | } | ||
416 | |||
417 | #[test] | ||
418 | fn test_unresolved_macro_range() { | ||
419 | check_diagnostics( | ||
420 | r#" | ||
421 | foo::bar!(92); | ||
422 | //^^^ unresolved macro `foo::bar!` | ||
423 | "#, | ||
424 | ); | ||
425 | } | ||
426 | |||
427 | #[test] | ||
428 | fn unresolved_import_in_use_tree() { | ||
429 | // Only the relevant part of a nested `use` item should be highlighted. | ||
430 | check_diagnostics( | ||
431 | r#" | ||
432 | use does_exist::{Exists, DoesntExist}; | ||
433 | //^^^^^^^^^^^ unresolved import | ||
434 | |||
435 | use {does_not_exist::*, does_exist}; | ||
436 | //^^^^^^^^^^^^^^^^^ unresolved import | ||
437 | |||
438 | use does_not_exist::{ | ||
439 | a, | ||
440 | //^ unresolved import | ||
441 | b, | ||
442 | //^ unresolved import | ||
443 | c, | ||
444 | //^ unresolved import | ||
445 | }; | ||
446 | |||
447 | mod does_exist { | ||
448 | pub struct Exists; | ||
449 | } | ||
450 | "#, | ||
451 | ); | ||
452 | } | ||
453 | |||
454 | #[test] | ||
455 | fn range_mapping_out_of_macros() { | ||
456 | // FIXME: this is very wrong, but somewhat tricky to fix. | ||
457 | check_fix( | ||
458 | r#" | ||
459 | fn some() {} | ||
460 | fn items() {} | ||
461 | fn here() {} | ||
462 | |||
463 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | ||
464 | |||
465 | fn main() { | ||
466 | let _x = id![Foo { a: $042 }]; | ||
467 | } | ||
468 | |||
469 | pub struct Foo { pub a: i32, pub b: i32 } | ||
470 | "#, | ||
471 | r#" | ||
472 | fn some(, b: () ) {} | ||
473 | fn items() {} | ||
474 | fn here() {} | ||
475 | |||
476 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | ||
477 | |||
478 | fn main() { | ||
479 | let _x = id![Foo { a: 42 }]; | ||
480 | } | ||
481 | |||
482 | pub struct Foo { pub a: i32, pub b: i32 } | ||
483 | "#, | ||
484 | ); | ||
485 | } | ||
486 | |||
487 | #[test] | ||
488 | fn test_check_unnecessary_braces_in_use_statement() { | ||
489 | check_no_diagnostics( | ||
490 | r#" | ||
491 | use a; | ||
492 | use a::{c, d::e}; | ||
493 | |||
494 | mod a { | ||
495 | mod c {} | ||
496 | mod d { | ||
497 | mod e {} | ||
498 | } | ||
499 | } | ||
500 | "#, | ||
501 | ); | ||
502 | check_no_diagnostics( | ||
503 | r#" | ||
504 | use a; | ||
505 | use a::{ | ||
506 | c, | ||
507 | // d::e | ||
508 | }; | ||
509 | |||
510 | mod a { | ||
511 | mod c {} | ||
512 | mod d { | ||
513 | mod e {} | ||
514 | } | ||
515 | } | ||
516 | "#, | ||
517 | ); | ||
518 | check_fix( | ||
519 | r" | ||
520 | mod b {} | ||
521 | use {$0b}; | ||
522 | ", | ||
523 | r" | ||
524 | mod b {} | ||
525 | use b; | ||
526 | ", | ||
527 | ); | ||
528 | check_fix( | ||
529 | r" | ||
530 | mod b {} | ||
531 | use {b$0}; | ||
532 | ", | ||
533 | r" | ||
534 | mod b {} | ||
535 | use b; | ||
536 | ", | ||
537 | ); | ||
538 | check_fix( | ||
539 | r" | ||
540 | mod a { mod c {} } | ||
541 | use a::{c$0}; | ||
542 | ", | ||
543 | r" | ||
544 | mod a { mod c {} } | ||
545 | use a::c; | ||
546 | ", | ||
547 | ); | ||
548 | check_fix( | ||
549 | r" | ||
550 | mod a {} | ||
551 | use a::{self$0}; | ||
552 | ", | ||
553 | r" | ||
554 | mod a {} | ||
555 | use a; | ||
556 | ", | ||
557 | ); | ||
558 | check_fix( | ||
559 | r" | ||
560 | mod a { mod c {} mod d { mod e {} } } | ||
561 | use a::{c, d::{e$0}}; | ||
562 | ", | ||
563 | r" | ||
564 | mod a { mod c {} mod d { mod e {} } } | ||
565 | use a::{c, d::e}; | ||
566 | ", | ||
567 | ); | ||
568 | } | ||
569 | |||
570 | #[test] | ||
571 | fn test_disabled_diagnostics() { | ||
572 | let mut config = DiagnosticsConfig::default(); | ||
573 | config.disabled.insert("unresolved-module".into()); | ||
574 | |||
575 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); | ||
576 | |||
577 | let diagnostics = | ||
578 | analysis.diagnostics(&config, AssistResolveStrategy::All, file_id).unwrap(); | ||
579 | assert!(diagnostics.is_empty()); | ||
580 | |||
581 | let diagnostics = analysis | ||
582 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) | ||
583 | .unwrap(); | ||
584 | assert!(!diagnostics.is_empty()); | ||
585 | } | ||
586 | |||
587 | #[test] | ||
588 | fn unlinked_file_prepend_first_item() { | ||
589 | cov_mark::check!(unlinked_file_prepend_before_first_item); | ||
590 | // Only tests the first one for `pub mod` since the rest are the same | ||
591 | check_fixes( | ||
592 | r#" | ||
593 | //- /main.rs | ||
594 | fn f() {} | ||
595 | //- /foo.rs | ||
596 | $0 | ||
597 | "#, | ||
598 | vec![ | ||
599 | r#" | ||
600 | mod foo; | ||
601 | |||
602 | fn f() {} | ||
603 | "#, | ||
604 | r#" | ||
605 | pub mod foo; | ||
606 | |||
607 | fn f() {} | ||
608 | "#, | ||
609 | ], | ||
610 | ); | ||
611 | } | ||
612 | |||
613 | #[test] | ||
614 | fn unlinked_file_append_mod() { | ||
615 | cov_mark::check!(unlinked_file_append_to_existing_mods); | ||
616 | check_fix( | ||
617 | r#" | ||
618 | //- /main.rs | ||
619 | //! Comment on top | ||
620 | |||
621 | mod preexisting; | ||
622 | |||
623 | mod preexisting2; | ||
624 | |||
625 | struct S; | ||
626 | |||
627 | mod preexisting_bottom;) | ||
628 | //- /foo.rs | ||
629 | $0 | ||
630 | "#, | ||
631 | r#" | ||
632 | //! Comment on top | ||
633 | |||
634 | mod preexisting; | ||
635 | |||
636 | mod preexisting2; | ||
637 | mod foo; | ||
638 | |||
639 | struct S; | ||
640 | |||
641 | mod preexisting_bottom;) | ||
642 | "#, | ||
643 | ); | ||
644 | } | ||
645 | |||
646 | #[test] | ||
647 | fn unlinked_file_insert_in_empty_file() { | ||
648 | cov_mark::check!(unlinked_file_empty_file); | ||
649 | check_fix( | ||
650 | r#" | ||
651 | //- /main.rs | ||
652 | //- /foo.rs | ||
653 | $0 | ||
654 | "#, | ||
655 | r#" | ||
656 | mod foo; | ||
657 | "#, | ||
658 | ); | ||
659 | } | ||
660 | |||
661 | #[test] | ||
662 | fn unlinked_file_old_style_modrs() { | ||
663 | check_fix( | ||
664 | r#" | ||
665 | //- /main.rs | ||
666 | mod submod; | ||
667 | //- /submod/mod.rs | ||
668 | // in mod.rs | ||
669 | //- /submod/foo.rs | ||
670 | $0 | ||
671 | "#, | ||
672 | r#" | ||
673 | // in mod.rs | ||
674 | mod foo; | ||
675 | "#, | ||
676 | ); | ||
677 | } | ||
678 | |||
679 | #[test] | ||
680 | fn unlinked_file_new_style_mod() { | ||
681 | check_fix( | ||
682 | r#" | ||
683 | //- /main.rs | ||
684 | mod submod; | ||
685 | //- /submod.rs | ||
686 | //- /submod/foo.rs | ||
687 | $0 | ||
688 | "#, | ||
689 | r#" | ||
690 | mod foo; | ||
691 | "#, | ||
692 | ); | ||
693 | } | ||
694 | |||
695 | #[test] | ||
696 | fn unlinked_file_with_cfg_off() { | ||
697 | cov_mark::check!(unlinked_file_skip_fix_when_mod_already_exists); | ||
698 | check_no_fix( | ||
699 | r#" | ||
700 | //- /main.rs | ||
701 | #[cfg(never)] | ||
702 | mod foo; | ||
703 | |||
704 | //- /foo.rs | ||
705 | $0 | ||
706 | "#, | ||
707 | ); | ||
708 | } | ||
709 | |||
710 | #[test] | ||
711 | fn unlinked_file_with_cfg_on() { | ||
712 | check_no_diagnostics( | ||
713 | r#" | ||
714 | //- /main.rs | ||
715 | #[cfg(not(never))] | ||
716 | mod foo; | ||
717 | |||
718 | //- /foo.rs | ||
719 | "#, | ||
720 | ); | ||
721 | } | ||
722 | } | ||
diff --git a/crates/ide/src/diagnostics/field_shorthand.rs b/crates/ide/src/diagnostics/field_shorthand.rs deleted file mode 100644 index 01bd2dba6..000000000 --- a/crates/ide/src/diagnostics/field_shorthand.rs +++ /dev/null | |||
@@ -1,200 +0,0 @@ | |||
1 | //! Suggests shortening `Foo { field: field }` to `Foo { field }` in both | ||
2 | //! expressions and patterns. | ||
3 | |||
4 | use ide_db::{base_db::FileId, source_change::SourceChange}; | ||
5 | use syntax::{ast, match_ast, AstNode, SyntaxNode}; | ||
6 | use text_edit::TextEdit; | ||
7 | |||
8 | use crate::{diagnostics::fix, Diagnostic}; | ||
9 | |||
10 | pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) { | ||
11 | match_ast! { | ||
12 | match node { | ||
13 | ast::RecordExpr(it) => check_expr_field_shorthand(acc, file_id, it), | ||
14 | ast::RecordPat(it) => check_pat_field_shorthand(acc, file_id, it), | ||
15 | _ => () | ||
16 | } | ||
17 | }; | ||
18 | } | ||
19 | |||
20 | fn check_expr_field_shorthand( | ||
21 | acc: &mut Vec<Diagnostic>, | ||
22 | file_id: FileId, | ||
23 | record_expr: ast::RecordExpr, | ||
24 | ) { | ||
25 | let record_field_list = match record_expr.record_expr_field_list() { | ||
26 | Some(it) => it, | ||
27 | None => return, | ||
28 | }; | ||
29 | for record_field in record_field_list.fields() { | ||
30 | let (name_ref, expr) = match record_field.name_ref().zip(record_field.expr()) { | ||
31 | Some(it) => it, | ||
32 | None => continue, | ||
33 | }; | ||
34 | |||
35 | let field_name = name_ref.syntax().text().to_string(); | ||
36 | let field_expr = expr.syntax().text().to_string(); | ||
37 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
38 | if field_name != field_expr || field_name_is_tup_index { | ||
39 | continue; | ||
40 | } | ||
41 | |||
42 | let mut edit_builder = TextEdit::builder(); | ||
43 | edit_builder.delete(record_field.syntax().text_range()); | ||
44 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); | ||
45 | let edit = edit_builder.finish(); | ||
46 | |||
47 | let field_range = record_field.syntax().text_range(); | ||
48 | acc.push( | ||
49 | Diagnostic::hint(field_range, "Shorthand struct initialization".to_string()) | ||
50 | .with_fixes(Some(vec![fix( | ||
51 | "use_expr_field_shorthand", | ||
52 | "Use struct shorthand initialization", | ||
53 | SourceChange::from_text_edit(file_id, edit), | ||
54 | field_range, | ||
55 | )])), | ||
56 | ); | ||
57 | } | ||
58 | } | ||
59 | |||
60 | fn check_pat_field_shorthand( | ||
61 | acc: &mut Vec<Diagnostic>, | ||
62 | file_id: FileId, | ||
63 | record_pat: ast::RecordPat, | ||
64 | ) { | ||
65 | let record_pat_field_list = match record_pat.record_pat_field_list() { | ||
66 | Some(it) => it, | ||
67 | None => return, | ||
68 | }; | ||
69 | for record_pat_field in record_pat_field_list.fields() { | ||
70 | let (name_ref, pat) = match record_pat_field.name_ref().zip(record_pat_field.pat()) { | ||
71 | Some(it) => it, | ||
72 | None => continue, | ||
73 | }; | ||
74 | |||
75 | let field_name = name_ref.syntax().text().to_string(); | ||
76 | let field_pat = pat.syntax().text().to_string(); | ||
77 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
78 | if field_name != field_pat || field_name_is_tup_index { | ||
79 | continue; | ||
80 | } | ||
81 | |||
82 | let mut edit_builder = TextEdit::builder(); | ||
83 | edit_builder.delete(record_pat_field.syntax().text_range()); | ||
84 | edit_builder.insert(record_pat_field.syntax().text_range().start(), field_name); | ||
85 | let edit = edit_builder.finish(); | ||
86 | |||
87 | let field_range = record_pat_field.syntax().text_range(); | ||
88 | acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fixes( | ||
89 | Some(vec![fix( | ||
90 | "use_pat_field_shorthand", | ||
91 | "Use struct field shorthand", | ||
92 | SourceChange::from_text_edit(file_id, edit), | ||
93 | field_range, | ||
94 | )]), | ||
95 | )); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | #[cfg(test)] | ||
100 | mod tests { | ||
101 | use crate::diagnostics::tests::{check_fix, check_no_diagnostics}; | ||
102 | |||
103 | #[test] | ||
104 | fn test_check_expr_field_shorthand() { | ||
105 | check_no_diagnostics( | ||
106 | r#" | ||
107 | struct A { a: &'static str } | ||
108 | fn main() { A { a: "hello" } } | ||
109 | "#, | ||
110 | ); | ||
111 | check_no_diagnostics( | ||
112 | r#" | ||
113 | struct A(usize); | ||
114 | fn main() { A { 0: 0 } } | ||
115 | "#, | ||
116 | ); | ||
117 | |||
118 | check_fix( | ||
119 | r#" | ||
120 | struct A { a: &'static str } | ||
121 | fn main() { | ||
122 | let a = "haha"; | ||
123 | A { a$0: a } | ||
124 | } | ||
125 | "#, | ||
126 | r#" | ||
127 | struct A { a: &'static str } | ||
128 | fn main() { | ||
129 | let a = "haha"; | ||
130 | A { a } | ||
131 | } | ||
132 | "#, | ||
133 | ); | ||
134 | |||
135 | check_fix( | ||
136 | r#" | ||
137 | struct A { a: &'static str, b: &'static str } | ||
138 | fn main() { | ||
139 | let a = "haha"; | ||
140 | let b = "bb"; | ||
141 | A { a$0: a, b } | ||
142 | } | ||
143 | "#, | ||
144 | r#" | ||
145 | struct A { a: &'static str, b: &'static str } | ||
146 | fn main() { | ||
147 | let a = "haha"; | ||
148 | let b = "bb"; | ||
149 | A { a, b } | ||
150 | } | ||
151 | "#, | ||
152 | ); | ||
153 | } | ||
154 | |||
155 | #[test] | ||
156 | fn test_check_pat_field_shorthand() { | ||
157 | check_no_diagnostics( | ||
158 | r#" | ||
159 | struct A { a: &'static str } | ||
160 | fn f(a: A) { let A { a: hello } = a; } | ||
161 | "#, | ||
162 | ); | ||
163 | check_no_diagnostics( | ||
164 | r#" | ||
165 | struct A(usize); | ||
166 | fn f(a: A) { let A { 0: 0 } = a; } | ||
167 | "#, | ||
168 | ); | ||
169 | |||
170 | check_fix( | ||
171 | r#" | ||
172 | struct A { a: &'static str } | ||
173 | fn f(a: A) { | ||
174 | let A { a$0: a } = a; | ||
175 | } | ||
176 | "#, | ||
177 | r#" | ||
178 | struct A { a: &'static str } | ||
179 | fn f(a: A) { | ||
180 | let A { a } = a; | ||
181 | } | ||
182 | "#, | ||
183 | ); | ||
184 | |||
185 | check_fix( | ||
186 | r#" | ||
187 | struct A { a: &'static str, b: &'static str } | ||
188 | fn f(a: A) { | ||
189 | let A { a$0: a, b } = a; | ||
190 | } | ||
191 | "#, | ||
192 | r#" | ||
193 | struct A { a: &'static str, b: &'static str } | ||
194 | fn f(a: A) { | ||
195 | let A { a, b } = a; | ||
196 | } | ||
197 | "#, | ||
198 | ); | ||
199 | } | ||
200 | } | ||
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs deleted file mode 100644 index 258ac6974..000000000 --- a/crates/ide/src/diagnostics/fixes.rs +++ /dev/null | |||
@@ -1,31 +0,0 @@ | |||
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 | mod change_case; | ||
4 | mod create_field; | ||
5 | mod fill_missing_fields; | ||
6 | mod remove_semicolon; | ||
7 | mod replace_with_find_map; | ||
8 | mod unresolved_module; | ||
9 | mod wrap_tail_expr; | ||
10 | |||
11 | use hir::{diagnostics::Diagnostic, Semantics}; | ||
12 | use ide_assists::AssistResolveStrategy; | ||
13 | use ide_db::RootDatabase; | ||
14 | |||
15 | use crate::Assist; | ||
16 | |||
17 | /// A [Diagnostic] that potentially has some fixes available. | ||
18 | /// | ||
19 | /// [Diagnostic]: hir::diagnostics::Diagnostic | ||
20 | pub(crate) trait DiagnosticWithFixes: Diagnostic { | ||
21 | /// `resolve` determines if the diagnostic should fill in the `edit` field | ||
22 | /// of the assist. | ||
23 | /// | ||
24 | /// If `resolve` is false, the edit will be computed later, on demand, and | ||
25 | /// can be omitted. | ||
26 | fn fixes( | ||
27 | &self, | ||
28 | sema: &Semantics<RootDatabase>, | ||
29 | _resolve: &AssistResolveStrategy, | ||
30 | ) -> Option<Vec<Assist>>; | ||
31 | } | ||
diff --git a/crates/ide/src/diagnostics/fixes/change_case.rs b/crates/ide/src/diagnostics/fixes/change_case.rs deleted file mode 100644 index 42be3375f..000000000 --- a/crates/ide/src/diagnostics/fixes/change_case.rs +++ /dev/null | |||
@@ -1,155 +0,0 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::IncorrectCase, InFile, Semantics}; | ||
2 | use ide_assists::{Assist, AssistResolveStrategy}; | ||
3 | use ide_db::{base_db::FilePosition, RootDatabase}; | ||
4 | use syntax::AstNode; | ||
5 | |||
6 | use crate::{ | ||
7 | diagnostics::{unresolved_fix, DiagnosticWithFixes}, | ||
8 | references::rename::rename_with_semantics, | ||
9 | }; | ||
10 | |||
11 | impl DiagnosticWithFixes for IncorrectCase { | ||
12 | fn fixes( | ||
13 | &self, | ||
14 | sema: &Semantics<RootDatabase>, | ||
15 | resolve: &AssistResolveStrategy, | ||
16 | ) -> Option<Vec<Assist>> { | ||
17 | let root = sema.db.parse_or_expand(self.file)?; | ||
18 | let name_node = self.ident.to_node(&root); | ||
19 | |||
20 | let name_node = InFile::new(self.file, name_node.syntax()); | ||
21 | let frange = name_node.original_file_range(sema.db); | ||
22 | let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; | ||
23 | |||
24 | let label = format!("Rename to {}", self.suggested_text); | ||
25 | let mut res = unresolved_fix("change_case", &label, frange.range); | ||
26 | if resolve.should_resolve(&res.id) { | ||
27 | let source_change = rename_with_semantics(sema, file_position, &self.suggested_text); | ||
28 | res.source_change = Some(source_change.ok().unwrap_or_default()); | ||
29 | } | ||
30 | |||
31 | Some(vec![res]) | ||
32 | } | ||
33 | } | ||
34 | |||
35 | #[cfg(test)] | ||
36 | mod change_case { | ||
37 | use crate::{ | ||
38 | diagnostics::tests::{check_fix, check_no_diagnostics}, | ||
39 | fixture, AssistResolveStrategy, DiagnosticsConfig, | ||
40 | }; | ||
41 | |||
42 | #[test] | ||
43 | fn test_rename_incorrect_case() { | ||
44 | check_fix( | ||
45 | r#" | ||
46 | pub struct test_struct$0 { one: i32 } | ||
47 | |||
48 | pub fn some_fn(val: test_struct) -> test_struct { | ||
49 | test_struct { one: val.one + 1 } | ||
50 | } | ||
51 | "#, | ||
52 | r#" | ||
53 | pub struct TestStruct { one: i32 } | ||
54 | |||
55 | pub fn some_fn(val: TestStruct) -> TestStruct { | ||
56 | TestStruct { one: val.one + 1 } | ||
57 | } | ||
58 | "#, | ||
59 | ); | ||
60 | |||
61 | check_fix( | ||
62 | r#" | ||
63 | pub fn some_fn(NonSnakeCase$0: u8) -> u8 { | ||
64 | NonSnakeCase | ||
65 | } | ||
66 | "#, | ||
67 | r#" | ||
68 | pub fn some_fn(non_snake_case: u8) -> u8 { | ||
69 | non_snake_case | ||
70 | } | ||
71 | "#, | ||
72 | ); | ||
73 | |||
74 | check_fix( | ||
75 | r#" | ||
76 | pub fn SomeFn$0(val: u8) -> u8 { | ||
77 | if val != 0 { SomeFn(val - 1) } else { val } | ||
78 | } | ||
79 | "#, | ||
80 | r#" | ||
81 | pub fn some_fn(val: u8) -> u8 { | ||
82 | if val != 0 { some_fn(val - 1) } else { val } | ||
83 | } | ||
84 | "#, | ||
85 | ); | ||
86 | |||
87 | check_fix( | ||
88 | r#" | ||
89 | fn some_fn() { | ||
90 | let whatAWeird_Formatting$0 = 10; | ||
91 | another_func(whatAWeird_Formatting); | ||
92 | } | ||
93 | "#, | ||
94 | r#" | ||
95 | fn some_fn() { | ||
96 | let what_a_weird_formatting = 10; | ||
97 | another_func(what_a_weird_formatting); | ||
98 | } | ||
99 | "#, | ||
100 | ); | ||
101 | } | ||
102 | |||
103 | #[test] | ||
104 | fn test_uppercase_const_no_diagnostics() { | ||
105 | check_no_diagnostics( | ||
106 | r#" | ||
107 | fn foo() { | ||
108 | const ANOTHER_ITEM$0: &str = "some_item"; | ||
109 | } | ||
110 | "#, | ||
111 | ); | ||
112 | } | ||
113 | |||
114 | #[test] | ||
115 | fn test_rename_incorrect_case_struct_method() { | ||
116 | check_fix( | ||
117 | r#" | ||
118 | pub struct TestStruct; | ||
119 | |||
120 | impl TestStruct { | ||
121 | pub fn SomeFn$0() -> TestStruct { | ||
122 | TestStruct | ||
123 | } | ||
124 | } | ||
125 | "#, | ||
126 | r#" | ||
127 | pub struct TestStruct; | ||
128 | |||
129 | impl TestStruct { | ||
130 | pub fn some_fn() -> TestStruct { | ||
131 | TestStruct | ||
132 | } | ||
133 | } | ||
134 | "#, | ||
135 | ); | ||
136 | } | ||
137 | |||
138 | #[test] | ||
139 | fn test_single_incorrect_case_diagnostic_in_function_name_issue_6970() { | ||
140 | let input = r#"fn FOO$0() {}"#; | ||
141 | let expected = r#"fn foo() {}"#; | ||
142 | |||
143 | let (analysis, file_position) = fixture::position(input); | ||
144 | let diagnostics = analysis | ||
145 | .diagnostics( | ||
146 | &DiagnosticsConfig::default(), | ||
147 | AssistResolveStrategy::All, | ||
148 | file_position.file_id, | ||
149 | ) | ||
150 | .unwrap(); | ||
151 | assert_eq!(diagnostics.len(), 1); | ||
152 | |||
153 | check_fix(input, expected); | ||
154 | } | ||
155 | } | ||
diff --git a/crates/ide/src/diagnostics/fixes/create_field.rs b/crates/ide/src/diagnostics/fixes/create_field.rs deleted file mode 100644 index a5f457dce..000000000 --- a/crates/ide/src/diagnostics/fixes/create_field.rs +++ /dev/null | |||
@@ -1,156 +0,0 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::NoSuchField, HasSource, HirDisplay, Semantics}; | ||
2 | use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase}; | ||
3 | use syntax::{ | ||
4 | ast::{self, edit::IndentLevel, make}, | ||
5 | AstNode, | ||
6 | }; | ||
7 | use text_edit::TextEdit; | ||
8 | |||
9 | use crate::{ | ||
10 | diagnostics::{fix, DiagnosticWithFixes}, | ||
11 | Assist, AssistResolveStrategy, | ||
12 | }; | ||
13 | impl DiagnosticWithFixes for NoSuchField { | ||
14 | fn fixes( | ||
15 | &self, | ||
16 | sema: &Semantics<RootDatabase>, | ||
17 | _resolve: &AssistResolveStrategy, | ||
18 | ) -> Option<Vec<Assist>> { | ||
19 | let root = sema.db.parse_or_expand(self.file)?; | ||
20 | missing_record_expr_field_fixes( | ||
21 | &sema, | ||
22 | self.file.original_file(sema.db), | ||
23 | &self.field.to_node(&root), | ||
24 | ) | ||
25 | } | ||
26 | } | ||
27 | |||
28 | fn missing_record_expr_field_fixes( | ||
29 | sema: &Semantics<RootDatabase>, | ||
30 | usage_file_id: FileId, | ||
31 | record_expr_field: &ast::RecordExprField, | ||
32 | ) -> Option<Vec<Assist>> { | ||
33 | let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?; | ||
34 | let def_id = sema.resolve_variant(record_lit)?; | ||
35 | let module; | ||
36 | let def_file_id; | ||
37 | let record_fields = match def_id { | ||
38 | hir::VariantDef::Struct(s) => { | ||
39 | module = s.module(sema.db); | ||
40 | let source = s.source(sema.db)?; | ||
41 | def_file_id = source.file_id; | ||
42 | let fields = source.value.field_list()?; | ||
43 | record_field_list(fields)? | ||
44 | } | ||
45 | hir::VariantDef::Union(u) => { | ||
46 | module = u.module(sema.db); | ||
47 | let source = u.source(sema.db)?; | ||
48 | def_file_id = source.file_id; | ||
49 | source.value.record_field_list()? | ||
50 | } | ||
51 | hir::VariantDef::Variant(e) => { | ||
52 | module = e.module(sema.db); | ||
53 | let source = e.source(sema.db)?; | ||
54 | def_file_id = source.file_id; | ||
55 | let fields = source.value.field_list()?; | ||
56 | record_field_list(fields)? | ||
57 | } | ||
58 | }; | ||
59 | let def_file_id = def_file_id.original_file(sema.db); | ||
60 | |||
61 | let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?; | ||
62 | if new_field_type.is_unknown() { | ||
63 | return None; | ||
64 | } | ||
65 | let new_field = make::record_field( | ||
66 | None, | ||
67 | make::name(&record_expr_field.field_name()?.text()), | ||
68 | make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?), | ||
69 | ); | ||
70 | |||
71 | let last_field = record_fields.fields().last()?; | ||
72 | let last_field_syntax = last_field.syntax(); | ||
73 | let indent = IndentLevel::from_node(last_field_syntax); | ||
74 | |||
75 | let mut new_field = new_field.to_string(); | ||
76 | if usage_file_id != def_file_id { | ||
77 | new_field = format!("pub(crate) {}", new_field); | ||
78 | } | ||
79 | new_field = format!("\n{}{}", indent, new_field); | ||
80 | |||
81 | let needs_comma = !last_field_syntax.to_string().ends_with(','); | ||
82 | if needs_comma { | ||
83 | new_field = format!(",{}", new_field); | ||
84 | } | ||
85 | |||
86 | let source_change = SourceChange::from_text_edit( | ||
87 | def_file_id, | ||
88 | TextEdit::insert(last_field_syntax.text_range().end(), new_field), | ||
89 | ); | ||
90 | |||
91 | return Some(vec![fix( | ||
92 | "create_field", | ||
93 | "Create field", | ||
94 | source_change, | ||
95 | record_expr_field.syntax().text_range(), | ||
96 | )]); | ||
97 | |||
98 | fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { | ||
99 | match field_def_list { | ||
100 | ast::FieldList::RecordFieldList(it) => Some(it), | ||
101 | ast::FieldList::TupleFieldList(_) => None, | ||
102 | } | ||
103 | } | ||
104 | } | ||
105 | |||
106 | #[cfg(test)] | ||
107 | mod tests { | ||
108 | use crate::diagnostics::tests::check_fix; | ||
109 | |||
110 | #[test] | ||
111 | fn test_add_field_from_usage() { | ||
112 | check_fix( | ||
113 | r" | ||
114 | fn main() { | ||
115 | Foo { bar: 3, baz$0: false}; | ||
116 | } | ||
117 | struct Foo { | ||
118 | bar: i32 | ||
119 | } | ||
120 | ", | ||
121 | r" | ||
122 | fn main() { | ||
123 | Foo { bar: 3, baz: false}; | ||
124 | } | ||
125 | struct Foo { | ||
126 | bar: i32, | ||
127 | baz: bool | ||
128 | } | ||
129 | ", | ||
130 | ) | ||
131 | } | ||
132 | |||
133 | #[test] | ||
134 | fn test_add_field_in_other_file_from_usage() { | ||
135 | check_fix( | ||
136 | r#" | ||
137 | //- /main.rs | ||
138 | mod foo; | ||
139 | |||
140 | fn main() { | ||
141 | foo::Foo { bar: 3, $0baz: false}; | ||
142 | } | ||
143 | //- /foo.rs | ||
144 | struct Foo { | ||
145 | bar: i32 | ||
146 | } | ||
147 | "#, | ||
148 | r#" | ||
149 | struct Foo { | ||
150 | bar: i32, | ||
151 | pub(crate) baz: bool | ||
152 | } | ||
153 | "#, | ||
154 | ) | ||
155 | } | ||
156 | } | ||
diff --git a/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs b/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs deleted file mode 100644 index b5dd64c08..000000000 --- a/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs +++ /dev/null | |||
@@ -1,217 +0,0 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::MissingFields, Semantics}; | ||
2 | use ide_assists::AssistResolveStrategy; | ||
3 | use ide_db::{source_change::SourceChange, RootDatabase}; | ||
4 | use syntax::{algo, ast::make, AstNode}; | ||
5 | use text_edit::TextEdit; | ||
6 | |||
7 | use crate::{ | ||
8 | diagnostics::{fix, fixes::DiagnosticWithFixes}, | ||
9 | Assist, | ||
10 | }; | ||
11 | |||
12 | impl DiagnosticWithFixes for MissingFields { | ||
13 | fn fixes( | ||
14 | &self, | ||
15 | sema: &Semantics<RootDatabase>, | ||
16 | _resolve: &AssistResolveStrategy, | ||
17 | ) -> Option<Vec<Assist>> { | ||
18 | // Note that although we could add a diagnostics to | ||
19 | // fill the missing tuple field, e.g : | ||
20 | // `struct A(usize);` | ||
21 | // `let a = A { 0: () }` | ||
22 | // but it is uncommon usage and it should not be encouraged. | ||
23 | if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { | ||
24 | return None; | ||
25 | } | ||
26 | |||
27 | let root = sema.db.parse_or_expand(self.file)?; | ||
28 | let field_list_parent = self.field_list_parent.to_node(&root); | ||
29 | let old_field_list = field_list_parent.record_expr_field_list()?; | ||
30 | let new_field_list = old_field_list.clone_for_update(); | ||
31 | for f in self.missed_fields.iter() { | ||
32 | let field = | ||
33 | make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit())) | ||
34 | .clone_for_update(); | ||
35 | new_field_list.add_field(field); | ||
36 | } | ||
37 | |||
38 | let edit = { | ||
39 | let mut builder = TextEdit::builder(); | ||
40 | algo::diff(&old_field_list.syntax(), &new_field_list.syntax()) | ||
41 | .into_text_edit(&mut builder); | ||
42 | builder.finish() | ||
43 | }; | ||
44 | Some(vec![fix( | ||
45 | "fill_missing_fields", | ||
46 | "Fill struct fields", | ||
47 | SourceChange::from_text_edit(self.file.original_file(sema.db), edit), | ||
48 | sema.original_range(&field_list_parent.syntax()).range, | ||
49 | )]) | ||
50 | } | ||
51 | } | ||
52 | |||
53 | #[cfg(test)] | ||
54 | mod tests { | ||
55 | use crate::diagnostics::tests::{check_fix, check_no_diagnostics}; | ||
56 | |||
57 | #[test] | ||
58 | fn test_fill_struct_fields_empty() { | ||
59 | check_fix( | ||
60 | r#" | ||
61 | struct TestStruct { one: i32, two: i64 } | ||
62 | |||
63 | fn test_fn() { | ||
64 | let s = TestStruct {$0}; | ||
65 | } | ||
66 | "#, | ||
67 | r#" | ||
68 | struct TestStruct { one: i32, two: i64 } | ||
69 | |||
70 | fn test_fn() { | ||
71 | let s = TestStruct { one: (), two: () }; | ||
72 | } | ||
73 | "#, | ||
74 | ); | ||
75 | } | ||
76 | |||
77 | #[test] | ||
78 | fn test_fill_struct_fields_self() { | ||
79 | check_fix( | ||
80 | r#" | ||
81 | struct TestStruct { one: i32 } | ||
82 | |||
83 | impl TestStruct { | ||
84 | fn test_fn() { let s = Self {$0}; } | ||
85 | } | ||
86 | "#, | ||
87 | r#" | ||
88 | struct TestStruct { one: i32 } | ||
89 | |||
90 | impl TestStruct { | ||
91 | fn test_fn() { let s = Self { one: () }; } | ||
92 | } | ||
93 | "#, | ||
94 | ); | ||
95 | } | ||
96 | |||
97 | #[test] | ||
98 | fn test_fill_struct_fields_enum() { | ||
99 | check_fix( | ||
100 | r#" | ||
101 | enum Expr { | ||
102 | Bin { lhs: Box<Expr>, rhs: Box<Expr> } | ||
103 | } | ||
104 | |||
105 | impl Expr { | ||
106 | fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr { | ||
107 | Expr::Bin {$0 } | ||
108 | } | ||
109 | } | ||
110 | "#, | ||
111 | r#" | ||
112 | enum Expr { | ||
113 | Bin { lhs: Box<Expr>, rhs: Box<Expr> } | ||
114 | } | ||
115 | |||
116 | impl Expr { | ||
117 | fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr { | ||
118 | Expr::Bin { lhs: (), rhs: () } | ||
119 | } | ||
120 | } | ||
121 | "#, | ||
122 | ); | ||
123 | } | ||
124 | |||
125 | #[test] | ||
126 | fn test_fill_struct_fields_partial() { | ||
127 | check_fix( | ||
128 | r#" | ||
129 | struct TestStruct { one: i32, two: i64 } | ||
130 | |||
131 | fn test_fn() { | ||
132 | let s = TestStruct{ two: 2$0 }; | ||
133 | } | ||
134 | "#, | ||
135 | r" | ||
136 | struct TestStruct { one: i32, two: i64 } | ||
137 | |||
138 | fn test_fn() { | ||
139 | let s = TestStruct{ two: 2, one: () }; | ||
140 | } | ||
141 | ", | ||
142 | ); | ||
143 | } | ||
144 | |||
145 | #[test] | ||
146 | fn test_fill_struct_fields_raw_ident() { | ||
147 | check_fix( | ||
148 | r#" | ||
149 | struct TestStruct { r#type: u8 } | ||
150 | |||
151 | fn test_fn() { | ||
152 | TestStruct { $0 }; | ||
153 | } | ||
154 | "#, | ||
155 | r" | ||
156 | struct TestStruct { r#type: u8 } | ||
157 | |||
158 | fn test_fn() { | ||
159 | TestStruct { r#type: () }; | ||
160 | } | ||
161 | ", | ||
162 | ); | ||
163 | } | ||
164 | |||
165 | #[test] | ||
166 | fn test_fill_struct_fields_no_diagnostic() { | ||
167 | check_no_diagnostics( | ||
168 | r#" | ||
169 | struct TestStruct { one: i32, two: i64 } | ||
170 | |||
171 | fn test_fn() { | ||
172 | let one = 1; | ||
173 | let s = TestStruct{ one, two: 2 }; | ||
174 | } | ||
175 | "#, | ||
176 | ); | ||
177 | } | ||
178 | |||
179 | #[test] | ||
180 | fn test_fill_struct_fields_no_diagnostic_on_spread() { | ||
181 | check_no_diagnostics( | ||
182 | r#" | ||
183 | struct TestStruct { one: i32, two: i64 } | ||
184 | |||
185 | fn test_fn() { | ||
186 | let one = 1; | ||
187 | let s = TestStruct{ ..a }; | ||
188 | } | ||
189 | "#, | ||
190 | ); | ||
191 | } | ||
192 | |||
193 | #[test] | ||
194 | fn test_fill_struct_fields_blank_line() { | ||
195 | check_fix( | ||
196 | r#" | ||
197 | struct S { a: (), b: () } | ||
198 | |||
199 | fn f() { | ||
200 | S { | ||
201 | $0 | ||
202 | }; | ||
203 | } | ||
204 | "#, | ||
205 | r#" | ||
206 | struct S { a: (), b: () } | ||
207 | |||
208 | fn f() { | ||
209 | S { | ||
210 | a: (), | ||
211 | b: (), | ||
212 | }; | ||
213 | } | ||
214 | "#, | ||
215 | ); | ||
216 | } | ||
217 | } | ||
diff --git a/crates/ide/src/diagnostics/fixes/remove_semicolon.rs b/crates/ide/src/diagnostics/fixes/remove_semicolon.rs deleted file mode 100644 index f1724d479..000000000 --- a/crates/ide/src/diagnostics/fixes/remove_semicolon.rs +++ /dev/null | |||
@@ -1,41 +0,0 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::RemoveThisSemicolon, Semantics}; | ||
2 | use ide_assists::{Assist, AssistResolveStrategy}; | ||
3 | use ide_db::{source_change::SourceChange, RootDatabase}; | ||
4 | use syntax::{ast, AstNode}; | ||
5 | use text_edit::TextEdit; | ||
6 | |||
7 | use crate::diagnostics::{fix, DiagnosticWithFixes}; | ||
8 | |||
9 | impl DiagnosticWithFixes for RemoveThisSemicolon { | ||
10 | fn fixes( | ||
11 | &self, | ||
12 | sema: &Semantics<RootDatabase>, | ||
13 | _resolve: &AssistResolveStrategy, | ||
14 | ) -> Option<Vec<Assist>> { | ||
15 | let root = sema.db.parse_or_expand(self.file)?; | ||
16 | |||
17 | let semicolon = self | ||
18 | .expr | ||
19 | .to_node(&root) | ||
20 | .syntax() | ||
21 | .parent() | ||
22 | .and_then(ast::ExprStmt::cast) | ||
23 | .and_then(|expr| expr.semicolon_token())? | ||
24 | .text_range(); | ||
25 | |||
26 | let edit = TextEdit::delete(semicolon); | ||
27 | let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); | ||
28 | |||
29 | Some(vec![fix("remove_semicolon", "Remove this semicolon", source_change, semicolon)]) | ||
30 | } | ||
31 | } | ||
32 | |||
33 | #[cfg(test)] | ||
34 | mod tests { | ||
35 | use crate::diagnostics::tests::check_fix; | ||
36 | |||
37 | #[test] | ||
38 | fn remove_semicolon() { | ||
39 | check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#); | ||
40 | } | ||
41 | } | ||
diff --git a/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs b/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs deleted file mode 100644 index 444bf563b..000000000 --- a/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs +++ /dev/null | |||
@@ -1,84 +0,0 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::ReplaceFilterMapNextWithFindMap, Semantics}; | ||
2 | use ide_assists::{Assist, AssistResolveStrategy}; | ||
3 | use ide_db::{source_change::SourceChange, RootDatabase}; | ||
4 | use syntax::{ | ||
5 | ast::{self, ArgListOwner}, | ||
6 | AstNode, TextRange, | ||
7 | }; | ||
8 | use text_edit::TextEdit; | ||
9 | |||
10 | use crate::diagnostics::{fix, DiagnosticWithFixes}; | ||
11 | |||
12 | impl DiagnosticWithFixes for ReplaceFilterMapNextWithFindMap { | ||
13 | fn fixes( | ||
14 | &self, | ||
15 | sema: &Semantics<RootDatabase>, | ||
16 | _resolve: &AssistResolveStrategy, | ||
17 | ) -> Option<Vec<Assist>> { | ||
18 | let root = sema.db.parse_or_expand(self.file)?; | ||
19 | let next_expr = self.next_expr.to_node(&root); | ||
20 | let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; | ||
21 | |||
22 | let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?; | ||
23 | let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range(); | ||
24 | let filter_map_args = filter_map_call.arg_list()?; | ||
25 | |||
26 | let range_to_replace = | ||
27 | TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end()); | ||
28 | let replacement = format!("find_map{}", filter_map_args.syntax().text()); | ||
29 | let trigger_range = next_expr.syntax().text_range(); | ||
30 | |||
31 | let edit = TextEdit::replace(range_to_replace, replacement); | ||
32 | |||
33 | let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); | ||
34 | |||
35 | Some(vec![fix( | ||
36 | "replace_with_find_map", | ||
37 | "Replace filter_map(..).next() with find_map()", | ||
38 | source_change, | ||
39 | trigger_range, | ||
40 | )]) | ||
41 | } | ||
42 | } | ||
43 | |||
44 | #[cfg(test)] | ||
45 | mod tests { | ||
46 | use crate::diagnostics::tests::check_fix; | ||
47 | |||
48 | #[test] | ||
49 | fn replace_with_wind_map() { | ||
50 | check_fix( | ||
51 | r#" | ||
52 | //- /main.rs crate:main deps:core | ||
53 | use core::iter::Iterator; | ||
54 | use core::option::Option::{self, Some, None}; | ||
55 | fn foo() { | ||
56 | let m = [1, 2, 3].iter().$0filter_map(|x| if *x == 2 { Some (4) } else { None }).next(); | ||
57 | } | ||
58 | //- /core/lib.rs crate:core | ||
59 | pub mod option { | ||
60 | pub enum Option<T> { Some(T), None } | ||
61 | } | ||
62 | pub mod iter { | ||
63 | pub trait Iterator { | ||
64 | type Item; | ||
65 | fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap } | ||
66 | fn next(&mut self) -> Option<Self::Item>; | ||
67 | } | ||
68 | pub struct FilterMap {} | ||
69 | impl Iterator for FilterMap { | ||
70 | type Item = i32; | ||
71 | fn next(&mut self) -> i32 { 7 } | ||
72 | } | ||
73 | } | ||
74 | "#, | ||
75 | r#" | ||
76 | use core::iter::Iterator; | ||
77 | use core::option::Option::{self, Some, None}; | ||
78 | fn foo() { | ||
79 | let m = [1, 2, 3].iter().find_map(|x| if *x == 2 { Some (4) } else { None }); | ||
80 | } | ||
81 | "#, | ||
82 | ) | ||
83 | } | ||
84 | } | ||
diff --git a/crates/ide/src/diagnostics/fixes/unresolved_module.rs b/crates/ide/src/diagnostics/fixes/unresolved_module.rs deleted file mode 100644 index b3d0283bb..000000000 --- a/crates/ide/src/diagnostics/fixes/unresolved_module.rs +++ /dev/null | |||
@@ -1,89 +0,0 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::UnresolvedModule, Semantics}; | ||
2 | use ide_assists::{Assist, AssistResolveStrategy}; | ||
3 | use ide_db::{base_db::AnchoredPathBuf, source_change::FileSystemEdit, RootDatabase}; | ||
4 | use syntax::AstNode; | ||
5 | |||
6 | use crate::diagnostics::{fix, DiagnosticWithFixes}; | ||
7 | |||
8 | impl DiagnosticWithFixes for UnresolvedModule { | ||
9 | fn fixes( | ||
10 | &self, | ||
11 | sema: &Semantics<RootDatabase>, | ||
12 | _resolve: &AssistResolveStrategy, | ||
13 | ) -> Option<Vec<Assist>> { | ||
14 | let root = sema.db.parse_or_expand(self.file)?; | ||
15 | let unresolved_module = self.decl.to_node(&root); | ||
16 | Some(vec![fix( | ||
17 | "create_module", | ||
18 | "Create module", | ||
19 | FileSystemEdit::CreateFile { | ||
20 | dst: AnchoredPathBuf { | ||
21 | anchor: self.file.original_file(sema.db), | ||
22 | path: self.candidate.clone(), | ||
23 | }, | ||
24 | initial_contents: "".to_string(), | ||
25 | } | ||
26 | .into(), | ||
27 | unresolved_module.syntax().text_range(), | ||
28 | )]) | ||
29 | } | ||
30 | } | ||
31 | |||
32 | #[cfg(test)] | ||
33 | mod tests { | ||
34 | use expect_test::expect; | ||
35 | |||
36 | use crate::diagnostics::tests::check_expect; | ||
37 | |||
38 | #[test] | ||
39 | fn test_unresolved_module_diagnostic() { | ||
40 | check_expect( | ||
41 | r#"mod foo;"#, | ||
42 | expect![[r#" | ||
43 | [ | ||
44 | Diagnostic { | ||
45 | message: "unresolved module", | ||
46 | range: 0..8, | ||
47 | severity: Error, | ||
48 | fixes: Some( | ||
49 | [ | ||
50 | Assist { | ||
51 | id: AssistId( | ||
52 | "create_module", | ||
53 | QuickFix, | ||
54 | ), | ||
55 | label: "Create module", | ||
56 | group: None, | ||
57 | target: 0..8, | ||
58 | source_change: Some( | ||
59 | SourceChange { | ||
60 | source_file_edits: {}, | ||
61 | file_system_edits: [ | ||
62 | CreateFile { | ||
63 | dst: AnchoredPathBuf { | ||
64 | anchor: FileId( | ||
65 | 0, | ||
66 | ), | ||
67 | path: "foo.rs", | ||
68 | }, | ||
69 | initial_contents: "", | ||
70 | }, | ||
71 | ], | ||
72 | is_snippet: false, | ||
73 | }, | ||
74 | ), | ||
75 | }, | ||
76 | ], | ||
77 | ), | ||
78 | unused: false, | ||
79 | code: Some( | ||
80 | DiagnosticCode( | ||
81 | "unresolved-module", | ||
82 | ), | ||
83 | ), | ||
84 | }, | ||
85 | ] | ||
86 | "#]], | ||
87 | ); | ||
88 | } | ||
89 | } | ||
diff --git a/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs b/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs deleted file mode 100644 index 715a403b9..000000000 --- a/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs +++ /dev/null | |||
@@ -1,211 +0,0 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::MissingOkOrSomeInTailExpr, Semantics}; | ||
2 | use ide_assists::{Assist, AssistResolveStrategy}; | ||
3 | use ide_db::{source_change::SourceChange, RootDatabase}; | ||
4 | use syntax::AstNode; | ||
5 | use text_edit::TextEdit; | ||
6 | |||
7 | use crate::diagnostics::{fix, DiagnosticWithFixes}; | ||
8 | |||
9 | impl DiagnosticWithFixes for MissingOkOrSomeInTailExpr { | ||
10 | fn fixes( | ||
11 | &self, | ||
12 | sema: &Semantics<RootDatabase>, | ||
13 | _resolve: &AssistResolveStrategy, | ||
14 | ) -> Option<Vec<Assist>> { | ||
15 | let root = sema.db.parse_or_expand(self.file)?; | ||
16 | let tail_expr = self.expr.to_node(&root); | ||
17 | let tail_expr_range = tail_expr.syntax().text_range(); | ||
18 | let replacement = format!("{}({})", self.required, tail_expr.syntax()); | ||
19 | let edit = TextEdit::replace(tail_expr_range, replacement); | ||
20 | let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); | ||
21 | let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" }; | ||
22 | Some(vec![fix("wrap_tail_expr", name, source_change, tail_expr_range)]) | ||
23 | } | ||
24 | } | ||
25 | |||
26 | #[cfg(test)] | ||
27 | mod tests { | ||
28 | use crate::diagnostics::tests::{check_fix, check_no_diagnostics}; | ||
29 | |||
30 | #[test] | ||
31 | fn test_wrap_return_type_option() { | ||
32 | check_fix( | ||
33 | r#" | ||
34 | //- /main.rs crate:main deps:core | ||
35 | use core::option::Option::{self, Some, None}; | ||
36 | |||
37 | fn div(x: i32, y: i32) -> Option<i32> { | ||
38 | if y == 0 { | ||
39 | return None; | ||
40 | } | ||
41 | x / y$0 | ||
42 | } | ||
43 | //- /core/lib.rs crate:core | ||
44 | pub mod result { | ||
45 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
46 | } | ||
47 | pub mod option { | ||
48 | pub enum Option<T> { Some(T), None } | ||
49 | } | ||
50 | "#, | ||
51 | r#" | ||
52 | use core::option::Option::{self, Some, None}; | ||
53 | |||
54 | fn div(x: i32, y: i32) -> Option<i32> { | ||
55 | if y == 0 { | ||
56 | return None; | ||
57 | } | ||
58 | Some(x / y) | ||
59 | } | ||
60 | "#, | ||
61 | ); | ||
62 | } | ||
63 | |||
64 | #[test] | ||
65 | fn test_wrap_return_type() { | ||
66 | check_fix( | ||
67 | r#" | ||
68 | //- /main.rs crate:main deps:core | ||
69 | use core::result::Result::{self, Ok, Err}; | ||
70 | |||
71 | fn div(x: i32, y: i32) -> Result<i32, ()> { | ||
72 | if y == 0 { | ||
73 | return Err(()); | ||
74 | } | ||
75 | x / y$0 | ||
76 | } | ||
77 | //- /core/lib.rs crate:core | ||
78 | pub mod result { | ||
79 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
80 | } | ||
81 | pub mod option { | ||
82 | pub enum Option<T> { Some(T), None } | ||
83 | } | ||
84 | "#, | ||
85 | r#" | ||
86 | use core::result::Result::{self, Ok, Err}; | ||
87 | |||
88 | fn div(x: i32, y: i32) -> Result<i32, ()> { | ||
89 | if y == 0 { | ||
90 | return Err(()); | ||
91 | } | ||
92 | Ok(x / y) | ||
93 | } | ||
94 | "#, | ||
95 | ); | ||
96 | } | ||
97 | |||
98 | #[test] | ||
99 | fn test_wrap_return_type_handles_generic_functions() { | ||
100 | check_fix( | ||
101 | r#" | ||
102 | //- /main.rs crate:main deps:core | ||
103 | use core::result::Result::{self, Ok, Err}; | ||
104 | |||
105 | fn div<T>(x: T) -> Result<T, i32> { | ||
106 | if x == 0 { | ||
107 | return Err(7); | ||
108 | } | ||
109 | $0x | ||
110 | } | ||
111 | //- /core/lib.rs crate:core | ||
112 | pub mod result { | ||
113 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
114 | } | ||
115 | pub mod option { | ||
116 | pub enum Option<T> { Some(T), None } | ||
117 | } | ||
118 | "#, | ||
119 | r#" | ||
120 | use core::result::Result::{self, Ok, Err}; | ||
121 | |||
122 | fn div<T>(x: T) -> Result<T, i32> { | ||
123 | if x == 0 { | ||
124 | return Err(7); | ||
125 | } | ||
126 | Ok(x) | ||
127 | } | ||
128 | "#, | ||
129 | ); | ||
130 | } | ||
131 | |||
132 | #[test] | ||
133 | fn test_wrap_return_type_handles_type_aliases() { | ||
134 | check_fix( | ||
135 | r#" | ||
136 | //- /main.rs crate:main deps:core | ||
137 | use core::result::Result::{self, Ok, Err}; | ||
138 | |||
139 | type MyResult<T> = Result<T, ()>; | ||
140 | |||
141 | fn div(x: i32, y: i32) -> MyResult<i32> { | ||
142 | if y == 0 { | ||
143 | return Err(()); | ||
144 | } | ||
145 | x $0/ y | ||
146 | } | ||
147 | //- /core/lib.rs crate:core | ||
148 | pub mod result { | ||
149 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
150 | } | ||
151 | pub mod option { | ||
152 | pub enum Option<T> { Some(T), None } | ||
153 | } | ||
154 | "#, | ||
155 | r#" | ||
156 | use core::result::Result::{self, Ok, Err}; | ||
157 | |||
158 | type MyResult<T> = Result<T, ()>; | ||
159 | |||
160 | fn div(x: i32, y: i32) -> MyResult<i32> { | ||
161 | if y == 0 { | ||
162 | return Err(()); | ||
163 | } | ||
164 | Ok(x / y) | ||
165 | } | ||
166 | "#, | ||
167 | ); | ||
168 | } | ||
169 | |||
170 | #[test] | ||
171 | fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { | ||
172 | check_no_diagnostics( | ||
173 | r#" | ||
174 | //- /main.rs crate:main deps:core | ||
175 | use core::result::Result::{self, Ok, Err}; | ||
176 | |||
177 | fn foo() -> Result<(), i32> { 0 } | ||
178 | |||
179 | //- /core/lib.rs crate:core | ||
180 | pub mod result { | ||
181 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
182 | } | ||
183 | pub mod option { | ||
184 | pub enum Option<T> { Some(T), None } | ||
185 | } | ||
186 | "#, | ||
187 | ); | ||
188 | } | ||
189 | |||
190 | #[test] | ||
191 | fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() { | ||
192 | check_no_diagnostics( | ||
193 | r#" | ||
194 | //- /main.rs crate:main deps:core | ||
195 | use core::result::Result::{self, Ok, Err}; | ||
196 | |||
197 | enum SomeOtherEnum { Ok(i32), Err(String) } | ||
198 | |||
199 | fn foo() -> SomeOtherEnum { 0 } | ||
200 | |||
201 | //- /core/lib.rs crate:core | ||
202 | pub mod result { | ||
203 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
204 | } | ||
205 | pub mod option { | ||
206 | pub enum Option<T> { Some(T), None } | ||
207 | } | ||
208 | "#, | ||
209 | ); | ||
210 | } | ||
211 | } | ||
diff --git a/crates/ide/src/diagnostics/unlinked_file.rs b/crates/ide/src/diagnostics/unlinked_file.rs deleted file mode 100644 index 51fe0f360..000000000 --- a/crates/ide/src/diagnostics/unlinked_file.rs +++ /dev/null | |||
@@ -1,183 +0,0 @@ | |||
1 | //! Diagnostic emitted for files that aren't part of any crate. | ||
2 | |||
3 | use hir::{ | ||
4 | db::DefDatabase, | ||
5 | diagnostics::{Diagnostic, DiagnosticCode}, | ||
6 | InFile, | ||
7 | }; | ||
8 | use ide_assists::AssistResolveStrategy; | ||
9 | use ide_db::{ | ||
10 | base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt}, | ||
11 | source_change::SourceChange, | ||
12 | RootDatabase, | ||
13 | }; | ||
14 | use syntax::{ | ||
15 | ast::{self, ModuleItemOwner, NameOwner}, | ||
16 | AstNode, SyntaxNodePtr, | ||
17 | }; | ||
18 | use text_edit::TextEdit; | ||
19 | |||
20 | use crate::{ | ||
21 | diagnostics::{fix, fixes::DiagnosticWithFixes}, | ||
22 | Assist, | ||
23 | }; | ||
24 | |||
25 | // Diagnostic: unlinked-file | ||
26 | // | ||
27 | // This diagnostic is shown for files that are not included in any crate, or files that are part of | ||
28 | // crates rust-analyzer failed to discover. The file will not have IDE features available. | ||
29 | #[derive(Debug)] | ||
30 | pub(crate) struct UnlinkedFile { | ||
31 | pub(crate) file_id: FileId, | ||
32 | pub(crate) node: SyntaxNodePtr, | ||
33 | } | ||
34 | |||
35 | impl Diagnostic for UnlinkedFile { | ||
36 | fn code(&self) -> DiagnosticCode { | ||
37 | DiagnosticCode("unlinked-file") | ||
38 | } | ||
39 | |||
40 | fn message(&self) -> String { | ||
41 | "file not included in module tree".to_string() | ||
42 | } | ||
43 | |||
44 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
45 | InFile::new(self.file_id.into(), self.node.clone()) | ||
46 | } | ||
47 | |||
48 | fn as_any(&self) -> &(dyn std::any::Any + Send + 'static) { | ||
49 | self | ||
50 | } | ||
51 | } | ||
52 | |||
53 | impl DiagnosticWithFixes for UnlinkedFile { | ||
54 | fn fixes( | ||
55 | &self, | ||
56 | sema: &hir::Semantics<RootDatabase>, | ||
57 | _resolve: &AssistResolveStrategy, | ||
58 | ) -> Option<Vec<Assist>> { | ||
59 | // If there's an existing module that could add `mod` or `pub mod` items to include the unlinked file, | ||
60 | // suggest that as a fix. | ||
61 | |||
62 | let source_root = sema.db.source_root(sema.db.file_source_root(self.file_id)); | ||
63 | let our_path = source_root.path_for_file(&self.file_id)?; | ||
64 | let module_name = our_path.name_and_extension()?.0; | ||
65 | |||
66 | // Candidates to look for: | ||
67 | // - `mod.rs` in the same folder | ||
68 | // - we also check `main.rs` and `lib.rs` | ||
69 | // - `$dir.rs` in the parent folder, where `$dir` is the directory containing `self.file_id` | ||
70 | let parent = our_path.parent()?; | ||
71 | let mut paths = | ||
72 | vec![parent.join("mod.rs")?, parent.join("lib.rs")?, parent.join("main.rs")?]; | ||
73 | |||
74 | // `submod/bla.rs` -> `submod.rs` | ||
75 | if let Some(newmod) = (|| { | ||
76 | let name = parent.name_and_extension()?.0; | ||
77 | parent.parent()?.join(&format!("{}.rs", name)) | ||
78 | })() { | ||
79 | paths.push(newmod); | ||
80 | } | ||
81 | |||
82 | for path in &paths { | ||
83 | if let Some(parent_id) = source_root.file_for_path(path) { | ||
84 | for krate in sema.db.relevant_crates(*parent_id).iter() { | ||
85 | let crate_def_map = sema.db.crate_def_map(*krate); | ||
86 | for (_, module) in crate_def_map.modules() { | ||
87 | if module.origin.is_inline() { | ||
88 | // We don't handle inline `mod parent {}`s, they use different paths. | ||
89 | continue; | ||
90 | } | ||
91 | |||
92 | if module.origin.file_id() == Some(*parent_id) { | ||
93 | return make_fixes(sema.db, *parent_id, module_name, self.file_id); | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | } | ||
98 | } | ||
99 | |||
100 | None | ||
101 | } | ||
102 | } | ||
103 | |||
104 | fn make_fixes( | ||
105 | db: &RootDatabase, | ||
106 | parent_file_id: FileId, | ||
107 | new_mod_name: &str, | ||
108 | added_file_id: FileId, | ||
109 | ) -> Option<Vec<Assist>> { | ||
110 | fn is_outline_mod(item: &ast::Item) -> bool { | ||
111 | matches!(item, ast::Item::Module(m) if m.item_list().is_none()) | ||
112 | } | ||
113 | |||
114 | let mod_decl = format!("mod {};", new_mod_name); | ||
115 | let pub_mod_decl = format!("pub mod {};", new_mod_name); | ||
116 | |||
117 | let ast: ast::SourceFile = db.parse(parent_file_id).tree(); | ||
118 | |||
119 | let mut mod_decl_builder = TextEdit::builder(); | ||
120 | let mut pub_mod_decl_builder = TextEdit::builder(); | ||
121 | |||
122 | // If there's an existing `mod m;` statement matching the new one, don't emit a fix (it's | ||
123 | // probably `#[cfg]`d out). | ||
124 | for item in ast.items() { | ||
125 | if let ast::Item::Module(m) = item { | ||
126 | if let Some(name) = m.name() { | ||
127 | if m.item_list().is_none() && name.to_string() == new_mod_name { | ||
128 | cov_mark::hit!(unlinked_file_skip_fix_when_mod_already_exists); | ||
129 | return None; | ||
130 | } | ||
131 | } | ||
132 | } | ||
133 | } | ||
134 | |||
135 | // If there are existing `mod m;` items, append after them (after the first group of them, rather). | ||
136 | match ast | ||
137 | .items() | ||
138 | .skip_while(|item| !is_outline_mod(item)) | ||
139 | .take_while(|item| is_outline_mod(item)) | ||
140 | .last() | ||
141 | { | ||
142 | Some(last) => { | ||
143 | cov_mark::hit!(unlinked_file_append_to_existing_mods); | ||
144 | let offset = last.syntax().text_range().end(); | ||
145 | mod_decl_builder.insert(offset, format!("\n{}", mod_decl)); | ||
146 | pub_mod_decl_builder.insert(offset, format!("\n{}", pub_mod_decl)); | ||
147 | } | ||
148 | None => { | ||
149 | // Prepend before the first item in the file. | ||
150 | match ast.items().next() { | ||
151 | Some(item) => { | ||
152 | cov_mark::hit!(unlinked_file_prepend_before_first_item); | ||
153 | let offset = item.syntax().text_range().start(); | ||
154 | mod_decl_builder.insert(offset, format!("{}\n\n", mod_decl)); | ||
155 | pub_mod_decl_builder.insert(offset, format!("{}\n\n", pub_mod_decl)); | ||
156 | } | ||
157 | None => { | ||
158 | // No items in the file, so just append at the end. | ||
159 | cov_mark::hit!(unlinked_file_empty_file); | ||
160 | let offset = ast.syntax().text_range().end(); | ||
161 | mod_decl_builder.insert(offset, format!("{}\n", mod_decl)); | ||
162 | pub_mod_decl_builder.insert(offset, format!("{}\n", pub_mod_decl)); | ||
163 | } | ||
164 | } | ||
165 | } | ||
166 | } | ||
167 | |||
168 | let trigger_range = db.parse(added_file_id).tree().syntax().text_range(); | ||
169 | Some(vec![ | ||
170 | fix( | ||
171 | "add_mod_declaration", | ||
172 | &format!("Insert `{}`", mod_decl), | ||
173 | SourceChange::from_text_edit(parent_file_id, mod_decl_builder.finish()), | ||
174 | trigger_range, | ||
175 | ), | ||
176 | fix( | ||
177 | "add_pub_mod_declaration", | ||
178 | &format!("Insert `{}`", pub_mod_decl), | ||
179 | SourceChange::from_text_edit(parent_file_id, pub_mod_decl_builder.finish()), | ||
180 | trigger_range, | ||
181 | ), | ||
182 | ]) | ||
183 | } | ||
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs index b75ec411c..455b32456 100644 --- a/crates/ide/src/display/navigation_target.rs +++ b/crates/ide/src/display/navigation_target.rs | |||
@@ -442,10 +442,10 @@ impl TryToNav for hir::TypeParam { | |||
442 | fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> { | 442 | fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> { |
443 | let src = self.source(db)?; | 443 | let src = self.source(db)?; |
444 | let full_range = match &src.value { | 444 | let full_range = match &src.value { |
445 | Either::Left(it) => it | 445 | Either::Left(type_param) => type_param.syntax().text_range(), |
446 | Either::Right(trait_) => trait_ | ||
446 | .name() | 447 | .name() |
447 | .map_or_else(|| it.syntax().text_range(), |name| name.syntax().text_range()), | 448 | .map_or_else(|| trait_.syntax().text_range(), |name| name.syntax().text_range()), |
448 | Either::Right(it) => it.syntax().text_range(), | ||
449 | }; | 449 | }; |
450 | let focus_range = match &src.value { | 450 | let focus_range = match &src.value { |
451 | Either::Left(it) => it.name(), | 451 | Either::Left(it) => it.name(), |
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index ec3828ab2..7ac0118fe 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs | |||
@@ -151,18 +151,18 @@ pub(crate) fn resolve_doc_path_for_def( | |||
151 | ) -> Option<hir::ModuleDef> { | 151 | ) -> Option<hir::ModuleDef> { |
152 | match def { | 152 | match def { |
153 | Definition::ModuleDef(def) => match def { | 153 | Definition::ModuleDef(def) => match def { |
154 | hir::ModuleDef::Module(it) => it.resolve_doc_path(db, &link, ns), | 154 | hir::ModuleDef::Module(it) => it.resolve_doc_path(db, link, ns), |
155 | hir::ModuleDef::Function(it) => it.resolve_doc_path(db, &link, ns), | 155 | hir::ModuleDef::Function(it) => it.resolve_doc_path(db, link, ns), |
156 | hir::ModuleDef::Adt(it) => it.resolve_doc_path(db, &link, ns), | 156 | hir::ModuleDef::Adt(it) => it.resolve_doc_path(db, link, ns), |
157 | hir::ModuleDef::Variant(it) => it.resolve_doc_path(db, &link, ns), | 157 | hir::ModuleDef::Variant(it) => it.resolve_doc_path(db, link, ns), |
158 | hir::ModuleDef::Const(it) => it.resolve_doc_path(db, &link, ns), | 158 | hir::ModuleDef::Const(it) => it.resolve_doc_path(db, link, ns), |
159 | hir::ModuleDef::Static(it) => it.resolve_doc_path(db, &link, ns), | 159 | hir::ModuleDef::Static(it) => it.resolve_doc_path(db, link, ns), |
160 | hir::ModuleDef::Trait(it) => it.resolve_doc_path(db, &link, ns), | 160 | hir::ModuleDef::Trait(it) => it.resolve_doc_path(db, link, ns), |
161 | hir::ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, &link, ns), | 161 | hir::ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, link, ns), |
162 | hir::ModuleDef::BuiltinType(_) => None, | 162 | hir::ModuleDef::BuiltinType(_) => None, |
163 | }, | 163 | }, |
164 | Definition::Macro(it) => it.resolve_doc_path(db, &link, ns), | 164 | Definition::Macro(it) => it.resolve_doc_path(db, link, ns), |
165 | Definition::Field(it) => it.resolve_doc_path(db, &link, ns), | 165 | Definition::Field(it) => it.resolve_doc_path(db, link, ns), |
166 | Definition::SelfType(_) | 166 | Definition::SelfType(_) |
167 | | Definition::Local(_) | 167 | | Definition::Local(_) |
168 | | Definition::GenericParam(_) | 168 | | Definition::GenericParam(_) |
@@ -192,7 +192,7 @@ pub(crate) fn doc_attributes( | |||
192 | ast::TupleField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))), | 192 | ast::TupleField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))), |
193 | ast::Macro(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Macro(def))), | 193 | ast::Macro(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Macro(def))), |
194 | // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))), | 194 | // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))), |
195 | _ => return None | 195 | _ => None |
196 | } | 196 | } |
197 | } | 197 | } |
198 | } | 198 | } |
@@ -241,6 +241,10 @@ fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option<String> { | |||
241 | Definition::ModuleDef(ModuleDef::Module(module)) => module.krate(), | 241 | Definition::ModuleDef(ModuleDef::Module(module)) => module.krate(), |
242 | _ => definition.module(db)?.krate(), | 242 | _ => definition.module(db)?.krate(), |
243 | }; | 243 | }; |
244 | // FIXME: using import map doesn't make sense here. What we want here is | ||
245 | // canonical path. What import map returns is the shortest path suitable for | ||
246 | // import. See this test: | ||
247 | cov_mark::hit!(test_reexport_order); | ||
244 | let import_map = db.import_map(krate.into()); | 248 | let import_map = db.import_map(krate.into()); |
245 | 249 | ||
246 | let mut base = krate.display_name(db)?.to_string(); | 250 | let mut base = krate.display_name(db)?.to_string(); |
@@ -642,13 +646,15 @@ pub mod foo { | |||
642 | ) | 646 | ) |
643 | } | 647 | } |
644 | 648 | ||
645 | // FIXME: ImportMap will return re-export paths instead of public module | ||
646 | // paths. The correct path to documentation will never be a re-export. | ||
647 | // This problem stops us from resolving stdlib items included in the prelude | ||
648 | // such as `Option::Some` correctly. | ||
649 | #[ignore = "ImportMap may return re-exports"] | ||
650 | #[test] | 649 | #[test] |
651 | fn test_reexport_order() { | 650 | fn test_reexport_order() { |
651 | cov_mark::check!(test_reexport_order); | ||
652 | // FIXME: This should return | ||
653 | // | ||
654 | // https://docs.rs/test/*/test/wrapper/modulestruct.Item.html | ||
655 | // | ||
656 | // That is, we should point inside the module, rather than at the | ||
657 | // re-export. | ||
652 | check( | 658 | check( |
653 | r#" | 659 | r#" |
654 | pub mod wrapper { | 660 | pub mod wrapper { |
@@ -663,7 +669,7 @@ fn foo() { | |||
663 | let bar: wrapper::It$0em; | 669 | let bar: wrapper::It$0em; |
664 | } | 670 | } |
665 | "#, | 671 | "#, |
666 | expect![[r#"https://docs.rs/test/*/test/wrapper/module/struct.Item.html"#]], | 672 | expect![[r#"https://docs.rs/test/*/test/wrapper/struct.Item.html"#]], |
667 | ) | 673 | ) |
668 | } | 674 | } |
669 | } | 675 | } |
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs index 7032889ac..c7ec87edf 100644 --- a/crates/ide/src/extend_selection.rs +++ b/crates/ide/src/extend_selection.rs | |||
@@ -328,7 +328,7 @@ mod tests { | |||
328 | use super::*; | 328 | use super::*; |
329 | 329 | ||
330 | fn do_check(before: &str, afters: &[&str]) { | 330 | fn do_check(before: &str, afters: &[&str]) { |
331 | let (analysis, position) = fixture::position(&before); | 331 | let (analysis, position) = fixture::position(before); |
332 | let before = analysis.file_text(position.file_id).unwrap(); | 332 | let before = analysis.file_text(position.file_id).unwrap(); |
333 | let range = TextRange::empty(position.offset); | 333 | let range = TextRange::empty(position.offset); |
334 | let mut frange = FileRange { file_id: position.file_id, range }; | 334 | let mut frange = FileRange { file_id: position.file_id, range }; |
diff --git a/crates/ide/src/fixture.rs b/crates/ide/src/fixture.rs index 6780af617..cf679edd3 100644 --- a/crates/ide/src/fixture.rs +++ b/crates/ide/src/fixture.rs | |||
@@ -1,6 +1,5 @@ | |||
1 | //! Utilities for creating `Analysis` instances for tests. | 1 | //! Utilities for creating `Analysis` instances for tests. |
2 | use ide_db::base_db::fixture::ChangeFixture; | 2 | use ide_db::base_db::fixture::ChangeFixture; |
3 | use syntax::{TextRange, TextSize}; | ||
4 | use test_utils::extract_annotations; | 3 | use test_utils::extract_annotations; |
5 | 4 | ||
6 | use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange}; | 5 | use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange}; |
@@ -13,14 +12,6 @@ pub(crate) fn file(ra_fixture: &str) -> (Analysis, FileId) { | |||
13 | (host.analysis(), change_fixture.files[0]) | 12 | (host.analysis(), change_fixture.files[0]) |
14 | } | 13 | } |
15 | 14 | ||
16 | /// Creates analysis for many files. | ||
17 | pub(crate) fn files(ra_fixture: &str) -> (Analysis, Vec<FileId>) { | ||
18 | let mut host = AnalysisHost::default(); | ||
19 | let change_fixture = ChangeFixture::parse(ra_fixture); | ||
20 | host.db.apply_change(change_fixture.change); | ||
21 | (host.analysis(), change_fixture.files) | ||
22 | } | ||
23 | |||
24 | /// Creates analysis from a multi-file fixture, returns positions marked with $0. | 15 | /// Creates analysis from a multi-file fixture, returns positions marked with $0. |
25 | pub(crate) fn position(ra_fixture: &str) -> (Analysis, FilePosition) { | 16 | pub(crate) fn position(ra_fixture: &str) -> (Analysis, FilePosition) { |
26 | let mut host = AnalysisHost::default(); | 17 | let mut host = AnalysisHost::default(); |
@@ -63,15 +54,8 @@ pub(crate) fn annotations(ra_fixture: &str) -> (Analysis, FilePosition, Vec<(Fil | |||
63 | 54 | ||
64 | pub(crate) fn nav_target_annotation(ra_fixture: &str) -> (Analysis, FilePosition, FileRange) { | 55 | pub(crate) fn nav_target_annotation(ra_fixture: &str) -> (Analysis, FilePosition, FileRange) { |
65 | let (analysis, position, mut annotations) = annotations(ra_fixture); | 56 | let (analysis, position, mut annotations) = annotations(ra_fixture); |
66 | let (mut expected, data) = annotations.pop().unwrap(); | 57 | let (expected, data) = annotations.pop().unwrap(); |
67 | assert!(annotations.is_empty()); | 58 | assert!(annotations.is_empty()); |
68 | match data.as_str() { | 59 | assert_eq!(data, ""); |
69 | "" => (), | ||
70 | "file" => { | ||
71 | expected.range = | ||
72 | TextRange::up_to(TextSize::of(&*analysis.file_text(expected.file_id).unwrap())) | ||
73 | } | ||
74 | data => panic!("bad data: {}", data), | ||
75 | } | ||
76 | (analysis, position, expected) | 60 | (analysis, position, expected) |
77 | } | 61 | } |
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 2d36c34e9..d8e0dc4d5 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -43,7 +43,7 @@ pub(crate) fn goto_definition( | |||
43 | let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?; | 43 | let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?; |
44 | let (_, link, ns) = | 44 | let (_, link, ns) = |
45 | extract_definitions_from_markdown(docs.as_str()).into_iter().find(|(range, ..)| { | 45 | extract_definitions_from_markdown(docs.as_str()).into_iter().find(|(range, ..)| { |
46 | doc_mapping.map(range.clone()).map_or(false, |InFile { file_id, value: range }| { | 46 | doc_mapping.map(*range).map_or(false, |InFile { file_id, value: range }| { |
47 | file_id == position.file_id.into() && range.contains(position.offset) | 47 | file_id == position.file_id.into() && range.contains(position.offset) |
48 | }) | 48 | }) |
49 | })?; | 49 | })?; |
@@ -57,7 +57,7 @@ pub(crate) fn goto_definition( | |||
57 | }, | 57 | }, |
58 | ast::Name(name) => { | 58 | ast::Name(name) => { |
59 | let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db); | 59 | let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db); |
60 | try_find_trait_item_definition(&sema.db, &def) | 60 | try_find_trait_item_definition(sema.db, &def) |
61 | .or_else(|| def.try_to_nav(sema.db)) | 61 | .or_else(|| def.try_to_nav(sema.db)) |
62 | }, | 62 | }, |
63 | ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, <) { | 63 | ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, <) { |
@@ -185,7 +185,7 @@ mod tests { | |||
185 | extern crate std$0; | 185 | extern crate std$0; |
186 | //- /std/lib.rs crate:std | 186 | //- /std/lib.rs crate:std |
187 | // empty | 187 | // empty |
188 | //^ file | 188 | //^file |
189 | "#, | 189 | "#, |
190 | ) | 190 | ) |
191 | } | 191 | } |
@@ -198,7 +198,7 @@ extern crate std$0; | |||
198 | extern crate std as abc$0; | 198 | extern crate std as abc$0; |
199 | //- /std/lib.rs crate:std | 199 | //- /std/lib.rs crate:std |
200 | // empty | 200 | // empty |
201 | //^ file | 201 | //^file |
202 | "#, | 202 | "#, |
203 | ) | 203 | ) |
204 | } | 204 | } |
@@ -253,7 +253,7 @@ mod $0foo; | |||
253 | 253 | ||
254 | //- /foo.rs | 254 | //- /foo.rs |
255 | // empty | 255 | // empty |
256 | //^ file | 256 | //^file |
257 | "#, | 257 | "#, |
258 | ); | 258 | ); |
259 | 259 | ||
@@ -264,7 +264,7 @@ mod $0foo; | |||
264 | 264 | ||
265 | //- /foo/mod.rs | 265 | //- /foo/mod.rs |
266 | // empty | 266 | // empty |
267 | //^ file | 267 | //^file |
268 | "#, | 268 | "#, |
269 | ); | 269 | ); |
270 | } | 270 | } |
@@ -395,7 +395,7 @@ use foo as bar$0; | |||
395 | 395 | ||
396 | //- /foo/lib.rs crate:foo | 396 | //- /foo/lib.rs crate:foo |
397 | // empty | 397 | // empty |
398 | //^ file | 398 | //^file |
399 | "#, | 399 | "#, |
400 | ); | 400 | ); |
401 | } | 401 | } |
@@ -1130,15 +1130,15 @@ fn foo<'foobar>(_: &'foobar ()) { | |||
1130 | } | 1130 | } |
1131 | 1131 | ||
1132 | #[test] | 1132 | #[test] |
1133 | #[ignore] // requires the HIR to somehow track these hrtb lifetimes | ||
1134 | fn goto_lifetime_hrtb() { | 1133 | fn goto_lifetime_hrtb() { |
1135 | check( | 1134 | // FIXME: requires the HIR to somehow track these hrtb lifetimes |
1135 | check_unresolved( | ||
1136 | r#"trait Foo<T> {} | 1136 | r#"trait Foo<T> {} |
1137 | fn foo<T>() where for<'a> T: Foo<&'a$0 (u8, u16)>, {} | 1137 | fn foo<T>() where for<'a> T: Foo<&'a$0 (u8, u16)>, {} |
1138 | //^^ | 1138 | //^^ |
1139 | "#, | 1139 | "#, |
1140 | ); | 1140 | ); |
1141 | check( | 1141 | check_unresolved( |
1142 | r#"trait Foo<T> {} | 1142 | r#"trait Foo<T> {} |
1143 | fn foo<T>() where for<'a$0> T: Foo<&'a (u8, u16)>, {} | 1143 | fn foo<T>() where for<'a$0> T: Foo<&'a (u8, u16)>, {} |
1144 | //^^ | 1144 | //^^ |
@@ -1147,9 +1147,9 @@ fn foo<T>() where for<'a$0> T: Foo<&'a (u8, u16)>, {} | |||
1147 | } | 1147 | } |
1148 | 1148 | ||
1149 | #[test] | 1149 | #[test] |
1150 | #[ignore] // requires ForTypes to be implemented | ||
1151 | fn goto_lifetime_hrtb_for_type() { | 1150 | fn goto_lifetime_hrtb_for_type() { |
1152 | check( | 1151 | // FIXME: requires ForTypes to be implemented |
1152 | check_unresolved( | ||
1153 | r#"trait Foo<T> {} | 1153 | r#"trait Foo<T> {} |
1154 | fn foo<T>() where T: for<'a> Foo<&'a$0 (u8, u16)>, {} | 1154 | fn foo<T>() where T: for<'a> Foo<&'a$0 (u8, u16)>, {} |
1155 | //^^ | 1155 | //^^ |
@@ -1287,7 +1287,7 @@ fn main() { | |||
1287 | } | 1287 | } |
1288 | //- /foo.txt | 1288 | //- /foo.txt |
1289 | // empty | 1289 | // empty |
1290 | //^ file | 1290 | //^file |
1291 | "#, | 1291 | "#, |
1292 | ); | 1292 | ); |
1293 | } | 1293 | } |
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs index 95fd39850..0013820b4 100644 --- a/crates/ide/src/goto_implementation.rs +++ b/crates/ide/src/goto_implementation.rs | |||
@@ -87,7 +87,7 @@ fn impls_for_trait_item( | |||
87 | .filter_map(|imp| { | 87 | .filter_map(|imp| { |
88 | let item = imp.items(sema.db).iter().find_map(|itm| { | 88 | let item = imp.items(sema.db).iter().find_map(|itm| { |
89 | let itm_name = itm.name(sema.db)?; | 89 | let itm_name = itm.name(sema.db)?; |
90 | (itm_name == fun_name).then(|| itm.clone()) | 90 | (itm_name == fun_name).then(|| *itm) |
91 | })?; | 91 | })?; |
92 | item.try_to_nav(sema.db) | 92 | item.try_to_nav(sema.db) |
93 | }) | 93 | }) |
diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs index 004d9cb68..ca3c02bf6 100644 --- a/crates/ide/src/goto_type_definition.rs +++ b/crates/ide/src/goto_type_definition.rs | |||
@@ -25,7 +25,7 @@ pub(crate) fn goto_type_definition( | |||
25 | let token: SyntaxToken = pick_best(file.syntax().token_at_offset(position.offset))?; | 25 | let token: SyntaxToken = pick_best(file.syntax().token_at_offset(position.offset))?; |
26 | let token: SyntaxToken = sema.descend_into_macros(token); | 26 | let token: SyntaxToken = sema.descend_into_macros(token); |
27 | 27 | ||
28 | let (ty, node) = sema.token_ancestors_with_macros(token).find_map(|node| { | 28 | let (ty, node) = sema.token_ancestors_with_macros(token.clone()).find_map(|node| { |
29 | let ty = match_ast! { | 29 | let ty = match_ast! { |
30 | match node { | 30 | match node { |
31 | ast::Expr(it) => sema.type_of_expr(&it)?, | 31 | ast::Expr(it) => sema.type_of_expr(&it)?, |
@@ -33,13 +33,23 @@ pub(crate) fn goto_type_definition( | |||
33 | ast::SelfParam(it) => sema.type_of_self(&it)?, | 33 | ast::SelfParam(it) => sema.type_of_self(&it)?, |
34 | ast::Type(it) => sema.resolve_type(&it)?, | 34 | ast::Type(it) => sema.resolve_type(&it)?, |
35 | ast::RecordField(it) => sema.to_def(&it).map(|d| d.ty(db.upcast()))?, | 35 | ast::RecordField(it) => sema.to_def(&it).map(|d| d.ty(db.upcast()))?, |
36 | ast::RecordField(it) => sema.to_def(&it).map(|d| d.ty(db.upcast()))?, | ||
37 | // can't match on RecordExprField directly as `ast::Expr` will match an iteration too early otherwise | ||
38 | ast::NameRef(it) => { | ||
39 | if let Some(record_field) = ast::RecordExprField::for_name_ref(&it) { | ||
40 | let (_, _, ty) = sema.resolve_record_field(&record_field)?; | ||
41 | ty | ||
42 | } else { | ||
43 | let record_field = ast::RecordPatField::for_field_name_ref(&it)?; | ||
44 | sema.resolve_record_pat_field(&record_field)?.ty(db) | ||
45 | } | ||
46 | }, | ||
36 | _ => return None, | 47 | _ => return None, |
37 | } | 48 | } |
38 | }; | 49 | }; |
39 | 50 | ||
40 | Some((ty, node)) | 51 | Some((ty, node)) |
41 | })?; | 52 | })?; |
42 | |||
43 | let adt_def = ty.autoderef(db).filter_map(|ty| ty.as_adt()).last()?; | 53 | let adt_def = ty.autoderef(db).filter_map(|ty| ty.as_adt()).last()?; |
44 | 54 | ||
45 | let nav = adt_def.try_to_nav(db)?; | 55 | let nav = adt_def.try_to_nav(db)?; |
@@ -88,6 +98,54 @@ fn foo() { | |||
88 | } | 98 | } |
89 | 99 | ||
90 | #[test] | 100 | #[test] |
101 | fn goto_type_definition_record_expr_field() { | ||
102 | check( | ||
103 | r#" | ||
104 | struct Bar; | ||
105 | // ^^^ | ||
106 | struct Foo { foo: Bar } | ||
107 | fn foo() { | ||
108 | Foo { foo$0 } | ||
109 | } | ||
110 | "#, | ||
111 | ); | ||
112 | check( | ||
113 | r#" | ||
114 | struct Bar; | ||
115 | // ^^^ | ||
116 | struct Foo { foo: Bar } | ||
117 | fn foo() { | ||
118 | Foo { foo$0: Bar } | ||
119 | } | ||
120 | "#, | ||
121 | ); | ||
122 | } | ||
123 | |||
124 | #[test] | ||
125 | fn goto_type_definition_record_pat_field() { | ||
126 | check( | ||
127 | r#" | ||
128 | struct Bar; | ||
129 | // ^^^ | ||
130 | struct Foo { foo: Bar } | ||
131 | fn foo() { | ||
132 | let Foo { foo$0 }; | ||
133 | } | ||
134 | "#, | ||
135 | ); | ||
136 | check( | ||
137 | r#" | ||
138 | struct Bar; | ||
139 | // ^^^ | ||
140 | struct Foo { foo: Bar } | ||
141 | fn foo() { | ||
142 | let Foo { foo$0: bar }; | ||
143 | } | ||
144 | "#, | ||
145 | ); | ||
146 | } | ||
147 | |||
148 | #[test] | ||
91 | fn goto_type_definition_works_simple_ref() { | 149 | fn goto_type_definition_works_simple_ref() { |
92 | check( | 150 | check( |
93 | r#" | 151 | r#" |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 1c6d36939..529cf5f33 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -131,7 +131,7 @@ pub(crate) fn hover( | |||
131 | let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?; | 131 | let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?; |
132 | let (idl_range, link, ns) = | 132 | let (idl_range, link, ns) = |
133 | extract_definitions_from_markdown(docs.as_str()).into_iter().find_map(|(range, link, ns)| { | 133 | extract_definitions_from_markdown(docs.as_str()).into_iter().find_map(|(range, link, ns)| { |
134 | let InFile { file_id, value: range } = doc_mapping.map(range.clone())?; | 134 | let InFile { file_id, value: range } = doc_mapping.map(range)?; |
135 | if file_id == position.file_id.into() && range.contains(position.offset) { | 135 | if file_id == position.file_id.into() && range.contains(position.offset) { |
136 | Some((range, link, ns)) | 136 | Some((range, link, ns)) |
137 | } else { | 137 | } else { |
@@ -288,7 +288,7 @@ fn runnable_action( | |||
288 | ) -> Option<HoverAction> { | 288 | ) -> Option<HoverAction> { |
289 | match def { | 289 | match def { |
290 | Definition::ModuleDef(it) => match it { | 290 | Definition::ModuleDef(it) => match it { |
291 | ModuleDef::Module(it) => runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it)), | 291 | ModuleDef::Module(it) => runnable_mod(sema, it).map(HoverAction::Runnable), |
292 | ModuleDef::Function(func) => { | 292 | ModuleDef::Function(func) => { |
293 | let src = func.source(sema.db)?; | 293 | let src = func.source(sema.db)?; |
294 | if src.file_id != file_id.into() { | 294 | if src.file_id != file_id.into() { |
@@ -297,7 +297,7 @@ fn runnable_action( | |||
297 | return None; | 297 | return None; |
298 | } | 298 | } |
299 | 299 | ||
300 | runnable_fn(&sema, func).map(HoverAction::Runnable) | 300 | runnable_fn(sema, func).map(HoverAction::Runnable) |
301 | } | 301 | } |
302 | _ => None, | 302 | _ => None, |
303 | }, | 303 | }, |
@@ -432,7 +432,7 @@ fn hover_for_definition( | |||
432 | return match def { | 432 | return match def { |
433 | Definition::Macro(it) => match &it.source(db)?.value { | 433 | Definition::Macro(it) => match &it.source(db)?.value { |
434 | Either::Left(mac) => { | 434 | Either::Left(mac) => { |
435 | let label = macro_label(&mac); | 435 | let label = macro_label(mac); |
436 | from_def_source_labeled(db, it, Some(label), mod_path) | 436 | from_def_source_labeled(db, it, Some(label), mod_path) |
437 | } | 437 | } |
438 | Either::Right(_) => { | 438 | Either::Right(_) => { |
@@ -516,7 +516,7 @@ fn hover_for_keyword( | |||
516 | if !token.kind().is_keyword() { | 516 | if !token.kind().is_keyword() { |
517 | return None; | 517 | return None; |
518 | } | 518 | } |
519 | let famous_defs = FamousDefs(&sema, sema.scope(&token.parent()?).krate()); | 519 | let famous_defs = FamousDefs(sema, sema.scope(&token.parent()?).krate()); |
520 | // std exposes {}_keyword modules with docstrings on the root to document keywords | 520 | // std exposes {}_keyword modules with docstrings on the root to document keywords |
521 | let keyword_mod = format!("{}_keyword", token.text()); | 521 | let keyword_mod = format!("{}_keyword", token.text()); |
522 | let doc_owner = find_std_module(&famous_defs, &keyword_mod)?; | 522 | let doc_owner = find_std_module(&famous_defs, &keyword_mod)?; |
@@ -1821,9 +1821,10 @@ pub struct B$0ar | |||
1821 | ); | 1821 | ); |
1822 | } | 1822 | } |
1823 | 1823 | ||
1824 | #[ignore = "path based links currently only support documentation on ModuleDef items"] | ||
1825 | #[test] | 1824 | #[test] |
1826 | fn test_hover_path_link_field() { | 1825 | fn test_hover_path_link_field() { |
1826 | // FIXME: Should be | ||
1827 | // [Foo](https://docs.rs/test/*/test/struct.Foo.html) | ||
1827 | check( | 1828 | check( |
1828 | r#" | 1829 | r#" |
1829 | pub struct Foo; | 1830 | pub struct Foo; |
@@ -1845,7 +1846,7 @@ pub struct Bar { | |||
1845 | 1846 | ||
1846 | --- | 1847 | --- |
1847 | 1848 | ||
1848 | [Foo](https://docs.rs/test/*/test/struct.Foo.html) | 1849 | [Foo](struct.Foo.html) |
1849 | "#]], | 1850 | "#]], |
1850 | ); | 1851 | ); |
1851 | } | 1852 | } |
@@ -2999,29 +3000,24 @@ fn foo(ar$0g: &impl Foo + Bar<S>) {} | |||
2999 | fn test_hover_async_block_impl_trait_has_goto_type_action() { | 3000 | fn test_hover_async_block_impl_trait_has_goto_type_action() { |
3000 | check_actions( | 3001 | check_actions( |
3001 | r#" | 3002 | r#" |
3003 | //- minicore: future | ||
3002 | struct S; | 3004 | struct S; |
3003 | fn foo() { | 3005 | fn foo() { |
3004 | let fo$0o = async { S }; | 3006 | let fo$0o = async { S }; |
3005 | } | 3007 | } |
3006 | |||
3007 | #[prelude_import] use future::*; | ||
3008 | mod future { | ||
3009 | #[lang = "future_trait"] | ||
3010 | pub trait Future { type Output; } | ||
3011 | } | ||
3012 | "#, | 3008 | "#, |
3013 | expect![[r#" | 3009 | expect![[r#" |
3014 | [ | 3010 | [ |
3015 | GoToType( | 3011 | GoToType( |
3016 | [ | 3012 | [ |
3017 | HoverGotoTypeData { | 3013 | HoverGotoTypeData { |
3018 | mod_path: "test::future::Future", | 3014 | mod_path: "core::future::Future", |
3019 | nav: NavigationTarget { | 3015 | nav: NavigationTarget { |
3020 | file_id: FileId( | 3016 | file_id: FileId( |
3021 | 0, | 3017 | 1, |
3022 | ), | 3018 | ), |
3023 | full_range: 101..163, | 3019 | full_range: 245..427, |
3024 | focus_range: 140..146, | 3020 | focus_range: 284..290, |
3025 | name: "Future", | 3021 | name: "Future", |
3026 | kind: Trait, | 3022 | kind: Trait, |
3027 | description: "pub trait Future", | 3023 | description: "pub trait Future", |
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 821c61403..9cd33d0e4 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs | |||
@@ -96,7 +96,7 @@ fn get_chaining_hints( | |||
96 | } | 96 | } |
97 | 97 | ||
98 | let krate = sema.scope(expr.syntax()).module().map(|it| it.krate()); | 98 | let krate = sema.scope(expr.syntax()).module().map(|it| it.krate()); |
99 | let famous_defs = FamousDefs(&sema, krate); | 99 | let famous_defs = FamousDefs(sema, krate); |
100 | 100 | ||
101 | let mut tokens = expr | 101 | let mut tokens = expr |
102 | .syntax() | 102 | .syntax() |
@@ -165,7 +165,7 @@ fn get_param_name_hints( | |||
165 | }; | 165 | }; |
166 | Some((param_name, arg)) | 166 | Some((param_name, arg)) |
167 | }) | 167 | }) |
168 | .filter(|(param_name, arg)| !should_hide_param_name_hint(sema, &callable, param_name, &arg)) | 168 | .filter(|(param_name, arg)| !should_hide_param_name_hint(sema, &callable, param_name, arg)) |
169 | .map(|(param_name, arg)| InlayHint { | 169 | .map(|(param_name, arg)| InlayHint { |
170 | range: arg.syntax().text_range(), | 170 | range: arg.syntax().text_range(), |
171 | kind: InlayKind::ParameterHint, | 171 | kind: InlayKind::ParameterHint, |
@@ -187,7 +187,7 @@ fn get_bind_pat_hints( | |||
187 | } | 187 | } |
188 | 188 | ||
189 | let krate = sema.scope(pat.syntax()).module().map(|it| it.krate()); | 189 | let krate = sema.scope(pat.syntax()).module().map(|it| it.krate()); |
190 | let famous_defs = FamousDefs(&sema, krate); | 190 | let famous_defs = FamousDefs(sema, krate); |
191 | 191 | ||
192 | let ty = sema.type_of_pat(&pat.clone().into())?; | 192 | let ty = sema.type_of_pat(&pat.clone().into())?; |
193 | 193 | ||
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs index c67ccd1a9..93d3760bf 100644 --- a/crates/ide/src/join_lines.rs +++ b/crates/ide/src/join_lines.rs | |||
@@ -60,7 +60,7 @@ fn remove_newlines(edit: &mut TextEditBuilder, token: &SyntaxToken, range: TextR | |||
60 | let pos: TextSize = (pos as u32).into(); | 60 | let pos: TextSize = (pos as u32).into(); |
61 | let offset = token.text_range().start() + range.start() + pos; | 61 | let offset = token.text_range().start() + range.start() + pos; |
62 | if !edit.invalidates_offset(offset) { | 62 | if !edit.invalidates_offset(offset) { |
63 | remove_newline(edit, &token, offset); | 63 | remove_newline(edit, token, offset); |
64 | } | 64 | } |
65 | } | 65 | } |
66 | } | 66 | } |
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 97c9e5d2b..4bd073cc3 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -24,7 +24,6 @@ mod display; | |||
24 | 24 | ||
25 | mod annotations; | 25 | mod annotations; |
26 | mod call_hierarchy; | 26 | mod call_hierarchy; |
27 | mod diagnostics; | ||
28 | mod expand_macro; | 27 | mod expand_macro; |
29 | mod extend_selection; | 28 | mod extend_selection; |
30 | mod file_structure; | 29 | mod file_structure; |
@@ -40,6 +39,7 @@ mod matching_brace; | |||
40 | mod move_item; | 39 | mod move_item; |
41 | mod parent_module; | 40 | mod parent_module; |
42 | mod references; | 41 | mod references; |
42 | mod rename; | ||
43 | mod fn_references; | 43 | mod fn_references; |
44 | mod runnables; | 44 | mod runnables; |
45 | mod ssr; | 45 | mod ssr; |
@@ -71,7 +71,6 @@ use crate::display::ToNav; | |||
71 | pub use crate::{ | 71 | pub use crate::{ |
72 | annotations::{Annotation, AnnotationConfig, AnnotationKind}, | 72 | annotations::{Annotation, AnnotationConfig, AnnotationKind}, |
73 | call_hierarchy::CallItem, | 73 | call_hierarchy::CallItem, |
74 | diagnostics::{Diagnostic, DiagnosticsConfig, Severity}, | ||
75 | display::navigation_target::NavigationTarget, | 74 | display::navigation_target::NavigationTarget, |
76 | expand_macro::ExpandedMacro, | 75 | expand_macro::ExpandedMacro, |
77 | file_structure::{StructureNode, StructureNodeKind}, | 76 | file_structure::{StructureNode, StructureNodeKind}, |
@@ -81,7 +80,8 @@ pub use crate::{ | |||
81 | markup::Markup, | 80 | markup::Markup, |
82 | move_item::Direction, | 81 | move_item::Direction, |
83 | prime_caches::PrimeCachesProgress, | 82 | prime_caches::PrimeCachesProgress, |
84 | references::{rename::RenameError, ReferenceSearchResult}, | 83 | references::ReferenceSearchResult, |
84 | rename::RenameError, | ||
85 | runnables::{Runnable, RunnableKind, TestId}, | 85 | runnables::{Runnable, RunnableKind, TestId}, |
86 | syntax_highlighting::{ | 86 | syntax_highlighting::{ |
87 | tags::{Highlight, HlMod, HlMods, HlOperator, HlPunct, HlTag}, | 87 | tags::{Highlight, HlMod, HlMods, HlOperator, HlPunct, HlTag}, |
@@ -109,6 +109,7 @@ pub use ide_db::{ | |||
109 | symbol_index::Query, | 109 | symbol_index::Query, |
110 | RootDatabase, SymbolKind, | 110 | RootDatabase, SymbolKind, |
111 | }; | 111 | }; |
112 | pub use ide_diagnostics::{Diagnostic, DiagnosticsConfig, Severity}; | ||
112 | pub use ide_ssr::SsrError; | 113 | pub use ide_ssr::SsrError; |
113 | pub use syntax::{TextRange, TextSize}; | 114 | pub use syntax::{TextRange, TextSize}; |
114 | pub use text_edit::{Indel, TextEdit}; | 115 | pub use text_edit::{Indel, TextEdit}; |
@@ -282,20 +283,20 @@ impl Analysis { | |||
282 | file_id: FileId, | 283 | file_id: FileId, |
283 | text_range: Option<TextRange>, | 284 | text_range: Option<TextRange>, |
284 | ) -> Cancellable<String> { | 285 | ) -> Cancellable<String> { |
285 | self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range)) | 286 | self.with_db(|db| syntax_tree::syntax_tree(db, file_id, text_range)) |
286 | } | 287 | } |
287 | 288 | ||
288 | pub fn view_hir(&self, position: FilePosition) -> Cancellable<String> { | 289 | pub fn view_hir(&self, position: FilePosition) -> Cancellable<String> { |
289 | self.with_db(|db| view_hir::view_hir(&db, position)) | 290 | self.with_db(|db| view_hir::view_hir(db, position)) |
290 | } | 291 | } |
291 | 292 | ||
292 | pub fn view_item_tree(&self, file_id: FileId) -> Cancellable<String> { | 293 | pub fn view_item_tree(&self, file_id: FileId) -> Cancellable<String> { |
293 | self.with_db(|db| view_item_tree::view_item_tree(&db, file_id)) | 294 | self.with_db(|db| view_item_tree::view_item_tree(db, file_id)) |
294 | } | 295 | } |
295 | 296 | ||
296 | /// Renders the crate graph to GraphViz "dot" syntax. | 297 | /// Renders the crate graph to GraphViz "dot" syntax. |
297 | pub fn view_crate_graph(&self) -> Cancellable<Result<String, String>> { | 298 | pub fn view_crate_graph(&self) -> Cancellable<Result<String, String>> { |
298 | self.with_db(|db| view_crate_graph::view_crate_graph(&db)) | 299 | self.with_db(|db| view_crate_graph::view_crate_graph(db)) |
299 | } | 300 | } |
300 | 301 | ||
301 | pub fn expand_macro(&self, position: FilePosition) -> Cancellable<Option<ExpandedMacro>> { | 302 | pub fn expand_macro(&self, position: FilePosition) -> Cancellable<Option<ExpandedMacro>> { |
@@ -315,7 +316,7 @@ impl Analysis { | |||
315 | /// up minor stuff like continuing the comment. | 316 | /// up minor stuff like continuing the comment. |
316 | /// The edit will be a snippet (with `$0`). | 317 | /// The edit will be a snippet (with `$0`). |
317 | pub fn on_enter(&self, position: FilePosition) -> Cancellable<Option<TextEdit>> { | 318 | pub fn on_enter(&self, position: FilePosition) -> Cancellable<Option<TextEdit>> { |
318 | self.with_db(|db| typing::on_enter(&db, position)) | 319 | self.with_db(|db| typing::on_enter(db, position)) |
319 | } | 320 | } |
320 | 321 | ||
321 | /// Returns an edit which should be applied after a character was typed. | 322 | /// Returns an edit which should be applied after a character was typed. |
@@ -331,7 +332,7 @@ impl Analysis { | |||
331 | if !typing::TRIGGER_CHARS.contains(char_typed) { | 332 | if !typing::TRIGGER_CHARS.contains(char_typed) { |
332 | return Ok(None); | 333 | return Ok(None); |
333 | } | 334 | } |
334 | self.with_db(|db| typing::on_char_typed(&db, position, char_typed)) | 335 | self.with_db(|db| typing::on_char_typed(db, position, char_typed)) |
335 | } | 336 | } |
336 | 337 | ||
337 | /// Returns a tree representation of symbols in the file. Useful to draw a | 338 | /// Returns a tree representation of symbols in the file. Useful to draw a |
@@ -536,7 +537,7 @@ impl Analysis { | |||
536 | ) -> Cancellable<Vec<Assist>> { | 537 | ) -> Cancellable<Vec<Assist>> { |
537 | self.with_db(|db| { | 538 | self.with_db(|db| { |
538 | let ssr_assists = ssr::ssr_assists(db, &resolve, frange); | 539 | let ssr_assists = ssr::ssr_assists(db, &resolve, frange); |
539 | let mut acc = Assist::get(db, config, resolve, frange); | 540 | let mut acc = ide_assists::assists(db, config, resolve, frange); |
540 | acc.extend(ssr_assists.into_iter()); | 541 | acc.extend(ssr_assists.into_iter()); |
541 | acc | 542 | acc |
542 | }) | 543 | }) |
@@ -549,7 +550,7 @@ impl Analysis { | |||
549 | resolve: AssistResolveStrategy, | 550 | resolve: AssistResolveStrategy, |
550 | file_id: FileId, | 551 | file_id: FileId, |
551 | ) -> Cancellable<Vec<Diagnostic>> { | 552 | ) -> Cancellable<Vec<Diagnostic>> { |
552 | self.with_db(|db| diagnostics::diagnostics(db, config, &resolve, file_id)) | 553 | self.with_db(|db| ide_diagnostics::diagnostics(db, config, &resolve, file_id)) |
553 | } | 554 | } |
554 | 555 | ||
555 | /// Convenience function to return assists + quick fixes for diagnostics | 556 | /// Convenience function to return assists + quick fixes for diagnostics |
@@ -566,9 +567,8 @@ impl Analysis { | |||
566 | }; | 567 | }; |
567 | 568 | ||
568 | self.with_db(|db| { | 569 | self.with_db(|db| { |
569 | let ssr_assists = ssr::ssr_assists(db, &resolve, frange); | ||
570 | let diagnostic_assists = if include_fixes { | 570 | let diagnostic_assists = if include_fixes { |
571 | diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id) | 571 | ide_diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id) |
572 | .into_iter() | 572 | .into_iter() |
573 | .flat_map(|it| it.fixes.unwrap_or_default()) | 573 | .flat_map(|it| it.fixes.unwrap_or_default()) |
574 | .filter(|it| it.target.intersect(frange.range).is_some()) | 574 | .filter(|it| it.target.intersect(frange.range).is_some()) |
@@ -576,10 +576,12 @@ impl Analysis { | |||
576 | } else { | 576 | } else { |
577 | Vec::new() | 577 | Vec::new() |
578 | }; | 578 | }; |
579 | let ssr_assists = ssr::ssr_assists(db, &resolve, frange); | ||
580 | let assists = ide_assists::assists(db, assist_config, resolve, frange); | ||
579 | 581 | ||
580 | let mut res = Assist::get(db, assist_config, resolve, frange); | 582 | let mut res = diagnostic_assists; |
581 | res.extend(ssr_assists.into_iter()); | 583 | res.extend(ssr_assists.into_iter()); |
582 | res.extend(diagnostic_assists.into_iter()); | 584 | res.extend(assists.into_iter()); |
583 | 585 | ||
584 | res | 586 | res |
585 | }) | 587 | }) |
@@ -592,14 +594,14 @@ impl Analysis { | |||
592 | position: FilePosition, | 594 | position: FilePosition, |
593 | new_name: &str, | 595 | new_name: &str, |
594 | ) -> Cancellable<Result<SourceChange, RenameError>> { | 596 | ) -> Cancellable<Result<SourceChange, RenameError>> { |
595 | self.with_db(|db| references::rename::rename(db, position, new_name)) | 597 | self.with_db(|db| rename::rename(db, position, new_name)) |
596 | } | 598 | } |
597 | 599 | ||
598 | pub fn prepare_rename( | 600 | pub fn prepare_rename( |
599 | &self, | 601 | &self, |
600 | position: FilePosition, | 602 | position: FilePosition, |
601 | ) -> Cancellable<Result<RangeInfo<()>, RenameError>> { | 603 | ) -> Cancellable<Result<RangeInfo<()>, RenameError>> { |
602 | self.with_db(|db| references::rename::prepare_rename(db, position)) | 604 | self.with_db(|db| rename::prepare_rename(db, position)) |
603 | } | 605 | } |
604 | 606 | ||
605 | pub fn will_rename_file( | 607 | pub fn will_rename_file( |
@@ -607,7 +609,7 @@ impl Analysis { | |||
607 | file_id: FileId, | 609 | file_id: FileId, |
608 | new_name_stem: &str, | 610 | new_name_stem: &str, |
609 | ) -> Cancellable<Option<SourceChange>> { | 611 | ) -> Cancellable<Option<SourceChange>> { |
610 | self.with_db(|db| references::rename::will_rename_file(db, file_id, new_name_stem)) | 612 | self.with_db(|db| rename::will_rename_file(db, file_id, new_name_stem)) |
611 | } | 613 | } |
612 | 614 | ||
613 | pub fn structural_search_replace( | 615 | pub fn structural_search_replace( |
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index f8b64a669..945c9b9e1 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -9,8 +9,6 @@ | |||
9 | //! at the index that the match starts at and its tree parent is | 9 | //! at the index that the match starts at and its tree parent is |
10 | //! resolved to the search element definition, we get a reference. | 10 | //! resolved to the search element definition, we get a reference. |
11 | 11 | ||
12 | pub(crate) mod rename; | ||
13 | |||
14 | use hir::{PathResolution, Semantics}; | 12 | use hir::{PathResolution, Semantics}; |
15 | use ide_db::{ | 13 | use ide_db::{ |
16 | base_db::FileId, | 14 | base_db::FileId, |
@@ -62,7 +60,7 @@ pub(crate) fn find_all_refs( | |||
62 | if let Some(name) = get_name_of_item_declaration(&syntax, position) { | 60 | if let Some(name) = get_name_of_item_declaration(&syntax, position) { |
63 | (NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), true) | 61 | (NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), true) |
64 | } else { | 62 | } else { |
65 | (find_def(&sema, &syntax, position)?, false) | 63 | (find_def(sema, &syntax, position)?, false) |
66 | }; | 64 | }; |
67 | 65 | ||
68 | let mut usages = def.usages(sema).set_scope(search_scope).include_self_refs().all(); | 66 | let mut usages = def.usages(sema).set_scope(search_scope).include_self_refs().all(); |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/rename.rs index 7dfc5043e..8096dfa0e 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/rename.rs | |||
@@ -1,45 +1,25 @@ | |||
1 | //! Renaming functionality | 1 | //! Renaming functionality. |
2 | //! | 2 | //! |
3 | //! All reference and file rename requests go through here where the corresponding [`SourceChange`]s | 3 | //! This is mostly front-end for [`ide_db::rename`], but it also includes the |
4 | //! will be calculated. | 4 | //! tests. This module also implements a couple of magic tricks, like renaming |
5 | use std::fmt::{self, Display}; | 5 | //! `self` and to `self` (to switch between associated function and method). |
6 | 6 | use hir::{AsAssocItem, InFile, Semantics}; | |
7 | use either::Either; | ||
8 | use hir::{AsAssocItem, InFile, Module, ModuleDef, ModuleSource, Semantics}; | ||
9 | use ide_db::{ | 7 | use ide_db::{ |
10 | base_db::{AnchoredPathBuf, FileId}, | 8 | base_db::FileId, |
11 | defs::{Definition, NameClass, NameRefClass}, | 9 | defs::{Definition, NameClass, NameRefClass}, |
12 | search::FileReference, | 10 | rename::{bail, format_err, source_edit_from_references, IdentifierKind}, |
13 | RootDatabase, | 11 | RootDatabase, |
14 | }; | 12 | }; |
15 | use stdx::never; | 13 | use stdx::never; |
16 | use syntax::{ | 14 | use syntax::{ast, AstNode, SyntaxNode}; |
17 | ast::{self, NameOwner}, | ||
18 | lex_single_syntax_kind, AstNode, SyntaxKind, SyntaxNode, T, | ||
19 | }; | ||
20 | 15 | ||
21 | use text_edit::TextEdit; | 16 | use text_edit::TextEdit; |
22 | 17 | ||
23 | use crate::{display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange}; | 18 | use crate::{FilePosition, RangeInfo, SourceChange}; |
24 | |||
25 | type RenameResult<T> = Result<T, RenameError>; | ||
26 | #[derive(Debug)] | ||
27 | pub struct RenameError(String); | ||
28 | |||
29 | impl fmt::Display for RenameError { | ||
30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
31 | Display::fmt(&self.0, f) | ||
32 | } | ||
33 | } | ||
34 | 19 | ||
35 | macro_rules! format_err { | 20 | pub use ide_db::rename::RenameError; |
36 | ($fmt:expr) => {RenameError(format!($fmt))}; | ||
37 | ($fmt:expr, $($arg:tt)+) => {RenameError(format!($fmt, $($arg)+))} | ||
38 | } | ||
39 | 21 | ||
40 | macro_rules! bail { | 22 | type RenameResult<T> = Result<T, RenameError>; |
41 | ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))} | ||
42 | } | ||
43 | 23 | ||
44 | /// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is | 24 | /// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is |
45 | /// being targeted for a rename. | 25 | /// being targeted for a rename. |
@@ -52,26 +32,10 @@ pub(crate) fn prepare_rename( | |||
52 | let syntax = source_file.syntax(); | 32 | let syntax = source_file.syntax(); |
53 | 33 | ||
54 | let def = find_definition(&sema, syntax, position)?; | 34 | let def = find_definition(&sema, syntax, position)?; |
55 | match def { | 35 | let frange = def |
56 | Definition::SelfType(_) => bail!("Cannot rename `Self`"), | 36 | .range_for_rename(&sema) |
57 | Definition::ModuleDef(ModuleDef::BuiltinType(_)) => bail!("Cannot rename builtin type"), | ||
58 | Definition::ModuleDef(ModuleDef::Module(_)) => (), | ||
59 | _ => { | ||
60 | let nav = def | ||
61 | .try_to_nav(sema.db) | ||
62 | .ok_or_else(|| format_err!("No references found at position"))?; | ||
63 | nav.focus_range.ok_or_else(|| format_err!("No identifier available to rename"))?; | ||
64 | } | ||
65 | }; | ||
66 | let name_like = sema | ||
67 | .find_node_at_offset_with_descend(&syntax, position.offset) | ||
68 | .ok_or_else(|| format_err!("No references found at position"))?; | 37 | .ok_or_else(|| format_err!("No references found at position"))?; |
69 | let node = match &name_like { | 38 | Ok(RangeInfo::new(frange.range, ())) |
70 | ast::NameLike::Name(it) => it.syntax(), | ||
71 | ast::NameLike::NameRef(it) => it.syntax(), | ||
72 | ast::NameLike::Lifetime(it) => it.syntax(), | ||
73 | }; | ||
74 | Ok(RangeInfo::new(sema.original_range(node).range, ())) | ||
75 | } | 39 | } |
76 | 40 | ||
77 | // Feature: Rename | 41 | // Feature: Rename |
@@ -103,12 +67,19 @@ pub(crate) fn rename_with_semantics( | |||
103 | let syntax = source_file.syntax(); | 67 | let syntax = source_file.syntax(); |
104 | 68 | ||
105 | let def = find_definition(sema, syntax, position)?; | 69 | let def = find_definition(sema, syntax, position)?; |
106 | match def { | 70 | |
107 | Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), | 71 | if let Definition::Local(local) = def { |
108 | Definition::SelfType(_) => bail!("Cannot rename `Self`"), | 72 | if let Some(self_param) = local.as_self_param(sema.db) { |
109 | Definition::ModuleDef(ModuleDef::BuiltinType(_)) => bail!("Cannot rename builtin type"), | 73 | cov_mark::hit!(rename_self_to_param); |
110 | def => rename_reference(sema, def, new_name), | 74 | return rename_self_to_param(sema, local, self_param, new_name); |
75 | } | ||
76 | if new_name == "self" { | ||
77 | cov_mark::hit!(rename_to_self); | ||
78 | return rename_to_self(sema, local); | ||
79 | } | ||
111 | } | 80 | } |
81 | |||
82 | def.rename(sema, new_name) | ||
112 | } | 83 | } |
113 | 84 | ||
114 | /// Called by the client when it is about to rename a file. | 85 | /// Called by the client when it is about to rename a file. |
@@ -119,38 +90,12 @@ pub(crate) fn will_rename_file( | |||
119 | ) -> Option<SourceChange> { | 90 | ) -> Option<SourceChange> { |
120 | let sema = Semantics::new(db); | 91 | let sema = Semantics::new(db); |
121 | let module = sema.to_module_def(file_id)?; | 92 | let module = sema.to_module_def(file_id)?; |
122 | let mut change = rename_mod(&sema, module, new_name_stem).ok()?; | 93 | let def = Definition::ModuleDef(module.into()); |
94 | let mut change = def.rename(&sema, new_name_stem).ok()?; | ||
123 | change.file_system_edits.clear(); | 95 | change.file_system_edits.clear(); |
124 | Some(change) | 96 | Some(change) |
125 | } | 97 | } |
126 | 98 | ||
127 | #[derive(Copy, Clone, Debug, PartialEq)] | ||
128 | enum IdentifierKind { | ||
129 | Ident, | ||
130 | Lifetime, | ||
131 | ToSelf, | ||
132 | Underscore, | ||
133 | } | ||
134 | |||
135 | fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> { | ||
136 | match lex_single_syntax_kind(new_name) { | ||
137 | Some(res) => match res { | ||
138 | (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident), | ||
139 | (T![_], _) => Ok(IdentifierKind::Underscore), | ||
140 | (T![self], _) => Ok(IdentifierKind::ToSelf), | ||
141 | (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => { | ||
142 | Ok(IdentifierKind::Lifetime) | ||
143 | } | ||
144 | (SyntaxKind::LIFETIME_IDENT, _) => { | ||
145 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) | ||
146 | } | ||
147 | (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), | ||
148 | (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), | ||
149 | }, | ||
150 | None => bail!("Invalid name `{}`: not an identifier", new_name), | ||
151 | } | ||
152 | } | ||
153 | |||
154 | fn find_definition( | 99 | fn find_definition( |
155 | sema: &Semantics<RootDatabase>, | 100 | sema: &Semantics<RootDatabase>, |
156 | syntax: &SyntaxNode, | 101 | syntax: &SyntaxNode, |
@@ -192,145 +137,6 @@ fn find_definition( | |||
192 | .ok_or_else(|| format_err!("No references found at position")) | 137 | .ok_or_else(|| format_err!("No references found at position")) |
193 | } | 138 | } |
194 | 139 | ||
195 | fn rename_mod( | ||
196 | sema: &Semantics<RootDatabase>, | ||
197 | module: Module, | ||
198 | new_name: &str, | ||
199 | ) -> RenameResult<SourceChange> { | ||
200 | if IdentifierKind::Ident != check_identifier(new_name)? { | ||
201 | bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); | ||
202 | } | ||
203 | |||
204 | let mut source_change = SourceChange::default(); | ||
205 | |||
206 | let InFile { file_id, value: def_source } = module.definition_source(sema.db); | ||
207 | let file_id = file_id.original_file(sema.db); | ||
208 | if let ModuleSource::SourceFile(..) = def_source { | ||
209 | // mod is defined in path/to/dir/mod.rs | ||
210 | let path = if module.is_mod_rs(sema.db) { | ||
211 | format!("../{}/mod.rs", new_name) | ||
212 | } else { | ||
213 | format!("{}.rs", new_name) | ||
214 | }; | ||
215 | let dst = AnchoredPathBuf { anchor: file_id, path }; | ||
216 | let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; | ||
217 | source_change.push_file_system_edit(move_file); | ||
218 | } | ||
219 | |||
220 | if let Some(InFile { file_id, value: decl_source }) = module.declaration_source(sema.db) { | ||
221 | let file_id = file_id.original_file(sema.db); | ||
222 | match decl_source.name() { | ||
223 | Some(name) => source_change.insert_source_edit( | ||
224 | file_id, | ||
225 | TextEdit::replace(name.syntax().text_range(), new_name.to_string()), | ||
226 | ), | ||
227 | _ => never!("Module source node is missing a name"), | ||
228 | } | ||
229 | } | ||
230 | let def = Definition::ModuleDef(ModuleDef::Module(module)); | ||
231 | let usages = def.usages(sema).all(); | ||
232 | let ref_edits = usages.iter().map(|(&file_id, references)| { | ||
233 | (file_id, source_edit_from_references(references, def, new_name)) | ||
234 | }); | ||
235 | source_change.extend(ref_edits); | ||
236 | |||
237 | Ok(source_change) | ||
238 | } | ||
239 | |||
240 | fn rename_reference( | ||
241 | sema: &Semantics<RootDatabase>, | ||
242 | mut def: Definition, | ||
243 | new_name: &str, | ||
244 | ) -> RenameResult<SourceChange> { | ||
245 | let ident_kind = check_identifier(new_name)?; | ||
246 | |||
247 | if matches!( | ||
248 | def, // is target a lifetime? | ||
249 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) | ||
250 | ) { | ||
251 | match ident_kind { | ||
252 | IdentifierKind::Ident | IdentifierKind::ToSelf | IdentifierKind::Underscore => { | ||
253 | cov_mark::hit!(rename_not_a_lifetime_ident_ref); | ||
254 | bail!("Invalid name `{}`: not a lifetime identifier", new_name); | ||
255 | } | ||
256 | IdentifierKind::Lifetime => cov_mark::hit!(rename_lifetime), | ||
257 | } | ||
258 | } else { | ||
259 | match (ident_kind, def) { | ||
260 | (IdentifierKind::Lifetime, _) => { | ||
261 | cov_mark::hit!(rename_not_an_ident_ref); | ||
262 | bail!("Invalid name `{}`: not an identifier", new_name); | ||
263 | } | ||
264 | (IdentifierKind::ToSelf, Definition::Local(local)) => { | ||
265 | if local.is_self(sema.db) { | ||
266 | // no-op | ||
267 | cov_mark::hit!(rename_self_to_self); | ||
268 | return Ok(SourceChange::default()); | ||
269 | } else { | ||
270 | cov_mark::hit!(rename_to_self); | ||
271 | return rename_to_self(sema, local); | ||
272 | } | ||
273 | } | ||
274 | (ident_kind, Definition::Local(local)) => { | ||
275 | if let Some(self_param) = local.as_self_param(sema.db) { | ||
276 | cov_mark::hit!(rename_self_to_param); | ||
277 | return rename_self_to_param(sema, local, self_param, new_name, ident_kind); | ||
278 | } else { | ||
279 | cov_mark::hit!(rename_local); | ||
280 | } | ||
281 | } | ||
282 | (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name), | ||
283 | (IdentifierKind::Ident, _) => cov_mark::hit!(rename_non_local), | ||
284 | (IdentifierKind::Underscore, _) => (), | ||
285 | } | ||
286 | } | ||
287 | |||
288 | def = match def { | ||
289 | // HACK: resolve trait impl items to the item def of the trait definition | ||
290 | // so that we properly resolve all trait item references | ||
291 | Definition::ModuleDef(mod_def) => mod_def | ||
292 | .as_assoc_item(sema.db) | ||
293 | .and_then(|it| it.containing_trait_impl(sema.db)) | ||
294 | .and_then(|it| { | ||
295 | it.items(sema.db).into_iter().find_map(|it| match (it, mod_def) { | ||
296 | (hir::AssocItem::Function(trait_func), ModuleDef::Function(func)) | ||
297 | if trait_func.name(sema.db) == func.name(sema.db) => | ||
298 | { | ||
299 | Some(Definition::ModuleDef(ModuleDef::Function(trait_func))) | ||
300 | } | ||
301 | (hir::AssocItem::Const(trait_konst), ModuleDef::Const(konst)) | ||
302 | if trait_konst.name(sema.db) == konst.name(sema.db) => | ||
303 | { | ||
304 | Some(Definition::ModuleDef(ModuleDef::Const(trait_konst))) | ||
305 | } | ||
306 | ( | ||
307 | hir::AssocItem::TypeAlias(trait_type_alias), | ||
308 | ModuleDef::TypeAlias(type_alias), | ||
309 | ) if trait_type_alias.name(sema.db) == type_alias.name(sema.db) => { | ||
310 | Some(Definition::ModuleDef(ModuleDef::TypeAlias(trait_type_alias))) | ||
311 | } | ||
312 | _ => None, | ||
313 | }) | ||
314 | }) | ||
315 | .unwrap_or(def), | ||
316 | _ => def, | ||
317 | }; | ||
318 | let usages = def.usages(sema).all(); | ||
319 | |||
320 | if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { | ||
321 | cov_mark::hit!(rename_underscore_multiple); | ||
322 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
323 | } | ||
324 | let mut source_change = SourceChange::default(); | ||
325 | source_change.extend(usages.iter().map(|(&file_id, references)| { | ||
326 | (file_id, source_edit_from_references(&references, def, new_name)) | ||
327 | })); | ||
328 | |||
329 | let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; | ||
330 | source_change.insert_source_edit(file_id, edit); | ||
331 | Ok(source_change) | ||
332 | } | ||
333 | |||
334 | fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { | 140 | fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { |
335 | if never!(local.is_self(sema.db)) { | 141 | if never!(local.is_self(sema.db)) { |
336 | bail!("rename_to_self invoked on self"); | 142 | bail!("rename_to_self invoked on self"); |
@@ -398,8 +204,15 @@ fn rename_self_to_param( | |||
398 | local: hir::Local, | 204 | local: hir::Local, |
399 | self_param: hir::SelfParam, | 205 | self_param: hir::SelfParam, |
400 | new_name: &str, | 206 | new_name: &str, |
401 | identifier_kind: IdentifierKind, | ||
402 | ) -> RenameResult<SourceChange> { | 207 | ) -> RenameResult<SourceChange> { |
208 | if new_name == "self" { | ||
209 | // Let's do nothing rather than complain. | ||
210 | cov_mark::hit!(rename_self_to_self); | ||
211 | return Ok(SourceChange::default()); | ||
212 | } | ||
213 | |||
214 | let identifier_kind = IdentifierKind::classify(new_name)?; | ||
215 | |||
403 | let InFile { file_id, value: self_param } = | 216 | let InFile { file_id, value: self_param } = |
404 | self_param.source(sema.db).ok_or_else(|| format_err!("cannot find function source"))?; | 217 | self_param.source(sema.db).ok_or_else(|| format_err!("cannot find function source"))?; |
405 | 218 | ||
@@ -413,7 +226,7 @@ fn rename_self_to_param( | |||
413 | let mut source_change = SourceChange::default(); | 226 | let mut source_change = SourceChange::default(); |
414 | source_change.insert_source_edit(file_id.original_file(sema.db), edit); | 227 | source_change.insert_source_edit(file_id.original_file(sema.db), edit); |
415 | source_change.extend(usages.iter().map(|(&file_id, references)| { | 228 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
416 | (file_id, source_edit_from_references(&references, def, new_name)) | 229 | (file_id, source_edit_from_references(references, def, new_name)) |
417 | })); | 230 | })); |
418 | Ok(source_change) | 231 | Ok(source_change) |
419 | } | 232 | } |
@@ -426,7 +239,7 @@ fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Opt | |||
426 | None | 239 | None |
427 | } | 240 | } |
428 | 241 | ||
429 | let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?; | 242 | let impl_def = self_param.syntax().ancestors().find_map(ast::Impl::cast)?; |
430 | let type_name = target_type_name(&impl_def)?; | 243 | let type_name = target_type_name(&impl_def)?; |
431 | 244 | ||
432 | let mut replacement_text = String::from(new_name); | 245 | let mut replacement_text = String::from(new_name); |
@@ -441,150 +254,6 @@ fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Opt | |||
441 | Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) | 254 | Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) |
442 | } | 255 | } |
443 | 256 | ||
444 | fn source_edit_from_references( | ||
445 | references: &[FileReference], | ||
446 | def: Definition, | ||
447 | new_name: &str, | ||
448 | ) -> TextEdit { | ||
449 | let mut edit = TextEdit::builder(); | ||
450 | for reference in references { | ||
451 | let (range, replacement) = match &reference.name { | ||
452 | // if the ranges differ then the node is inside a macro call, we can't really attempt | ||
453 | // to make special rewrites like shorthand syntax and such, so just rename the node in | ||
454 | // the macro input | ||
455 | ast::NameLike::NameRef(name_ref) | ||
456 | if name_ref.syntax().text_range() == reference.range => | ||
457 | { | ||
458 | source_edit_from_name_ref(name_ref, new_name, def) | ||
459 | } | ||
460 | ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => { | ||
461 | source_edit_from_name(name, new_name) | ||
462 | } | ||
463 | _ => None, | ||
464 | } | ||
465 | .unwrap_or_else(|| (reference.range, new_name.to_string())); | ||
466 | edit.replace(range, replacement); | ||
467 | } | ||
468 | edit.finish() | ||
469 | } | ||
470 | |||
471 | fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> { | ||
472 | if let Some(_) = ast::RecordPatField::for_field_name(name) { | ||
473 | if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { | ||
474 | return Some(( | ||
475 | TextRange::empty(ident_pat.syntax().text_range().start()), | ||
476 | [new_name, ": "].concat(), | ||
477 | )); | ||
478 | } | ||
479 | } | ||
480 | None | ||
481 | } | ||
482 | |||
483 | fn source_edit_from_name_ref( | ||
484 | name_ref: &ast::NameRef, | ||
485 | new_name: &str, | ||
486 | def: Definition, | ||
487 | ) -> Option<(TextRange, String)> { | ||
488 | if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { | ||
489 | let rcf_name_ref = record_field.name_ref(); | ||
490 | let rcf_expr = record_field.expr(); | ||
491 | match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) { | ||
492 | // field: init-expr, check if we can use a field init shorthand | ||
493 | (Some(field_name), Some(init)) => { | ||
494 | if field_name == *name_ref { | ||
495 | if init.text() == new_name { | ||
496 | cov_mark::hit!(test_rename_field_put_init_shorthand); | ||
497 | // same names, we can use a shorthand here instead. | ||
498 | // we do not want to erase attributes hence this range start | ||
499 | let s = field_name.syntax().text_range().start(); | ||
500 | let e = record_field.syntax().text_range().end(); | ||
501 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
502 | } | ||
503 | } else if init == *name_ref { | ||
504 | if field_name.text() == new_name { | ||
505 | cov_mark::hit!(test_rename_local_put_init_shorthand); | ||
506 | // same names, we can use a shorthand here instead. | ||
507 | // we do not want to erase attributes hence this range start | ||
508 | let s = field_name.syntax().text_range().start(); | ||
509 | let e = record_field.syntax().text_range().end(); | ||
510 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
511 | } | ||
512 | } | ||
513 | None | ||
514 | } | ||
515 | // init shorthand | ||
516 | // FIXME: instead of splitting the shorthand, recursively trigger a rename of the | ||
517 | // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 | ||
518 | (None, Some(_)) if matches!(def, Definition::Field(_)) => { | ||
519 | cov_mark::hit!(test_rename_field_in_field_shorthand); | ||
520 | let s = name_ref.syntax().text_range().start(); | ||
521 | Some((TextRange::empty(s), format!("{}: ", new_name))) | ||
522 | } | ||
523 | (None, Some(_)) if matches!(def, Definition::Local(_)) => { | ||
524 | cov_mark::hit!(test_rename_local_in_field_shorthand); | ||
525 | let s = name_ref.syntax().text_range().end(); | ||
526 | Some((TextRange::empty(s), format!(": {}", new_name))) | ||
527 | } | ||
528 | _ => None, | ||
529 | } | ||
530 | } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) { | ||
531 | let rcf_name_ref = record_field.name_ref(); | ||
532 | let rcf_pat = record_field.pat(); | ||
533 | match (rcf_name_ref, rcf_pat) { | ||
534 | // field: rename | ||
535 | (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => { | ||
536 | // field name is being renamed | ||
537 | if pat.name().map_or(false, |it| it.text() == new_name) { | ||
538 | cov_mark::hit!(test_rename_field_put_init_shorthand_pat); | ||
539 | // same names, we can use a shorthand here instead/ | ||
540 | // we do not want to erase attributes hence this range start | ||
541 | let s = field_name.syntax().text_range().start(); | ||
542 | let e = record_field.syntax().text_range().end(); | ||
543 | Some((TextRange::new(s, e), pat.to_string())) | ||
544 | } else { | ||
545 | None | ||
546 | } | ||
547 | } | ||
548 | _ => None, | ||
549 | } | ||
550 | } else { | ||
551 | None | ||
552 | } | ||
553 | } | ||
554 | |||
555 | fn source_edit_from_def( | ||
556 | sema: &Semantics<RootDatabase>, | ||
557 | def: Definition, | ||
558 | new_name: &str, | ||
559 | ) -> RenameResult<(FileId, TextEdit)> { | ||
560 | let nav = | ||
561 | def.try_to_nav(sema.db).ok_or_else(|| format_err!("No references found at position"))?; | ||
562 | |||
563 | let mut replacement_text = String::new(); | ||
564 | let mut repl_range = | ||
565 | nav.focus_range.ok_or_else(|| format_err!("No identifier available to rename"))?; | ||
566 | if let Definition::Local(local) = def { | ||
567 | if let Either::Left(pat) = local.source(sema.db).value { | ||
568 | if matches!( | ||
569 | pat.syntax().parent().and_then(ast::RecordPatField::cast), | ||
570 | Some(pat_field) if pat_field.name_ref().is_none() | ||
571 | ) { | ||
572 | replacement_text.push_str(": "); | ||
573 | replacement_text.push_str(new_name); | ||
574 | repl_range = TextRange::new( | ||
575 | pat.syntax().text_range().end(), | ||
576 | pat.syntax().text_range().end(), | ||
577 | ); | ||
578 | } | ||
579 | } | ||
580 | } | ||
581 | if replacement_text.is_empty() { | ||
582 | replacement_text.push_str(new_name); | ||
583 | } | ||
584 | let edit = TextEdit::replace(repl_range, replacement_text); | ||
585 | Ok((nav.file_id, edit)) | ||
586 | } | ||
587 | |||
588 | #[cfg(test)] | 257 | #[cfg(test)] |
589 | mod tests { | 258 | mod tests { |
590 | use expect_test::{expect, Expect}; | 259 | use expect_test::{expect, Expect}; |
@@ -659,7 +328,7 @@ mod tests { | |||
659 | fn test_prepare_rename_namelikes() { | 328 | fn test_prepare_rename_namelikes() { |
660 | check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]); | 329 | check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]); |
661 | check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"8..17: 'lifetime"#]]); | 330 | check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"8..17: 'lifetime"#]]); |
662 | check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"23..27: name"#]]); | 331 | check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"3..7: name"#]]); |
663 | } | 332 | } |
664 | 333 | ||
665 | #[test] | 334 | #[test] |
@@ -691,7 +360,7 @@ fn baz() { | |||
691 | x.0$0 = 5; | 360 | x.0$0 = 5; |
692 | } | 361 | } |
693 | "#, | 362 | "#, |
694 | expect![[r#"No identifier available to rename"#]], | 363 | expect![[r#"No references found at position"#]], |
695 | ); | 364 | ); |
696 | } | 365 | } |
697 | 366 | ||
@@ -703,7 +372,7 @@ fn foo() { | |||
703 | let x: i32$0 = 0; | 372 | let x: i32$0 = 0; |
704 | } | 373 | } |
705 | "#, | 374 | "#, |
706 | expect![[r#"Cannot rename builtin type"#]], | 375 | expect![[r#"No references found at position"#]], |
707 | ); | 376 | ); |
708 | } | 377 | } |
709 | 378 | ||
@@ -719,7 +388,7 @@ impl Foo { | |||
719 | } | 388 | } |
720 | } | 389 | } |
721 | "#, | 390 | "#, |
722 | expect![[r#"Cannot rename `Self`"#]], | 391 | expect![[r#"No references found at position"#]], |
723 | ); | 392 | ); |
724 | } | 393 | } |
725 | 394 | ||
@@ -801,7 +470,6 @@ impl Foo { | |||
801 | 470 | ||
802 | #[test] | 471 | #[test] |
803 | fn test_rename_for_local() { | 472 | fn test_rename_for_local() { |
804 | cov_mark::check!(rename_local); | ||
805 | check( | 473 | check( |
806 | "k", | 474 | "k", |
807 | r#" | 475 | r#" |
@@ -2101,4 +1769,22 @@ fn f() { <()>::BAR$0; }"#, | |||
2101 | res, | 1769 | res, |
2102 | ); | 1770 | ); |
2103 | } | 1771 | } |
1772 | |||
1773 | #[test] | ||
1774 | fn macros_are_broken_lol() { | ||
1775 | cov_mark::check!(macros_are_broken_lol); | ||
1776 | check( | ||
1777 | "lol", | ||
1778 | r#" | ||
1779 | macro_rules! m { () => { fn f() {} } } | ||
1780 | m!(); | ||
1781 | fn main() { f$0() } | ||
1782 | "#, | ||
1783 | r#" | ||
1784 | macro_rules! m { () => { fn f() {} } } | ||
1785 | lol | ||
1786 | fn main() { lol() } | ||
1787 | "#, | ||
1788 | ) | ||
1789 | } | ||
2104 | } | 1790 | } |
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 552054951..03faabadc 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs | |||
@@ -158,7 +158,7 @@ fn find_related_tests( | |||
158 | search_scope: Option<SearchScope>, | 158 | search_scope: Option<SearchScope>, |
159 | tests: &mut FxHashSet<Runnable>, | 159 | tests: &mut FxHashSet<Runnable>, |
160 | ) { | 160 | ) { |
161 | if let Some(refs) = references::find_all_refs(&sema, position, search_scope) { | 161 | if let Some(refs) = references::find_all_refs(sema, position, search_scope) { |
162 | for (file_id, refs) in refs.references { | 162 | for (file_id, refs) in refs.references { |
163 | let file = sema.parse(file_id); | 163 | let file = sema.parse(file_id); |
164 | let file = file.syntax(); | 164 | let file = file.syntax(); |
@@ -169,10 +169,10 @@ fn find_related_tests( | |||
169 | }); | 169 | }); |
170 | 170 | ||
171 | for fn_def in functions { | 171 | for fn_def in functions { |
172 | if let Some(runnable) = as_test_runnable(&sema, &fn_def) { | 172 | if let Some(runnable) = as_test_runnable(sema, &fn_def) { |
173 | // direct test | 173 | // direct test |
174 | tests.insert(runnable); | 174 | tests.insert(runnable); |
175 | } else if let Some(module) = parent_test_module(&sema, &fn_def) { | 175 | } else if let Some(module) = parent_test_module(sema, &fn_def) { |
176 | // indirect test | 176 | // indirect test |
177 | find_related_tests_in_module(sema, &fn_def, &module, tests); | 177 | find_related_tests_in_module(sema, &fn_def, &module, tests); |
178 | } | 178 | } |
@@ -203,7 +203,7 @@ fn find_related_tests_in_module( | |||
203 | } | 203 | } |
204 | 204 | ||
205 | fn as_test_runnable(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> { | 205 | fn as_test_runnable(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> { |
206 | if test_related_attribute(&fn_def).is_some() { | 206 | if test_related_attribute(fn_def).is_some() { |
207 | let function = sema.to_def(fn_def)?; | 207 | let function = sema.to_def(fn_def)?; |
208 | runnable_fn(sema, function) | 208 | runnable_fn(sema, function) |
209 | } else { | 209 | } else { |
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index b03f1c71f..e186b82b7 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -323,7 +323,7 @@ fn traverse( | |||
323 | if let Some(token) = element.as_token().cloned().and_then(ast::String::cast) { | 323 | if let Some(token) = element.as_token().cloned().and_then(ast::String::cast) { |
324 | if token.is_raw() { | 324 | if token.is_raw() { |
325 | let expanded = element_to_highlight.as_token().unwrap().clone(); | 325 | let expanded = element_to_highlight.as_token().unwrap().clone(); |
326 | if inject::ra_fixture(hl, &sema, token, expanded).is_some() { | 326 | if inject::ra_fixture(hl, sema, token, expanded).is_some() { |
327 | continue; | 327 | continue; |
328 | } | 328 | } |
329 | } | 329 | } |
@@ -334,7 +334,7 @@ fn traverse( | |||
334 | } | 334 | } |
335 | 335 | ||
336 | if let Some((mut highlight, binding_hash)) = highlight::element( | 336 | if let Some((mut highlight, binding_hash)) = highlight::element( |
337 | &sema, | 337 | sema, |
338 | krate, | 338 | krate, |
339 | &mut bindings_shadow_count, | 339 | &mut bindings_shadow_count, |
340 | syntactic_name_ref_highlighting, | 340 | syntactic_name_ref_highlighting, |
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 9503c936d..6834fe11a 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs | |||
@@ -48,7 +48,13 @@ pub(super) fn element( | |||
48 | match name_kind { | 48 | match name_kind { |
49 | Some(NameClass::ExternCrate(_)) => SymbolKind::Module.into(), | 49 | Some(NameClass::ExternCrate(_)) => SymbolKind::Module.into(), |
50 | Some(NameClass::Definition(def)) => { | 50 | Some(NameClass::Definition(def)) => { |
51 | highlight_def(db, krate, def) | HlMod::Definition | 51 | let mut h = highlight_def(db, krate, def) | HlMod::Definition; |
52 | if let Definition::ModuleDef(hir::ModuleDef::Trait(trait_)) = &def { | ||
53 | if trait_.is_unsafe(db) { | ||
54 | h |= HlMod::Unsafe; | ||
55 | } | ||
56 | } | ||
57 | h | ||
52 | } | 58 | } |
53 | Some(NameClass::ConstReference(def)) => highlight_def(db, krate, def), | 59 | Some(NameClass::ConstReference(def)) => highlight_def(db, krate, def), |
54 | Some(NameClass::PatFieldShorthand { field_ref, .. }) => { | 60 | Some(NameClass::PatFieldShorthand { field_ref, .. }) => { |
@@ -87,20 +93,34 @@ pub(super) fn element( | |||
87 | 93 | ||
88 | let mut h = highlight_def(db, krate, def); | 94 | let mut h = highlight_def(db, krate, def); |
89 | 95 | ||
90 | if let Definition::Local(local) = &def { | 96 | match def { |
91 | if is_consumed_lvalue(name_ref.syntax().clone().into(), local, db) { | 97 | Definition::Local(local) |
98 | if is_consumed_lvalue( | ||
99 | name_ref.syntax().clone().into(), | ||
100 | &local, | ||
101 | db, | ||
102 | ) => | ||
103 | { | ||
92 | h |= HlMod::Consuming; | 104 | h |= HlMod::Consuming; |
93 | } | 105 | } |
94 | } | 106 | Definition::ModuleDef(hir::ModuleDef::Trait(trait_)) |
95 | 107 | if trait_.is_unsafe(db) => | |
96 | if let Some(parent) = name_ref.syntax().parent() { | 108 | { |
97 | if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) { | 109 | if ast::Impl::for_trait_name_ref(&name_ref).is_some() { |
98 | if let Definition::Field(field) = def { | 110 | h |= HlMod::Unsafe; |
99 | if let hir::VariantDef::Union(_) = field.parent_def(db) { | 111 | } |
100 | h |= HlMod::Unsafe; | 112 | } |
113 | Definition::Field(field) => { | ||
114 | if let Some(parent) = name_ref.syntax().parent() { | ||
115 | if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) { | ||
116 | if let hir::VariantDef::Union(_) = field.parent_def(db) | ||
117 | { | ||
118 | h |= HlMod::Unsafe; | ||
119 | } | ||
101 | } | 120 | } |
102 | } | 121 | } |
103 | } | 122 | } |
123 | _ => (), | ||
104 | } | 124 | } |
105 | 125 | ||
106 | h | 126 | h |
@@ -131,6 +151,9 @@ pub(super) fn element( | |||
131 | } | 151 | } |
132 | STRING | BYTE_STRING => HlTag::StringLiteral.into(), | 152 | STRING | BYTE_STRING => HlTag::StringLiteral.into(), |
133 | ATTR => HlTag::Attribute.into(), | 153 | ATTR => HlTag::Attribute.into(), |
154 | INT_NUMBER if element.ancestors().nth(1).map_or(false, |it| it.kind() == FIELD_EXPR) => { | ||
155 | SymbolKind::Field.into() | ||
156 | } | ||
134 | INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(), | 157 | INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(), |
135 | BYTE => HlTag::ByteLiteral.into(), | 158 | BYTE => HlTag::ByteLiteral.into(), |
136 | CHAR => HlTag::CharLiteral.into(), | 159 | CHAR => HlTag::CharLiteral.into(), |
@@ -351,15 +374,7 @@ fn highlight_def(db: &RootDatabase, krate: Option<hir::Crate>, def: Definition) | |||
351 | 374 | ||
352 | h | 375 | h |
353 | } | 376 | } |
354 | hir::ModuleDef::Trait(trait_) => { | 377 | hir::ModuleDef::Trait(_) => Highlight::new(HlTag::Symbol(SymbolKind::Trait)), |
355 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Trait)); | ||
356 | |||
357 | if trait_.is_unsafe(db) { | ||
358 | h |= HlMod::Unsafe; | ||
359 | } | ||
360 | |||
361 | h | ||
362 | } | ||
363 | hir::ModuleDef::TypeAlias(type_) => { | 378 | hir::ModuleDef::TypeAlias(type_) => { |
364 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias)); | 379 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias)); |
365 | 380 | ||
@@ -446,12 +461,12 @@ fn highlight_method_call( | |||
446 | krate: Option<hir::Crate>, | 461 | krate: Option<hir::Crate>, |
447 | method_call: &ast::MethodCallExpr, | 462 | method_call: &ast::MethodCallExpr, |
448 | ) -> Option<Highlight> { | 463 | ) -> Option<Highlight> { |
449 | let func = sema.resolve_method_call(&method_call)?; | 464 | let func = sema.resolve_method_call(method_call)?; |
450 | 465 | ||
451 | let mut h = SymbolKind::Function.into(); | 466 | let mut h = SymbolKind::Function.into(); |
452 | h |= HlMod::Associated; | 467 | h |= HlMod::Associated; |
453 | 468 | ||
454 | if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) { | 469 | if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(method_call) { |
455 | h |= HlMod::Unsafe; | 470 | h |= HlMod::Unsafe; |
456 | } | 471 | } |
457 | if func.is_async(sema.db) { | 472 | if func.is_async(sema.db) { |
@@ -523,11 +538,9 @@ fn highlight_name_ref_by_syntax( | |||
523 | }; | 538 | }; |
524 | 539 | ||
525 | match parent.kind() { | 540 | match parent.kind() { |
526 | METHOD_CALL_EXPR => { | 541 | METHOD_CALL_EXPR => ast::MethodCallExpr::cast(parent) |
527 | return ast::MethodCallExpr::cast(parent) | 542 | .and_then(|it| highlight_method_call(sema, krate, &it)) |
528 | .and_then(|it| highlight_method_call(sema, krate, &it)) | 543 | .unwrap_or_else(|| SymbolKind::Function.into()), |
529 | .unwrap_or_else(|| SymbolKind::Function.into()); | ||
530 | } | ||
531 | FIELD_EXPR => { | 544 | FIELD_EXPR => { |
532 | let h = HlTag::Symbol(SymbolKind::Field); | 545 | let h = HlTag::Symbol(SymbolKind::Field); |
533 | let is_union = ast::FieldExpr::cast(parent) | 546 | let is_union = ast::FieldExpr::cast(parent) |
diff --git a/crates/ide/src/syntax_highlighting/html.rs b/crates/ide/src/syntax_highlighting/html.rs index 5327af845..21376a7ae 100644 --- a/crates/ide/src/syntax_highlighting/html.rs +++ b/crates/ide/src/syntax_highlighting/html.rs | |||
@@ -23,7 +23,7 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo | |||
23 | let hl_ranges = highlight(db, file_id, None, false); | 23 | let hl_ranges = highlight(db, file_id, None, false); |
24 | let text = parse.tree().syntax().to_string(); | 24 | let text = parse.tree().syntax().to_string(); |
25 | let mut buf = String::new(); | 25 | let mut buf = String::new(); |
26 | buf.push_str(&STYLE); | 26 | buf.push_str(STYLE); |
27 | buf.push_str("<pre><code>"); | 27 | buf.push_str("<pre><code>"); |
28 | for r in &hl_ranges { | 28 | for r in &hl_ranges { |
29 | let chunk = html_escape(&text[r.range]); | 29 | let chunk = html_escape(&text[r.range]); |
@@ -67,6 +67,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
67 | .field { color: #94BFF3; } | 67 | .field { color: #94BFF3; } |
68 | .function { color: #93E0E3; } | 68 | .function { color: #93E0E3; } |
69 | .function.unsafe { color: #BC8383; } | 69 | .function.unsafe { color: #BC8383; } |
70 | .trait.unsafe { color: #BC8383; } | ||
70 | .operator.unsafe { color: #BC8383; } | 71 | .operator.unsafe { color: #BC8383; } |
71 | .parameter { color: #94BFF3; } | 72 | .parameter { color: #94BFF3; } |
72 | .text { color: #DCDCCC; } | 73 | .text { color: #DCDCCC; } |
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index 4269d339e..ec43c8579 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs | |||
@@ -23,7 +23,7 @@ pub(super) fn ra_fixture( | |||
23 | literal: ast::String, | 23 | literal: ast::String, |
24 | expanded: SyntaxToken, | 24 | expanded: SyntaxToken, |
25 | ) -> Option<()> { | 25 | ) -> Option<()> { |
26 | let active_parameter = ActiveParameter::at_token(&sema, expanded)?; | 26 | let active_parameter = ActiveParameter::at_token(sema, expanded)?; |
27 | if !active_parameter.ident().map_or(false, |name| name.text().starts_with("ra_fixture")) { | 27 | if !active_parameter.ident().map_or(false, |name| name.text().starts_with("ra_fixture")) { |
28 | return None; | 28 | return None; |
29 | } | 29 | } |
@@ -124,7 +124,7 @@ pub(super) fn doc_comment( | |||
124 | } | 124 | } |
125 | 125 | ||
126 | for attr in attributes.by_key("doc").attrs() { | 126 | for attr in attributes.by_key("doc").attrs() { |
127 | let InFile { file_id, value: src } = attrs_source_map.source_of(&attr); | 127 | let InFile { file_id, value: src } = attrs_source_map.source_of(attr); |
128 | if file_id != node.file_id { | 128 | if file_id != node.file_id { |
129 | continue; | 129 | continue; |
130 | } | 130 | } |
@@ -232,7 +232,7 @@ fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option<ast::Stri | |||
232 | string.text().get(1..string.text().len() - 1).map_or(false, |it| it == text) | 232 | string.text().get(1..string.text().len() - 1).map_or(false, |it| it == text) |
233 | }) | 233 | }) |
234 | } | 234 | } |
235 | _ => return None, | 235 | _ => None, |
236 | } | 236 | } |
237 | } | 237 | } |
238 | 238 | ||
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html index a0ea1db34..4e85f7c0b 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html | |||
@@ -15,6 +15,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
15 | .field { color: #94BFF3; } | 15 | .field { color: #94BFF3; } |
16 | .function { color: #93E0E3; } | 16 | .function { color: #93E0E3; } |
17 | .function.unsafe { color: #BC8383; } | 17 | .function.unsafe { color: #BC8383; } |
18 | .trait.unsafe { color: #BC8383; } | ||
18 | .operator.unsafe { color: #BC8383; } | 19 | .operator.unsafe { color: #BC8383; } |
19 | .parameter { color: #94BFF3; } | 20 | .parameter { color: #94BFF3; } |
20 | .text { color: #DCDCCC; } | 21 | .text { color: #DCDCCC; } |
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html index 921a956e6..79a285107 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html | |||
@@ -15,6 +15,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
15 | .field { color: #94BFF3; } | 15 | .field { color: #94BFF3; } |
16 | .function { color: #93E0E3; } | 16 | .function { color: #93E0E3; } |
17 | .function.unsafe { color: #BC8383; } | 17 | .function.unsafe { color: #BC8383; } |
18 | .trait.unsafe { color: #BC8383; } | ||
18 | .operator.unsafe { color: #BC8383; } | 19 | .operator.unsafe { color: #BC8383; } |
19 | .parameter { color: #94BFF3; } | 20 | .parameter { color: #94BFF3; } |
20 | .text { color: #DCDCCC; } | 21 | .text { color: #DCDCCC; } |
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html index ca9bb1e7d..13f589cc0 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html | |||
@@ -15,6 +15,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
15 | .field { color: #94BFF3; } | 15 | .field { color: #94BFF3; } |
16 | .function { color: #93E0E3; } | 16 | .function { color: #93E0E3; } |
17 | .function.unsafe { color: #BC8383; } | 17 | .function.unsafe { color: #BC8383; } |
18 | .trait.unsafe { color: #BC8383; } | ||
18 | .operator.unsafe { color: #BC8383; } | 19 | .operator.unsafe { color: #BC8383; } |
19 | .parameter { color: #94BFF3; } | 20 | .parameter { color: #94BFF3; } |
20 | .text { color: #DCDCCC; } | 21 | .text { color: #DCDCCC; } |
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html index 6202a03ce..50df376ae 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html | |||
@@ -15,6 +15,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
15 | .field { color: #94BFF3; } | 15 | .field { color: #94BFF3; } |
16 | .function { color: #93E0E3; } | 16 | .function { color: #93E0E3; } |
17 | .function.unsafe { color: #BC8383; } | 17 | .function.unsafe { color: #BC8383; } |
18 | .trait.unsafe { color: #BC8383; } | ||
18 | .operator.unsafe { color: #BC8383; } | 19 | .operator.unsafe { color: #BC8383; } |
19 | .parameter { color: #94BFF3; } | 20 | .parameter { color: #94BFF3; } |
20 | .text { color: #DCDCCC; } | 21 | .text { color: #DCDCCC; } |
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html index e860d713e..96cb09642 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html | |||
@@ -15,6 +15,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
15 | .field { color: #94BFF3; } | 15 | .field { color: #94BFF3; } |
16 | .function { color: #93E0E3; } | 16 | .function { color: #93E0E3; } |
17 | .function.unsafe { color: #BC8383; } | 17 | .function.unsafe { color: #BC8383; } |
18 | .trait.unsafe { color: #BC8383; } | ||
18 | .operator.unsafe { color: #BC8383; } | 19 | .operator.unsafe { color: #BC8383; } |
19 | .parameter { color: #94BFF3; } | 20 | .parameter { color: #94BFF3; } |
20 | .text { color: #DCDCCC; } | 21 | .text { color: #DCDCCC; } |
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html index 68165bdbf..55453468b 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html | |||
@@ -15,6 +15,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
15 | .field { color: #94BFF3; } | 15 | .field { color: #94BFF3; } |
16 | .function { color: #93E0E3; } | 16 | .function { color: #93E0E3; } |
17 | .function.unsafe { color: #BC8383; } | 17 | .function.unsafe { color: #BC8383; } |
18 | .trait.unsafe { color: #BC8383; } | ||
18 | .operator.unsafe { color: #BC8383; } | 19 | .operator.unsafe { color: #BC8383; } |
19 | .parameter { color: #94BFF3; } | 20 | .parameter { color: #94BFF3; } |
20 | .text { color: #DCDCCC; } | 21 | .text { color: #DCDCCC; } |
@@ -61,6 +62,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
61 | <span class="field declaration">a</span><span class="colon">:</span> <span class="builtin_type">u16</span><span class="comma">,</span> | 62 | <span class="field declaration">a</span><span class="colon">:</span> <span class="builtin_type">u16</span><span class="comma">,</span> |
62 | <span class="brace">}</span> | 63 | <span class="brace">}</span> |
63 | 64 | ||
65 | <span class="keyword unsafe">unsafe</span> <span class="keyword">trait</span> <span class="trait declaration unsafe">UnsafeTrait</span> <span class="brace">{</span><span class="brace">}</span> | ||
66 | <span class="keyword unsafe">unsafe</span> <span class="keyword">impl</span> <span class="trait unsafe">UnsafeTrait</span> <span class="keyword">for</span> <span class="struct">Packed</span> <span class="brace">{</span><span class="brace">}</span> | ||
67 | |||
68 | <span class="keyword">fn</span> <span class="function declaration">require_unsafe_trait</span><span class="angle"><</span><span class="type_param declaration">T</span><span class="colon">:</span> <span class="trait">UnsafeTrait</span><span class="angle">></span><span class="parenthesis">(</span><span class="punctuation">_</span><span class="colon">:</span> <span class="type_param">T</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> | ||
69 | |||
64 | <span class="keyword">trait</span> <span class="trait declaration">DoTheAutoref</span> <span class="brace">{</span> | 70 | <span class="keyword">trait</span> <span class="trait declaration">DoTheAutoref</span> <span class="brace">{</span> |
65 | <span class="keyword">fn</span> <span class="function associated declaration trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span><span class="semicolon">;</span> | 71 | <span class="keyword">fn</span> <span class="function associated declaration trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span><span class="semicolon">;</span> |
66 | <span class="brace">}</span> | 72 | <span class="brace">}</span> |
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html index a7b5c3b89..9232cf905 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html | |||
@@ -15,6 +15,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
15 | .field { color: #94BFF3; } | 15 | .field { color: #94BFF3; } |
16 | .function { color: #93E0E3; } | 16 | .function { color: #93E0E3; } |
17 | .function.unsafe { color: #BC8383; } | 17 | .function.unsafe { color: #BC8383; } |
18 | .trait.unsafe { color: #BC8383; } | ||
18 | .operator.unsafe { color: #BC8383; } | 19 | .operator.unsafe { color: #BC8383; } |
19 | .parameter { color: #94BFF3; } | 20 | .parameter { color: #94BFF3; } |
20 | .text { color: #DCDCCC; } | 21 | .text { color: #DCDCCC; } |
@@ -215,8 +216,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
215 | <span class="keyword">let</span> <span class="variable callable declaration">a</span> <span class="operator">=</span> <span class="punctuation">|</span><span class="value_param declaration">x</span><span class="punctuation">|</span> <span class="value_param">x</span><span class="semicolon">;</span> | 216 | <span class="keyword">let</span> <span class="variable callable declaration">a</span> <span class="operator">=</span> <span class="punctuation">|</span><span class="value_param declaration">x</span><span class="punctuation">|</span> <span class="value_param">x</span><span class="semicolon">;</span> |
216 | <span class="keyword">let</span> <span class="variable callable declaration">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="function associated">baz</span><span class="semicolon">;</span> | 217 | <span class="keyword">let</span> <span class="variable callable declaration">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="function associated">baz</span><span class="semicolon">;</span> |
217 | 218 | ||
218 | <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="semicolon">;</span> | 219 | <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="parenthesis">(</span><span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="comma">,</span><span class="parenthesis">)</span><span class="semicolon">;</span> |
219 | <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="semicolon">;</span> | 220 | <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="operator">.</span><span class="field">0</span><span class="semicolon">;</span> |
220 | 221 | ||
221 | <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="logical">!</span><span class="bool_literal">true</span><span class="semicolon">;</span> | 222 | <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="logical">!</span><span class="bool_literal">true</span><span class="semicolon">;</span> |
222 | 223 | ||
diff --git a/crates/ide/src/syntax_highlighting/test_data/injection.html b/crates/ide/src/syntax_highlighting/test_data/injection.html index 9ab46d05c..082837328 100644 --- a/crates/ide/src/syntax_highlighting/test_data/injection.html +++ b/crates/ide/src/syntax_highlighting/test_data/injection.html | |||
@@ -15,6 +15,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
15 | .field { color: #94BFF3; } | 15 | .field { color: #94BFF3; } |
16 | .function { color: #93E0E3; } | 16 | .function { color: #93E0E3; } |
17 | .function.unsafe { color: #BC8383; } | 17 | .function.unsafe { color: #BC8383; } |
18 | .trait.unsafe { color: #BC8383; } | ||
18 | .operator.unsafe { color: #BC8383; } | 19 | .operator.unsafe { color: #BC8383; } |
19 | .parameter { color: #94BFF3; } | 20 | .parameter { color: #94BFF3; } |
20 | .text { color: #DCDCCC; } | 21 | .text { color: #DCDCCC; } |
diff --git a/crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html b/crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html index 666b0b228..763917714 100644 --- a/crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html +++ b/crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html | |||
@@ -15,6 +15,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
15 | .field { color: #94BFF3; } | 15 | .field { color: #94BFF3; } |
16 | .function { color: #93E0E3; } | 16 | .function { color: #93E0E3; } |
17 | .function.unsafe { color: #BC8383; } | 17 | .function.unsafe { color: #BC8383; } |
18 | .trait.unsafe { color: #BC8383; } | ||
18 | .operator.unsafe { color: #BC8383; } | 19 | .operator.unsafe { color: #BC8383; } |
19 | .parameter { color: #94BFF3; } | 20 | .parameter { color: #94BFF3; } |
20 | .text { color: #DCDCCC; } | 21 | .text { color: #DCDCCC; } |
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index 6ad2a362a..4f0b1ce85 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs | |||
@@ -189,8 +189,8 @@ fn main() { | |||
189 | let a = |x| x; | 189 | let a = |x| x; |
190 | let bar = Foo::baz; | 190 | let bar = Foo::baz; |
191 | 191 | ||
192 | let baz = -42; | 192 | let baz = (-42,); |
193 | let baz = -baz; | 193 | let baz = -baz.0; |
194 | 194 | ||
195 | let _ = !true; | 195 | let _ = !true; |
196 | 196 | ||
@@ -527,6 +527,11 @@ struct Packed { | |||
527 | a: u16, | 527 | a: u16, |
528 | } | 528 | } |
529 | 529 | ||
530 | unsafe trait UnsafeTrait {} | ||
531 | unsafe impl UnsafeTrait for Packed {} | ||
532 | |||
533 | fn require_unsafe_trait<T: UnsafeTrait>(_: T) {} | ||
534 | |||
530 | trait DoTheAutoref { | 535 | trait DoTheAutoref { |
531 | fn calls_autoref(&self); | 536 | fn calls_autoref(&self); |
532 | } | 537 | } |
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs index 81c4d95b1..5cba9d11d 100644 --- a/crates/ide/src/typing/on_enter.rs +++ b/crates/ide/src/typing/on_enter.rs | |||
@@ -88,12 +88,12 @@ fn on_enter_in_comment( | |||
88 | if comment.text().ends_with(' ') { | 88 | if comment.text().ends_with(' ') { |
89 | cov_mark::hit!(continues_end_of_line_comment_with_space); | 89 | cov_mark::hit!(continues_end_of_line_comment_with_space); |
90 | remove_trailing_whitespace = true; | 90 | remove_trailing_whitespace = true; |
91 | } else if !followed_by_comment(&comment) { | 91 | } else if !followed_by_comment(comment) { |
92 | return None; | 92 | return None; |
93 | } | 93 | } |
94 | } | 94 | } |
95 | 95 | ||
96 | let indent = node_indent(&file, comment.syntax())?; | 96 | let indent = node_indent(file, comment.syntax())?; |
97 | let inserted = format!("\n{}{} $0", indent, prefix); | 97 | let inserted = format!("\n{}{} $0", indent, prefix); |
98 | let delete = if remove_trailing_whitespace { | 98 | let delete = if remove_trailing_whitespace { |
99 | let trimmed_len = comment.text().trim_end().len() as u32; | 99 | let trimmed_len = comment.text().trim_end().len() as u32; |
@@ -188,7 +188,7 @@ mod tests { | |||
188 | use crate::fixture; | 188 | use crate::fixture; |
189 | 189 | ||
190 | fn apply_on_enter(before: &str) -> Option<String> { | 190 | fn apply_on_enter(before: &str) -> Option<String> { |
191 | let (analysis, position) = fixture::position(&before); | 191 | let (analysis, position) = fixture::position(before); |
192 | let result = analysis.on_enter(position).unwrap()?; | 192 | let result = analysis.on_enter(position).unwrap()?; |
193 | 193 | ||
194 | let mut actual = analysis.file_text(position.file_id).unwrap().to_string(); | 194 | let mut actual = analysis.file_text(position.file_id).unwrap().to_string(); |