aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_ide/src/completion/complete_keyword.rs6
-rw-r--r--crates/ra_ide/src/completion/presentation.rs50
-rw-r--r--crates/ra_ide/src/ssr.rs12
-rw-r--r--crates/ra_ssr/Cargo.toml1
-rw-r--r--crates/ra_ssr/src/matching.rs39
-rw-r--r--crates/ra_ssr/src/parsing.rs108
-rw-r--r--crates/ra_ssr/src/tests.rs17
7 files changed, 218 insertions, 15 deletions
diff --git a/crates/ra_ide/src/completion/complete_keyword.rs b/crates/ra_ide/src/completion/complete_keyword.rs
index 3b174f916..e599cc3d1 100644
--- a/crates/ra_ide/src/completion/complete_keyword.rs
+++ b/crates/ra_ide/src/completion/complete_keyword.rs
@@ -1,6 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use ra_syntax::ast; 3use ra_syntax::{ast, SyntaxKind};
4 4
5use crate::completion::{ 5use crate::completion::{
6 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, 6 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions,
@@ -37,6 +37,10 @@ pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC
37} 37}
38 38
39pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { 39pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
40 if ctx.token.kind() == SyntaxKind::COMMENT {
41 return;
42 }
43
40 let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent; 44 let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent;
41 if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling { 45 if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling {
42 add_keyword(ctx, acc, "where", "where "); 46 add_keyword(ctx, acc, "where", "where ");
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs
index 4fdc2f0bb..b18279746 100644
--- a/crates/ra_ide/src/completion/presentation.rs
+++ b/crates/ra_ide/src/completion/presentation.rs
@@ -1516,4 +1516,54 @@ mod tests {
1516 "### 1516 "###
1517 ); 1517 );
1518 } 1518 }
1519
1520 #[test]
1521 fn no_keyword_autocompletion_on_line_comments() {
1522 assert_debug_snapshot!(
1523 do_completion(
1524 r"
1525 fn test() {
1526 let x = 2; // A comment<|>
1527 }
1528 ",
1529 CompletionKind::Keyword
1530 ),
1531 @r###"
1532 []
1533 "###
1534 );
1535 }
1536
1537 #[test]
1538 fn no_keyword_autocompletion_on_multi_line_comments() {
1539 assert_debug_snapshot!(
1540 do_completion(
1541 r"
1542 /*
1543 Some multi-line comment<|>
1544 */
1545 ",
1546 CompletionKind::Keyword
1547 ),
1548 @r###"
1549 []
1550 "###
1551 );
1552 }
1553
1554 #[test]
1555 fn no_keyword_autocompletion_on_doc_comments() {
1556 assert_debug_snapshot!(
1557 do_completion(
1558 r"
1559 /// Some doc comment
1560 /// let test<|> = 1
1561 ",
1562 CompletionKind::Keyword
1563 ),
1564 @r###"
1565 []
1566 "###
1567 );
1568 }
1519} 1569}
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 9f8e540c0..b3e9e5dfe 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -10,6 +10,18 @@ use ra_ssr::{MatchFinder, SsrError, SsrRule};
10// The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. 10// The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`.
11// A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement. 11// A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement.
12// Within a macro call, a placeholder will match up until whatever token follows the placeholder. 12// Within a macro call, a placeholder will match up until whatever token follows the placeholder.
13//
14// Placeholders may be given constraints by writing them as `${<name>:<constraint1>:<constraint2>...}`.
15//
16// Supported constraints:
17//
18// |===
19// | Constraint | Restricts placeholder
20//
21// | kind(literal) | Is a literal (e.g. `42` or `"forty two"`)
22// | not(a) | Negates the constraint `a`
23// |===
24//
13// Available via the command `rust-analyzer.ssr`. 25// Available via the command `rust-analyzer.ssr`.
14// 26//
15// ```rust 27// ```rust
diff --git a/crates/ra_ssr/Cargo.toml b/crates/ra_ssr/Cargo.toml
index 3c2f15a83..fe098aaee 100644
--- a/crates/ra_ssr/Cargo.toml
+++ b/crates/ra_ssr/Cargo.toml
@@ -17,3 +17,4 @@ ra_db = { path = "../ra_db" }
17ra_ide_db = { path = "../ra_ide_db" } 17ra_ide_db = { path = "../ra_ide_db" }
18hir = { path = "../ra_hir", package = "ra_hir" } 18hir = { path = "../ra_hir", package = "ra_hir" }
19rustc-hash = "1.1.0" 19rustc-hash = "1.1.0"
20test_utils = { path = "../test_utils" }
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs
index 53d802e77..ce53d46d2 100644
--- a/crates/ra_ssr/src/matching.rs
+++ b/crates/ra_ssr/src/matching.rs
@@ -2,7 +2,7 @@
2//! process of matching, placeholder values are recorded. 2//! process of matching, placeholder values are recorded.
3 3
4use crate::{ 4use crate::{
5 parsing::{Placeholder, SsrTemplate}, 5 parsing::{Constraint, NodeKind, Placeholder, SsrTemplate},
6 SsrMatches, SsrPattern, SsrRule, 6 SsrMatches, SsrPattern, SsrRule,
7}; 7};
8use hir::Semantics; 8use hir::Semantics;
@@ -11,6 +11,7 @@ use ra_syntax::ast::{AstNode, AstToken};
11use ra_syntax::{ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken}; 11use ra_syntax::{ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken};
12use rustc_hash::FxHashMap; 12use rustc_hash::FxHashMap;
13use std::{cell::Cell, iter::Peekable}; 13use std::{cell::Cell, iter::Peekable};
14use test_utils::mark;
14 15
15// Creates a match error. If we're currently attempting to match some code that we thought we were 16// Creates a match error. If we're currently attempting to match some code that we thought we were
16// going to match, as indicated by the --debug-snippet flag, then populate the reason field. 17// going to match, as indicated by the --debug-snippet flag, then populate the reason field.
@@ -169,6 +170,9 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
169 if let Some(placeholder) = 170 if let Some(placeholder) =
170 match_inputs.get_placeholder(&SyntaxElement::Node(pattern.clone())) 171 match_inputs.get_placeholder(&SyntaxElement::Node(pattern.clone()))
171 { 172 {
173 for constraint in &placeholder.constraints {
174 self.check_constraint(constraint, code)?;
175 }
172 if self.match_out.is_none() { 176 if self.match_out.is_none() {
173 return Ok(()); 177 return Ok(());
174 } 178 }
@@ -292,6 +296,24 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
292 Ok(()) 296 Ok(())
293 } 297 }
294 298
299 fn check_constraint(
300 &self,
301 constraint: &Constraint,
302 code: &SyntaxNode,
303 ) -> Result<(), MatchFailed> {
304 match constraint {
305 Constraint::Kind(kind) => {
306 kind.matches(code)?;
307 }
308 Constraint::Not(sub) => {
309 if self.check_constraint(&*sub, code).is_ok() {
310 fail_match!("Constraint {:?} failed for '{}'", constraint, code.text());
311 }
312 }
313 }
314 Ok(())
315 }
316
295 /// We want to allow the records to match in any order, so we have special matching logic for 317 /// We want to allow the records to match in any order, so we have special matching logic for
296 /// them. 318 /// them.
297 fn attempt_match_record_field_list( 319 fn attempt_match_record_field_list(
@@ -515,6 +537,21 @@ impl SsrPattern {
515 } 537 }
516} 538}
517 539
540impl NodeKind {
541 fn matches(&self, node: &SyntaxNode) -> Result<(), MatchFailed> {
542 let ok = match self {
543 Self::Literal => {
544 mark::hit!(literal_constraint);
545 ast::Literal::can_cast(node.kind())
546 }
547 };
548 if !ok {
549 fail_match!("Code '{}' isn't of kind {:?}", node.text(), self);
550 }
551 Ok(())
552 }
553}
554
518// If `node` contains nothing but an ident then return it, otherwise return None. 555// If `node` contains nothing but an ident then return it, otherwise return None.
519fn only_ident(element: SyntaxElement) -> Option<SyntaxToken> { 556fn only_ident(element: SyntaxElement) -> Option<SyntaxToken> {
520 match element { 557 match element {
diff --git a/crates/ra_ssr/src/parsing.rs b/crates/ra_ssr/src/parsing.rs
index 04d46bd32..5ea125616 100644
--- a/crates/ra_ssr/src/parsing.rs
+++ b/crates/ra_ssr/src/parsing.rs
@@ -6,7 +6,7 @@
6//! e.g. expressions, type references etc. 6//! e.g. expressions, type references etc.
7 7
8use crate::{SsrError, SsrPattern, SsrRule}; 8use crate::{SsrError, SsrPattern, SsrRule};
9use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind}; 9use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, T};
10use rustc_hash::{FxHashMap, FxHashSet}; 10use rustc_hash::{FxHashMap, FxHashSet};
11use std::str::FromStr; 11use std::str::FromStr;
12 12
@@ -39,6 +39,18 @@ pub(crate) struct Placeholder {
39 pub(crate) ident: SmolStr, 39 pub(crate) ident: SmolStr,
40 /// A unique name used in place of this placeholder when we parse the pattern as Rust code. 40 /// A unique name used in place of this placeholder when we parse the pattern as Rust code.
41 stand_in_name: String, 41 stand_in_name: String,
42 pub(crate) constraints: Vec<Constraint>,
43}
44
45#[derive(Clone, Debug, PartialEq, Eq)]
46pub(crate) enum Constraint {
47 Kind(NodeKind),
48 Not(Box<Constraint>),
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
52pub(crate) enum NodeKind {
53 Literal,
42} 54}
43 55
44#[derive(Debug, Clone, PartialEq, Eq)] 56#[derive(Debug, Clone, PartialEq, Eq)]
@@ -149,7 +161,7 @@ fn parse_pattern(pattern_str: &str) -> Result<Vec<PatternElement>, SsrError> {
149 let mut placeholder_names = FxHashSet::default(); 161 let mut placeholder_names = FxHashSet::default();
150 let mut tokens = tokenize(pattern_str)?.into_iter(); 162 let mut tokens = tokenize(pattern_str)?.into_iter();
151 while let Some(token) = tokens.next() { 163 while let Some(token) = tokens.next() {
152 if token.kind == SyntaxKind::DOLLAR { 164 if token.kind == T![$] {
153 let placeholder = parse_placeholder(&mut tokens)?; 165 let placeholder = parse_placeholder(&mut tokens)?;
154 if !placeholder_names.insert(placeholder.ident.clone()) { 166 if !placeholder_names.insert(placeholder.ident.clone()) {
155 bail!("Name `{}` repeats more than once", placeholder.ident); 167 bail!("Name `{}` repeats more than once", placeholder.ident);
@@ -177,6 +189,9 @@ fn validate_rule(rule: &SsrRule) -> Result<(), SsrError> {
177 if !defined_placeholders.contains(&placeholder.ident) { 189 if !defined_placeholders.contains(&placeholder.ident) {
178 undefined.push(format!("${}", placeholder.ident)); 190 undefined.push(format!("${}", placeholder.ident));
179 } 191 }
192 if !placeholder.constraints.is_empty() {
193 bail!("Replacement placeholders cannot have constraints");
194 }
180 } 195 }
181 } 196 }
182 if !undefined.is_empty() { 197 if !undefined.is_empty() {
@@ -205,23 +220,90 @@ fn tokenize(source: &str) -> Result<Vec<Token>, SsrError> {
205 220
206fn parse_placeholder(tokens: &mut std::vec::IntoIter<Token>) -> Result<Placeholder, SsrError> { 221fn parse_placeholder(tokens: &mut std::vec::IntoIter<Token>) -> Result<Placeholder, SsrError> {
207 let mut name = None; 222 let mut name = None;
223 let mut constraints = Vec::new();
208 if let Some(token) = tokens.next() { 224 if let Some(token) = tokens.next() {
209 match token.kind { 225 match token.kind {
210 SyntaxKind::IDENT => { 226 SyntaxKind::IDENT => {
211 name = Some(token.text); 227 name = Some(token.text);
212 } 228 }
229 T!['{'] => {
230 let token =
231 tokens.next().ok_or_else(|| SsrError::new("Unexpected end of placeholder"))?;
232 if token.kind == SyntaxKind::IDENT {
233 name = Some(token.text);
234 }
235 loop {
236 let token = tokens
237 .next()
238 .ok_or_else(|| SsrError::new("Placeholder is missing closing brace '}'"))?;
239 match token.kind {
240 T![:] => {
241 constraints.push(parse_constraint(tokens)?);
242 }
243 T!['}'] => break,
244 _ => bail!("Unexpected token while parsing placeholder: '{}'", token.text),
245 }
246 }
247 }
213 _ => { 248 _ => {
214 bail!("Placeholders should be $name"); 249 bail!("Placeholders should either be $name or ${name:constraints}");
215 } 250 }
216 } 251 }
217 } 252 }
218 let name = name.ok_or_else(|| SsrError::new("Placeholder ($) with no name"))?; 253 let name = name.ok_or_else(|| SsrError::new("Placeholder ($) with no name"))?;
219 Ok(Placeholder::new(name)) 254 Ok(Placeholder::new(name, constraints))
255}
256
257fn parse_constraint(tokens: &mut std::vec::IntoIter<Token>) -> Result<Constraint, SsrError> {
258 let constraint_type = tokens
259 .next()
260 .ok_or_else(|| SsrError::new("Found end of placeholder while looking for a constraint"))?
261 .text
262 .to_string();
263 match constraint_type.as_str() {
264 "kind" => {
265 expect_token(tokens, "(")?;
266 let t = tokens.next().ok_or_else(|| {
267 SsrError::new("Unexpected end of constraint while looking for kind")
268 })?;
269 if t.kind != SyntaxKind::IDENT {
270 bail!("Expected ident, found {:?} while parsing kind constraint", t.kind);
271 }
272 expect_token(tokens, ")")?;
273 Ok(Constraint::Kind(NodeKind::from(&t.text)?))
274 }
275 "not" => {
276 expect_token(tokens, "(")?;
277 let sub = parse_constraint(tokens)?;
278 expect_token(tokens, ")")?;
279 Ok(Constraint::Not(Box::new(sub)))
280 }
281 x => bail!("Unsupported constraint type '{}'", x),
282 }
283}
284
285fn expect_token(tokens: &mut std::vec::IntoIter<Token>, expected: &str) -> Result<(), SsrError> {
286 if let Some(t) = tokens.next() {
287 if t.text == expected {
288 return Ok(());
289 }
290 bail!("Expected {} found {}", expected, t.text);
291 }
292 bail!("Expected {} found end of stream");
293}
294
295impl NodeKind {
296 fn from(name: &SmolStr) -> Result<NodeKind, SsrError> {
297 Ok(match name.as_str() {
298 "literal" => NodeKind::Literal,
299 _ => bail!("Unknown node kind '{}'", name),
300 })
301 }
220} 302}
221 303
222impl Placeholder { 304impl Placeholder {
223 fn new(name: SmolStr) -> Self { 305 fn new(name: SmolStr, constraints: Vec<Constraint>) -> Self {
224 Self { stand_in_name: format!("__placeholder_{}", name), ident: name } 306 Self { stand_in_name: format!("__placeholder_{}", name), constraints, ident: name }
225 } 307 }
226} 308}
227 309
@@ -241,31 +323,31 @@ mod tests {
241 PatternElement::Token(Token { kind, text: SmolStr::new(text) }) 323 PatternElement::Token(Token { kind, text: SmolStr::new(text) })
242 } 324 }
243 fn placeholder(name: &str) -> PatternElement { 325 fn placeholder(name: &str) -> PatternElement {
244 PatternElement::Placeholder(Placeholder::new(SmolStr::new(name))) 326 PatternElement::Placeholder(Placeholder::new(SmolStr::new(name), Vec::new()))
245 } 327 }
246 let result: SsrRule = "foo($a, $b) ==>> bar($b, $a)".parse().unwrap(); 328 let result: SsrRule = "foo($a, $b) ==>> bar($b, $a)".parse().unwrap();
247 assert_eq!( 329 assert_eq!(
248 result.pattern.raw.tokens, 330 result.pattern.raw.tokens,
249 vec![ 331 vec![
250 token(SyntaxKind::IDENT, "foo"), 332 token(SyntaxKind::IDENT, "foo"),
251 token(SyntaxKind::L_PAREN, "("), 333 token(T!['('], "("),
252 placeholder("a"), 334 placeholder("a"),
253 token(SyntaxKind::COMMA, ","), 335 token(T![,], ","),
254 token(SyntaxKind::WHITESPACE, " "), 336 token(SyntaxKind::WHITESPACE, " "),
255 placeholder("b"), 337 placeholder("b"),
256 token(SyntaxKind::R_PAREN, ")"), 338 token(T![')'], ")"),
257 ] 339 ]
258 ); 340 );
259 assert_eq!( 341 assert_eq!(
260 result.template.tokens, 342 result.template.tokens,
261 vec![ 343 vec![
262 token(SyntaxKind::IDENT, "bar"), 344 token(SyntaxKind::IDENT, "bar"),
263 token(SyntaxKind::L_PAREN, "("), 345 token(T!['('], "("),
264 placeholder("b"), 346 placeholder("b"),
265 token(SyntaxKind::COMMA, ","), 347 token(T![,], ","),
266 token(SyntaxKind::WHITESPACE, " "), 348 token(SyntaxKind::WHITESPACE, " "),
267 placeholder("a"), 349 placeholder("a"),
268 token(SyntaxKind::R_PAREN, ")"), 350 token(T![')'], ")"),
269 ] 351 ]
270 ); 352 );
271 } 353 }
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs
index c692c97e2..9568d4432 100644
--- a/crates/ra_ssr/src/tests.rs
+++ b/crates/ra_ssr/src/tests.rs
@@ -1,5 +1,6 @@
1use crate::{MatchFinder, SsrRule}; 1use crate::{MatchFinder, SsrRule};
2use ra_db::{FileId, SourceDatabaseExt}; 2use ra_db::{FileId, SourceDatabaseExt};
3use test_utils::mark;
3 4
4fn parse_error_text(query: &str) -> String { 5fn parse_error_text(query: &str) -> String {
5 format!("{}", query.parse::<SsrRule>().unwrap_err()) 6 format!("{}", query.parse::<SsrRule>().unwrap_err())
@@ -302,6 +303,22 @@ fn match_pattern() {
302} 303}
303 304
304#[test] 305#[test]
306fn literal_constraint() {
307 mark::check!(literal_constraint);
308 let code = r#"
309 fn f1() {
310 let x1 = Some(42);
311 let x2 = Some("foo");
312 let x3 = Some(x1);
313 let x4 = Some(40 + 2);
314 let x5 = Some(true);
315 }
316 "#;
317 assert_matches("Some(${a:kind(literal)})", code, &["Some(42)", "Some(\"foo\")", "Some(true)"]);
318 assert_matches("Some(${a:not(kind(literal))})", code, &["Some(x1)", "Some(40 + 2)"]);
319}
320
321#[test]
305fn match_reordered_struct_instantiation() { 322fn match_reordered_struct_instantiation() {
306 assert_matches( 323 assert_matches(
307 "Foo {aa: 1, b: 2, ccc: 3}", 324 "Foo {aa: 1, b: 2, ccc: 3}",