mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
Merge #3540
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:
commit
a99cac671c
7 changed files with 63 additions and 20 deletions
|
@ -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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(¶ms.arg)??.try_conv_with(&world)
|
world
|
||||||
|
.analysis()
|
||||||
|
.structural_search_replace(¶ms.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> {
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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");
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue