diff options
author | Aleksey Kladov <[email protected]> | 2020-04-17 12:06:02 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2020-04-17 12:56:38 +0100 |
commit | a8196ffe8466aa60dec56e77c2da717793c0debe (patch) | |
tree | 03869b8175d5325de1baa95e0a385d2823b5946e | |
parent | 302bf97bbf1855e3c7def9ab4f9f3d338be5e3b7 (diff) |
Correctly highlight ranges of diagnostics from macros
closes #2799
-rw-r--r-- | crates/ra_hir/src/semantics.rs | 8 | ||||
-rw-r--r-- | crates/ra_hir_def/src/diagnostics.rs | 6 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/diagnostics.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/diagnostics.rs | 22 | ||||
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 72 |
5 files changed, 89 insertions, 21 deletions
diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs index 2707e422d..0b477f0e9 100644 --- a/crates/ra_hir/src/semantics.rs +++ b/crates/ra_hir/src/semantics.rs | |||
@@ -20,6 +20,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; | |||
20 | 20 | ||
21 | use crate::{ | 21 | use crate::{ |
22 | db::HirDatabase, | 22 | db::HirDatabase, |
23 | diagnostics::Diagnostic, | ||
23 | semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, | 24 | semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, |
24 | source_analyzer::{resolve_hir_path, SourceAnalyzer}, | 25 | source_analyzer::{resolve_hir_path, SourceAnalyzer}, |
25 | AssocItem, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef, Name, | 26 | AssocItem, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef, Name, |
@@ -126,6 +127,13 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { | |||
126 | original_range(self.db, node.as_ref()) | 127 | original_range(self.db, node.as_ref()) |
127 | } | 128 | } |
128 | 129 | ||
130 | pub fn diagnostics_range(&self, diagnostics: &dyn Diagnostic) -> FileRange { | ||
131 | let src = diagnostics.source(); | ||
132 | let root = self.db.parse_or_expand(src.file_id).unwrap(); | ||
133 | let node = src.value.to_node(&root); | ||
134 | original_range(self.db, src.with_value(&node)) | ||
135 | } | ||
136 | |||
129 | pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ { | 137 | pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ { |
130 | let node = self.find_file(node); | 138 | let node = self.find_file(node); |
131 | node.ancestors_with_macros(self.db).map(|it| it.value) | 139 | node.ancestors_with_macros(self.db).map(|it| it.value) |
diff --git a/crates/ra_hir_def/src/diagnostics.rs b/crates/ra_hir_def/src/diagnostics.rs index dbaf4deef..2ee28fbaa 100644 --- a/crates/ra_hir_def/src/diagnostics.rs +++ b/crates/ra_hir_def/src/diagnostics.rs | |||
@@ -20,11 +20,11 @@ impl Diagnostic for UnresolvedModule { | |||
20 | fn message(&self) -> String { | 20 | fn message(&self) -> String { |
21 | "unresolved module".to_string() | 21 | "unresolved module".to_string() |
22 | } | 22 | } |
23 | fn highlight_range(&self) -> TextRange { | 23 | fn highlight_range(&self) -> InFile<TextRange> { |
24 | self.highlight_range | 24 | InFile::new(self.file, self.highlight_range) |
25 | } | 25 | } |
26 | fn source(&self) -> InFile<SyntaxNodePtr> { | 26 | fn source(&self) -> InFile<SyntaxNodePtr> { |
27 | InFile { file_id: self.file, value: self.decl.clone().into() } | 27 | InFile::new(self.file, self.decl.clone().into()) |
28 | } | 28 | } |
29 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | 29 | fn as_any(&self) -> &(dyn Any + Send + 'static) { |
30 | self | 30 | self |
diff --git a/crates/ra_hir_expand/src/diagnostics.rs b/crates/ra_hir_expand/src/diagnostics.rs index 714e700f7..813fbf0e2 100644 --- a/crates/ra_hir_expand/src/diagnostics.rs +++ b/crates/ra_hir_expand/src/diagnostics.rs | |||
@@ -22,7 +22,7 @@ use crate::{db::AstDatabase, InFile}; | |||
22 | 22 | ||
23 | pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { | 23 | pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { |
24 | fn message(&self) -> String; | 24 | fn message(&self) -> String; |
25 | fn highlight_range(&self) -> TextRange; | 25 | fn highlight_range(&self) -> InFile<TextRange>; |
26 | fn source(&self) -> InFile<SyntaxNodePtr>; | 26 | fn source(&self) -> InFile<SyntaxNodePtr>; |
27 | fn as_any(&self) -> &(dyn Any + Send + 'static); | 27 | fn as_any(&self) -> &(dyn Any + Send + 'static); |
28 | } | 28 | } |
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs index da85bd082..018c2ad3f 100644 --- a/crates/ra_hir_ty/src/diagnostics.rs +++ b/crates/ra_hir_ty/src/diagnostics.rs | |||
@@ -21,12 +21,12 @@ impl Diagnostic for NoSuchField { | |||
21 | "no such field".to_string() | 21 | "no such field".to_string() |
22 | } | 22 | } |
23 | 23 | ||
24 | fn highlight_range(&self) -> TextRange { | 24 | fn highlight_range(&self) -> InFile<TextRange> { |
25 | self.highlight_range | 25 | InFile::new(self.file, self.highlight_range) |
26 | } | 26 | } |
27 | 27 | ||
28 | fn source(&self) -> InFile<SyntaxNodePtr> { | 28 | fn source(&self) -> InFile<SyntaxNodePtr> { |
29 | InFile { file_id: self.file, value: self.field.clone().into() } | 29 | InFile::new(self.file, self.field.clone().into()) |
30 | } | 30 | } |
31 | 31 | ||
32 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | 32 | fn as_any(&self) -> &(dyn Any + Send + 'static) { |
@@ -50,8 +50,8 @@ impl Diagnostic for MissingFields { | |||
50 | } | 50 | } |
51 | buf | 51 | buf |
52 | } | 52 | } |
53 | fn highlight_range(&self) -> TextRange { | 53 | fn highlight_range(&self) -> InFile<TextRange> { |
54 | self.highlight_range | 54 | InFile::new(self.file, self.highlight_range) |
55 | } | 55 | } |
56 | 56 | ||
57 | fn source(&self) -> InFile<SyntaxNodePtr> { | 57 | fn source(&self) -> InFile<SyntaxNodePtr> { |
@@ -88,8 +88,8 @@ impl Diagnostic for MissingPatFields { | |||
88 | } | 88 | } |
89 | buf | 89 | buf |
90 | } | 90 | } |
91 | fn highlight_range(&self) -> TextRange { | 91 | fn highlight_range(&self) -> InFile<TextRange> { |
92 | self.highlight_range | 92 | InFile::new(self.file, self.highlight_range) |
93 | } | 93 | } |
94 | fn source(&self) -> InFile<SyntaxNodePtr> { | 94 | fn source(&self) -> InFile<SyntaxNodePtr> { |
95 | InFile { file_id: self.file, value: self.field_list.clone().into() } | 95 | InFile { file_id: self.file, value: self.field_list.clone().into() } |
@@ -111,8 +111,8 @@ impl Diagnostic for MissingMatchArms { | |||
111 | fn message(&self) -> String { | 111 | fn message(&self) -> String { |
112 | String::from("Missing match arm") | 112 | String::from("Missing match arm") |
113 | } | 113 | } |
114 | fn highlight_range(&self) -> TextRange { | 114 | fn highlight_range(&self) -> InFile<TextRange> { |
115 | self.highlight_range | 115 | InFile::new(self.file, self.highlight_range) |
116 | } | 116 | } |
117 | fn source(&self) -> InFile<SyntaxNodePtr> { | 117 | fn source(&self) -> InFile<SyntaxNodePtr> { |
118 | InFile { file_id: self.file, value: self.match_expr.clone().into() } | 118 | InFile { file_id: self.file, value: self.match_expr.clone().into() } |
@@ -133,8 +133,8 @@ impl Diagnostic for MissingOkInTailExpr { | |||
133 | fn message(&self) -> String { | 133 | fn message(&self) -> String { |
134 | "wrap return expression in Ok".to_string() | 134 | "wrap return expression in Ok".to_string() |
135 | } | 135 | } |
136 | fn highlight_range(&self) -> TextRange { | 136 | fn highlight_range(&self) -> InFile<TextRange> { |
137 | self.highlight_range | 137 | InFile::new(self.file, self.highlight_range) |
138 | } | 138 | } |
139 | fn source(&self) -> InFile<SyntaxNodePtr> { | 139 | fn source(&self) -> InFile<SyntaxNodePtr> { |
140 | InFile { file_id: self.file, value: self.expr.clone().into() } | 140 | InFile { file_id: self.file, value: self.expr.clone().into() } |
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index 901ad104c..e7e201709 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -1,4 +1,8 @@ | |||
1 | //! FIXME: write short doc here | 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. | ||
2 | 6 | ||
3 | use std::cell::RefCell; | 7 | use std::cell::RefCell; |
4 | 8 | ||
@@ -46,7 +50,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
46 | let mut sink = DiagnosticSink::new(|d| { | 50 | let mut sink = DiagnosticSink::new(|d| { |
47 | res.borrow_mut().push(Diagnostic { | 51 | res.borrow_mut().push(Diagnostic { |
48 | message: d.message(), | 52 | message: d.message(), |
49 | range: d.highlight_range(), | 53 | range: sema.diagnostics_range(d).range, |
50 | severity: Severity::Error, | 54 | severity: Severity::Error, |
51 | fix: None, | 55 | fix: None, |
52 | }) | 56 | }) |
@@ -62,7 +66,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
62 | let create_file = FileSystemEdit::CreateFile { source_root, path }; | 66 | let create_file = FileSystemEdit::CreateFile { source_root, path }; |
63 | let fix = SourceChange::file_system_edit("create module", create_file); | 67 | let fix = SourceChange::file_system_edit("create module", create_file); |
64 | res.borrow_mut().push(Diagnostic { | 68 | res.borrow_mut().push(Diagnostic { |
65 | range: d.highlight_range(), | 69 | range: sema.diagnostics_range(d).range, |
66 | message: d.message(), | 70 | message: d.message(), |
67 | severity: Severity::Error, | 71 | severity: Severity::Error, |
68 | fix: Some(fix), | 72 | fix: Some(fix), |
@@ -95,7 +99,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
95 | }; | 99 | }; |
96 | 100 | ||
97 | res.borrow_mut().push(Diagnostic { | 101 | res.borrow_mut().push(Diagnostic { |
98 | range: d.highlight_range(), | 102 | range: sema.diagnostics_range(d).range, |
99 | message: d.message(), | 103 | message: d.message(), |
100 | severity: Severity::Error, | 104 | severity: Severity::Error, |
101 | fix, | 105 | fix, |
@@ -103,7 +107,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
103 | }) | 107 | }) |
104 | .on::<hir::diagnostics::MissingMatchArms, _>(|d| { | 108 | .on::<hir::diagnostics::MissingMatchArms, _>(|d| { |
105 | res.borrow_mut().push(Diagnostic { | 109 | res.borrow_mut().push(Diagnostic { |
106 | range: d.highlight_range(), | 110 | range: sema.diagnostics_range(d).range, |
107 | message: d.message(), | 111 | message: d.message(), |
108 | severity: Severity::Error, | 112 | severity: Severity::Error, |
109 | fix: None, | 113 | fix: None, |
@@ -115,7 +119,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
115 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); | 119 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); |
116 | let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit); | 120 | let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit); |
117 | res.borrow_mut().push(Diagnostic { | 121 | res.borrow_mut().push(Diagnostic { |
118 | range: d.highlight_range(), | 122 | range: sema.diagnostics_range(d).range, |
119 | message: d.message(), | 123 | message: d.message(), |
120 | severity: Severity::Error, | 124 | severity: Severity::Error, |
121 | fix: Some(fix), | 125 | fix: Some(fix), |
@@ -622,6 +626,62 @@ mod tests { | |||
622 | } | 626 | } |
623 | 627 | ||
624 | #[test] | 628 | #[test] |
629 | fn range_mapping_out_of_macros() { | ||
630 | let (analysis, file_id) = single_file( | ||
631 | r" | ||
632 | fn some() {} | ||
633 | fn items() {} | ||
634 | fn here() {} | ||
635 | |||
636 | macro_rules! id { | ||
637 | ($($tt:tt)*) => { $($tt)*}; | ||
638 | } | ||
639 | |||
640 | fn main() { | ||
641 | let _x = id![Foo { a: 42 }]; | ||
642 | } | ||
643 | |||
644 | pub struct Foo { | ||
645 | pub a: i32, | ||
646 | pub b: i32, | ||
647 | } | ||
648 | ", | ||
649 | ); | ||
650 | let diagnostics = analysis.diagnostics(file_id).unwrap(); | ||
651 | assert_debug_snapshot!(diagnostics, @r###" | ||
652 | [ | ||
653 | Diagnostic { | ||
654 | message: "Missing structure fields:\n- b", | ||
655 | range: [224; 233), | ||
656 | fix: Some( | ||
657 | SourceChange { | ||
658 | label: "fill struct fields", | ||
659 | source_file_edits: [ | ||
660 | SourceFileEdit { | ||
661 | file_id: FileId( | ||
662 | 1, | ||
663 | ), | ||
664 | edit: TextEdit { | ||
665 | atoms: [ | ||
666 | AtomTextEdit { | ||
667 | delete: [3; 9), | ||
668 | insert: "{a:42, b: ()}", | ||
669 | }, | ||
670 | ], | ||
671 | }, | ||
672 | }, | ||
673 | ], | ||
674 | file_system_edits: [], | ||
675 | cursor_position: None, | ||
676 | }, | ||
677 | ), | ||
678 | severity: Error, | ||
679 | }, | ||
680 | ] | ||
681 | "###); | ||
682 | } | ||
683 | |||
684 | #[test] | ||
625 | fn test_check_unnecessary_braces_in_use_statement() { | 685 | fn test_check_unnecessary_braces_in_use_statement() { |
626 | check_not_applicable( | 686 | check_not_applicable( |
627 | " | 687 | " |