diff options
Diffstat (limited to 'crates/ra_ssr/src/parsing.rs')
-rw-r--r-- | crates/ra_ssr/src/parsing.rs | 142 |
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 | ||
8 | use crate::errors::bail; | 8 | use crate::errors::bail; |
9 | use crate::{SsrError, SsrPattern, SsrRule}; | 9 | use crate::{SsrError, SsrPattern, SsrRule}; |
10 | use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, T}; | 10 | use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, T}; |
11 | use rustc_hash::{FxHashMap, FxHashSet}; | 11 | use rustc_hash::{FxHashMap, FxHashSet}; |
12 | use std::str::FromStr; | 12 | use std::str::FromStr; |
13 | use test_utils::mark; | ||
13 | 14 | ||
14 | #[derive(Clone, Debug)] | 15 | #[derive(Debug)] |
15 | pub(crate) struct SsrTemplate { | 16 | pub(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)] |
20 | pub(crate) struct RawSearchPattern { | 23 | pub(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 | ||
60 | impl 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 | |||
81 | struct RuleBuilder { | ||
82 | placeholders_by_stand_in: FxHashMap<SmolStr, Placeholder>, | ||
83 | rules: Vec<ParsedRule>, | ||
84 | } | ||
85 | |||
86 | impl 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`. | ||
127 | fn contains_path(node: &SyntaxNode) -> bool { | ||
128 | node.kind() == SyntaxKind::PATH | ||
129 | || node.descendants().any(|node| node.kind() == SyntaxKind::PATH) | ||
130 | } | ||
131 | |||
57 | impl FromStr for SsrRule { | 132 | impl 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 | ||
77 | impl FromStr for RawSearchPattern { | 155 | impl 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 | ||
85 | impl RawSearchPattern { | 163 | impl 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 | |||
136 | impl 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. |
174 | fn validate_rule(rule: &SsrRule) -> Result<(), SsrError> { | 220 | fn 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!['('], "("), |