3540: Swtches to rust SSR query check r=matklad a=mikhail-m1

related to #3186 

Co-authored-by: Mikhail Modin <mikhailm1@gmail.com>
This commit is contained in:
bors[bot] 2020-03-16 09:48:09 +00:00 committed by GitHub
commit a99cac671c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 63 additions and 20 deletions

View file

@ -473,9 +473,10 @@ impl Analysis {
pub fn structural_search_replace( pub fn structural_search_replace(
&self, &self,
query: &str, query: &str,
parse_only: bool,
) -> Cancelable<Result<SourceChange, SsrError>> { ) -> Cancelable<Result<SourceChange, SsrError>> {
self.with_db(|db| { 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)) Ok(SourceChange::source_file_edits("ssr", edits))
}) })
} }

View file

@ -1,8 +1,10 @@
//! structural search replace //! structural search replace
use crate::source_change::SourceFileEdit; use crate::source_change::SourceFileEdit;
use ra_db::{SourceDatabase, SourceDatabaseExt};
use ra_ide_db::symbol_index::SymbolsDatabase;
use ra_ide_db::RootDatabase; 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::ast::{AstToken, Comment};
use ra_syntax::{AstNode, SyntaxElement, SyntaxNode}; use ra_syntax::{AstNode, SyntaxElement, SyntaxNode};
use ra_text_edit::{TextEdit, TextEditBuilder}; use ra_text_edit::{TextEdit, TextEditBuilder};
@ -10,9 +12,6 @@ use rustc_hash::FxHashMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::str::FromStr; use std::str::FromStr;
pub use ra_db::{SourceDatabase, SourceDatabaseExt};
use ra_ide_db::symbol_index::SymbolsDatabase;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct SsrError(String); pub struct SsrError(String);
@ -26,14 +25,17 @@ impl std::error::Error for SsrError {}
pub fn parse_search_replace( pub fn parse_search_replace(
query: &str, query: &str,
parse_only: bool,
db: &RootDatabase, db: &RootDatabase,
) -> Result<Vec<SourceFileEdit>, SsrError> { ) -> Result<Vec<SourceFileEdit>, SsrError> {
let mut edits = vec![]; let mut edits = vec![];
let query: SsrQuery = query.parse()?; let query: SsrQuery = query.parse()?;
if parse_only {
return Ok(edits);
}
for &root in db.local_roots().iter() { for &root in db.local_roots().iter() {
let sr = db.source_root(root); let sr = db.source_root(root);
for file_id in sr.walk() { for file_id in sr.walk() {
dbg!(db.file_relative_path(file_id));
let matches = find(&query.pattern, db.parse(file_id).tree().syntax()); let matches = find(&query.pattern, db.parse(file_id).tree().syntax());
if !matches.matches.is_empty() { if !matches.matches.is_empty() {
edits.push(SourceFileEdit { file_id, edit: replace(&matches, &query.template) }); 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); 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(); let mut placeholders = FxHashMap::default();
traverse(&template, &mut |n| { 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 }; let template = SsrTemplate { template, placeholders };
Ok(SsrQuery { pattern, template }) 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[0].0, "__search_pattern_a");
assert_eq!(result.pattern.vars[1].0, "__search_pattern_b"); assert_eq!(result.pattern.vars[1].0, "__search_pattern_b");
assert_eq!(&result.template.template.text(), "bar(__search_pattern_b, __search_pattern_a)"); assert_eq!(&result.template.template.text(), "bar(__search_pattern_b, __search_pattern_a)");
dbg!(result.template.placeholders);
} }
#[test] #[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] #[test]
fn parse_match_replace() { fn parse_match_replace() {
let query: SsrQuery = "foo($x:expr) ==>> bar($x)".parse().unwrap(); let query: SsrQuery = "foo($x:expr) ==>> bar($x)".parse().unwrap();

View file

@ -112,10 +112,14 @@ pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr {
let token = token(op); let token = token(op);
expr_from_text(&format!("{}{}", token, expr.syntax())) 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)) 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 { pub fn bind_pat(name: ast::Name) -> ast::BindPat {
return from_text(name.text()); return from_text(name.text());
@ -239,6 +243,16 @@ fn ast_from_text<N: AstNode>(text: &str) -> N {
node 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 { fn unroot(n: SyntaxNode) -> SyntaxNode {
SyntaxNode::new_root(n.green().clone()) SyntaxNode::new_root(n.green().clone())
} }

View file

@ -932,7 +932,10 @@ pub fn handle_document_highlight(
pub fn handle_ssr(world: WorldSnapshot, params: req::SsrParams) -> Result<req::SourceChange> { pub fn handle_ssr(world: WorldSnapshot, params: req::SsrParams) -> Result<req::SourceChange> {
let _p = profile("handle_ssr"); 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> { pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> {

View file

@ -218,6 +218,8 @@ impl Request for Ssr {
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SsrParams { pub struct SsrParams {
pub arg: String, pub query: String,
pub parse_only: bool,
} }

View file

@ -10,20 +10,22 @@ export function ssr(ctx: Ctx): Cmd {
if (!client) return; if (!client) return;
const options: vscode.InputBoxOptions = { const options: vscode.InputBoxOptions = {
placeHolder: "foo($a:expr, $b:expr) ==>> bar($a, foo($b))", value: "() ==>> ()",
prompt: "Enter request", prompt: "EnteR request, for example 'Foo($a:expr) ==> Foo::new($a)' ",
validateInput: (x: string) => { validateInput: async (x: string) => {
if (x.includes('==>>')) { try {
return null; 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); const request = await vscode.window.showInputBox(options);
if (!request) return; 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); await applySourceChange(ctx, change);
}; };

View file

@ -107,7 +107,8 @@ export const inlayHints = request<InlayHintsParams, Vec<InlayHint>>("inlayHints"
export interface SsrParams { export interface SsrParams {
arg: string; query: string;
parseOnly: boolean;
} }
export const ssr = request<SsrParams, SourceChange>("ssr"); export const ssr = request<SsrParams, SourceChange>("ssr");