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(
&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))
})
}

View file

@ -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();

View file

@ -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())
}

View file

@ -932,7 +932,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> {

View file

@ -218,6 +218,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,
}

View file

@ -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);
};

View file

@ -107,7 +107,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");