From 757e593b253b4df7e6fc8bf15a4d4f34c9d484c5 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 21:32:33 +0300 Subject: rename ra_ide_api -> ra_ide --- crates/ra_ide/src/references/classify.rs | 186 ++++++++++++++ crates/ra_ide/src/references/name_definition.rs | 83 ++++++ crates/ra_ide/src/references/rename.rs | 328 ++++++++++++++++++++++++ crates/ra_ide/src/references/search_scope.rs | 145 +++++++++++ 4 files changed, 742 insertions(+) create mode 100644 crates/ra_ide/src/references/classify.rs create mode 100644 crates/ra_ide/src/references/name_definition.rs create mode 100644 crates/ra_ide/src/references/rename.rs create mode 100644 crates/ra_ide/src/references/search_scope.rs (limited to 'crates/ra_ide/src/references') diff --git a/crates/ra_ide/src/references/classify.rs b/crates/ra_ide/src/references/classify.rs new file mode 100644 index 000000000..5cea805ec --- /dev/null +++ b/crates/ra_ide/src/references/classify.rs @@ -0,0 +1,186 @@ +//! Functions that are used to classify an element from its definition or reference. + +use hir::{FromSource, Module, ModuleSource, PathResolution, Source, SourceAnalyzer}; +use ra_prof::profile; +use ra_syntax::{ast, match_ast, AstNode}; +use test_utils::tested_by; + +use super::{ + name_definition::{from_assoc_item, from_module_def, from_struct_field}, + NameDefinition, NameKind, +}; +use crate::db::RootDatabase; + +pub(crate) fn classify_name(db: &RootDatabase, name: Source<&ast::Name>) -> Option { + let _p = profile("classify_name"); + let parent = name.value.syntax().parent()?; + + match_ast! { + match parent { + ast::BindPat(it) => { + let src = name.with_value(it); + let local = hir::Local::from_source(db, src)?; + Some(NameDefinition { + visibility: None, + container: local.module(db), + kind: NameKind::Local(local), + }) + }, + ast::RecordFieldDef(it) => { + let ast = hir::FieldSource::Named(it); + let src = name.with_value(ast); + let field = hir::StructField::from_source(db, src)?; + Some(from_struct_field(db, field)) + }, + ast::Module(it) => { + let def = { + if !it.has_semi() { + let ast = hir::ModuleSource::Module(it); + let src = name.with_value(ast); + hir::Module::from_definition(db, src) + } else { + let src = name.with_value(it); + hir::Module::from_declaration(db, src) + } + }?; + Some(from_module_def(db, def.into(), None)) + }, + ast::StructDef(it) => { + let src = name.with_value(it); + let def = hir::Struct::from_source(db, src)?; + Some(from_module_def(db, def.into(), None)) + }, + ast::EnumDef(it) => { + let src = name.with_value(it); + let def = hir::Enum::from_source(db, src)?; + Some(from_module_def(db, def.into(), None)) + }, + ast::TraitDef(it) => { + let src = name.with_value(it); + let def = hir::Trait::from_source(db, src)?; + Some(from_module_def(db, def.into(), None)) + }, + ast::StaticDef(it) => { + let src = name.with_value(it); + let def = hir::Static::from_source(db, src)?; + Some(from_module_def(db, def.into(), None)) + }, + ast::EnumVariant(it) => { + let src = name.with_value(it); + let def = hir::EnumVariant::from_source(db, src)?; + Some(from_module_def(db, def.into(), None)) + }, + ast::FnDef(it) => { + let src = name.with_value(it); + let def = hir::Function::from_source(db, src)?; + if parent.parent().and_then(ast::ItemList::cast).is_some() { + Some(from_assoc_item(db, def.into())) + } else { + Some(from_module_def(db, def.into(), None)) + } + }, + ast::ConstDef(it) => { + let src = name.with_value(it); + let def = hir::Const::from_source(db, src)?; + if parent.parent().and_then(ast::ItemList::cast).is_some() { + Some(from_assoc_item(db, def.into())) + } else { + Some(from_module_def(db, def.into(), None)) + } + }, + ast::TypeAliasDef(it) => { + let src = name.with_value(it); + let def = hir::TypeAlias::from_source(db, src)?; + if parent.parent().and_then(ast::ItemList::cast).is_some() { + Some(from_assoc_item(db, def.into())) + } else { + Some(from_module_def(db, def.into(), None)) + } + }, + ast::MacroCall(it) => { + let src = name.with_value(it); + let def = hir::MacroDef::from_source(db, src.clone())?; + + let module_src = ModuleSource::from_child_node(db, src.as_ref().map(|it| it.syntax())); + let module = Module::from_definition(db, src.with_value(module_src))?; + + Some(NameDefinition { + visibility: None, + container: module, + kind: NameKind::Macro(def), + }) + }, + _ => None, + } + } +} + +pub(crate) fn classify_name_ref( + db: &RootDatabase, + name_ref: Source<&ast::NameRef>, +) -> Option { + let _p = profile("classify_name_ref"); + + let parent = name_ref.value.syntax().parent()?; + let analyzer = SourceAnalyzer::new(db, name_ref.map(|it| it.syntax()), None); + + if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) { + tested_by!(goto_definition_works_for_methods); + if let Some(func) = analyzer.resolve_method_call(&method_call) { + return Some(from_assoc_item(db, func.into())); + } + } + + if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { + tested_by!(goto_definition_works_for_fields); + if let Some(field) = analyzer.resolve_field(&field_expr) { + return Some(from_struct_field(db, field)); + } + } + + if let Some(record_field) = ast::RecordField::cast(parent.clone()) { + tested_by!(goto_definition_works_for_record_fields); + if let Some(field_def) = analyzer.resolve_record_field(&record_field) { + return Some(from_struct_field(db, field_def)); + } + } + + let ast = ModuleSource::from_child_node(db, name_ref.with_value(&parent)); + // FIXME: find correct container and visibility for each case + let container = Module::from_definition(db, name_ref.with_value(ast))?; + let visibility = None; + + if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) { + tested_by!(goto_definition_works_for_macros); + if let Some(macro_def) = analyzer.resolve_macro_call(db, name_ref.with_value(¯o_call)) { + let kind = NameKind::Macro(macro_def); + return Some(NameDefinition { kind, container, visibility }); + } + } + + let path = name_ref.value.syntax().ancestors().find_map(ast::Path::cast)?; + let resolved = analyzer.resolve_path(db, &path)?; + match resolved { + PathResolution::Def(def) => Some(from_module_def(db, def, Some(container))), + PathResolution::AssocItem(item) => Some(from_assoc_item(db, item)), + PathResolution::Local(local) => { + let container = local.module(db); + let kind = NameKind::Local(local); + Some(NameDefinition { kind, container, visibility: None }) + } + PathResolution::GenericParam(par) => { + // FIXME: get generic param def + let kind = NameKind::GenericParam(par); + Some(NameDefinition { kind, container, visibility }) + } + PathResolution::Macro(def) => { + let kind = NameKind::Macro(def); + Some(NameDefinition { kind, container, visibility }) + } + PathResolution::SelfType(impl_block) => { + let kind = NameKind::SelfType(impl_block); + let container = impl_block.module(db); + Some(NameDefinition { kind, container, visibility }) + } + } +} diff --git a/crates/ra_ide/src/references/name_definition.rs b/crates/ra_ide/src/references/name_definition.rs new file mode 100644 index 000000000..10d3a2364 --- /dev/null +++ b/crates/ra_ide/src/references/name_definition.rs @@ -0,0 +1,83 @@ +//! `NameDefinition` keeps information about the element we want to search references for. +//! The element is represented by `NameKind`. It's located inside some `container` and +//! has a `visibility`, which defines a search scope. +//! Note that the reference search is possible for not all of the classified items. + +use hir::{ + Adt, AssocItem, GenericParam, HasSource, ImplBlock, Local, MacroDef, Module, ModuleDef, + StructField, VariantDef, +}; +use ra_syntax::{ast, ast::VisibilityOwner}; + +use crate::db::RootDatabase; + +#[derive(Debug, PartialEq, Eq)] +pub enum NameKind { + Macro(MacroDef), + Field(StructField), + AssocItem(AssocItem), + Def(ModuleDef), + SelfType(ImplBlock), + Local(Local), + GenericParam(GenericParam), +} + +#[derive(PartialEq, Eq)] +pub(crate) struct NameDefinition { + pub visibility: Option, + pub container: Module, + pub kind: NameKind, +} + +pub(super) fn from_assoc_item(db: &RootDatabase, item: AssocItem) -> NameDefinition { + let container = item.module(db); + let visibility = match item { + AssocItem::Function(f) => f.source(db).value.visibility(), + AssocItem::Const(c) => c.source(db).value.visibility(), + AssocItem::TypeAlias(a) => a.source(db).value.visibility(), + }; + let kind = NameKind::AssocItem(item); + NameDefinition { kind, container, visibility } +} + +pub(super) fn from_struct_field(db: &RootDatabase, field: StructField) -> NameDefinition { + let kind = NameKind::Field(field); + let parent = field.parent_def(db); + let container = parent.module(db); + let visibility = match parent { + VariantDef::Struct(s) => s.source(db).value.visibility(), + VariantDef::Union(e) => e.source(db).value.visibility(), + VariantDef::EnumVariant(e) => e.source(db).value.parent_enum().visibility(), + }; + NameDefinition { kind, container, visibility } +} + +pub(super) fn from_module_def( + db: &RootDatabase, + def: ModuleDef, + module: Option, +) -> NameDefinition { + let kind = NameKind::Def(def); + let (container, visibility) = match def { + ModuleDef::Module(it) => { + let container = it.parent(db).or_else(|| Some(it)).unwrap(); + let visibility = it.declaration_source(db).and_then(|s| s.value.visibility()); + (container, visibility) + } + ModuleDef::EnumVariant(it) => { + let container = it.module(db); + let visibility = it.source(db).value.parent_enum().visibility(); + (container, visibility) + } + ModuleDef::Function(it) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::Const(it) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::Static(it) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::Trait(it) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::TypeAlias(it) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::Adt(Adt::Struct(it)) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::Adt(Adt::Union(it)) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::Adt(Adt::Enum(it)) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::BuiltinType(..) => (module.unwrap(), None), + }; + NameDefinition { kind, container, visibility } +} diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs new file mode 100644 index 000000000..d58496049 --- /dev/null +++ b/crates/ra_ide/src/references/rename.rs @@ -0,0 +1,328 @@ +//! FIXME: write short doc here + +use hir::ModuleSource; +use ra_db::{RelativePath, RelativePathBuf, SourceDatabase, SourceDatabaseExt}; +use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode}; +use ra_text_edit::TextEdit; + +use crate::{ + db::RootDatabase, FileId, FilePosition, FileSystemEdit, RangeInfo, SourceChange, + SourceFileEdit, TextRange, +}; + +use super::find_all_refs; + +pub(crate) fn rename( + db: &RootDatabase, + position: FilePosition, + new_name: &str, +) -> Option> { + let parse = db.parse(position.file_id); + if let Some((ast_name, ast_module)) = + find_name_and_module_at_offset(parse.tree().syntax(), position) + { + let range = ast_name.syntax().text_range(); + rename_mod(db, &ast_name, &ast_module, position, new_name) + .map(|info| RangeInfo::new(range, info)) + } else { + rename_reference(db, position, new_name) + } +} + +fn find_name_and_module_at_offset( + syntax: &SyntaxNode, + position: FilePosition, +) -> Option<(ast::Name, ast::Module)> { + let ast_name = find_node_at_offset::(syntax, position.offset)?; + let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; + Some((ast_name, ast_module)) +} + +fn source_edit_from_file_id_range( + file_id: FileId, + range: TextRange, + new_name: &str, +) -> SourceFileEdit { + SourceFileEdit { file_id, edit: TextEdit::replace(range, new_name.into()) } +} + +fn rename_mod( + db: &RootDatabase, + ast_name: &ast::Name, + ast_module: &ast::Module, + position: FilePosition, + new_name: &str, +) -> Option { + let mut source_file_edits = Vec::new(); + let mut file_system_edits = Vec::new(); + let module_src = hir::Source { file_id: position.file_id.into(), value: ast_module.clone() }; + if let Some(module) = hir::Module::from_declaration(db, module_src) { + let src = module.definition_source(db); + let file_id = src.file_id.original_file(db); + match src.value { + ModuleSource::SourceFile(..) => { + let mod_path: RelativePathBuf = db.file_relative_path(file_id); + // mod is defined in path/to/dir/mod.rs + let dst_path = if mod_path.file_stem() == Some("mod") { + mod_path + .parent() + .and_then(|p| p.parent()) + .or_else(|| Some(RelativePath::new(""))) + .map(|p| p.join(new_name).join("mod.rs")) + } else { + Some(mod_path.with_file_name(new_name).with_extension("rs")) + }; + if let Some(path) = dst_path { + let move_file = FileSystemEdit::MoveFile { + src: file_id, + dst_source_root: db.file_source_root(position.file_id), + dst_path: path, + }; + file_system_edits.push(move_file); + } + } + ModuleSource::Module(..) => {} + } + } + + let edit = SourceFileEdit { + file_id: position.file_id, + edit: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()), + }; + source_file_edits.push(edit); + + Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) +} + +fn rename_reference( + db: &RootDatabase, + position: FilePosition, + new_name: &str, +) -> Option> { + let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?; + + let edit = refs + .into_iter() + .map(|range| source_edit_from_file_id_range(range.file_id, range.range, new_name)) + .collect::>(); + + if edit.is_empty() { + return None; + } + + Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) +} + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + use ra_text_edit::TextEditBuilder; + use test_utils::assert_eq_text; + + use crate::{ + mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, + }; + + #[test] + fn test_rename_for_local() { + test_rename( + r#" + fn main() { + let mut i = 1; + let j = 1; + i = i<|> + j; + + { + i = 0; + } + + i = 5; + }"#, + "k", + r#" + fn main() { + let mut k = 1; + let j = 1; + k = k + j; + + { + k = 0; + } + + k = 5; + }"#, + ); + } + + #[test] + fn test_rename_for_param_inside() { + test_rename( + r#" + fn foo(i : u32) -> u32 { + i<|> + }"#, + "j", + r#" + fn foo(j : u32) -> u32 { + j + }"#, + ); + } + + #[test] + fn test_rename_refs_for_fn_param() { + test_rename( + r#" + fn foo(i<|> : u32) -> u32 { + i + }"#, + "new_name", + r#" + fn foo(new_name : u32) -> u32 { + new_name + }"#, + ); + } + + #[test] + fn test_rename_for_mut_param() { + test_rename( + r#" + fn foo(mut i<|> : u32) -> u32 { + i + }"#, + "new_name", + r#" + fn foo(mut new_name : u32) -> u32 { + new_name + }"#, + ); + } + + #[test] + fn test_rename_mod() { + let (analysis, position) = analysis_and_position( + " + //- /lib.rs + mod bar; + + //- /bar.rs + mod foo<|>; + + //- /bar/foo.rs + // emtpy + ", + ); + let new_name = "foo2"; + let source_change = analysis.rename(position, new_name).unwrap(); + assert_debug_snapshot!(&source_change, +@r###" + Some( + RangeInfo { + range: [4; 7), + info: SourceChange { + label: "rename", + source_file_edits: [ + SourceFileEdit { + file_id: FileId( + 2, + ), + edit: TextEdit { + atoms: [ + AtomTextEdit { + delete: [4; 7), + insert: "foo2", + }, + ], + }, + }, + ], + file_system_edits: [ + MoveFile { + src: FileId( + 3, + ), + dst_source_root: SourceRootId( + 0, + ), + dst_path: "bar/foo2.rs", + }, + ], + cursor_position: None, + }, + }, + ) + "###); + } + + #[test] + fn test_rename_mod_in_dir() { + let (analysis, position) = analysis_and_position( + " + //- /lib.rs + mod fo<|>o; + //- /foo/mod.rs + // emtpy + ", + ); + let new_name = "foo2"; + let source_change = analysis.rename(position, new_name).unwrap(); + assert_debug_snapshot!(&source_change, + @r###" + Some( + RangeInfo { + range: [4; 7), + info: SourceChange { + label: "rename", + source_file_edits: [ + SourceFileEdit { + file_id: FileId( + 1, + ), + edit: TextEdit { + atoms: [ + AtomTextEdit { + delete: [4; 7), + insert: "foo2", + }, + ], + }, + }, + ], + file_system_edits: [ + MoveFile { + src: FileId( + 2, + ), + dst_source_root: SourceRootId( + 0, + ), + dst_path: "foo2/mod.rs", + }, + ], + cursor_position: None, + }, + }, + ) + "### + ); + } + + fn test_rename(text: &str, new_name: &str, expected: &str) { + let (analysis, position) = single_file_with_position(text); + let source_change = analysis.rename(position, new_name).unwrap(); + let mut text_edit_builder = TextEditBuilder::default(); + let mut file_id: Option = None; + if let Some(change) = source_change { + for edit in change.info.source_file_edits { + file_id = Some(edit.file_id); + for atom in edit.edit.as_atoms() { + text_edit_builder.replace(atom.delete, atom.insert.clone()); + } + } + } + let result = + text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); + assert_eq_text!(expected, &*result); + } +} diff --git a/crates/ra_ide/src/references/search_scope.rs b/crates/ra_ide/src/references/search_scope.rs new file mode 100644 index 000000000..f5c9589f4 --- /dev/null +++ b/crates/ra_ide/src/references/search_scope.rs @@ -0,0 +1,145 @@ +//! Generally, `search_scope` returns files that might contain references for the element. +//! For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates. +//! In some cases, the location of the references is known to within a `TextRange`, +//! e.g. for things like local variables. +use std::mem; + +use hir::{DefWithBody, HasSource, ModuleSource}; +use ra_db::{FileId, SourceDatabase, SourceDatabaseExt}; +use ra_prof::profile; +use ra_syntax::{AstNode, TextRange}; +use rustc_hash::FxHashMap; + +use crate::db::RootDatabase; + +use super::{NameDefinition, NameKind}; + +pub struct SearchScope { + entries: FxHashMap>, +} + +impl SearchScope { + fn new(entries: FxHashMap>) -> SearchScope { + SearchScope { entries } + } + pub fn single_file(file: FileId) -> SearchScope { + SearchScope::new(std::iter::once((file, None)).collect()) + } + pub(crate) fn intersection(&self, other: &SearchScope) -> SearchScope { + let (mut small, mut large) = (&self.entries, &other.entries); + if small.len() > large.len() { + mem::swap(&mut small, &mut large) + } + + let res = small + .iter() + .filter_map(|(file_id, r1)| { + let r2 = large.get(file_id)?; + let r = intersect_ranges(*r1, *r2)?; + Some((*file_id, r)) + }) + .collect(); + return SearchScope::new(res); + + fn intersect_ranges( + r1: Option, + r2: Option, + ) -> Option> { + match (r1, r2) { + (None, r) | (r, None) => Some(r), + (Some(r1), Some(r2)) => { + let r = r1.intersection(&r2)?; + Some(Some(r)) + } + } + } + } +} + +impl IntoIterator for SearchScope { + type Item = (FileId, Option); + type IntoIter = std::collections::hash_map::IntoIter>; + fn into_iter(self) -> Self::IntoIter { + self.entries.into_iter() + } +} + +impl NameDefinition { + pub(crate) fn search_scope(&self, db: &RootDatabase) -> SearchScope { + let _p = profile("search_scope"); + + let module_src = self.container.definition_source(db); + let file_id = module_src.file_id.original_file(db); + + if let NameKind::Local(var) = self.kind { + let range = match var.parent(db) { + DefWithBody::Function(f) => f.source(db).value.syntax().text_range(), + DefWithBody::Const(c) => c.source(db).value.syntax().text_range(), + DefWithBody::Static(s) => s.source(db).value.syntax().text_range(), + }; + let mut res = FxHashMap::default(); + res.insert(file_id, Some(range)); + return SearchScope::new(res); + } + + let vis = + self.visibility.as_ref().map(|v| v.syntax().to_string()).unwrap_or("".to_string()); + + if vis.as_str() == "pub(super)" { + if let Some(parent_module) = self.container.parent(db) { + let mut res = FxHashMap::default(); + let parent_src = parent_module.definition_source(db); + let file_id = parent_src.file_id.original_file(db); + + match parent_src.value { + ModuleSource::Module(m) => { + let range = Some(m.syntax().text_range()); + res.insert(file_id, range); + } + ModuleSource::SourceFile(_) => { + res.insert(file_id, None); + res.extend(parent_module.children(db).map(|m| { + let src = m.definition_source(db); + (src.file_id.original_file(db), None) + })); + } + } + return SearchScope::new(res); + } + } + + if vis.as_str() != "" { + let source_root_id = db.file_source_root(file_id); + let source_root = db.source_root(source_root_id); + let mut res = source_root.walk().map(|id| (id, None)).collect::>(); + + // FIXME: add "pub(in path)" + + if vis.as_str() == "pub(crate)" { + return SearchScope::new(res); + } + if vis.as_str() == "pub" { + let krate = self.container.krate(); + let crate_graph = db.crate_graph(); + for crate_id in crate_graph.iter() { + let mut crate_deps = crate_graph.dependencies(crate_id); + if crate_deps.any(|dep| dep.crate_id() == krate.crate_id()) { + let root_file = crate_graph.crate_root(crate_id); + let source_root_id = db.file_source_root(root_file); + let source_root = db.source_root(source_root_id); + res.extend(source_root.walk().map(|id| (id, None))); + } + } + return SearchScope::new(res); + } + } + + let mut res = FxHashMap::default(); + let range = match module_src.value { + ModuleSource::Module(m) => Some(m.syntax().text_range()), + ModuleSource::SourceFile(_) => None, + }; + res.insert(file_id, range); + SearchScope::new(res) + } +} -- cgit v1.2.3