mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 04:53:34 +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(
|
||||
&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))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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(¶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> {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
Loading…
Reference in a new issue