aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/hir/src/lib.rs1
-rw-r--r--crates/hir_def/src/nameres/path_resolution.rs16
-rw-r--r--crates/hir_ty/src/diagnostics.rs29
-rw-r--r--crates/ide/src/lib.rs8
-rw-r--r--crates/ide/src/references/rename.rs1
-rw-r--r--crates/ide/src/ssr.rs259
-rw-r--r--crates/ide_ssr/src/from_comment.rs32
-rw-r--r--crates/ide_ssr/src/lib.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs3
-rw-r--r--crates/rust-analyzer/src/handlers.rs12
10 files changed, 355 insertions, 8 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 4ef38c0f0..58adc8fd3 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -305,7 +305,6 @@ impl ModuleDef {
305 ModuleDef::Module(it) => it.name(db), 305 ModuleDef::Module(it) => it.name(db),
306 ModuleDef::Const(it) => it.name(db), 306 ModuleDef::Const(it) => it.name(db),
307 ModuleDef::Static(it) => it.name(db), 307 ModuleDef::Static(it) => it.name(db),
308
309 ModuleDef::BuiltinType(it) => Some(it.name()), 308 ModuleDef::BuiltinType(it) => Some(it.name()),
310 } 309 }
311 } 310 }
diff --git a/crates/hir_def/src/nameres/path_resolution.rs b/crates/hir_def/src/nameres/path_resolution.rs
index 8258dcffb..db459b1ed 100644
--- a/crates/hir_def/src/nameres/path_resolution.rs
+++ b/crates/hir_def/src/nameres/path_resolution.rs
@@ -156,7 +156,7 @@ impl DefMap {
156 } 156 }
157 } 157 }
158 158
159 pub(super) fn resolve_path_fp_with_macro_single( 159 fn resolve_path_fp_with_macro_single(
160 &self, 160 &self,
161 db: &dyn DefDatabase, 161 db: &dyn DefDatabase,
162 mode: ResolveMode, 162 mode: ResolveMode,
@@ -384,10 +384,16 @@ impl DefMap {
384 } 384 }
385 } 385 }
386 }; 386 };
387 let from_extern_prelude = self 387 // Give precedence to names in outer `DefMap`s over the extern prelude; only check prelude
388 .extern_prelude 388 // from the crate DefMap.
389 .get(name) 389 let from_extern_prelude = match self.block {
390 .map_or(PerNs::none(), |&it| PerNs::types(it, Visibility::Public)); 390 Some(_) => PerNs::none(),
391 None => self
392 .extern_prelude
393 .get(name)
394 .map_or(PerNs::none(), |&it| PerNs::types(it, Visibility::Public)),
395 };
396
391 let from_prelude = self.resolve_in_prelude(db, name); 397 let from_prelude = self.resolve_in_prelude(db, name);
392 398
393 from_legacy_macro.or(from_scope_or_builtin).or(from_extern_prelude).or(from_prelude) 399 from_legacy_macro.or(from_scope_or_builtin).or(from_extern_prelude).or(from_prelude)
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs
index 6bca7aa0d..86f937e1d 100644
--- a/crates/hir_ty/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -706,6 +706,35 @@ fn x(a: S) {
706 } 706 }
707 707
708 #[test] 708 #[test]
709 fn import_extern_crate_clash_with_inner_item() {
710 // This is more of a resolver test, but doesn't really work with the hir_def testsuite.
711
712 check_diagnostics(
713 r#"
714//- /lib.rs crate:lib deps:jwt
715mod permissions;
716
717use permissions::jwt;
718
719fn f() {
720 fn inner() {}
721 jwt::Claims {}; // should resolve to the local one with 0 fields, and not get a diagnostic
722}
723
724//- /permissions.rs
725pub mod jwt {
726 pub struct Claims {}
727}
728
729//- /jwt/lib.rs crate:jwt
730pub struct Claims {
731 field: u8,
732}
733 "#,
734 );
735 }
736
737 #[test]
709 fn break_outside_of_loop() { 738 fn break_outside_of_loop() {
710 check_diagnostics( 739 check_diagnostics(
711 r#" 740 r#"
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index f83ed65d5..d1a250d48 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -41,6 +41,7 @@ mod parent_module;
41mod references; 41mod references;
42mod fn_references; 42mod fn_references;
43mod runnables; 43mod runnables;
44mod ssr;
44mod status; 45mod status;
45mod syntax_highlighting; 46mod syntax_highlighting;
46mod syntax_tree; 47mod syntax_tree;
@@ -51,6 +52,7 @@ mod doc_links;
51use std::sync::Arc; 52use std::sync::Arc;
52 53
53use cfg::CfgOptions; 54use cfg::CfgOptions;
55
54use ide_db::base_db::{ 56use ide_db::base_db::{
55 salsa::{self, ParallelDatabase}, 57 salsa::{self, ParallelDatabase},
56 CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath, 58 CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath,
@@ -502,7 +504,11 @@ impl Analysis {
502 resolve: bool, 504 resolve: bool,
503 frange: FileRange, 505 frange: FileRange,
504 ) -> Cancelable<Vec<Assist>> { 506 ) -> Cancelable<Vec<Assist>> {
505 self.with_db(|db| Assist::get(db, config, resolve, frange)) 507 self.with_db(|db| {
508 let mut acc = Assist::get(db, config, resolve, frange);
509 ssr::add_ssr_assist(db, &mut acc, resolve, frange);
510 acc
511 })
506 } 512 }
507 513
508 /// Computes the set of diagnostics for the given file. 514 /// Computes the set of diagnostics for the given file.
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 05c73de88..bb68bcc78 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -94,6 +94,7 @@ pub(crate) fn rename_with_semantics(
94 } 94 }
95} 95}
96 96
97/// Called by the client when it is about to rename a file.
97pub(crate) fn will_rename_file( 98pub(crate) fn will_rename_file(
98 db: &RootDatabase, 99 db: &RootDatabase,
99 file_id: FileId, 100 file_id: FileId,
diff --git a/crates/ide/src/ssr.rs b/crates/ide/src/ssr.rs
new file mode 100644
index 000000000..f3638d928
--- /dev/null
+++ b/crates/ide/src/ssr.rs
@@ -0,0 +1,259 @@
1//! This module provides an SSR assist. It is not desirable to include this
2//! assist in ide_assists because that would require the ide_assists crate
3//! depend on the ide_ssr crate.
4
5use ide_assists::{Assist, AssistId, AssistKind, GroupLabel};
6use ide_db::{base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase};
7
8pub(crate) fn add_ssr_assist(
9 db: &RootDatabase,
10 base: &mut Vec<Assist>,
11 resolve: bool,
12 frange: FileRange,
13) -> Option<()> {
14 let (match_finder, comment_range) = ide_ssr::ssr_from_comment(db, frange)?;
15
16 let (source_change_for_file, source_change_for_workspace) = if resolve {
17 let edits = match_finder.edits();
18
19 let source_change_for_file = {
20 let text_edit_for_file = edits.get(&frange.file_id).cloned().unwrap_or_default();
21 SourceChange::from_text_edit(frange.file_id, text_edit_for_file)
22 };
23
24 let source_change_for_workspace = SourceChange::from(match_finder.edits());
25
26 (Some(source_change_for_file), Some(source_change_for_workspace))
27 } else {
28 (None, None)
29 };
30
31 let assists = vec![
32 ("Apply SSR in file", source_change_for_file),
33 ("Apply SSR in workspace", source_change_for_workspace),
34 ];
35
36 for (label, source_change) in assists.into_iter() {
37 let assist = Assist {
38 id: AssistId("ssr", AssistKind::RefactorRewrite),
39 label: Label::new(label),
40 group: Some(GroupLabel("Apply SSR".into())),
41 target: comment_range,
42 source_change,
43 };
44
45 base.push(assist);
46 }
47 Some(())
48}
49
50#[cfg(test)]
51mod tests {
52 use std::sync::Arc;
53
54 use expect_test::expect;
55 use ide_assists::Assist;
56 use ide_db::{
57 base_db::{fixture::WithFixture, salsa::Durability, FileRange},
58 symbol_index::SymbolsDatabase,
59 RootDatabase,
60 };
61 use rustc_hash::FxHashSet;
62
63 use super::add_ssr_assist;
64
65 fn get_assists(ra_fixture: &str, resolve: bool) -> Vec<Assist> {
66 let (mut db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture);
67 let mut local_roots = FxHashSet::default();
68 local_roots.insert(ide_db::base_db::fixture::WORKSPACE);
69 db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
70
71 let mut assists = vec![];
72
73 add_ssr_assist(
74 &db,
75 &mut assists,
76 resolve,
77 FileRange { file_id, range: range_or_offset.into() },
78 );
79
80 assists
81 }
82
83 #[test]
84 fn not_applicable_comment_not_ssr() {
85 let ra_fixture = r#"
86 //- /lib.rs
87
88 // This is foo $0
89 fn foo() {}
90 "#;
91 let resolve = true;
92
93 let assists = get_assists(ra_fixture, resolve);
94
95 assert_eq!(0, assists.len());
96 }
97
98 #[test]
99 fn resolve_edits_true() {
100 let resolve = true;
101 let assists = get_assists(
102 r#"
103 //- /lib.rs
104 mod bar;
105
106 // 2 ==>> 3$0
107 fn foo() { 2 }
108
109 //- /bar.rs
110 fn bar() { 2 }
111 "#,
112 resolve,
113 );
114
115 assert_eq!(2, assists.len());
116 let mut assists = assists.into_iter();
117
118 let apply_in_file_assist = assists.next().unwrap();
119 expect![[r#"
120 Assist {
121 id: AssistId(
122 "ssr",
123 RefactorRewrite,
124 ),
125 label: "Apply SSR in file",
126 group: Some(
127 GroupLabel(
128 "Apply SSR",
129 ),
130 ),
131 target: 10..21,
132 source_change: Some(
133 SourceChange {
134 source_file_edits: {
135 FileId(
136 0,
137 ): TextEdit {
138 indels: [
139 Indel {
140 insert: "3",
141 delete: 33..34,
142 },
143 ],
144 },
145 },
146 file_system_edits: [],
147 is_snippet: false,
148 },
149 ),
150 }
151 "#]]
152 .assert_debug_eq(&apply_in_file_assist);
153
154 let apply_in_workspace_assist = assists.next().unwrap();
155 expect![[r#"
156 Assist {
157 id: AssistId(
158 "ssr",
159 RefactorRewrite,
160 ),
161 label: "Apply SSR in workspace",
162 group: Some(
163 GroupLabel(
164 "Apply SSR",
165 ),
166 ),
167 target: 10..21,
168 source_change: Some(
169 SourceChange {
170 source_file_edits: {
171 FileId(
172 0,
173 ): TextEdit {
174 indels: [
175 Indel {
176 insert: "3",
177 delete: 33..34,
178 },
179 ],
180 },
181 FileId(
182 1,
183 ): TextEdit {
184 indels: [
185 Indel {
186 insert: "3",
187 delete: 11..12,
188 },
189 ],
190 },
191 },
192 file_system_edits: [],
193 is_snippet: false,
194 },
195 ),
196 }
197 "#]]
198 .assert_debug_eq(&apply_in_workspace_assist);
199 }
200
201 #[test]
202 fn resolve_edits_false() {
203 let resolve = false;
204 let assists = get_assists(
205 r#"
206 //- /lib.rs
207 mod bar;
208
209 // 2 ==>> 3$0
210 fn foo() { 2 }
211
212 //- /bar.rs
213 fn bar() { 2 }
214 "#,
215 resolve,
216 );
217
218 assert_eq!(2, assists.len());
219 let mut assists = assists.into_iter();
220
221 let apply_in_file_assist = assists.next().unwrap();
222 expect![[r#"
223 Assist {
224 id: AssistId(
225 "ssr",
226 RefactorRewrite,
227 ),
228 label: "Apply SSR in file",
229 group: Some(
230 GroupLabel(
231 "Apply SSR",
232 ),
233 ),
234 target: 10..21,
235 source_change: None,
236 }
237 "#]]
238 .assert_debug_eq(&apply_in_file_assist);
239
240 let apply_in_workspace_assist = assists.next().unwrap();
241 expect![[r#"
242 Assist {
243 id: AssistId(
244 "ssr",
245 RefactorRewrite,
246 ),
247 label: "Apply SSR in workspace",
248 group: Some(
249 GroupLabel(
250 "Apply SSR",
251 ),
252 ),
253 target: 10..21,
254 source_change: None,
255 }
256 "#]]
257 .assert_debug_eq(&apply_in_workspace_assist);
258 }
259}
diff --git a/crates/ide_ssr/src/from_comment.rs b/crates/ide_ssr/src/from_comment.rs
new file mode 100644
index 000000000..f1b312284
--- /dev/null
+++ b/crates/ide_ssr/src/from_comment.rs
@@ -0,0 +1,32 @@
1//! This module allows building an SSR MatchFinder by parsing the SSR rule
2//! from a comment.
3
4use ide_db::{
5 base_db::{FilePosition, FileRange, SourceDatabase},
6 RootDatabase,
7};
8use syntax::{
9 ast::{self, AstNode, AstToken},
10 TextRange,
11};
12
13use crate::MatchFinder;
14
15/// Attempts to build an SSR MatchFinder from a comment at the given file
16/// range. If successful, returns the MatchFinder and a TextRange covering
17/// comment.
18pub fn ssr_from_comment(db: &RootDatabase, frange: FileRange) -> Option<(MatchFinder, TextRange)> {
19 let comment = {
20 let file = db.parse(frange.file_id);
21 file.tree().syntax().token_at_offset(frange.range.start()).find_map(ast::Comment::cast)
22 }?;
23 let comment_text_without_prefix = comment.text().strip_prefix(comment.prefix()).unwrap();
24 let ssr_rule = comment_text_without_prefix.parse().ok()?;
25
26 let lookup_context = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
27
28 let mut match_finder = MatchFinder::in_context(db, lookup_context, vec![]);
29 match_finder.add_rule(ssr_rule).ok()?;
30
31 Some((match_finder, comment.syntax().text_range()))
32}
diff --git a/crates/ide_ssr/src/lib.rs b/crates/ide_ssr/src/lib.rs
index b894d326e..00585f448 100644
--- a/crates/ide_ssr/src/lib.rs
+++ b/crates/ide_ssr/src/lib.rs
@@ -67,6 +67,7 @@
67// // foo($a, $b) ==>> ($a).foo($b) 67// // foo($a, $b) ==>> ($a).foo($b)
68// ``` 68// ```
69 69
70mod from_comment;
70mod matching; 71mod matching;
71mod nester; 72mod nester;
72mod parsing; 73mod parsing;
@@ -80,6 +81,7 @@ mod tests;
80 81
81use crate::errors::bail; 82use crate::errors::bail;
82pub use crate::errors::SsrError; 83pub use crate::errors::SsrError;
84pub use crate::from_comment::ssr_from_comment;
83pub use crate::matching::Match; 85pub use crate::matching::Match;
84use crate::matching::MatchFailureReason; 86use crate::matching::MatchFailureReason;
85use hir::Semantics; 87use hir::Semantics;
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 25df13554..8af7871ac 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -395,6 +395,9 @@ impl Config {
395 pub fn work_done_progress(&self) -> bool { 395 pub fn work_done_progress(&self) -> bool {
396 try_or!(self.caps.window.as_ref()?.work_done_progress?, false) 396 try_or!(self.caps.window.as_ref()?.work_done_progress?, false)
397 } 397 }
398 pub fn will_rename(&self) -> bool {
399 try_or!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?, false)
400 }
398 pub fn code_action_resolve(&self) -> bool { 401 pub fn code_action_resolve(&self) -> bool {
399 try_or!( 402 try_or!(
400 self.caps 403 self.caps
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index b5b2ffe50..6cc433cb8 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -799,8 +799,18 @@ pub(crate) fn handle_rename(
799 let _p = profile::span("handle_rename"); 799 let _p = profile::span("handle_rename");
800 let position = from_proto::file_position(&snap, params.text_document_position)?; 800 let position = from_proto::file_position(&snap, params.text_document_position)?;
801 801
802 let change = 802 let mut change =
803 snap.analysis.rename(position, &*params.new_name)?.map_err(to_proto::rename_error)?; 803 snap.analysis.rename(position, &*params.new_name)?.map_err(to_proto::rename_error)?;
804
805 // this is kind of a hack to prevent double edits from happening when moving files
806 // When a module gets renamed by renaming the mod declaration this causes the file to move
807 // which in turn will trigger a WillRenameFiles request to the server for which we reply with a
808 // a second identical set of renames, the client will then apply both edits causing incorrect edits
809 // with this we only emit source_file_edits in the WillRenameFiles response which will do the rename instead
810 // See https://github.com/microsoft/vscode-languageserver-node/issues/752 for more info
811 if !change.file_system_edits.is_empty() && snap.config.will_rename() {
812 change.source_file_edits.clear();
813 }
804 let workspace_edit = to_proto::workspace_edit(&snap, change)?; 814 let workspace_edit = to_proto::workspace_edit(&snap, change)?;
805 Ok(Some(workspace_edit)) 815 Ok(Some(workspace_edit))
806} 816}