From b150965ed7994c679711bc807de301a12f5c7944 Mon Sep 17 00:00:00 2001
From: Mikhail Modin <mikhailm1@gmail.com>
Date: Sun, 15 Mar 2020 21:23:18 +0000
Subject: [PATCH] Swtches to rust SSR query check

---
 crates/ra_ide/src/lib.rs                      |  3 +-
 crates/ra_ide/src/ssr.rs                      | 36 ++++++++++++++-----
 crates/ra_syntax/src/ast/make.rs              | 16 ++++++++-
 .../rust-analyzer/src/main_loop/handlers.rs   |  5 ++-
 crates/rust-analyzer/src/req.rs               |  4 ++-
 editors/code/src/commands/ssr.ts              | 16 +++++----
 editors/code/src/rust-analyzer-api.ts         |  3 +-
 7 files changed, 63 insertions(+), 20 deletions(-)

diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index c60e86aead5..40276d4fef1 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -478,9 +478,10 @@ impl Analysis {
     pub fn structural_search_replace(
         &self,
         query: &str,
+        parse_only: bool,
     ) -> Cancelable<Result<SourceChange, SsrError>> {
         self.with_db(|db| {
-            let edits = ssr::parse_search_replace(query, db)?;
+            let edits = ssr::parse_search_replace(query, parse_only, db)?;
             Ok(SourceChange::source_file_edits("ssr", edits))
         })
     }
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index c011a2e74f3..1c9710a5d51 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -1,8 +1,10 @@
 //!  structural search replace
 
 use crate::source_change::SourceFileEdit;
+use ra_db::{SourceDatabase, SourceDatabaseExt};
+use ra_ide_db::symbol_index::SymbolsDatabase;
 use ra_ide_db::RootDatabase;
-use ra_syntax::ast::make::expr_from_text;
+use ra_syntax::ast::make::try_expr_from_text;
 use ra_syntax::ast::{AstToken, Comment};
 use ra_syntax::{AstNode, SyntaxElement, SyntaxNode};
 use ra_text_edit::{TextEdit, TextEditBuilder};
@@ -10,9 +12,6 @@ use rustc_hash::FxHashMap;
 use std::collections::HashMap;
 use std::str::FromStr;
 
-pub use ra_db::{SourceDatabase, SourceDatabaseExt};
-use ra_ide_db::symbol_index::SymbolsDatabase;
-
 #[derive(Debug, PartialEq)]
 pub struct SsrError(String);
 
@@ -26,14 +25,17 @@ impl std::error::Error for SsrError {}
 
 pub fn parse_search_replace(
     query: &str,
+    parse_only: bool,
     db: &RootDatabase,
 ) -> Result<Vec<SourceFileEdit>, SsrError> {
     let mut edits = vec![];
     let query: SsrQuery = query.parse()?;
+    if parse_only {
+        return Ok(edits);
+    }
     for &root in db.local_roots().iter() {
         let sr = db.source_root(root);
         for file_id in sr.walk() {
-            dbg!(db.file_relative_path(file_id));
             let matches = find(&query.pattern, db.parse(file_id).tree().syntax());
             if !matches.matches.is_empty() {
                 edits.push(SourceFileEdit { file_id, edit: replace(&matches, &query.template) });
@@ -106,7 +108,10 @@ impl FromStr for SsrQuery {
             template = replace_in_template(template, var, new_var);
         }
 
-        let template = expr_from_text(&template).syntax().clone();
+        let template = try_expr_from_text(&template)
+            .ok_or(SsrError("Template is not an expression".into()))?
+            .syntax()
+            .clone();
         let mut placeholders = FxHashMap::default();
 
         traverse(&template, &mut |n| {
@@ -118,7 +123,13 @@ impl FromStr for SsrQuery {
             }
         });
 
-        let pattern = SsrPattern { pattern: expr_from_text(&pattern).syntax().clone(), vars };
+        let pattern = SsrPattern {
+            pattern: try_expr_from_text(&pattern)
+                .ok_or(SsrError("Pattern is not an expression".into()))?
+                .syntax()
+                .clone(),
+            vars,
+        };
         let template = SsrTemplate { template, placeholders };
         Ok(SsrQuery { pattern, template })
     }
@@ -284,7 +295,6 @@ mod tests {
         assert_eq!(result.pattern.vars[0].0, "__search_pattern_a");
         assert_eq!(result.pattern.vars[1].0, "__search_pattern_b");
         assert_eq!(&result.template.template.text(), "bar(__search_pattern_b, __search_pattern_a)");
-        dbg!(result.template.placeholders);
     }
 
     #[test]
@@ -334,6 +344,16 @@ mod tests {
         );
     }
 
+    #[test]
+    fn parser_invlid_pattern() {
+        assert_eq!(parse_error_text(" ==>> ()"), "Parse error: Pattern is not an expression");
+    }
+
+    #[test]
+    fn parser_invlid_template() {
+        assert_eq!(parse_error_text("() ==>> )"), "Parse error: Template is not an expression");
+    }
+
     #[test]
     fn parse_match_replace() {
         let query: SsrQuery = "foo($x:expr) ==>> bar($x)".parse().unwrap();
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index ae8829807ce..9f6f1cc5310 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -112,10 +112,14 @@ pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr {
     let token = token(op);
     expr_from_text(&format!("{}{}", token, expr.syntax()))
 }
-pub fn expr_from_text(text: &str) -> ast::Expr {
+fn expr_from_text(text: &str) -> ast::Expr {
     ast_from_text(&format!("const C: () = {};", text))
 }
 
+pub fn try_expr_from_text(text: &str) -> Option<ast::Expr> {
+    try_ast_from_text(&format!("const C: () = {};", text))
+}
+
 pub fn bind_pat(name: ast::Name) -> ast::BindPat {
     return from_text(name.text());
 
@@ -239,6 +243,16 @@ fn ast_from_text<N: AstNode>(text: &str) -> N {
     node
 }
 
+fn try_ast_from_text<N: AstNode>(text: &str) -> Option<N> {
+    let parse = SourceFile::parse(text);
+    let node = parse.tree().syntax().descendants().find_map(N::cast)?;
+    let node = node.syntax().clone();
+    let node = unroot(node);
+    let node = N::cast(node).unwrap();
+    assert_eq!(node.syntax().text_range().start(), 0.into());
+    Some(node)
+}
+
 fn unroot(n: SyntaxNode) -> SyntaxNode {
     SyntaxNode::new_root(n.green().clone())
 }
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index 8dc6e8dc05d..3a31a6dd761 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -906,7 +906,10 @@ pub fn handle_document_highlight(
 
 pub fn handle_ssr(world: WorldSnapshot, params: req::SsrParams) -> Result<req::SourceChange> {
     let _p = profile("handle_ssr");
-    world.analysis().structural_search_replace(&params.arg)??.try_conv_with(&world)
+    world
+        .analysis()
+        .structural_search_replace(&params.query, params.parse_only)??
+        .try_conv_with(&world)
 }
 
 pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> {
diff --git a/crates/rust-analyzer/src/req.rs b/crates/rust-analyzer/src/req.rs
index a3efe3b9feb..435f717aeb2 100644
--- a/crates/rust-analyzer/src/req.rs
+++ b/crates/rust-analyzer/src/req.rs
@@ -217,6 +217,8 @@ impl Request for Ssr {
 }
 
 #[derive(Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
 pub struct SsrParams {
-    pub arg: String,
+    pub query: String,
+    pub parse_only: bool,
 }
diff --git a/editors/code/src/commands/ssr.ts b/editors/code/src/commands/ssr.ts
index eee48c69352..6fee051fdf6 100644
--- a/editors/code/src/commands/ssr.ts
+++ b/editors/code/src/commands/ssr.ts
@@ -10,20 +10,22 @@ export function ssr(ctx: Ctx): Cmd {
         if (!client) return;
 
         const options: vscode.InputBoxOptions = {
-            placeHolder: "foo($a:expr, $b:expr) ==>> bar($a, foo($b))",
-            prompt: "Enter request",
-            validateInput: (x: string) => {
-                if (x.includes('==>>')) {
-                    return null;
+            value: "() ==>> ()",
+            prompt: "EnteR request, for example 'Foo($a:expr) ==> Foo::new($a)' ",
+            validateInput: async (x: string) => {
+                try {
+                    await client.sendRequest(ra.ssr, { query: x, parseOnly: true });
+                } catch (e) {
+                    return e.toString();
                 }
-                return "Enter request: pattern ==>> template";
+                return null;
             }
         };
         const request = await vscode.window.showInputBox(options);
 
         if (!request) return;
 
-        const change = await client.sendRequest(ra.ssr, { arg: request });
+        const change = await client.sendRequest(ra.ssr, { query: request, parseOnly: false });
 
         await applySourceChange(ctx, change);
     };
diff --git a/editors/code/src/rust-analyzer-api.ts b/editors/code/src/rust-analyzer-api.ts
index bd6e3ada083..6ad93715f93 100644
--- a/editors/code/src/rust-analyzer-api.ts
+++ b/editors/code/src/rust-analyzer-api.ts
@@ -108,7 +108,8 @@ export const inlayHints = request<InlayHintsParams, Vec<InlayHint>>("inlayHints"
 
 
 export interface SsrParams {
-    arg: string;
+    query: string;
+    parseOnly: boolean;
 }
 export const ssr = request<SsrParams, SourceChange>("ssr");