diff options
-rw-r--r-- | crates/ra_db/src/fixture.rs | 8 | ||||
-rw-r--r-- | crates/ra_db/src/input.rs | 24 | ||||
-rw-r--r-- | crates/ra_hir/src/code_model.rs | 6 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/builtin_derive.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/name.rs | 2 | ||||
-rw-r--r-- | crates/ra_ide/src/mock_analysis.rs | 2 | ||||
-rw-r--r-- | crates/ra_project_model/src/lib.rs | 13 | ||||
-rw-r--r-- | crates/ra_project_model/src/project_json.rs | 17 | ||||
-rw-r--r-- | crates/ra_ssr/src/lib.rs | 162 | ||||
-rw-r--r-- | crates/ra_ssr/src/matching.rs | 10 | ||||
-rw-r--r-- | crates/ra_ssr/src/replacing.rs | 6 | ||||
-rw-r--r-- | crates/ra_ssr/src/tests.rs | 189 | ||||
-rw-r--r-- | crates/rust-analyzer/src/bin/args.rs | 36 | ||||
-rw-r--r-- | crates/rust-analyzer/src/bin/main.rs | 3 | ||||
-rw-r--r-- | crates/rust-analyzer/src/cli.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/cli/ssr.rs | 40 |
16 files changed, 330 insertions, 192 deletions
diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs index 4f4fb4494..209713987 100644 --- a/crates/ra_db/src/fixture.rs +++ b/crates/ra_db/src/fixture.rs | |||
@@ -149,15 +149,17 @@ fn with_files( | |||
149 | let crate_id = crate_graph.add_crate_root( | 149 | let crate_id = crate_graph.add_crate_root( |
150 | file_id, | 150 | file_id, |
151 | meta.edition, | 151 | meta.edition, |
152 | Some(CrateName::new(&krate).unwrap()), | 152 | Some(krate.clone()), |
153 | meta.cfg, | 153 | meta.cfg, |
154 | meta.env, | 154 | meta.env, |
155 | Default::default(), | 155 | Default::default(), |
156 | ); | 156 | ); |
157 | let prev = crates.insert(krate.clone(), crate_id); | 157 | let crate_name = CrateName::new(&krate).unwrap(); |
158 | let prev = crates.insert(crate_name.clone(), crate_id); | ||
158 | assert!(prev.is_none()); | 159 | assert!(prev.is_none()); |
159 | for dep in meta.deps { | 160 | for dep in meta.deps { |
160 | crate_deps.push((krate.clone(), dep)) | 161 | let dep = CrateName::new(&dep).unwrap(); |
162 | crate_deps.push((crate_name.clone(), dep)) | ||
161 | } | 163 | } |
162 | } else if meta.path == "/main.rs" || meta.path == "/lib.rs" { | 164 | } else if meta.path == "/main.rs" || meta.path == "/lib.rs" { |
163 | assert!(default_crate_root.is_none()); | 165 | assert!(default_crate_root.is_none()); |
diff --git a/crates/ra_db/src/input.rs b/crates/ra_db/src/input.rs index 7f3660118..445a1ee48 100644 --- a/crates/ra_db/src/input.rs +++ b/crates/ra_db/src/input.rs | |||
@@ -67,7 +67,7 @@ pub struct CrateGraph { | |||
67 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | 67 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
68 | pub struct CrateId(pub u32); | 68 | pub struct CrateId(pub u32); |
69 | 69 | ||
70 | #[derive(Debug, Clone, PartialEq, Eq)] | 70 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
71 | pub struct CrateName(SmolStr); | 71 | pub struct CrateName(SmolStr); |
72 | 72 | ||
73 | impl CrateName { | 73 | impl CrateName { |
@@ -94,6 +94,13 @@ impl fmt::Display for CrateName { | |||
94 | } | 94 | } |
95 | } | 95 | } |
96 | 96 | ||
97 | impl ops::Deref for CrateName { | ||
98 | type Target = str; | ||
99 | fn deref(&self) -> &Self::Target { | ||
100 | &*self.0 | ||
101 | } | ||
102 | } | ||
103 | |||
97 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] | 104 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] |
98 | pub struct ProcMacroId(pub u32); | 105 | pub struct ProcMacroId(pub u32); |
99 | 106 | ||
@@ -117,7 +124,7 @@ pub struct CrateData { | |||
117 | /// The name to display to the end user. | 124 | /// The name to display to the end user. |
118 | /// This actual crate name can be different in a particular dependent crate | 125 | /// This actual crate name can be different in a particular dependent crate |
119 | /// or may even be missing for some cases, such as a dummy crate for the code snippet. | 126 | /// or may even be missing for some cases, such as a dummy crate for the code snippet. |
120 | pub display_name: Option<CrateName>, | 127 | pub display_name: Option<String>, |
121 | pub cfg_options: CfgOptions, | 128 | pub cfg_options: CfgOptions, |
122 | pub env: Env, | 129 | pub env: Env, |
123 | pub dependencies: Vec<Dependency>, | 130 | pub dependencies: Vec<Dependency>, |
@@ -138,7 +145,7 @@ pub struct Env { | |||
138 | #[derive(Debug, Clone, PartialEq, Eq)] | 145 | #[derive(Debug, Clone, PartialEq, Eq)] |
139 | pub struct Dependency { | 146 | pub struct Dependency { |
140 | pub crate_id: CrateId, | 147 | pub crate_id: CrateId, |
141 | pub name: SmolStr, | 148 | pub name: CrateName, |
142 | } | 149 | } |
143 | 150 | ||
144 | impl CrateGraph { | 151 | impl CrateGraph { |
@@ -146,7 +153,7 @@ impl CrateGraph { | |||
146 | &mut self, | 153 | &mut self, |
147 | file_id: FileId, | 154 | file_id: FileId, |
148 | edition: Edition, | 155 | edition: Edition, |
149 | display_name: Option<CrateName>, | 156 | display_name: Option<String>, |
150 | cfg_options: CfgOptions, | 157 | cfg_options: CfgOptions, |
151 | env: Env, | 158 | env: Env, |
152 | proc_macro: Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)>, | 159 | proc_macro: Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)>, |
@@ -178,7 +185,7 @@ impl CrateGraph { | |||
178 | if self.dfs_find(from, to, &mut FxHashSet::default()) { | 185 | if self.dfs_find(from, to, &mut FxHashSet::default()) { |
179 | return Err(CyclicDependenciesError); | 186 | return Err(CyclicDependenciesError); |
180 | } | 187 | } |
181 | self.arena.get_mut(&from).unwrap().add_dep(name.0, to); | 188 | self.arena.get_mut(&from).unwrap().add_dep(name, to); |
182 | Ok(()) | 189 | Ok(()) |
183 | } | 190 | } |
184 | 191 | ||
@@ -247,7 +254,7 @@ impl CrateId { | |||
247 | } | 254 | } |
248 | 255 | ||
249 | impl CrateData { | 256 | impl CrateData { |
250 | fn add_dep(&mut self, name: SmolStr, crate_id: CrateId) { | 257 | fn add_dep(&mut self, name: CrateName, crate_id: CrateId) { |
251 | self.dependencies.push(Dependency { name, crate_id }) | 258 | self.dependencies.push(Dependency { name, crate_id }) |
252 | } | 259 | } |
253 | } | 260 | } |
@@ -429,7 +436,10 @@ mod tests { | |||
429 | .is_ok()); | 436 | .is_ok()); |
430 | assert_eq!( | 437 | assert_eq!( |
431 | graph[crate1].dependencies, | 438 | graph[crate1].dependencies, |
432 | vec![Dependency { crate_id: crate2, name: "crate_name_with_dashes".into() }] | 439 | vec![Dependency { |
440 | crate_id: crate2, | ||
441 | name: CrateName::new("crate_name_with_dashes").unwrap() | ||
442 | }] | ||
433 | ); | 443 | ); |
434 | } | 444 | } |
435 | } | 445 | } |
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index e86077dd6..e09eb77c2 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs | |||
@@ -31,7 +31,7 @@ use hir_ty::{ | |||
31 | ApplicationTy, Canonical, GenericPredicate, InEnvironment, Substs, TraitEnvironment, Ty, | 31 | ApplicationTy, Canonical, GenericPredicate, InEnvironment, Substs, TraitEnvironment, Ty, |
32 | TyDefId, TypeCtor, | 32 | TyDefId, TypeCtor, |
33 | }; | 33 | }; |
34 | use ra_db::{CrateId, CrateName, Edition, FileId}; | 34 | use ra_db::{CrateId, Edition, FileId}; |
35 | use ra_prof::profile; | 35 | use ra_prof::profile; |
36 | use ra_syntax::ast::{self, AttrsOwner, NameOwner}; | 36 | use ra_syntax::ast::{self, AttrsOwner, NameOwner}; |
37 | use rustc_hash::FxHashSet; | 37 | use rustc_hash::FxHashSet; |
@@ -94,8 +94,8 @@ impl Crate { | |||
94 | db.crate_graph()[self.id].edition | 94 | db.crate_graph()[self.id].edition |
95 | } | 95 | } |
96 | 96 | ||
97 | pub fn display_name(self, db: &dyn HirDatabase) -> Option<CrateName> { | 97 | pub fn display_name(self, db: &dyn HirDatabase) -> Option<String> { |
98 | db.crate_graph()[self.id].display_name.as_ref().cloned() | 98 | db.crate_graph()[self.id].display_name.clone() |
99 | } | 99 | } |
100 | 100 | ||
101 | pub fn query_external_importables( | 101 | pub fn query_external_importables( |
diff --git a/crates/ra_hir_expand/src/builtin_derive.rs b/crates/ra_hir_expand/src/builtin_derive.rs index 26b667b55..f2d664863 100644 --- a/crates/ra_hir_expand/src/builtin_derive.rs +++ b/crates/ra_hir_expand/src/builtin_derive.rs | |||
@@ -161,7 +161,7 @@ fn find_builtin_crate(db: &dyn AstDatabase, id: LazyMacroId) -> tt::TokenTree { | |||
161 | // XXX | 161 | // XXX |
162 | // All crates except core itself should have a dependency on core, | 162 | // All crates except core itself should have a dependency on core, |
163 | // We detect `core` by seeing whether it doesn't have such a dependency. | 163 | // We detect `core` by seeing whether it doesn't have such a dependency. |
164 | let tt = if cg[krate].dependencies.iter().any(|dep| dep.name == "core") { | 164 | let tt = if cg[krate].dependencies.iter().any(|dep| &*dep.name == "core") { |
165 | quote! { core } | 165 | quote! { core } |
166 | } else { | 166 | } else { |
167 | quote! { crate } | 167 | quote! { crate } |
diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs index 1b0303685..969a2e5b8 100644 --- a/crates/ra_hir_expand/src/name.rs +++ b/crates/ra_hir_expand/src/name.rs | |||
@@ -117,7 +117,7 @@ impl AsName for ast::FieldKind { | |||
117 | 117 | ||
118 | impl AsName for ra_db::Dependency { | 118 | impl AsName for ra_db::Dependency { |
119 | fn as_name(&self) -> Name { | 119 | fn as_name(&self) -> Name { |
120 | Name::new_text(self.name.clone()) | 120 | Name::new_text(SmolStr::new(&*self.name)) |
121 | } | 121 | } |
122 | } | 122 | } |
123 | 123 | ||
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs index db6d50694..a393d3dba 100644 --- a/crates/ra_ide/src/mock_analysis.rs +++ b/crates/ra_ide/src/mock_analysis.rs | |||
@@ -130,7 +130,7 @@ impl MockAnalysis { | |||
130 | let other_crate = crate_graph.add_crate_root( | 130 | let other_crate = crate_graph.add_crate_root( |
131 | file_id, | 131 | file_id, |
132 | edition, | 132 | edition, |
133 | Some(CrateName::new(crate_name).unwrap()), | 133 | Some(crate_name.to_string()), |
134 | cfg, | 134 | cfg, |
135 | env, | 135 | env, |
136 | Default::default(), | 136 | Default::default(), |
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index 8b85b4831..8dbf4e6ea 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs | |||
@@ -288,10 +288,7 @@ impl ProjectWorkspace { | |||
288 | if let (Some(&from), Some(&to)) = | 288 | if let (Some(&from), Some(&to)) = |
289 | (crates.get(&from_crate_id), crates.get(&to_crate_id)) | 289 | (crates.get(&from_crate_id), crates.get(&to_crate_id)) |
290 | { | 290 | { |
291 | if crate_graph | 291 | if crate_graph.add_dep(from, dep.name.clone(), to).is_err() { |
292 | .add_dep(from, CrateName::new(&dep.name).unwrap(), to) | ||
293 | .is_err() | ||
294 | { | ||
295 | log::error!( | 292 | log::error!( |
296 | "cyclic dependency {:?} -> {:?}", | 293 | "cyclic dependency {:?} -> {:?}", |
297 | from_crate_id, | 294 | from_crate_id, |
@@ -312,13 +309,11 @@ impl ProjectWorkspace { | |||
312 | 309 | ||
313 | let env = Env::default(); | 310 | let env = Env::default(); |
314 | let proc_macro = vec![]; | 311 | let proc_macro = vec![]; |
315 | let crate_name = CrateName::new(&sysroot[krate].name) | 312 | let name = sysroot[krate].name.clone(); |
316 | .expect("Sysroot crate names should not contain dashes"); | ||
317 | |||
318 | let crate_id = crate_graph.add_crate_root( | 313 | let crate_id = crate_graph.add_crate_root( |
319 | file_id, | 314 | file_id, |
320 | Edition::Edition2018, | 315 | Edition::Edition2018, |
321 | Some(crate_name), | 316 | Some(name), |
322 | cfg_options.clone(), | 317 | cfg_options.clone(), |
323 | env, | 318 | env, |
324 | proc_macro, | 319 | proc_macro, |
@@ -392,7 +387,7 @@ impl ProjectWorkspace { | |||
392 | let crate_id = crate_graph.add_crate_root( | 387 | let crate_id = crate_graph.add_crate_root( |
393 | file_id, | 388 | file_id, |
394 | edition, | 389 | edition, |
395 | Some(CrateName::normalize_dashes(&cargo[pkg].name)), | 390 | Some(cargo[pkg].name.clone()), |
396 | cfg_options, | 391 | cfg_options, |
397 | env, | 392 | env, |
398 | proc_macro.clone(), | 393 | proc_macro.clone(), |
diff --git a/crates/ra_project_model/src/project_json.rs b/crates/ra_project_model/src/project_json.rs index 4b5dcd634..9fe1e2dcb 100644 --- a/crates/ra_project_model/src/project_json.rs +++ b/crates/ra_project_model/src/project_json.rs | |||
@@ -4,9 +4,9 @@ use std::path::PathBuf; | |||
4 | 4 | ||
5 | use paths::{AbsPath, AbsPathBuf}; | 5 | use paths::{AbsPath, AbsPathBuf}; |
6 | use ra_cfg::CfgOptions; | 6 | use ra_cfg::CfgOptions; |
7 | use ra_db::{CrateId, Dependency, Edition}; | 7 | use ra_db::{CrateId, CrateName, Dependency, Edition}; |
8 | use rustc_hash::FxHashSet; | 8 | use rustc_hash::FxHashSet; |
9 | use serde::Deserialize; | 9 | use serde::{de, Deserialize}; |
10 | use stdx::split_delim; | 10 | use stdx::split_delim; |
11 | 11 | ||
12 | /// Roots and crates that compose this Rust project. | 12 | /// Roots and crates that compose this Rust project. |
@@ -50,7 +50,7 @@ impl ProjectJson { | |||
50 | .into_iter() | 50 | .into_iter() |
51 | .map(|dep_data| Dependency { | 51 | .map(|dep_data| Dependency { |
52 | crate_id: CrateId(dep_data.krate as u32), | 52 | crate_id: CrateId(dep_data.krate as u32), |
53 | name: dep_data.name.into(), | 53 | name: dep_data.name, |
54 | }) | 54 | }) |
55 | .collect::<Vec<_>>(), | 55 | .collect::<Vec<_>>(), |
56 | cfg: { | 56 | cfg: { |
@@ -113,5 +113,14 @@ struct DepData { | |||
113 | /// Identifies a crate by position in the crates array. | 113 | /// Identifies a crate by position in the crates array. |
114 | #[serde(rename = "crate")] | 114 | #[serde(rename = "crate")] |
115 | krate: usize, | 115 | krate: usize, |
116 | name: String, | 116 | #[serde(deserialize_with = "deserialize_crate_name")] |
117 | name: CrateName, | ||
118 | } | ||
119 | |||
120 | fn deserialize_crate_name<'de, D>(de: D) -> Result<CrateName, D::Error> | ||
121 | where | ||
122 | D: de::Deserializer<'de>, | ||
123 | { | ||
124 | let name = String::deserialize(de)?; | ||
125 | CrateName::new(&name).map_err(|err| de::Error::custom(format!("invalid crate name: {:?}", err))) | ||
117 | } | 126 | } |
diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs index e148f4564..422e15ee6 100644 --- a/crates/ra_ssr/src/lib.rs +++ b/crates/ra_ssr/src/lib.rs | |||
@@ -9,10 +9,11 @@ mod replacing; | |||
9 | #[cfg(test)] | 9 | #[cfg(test)] |
10 | mod tests; | 10 | mod tests; |
11 | 11 | ||
12 | use crate::matching::Match; | 12 | pub use crate::matching::Match; |
13 | use crate::matching::{record_match_fails_reasons_scope, MatchFailureReason}; | ||
13 | use hir::Semantics; | 14 | use hir::Semantics; |
14 | use ra_db::{FileId, FileRange}; | 15 | use ra_db::{FileId, FileRange}; |
15 | use ra_syntax::{ast, AstNode, SmolStr, SyntaxNode}; | 16 | use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, TextRange}; |
16 | use ra_text_edit::TextEdit; | 17 | use ra_text_edit::TextEdit; |
17 | use rustc_hash::FxHashMap; | 18 | use rustc_hash::FxHashMap; |
18 | 19 | ||
@@ -26,7 +27,7 @@ pub struct SsrRule { | |||
26 | } | 27 | } |
27 | 28 | ||
28 | #[derive(Debug)] | 29 | #[derive(Debug)] |
29 | struct SsrPattern { | 30 | pub struct SsrPattern { |
30 | raw: parsing::RawSearchPattern, | 31 | raw: parsing::RawSearchPattern, |
31 | /// Placeholders keyed by the stand-in ident that we use in Rust source code. | 32 | /// Placeholders keyed by the stand-in ident that we use in Rust source code. |
32 | placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>, | 33 | placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>, |
@@ -45,7 +46,7 @@ pub struct SsrError(String); | |||
45 | 46 | ||
46 | #[derive(Debug, Default)] | 47 | #[derive(Debug, Default)] |
47 | pub struct SsrMatches { | 48 | pub struct SsrMatches { |
48 | matches: Vec<Match>, | 49 | pub matches: Vec<Match>, |
49 | } | 50 | } |
50 | 51 | ||
51 | /// Searches a crate for pattern matches and possibly replaces them with something else. | 52 | /// Searches a crate for pattern matches and possibly replaces them with something else. |
@@ -64,6 +65,12 @@ impl<'db> MatchFinder<'db> { | |||
64 | self.rules.push(rule); | 65 | self.rules.push(rule); |
65 | } | 66 | } |
66 | 67 | ||
68 | /// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you | ||
69 | /// intend to do replacement, use `add_rule` instead. | ||
70 | pub fn add_search_pattern(&mut self, pattern: SsrPattern) { | ||
71 | self.add_rule(SsrRule { pattern, template: "()".parse().unwrap() }) | ||
72 | } | ||
73 | |||
67 | pub fn edits_for_file(&self, file_id: FileId) -> Option<TextEdit> { | 74 | pub fn edits_for_file(&self, file_id: FileId) -> Option<TextEdit> { |
68 | let matches = self.find_matches_in_file(file_id); | 75 | let matches = self.find_matches_in_file(file_id); |
69 | if matches.matches.is_empty() { | 76 | if matches.matches.is_empty() { |
@@ -74,7 +81,7 @@ impl<'db> MatchFinder<'db> { | |||
74 | } | 81 | } |
75 | } | 82 | } |
76 | 83 | ||
77 | fn find_matches_in_file(&self, file_id: FileId) -> SsrMatches { | 84 | pub fn find_matches_in_file(&self, file_id: FileId) -> SsrMatches { |
78 | let file = self.sema.parse(file_id); | 85 | let file = self.sema.parse(file_id); |
79 | let code = file.syntax(); | 86 | let code = file.syntax(); |
80 | let mut matches = SsrMatches::default(); | 87 | let mut matches = SsrMatches::default(); |
@@ -82,6 +89,32 @@ impl<'db> MatchFinder<'db> { | |||
82 | matches | 89 | matches |
83 | } | 90 | } |
84 | 91 | ||
92 | /// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match | ||
93 | /// them, while recording reasons why they don't match. This API is useful for command | ||
94 | /// line-based debugging where providing a range is difficult. | ||
95 | pub fn debug_where_text_equal(&self, file_id: FileId, snippet: &str) -> Vec<MatchDebugInfo> { | ||
96 | use ra_db::SourceDatabaseExt; | ||
97 | let file = self.sema.parse(file_id); | ||
98 | let mut res = Vec::new(); | ||
99 | let file_text = self.sema.db.file_text(file_id); | ||
100 | let mut remaining_text = file_text.as_str(); | ||
101 | let mut base = 0; | ||
102 | let len = snippet.len() as u32; | ||
103 | while let Some(offset) = remaining_text.find(snippet) { | ||
104 | let start = base + offset as u32; | ||
105 | let end = start + len; | ||
106 | self.output_debug_for_nodes_at_range( | ||
107 | file.syntax(), | ||
108 | FileRange { file_id, range: TextRange::new(start.into(), end.into()) }, | ||
109 | &None, | ||
110 | &mut res, | ||
111 | ); | ||
112 | remaining_text = &remaining_text[offset + snippet.len()..]; | ||
113 | base = end; | ||
114 | } | ||
115 | res | ||
116 | } | ||
117 | |||
85 | fn find_matches( | 118 | fn find_matches( |
86 | &self, | 119 | &self, |
87 | code: &SyntaxNode, | 120 | code: &SyntaxNode, |
@@ -128,6 +161,59 @@ impl<'db> MatchFinder<'db> { | |||
128 | self.find_matches(&child, restrict_range, matches_out); | 161 | self.find_matches(&child, restrict_range, matches_out); |
129 | } | 162 | } |
130 | } | 163 | } |
164 | |||
165 | fn output_debug_for_nodes_at_range( | ||
166 | &self, | ||
167 | node: &SyntaxNode, | ||
168 | range: FileRange, | ||
169 | restrict_range: &Option<FileRange>, | ||
170 | out: &mut Vec<MatchDebugInfo>, | ||
171 | ) { | ||
172 | for node in node.children() { | ||
173 | let node_range = self.sema.original_range(&node); | ||
174 | if node_range.file_id != range.file_id || !node_range.range.contains_range(range.range) | ||
175 | { | ||
176 | continue; | ||
177 | } | ||
178 | if node_range.range == range.range { | ||
179 | for rule in &self.rules { | ||
180 | let pattern = | ||
181 | rule.pattern.tree_for_kind_with_reason(node.kind()).map(|p| p.clone()); | ||
182 | out.push(MatchDebugInfo { | ||
183 | matched: matching::get_match(true, rule, &node, restrict_range, &self.sema) | ||
184 | .map_err(|e| MatchFailureReason { | ||
185 | reason: e.reason.unwrap_or_else(|| { | ||
186 | "Match failed, but no reason was given".to_owned() | ||
187 | }), | ||
188 | }), | ||
189 | pattern, | ||
190 | node: node.clone(), | ||
191 | }); | ||
192 | } | ||
193 | } else if let Some(macro_call) = ast::MacroCall::cast(node.clone()) { | ||
194 | if let Some(expanded) = self.sema.expand(¯o_call) { | ||
195 | if let Some(tt) = macro_call.token_tree() { | ||
196 | self.output_debug_for_nodes_at_range( | ||
197 | &expanded, | ||
198 | range, | ||
199 | &Some(self.sema.original_range(tt.syntax())), | ||
200 | out, | ||
201 | ); | ||
202 | } | ||
203 | } | ||
204 | } else { | ||
205 | self.output_debug_for_nodes_at_range(&node, range, restrict_range, out); | ||
206 | } | ||
207 | } | ||
208 | } | ||
209 | } | ||
210 | |||
211 | pub struct MatchDebugInfo { | ||
212 | node: SyntaxNode, | ||
213 | /// Our search pattern parsed as the same kind of syntax node as `node`. e.g. expression, item, | ||
214 | /// etc. Will be absent if the pattern can't be parsed as that kind. | ||
215 | pattern: Result<SyntaxNode, MatchFailureReason>, | ||
216 | matched: Result<Match, MatchFailureReason>, | ||
131 | } | 217 | } |
132 | 218 | ||
133 | impl std::fmt::Display for SsrError { | 219 | impl std::fmt::Display for SsrError { |
@@ -136,4 +222,70 @@ impl std::fmt::Display for SsrError { | |||
136 | } | 222 | } |
137 | } | 223 | } |
138 | 224 | ||
225 | impl std::fmt::Debug for MatchDebugInfo { | ||
226 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
227 | write!(f, "========= PATTERN ==========\n")?; | ||
228 | match &self.pattern { | ||
229 | Ok(pattern) => { | ||
230 | write!(f, "{:#?}", pattern)?; | ||
231 | } | ||
232 | Err(err) => { | ||
233 | write!(f, "{}", err.reason)?; | ||
234 | } | ||
235 | } | ||
236 | write!( | ||
237 | f, | ||
238 | "\n============ AST ===========\n\ | ||
239 | {:#?}\n============================\n", | ||
240 | self.node | ||
241 | )?; | ||
242 | match &self.matched { | ||
243 | Ok(_) => write!(f, "Node matched")?, | ||
244 | Err(reason) => write!(f, "Node failed to match because: {}", reason.reason)?, | ||
245 | } | ||
246 | Ok(()) | ||
247 | } | ||
248 | } | ||
249 | |||
250 | impl SsrPattern { | ||
251 | fn tree_for_kind_with_reason( | ||
252 | &self, | ||
253 | kind: SyntaxKind, | ||
254 | ) -> Result<&SyntaxNode, MatchFailureReason> { | ||
255 | record_match_fails_reasons_scope(true, || self.tree_for_kind(kind)) | ||
256 | .map_err(|e| MatchFailureReason { reason: e.reason.unwrap() }) | ||
257 | } | ||
258 | } | ||
259 | |||
260 | impl SsrMatches { | ||
261 | /// Returns `self` with any nested matches removed and made into top-level matches. | ||
262 | pub fn flattened(self) -> SsrMatches { | ||
263 | let mut out = SsrMatches::default(); | ||
264 | self.flatten_into(&mut out); | ||
265 | out | ||
266 | } | ||
267 | |||
268 | fn flatten_into(self, out: &mut SsrMatches) { | ||
269 | for mut m in self.matches { | ||
270 | for p in m.placeholder_values.values_mut() { | ||
271 | std::mem::replace(&mut p.inner_matches, SsrMatches::default()).flatten_into(out); | ||
272 | } | ||
273 | out.matches.push(m); | ||
274 | } | ||
275 | } | ||
276 | } | ||
277 | |||
278 | impl Match { | ||
279 | pub fn matched_text(&self) -> String { | ||
280 | self.matched_node.text().to_string() | ||
281 | } | ||
282 | } | ||
283 | |||
139 | impl std::error::Error for SsrError {} | 284 | impl std::error::Error for SsrError {} |
285 | |||
286 | #[cfg(test)] | ||
287 | impl MatchDebugInfo { | ||
288 | pub(crate) fn match_failure_reason(&self) -> Option<&str> { | ||
289 | self.matched.as_ref().err().map(|r| r.reason.as_str()) | ||
290 | } | ||
291 | } | ||
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs index 54413a151..53d802e77 100644 --- a/crates/ra_ssr/src/matching.rs +++ b/crates/ra_ssr/src/matching.rs | |||
@@ -8,9 +8,7 @@ use crate::{ | |||
8 | use hir::Semantics; | 8 | use hir::Semantics; |
9 | use ra_db::FileRange; | 9 | use ra_db::FileRange; |
10 | use ra_syntax::ast::{AstNode, AstToken}; | 10 | use ra_syntax::ast::{AstNode, AstToken}; |
11 | use ra_syntax::{ | 11 | use ra_syntax::{ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken}; |
12 | ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, | ||
13 | }; | ||
14 | use rustc_hash::FxHashMap; | 12 | use rustc_hash::FxHashMap; |
15 | use std::{cell::Cell, iter::Peekable}; | 13 | use std::{cell::Cell, iter::Peekable}; |
16 | 14 | ||
@@ -44,8 +42,8 @@ macro_rules! fail_match { | |||
44 | 42 | ||
45 | /// Information about a match that was found. | 43 | /// Information about a match that was found. |
46 | #[derive(Debug)] | 44 | #[derive(Debug)] |
47 | pub(crate) struct Match { | 45 | pub struct Match { |
48 | pub(crate) range: TextRange, | 46 | pub(crate) range: FileRange, |
49 | pub(crate) matched_node: SyntaxNode, | 47 | pub(crate) matched_node: SyntaxNode, |
50 | pub(crate) placeholder_values: FxHashMap<Var, PlaceholderMatch>, | 48 | pub(crate) placeholder_values: FxHashMap<Var, PlaceholderMatch>, |
51 | pub(crate) ignored_comments: Vec<ast::Comment>, | 49 | pub(crate) ignored_comments: Vec<ast::Comment>, |
@@ -135,7 +133,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
135 | match_state.attempt_match_node(&match_inputs, &pattern_tree, code)?; | 133 | match_state.attempt_match_node(&match_inputs, &pattern_tree, code)?; |
136 | match_state.validate_range(&sema.original_range(code))?; | 134 | match_state.validate_range(&sema.original_range(code))?; |
137 | match_state.match_out = Some(Match { | 135 | match_state.match_out = Some(Match { |
138 | range: sema.original_range(code).range, | 136 | range: sema.original_range(code), |
139 | matched_node: code.clone(), | 137 | matched_node: code.clone(), |
140 | placeholder_values: FxHashMap::default(), | 138 | placeholder_values: FxHashMap::default(), |
141 | ignored_comments: Vec::new(), | 139 | ignored_comments: Vec::new(), |
diff --git a/crates/ra_ssr/src/replacing.rs b/crates/ra_ssr/src/replacing.rs index 70ce1c185..e43cc5167 100644 --- a/crates/ra_ssr/src/replacing.rs +++ b/crates/ra_ssr/src/replacing.rs | |||
@@ -21,8 +21,10 @@ fn matches_to_edit_at_offset( | |||
21 | ) -> TextEdit { | 21 | ) -> TextEdit { |
22 | let mut edit_builder = ra_text_edit::TextEditBuilder::default(); | 22 | let mut edit_builder = ra_text_edit::TextEditBuilder::default(); |
23 | for m in &matches.matches { | 23 | for m in &matches.matches { |
24 | edit_builder | 24 | edit_builder.replace( |
25 | .replace(m.range.checked_sub(relative_start).unwrap(), render_replace(m, file_src)); | 25 | m.range.range.checked_sub(relative_start).unwrap(), |
26 | render_replace(m, file_src), | ||
27 | ); | ||
26 | } | 28 | } |
27 | edit_builder.finish() | 29 | edit_builder.finish() |
28 | } | 30 | } |
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 57b2f50b2..c692c97e2 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs | |||
@@ -1,150 +1,5 @@ | |||
1 | use crate::matching::MatchFailureReason; | 1 | use crate::{MatchFinder, SsrRule}; |
2 | use crate::{matching, Match, MatchFinder, SsrMatches, SsrPattern, SsrRule}; | 2 | use ra_db::{FileId, SourceDatabaseExt}; |
3 | use matching::record_match_fails_reasons_scope; | ||
4 | use ra_db::{FileId, FileRange, SourceDatabaseExt}; | ||
5 | use ra_syntax::ast::AstNode; | ||
6 | use ra_syntax::{ast, SyntaxKind, SyntaxNode, TextRange}; | ||
7 | |||
8 | struct MatchDebugInfo { | ||
9 | node: SyntaxNode, | ||
10 | /// Our search pattern parsed as the same kind of syntax node as `node`. e.g. expression, item, | ||
11 | /// etc. Will be absent if the pattern can't be parsed as that kind. | ||
12 | pattern: Result<SyntaxNode, MatchFailureReason>, | ||
13 | matched: Result<Match, MatchFailureReason>, | ||
14 | } | ||
15 | |||
16 | impl SsrPattern { | ||
17 | pub(crate) fn tree_for_kind_with_reason( | ||
18 | &self, | ||
19 | kind: SyntaxKind, | ||
20 | ) -> Result<&SyntaxNode, MatchFailureReason> { | ||
21 | record_match_fails_reasons_scope(true, || self.tree_for_kind(kind)) | ||
22 | .map_err(|e| MatchFailureReason { reason: e.reason.unwrap() }) | ||
23 | } | ||
24 | } | ||
25 | |||
26 | impl std::fmt::Debug for MatchDebugInfo { | ||
27 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
28 | write!(f, "========= PATTERN ==========\n")?; | ||
29 | match &self.pattern { | ||
30 | Ok(pattern) => { | ||
31 | write!(f, "{:#?}", pattern)?; | ||
32 | } | ||
33 | Err(err) => { | ||
34 | write!(f, "{}", err.reason)?; | ||
35 | } | ||
36 | } | ||
37 | write!( | ||
38 | f, | ||
39 | "\n============ AST ===========\n\ | ||
40 | {:#?}\n============================", | ||
41 | self.node | ||
42 | )?; | ||
43 | match &self.matched { | ||
44 | Ok(_) => write!(f, "Node matched")?, | ||
45 | Err(reason) => write!(f, "Node failed to match because: {}", reason.reason)?, | ||
46 | } | ||
47 | Ok(()) | ||
48 | } | ||
49 | } | ||
50 | |||
51 | impl SsrMatches { | ||
52 | /// Returns `self` with any nested matches removed and made into top-level matches. | ||
53 | pub(crate) fn flattened(self) -> SsrMatches { | ||
54 | let mut out = SsrMatches::default(); | ||
55 | self.flatten_into(&mut out); | ||
56 | out | ||
57 | } | ||
58 | |||
59 | fn flatten_into(self, out: &mut SsrMatches) { | ||
60 | for mut m in self.matches { | ||
61 | for p in m.placeholder_values.values_mut() { | ||
62 | std::mem::replace(&mut p.inner_matches, SsrMatches::default()).flatten_into(out); | ||
63 | } | ||
64 | out.matches.push(m); | ||
65 | } | ||
66 | } | ||
67 | } | ||
68 | |||
69 | impl Match { | ||
70 | pub(crate) fn matched_text(&self) -> String { | ||
71 | self.matched_node.text().to_string() | ||
72 | } | ||
73 | } | ||
74 | |||
75 | impl<'db> MatchFinder<'db> { | ||
76 | /// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you | ||
77 | /// intend to do replacement, use `add_rule` instead. | ||
78 | fn add_search_pattern(&mut self, pattern: SsrPattern) { | ||
79 | self.add_rule(SsrRule { pattern, template: "()".parse().unwrap() }) | ||
80 | } | ||
81 | |||
82 | /// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match | ||
83 | /// them, while recording reasons why they don't match. This API is useful for command | ||
84 | /// line-based debugging where providing a range is difficult. | ||
85 | fn debug_where_text_equal(&self, file_id: FileId, snippet: &str) -> Vec<MatchDebugInfo> { | ||
86 | let file = self.sema.parse(file_id); | ||
87 | let mut res = Vec::new(); | ||
88 | let file_text = self.sema.db.file_text(file_id); | ||
89 | let mut remaining_text = file_text.as_str(); | ||
90 | let mut base = 0; | ||
91 | let len = snippet.len() as u32; | ||
92 | while let Some(offset) = remaining_text.find(snippet) { | ||
93 | let start = base + offset as u32; | ||
94 | let end = start + len; | ||
95 | self.output_debug_for_nodes_at_range( | ||
96 | file.syntax(), | ||
97 | TextRange::new(start.into(), end.into()), | ||
98 | &None, | ||
99 | &mut res, | ||
100 | ); | ||
101 | remaining_text = &remaining_text[offset + snippet.len()..]; | ||
102 | base = end; | ||
103 | } | ||
104 | res | ||
105 | } | ||
106 | |||
107 | fn output_debug_for_nodes_at_range( | ||
108 | &self, | ||
109 | node: &SyntaxNode, | ||
110 | range: TextRange, | ||
111 | restrict_range: &Option<FileRange>, | ||
112 | out: &mut Vec<MatchDebugInfo>, | ||
113 | ) { | ||
114 | for node in node.children() { | ||
115 | if !node.text_range().contains_range(range) { | ||
116 | continue; | ||
117 | } | ||
118 | if node.text_range() == range { | ||
119 | for rule in &self.rules { | ||
120 | let pattern = | ||
121 | rule.pattern.tree_for_kind_with_reason(node.kind()).map(|p| p.clone()); | ||
122 | out.push(MatchDebugInfo { | ||
123 | matched: matching::get_match(true, rule, &node, restrict_range, &self.sema) | ||
124 | .map_err(|e| MatchFailureReason { | ||
125 | reason: e.reason.unwrap_or_else(|| { | ||
126 | "Match failed, but no reason was given".to_owned() | ||
127 | }), | ||
128 | }), | ||
129 | pattern, | ||
130 | node: node.clone(), | ||
131 | }); | ||
132 | } | ||
133 | } else if let Some(macro_call) = ast::MacroCall::cast(node.clone()) { | ||
134 | if let Some(expanded) = self.sema.expand(¯o_call) { | ||
135 | if let Some(tt) = macro_call.token_tree() { | ||
136 | self.output_debug_for_nodes_at_range( | ||
137 | &expanded, | ||
138 | range, | ||
139 | &Some(self.sema.original_range(tt.syntax())), | ||
140 | out, | ||
141 | ); | ||
142 | } | ||
143 | } | ||
144 | } | ||
145 | } | ||
146 | } | ||
147 | } | ||
148 | 3 | ||
149 | fn parse_error_text(query: &str) -> String { | 4 | fn parse_error_text(query: &str) -> String { |
150 | format!("{}", query.parse::<SsrRule>().unwrap_err()) | 5 | format!("{}", query.parse::<SsrRule>().unwrap_err()) |
@@ -260,6 +115,19 @@ fn assert_no_match(pattern: &str, code: &str) { | |||
260 | assert_matches(pattern, code, &[]); | 115 | assert_matches(pattern, code, &[]); |
261 | } | 116 | } |
262 | 117 | ||
118 | fn assert_match_failure_reason(pattern: &str, code: &str, snippet: &str, expected_reason: &str) { | ||
119 | let (db, file_id) = single_file(code); | ||
120 | let mut match_finder = MatchFinder::new(&db); | ||
121 | match_finder.add_search_pattern(pattern.parse().unwrap()); | ||
122 | let mut reasons = Vec::new(); | ||
123 | for d in match_finder.debug_where_text_equal(file_id, snippet) { | ||
124 | if let Some(reason) = d.match_failure_reason() { | ||
125 | reasons.push(reason.to_owned()); | ||
126 | } | ||
127 | } | ||
128 | assert_eq!(reasons, vec![expected_reason]); | ||
129 | } | ||
130 | |||
263 | #[test] | 131 | #[test] |
264 | fn ssr_function_to_method() { | 132 | fn ssr_function_to_method() { |
265 | assert_ssr_transform( | 133 | assert_ssr_transform( |
@@ -623,3 +491,30 @@ fn preserves_whitespace_within_macro_expansion() { | |||
623 | fn f() {macro1!(4 - 3 - 1 * 2}"#, | 491 | fn f() {macro1!(4 - 3 - 1 * 2}"#, |
624 | ) | 492 | ) |
625 | } | 493 | } |
494 | |||
495 | #[test] | ||
496 | fn match_failure_reasons() { | ||
497 | let code = r#" | ||
498 | macro_rules! foo { | ||
499 | ($a:expr) => { | ||
500 | 1 + $a + 2 | ||
501 | }; | ||
502 | } | ||
503 | fn f1() { | ||
504 | bar(1, 2); | ||
505 | foo!(5 + 43.to_string() + 5); | ||
506 | } | ||
507 | "#; | ||
508 | assert_match_failure_reason( | ||
509 | "bar($a, 3)", | ||
510 | code, | ||
511 | "bar(1, 2)", | ||
512 | r#"Pattern wanted token '3' (INT_NUMBER), but code had token '2' (INT_NUMBER)"#, | ||
513 | ); | ||
514 | assert_match_failure_reason( | ||
515 | "42.to_string()", | ||
516 | code, | ||
517 | "43.to_string()", | ||
518 | r#"Pattern wanted token '42' (INT_NUMBER), but code had token '43' (INT_NUMBER)"#, | ||
519 | ); | ||
520 | } | ||
diff --git a/crates/rust-analyzer/src/bin/args.rs b/crates/rust-analyzer/src/bin/args.rs index 8a0b10117..8c0f4df8b 100644 --- a/crates/rust-analyzer/src/bin/args.rs +++ b/crates/rust-analyzer/src/bin/args.rs | |||
@@ -5,7 +5,7 @@ | |||
5 | 5 | ||
6 | use anyhow::{bail, Result}; | 6 | use anyhow::{bail, Result}; |
7 | use pico_args::Arguments; | 7 | use pico_args::Arguments; |
8 | use ra_ssr::SsrRule; | 8 | use ra_ssr::{SsrPattern, SsrRule}; |
9 | use rust_analyzer::cli::{BenchWhat, Position, Verbosity}; | 9 | use rust_analyzer::cli::{BenchWhat, Position, Verbosity}; |
10 | 10 | ||
11 | use std::{fmt::Write, path::PathBuf}; | 11 | use std::{fmt::Write, path::PathBuf}; |
@@ -50,6 +50,10 @@ pub(crate) enum Command { | |||
50 | Ssr { | 50 | Ssr { |
51 | rules: Vec<SsrRule>, | 51 | rules: Vec<SsrRule>, |
52 | }, | 52 | }, |
53 | StructuredSearch { | ||
54 | debug_snippet: Option<String>, | ||
55 | patterns: Vec<SsrPattern>, | ||
56 | }, | ||
53 | ProcMacro, | 57 | ProcMacro, |
54 | RunServer, | 58 | RunServer, |
55 | Version, | 59 | Version, |
@@ -294,6 +298,7 @@ EXAMPLE: | |||
294 | rust-analyzer ssr '$a.foo($b) ==> bar($a, $b)' | 298 | rust-analyzer ssr '$a.foo($b) ==> bar($a, $b)' |
295 | 299 | ||
296 | FLAGS: | 300 | FLAGS: |
301 | --debug <snippet> Prints debug information for any nodes with source exactly equal to <snippet> | ||
297 | -h, --help Prints help information | 302 | -h, --help Prints help information |
298 | 303 | ||
299 | ARGS: | 304 | ARGS: |
@@ -307,6 +312,34 @@ ARGS: | |||
307 | } | 312 | } |
308 | Command::Ssr { rules } | 313 | Command::Ssr { rules } |
309 | } | 314 | } |
315 | "search" => { | ||
316 | if matches.contains(["-h", "--help"]) { | ||
317 | eprintln!( | ||
318 | "\ | ||
319 | rust-analyzer search | ||
320 | |||
321 | USAGE: | ||
322 | rust-analyzer search [FLAGS] [PATTERN...] | ||
323 | |||
324 | EXAMPLE: | ||
325 | rust-analyzer search '$a.foo($b)' | ||
326 | |||
327 | FLAGS: | ||
328 | --debug <snippet> Prints debug information for any nodes with source exactly equal to <snippet> | ||
329 | -h, --help Prints help information | ||
330 | |||
331 | ARGS: | ||
332 | <PATTERN> A structured search pattern" | ||
333 | ); | ||
334 | return Ok(Err(HelpPrinted)); | ||
335 | } | ||
336 | let debug_snippet = matches.opt_value_from_str("--debug")?; | ||
337 | let mut patterns = Vec::new(); | ||
338 | while let Some(rule) = matches.free_from_str()? { | ||
339 | patterns.push(rule); | ||
340 | } | ||
341 | Command::StructuredSearch { patterns, debug_snippet } | ||
342 | } | ||
310 | _ => { | 343 | _ => { |
311 | print_subcommands(); | 344 | print_subcommands(); |
312 | return Ok(Err(HelpPrinted)); | 345 | return Ok(Err(HelpPrinted)); |
@@ -334,6 +367,7 @@ SUBCOMMANDS: | |||
334 | diagnostics | 367 | diagnostics |
335 | proc-macro | 368 | proc-macro |
336 | parse | 369 | parse |
370 | search | ||
337 | ssr | 371 | ssr |
338 | symbols" | 372 | symbols" |
339 | ) | 373 | ) |
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index 0f55c3ee2..eec76d415 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs | |||
@@ -65,6 +65,9 @@ fn main() -> Result<()> { | |||
65 | args::Command::Ssr { rules } => { | 65 | args::Command::Ssr { rules } => { |
66 | cli::apply_ssr_rules(rules)?; | 66 | cli::apply_ssr_rules(rules)?; |
67 | } | 67 | } |
68 | args::Command::StructuredSearch { patterns, debug_snippet } => { | ||
69 | cli::search_for_patterns(patterns, debug_snippet)?; | ||
70 | } | ||
68 | args::Command::Version => println!("rust-analyzer {}", env!("REV")), | 71 | args::Command::Version => println!("rust-analyzer {}", env!("REV")), |
69 | } | 72 | } |
70 | Ok(()) | 73 | Ok(()) |
diff --git a/crates/rust-analyzer/src/cli.rs b/crates/rust-analyzer/src/cli.rs index 13e3d75be..6863f100b 100644 --- a/crates/rust-analyzer/src/cli.rs +++ b/crates/rust-analyzer/src/cli.rs | |||
@@ -18,7 +18,7 @@ pub use analysis_bench::{analysis_bench, BenchWhat, Position}; | |||
18 | pub use analysis_stats::analysis_stats; | 18 | pub use analysis_stats::analysis_stats; |
19 | pub use diagnostics::diagnostics; | 19 | pub use diagnostics::diagnostics; |
20 | pub use load_cargo::load_cargo; | 20 | pub use load_cargo::load_cargo; |
21 | pub use ssr::apply_ssr_rules; | 21 | pub use ssr::{apply_ssr_rules, search_for_patterns}; |
22 | 22 | ||
23 | #[derive(Clone, Copy)] | 23 | #[derive(Clone, Copy)] |
24 | pub enum Verbosity { | 24 | pub enum Verbosity { |
diff --git a/crates/rust-analyzer/src/cli/ssr.rs b/crates/rust-analyzer/src/cli/ssr.rs index a5265ac15..4fb829ea5 100644 --- a/crates/rust-analyzer/src/cli/ssr.rs +++ b/crates/rust-analyzer/src/cli/ssr.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | use crate::cli::{load_cargo::load_cargo, Result}; | 3 | use crate::cli::{load_cargo::load_cargo, Result}; |
4 | use ra_ide::SourceFileEdit; | 4 | use ra_ide::SourceFileEdit; |
5 | use ra_ssr::{MatchFinder, SsrRule}; | 5 | use ra_ssr::{MatchFinder, SsrPattern, SsrRule}; |
6 | 6 | ||
7 | pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> { | 7 | pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> { |
8 | use ra_db::SourceDatabaseExt; | 8 | use ra_db::SourceDatabaseExt; |
@@ -31,3 +31,41 @@ pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> { | |||
31 | } | 31 | } |
32 | Ok(()) | 32 | Ok(()) |
33 | } | 33 | } |
34 | |||
35 | /// Searches for `patterns`, printing debug information for any nodes whose text exactly matches | ||
36 | /// `debug_snippet`. This is intended for debugging and probably isn't in it's current form useful | ||
37 | /// for much else. | ||
38 | pub fn search_for_patterns(patterns: Vec<SsrPattern>, debug_snippet: Option<String>) -> Result<()> { | ||
39 | use ra_db::SourceDatabaseExt; | ||
40 | use ra_ide_db::symbol_index::SymbolsDatabase; | ||
41 | let (host, vfs) = load_cargo(&std::env::current_dir()?, true, true)?; | ||
42 | let db = host.raw_database(); | ||
43 | let mut match_finder = MatchFinder::new(db); | ||
44 | for pattern in patterns { | ||
45 | match_finder.add_search_pattern(pattern); | ||
46 | } | ||
47 | for &root in db.local_roots().iter() { | ||
48 | let sr = db.source_root(root); | ||
49 | for file_id in sr.iter() { | ||
50 | if let Some(debug_snippet) = &debug_snippet { | ||
51 | for debug_info in match_finder.debug_where_text_equal(file_id, debug_snippet) { | ||
52 | println!("{:#?}", debug_info); | ||
53 | } | ||
54 | } else { | ||
55 | let matches = match_finder.find_matches_in_file(file_id); | ||
56 | if !matches.matches.is_empty() { | ||
57 | let matches = matches.flattened().matches; | ||
58 | if let Some(path) = vfs.file_path(file_id).as_path() { | ||
59 | println!("{} matches in '{}'", matches.len(), path.to_string_lossy()); | ||
60 | } | ||
61 | // We could possibly at some point do something more useful than just printing | ||
62 | // the matched text. For now though, that's the easiest thing to do. | ||
63 | for m in matches { | ||
64 | println!("{}", m.matched_text()); | ||
65 | } | ||
66 | } | ||
67 | } | ||
68 | } | ||
69 | } | ||
70 | Ok(()) | ||
71 | } | ||