aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ssr/src/parsing.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ssr/src/parsing.rs')
-rw-r--r--crates/ra_ssr/src/parsing.rs142
1 files changed, 94 insertions, 48 deletions
diff --git a/crates/ra_ssr/src/parsing.rs b/crates/ra_ssr/src/parsing.rs
index 4aee97bb2..4e046910c 100644
--- a/crates/ra_ssr/src/parsing.rs
+++ b/crates/ra_ssr/src/parsing.rs
@@ -7,17 +7,20 @@
7 7
8use crate::errors::bail; 8use crate::errors::bail;
9use crate::{SsrError, SsrPattern, SsrRule}; 9use crate::{SsrError, SsrPattern, SsrRule};
10use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, T}; 10use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, T};
11use rustc_hash::{FxHashMap, FxHashSet}; 11use rustc_hash::{FxHashMap, FxHashSet};
12use std::str::FromStr; 12use std::str::FromStr;
13use test_utils::mark;
13 14
14#[derive(Clone, Debug)] 15#[derive(Debug)]
15pub(crate) struct SsrTemplate { 16pub(crate) struct ParsedRule {
16 pub(crate) tokens: Vec<PatternElement>, 17 pub(crate) placeholders_by_stand_in: FxHashMap<SmolStr, Placeholder>,
18 pub(crate) pattern: SyntaxNode,
19 pub(crate) template: Option<SyntaxNode>,
17} 20}
18 21
19#[derive(Debug)] 22#[derive(Debug)]
20pub(crate) struct RawSearchPattern { 23pub(crate) struct RawPattern {
21 tokens: Vec<PatternElement>, 24 tokens: Vec<PatternElement>,
22} 25}
23 26
@@ -54,6 +57,78 @@ pub(crate) struct Token {
54 pub(crate) text: SmolStr, 57 pub(crate) text: SmolStr,
55} 58}
56 59
60impl ParsedRule {
61 fn new(
62 pattern: &RawPattern,
63 template: Option<&RawPattern>,
64 ) -> Result<Vec<ParsedRule>, SsrError> {
65 let raw_pattern = pattern.as_rust_code();
66 let raw_template = template.map(|t| t.as_rust_code());
67 let raw_template = raw_template.as_ref().map(|s| s.as_str());
68 let mut builder = RuleBuilder {
69 placeholders_by_stand_in: pattern.placeholders_by_stand_in(),
70 rules: Vec::new(),
71 };
72 builder.try_add(ast::Expr::parse(&raw_pattern), raw_template.map(ast::Expr::parse));
73 builder.try_add(ast::Type::parse(&raw_pattern), raw_template.map(ast::Type::parse));
74 builder.try_add(ast::Item::parse(&raw_pattern), raw_template.map(ast::Item::parse));
75 builder.try_add(ast::Path::parse(&raw_pattern), raw_template.map(ast::Path::parse));
76 builder.try_add(ast::Pat::parse(&raw_pattern), raw_template.map(ast::Pat::parse));
77 builder.build()
78 }
79}
80
81struct RuleBuilder {
82 placeholders_by_stand_in: FxHashMap<SmolStr, Placeholder>,
83 rules: Vec<ParsedRule>,
84}
85
86impl RuleBuilder {
87 fn try_add<T: AstNode>(&mut self, pattern: Result<T, ()>, template: Option<Result<T, ()>>) {
88 match (pattern, template) {
89 (Ok(pattern), Some(Ok(template))) => self.rules.push(ParsedRule {
90 placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
91 pattern: pattern.syntax().clone(),
92 template: Some(template.syntax().clone()),
93 }),
94 (Ok(pattern), None) => self.rules.push(ParsedRule {
95 placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
96 pattern: pattern.syntax().clone(),
97 template: None,
98 }),
99 _ => {}
100 }
101 }
102
103 fn build(mut self) -> Result<Vec<ParsedRule>, SsrError> {
104 if self.rules.is_empty() {
105 bail!("Not a valid Rust expression, type, item, path or pattern");
106 }
107 // If any rules contain paths, then we reject any rules that don't contain paths. Allowing a
108 // mix leads to strange semantics, since the path-based rules only match things where the
109 // path refers to semantically the same thing, whereas the non-path-based rules could match
110 // anything. Specifically, if we have a rule like `foo ==>> bar` we only want to match the
111 // `foo` that is in the current scope, not any `foo`. However "foo" can be parsed as a
112 // pattern (BIND_PAT -> NAME -> IDENT). Allowing such a rule through would result in
113 // renaming everything called `foo` to `bar`. It'd also be slow, since without a path, we'd
114 // have to use the slow-scan search mechanism.
115 if self.rules.iter().any(|rule| contains_path(&rule.pattern)) {
116 let old_len = self.rules.len();
117 self.rules.retain(|rule| contains_path(&rule.pattern));
118 if self.rules.len() < old_len {
119 mark::hit!(pattern_is_a_single_segment_path);
120 }
121 }
122 Ok(self.rules)
123 }
124}
125
126/// Returns whether there are any paths in `node`.
127fn contains_path(node: &SyntaxNode) -> bool {
128 node.kind() == SyntaxKind::PATH
129 || node.descendants().any(|node| node.kind() == SyntaxKind::PATH)
130}
131
57impl FromStr for SsrRule { 132impl FromStr for SsrRule {
58 type Err = SsrError; 133 type Err = SsrError;
59 134
@@ -68,21 +143,24 @@ impl FromStr for SsrRule {
68 if it.next().is_some() { 143 if it.next().is_some() {
69 return Err(SsrError("More than one delimiter found".into())); 144 return Err(SsrError("More than one delimiter found".into()));
70 } 145 }
71 let rule = SsrRule { pattern: pattern.parse()?, template: template.parse()? }; 146 let raw_pattern = pattern.parse()?;
147 let raw_template = template.parse()?;
148 let parsed_rules = ParsedRule::new(&raw_pattern, Some(&raw_template))?;
149 let rule = SsrRule { pattern: raw_pattern, template: raw_template, parsed_rules };
72 validate_rule(&rule)?; 150 validate_rule(&rule)?;
73 Ok(rule) 151 Ok(rule)
74 } 152 }
75} 153}
76 154
77impl FromStr for RawSearchPattern { 155impl FromStr for RawPattern {
78 type Err = SsrError; 156 type Err = SsrError;
79 157
80 fn from_str(pattern_str: &str) -> Result<RawSearchPattern, SsrError> { 158 fn from_str(pattern_str: &str) -> Result<RawPattern, SsrError> {
81 Ok(RawSearchPattern { tokens: parse_pattern(pattern_str)? }) 159 Ok(RawPattern { tokens: parse_pattern(pattern_str)? })
82 } 160 }
83} 161}
84 162
85impl RawSearchPattern { 163impl RawPattern {
86 /// Returns this search pattern as Rust source code that we can feed to the Rust parser. 164 /// Returns this search pattern as Rust source code that we can feed to the Rust parser.
87 fn as_rust_code(&self) -> String { 165 fn as_rust_code(&self) -> String {
88 let mut res = String::new(); 166 let mut res = String::new();
@@ -95,7 +173,7 @@ impl RawSearchPattern {
95 res 173 res
96 } 174 }
97 175
98 fn placeholders_by_stand_in(&self) -> FxHashMap<SmolStr, Placeholder> { 176 pub(crate) fn placeholders_by_stand_in(&self) -> FxHashMap<SmolStr, Placeholder> {
99 let mut res = FxHashMap::default(); 177 let mut res = FxHashMap::default();
100 for t in &self.tokens { 178 for t in &self.tokens {
101 if let PatternElement::Placeholder(placeholder) = t { 179 if let PatternElement::Placeholder(placeholder) = t {
@@ -110,41 +188,9 @@ impl FromStr for SsrPattern {
110 type Err = SsrError; 188 type Err = SsrError;
111 189
112 fn from_str(pattern_str: &str) -> Result<SsrPattern, SsrError> { 190 fn from_str(pattern_str: &str) -> Result<SsrPattern, SsrError> {
113 let raw: RawSearchPattern = pattern_str.parse()?; 191 let raw_pattern = pattern_str.parse()?;
114 let raw_str = raw.as_rust_code(); 192 let parsed_rules = ParsedRule::new(&raw_pattern, None)?;
115 let res = SsrPattern { 193 Ok(SsrPattern { raw: raw_pattern, parsed_rules })
116 expr: ast::Expr::parse(&raw_str).ok().map(|n| n.syntax().clone()),
117 type_ref: ast::TypeRef::parse(&raw_str).ok().map(|n| n.syntax().clone()),
118 item: ast::ModuleItem::parse(&raw_str).ok().map(|n| n.syntax().clone()),
119 path: ast::Path::parse(&raw_str).ok().map(|n| n.syntax().clone()),
120 pattern: ast::Pat::parse(&raw_str).ok().map(|n| n.syntax().clone()),
121 placeholders_by_stand_in: raw.placeholders_by_stand_in(),
122 raw,
123 };
124 if res.expr.is_none()
125 && res.type_ref.is_none()
126 && res.item.is_none()
127 && res.path.is_none()
128 && res.pattern.is_none()
129 {
130 bail!("Pattern is not a valid Rust expression, type, item, path or pattern");
131 }
132 Ok(res)
133 }
134}
135
136impl FromStr for SsrTemplate {
137 type Err = SsrError;
138
139 fn from_str(pattern_str: &str) -> Result<SsrTemplate, SsrError> {
140 let tokens = parse_pattern(pattern_str)?;
141 // Validate that the template is a valid fragment of Rust code. We reuse the validation
142 // logic for search patterns since the only thing that differs is the error message.
143 if SsrPattern::from_str(pattern_str).is_err() {
144 bail!("Replacement is not a valid Rust expression, type, item, path or pattern");
145 }
146 // Our actual template needs to preserve whitespace, so we can't reuse `tokens`.
147 Ok(SsrTemplate { tokens })
148 } 194 }
149} 195}
150 196
@@ -173,7 +219,7 @@ fn parse_pattern(pattern_str: &str) -> Result<Vec<PatternElement>, SsrError> {
173/// pattern didn't define. 219/// pattern didn't define.
174fn validate_rule(rule: &SsrRule) -> Result<(), SsrError> { 220fn validate_rule(rule: &SsrRule) -> Result<(), SsrError> {
175 let mut defined_placeholders = FxHashSet::default(); 221 let mut defined_placeholders = FxHashSet::default();
176 for p in &rule.pattern.raw.tokens { 222 for p in &rule.pattern.tokens {
177 if let PatternElement::Placeholder(placeholder) = p { 223 if let PatternElement::Placeholder(placeholder) = p {
178 defined_placeholders.insert(&placeholder.ident); 224 defined_placeholders.insert(&placeholder.ident);
179 } 225 }
@@ -316,7 +362,7 @@ mod tests {
316 } 362 }
317 let result: SsrRule = "foo($a, $b) ==>> bar($b, $a)".parse().unwrap(); 363 let result: SsrRule = "foo($a, $b) ==>> bar($b, $a)".parse().unwrap();
318 assert_eq!( 364 assert_eq!(
319 result.pattern.raw.tokens, 365 result.pattern.tokens,
320 vec![ 366 vec![
321 token(SyntaxKind::IDENT, "foo"), 367 token(SyntaxKind::IDENT, "foo"),
322 token(T!['('], "("), 368 token(T!['('], "("),