mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Structured search debugging
This commit is contained in:
parent
b1a2d01645
commit
95f8310514
8 changed files with 285 additions and 163 deletions
|
@ -9,10 +9,11 @@ mod replacing;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use crate::matching::Match;
|
pub use crate::matching::Match;
|
||||||
|
use crate::matching::{record_match_fails_reasons_scope, MatchFailureReason};
|
||||||
use hir::Semantics;
|
use hir::Semantics;
|
||||||
use ra_db::{FileId, FileRange};
|
use ra_db::{FileId, FileRange};
|
||||||
use ra_syntax::{ast, AstNode, SmolStr, SyntaxNode};
|
use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, TextRange};
|
||||||
use ra_text_edit::TextEdit;
|
use ra_text_edit::TextEdit;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ pub struct SsrRule {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct SsrPattern {
|
pub struct SsrPattern {
|
||||||
raw: parsing::RawSearchPattern,
|
raw: parsing::RawSearchPattern,
|
||||||
/// Placeholders keyed by the stand-in ident that we use in Rust source code.
|
/// Placeholders keyed by the stand-in ident that we use in Rust source code.
|
||||||
placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
|
placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
|
||||||
|
@ -45,7 +46,7 @@ pub struct SsrError(String);
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct SsrMatches {
|
pub struct SsrMatches {
|
||||||
matches: Vec<Match>,
|
pub matches: Vec<Match>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Searches a crate for pattern matches and possibly replaces them with something else.
|
/// Searches a crate for pattern matches and possibly replaces them with something else.
|
||||||
|
@ -64,6 +65,12 @@ impl<'db> MatchFinder<'db> {
|
||||||
self.rules.push(rule);
|
self.rules.push(rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you
|
||||||
|
/// intend to do replacement, use `add_rule` instead.
|
||||||
|
pub fn add_search_pattern(&mut self, pattern: SsrPattern) {
|
||||||
|
self.add_rule(SsrRule { pattern, template: "()".parse().unwrap() })
|
||||||
|
}
|
||||||
|
|
||||||
pub fn edits_for_file(&self, file_id: FileId) -> Option<TextEdit> {
|
pub fn edits_for_file(&self, file_id: FileId) -> Option<TextEdit> {
|
||||||
let matches = self.find_matches_in_file(file_id);
|
let matches = self.find_matches_in_file(file_id);
|
||||||
if matches.matches.is_empty() {
|
if matches.matches.is_empty() {
|
||||||
|
@ -74,7 +81,7 @@ impl<'db> MatchFinder<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_matches_in_file(&self, file_id: FileId) -> SsrMatches {
|
pub fn find_matches_in_file(&self, file_id: FileId) -> SsrMatches {
|
||||||
let file = self.sema.parse(file_id);
|
let file = self.sema.parse(file_id);
|
||||||
let code = file.syntax();
|
let code = file.syntax();
|
||||||
let mut matches = SsrMatches::default();
|
let mut matches = SsrMatches::default();
|
||||||
|
@ -82,6 +89,32 @@ impl<'db> MatchFinder<'db> {
|
||||||
matches
|
matches
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match
|
||||||
|
/// them, while recording reasons why they don't match. This API is useful for command
|
||||||
|
/// line-based debugging where providing a range is difficult.
|
||||||
|
pub fn debug_where_text_equal(&self, file_id: FileId, snippet: &str) -> Vec<MatchDebugInfo> {
|
||||||
|
use ra_db::SourceDatabaseExt;
|
||||||
|
let file = self.sema.parse(file_id);
|
||||||
|
let mut res = Vec::new();
|
||||||
|
let file_text = self.sema.db.file_text(file_id);
|
||||||
|
let mut remaining_text = file_text.as_str();
|
||||||
|
let mut base = 0;
|
||||||
|
let len = snippet.len() as u32;
|
||||||
|
while let Some(offset) = remaining_text.find(snippet) {
|
||||||
|
let start = base + offset as u32;
|
||||||
|
let end = start + len;
|
||||||
|
self.output_debug_for_nodes_at_range(
|
||||||
|
file.syntax(),
|
||||||
|
FileRange { file_id, range: TextRange::new(start.into(), end.into()) },
|
||||||
|
&None,
|
||||||
|
&mut res,
|
||||||
|
);
|
||||||
|
remaining_text = &remaining_text[offset + snippet.len()..];
|
||||||
|
base = end;
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
fn find_matches(
|
fn find_matches(
|
||||||
&self,
|
&self,
|
||||||
code: &SyntaxNode,
|
code: &SyntaxNode,
|
||||||
|
@ -128,6 +161,59 @@ impl<'db> MatchFinder<'db> {
|
||||||
self.find_matches(&child, restrict_range, matches_out);
|
self.find_matches(&child, restrict_range, matches_out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn output_debug_for_nodes_at_range(
|
||||||
|
&self,
|
||||||
|
node: &SyntaxNode,
|
||||||
|
range: FileRange,
|
||||||
|
restrict_range: &Option<FileRange>,
|
||||||
|
out: &mut Vec<MatchDebugInfo>,
|
||||||
|
) {
|
||||||
|
for node in node.children() {
|
||||||
|
let node_range = self.sema.original_range(&node);
|
||||||
|
if node_range.file_id != range.file_id || !node_range.range.contains_range(range.range)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if node_range.range == range.range {
|
||||||
|
for rule in &self.rules {
|
||||||
|
let pattern =
|
||||||
|
rule.pattern.tree_for_kind_with_reason(node.kind()).map(|p| p.clone());
|
||||||
|
out.push(MatchDebugInfo {
|
||||||
|
matched: matching::get_match(true, rule, &node, restrict_range, &self.sema)
|
||||||
|
.map_err(|e| MatchFailureReason {
|
||||||
|
reason: e.reason.unwrap_or_else(|| {
|
||||||
|
"Match failed, but no reason was given".to_owned()
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
pattern,
|
||||||
|
node: node.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if let Some(macro_call) = ast::MacroCall::cast(node.clone()) {
|
||||||
|
if let Some(expanded) = self.sema.expand(¯o_call) {
|
||||||
|
if let Some(tt) = macro_call.token_tree() {
|
||||||
|
self.output_debug_for_nodes_at_range(
|
||||||
|
&expanded,
|
||||||
|
range,
|
||||||
|
&Some(self.sema.original_range(tt.syntax())),
|
||||||
|
out,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.output_debug_for_nodes_at_range(&node, range, restrict_range, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MatchDebugInfo {
|
||||||
|
node: SyntaxNode,
|
||||||
|
/// Our search pattern parsed as the same kind of syntax node as `node`. e.g. expression, item,
|
||||||
|
/// etc. Will be absent if the pattern can't be parsed as that kind.
|
||||||
|
pattern: Result<SyntaxNode, MatchFailureReason>,
|
||||||
|
matched: Result<Match, MatchFailureReason>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for SsrError {
|
impl std::fmt::Display for SsrError {
|
||||||
|
@ -136,4 +222,70 @@ impl std::fmt::Display for SsrError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for MatchDebugInfo {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "========= PATTERN ==========\n")?;
|
||||||
|
match &self.pattern {
|
||||||
|
Ok(pattern) => {
|
||||||
|
write!(f, "{:#?}", pattern)?;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
write!(f, "{}", err.reason)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n============ AST ===========\n\
|
||||||
|
{:#?}\n============================\n",
|
||||||
|
self.node
|
||||||
|
)?;
|
||||||
|
match &self.matched {
|
||||||
|
Ok(_) => write!(f, "Node matched")?,
|
||||||
|
Err(reason) => write!(f, "Node failed to match because: {}", reason.reason)?,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SsrPattern {
|
||||||
|
fn tree_for_kind_with_reason(
|
||||||
|
&self,
|
||||||
|
kind: SyntaxKind,
|
||||||
|
) -> Result<&SyntaxNode, MatchFailureReason> {
|
||||||
|
record_match_fails_reasons_scope(true, || self.tree_for_kind(kind))
|
||||||
|
.map_err(|e| MatchFailureReason { reason: e.reason.unwrap() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SsrMatches {
|
||||||
|
/// Returns `self` with any nested matches removed and made into top-level matches.
|
||||||
|
pub fn flattened(self) -> SsrMatches {
|
||||||
|
let mut out = SsrMatches::default();
|
||||||
|
self.flatten_into(&mut out);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flatten_into(self, out: &mut SsrMatches) {
|
||||||
|
for mut m in self.matches {
|
||||||
|
for p in m.placeholder_values.values_mut() {
|
||||||
|
std::mem::replace(&mut p.inner_matches, SsrMatches::default()).flatten_into(out);
|
||||||
|
}
|
||||||
|
out.matches.push(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Match {
|
||||||
|
pub fn matched_text(&self) -> String {
|
||||||
|
self.matched_node.text().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::error::Error for SsrError {}
|
impl std::error::Error for SsrError {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl MatchDebugInfo {
|
||||||
|
pub(crate) fn match_failure_reason(&self) -> Option<&str> {
|
||||||
|
self.matched.as_ref().err().map(|r| r.reason.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,9 +8,7 @@ use crate::{
|
||||||
use hir::Semantics;
|
use hir::Semantics;
|
||||||
use ra_db::FileRange;
|
use ra_db::FileRange;
|
||||||
use ra_syntax::ast::{AstNode, AstToken};
|
use ra_syntax::ast::{AstNode, AstToken};
|
||||||
use ra_syntax::{
|
use ra_syntax::{ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken};
|
||||||
ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken, TextRange,
|
|
||||||
};
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::{cell::Cell, iter::Peekable};
|
use std::{cell::Cell, iter::Peekable};
|
||||||
|
|
||||||
|
@ -44,8 +42,8 @@ macro_rules! fail_match {
|
||||||
|
|
||||||
/// Information about a match that was found.
|
/// Information about a match that was found.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Match {
|
pub struct Match {
|
||||||
pub(crate) range: TextRange,
|
pub(crate) range: FileRange,
|
||||||
pub(crate) matched_node: SyntaxNode,
|
pub(crate) matched_node: SyntaxNode,
|
||||||
pub(crate) placeholder_values: FxHashMap<Var, PlaceholderMatch>,
|
pub(crate) placeholder_values: FxHashMap<Var, PlaceholderMatch>,
|
||||||
pub(crate) ignored_comments: Vec<ast::Comment>,
|
pub(crate) ignored_comments: Vec<ast::Comment>,
|
||||||
|
@ -135,7 +133,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
|
||||||
match_state.attempt_match_node(&match_inputs, &pattern_tree, code)?;
|
match_state.attempt_match_node(&match_inputs, &pattern_tree, code)?;
|
||||||
match_state.validate_range(&sema.original_range(code))?;
|
match_state.validate_range(&sema.original_range(code))?;
|
||||||
match_state.match_out = Some(Match {
|
match_state.match_out = Some(Match {
|
||||||
range: sema.original_range(code).range,
|
range: sema.original_range(code),
|
||||||
matched_node: code.clone(),
|
matched_node: code.clone(),
|
||||||
placeholder_values: FxHashMap::default(),
|
placeholder_values: FxHashMap::default(),
|
||||||
ignored_comments: Vec::new(),
|
ignored_comments: Vec::new(),
|
||||||
|
|
|
@ -21,8 +21,10 @@ fn matches_to_edit_at_offset(
|
||||||
) -> TextEdit {
|
) -> TextEdit {
|
||||||
let mut edit_builder = ra_text_edit::TextEditBuilder::default();
|
let mut edit_builder = ra_text_edit::TextEditBuilder::default();
|
||||||
for m in &matches.matches {
|
for m in &matches.matches {
|
||||||
edit_builder
|
edit_builder.replace(
|
||||||
.replace(m.range.checked_sub(relative_start).unwrap(), render_replace(m, file_src));
|
m.range.range.checked_sub(relative_start).unwrap(),
|
||||||
|
render_replace(m, file_src),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
edit_builder.finish()
|
edit_builder.finish()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,150 +1,5 @@
|
||||||
use crate::matching::MatchFailureReason;
|
use crate::{MatchFinder, SsrRule};
|
||||||
use crate::{matching, Match, MatchFinder, SsrMatches, SsrPattern, SsrRule};
|
use ra_db::{FileId, SourceDatabaseExt};
|
||||||
use matching::record_match_fails_reasons_scope;
|
|
||||||
use ra_db::{FileId, FileRange, SourceDatabaseExt};
|
|
||||||
use ra_syntax::ast::AstNode;
|
|
||||||
use ra_syntax::{ast, SyntaxKind, SyntaxNode, TextRange};
|
|
||||||
|
|
||||||
struct MatchDebugInfo {
|
|
||||||
node: SyntaxNode,
|
|
||||||
/// Our search pattern parsed as the same kind of syntax node as `node`. e.g. expression, item,
|
|
||||||
/// etc. Will be absent if the pattern can't be parsed as that kind.
|
|
||||||
pattern: Result<SyntaxNode, MatchFailureReason>,
|
|
||||||
matched: Result<Match, MatchFailureReason>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SsrPattern {
|
|
||||||
pub(crate) fn tree_for_kind_with_reason(
|
|
||||||
&self,
|
|
||||||
kind: SyntaxKind,
|
|
||||||
) -> Result<&SyntaxNode, MatchFailureReason> {
|
|
||||||
record_match_fails_reasons_scope(true, || self.tree_for_kind(kind))
|
|
||||||
.map_err(|e| MatchFailureReason { reason: e.reason.unwrap() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for MatchDebugInfo {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "========= PATTERN ==========\n")?;
|
|
||||||
match &self.pattern {
|
|
||||||
Ok(pattern) => {
|
|
||||||
write!(f, "{:#?}", pattern)?;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
write!(f, "{}", err.reason)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"\n============ AST ===========\n\
|
|
||||||
{:#?}\n============================",
|
|
||||||
self.node
|
|
||||||
)?;
|
|
||||||
match &self.matched {
|
|
||||||
Ok(_) => write!(f, "Node matched")?,
|
|
||||||
Err(reason) => write!(f, "Node failed to match because: {}", reason.reason)?,
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SsrMatches {
|
|
||||||
/// Returns `self` with any nested matches removed and made into top-level matches.
|
|
||||||
pub(crate) fn flattened(self) -> SsrMatches {
|
|
||||||
let mut out = SsrMatches::default();
|
|
||||||
self.flatten_into(&mut out);
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flatten_into(self, out: &mut SsrMatches) {
|
|
||||||
for mut m in self.matches {
|
|
||||||
for p in m.placeholder_values.values_mut() {
|
|
||||||
std::mem::replace(&mut p.inner_matches, SsrMatches::default()).flatten_into(out);
|
|
||||||
}
|
|
||||||
out.matches.push(m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Match {
|
|
||||||
pub(crate) fn matched_text(&self) -> String {
|
|
||||||
self.matched_node.text().to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'db> MatchFinder<'db> {
|
|
||||||
/// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you
|
|
||||||
/// intend to do replacement, use `add_rule` instead.
|
|
||||||
fn add_search_pattern(&mut self, pattern: SsrPattern) {
|
|
||||||
self.add_rule(SsrRule { pattern, template: "()".parse().unwrap() })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match
|
|
||||||
/// them, while recording reasons why they don't match. This API is useful for command
|
|
||||||
/// line-based debugging where providing a range is difficult.
|
|
||||||
fn debug_where_text_equal(&self, file_id: FileId, snippet: &str) -> Vec<MatchDebugInfo> {
|
|
||||||
let file = self.sema.parse(file_id);
|
|
||||||
let mut res = Vec::new();
|
|
||||||
let file_text = self.sema.db.file_text(file_id);
|
|
||||||
let mut remaining_text = file_text.as_str();
|
|
||||||
let mut base = 0;
|
|
||||||
let len = snippet.len() as u32;
|
|
||||||
while let Some(offset) = remaining_text.find(snippet) {
|
|
||||||
let start = base + offset as u32;
|
|
||||||
let end = start + len;
|
|
||||||
self.output_debug_for_nodes_at_range(
|
|
||||||
file.syntax(),
|
|
||||||
TextRange::new(start.into(), end.into()),
|
|
||||||
&None,
|
|
||||||
&mut res,
|
|
||||||
);
|
|
||||||
remaining_text = &remaining_text[offset + snippet.len()..];
|
|
||||||
base = end;
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
fn output_debug_for_nodes_at_range(
|
|
||||||
&self,
|
|
||||||
node: &SyntaxNode,
|
|
||||||
range: TextRange,
|
|
||||||
restrict_range: &Option<FileRange>,
|
|
||||||
out: &mut Vec<MatchDebugInfo>,
|
|
||||||
) {
|
|
||||||
for node in node.children() {
|
|
||||||
if !node.text_range().contains_range(range) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if node.text_range() == range {
|
|
||||||
for rule in &self.rules {
|
|
||||||
let pattern =
|
|
||||||
rule.pattern.tree_for_kind_with_reason(node.kind()).map(|p| p.clone());
|
|
||||||
out.push(MatchDebugInfo {
|
|
||||||
matched: matching::get_match(true, rule, &node, restrict_range, &self.sema)
|
|
||||||
.map_err(|e| MatchFailureReason {
|
|
||||||
reason: e.reason.unwrap_or_else(|| {
|
|
||||||
"Match failed, but no reason was given".to_owned()
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
pattern,
|
|
||||||
node: node.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if let Some(macro_call) = ast::MacroCall::cast(node.clone()) {
|
|
||||||
if let Some(expanded) = self.sema.expand(¯o_call) {
|
|
||||||
if let Some(tt) = macro_call.token_tree() {
|
|
||||||
self.output_debug_for_nodes_at_range(
|
|
||||||
&expanded,
|
|
||||||
range,
|
|
||||||
&Some(self.sema.original_range(tt.syntax())),
|
|
||||||
out,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_error_text(query: &str) -> String {
|
fn parse_error_text(query: &str) -> String {
|
||||||
format!("{}", query.parse::<SsrRule>().unwrap_err())
|
format!("{}", query.parse::<SsrRule>().unwrap_err())
|
||||||
|
@ -260,6 +115,19 @@ fn assert_no_match(pattern: &str, code: &str) {
|
||||||
assert_matches(pattern, code, &[]);
|
assert_matches(pattern, code, &[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assert_match_failure_reason(pattern: &str, code: &str, snippet: &str, expected_reason: &str) {
|
||||||
|
let (db, file_id) = single_file(code);
|
||||||
|
let mut match_finder = MatchFinder::new(&db);
|
||||||
|
match_finder.add_search_pattern(pattern.parse().unwrap());
|
||||||
|
let mut reasons = Vec::new();
|
||||||
|
for d in match_finder.debug_where_text_equal(file_id, snippet) {
|
||||||
|
if let Some(reason) = d.match_failure_reason() {
|
||||||
|
reasons.push(reason.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(reasons, vec![expected_reason]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ssr_function_to_method() {
|
fn ssr_function_to_method() {
|
||||||
assert_ssr_transform(
|
assert_ssr_transform(
|
||||||
|
@ -623,3 +491,30 @@ fn preserves_whitespace_within_macro_expansion() {
|
||||||
fn f() {macro1!(4 - 3 - 1 * 2}"#,
|
fn f() {macro1!(4 - 3 - 1 * 2}"#,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn match_failure_reasons() {
|
||||||
|
let code = r#"
|
||||||
|
macro_rules! foo {
|
||||||
|
($a:expr) => {
|
||||||
|
1 + $a + 2
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn f1() {
|
||||||
|
bar(1, 2);
|
||||||
|
foo!(5 + 43.to_string() + 5);
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
assert_match_failure_reason(
|
||||||
|
"bar($a, 3)",
|
||||||
|
code,
|
||||||
|
"bar(1, 2)",
|
||||||
|
r#"Pattern wanted token '3' (INT_NUMBER), but code had token '2' (INT_NUMBER)"#,
|
||||||
|
);
|
||||||
|
assert_match_failure_reason(
|
||||||
|
"42.to_string()",
|
||||||
|
code,
|
||||||
|
"43.to_string()",
|
||||||
|
r#"Pattern wanted token '42' (INT_NUMBER), but code had token '43' (INT_NUMBER)"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use pico_args::Arguments;
|
use pico_args::Arguments;
|
||||||
use ra_ssr::SsrRule;
|
use ra_ssr::{SsrPattern, SsrRule};
|
||||||
use rust_analyzer::cli::{BenchWhat, Position, Verbosity};
|
use rust_analyzer::cli::{BenchWhat, Position, Verbosity};
|
||||||
|
|
||||||
use std::{fmt::Write, path::PathBuf};
|
use std::{fmt::Write, path::PathBuf};
|
||||||
|
@ -50,6 +50,10 @@ pub(crate) enum Command {
|
||||||
Ssr {
|
Ssr {
|
||||||
rules: Vec<SsrRule>,
|
rules: Vec<SsrRule>,
|
||||||
},
|
},
|
||||||
|
StructuredSearch {
|
||||||
|
debug_snippet: Option<String>,
|
||||||
|
patterns: Vec<SsrPattern>,
|
||||||
|
},
|
||||||
ProcMacro,
|
ProcMacro,
|
||||||
RunServer,
|
RunServer,
|
||||||
Version,
|
Version,
|
||||||
|
@ -294,6 +298,7 @@ EXAMPLE:
|
||||||
rust-analyzer ssr '$a.foo($b) ==> bar($a, $b)'
|
rust-analyzer ssr '$a.foo($b) ==> bar($a, $b)'
|
||||||
|
|
||||||
FLAGS:
|
FLAGS:
|
||||||
|
--debug <snippet> Prints debug information for any nodes with source exactly equal to <snippet>
|
||||||
-h, --help Prints help information
|
-h, --help Prints help information
|
||||||
|
|
||||||
ARGS:
|
ARGS:
|
||||||
|
@ -307,6 +312,34 @@ ARGS:
|
||||||
}
|
}
|
||||||
Command::Ssr { rules }
|
Command::Ssr { rules }
|
||||||
}
|
}
|
||||||
|
"search" => {
|
||||||
|
if matches.contains(["-h", "--help"]) {
|
||||||
|
eprintln!(
|
||||||
|
"\
|
||||||
|
rust-analyzer search
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
rust-analyzer search [FLAGS] [PATTERN...]
|
||||||
|
|
||||||
|
EXAMPLE:
|
||||||
|
rust-analyzer search '$a.foo($b)'
|
||||||
|
|
||||||
|
FLAGS:
|
||||||
|
--debug <snippet> Prints debug information for any nodes with source exactly equal to <snippet>
|
||||||
|
-h, --help Prints help information
|
||||||
|
|
||||||
|
ARGS:
|
||||||
|
<PATTERN> A structured search pattern"
|
||||||
|
);
|
||||||
|
return Ok(Err(HelpPrinted));
|
||||||
|
}
|
||||||
|
let debug_snippet = matches.opt_value_from_str("--debug")?;
|
||||||
|
let mut patterns = Vec::new();
|
||||||
|
while let Some(rule) = matches.free_from_str()? {
|
||||||
|
patterns.push(rule);
|
||||||
|
}
|
||||||
|
Command::StructuredSearch { patterns, debug_snippet }
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
print_subcommands();
|
print_subcommands();
|
||||||
return Ok(Err(HelpPrinted));
|
return Ok(Err(HelpPrinted));
|
||||||
|
@ -334,6 +367,7 @@ SUBCOMMANDS:
|
||||||
diagnostics
|
diagnostics
|
||||||
proc-macro
|
proc-macro
|
||||||
parse
|
parse
|
||||||
|
search
|
||||||
ssr
|
ssr
|
||||||
symbols"
|
symbols"
|
||||||
)
|
)
|
||||||
|
|
|
@ -65,6 +65,9 @@ fn main() -> Result<()> {
|
||||||
args::Command::Ssr { rules } => {
|
args::Command::Ssr { rules } => {
|
||||||
cli::apply_ssr_rules(rules)?;
|
cli::apply_ssr_rules(rules)?;
|
||||||
}
|
}
|
||||||
|
args::Command::StructuredSearch { patterns, debug_snippet } => {
|
||||||
|
cli::search_for_patterns(patterns, debug_snippet)?;
|
||||||
|
}
|
||||||
args::Command::Version => println!("rust-analyzer {}", env!("REV")),
|
args::Command::Version => println!("rust-analyzer {}", env!("REV")),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub use analysis_bench::{analysis_bench, BenchWhat, Position};
|
||||||
pub use analysis_stats::analysis_stats;
|
pub use analysis_stats::analysis_stats;
|
||||||
pub use diagnostics::diagnostics;
|
pub use diagnostics::diagnostics;
|
||||||
pub use load_cargo::load_cargo;
|
pub use load_cargo::load_cargo;
|
||||||
pub use ssr::apply_ssr_rules;
|
pub use ssr::{apply_ssr_rules, search_for_patterns};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum Verbosity {
|
pub enum Verbosity {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use crate::cli::{load_cargo::load_cargo, Result};
|
use crate::cli::{load_cargo::load_cargo, Result};
|
||||||
use ra_ide::SourceFileEdit;
|
use ra_ide::SourceFileEdit;
|
||||||
use ra_ssr::{MatchFinder, SsrRule};
|
use ra_ssr::{MatchFinder, SsrPattern, SsrRule};
|
||||||
|
|
||||||
pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
|
pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
|
||||||
use ra_db::SourceDatabaseExt;
|
use ra_db::SourceDatabaseExt;
|
||||||
|
@ -31,3 +31,41 @@ pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Searches for `patterns`, printing debug information for any nodes whose text exactly matches
|
||||||
|
/// `debug_snippet`. This is intended for debugging and probably isn't in it's current form useful
|
||||||
|
/// for much else.
|
||||||
|
pub fn search_for_patterns(patterns: Vec<SsrPattern>, debug_snippet: Option<String>) -> Result<()> {
|
||||||
|
use ra_db::SourceDatabaseExt;
|
||||||
|
use ra_ide_db::symbol_index::SymbolsDatabase;
|
||||||
|
let (host, vfs) = load_cargo(&std::env::current_dir()?, true, true)?;
|
||||||
|
let db = host.raw_database();
|
||||||
|
let mut match_finder = MatchFinder::new(db);
|
||||||
|
for pattern in patterns {
|
||||||
|
match_finder.add_search_pattern(pattern);
|
||||||
|
}
|
||||||
|
for &root in db.local_roots().iter() {
|
||||||
|
let sr = db.source_root(root);
|
||||||
|
for file_id in sr.iter() {
|
||||||
|
if let Some(debug_snippet) = &debug_snippet {
|
||||||
|
for debug_info in match_finder.debug_where_text_equal(file_id, debug_snippet) {
|
||||||
|
println!("{:#?}", debug_info);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let matches = match_finder.find_matches_in_file(file_id);
|
||||||
|
if !matches.matches.is_empty() {
|
||||||
|
let matches = matches.flattened().matches;
|
||||||
|
if let Some(path) = vfs.file_path(file_id).as_path() {
|
||||||
|
println!("{} matches in '{}'", matches.len(), path.to_string_lossy());
|
||||||
|
}
|
||||||
|
// We could possibly at some point do something more useful than just printing
|
||||||
|
// the matched text. For now though, that's the easiest thing to do.
|
||||||
|
for m in matches {
|
||||||
|
println!("{}", m.matched_text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue