From f32081fa185b3a9df021f277c2c27fbd123d0951 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 25 Sep 2019 14:29:41 +0300 Subject: move assists to subdir --- crates/ra_assists/src/add_derive.rs | 105 --- crates/ra_assists/src/add_explicit_type.rs | 86 -- crates/ra_assists/src/add_impl.rs | 77 -- crates/ra_assists/src/add_missing_impl_members.rs | 340 -------- crates/ra_assists/src/assists/add_derive.rs | 105 +++ crates/ra_assists/src/assists/add_explicit_type.rs | 86 ++ crates/ra_assists/src/assists/add_impl.rs | 77 ++ .../src/assists/add_missing_impl_members.rs | 340 ++++++++ crates/ra_assists/src/assists/auto_import.rs | 939 +++++++++++++++++++++ crates/ra_assists/src/assists/change_visibility.rs | 159 ++++ crates/ra_assists/src/assists/fill_match_arms.rs | 229 +++++ crates/ra_assists/src/assists/flip_binexpr.rs | 141 ++++ crates/ra_assists/src/assists/flip_comma.rs | 68 ++ .../src/assists/inline_local_variable.rs | 636 ++++++++++++++ .../ra_assists/src/assists/introduce_variable.rs | 516 +++++++++++ crates/ra_assists/src/assists/merge_match_arms.rs | 188 +++++ crates/ra_assists/src/assists/move_bounds.rs | 135 +++ crates/ra_assists/src/assists/move_guard.rs | 261 ++++++ crates/ra_assists/src/assists/raw_string.rs | 370 ++++++++ crates/ra_assists/src/assists/remove_dbg.rs | 137 +++ .../src/assists/replace_if_let_with_match.rs | 102 +++ crates/ra_assists/src/assists/split_import.rs | 61 ++ crates/ra_assists/src/auto_import.rs | 939 --------------------- crates/ra_assists/src/change_visibility.rs | 159 ---- crates/ra_assists/src/fill_match_arms.rs | 229 ----- crates/ra_assists/src/flip_binexpr.rs | 141 ---- crates/ra_assists/src/flip_comma.rs | 68 -- crates/ra_assists/src/inline_local_variable.rs | 636 -------------- crates/ra_assists/src/introduce_variable.rs | 516 ----------- crates/ra_assists/src/lib.rs | 100 +-- crates/ra_assists/src/merge_match_arms.rs | 188 ----- crates/ra_assists/src/move_bounds.rs | 135 --- crates/ra_assists/src/move_guard.rs | 261 ------ crates/ra_assists/src/raw_string.rs | 370 -------- crates/ra_assists/src/remove_dbg.rs | 137 --- crates/ra_assists/src/replace_if_let_with_match.rs | 102 --- crates/ra_assists/src/split_import.rs | 61 -- crates/ra_ide_api/src/completion/complete_scope.rs | 4 +- 38 files changed, 4605 insertions(+), 4599 deletions(-) delete mode 100644 crates/ra_assists/src/add_derive.rs delete mode 100644 crates/ra_assists/src/add_explicit_type.rs delete mode 100644 crates/ra_assists/src/add_impl.rs delete mode 100644 crates/ra_assists/src/add_missing_impl_members.rs create mode 100644 crates/ra_assists/src/assists/add_derive.rs create mode 100644 crates/ra_assists/src/assists/add_explicit_type.rs create mode 100644 crates/ra_assists/src/assists/add_impl.rs create mode 100644 crates/ra_assists/src/assists/add_missing_impl_members.rs create mode 100644 crates/ra_assists/src/assists/auto_import.rs create mode 100644 crates/ra_assists/src/assists/change_visibility.rs create mode 100644 crates/ra_assists/src/assists/fill_match_arms.rs create mode 100644 crates/ra_assists/src/assists/flip_binexpr.rs create mode 100644 crates/ra_assists/src/assists/flip_comma.rs create mode 100644 crates/ra_assists/src/assists/inline_local_variable.rs create mode 100644 crates/ra_assists/src/assists/introduce_variable.rs create mode 100644 crates/ra_assists/src/assists/merge_match_arms.rs create mode 100644 crates/ra_assists/src/assists/move_bounds.rs create mode 100644 crates/ra_assists/src/assists/move_guard.rs create mode 100644 crates/ra_assists/src/assists/raw_string.rs create mode 100644 crates/ra_assists/src/assists/remove_dbg.rs create mode 100644 crates/ra_assists/src/assists/replace_if_let_with_match.rs create mode 100644 crates/ra_assists/src/assists/split_import.rs delete mode 100644 crates/ra_assists/src/auto_import.rs delete mode 100644 crates/ra_assists/src/change_visibility.rs delete mode 100644 crates/ra_assists/src/fill_match_arms.rs delete mode 100644 crates/ra_assists/src/flip_binexpr.rs delete mode 100644 crates/ra_assists/src/flip_comma.rs delete mode 100644 crates/ra_assists/src/inline_local_variable.rs delete mode 100644 crates/ra_assists/src/introduce_variable.rs delete mode 100644 crates/ra_assists/src/merge_match_arms.rs delete mode 100644 crates/ra_assists/src/move_bounds.rs delete mode 100644 crates/ra_assists/src/move_guard.rs delete mode 100644 crates/ra_assists/src/raw_string.rs delete mode 100644 crates/ra_assists/src/remove_dbg.rs delete mode 100644 crates/ra_assists/src/replace_if_let_with_match.rs delete mode 100644 crates/ra_assists/src/split_import.rs diff --git a/crates/ra_assists/src/add_derive.rs b/crates/ra_assists/src/add_derive.rs deleted file mode 100644 index 9c88644df..000000000 --- a/crates/ra_assists/src/add_derive.rs +++ /dev/null @@ -1,105 +0,0 @@ -use hir::db::HirDatabase; -use ra_syntax::{ - ast::{self, AstNode, AttrsOwner}, - SyntaxKind::{COMMENT, WHITESPACE}, - TextUnit, -}; - -use crate::{Assist, AssistCtx, AssistId}; - -pub(crate) fn add_derive(mut ctx: AssistCtx) -> Option { - let nominal = ctx.node_at_offset::()?; - let node_start = derive_insertion_offset(&nominal)?; - ctx.add_action(AssistId("add_derive"), "add `#[derive]`", |edit| { - let derive_attr = nominal - .attrs() - .filter_map(|x| x.as_call()) - .filter(|(name, _arg)| name == "derive") - .map(|(_name, arg)| arg) - .next(); - let offset = match derive_attr { - None => { - edit.insert(node_start, "#[derive()]\n"); - node_start + TextUnit::of_str("#[derive(") - } - Some(tt) => tt.syntax().text_range().end() - TextUnit::of_char(')'), - }; - edit.target(nominal.syntax().text_range()); - edit.set_cursor(offset) - }); - - ctx.build() -} - -// Insert `derive` after doc comments. -fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option { - let non_ws_child = nominal - .syntax() - .children_with_tokens() - .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; - Some(non_ws_child.text_range().start()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::helpers::{check_assist, check_assist_target}; - - #[test] - fn add_derive_new() { - check_assist( - add_derive, - "struct Foo { a: i32, <|>}", - "#[derive(<|>)]\nstruct Foo { a: i32, }", - ); - check_assist( - add_derive, - "struct Foo { <|> a: i32, }", - "#[derive(<|>)]\nstruct Foo { a: i32, }", - ); - } - - #[test] - fn add_derive_existing() { - check_assist( - add_derive, - "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", - "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", - ); - } - - #[test] - fn add_derive_new_with_doc_comment() { - check_assist( - add_derive, - " -/// `Foo` is a pretty important struct. -/// It does stuff. -struct Foo { a: i32<|>, } - ", - " -/// `Foo` is a pretty important struct. -/// It does stuff. -#[derive(<|>)] -struct Foo { a: i32, } - ", - ); - } - - #[test] - fn add_derive_target() { - check_assist_target( - add_derive, - " -struct SomeThingIrrelevant; -/// `Foo` is a pretty important struct. -/// It does stuff. -struct Foo { a: i32<|>, } -struct EvenMoreIrrelevant; - ", - "/// `Foo` is a pretty important struct. -/// It does stuff. -struct Foo { a: i32, }", - ); - } -} diff --git a/crates/ra_assists/src/add_explicit_type.rs b/crates/ra_assists/src/add_explicit_type.rs deleted file mode 100644 index 78f0f7f28..000000000 --- a/crates/ra_assists/src/add_explicit_type.rs +++ /dev/null @@ -1,86 +0,0 @@ -use hir::{db::HirDatabase, HirDisplay, Ty}; -use ra_syntax::{ - ast::{self, AstNode, LetStmt, NameOwner}, - T, -}; - -use crate::{Assist, AssistCtx, AssistId}; - -/// Add explicit type assist. -pub(crate) fn add_explicit_type(mut ctx: AssistCtx) -> Option { - let stmt = ctx.node_at_offset::()?; - let expr = stmt.initializer()?; - let pat = stmt.pat()?; - // Must be a binding - let pat = match pat { - ast::Pat::BindPat(bind_pat) => bind_pat, - _ => return None, - }; - let pat_range = pat.syntax().text_range(); - // The binding must have a name - let name = pat.name()?; - let name_range = name.syntax().text_range(); - // Assist not applicable if the type has already been specified - if stmt.syntax().children_with_tokens().any(|child| child.kind() == T![:]) { - return None; - } - // Infer type - let db = ctx.db; - let analyzer = hir::SourceAnalyzer::new(db, ctx.frange.file_id, stmt.syntax(), None); - let ty = analyzer.type_of(db, &expr)?; - // Assist not applicable if the type is unknown - if is_unknown(&ty) { - return None; - } - - ctx.add_action(AssistId("add_explicit_type"), "add explicit type", |edit| { - edit.target(pat_range); - edit.insert(name_range.end(), format!(": {}", ty.display(db))); - }); - ctx.build() -} - -/// Returns true if any type parameter is unknown -fn is_unknown(ty: &Ty) -> bool { - match ty { - Ty::Unknown => true, - Ty::Apply(a_ty) => a_ty.parameters.iter().any(is_unknown), - _ => false, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; - - #[test] - fn add_explicit_type_target() { - check_assist_target(add_explicit_type, "fn f() { let a<|> = 1; }", "a"); - } - - #[test] - fn add_explicit_type_works_for_simple_expr() { - check_assist( - add_explicit_type, - "fn f() { let a<|> = 1; }", - "fn f() { let a<|>: i32 = 1; }", - ); - } - - #[test] - fn add_explicit_type_not_applicable_if_ty_not_inferred() { - check_assist_not_applicable(add_explicit_type, "fn f() { let a<|> = None; }"); - } - - #[test] - fn add_explicit_type_not_applicable_if_ty_already_specified() { - check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: i32 = 1; }"); - } - - #[test] - fn add_explicit_type_not_applicable_if_specified_ty_is_tuple() { - check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: (i32, i32) = (3, 4); }"); - } -} diff --git a/crates/ra_assists/src/add_impl.rs b/crates/ra_assists/src/add_impl.rs deleted file mode 100644 index 4b61f4031..000000000 --- a/crates/ra_assists/src/add_impl.rs +++ /dev/null @@ -1,77 +0,0 @@ -use format_buf::format; -use hir::db::HirDatabase; -use join_to_string::join; -use ra_syntax::{ - ast::{self, AstNode, NameOwner, TypeParamsOwner}, - TextUnit, -}; - -use crate::{Assist, AssistCtx, AssistId}; - -pub(crate) fn add_impl(mut ctx: AssistCtx) -> Option { - let nominal = ctx.node_at_offset::()?; - let name = nominal.name()?; - ctx.add_action(AssistId("add_impl"), "add impl", |edit| { - edit.target(nominal.syntax().text_range()); - let type_params = nominal.type_param_list(); - let start_offset = nominal.syntax().text_range().end(); - let mut buf = String::new(); - buf.push_str("\n\nimpl"); - if let Some(type_params) = &type_params { - format!(buf, "{}", type_params.syntax()); - } - buf.push_str(" "); - buf.push_str(name.text().as_str()); - if let Some(type_params) = type_params { - let lifetime_params = type_params - .lifetime_params() - .filter_map(|it| it.lifetime_token()) - .map(|it| it.text().clone()); - let type_params = - type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); - join(lifetime_params.chain(type_params)).surround_with("<", ">").to_buf(&mut buf); - } - buf.push_str(" {\n"); - edit.set_cursor(start_offset + TextUnit::of_str(&buf)); - buf.push_str("\n}"); - edit.insert(start_offset, buf); - }); - - ctx.build() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::helpers::{check_assist, check_assist_target}; - - #[test] - fn test_add_impl() { - check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n<|>\n}\n"); - check_assist( - add_impl, - "struct Foo {<|>}", - "struct Foo {}\n\nimpl Foo {\n<|>\n}", - ); - check_assist( - add_impl, - "struct Foo<'a, T: Foo<'a>> {<|>}", - "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", - ); - } - - #[test] - fn add_impl_target() { - check_assist_target( - add_impl, - " -struct SomeThingIrrelevant; -/// Has a lifetime parameter -struct Foo<'a, T: Foo<'a>> {<|>} -struct EvenMoreIrrelevant; -", - "/// Has a lifetime parameter -struct Foo<'a, T: Foo<'a>> {}", - ); - } -} diff --git a/crates/ra_assists/src/add_missing_impl_members.rs b/crates/ra_assists/src/add_missing_impl_members.rs deleted file mode 100644 index cbeb7054f..000000000 --- a/crates/ra_assists/src/add_missing_impl_members.rs +++ /dev/null @@ -1,340 +0,0 @@ -use hir::{db::HirDatabase, HasSource}; -use ra_syntax::{ - ast::{self, AstNode, NameOwner}, - SmolStr, -}; - -use crate::{ - ast_editor::{AstBuilder, AstEditor}, - Assist, AssistCtx, AssistId, -}; - -#[derive(PartialEq)] -enum AddMissingImplMembersMode { - DefaultMethodsOnly, - NoDefaultMethods, -} - -pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option { - add_missing_impl_members_inner( - ctx, - AddMissingImplMembersMode::NoDefaultMethods, - "add_impl_missing_members", - "add missing impl members", - ) -} - -pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option { - add_missing_impl_members_inner( - ctx, - AddMissingImplMembersMode::DefaultMethodsOnly, - "add_impl_default_members", - "add impl default members", - ) -} - -fn add_missing_impl_members_inner( - mut ctx: AssistCtx, - mode: AddMissingImplMembersMode, - assist_id: &'static str, - label: &'static str, -) -> Option { - let impl_node = ctx.node_at_offset::()?; - let impl_item_list = impl_node.item_list()?; - - let trait_def = { - let file_id = ctx.frange.file_id; - let analyzer = hir::SourceAnalyzer::new(ctx.db, file_id, impl_node.syntax(), None); - - resolve_target_trait_def(ctx.db, &analyzer, &impl_node)? - }; - - let def_name = |item: &ast::ImplItem| -> Option { - match item { - ast::ImplItem::FnDef(def) => def.name(), - ast::ImplItem::TypeAliasDef(def) => def.name(), - ast::ImplItem::ConstDef(def) => def.name(), - } - .map(|it| it.text().clone()) - }; - - let trait_items = trait_def.item_list()?.impl_items(); - let impl_items = impl_item_list.impl_items().collect::>(); - - let missing_items: Vec<_> = trait_items - .filter(|t| def_name(t).is_some()) - .filter(|t| match t { - ast::ImplItem::FnDef(def) => match mode { - AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(), - AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(), - }, - _ => mode == AddMissingImplMembersMode::NoDefaultMethods, - }) - .filter(|t| impl_items.iter().all(|i| def_name(i) != def_name(t))) - .collect(); - if missing_items.is_empty() { - return None; - } - - ctx.add_action(AssistId(assist_id), label, |edit| { - let n_existing_items = impl_item_list.impl_items().count(); - let items = missing_items.into_iter().map(|it| match it { - ast::ImplItem::FnDef(def) => strip_docstring(add_body(def).into()), - _ => strip_docstring(it), - }); - let mut ast_editor = AstEditor::new(impl_item_list); - - ast_editor.append_items(items); - - let first_new_item = ast_editor.ast().impl_items().nth(n_existing_items).unwrap(); - let cursor_position = first_new_item.syntax().text_range().start(); - ast_editor.into_text_edit(edit.text_edit_builder()); - - edit.set_cursor(cursor_position); - }); - - ctx.build() -} - -fn strip_docstring(item: ast::ImplItem) -> ast::ImplItem { - let mut ast_editor = AstEditor::new(item); - ast_editor.strip_attrs_and_docs(); - ast_editor.ast().to_owned() -} - -fn add_body(fn_def: ast::FnDef) -> ast::FnDef { - let mut ast_editor = AstEditor::new(fn_def.clone()); - if fn_def.body().is_none() { - ast_editor.set_body(&AstBuilder::::single_expr( - &AstBuilder::::unimplemented(), - )); - } - ast_editor.ast().to_owned() -} - -/// Given an `ast::ImplBlock`, resolves the target trait (the one being -/// implemented) to a `ast::TraitDef`. -fn resolve_target_trait_def( - db: &impl HirDatabase, - analyzer: &hir::SourceAnalyzer, - impl_block: &ast::ImplBlock, -) -> Option { - let ast_path = impl_block - .target_trait() - .map(|it| it.syntax().clone()) - .and_then(ast::PathType::cast)? - .path()?; - - match analyzer.resolve_path(db, &ast_path) { - Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def.source(db).ast), - _ => None, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::helpers::{check_assist, check_assist_not_applicable}; - - #[test] - fn test_add_missing_impl_members() { - check_assist( - add_missing_impl_members, - " -trait Foo { - type Output; - - const CONST: usize = 42; - - fn foo(&self); - fn bar(&self); - fn baz(&self); -} - -struct S; - -impl Foo for S { - fn bar(&self) {} -<|> -}", - " -trait Foo { - type Output; - - const CONST: usize = 42; - - fn foo(&self); - fn bar(&self); - fn baz(&self); -} - -struct S; - -impl Foo for S { - fn bar(&self) {} - <|>type Output; - const CONST: usize = 42; - fn foo(&self) { unimplemented!() } - fn baz(&self) { unimplemented!() } - -}", - ); - } - - #[test] - fn test_copied_overriden_members() { - check_assist( - add_missing_impl_members, - " -trait Foo { - fn foo(&self); - fn bar(&self) -> bool { true } - fn baz(&self) -> u32 { 42 } -} - -struct S; - -impl Foo for S { - fn bar(&self) {} -<|> -}", - " -trait Foo { - fn foo(&self); - fn bar(&self) -> bool { true } - fn baz(&self) -> u32 { 42 } -} - -struct S; - -impl Foo for S { - fn bar(&self) {} - <|>fn foo(&self) { unimplemented!() } - -}", - ); - } - - #[test] - fn test_empty_impl_block() { - check_assist( - add_missing_impl_members, - " -trait Foo { fn foo(&self); } -struct S; -impl Foo for S { <|> }", - " -trait Foo { fn foo(&self); } -struct S; -impl Foo for S { - <|>fn foo(&self) { unimplemented!() } -}", - ); - } - - #[test] - fn test_cursor_after_empty_impl_block() { - check_assist( - add_missing_impl_members, - " -trait Foo { fn foo(&self); } -struct S; -impl Foo for S {}<|>", - " -trait Foo { fn foo(&self); } -struct S; -impl Foo for S { - <|>fn foo(&self) { unimplemented!() } -}", - ) - } - - #[test] - fn test_empty_trait() { - check_assist_not_applicable( - add_missing_impl_members, - " -trait Foo; -struct S; -impl Foo for S { <|> }", - ) - } - - #[test] - fn test_ignore_unnamed_trait_members_and_default_methods() { - check_assist_not_applicable( - add_missing_impl_members, - " -trait Foo { - fn (arg: u32); - fn valid(some: u32) -> bool { false } -} -struct S; -impl Foo for S { <|> }", - ) - } - - #[test] - fn test_with_docstring_and_attrs() { - check_assist( - add_missing_impl_members, - r#" -#[doc(alias = "test alias")] -trait Foo { - /// doc string - type Output; - - #[must_use] - fn foo(&self); -} -struct S; -impl Foo for S {}<|>"#, - r#" -#[doc(alias = "test alias")] -trait Foo { - /// doc string - type Output; - - #[must_use] - fn foo(&self); -} -struct S; -impl Foo for S { - <|>type Output; - fn foo(&self) { unimplemented!() } -}"#, - ) - } - - #[test] - fn test_default_methods() { - check_assist( - add_missing_default_members, - " -trait Foo { - type Output; - - const CONST: usize = 42; - - fn valid(some: u32) -> bool { false } - fn foo(some: u32) -> bool; -} -struct S; -impl Foo for S { <|> }", - " -trait Foo { - type Output; - - const CONST: usize = 42; - - fn valid(some: u32) -> bool { false } - fn foo(some: u32) -> bool; -} -struct S; -impl Foo for S { - <|>fn valid(some: u32) -> bool { false } -}", - ) - } - -} diff --git a/crates/ra_assists/src/assists/add_derive.rs b/crates/ra_assists/src/assists/add_derive.rs new file mode 100644 index 000000000..9c88644df --- /dev/null +++ b/crates/ra_assists/src/assists/add_derive.rs @@ -0,0 +1,105 @@ +use hir::db::HirDatabase; +use ra_syntax::{ + ast::{self, AstNode, AttrsOwner}, + SyntaxKind::{COMMENT, WHITESPACE}, + TextUnit, +}; + +use crate::{Assist, AssistCtx, AssistId}; + +pub(crate) fn add_derive(mut ctx: AssistCtx) -> Option { + let nominal = ctx.node_at_offset::()?; + let node_start = derive_insertion_offset(&nominal)?; + ctx.add_action(AssistId("add_derive"), "add `#[derive]`", |edit| { + let derive_attr = nominal + .attrs() + .filter_map(|x| x.as_call()) + .filter(|(name, _arg)| name == "derive") + .map(|(_name, arg)| arg) + .next(); + let offset = match derive_attr { + None => { + edit.insert(node_start, "#[derive()]\n"); + node_start + TextUnit::of_str("#[derive(") + } + Some(tt) => tt.syntax().text_range().end() - TextUnit::of_char(')'), + }; + edit.target(nominal.syntax().text_range()); + edit.set_cursor(offset) + }); + + ctx.build() +} + +// Insert `derive` after doc comments. +fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option { + let non_ws_child = nominal + .syntax() + .children_with_tokens() + .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; + Some(non_ws_child.text_range().start()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::helpers::{check_assist, check_assist_target}; + + #[test] + fn add_derive_new() { + check_assist( + add_derive, + "struct Foo { a: i32, <|>}", + "#[derive(<|>)]\nstruct Foo { a: i32, }", + ); + check_assist( + add_derive, + "struct Foo { <|> a: i32, }", + "#[derive(<|>)]\nstruct Foo { a: i32, }", + ); + } + + #[test] + fn add_derive_existing() { + check_assist( + add_derive, + "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", + "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", + ); + } + + #[test] + fn add_derive_new_with_doc_comment() { + check_assist( + add_derive, + " +/// `Foo` is a pretty important struct. +/// It does stuff. +struct Foo { a: i32<|>, } + ", + " +/// `Foo` is a pretty important struct. +/// It does stuff. +#[derive(<|>)] +struct Foo { a: i32, } + ", + ); + } + + #[test] + fn add_derive_target() { + check_assist_target( + add_derive, + " +struct SomeThingIrrelevant; +/// `Foo` is a pretty important struct. +/// It does stuff. +struct Foo { a: i32<|>, } +struct EvenMoreIrrelevant; + ", + "/// `Foo` is a pretty important struct. +/// It does stuff. +struct Foo { a: i32, }", + ); + } +} diff --git a/crates/ra_assists/src/assists/add_explicit_type.rs b/crates/ra_assists/src/assists/add_explicit_type.rs new file mode 100644 index 000000000..78f0f7f28 --- /dev/null +++ b/crates/ra_assists/src/assists/add_explicit_type.rs @@ -0,0 +1,86 @@ +use hir::{db::HirDatabase, HirDisplay, Ty}; +use ra_syntax::{ + ast::{self, AstNode, LetStmt, NameOwner}, + T, +}; + +use crate::{Assist, AssistCtx, AssistId}; + +/// Add explicit type assist. +pub(crate) fn add_explicit_type(mut ctx: AssistCtx) -> Option { + let stmt = ctx.node_at_offset::()?; + let expr = stmt.initializer()?; + let pat = stmt.pat()?; + // Must be a binding + let pat = match pat { + ast::Pat::BindPat(bind_pat) => bind_pat, + _ => return None, + }; + let pat_range = pat.syntax().text_range(); + // The binding must have a name + let name = pat.name()?; + let name_range = name.syntax().text_range(); + // Assist not applicable if the type has already been specified + if stmt.syntax().children_with_tokens().any(|child| child.kind() == T![:]) { + return None; + } + // Infer type + let db = ctx.db; + let analyzer = hir::SourceAnalyzer::new(db, ctx.frange.file_id, stmt.syntax(), None); + let ty = analyzer.type_of(db, &expr)?; + // Assist not applicable if the type is unknown + if is_unknown(&ty) { + return None; + } + + ctx.add_action(AssistId("add_explicit_type"), "add explicit type", |edit| { + edit.target(pat_range); + edit.insert(name_range.end(), format!(": {}", ty.display(db))); + }); + ctx.build() +} + +/// Returns true if any type parameter is unknown +fn is_unknown(ty: &Ty) -> bool { + match ty { + Ty::Unknown => true, + Ty::Apply(a_ty) => a_ty.parameters.iter().any(is_unknown), + _ => false, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + + #[test] + fn add_explicit_type_target() { + check_assist_target(add_explicit_type, "fn f() { let a<|> = 1; }", "a"); + } + + #[test] + fn add_explicit_type_works_for_simple_expr() { + check_assist( + add_explicit_type, + "fn f() { let a<|> = 1; }", + "fn f() { let a<|>: i32 = 1; }", + ); + } + + #[test] + fn add_explicit_type_not_applicable_if_ty_not_inferred() { + check_assist_not_applicable(add_explicit_type, "fn f() { let a<|> = None; }"); + } + + #[test] + fn add_explicit_type_not_applicable_if_ty_already_specified() { + check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: i32 = 1; }"); + } + + #[test] + fn add_explicit_type_not_applicable_if_specified_ty_is_tuple() { + check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: (i32, i32) = (3, 4); }"); + } +} diff --git a/crates/ra_assists/src/assists/add_impl.rs b/crates/ra_assists/src/assists/add_impl.rs new file mode 100644 index 000000000..4b61f4031 --- /dev/null +++ b/crates/ra_assists/src/assists/add_impl.rs @@ -0,0 +1,77 @@ +use format_buf::format; +use hir::db::HirDatabase; +use join_to_string::join; +use ra_syntax::{ + ast::{self, AstNode, NameOwner, TypeParamsOwner}, + TextUnit, +}; + +use crate::{Assist, AssistCtx, AssistId}; + +pub(crate) fn add_impl(mut ctx: AssistCtx) -> Option { + let nominal = ctx.node_at_offset::()?; + let name = nominal.name()?; + ctx.add_action(AssistId("add_impl"), "add impl", |edit| { + edit.target(nominal.syntax().text_range()); + let type_params = nominal.type_param_list(); + let start_offset = nominal.syntax().text_range().end(); + let mut buf = String::new(); + buf.push_str("\n\nimpl"); + if let Some(type_params) = &type_params { + format!(buf, "{}", type_params.syntax()); + } + buf.push_str(" "); + buf.push_str(name.text().as_str()); + if let Some(type_params) = type_params { + let lifetime_params = type_params + .lifetime_params() + .filter_map(|it| it.lifetime_token()) + .map(|it| it.text().clone()); + let type_params = + type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); + join(lifetime_params.chain(type_params)).surround_with("<", ">").to_buf(&mut buf); + } + buf.push_str(" {\n"); + edit.set_cursor(start_offset + TextUnit::of_str(&buf)); + buf.push_str("\n}"); + edit.insert(start_offset, buf); + }); + + ctx.build() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::helpers::{check_assist, check_assist_target}; + + #[test] + fn test_add_impl() { + check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n<|>\n}\n"); + check_assist( + add_impl, + "struct Foo {<|>}", + "struct Foo {}\n\nimpl Foo {\n<|>\n}", + ); + check_assist( + add_impl, + "struct Foo<'a, T: Foo<'a>> {<|>}", + "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", + ); + } + + #[test] + fn add_impl_target() { + check_assist_target( + add_impl, + " +struct SomeThingIrrelevant; +/// Has a lifetime parameter +struct Foo<'a, T: Foo<'a>> {<|>} +struct EvenMoreIrrelevant; +", + "/// Has a lifetime parameter +struct Foo<'a, T: Foo<'a>> {}", + ); + } +} diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/assists/add_missing_impl_members.rs new file mode 100644 index 000000000..cbeb7054f --- /dev/null +++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs @@ -0,0 +1,340 @@ +use hir::{db::HirDatabase, HasSource}; +use ra_syntax::{ + ast::{self, AstNode, NameOwner}, + SmolStr, +}; + +use crate::{ + ast_editor::{AstBuilder, AstEditor}, + Assist, AssistCtx, AssistId, +}; + +#[derive(PartialEq)] +enum AddMissingImplMembersMode { + DefaultMethodsOnly, + NoDefaultMethods, +} + +pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option { + add_missing_impl_members_inner( + ctx, + AddMissingImplMembersMode::NoDefaultMethods, + "add_impl_missing_members", + "add missing impl members", + ) +} + +pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option { + add_missing_impl_members_inner( + ctx, + AddMissingImplMembersMode::DefaultMethodsOnly, + "add_impl_default_members", + "add impl default members", + ) +} + +fn add_missing_impl_members_inner( + mut ctx: AssistCtx, + mode: AddMissingImplMembersMode, + assist_id: &'static str, + label: &'static str, +) -> Option { + let impl_node = ctx.node_at_offset::()?; + let impl_item_list = impl_node.item_list()?; + + let trait_def = { + let file_id = ctx.frange.file_id; + let analyzer = hir::SourceAnalyzer::new(ctx.db, file_id, impl_node.syntax(), None); + + resolve_target_trait_def(ctx.db, &analyzer, &impl_node)? + }; + + let def_name = |item: &ast::ImplItem| -> Option { + match item { + ast::ImplItem::FnDef(def) => def.name(), + ast::ImplItem::TypeAliasDef(def) => def.name(), + ast::ImplItem::ConstDef(def) => def.name(), + } + .map(|it| it.text().clone()) + }; + + let trait_items = trait_def.item_list()?.impl_items(); + let impl_items = impl_item_list.impl_items().collect::>(); + + let missing_items: Vec<_> = trait_items + .filter(|t| def_name(t).is_some()) + .filter(|t| match t { + ast::ImplItem::FnDef(def) => match mode { + AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(), + AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(), + }, + _ => mode == AddMissingImplMembersMode::NoDefaultMethods, + }) + .filter(|t| impl_items.iter().all(|i| def_name(i) != def_name(t))) + .collect(); + if missing_items.is_empty() { + return None; + } + + ctx.add_action(AssistId(assist_id), label, |edit| { + let n_existing_items = impl_item_list.impl_items().count(); + let items = missing_items.into_iter().map(|it| match it { + ast::ImplItem::FnDef(def) => strip_docstring(add_body(def).into()), + _ => strip_docstring(it), + }); + let mut ast_editor = AstEditor::new(impl_item_list); + + ast_editor.append_items(items); + + let first_new_item = ast_editor.ast().impl_items().nth(n_existing_items).unwrap(); + let cursor_position = first_new_item.syntax().text_range().start(); + ast_editor.into_text_edit(edit.text_edit_builder()); + + edit.set_cursor(cursor_position); + }); + + ctx.build() +} + +fn strip_docstring(item: ast::ImplItem) -> ast::ImplItem { + let mut ast_editor = AstEditor::new(item); + ast_editor.strip_attrs_and_docs(); + ast_editor.ast().to_owned() +} + +fn add_body(fn_def: ast::FnDef) -> ast::FnDef { + let mut ast_editor = AstEditor::new(fn_def.clone()); + if fn_def.body().is_none() { + ast_editor.set_body(&AstBuilder::::single_expr( + &AstBuilder::::unimplemented(), + )); + } + ast_editor.ast().to_owned() +} + +/// Given an `ast::ImplBlock`, resolves the target trait (the one being +/// implemented) to a `ast::TraitDef`. +fn resolve_target_trait_def( + db: &impl HirDatabase, + analyzer: &hir::SourceAnalyzer, + impl_block: &ast::ImplBlock, +) -> Option { + let ast_path = impl_block + .target_trait() + .map(|it| it.syntax().clone()) + .and_then(ast::PathType::cast)? + .path()?; + + match analyzer.resolve_path(db, &ast_path) { + Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def.source(db).ast), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::helpers::{check_assist, check_assist_not_applicable}; + + #[test] + fn test_add_missing_impl_members() { + check_assist( + add_missing_impl_members, + " +trait Foo { + type Output; + + const CONST: usize = 42; + + fn foo(&self); + fn bar(&self); + fn baz(&self); +} + +struct S; + +impl Foo for S { + fn bar(&self) {} +<|> +}", + " +trait Foo { + type Output; + + const CONST: usize = 42; + + fn foo(&self); + fn bar(&self); + fn baz(&self); +} + +struct S; + +impl Foo for S { + fn bar(&self) {} + <|>type Output; + const CONST: usize = 42; + fn foo(&self) { unimplemented!() } + fn baz(&self) { unimplemented!() } + +}", + ); + } + + #[test] + fn test_copied_overriden_members() { + check_assist( + add_missing_impl_members, + " +trait Foo { + fn foo(&self); + fn bar(&self) -> bool { true } + fn baz(&self) -> u32 { 42 } +} + +struct S; + +impl Foo for S { + fn bar(&self) {} +<|> +}", + " +trait Foo { + fn foo(&self); + fn bar(&self) -> bool { true } + fn baz(&self) -> u32 { 42 } +} + +struct S; + +impl Foo for S { + fn bar(&self) {} + <|>fn foo(&self) { unimplemented!() } + +}", + ); + } + + #[test] + fn test_empty_impl_block() { + check_assist( + add_missing_impl_members, + " +trait Foo { fn foo(&self); } +struct S; +impl Foo for S { <|> }", + " +trait Foo { fn foo(&self); } +struct S; +impl Foo for S { + <|>fn foo(&self) { unimplemented!() } +}", + ); + } + + #[test] + fn test_cursor_after_empty_impl_block() { + check_assist( + add_missing_impl_members, + " +trait Foo { fn foo(&self); } +struct S; +impl Foo for S {}<|>", + " +trait Foo { fn foo(&self); } +struct S; +impl Foo for S { + <|>fn foo(&self) { unimplemented!() } +}", + ) + } + + #[test] + fn test_empty_trait() { + check_assist_not_applicable( + add_missing_impl_members, + " +trait Foo; +struct S; +impl Foo for S { <|> }", + ) + } + + #[test] + fn test_ignore_unnamed_trait_members_and_default_methods() { + check_assist_not_applicable( + add_missing_impl_members, + " +trait Foo { + fn (arg: u32); + fn valid(some: u32) -> bool { false } +} +struct S; +impl Foo for S { <|> }", + ) + } + + #[test] + fn test_with_docstring_and_attrs() { + check_assist( + add_missing_impl_members, + r#" +#[doc(alias = "test alias")] +trait Foo { + /// doc string + type Output; + + #[must_use] + fn foo(&self); +} +struct S; +impl Foo for S {}<|>"#, + r#" +#[doc(alias = "test alias")] +trait Foo { + /// doc string + type Output; + + #[must_use] + fn foo(&self); +} +struct S; +impl Foo for S { + <|>type Output; + fn foo(&self) { unimplemented!() } +}"#, + ) + } + + #[test] + fn test_default_methods() { + check_assist( + add_missing_default_members, + " +trait Foo { + type Output; + + const CONST: usize = 42; + + fn valid(some: u32) -> bool { false } + fn foo(some: u32) -> bool; +} +struct S; +impl Foo for S { <|> }", + " +trait Foo { + type Output; + + const CONST: usize = 42; + + fn valid(some: u32) -> bool { false } + fn foo(some: u32) -> bool; +} +struct S; +impl Foo for S { + <|>fn valid(some: u32) -> bool { false } +}", + ) + } + +} diff --git a/crates/ra_assists/src/assists/auto_import.rs b/crates/ra_assists/src/assists/auto_import.rs new file mode 100644 index 000000000..5aae98546 --- /dev/null +++ b/crates/ra_assists/src/assists/auto_import.rs @@ -0,0 +1,939 @@ +use hir::{self, db::HirDatabase}; +use ra_text_edit::TextEditBuilder; + +use crate::{ + assist_ctx::{Assist, AssistCtx}, + AssistId, +}; +use ra_syntax::{ + ast::{self, NameOwner}, + AstNode, Direction, SmolStr, + SyntaxKind::{PATH, PATH_SEGMENT}, + SyntaxNode, TextRange, T, +}; + +fn collect_path_segments_raw( + segments: &mut Vec, + mut path: ast::Path, +) -> Option { + let oldlen = segments.len(); + loop { + let mut children = path.syntax().children_with_tokens(); + let (first, second, third) = ( + children.next().map(|n| (n.clone(), n.kind())), + children.next().map(|n| (n.clone(), n.kind())), + children.next().map(|n| (n.clone(), n.kind())), + ); + match (first, second, third) { + (Some((subpath, PATH)), Some((_, T![::])), Some((segment, PATH_SEGMENT))) => { + path = ast::Path::cast(subpath.as_node()?.clone())?; + segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?); + } + (Some((segment, PATH_SEGMENT)), _, _) => { + segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?); + break; + } + (_, _, _) => return None, + } + } + // We need to reverse only the new added segments + let only_new_segments = segments.split_at_mut(oldlen).1; + only_new_segments.reverse(); + Some(segments.len() - oldlen) +} + +fn fmt_segments(segments: &[SmolStr]) -> String { + let mut buf = String::new(); + fmt_segments_raw(segments, &mut buf); + buf +} + +fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) { + let mut iter = segments.iter(); + if let Some(s) = iter.next() { + buf.push_str(s); + } + for s in iter { + buf.push_str("::"); + buf.push_str(s); + } +} + +// Returns the numeber of common segments. +fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize { + left.iter().zip(right).filter(|(l, r)| compare_path_segment(l, r)).count() +} + +fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool { + if let Some(kb) = b.kind() { + match kb { + ast::PathSegmentKind::Name(nameref_b) => a == nameref_b.text(), + ast::PathSegmentKind::SelfKw => a == "self", + ast::PathSegmentKind::SuperKw => a == "super", + ast::PathSegmentKind::CrateKw => a == "crate", + ast::PathSegmentKind::Type { .. } => false, // not allowed in imports + } + } else { + false + } +} + +fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool { + a == b.text() +} + +#[derive(Clone)] +enum ImportAction { + Nothing, + // Add a brand new use statement. + AddNewUse { + anchor: Option, // anchor node + add_after_anchor: bool, + }, + + // To split an existing use statement creating a nested import. + AddNestedImport { + // how may segments matched with the target path + common_segments: usize, + path_to_split: ast::Path, + // the first segment of path_to_split we want to add into the new nested list + first_segment_to_split: Option, + // Wether to add 'self' in addition to the target path + add_self: bool, + }, + // To add the target path to an existing nested import tree list. + AddInTreeList { + common_segments: usize, + // The UseTreeList where to add the target path + tree_list: ast::UseTreeList, + add_self: bool, + }, +} + +impl ImportAction { + fn add_new_use(anchor: Option, add_after_anchor: bool) -> Self { + ImportAction::AddNewUse { anchor, add_after_anchor } + } + + fn add_nested_import( + common_segments: usize, + path_to_split: ast::Path, + first_segment_to_split: Option, + add_self: bool, + ) -> Self { + ImportAction::AddNestedImport { + common_segments, + path_to_split, + first_segment_to_split, + add_self, + } + } + + fn add_in_tree_list( + common_segments: usize, + tree_list: ast::UseTreeList, + add_self: bool, + ) -> Self { + ImportAction::AddInTreeList { common_segments, tree_list, add_self } + } + + fn better(left: ImportAction, right: ImportAction) -> ImportAction { + if left.is_better(&right) { + left + } else { + right + } + } + + fn is_better(&self, other: &ImportAction) -> bool { + match (self, other) { + (ImportAction::Nothing, _) => true, + (ImportAction::AddInTreeList { .. }, ImportAction::Nothing) => false, + ( + ImportAction::AddNestedImport { common_segments: n, .. }, + ImportAction::AddInTreeList { common_segments: m, .. }, + ) => n > m, + ( + ImportAction::AddInTreeList { common_segments: n, .. }, + ImportAction::AddNestedImport { common_segments: m, .. }, + ) => n > m, + (ImportAction::AddInTreeList { .. }, _) => true, + (ImportAction::AddNestedImport { .. }, ImportAction::Nothing) => false, + (ImportAction::AddNestedImport { .. }, _) => true, + (ImportAction::AddNewUse { .. }, _) => false, + } + } +} + +// Find out the best ImportAction to import target path against current_use_tree. +// If current_use_tree has a nested import the function gets called recursively on every UseTree inside a UseTreeList. +fn walk_use_tree_for_best_action( + current_path_segments: &mut Vec, // buffer containing path segments + current_parent_use_tree_list: Option, // will be Some value if we are in a nested import + current_use_tree: ast::UseTree, // the use tree we are currently examinating + target: &[SmolStr], // the path we want to import +) -> ImportAction { + // We save the number of segments in the buffer so we can restore the correct segments + // before returning. Recursive call will add segments so we need to delete them. + let prev_len = current_path_segments.len(); + + let tree_list = current_use_tree.use_tree_list(); + let alias = current_use_tree.alias(); + + let path = match current_use_tree.path() { + Some(path) => path, + None => { + // If the use item don't have a path, it means it's broken (syntax error) + return ImportAction::add_new_use( + current_use_tree + .syntax() + .ancestors() + .find_map(ast::UseItem::cast) + .map(|it| it.syntax().clone()), + true, + ); + } + }; + + // This can happen only if current_use_tree is a direct child of a UseItem + if let Some(name) = alias.and_then(|it| it.name()) { + if compare_path_segment_with_name(&target[0], &name) { + return ImportAction::Nothing; + } + } + + collect_path_segments_raw(current_path_segments, path.clone()); + + // We compare only the new segments added in the line just above. + // The first prev_len segments were already compared in 'parent' recursive calls. + let left = target.split_at(prev_len).1; + let right = current_path_segments.split_at(prev_len).1; + let common = compare_path_segments(left, &right); + let mut action = match common { + 0 => ImportAction::add_new_use( + // e.g: target is std::fmt and we can have + // use foo::bar + // We add a brand new use statement + current_use_tree + .syntax() + .ancestors() + .find_map(ast::UseItem::cast) + .map(|it| it.syntax().clone()), + true, + ), + common if common == left.len() && left.len() == right.len() => { + // e.g: target is std::fmt and we can have + // 1- use std::fmt; + // 2- use std::fmt:{ ... } + if let Some(list) = tree_list { + // In case 2 we need to add self to the nested list + // unless it's already there + let has_self = list.use_trees().map(|it| it.path()).any(|p| { + p.and_then(|it| it.segment()) + .and_then(|it| it.kind()) + .filter(|k| *k == ast::PathSegmentKind::SelfKw) + .is_some() + }); + + if has_self { + ImportAction::Nothing + } else { + ImportAction::add_in_tree_list(current_path_segments.len(), list, true) + } + } else { + // Case 1 + ImportAction::Nothing + } + } + common if common != left.len() && left.len() == right.len() => { + // e.g: target is std::fmt and we have + // use std::io; + // We need to split. + let segments_to_split = current_path_segments.split_at(prev_len + common).1; + ImportAction::add_nested_import( + prev_len + common, + path, + Some(segments_to_split[0].clone()), + false, + ) + } + common if common == right.len() && left.len() > right.len() => { + // e.g: target is std::fmt and we can have + // 1- use std; + // 2- use std::{ ... }; + + // fallback action + let mut better_action = ImportAction::add_new_use( + current_use_tree + .syntax() + .ancestors() + .find_map(ast::UseItem::cast) + .map(|it| it.syntax().clone()), + true, + ); + if let Some(list) = tree_list { + // Case 2, check recursively if the path is already imported in the nested list + for u in list.use_trees() { + let child_action = walk_use_tree_for_best_action( + current_path_segments, + Some(list.clone()), + u, + target, + ); + if child_action.is_better(&better_action) { + better_action = child_action; + if let ImportAction::Nothing = better_action { + return better_action; + } + } + } + } else { + // Case 1, split adding self + better_action = ImportAction::add_nested_import(prev_len + common, path, None, true) + } + better_action + } + common if common == left.len() && left.len() < right.len() => { + // e.g: target is std::fmt and we can have + // use std::fmt::Debug; + let segments_to_split = current_path_segments.split_at(prev_len + common).1; + ImportAction::add_nested_import( + prev_len + common, + path, + Some(segments_to_split[0].clone()), + true, + ) + } + common if common < left.len() && common < right.len() => { + // e.g: target is std::fmt::nested::Debug + // use std::fmt::Display + let segments_to_split = current_path_segments.split_at(prev_len + common).1; + ImportAction::add_nested_import( + prev_len + common, + path, + Some(segments_to_split[0].clone()), + false, + ) + } + _ => unreachable!(), + }; + + // If we are inside a UseTreeList adding a use statement become adding to the existing + // tree list. + action = match (current_parent_use_tree_list, action.clone()) { + (Some(use_tree_list), ImportAction::AddNewUse { .. }) => { + ImportAction::add_in_tree_list(prev_len, use_tree_list, false) + } + (_, _) => action, + }; + + // We remove the segments added + current_path_segments.truncate(prev_len); + action +} + +fn best_action_for_target( + container: SyntaxNode, + anchor: SyntaxNode, + target: &[SmolStr], +) -> ImportAction { + let mut storage = Vec::with_capacity(16); // this should be the only allocation + let best_action = container + .children() + .filter_map(ast::UseItem::cast) + .filter_map(|it| it.use_tree()) + .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target)) + .fold(None, |best, a| match best { + Some(best) => Some(ImportAction::better(best, a)), + None => Some(a), + }); + + match best_action { + Some(action) => action, + None => { + // We have no action and no UseItem was found in container so we find + // another item and we use it as anchor. + // If there are no items above, we choose the target path itself as anchor. + // todo: we should include even whitespace blocks as anchor candidates + let anchor = container + .children() + .find(|n| n.text_range().start() < anchor.text_range().start()) + .or_else(|| Some(anchor)); + + ImportAction::add_new_use(anchor, false) + } + } +} + +fn make_assist(action: &ImportAction, target: &[SmolStr], edit: &mut TextEditBuilder) { + match action { + ImportAction::AddNewUse { anchor, add_after_anchor } => { + make_assist_add_new_use(anchor, *add_after_anchor, target, edit) + } + ImportAction::AddInTreeList { common_segments, tree_list, add_self } => { + // We know that the fist n segments already exists in the use statement we want + // to modify, so we want to add only the last target.len() - n segments. + let segments_to_add = target.split_at(*common_segments).1; + make_assist_add_in_tree_list(tree_list, segments_to_add, *add_self, edit) + } + ImportAction::AddNestedImport { + common_segments, + path_to_split, + first_segment_to_split, + add_self, + } => { + let segments_to_add = target.split_at(*common_segments).1; + make_assist_add_nested_import( + path_to_split, + first_segment_to_split, + segments_to_add, + *add_self, + edit, + ) + } + _ => {} + } +} + +fn make_assist_add_new_use( + anchor: &Option, + after: bool, + target: &[SmolStr], + edit: &mut TextEditBuilder, +) { + if let Some(anchor) = anchor { + let indent = ra_fmt::leading_indent(anchor); + let mut buf = String::new(); + if after { + buf.push_str("\n"); + if let Some(spaces) = &indent { + buf.push_str(spaces); + } + } + buf.push_str("use "); + fmt_segments_raw(target, &mut buf); + buf.push_str(";"); + if !after { + buf.push_str("\n\n"); + if let Some(spaces) = &indent { + buf.push_str(&spaces); + } + } + let position = if after { anchor.text_range().end() } else { anchor.text_range().start() }; + edit.insert(position, buf); + } +} + +fn make_assist_add_in_tree_list( + tree_list: &ast::UseTreeList, + target: &[SmolStr], + add_self: bool, + edit: &mut TextEditBuilder, +) { + let last = tree_list.use_trees().last(); + if let Some(last) = last { + let mut buf = String::new(); + let comma = last.syntax().siblings(Direction::Next).find(|n| n.kind() == T![,]); + let offset = if let Some(comma) = comma { + comma.text_range().end() + } else { + buf.push_str(","); + last.syntax().text_range().end() + }; + if add_self { + buf.push_str(" self") + } else { + buf.push_str(" "); + } + fmt_segments_raw(target, &mut buf); + edit.insert(offset, buf); + } else { + + } +} + +fn make_assist_add_nested_import( + path: &ast::Path, + first_segment_to_split: &Option, + target: &[SmolStr], + add_self: bool, + edit: &mut TextEditBuilder, +) { + let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast); + if let Some(use_tree) = use_tree { + let (start, add_colon_colon) = if let Some(first_segment_to_split) = first_segment_to_split + { + (first_segment_to_split.syntax().text_range().start(), false) + } else { + (use_tree.syntax().text_range().end(), true) + }; + let end = use_tree.syntax().text_range().end(); + + let mut buf = String::new(); + if add_colon_colon { + buf.push_str("::"); + } + buf.push_str("{ "); + if add_self { + buf.push_str("self, "); + } + fmt_segments_raw(target, &mut buf); + if !target.is_empty() { + buf.push_str(", "); + } + edit.insert(start, buf); + edit.insert(end, "}".to_string()); + } +} + +fn apply_auto_import( + container: &SyntaxNode, + path: &ast::Path, + target: &[SmolStr], + edit: &mut TextEditBuilder, +) { + let action = best_action_for_target(container.clone(), path.syntax().clone(), target); + make_assist(&action, target, edit); + if let Some(last) = path.segment() { + // Here we are assuming the assist will provide a correct use statement + // so we can delete the path qualifier + edit.delete(TextRange::from_to( + path.syntax().text_range().start(), + last.syntax().text_range().start(), + )); + } +} + +pub fn collect_hir_path_segments(path: &hir::Path) -> Option> { + let mut ps = Vec::::with_capacity(10); + match path.kind { + hir::PathKind::Abs => ps.push("".into()), + hir::PathKind::Crate => ps.push("crate".into()), + hir::PathKind::Plain => {} + hir::PathKind::Self_ => ps.push("self".into()), + hir::PathKind::Super => ps.push("super".into()), + hir::PathKind::Type(_) => return None, + } + for s in path.segments.iter() { + ps.push(s.name.to_string().into()); + } + Some(ps) +} + +// This function produces sequence of text edits into edit +// to import the target path in the most appropriate scope given +// the cursor position +pub fn auto_import_text_edit( + // Ideally the position of the cursor, used to + position: &SyntaxNode, + // The statement to use as anchor (last resort) + anchor: &SyntaxNode, + // The path to import as a sequence of strings + target: &[SmolStr], + edit: &mut TextEditBuilder, +) { + let container = position.ancestors().find_map(|n| { + if let Some(module) = ast::Module::cast(n.clone()) { + return module.item_list().map(|it| it.syntax().clone()); + } + ast::SourceFile::cast(n).map(|it| it.syntax().clone()) + }); + + if let Some(container) = container { + let action = best_action_for_target(container, anchor.clone(), target); + make_assist(&action, target, edit); + } +} + +pub(crate) fn auto_import(mut ctx: AssistCtx) -> Option { + let path: ast::Path = ctx.node_at_offset()?; + // We don't want to mess with use statements + if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { + return None; + } + + let hir_path = hir::Path::from_ast(path.clone())?; + let segments = collect_hir_path_segments(&hir_path)?; + if segments.len() < 2 { + return None; + } + + if let Some(module) = path.syntax().ancestors().find_map(ast::Module::cast) { + if let (Some(item_list), Some(name)) = (module.item_list(), module.name()) { + ctx.add_action( + AssistId("auto_import"), + format!("import {} in mod {}", fmt_segments(&segments), name.text()), + |edit| { + apply_auto_import( + item_list.syntax(), + &path, + &segments, + edit.text_edit_builder(), + ); + }, + ); + } + } else { + let current_file = path.syntax().ancestors().find_map(ast::SourceFile::cast)?; + ctx.add_action( + AssistId("auto_import"), + format!("import {} in the current file", fmt_segments(&segments)), + |edit| { + apply_auto_import( + current_file.syntax(), + &path, + &segments, + edit.text_edit_builder(), + ); + }, + ); + } + + ctx.build() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::helpers::{check_assist, check_assist_not_applicable}; + + #[test] + fn test_auto_import_add_use_no_anchor() { + check_assist( + auto_import, + " +std::fmt::Debug<|> + ", + " +use std::fmt::Debug; + +Debug<|> + ", + ); + } + #[test] + fn test_auto_import_add_use_no_anchor_with_item_below() { + check_assist( + auto_import, + " +std::fmt::Debug<|> + +fn main() { +} + ", + " +use std::fmt::Debug; + +Debug<|> + +fn main() { +} + ", + ); + } + + #[test] + fn test_auto_import_add_use_no_anchor_with_item_above() { + check_assist( + auto_import, + " +fn main() { +} + +std::fmt::Debug<|> + ", + " +use std::fmt::Debug; + +fn main() { +} + +Debug<|> + ", + ); + } + + #[test] + fn test_auto_import_add_use_no_anchor_2seg() { + check_assist( + auto_import, + " +std::fmt<|>::Debug + ", + " +use std::fmt; + +fmt<|>::Debug + ", + ); + } + + #[test] + fn test_auto_import_add_use() { + check_assist( + auto_import, + " +use stdx; + +impl std::fmt::Debug<|> for Foo { +} + ", + " +use stdx; +use std::fmt::Debug; + +impl Debug<|> for Foo { +} + ", + ); + } + + #[test] + fn test_auto_import_file_use_other_anchor() { + check_assist( + auto_import, + " +impl std::fmt::Debug<|> for Foo { +} + ", + " +use std::fmt::Debug; + +impl Debug<|> for Foo { +} + ", + ); + } + + #[test] + fn test_auto_import_add_use_other_anchor_indent() { + check_assist( + auto_import, + " + impl std::fmt::Debug<|> for Foo { + } + ", + " + use std::fmt::Debug; + + impl Debug<|> for Foo { + } + ", + ); + } + + #[test] + fn test_auto_import_split_different() { + check_assist( + auto_import, + " +use std::fmt; + +impl std::io<|> for Foo { +} + ", + " +use std::{ io, fmt}; + +impl io<|> for Foo { +} + ", + ); + } + + #[test] + fn test_auto_import_split_self_for_use() { + check_assist( + auto_import, + " +use std::fmt; + +impl std::fmt::Debug<|> for Foo { +} + ", + " +use std::fmt::{ self, Debug, }; + +impl Debug<|> for Foo { +} + ", + ); + } + + #[test] + fn test_auto_import_split_self_for_target() { + check_assist( + auto_import, + " +use std::fmt::Debug; + +impl std::fmt<|> for Foo { +} + ", + " +use std::fmt::{ self, Debug}; + +impl fmt<|> for Foo { +} + ", + ); + } + + #[test] + fn test_auto_import_add_to_nested_self_nested() { + check_assist( + auto_import, + " +use std::fmt::{Debug, nested::{Display}}; + +impl std::fmt::nested<|> for Foo { +} +", + " +use std::fmt::{Debug, nested::{Display, self}}; + +impl nested<|> for Foo { +} +", + ); + } + + #[test] + fn test_auto_import_add_to_nested_self_already_included() { + check_assist( + auto_import, + " +use std::fmt::{Debug, nested::{self, Display}}; + +impl std::fmt::nested<|> for Foo { +} +", + " +use std::fmt::{Debug, nested::{self, Display}}; + +impl nested<|> for Foo { +} +", + ); + } + + #[test] + fn test_auto_import_add_to_nested_nested() { + check_assist( + auto_import, + " +use std::fmt::{Debug, nested::{Display}}; + +impl std::fmt::nested::Debug<|> for Foo { +} +", + " +use std::fmt::{Debug, nested::{Display, Debug}}; + +impl Debug<|> for Foo { +} +", + ); + } + + #[test] + fn test_auto_import_split_common_target_longer() { + check_assist( + auto_import, + " +use std::fmt::Debug; + +impl std::fmt::nested::Display<|> for Foo { +} +", + " +use std::fmt::{ nested::Display, Debug}; + +impl Display<|> for Foo { +} +", + ); + } + + #[test] + fn test_auto_import_split_common_use_longer() { + check_assist( + auto_import, + " +use std::fmt::nested::Debug; + +impl std::fmt::Display<|> for Foo { +} +", + " +use std::fmt::{ Display, nested::Debug}; + +impl Display<|> for Foo { +} +", + ); + } + + #[test] + fn test_auto_import_alias() { + check_assist( + auto_import, + " +use std::fmt as foo; + +impl foo::Debug<|> for Foo { +} +", + " +use std::fmt as foo; + +impl Debug<|> for Foo { +} +", + ); + } + + #[test] + fn test_auto_import_not_applicable_one_segment() { + check_assist_not_applicable( + auto_import, + " +impl foo<|> for Foo { +} +", + ); + } + + #[test] + fn test_auto_import_not_applicable_in_use() { + check_assist_not_applicable( + auto_import, + " +use std::fmt<|>; +", + ); + } + + #[test] + fn test_auto_import_add_use_no_anchor_in_mod_mod() { + check_assist( + auto_import, + " +mod foo { + mod bar { + std::fmt::Debug<|> + } +} + ", + " +mod foo { + mod bar { + use std::fmt::Debug; + + Debug<|> + } +} + ", + ); + } +} diff --git a/crates/ra_assists/src/assists/change_visibility.rs b/crates/ra_assists/src/assists/change_visibility.rs new file mode 100644 index 000000000..60c74debc --- /dev/null +++ b/crates/ra_assists/src/assists/change_visibility.rs @@ -0,0 +1,159 @@ +use hir::db::HirDatabase; +use ra_syntax::{ + ast::{self, NameOwner, VisibilityOwner}, + AstNode, + SyntaxKind::{ + ATTR, COMMENT, ENUM_DEF, FN_DEF, IDENT, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY, + WHITESPACE, + }, + SyntaxNode, TextUnit, T, +}; + +use crate::{Assist, AssistCtx, AssistId}; + +pub(crate) fn change_visibility(ctx: AssistCtx) -> Option { + if let Some(vis) = ctx.node_at_offset::() { + return change_vis(ctx, vis); + } + add_vis(ctx) +} + +fn add_vis(mut ctx: AssistCtx) -> Option { + let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { + T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, + _ => false, + }); + + let (offset, target) = if let Some(keyword) = item_keyword { + let parent = keyword.parent(); + let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; + // Parent is not a definition, can't add visibility + if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { + return None; + } + // Already have visibility, do nothing + if parent.children().any(|child| child.kind() == VISIBILITY) { + return None; + } + (vis_offset(&parent), keyword.text_range()) + } else { + let ident = ctx.token_at_offset().find(|leaf| leaf.kind() == IDENT)?; + let field = ident.parent().ancestors().find_map(ast::RecordFieldDef::cast)?; + if field.name()?.syntax().text_range() != ident.text_range() && field.visibility().is_some() + { + return None; + } + (vis_offset(field.syntax()), ident.text_range()) + }; + + ctx.add_action(AssistId("change_visibility"), "make pub(crate)", |edit| { + edit.target(target); + edit.insert(offset, "pub(crate) "); + edit.set_cursor(offset); + }); + + ctx.build() +} + +fn vis_offset(node: &SyntaxNode) -> TextUnit { + node.children_with_tokens() + .skip_while(|it| match it.kind() { + WHITESPACE | COMMENT | ATTR => true, + _ => false, + }) + .next() + .map(|it| it.text_range().start()) + .unwrap_or_else(|| node.text_range().start()) +} + +fn change_vis(mut ctx: AssistCtx, vis: ast::Visibility) -> Option { + if vis.syntax().text() == "pub" { + ctx.add_action(AssistId("change_visibility"), "change to pub(crate)", |edit| { + edit.target(vis.syntax().text_range()); + edit.replace(vis.syntax().text_range(), "pub(crate)"); + edit.set_cursor(vis.syntax().text_range().start()) + }); + + return ctx.build(); + } + if vis.syntax().text() == "pub(crate)" { + ctx.add_action(AssistId("change_visibility"), "change to pub", |edit| { + edit.target(vis.syntax().text_range()); + edit.replace(vis.syntax().text_range(), "pub"); + edit.set_cursor(vis.syntax().text_range().start()); + }); + + return ctx.build(); + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::helpers::{check_assist, check_assist_target}; + + #[test] + fn change_visibility_adds_pub_crate_to_items() { + check_assist(change_visibility, "<|>fn foo() {}", "<|>pub(crate) fn foo() {}"); + check_assist(change_visibility, "f<|>n foo() {}", "<|>pub(crate) fn foo() {}"); + check_assist(change_visibility, "<|>struct Foo {}", "<|>pub(crate) struct Foo {}"); + check_assist(change_visibility, "<|>mod foo {}", "<|>pub(crate) mod foo {}"); + check_assist(change_visibility, "<|>trait Foo {}", "<|>pub(crate) trait Foo {}"); + check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}"); + check_assist( + change_visibility, + "unsafe f<|>n foo() {}", + "<|>pub(crate) unsafe fn foo() {}", + ); + } + + #[test] + fn change_visibility_works_with_struct_fields() { + check_assist( + change_visibility, + "struct S { <|>field: u32 }", + "struct S { <|>pub(crate) field: u32 }", + ) + } + + #[test] + fn change_visibility_pub_to_pub_crate() { + check_assist(change_visibility, "<|>pub fn foo() {}", "<|>pub(crate) fn foo() {}") + } + + #[test] + fn change_visibility_pub_crate_to_pub() { + check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "<|>pub fn foo() {}") + } + + #[test] + fn change_visibility_handles_comment_attrs() { + check_assist( + change_visibility, + " + /// docs + + // comments + + #[derive(Debug)] + <|>struct Foo; + ", + " + /// docs + + // comments + + #[derive(Debug)] + <|>pub(crate) struct Foo; + ", + ) + } + + #[test] + fn change_visibility_target() { + check_assist_target(change_visibility, "<|>fn foo() {}", "fn"); + check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)"); + check_assist_target(change_visibility, "struct S { <|>field: u32 }", "field"); + } +} diff --git a/crates/ra_assists/src/assists/fill_match_arms.rs b/crates/ra_assists/src/assists/fill_match_arms.rs new file mode 100644 index 000000000..f59062bb9 --- /dev/null +++ b/crates/ra_assists/src/assists/fill_match_arms.rs @@ -0,0 +1,229 @@ +use std::iter; + +use hir::{db::HirDatabase, Adt, HasSource}; +use ra_syntax::ast::{self, AstNode, NameOwner}; + +use crate::{ast_editor::AstBuilder, Assist, AssistCtx, AssistId}; + +pub(crate) fn fill_match_arms(mut ctx: AssistCtx) -> Option { + let match_expr = ctx.node_at_offset::()?; + let match_arm_list = match_expr.match_arm_list()?; + + // We already have some match arms, so we don't provide any assists. + // Unless if there is only one trivial match arm possibly created + // by match postfix complete. Trivial match arm is the catch all arm. + let mut existing_arms = match_arm_list.arms(); + if let Some(arm) = existing_arms.next() { + if !is_trivial(&arm) || existing_arms.next().is_some() { + return None; + } + }; + + let expr = match_expr.expr()?; + let enum_def = { + let file_id = ctx.frange.file_id; + let analyzer = hir::SourceAnalyzer::new(ctx.db, file_id, expr.syntax(), None); + resolve_enum_def(ctx.db, &analyzer, &expr)? + }; + let variant_list = enum_def.variant_list()?; + + ctx.add_action(AssistId("fill_match_arms"), "fill match arms", |edit| { + let variants = variant_list.variants(); + let arms = variants.filter_map(build_pat).map(|pat| { + AstBuilder::::from_pieces( + iter::once(pat), + &AstBuilder::::unit(), + ) + }); + let new_arm_list = AstBuilder::::from_arms(arms); + + edit.target(match_expr.syntax().text_range()); + edit.set_cursor(expr.syntax().text_range().start()); + edit.replace_node_and_indent(match_arm_list.syntax(), new_arm_list.syntax().text()); + }); + + ctx.build() +} + +fn is_trivial(arm: &ast::MatchArm) -> bool { + arm.pats().any(|pat| match pat { + ast::Pat::PlaceholderPat(..) => true, + _ => false, + }) +} + +fn resolve_enum_def( + db: &impl HirDatabase, + analyzer: &hir::SourceAnalyzer, + expr: &ast::Expr, +) -> Option { + let expr_ty = analyzer.type_of(db, &expr)?; + + analyzer.autoderef(db, expr_ty).find_map(|ty| match ty.as_adt() { + Some((Adt::Enum(e), _)) => Some(e.source(db).ast), + _ => None, + }) +} + +fn build_pat(var: ast::EnumVariant) -> Option { + let path = &AstBuilder::::from_pieces(var.parent_enum().name()?, var.name()?); + + let pat: ast::Pat = match var.kind() { + ast::StructKind::Tuple(field_list) => { + let pats = iter::repeat(AstBuilder::::placeholder().into()) + .take(field_list.fields().count()); + AstBuilder::::from_pieces(path, pats).into() + } + ast::StructKind::Named(field_list) => { + let pats = field_list + .fields() + .map(|f| AstBuilder::::from_name(&f.name().unwrap()).into()); + AstBuilder::::from_pieces(path, pats).into() + } + ast::StructKind::Unit => AstBuilder::::from_path(path).into(), + }; + + Some(pat) +} + +#[cfg(test)] +mod tests { + use crate::helpers::{check_assist, check_assist_target}; + + use super::fill_match_arms; + + #[test] + fn fill_match_arms_empty_body() { + check_assist( + fill_match_arms, + r#" + enum A { + As, + Bs, + Cs(String), + Ds(String, String), + Es{ x: usize, y: usize } + } + + fn main() { + let a = A::As; + match a<|> {} + } + "#, + r#" + enum A { + As, + Bs, + Cs(String), + Ds(String, String), + Es{ x: usize, y: usize } + } + + fn main() { + let a = A::As; + match <|>a { + A::As => (), + A::Bs => (), + A::Cs(_) => (), + A::Ds(_, _) => (), + A::Es{ x, y } => (), + } + } + "#, + ); + } + + #[test] + fn test_fill_match_arm_refs() { + check_assist( + fill_match_arms, + r#" + enum A { + As, + } + + fn foo(a: &A) { + match a<|> { + } + } + "#, + r#" + enum A { + As, + } + + fn foo(a: &A) { + match <|>a { + A::As => (), + } + } + "#, + ); + + check_assist( + fill_match_arms, + r#" + enum A { + Es{ x: usize, y: usize } + } + + fn foo(a: &mut A) { + match a<|> { + } + } + "#, + r#" + enum A { + Es{ x: usize, y: usize } + } + + fn foo(a: &mut A) { + match <|>a { + A::Es{ x, y } => (), + } + } + "#, + ); + } + + #[test] + fn fill_match_arms_target() { + check_assist_target( + fill_match_arms, + r#" + enum E { X, Y } + + fn main() { + match E::X<|> {} + } + "#, + "match E::X {}", + ); + } + + #[test] + fn fill_match_arms_trivial_arm() { + check_assist( + fill_match_arms, + r#" + enum E { X, Y } + + fn main() { + match E::X { + <|>_ => {}, + } + } + "#, + r#" + enum E { X, Y } + + fn main() { + match <|>E::X { + E::X => (), + E::Y => (), + } + } + "#, + ); + } +} diff --git a/crates/ra_assists/src/assists/flip_binexpr.rs b/crates/ra_assists/src/assists/flip_binexpr.rs new file mode 100644 index 000000000..b55b36a8e --- /dev/null +++ b/crates/ra_assists/src/assists/flip_binexpr.rs @@ -0,0 +1,141 @@ +use hir::db::HirDatabase; +use ra_syntax::ast::{AstNode, BinExpr, BinOp}; + +use crate::{Assist, AssistCtx, AssistId}; + +/// Flip binary expression assist. +pub(crate) fn flip_binexpr(mut ctx: AssistCtx) -> Option { + let expr = ctx.node_at_offset::()?; + let lhs = expr.lhs()?.syntax().clone(); + let rhs = expr.rhs()?.syntax().clone(); + let op_range = expr.op_token()?.text_range(); + // The assist should be applied only if the cursor is on the operator + let cursor_in_range = ctx.frange.range.is_subrange(&op_range); + if !cursor_in_range { + return None; + } + let action: FlipAction = expr.op_kind()?.into(); + // The assist should not be applied for certain operators + if let FlipAction::DontFlip = action { + return None; + } + + ctx.add_action(AssistId("flip_binexpr"), "flip binary expression", |edit| { + edit.target(op_range); + if let FlipAction::FlipAndReplaceOp(new_op) = action { + edit.replace(op_range, new_op); + } + edit.replace(lhs.text_range(), rhs.text()); + edit.replace(rhs.text_range(), lhs.text()); + }); + + ctx.build() +} + +enum FlipAction { + // Flip the expression + Flip, + // Flip the expression and replace the operator with this string + FlipAndReplaceOp(&'static str), + // Do not flip the expression + DontFlip, +} + +impl From for FlipAction { + fn from(op_kind: BinOp) -> Self { + match op_kind { + BinOp::Assignment => FlipAction::DontFlip, + BinOp::AddAssign => FlipAction::DontFlip, + BinOp::DivAssign => FlipAction::DontFlip, + BinOp::MulAssign => FlipAction::DontFlip, + BinOp::RemAssign => FlipAction::DontFlip, + BinOp::ShrAssign => FlipAction::DontFlip, + BinOp::ShlAssign => FlipAction::DontFlip, + BinOp::SubAssign => FlipAction::DontFlip, + BinOp::BitOrAssign => FlipAction::DontFlip, + BinOp::BitAndAssign => FlipAction::DontFlip, + BinOp::BitXorAssign => FlipAction::DontFlip, + BinOp::GreaterTest => FlipAction::FlipAndReplaceOp("<"), + BinOp::GreaterEqualTest => FlipAction::FlipAndReplaceOp("<="), + BinOp::LesserTest => FlipAction::FlipAndReplaceOp(">"), + BinOp::LesserEqualTest => FlipAction::FlipAndReplaceOp(">="), + _ => FlipAction::Flip, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + + #[test] + fn flip_binexpr_target_is_the_op() { + check_assist_target(flip_binexpr, "fn f() { let res = 1 ==<|> 2; }", "==") + } + + #[test] + fn flip_binexpr_not_applicable_for_assignment() { + check_assist_not_applicable(flip_binexpr, "fn f() { let mut _x = 1; _x +=<|> 2 }") + } + + #[test] + fn flip_binexpr_works_for_eq() { + check_assist( + flip_binexpr, + "fn f() { let res = 1 ==<|> 2; }", + "fn f() { let res = 2 ==<|> 1; }", + ) + } + + #[test] + fn flip_binexpr_works_for_gt() { + check_assist( + flip_binexpr, + "fn f() { let res = 1 ><|> 2; }", + "fn f() { let res = 2 <<|> 1; }", + ) + } + + #[test] + fn flip_binexpr_works_for_lteq() { + check_assist( + flip_binexpr, + "fn f() { let res = 1 <=<|> 2; }", + "fn f() { let res = 2 >=<|> 1; }", + ) + } + + #[test] + fn flip_binexpr_works_for_complex_expr() { + check_assist( + flip_binexpr, + "fn f() { let res = (1 + 1) ==<|> (2 + 2); }", + "fn f() { let res = (2 + 2) ==<|> (1 + 1); }", + ) + } + + #[test] + fn flip_binexpr_works_inside_match() { + check_assist( + flip_binexpr, + r#" + fn dyn_eq(&self, other: &dyn Diagnostic) -> bool { + match other.downcast_ref::() { + None => false, + Some(it) => it ==<|> self, + } + } + "#, + r#" + fn dyn_eq(&self, other: &dyn Diagnostic) -> bool { + match other.downcast_ref::() { + None => false, + Some(it) => self ==<|> it, + } + } + "#, + ) + } +} diff --git a/crates/ra_assists/src/assists/flip_comma.rs b/crates/ra_assists/src/assists/flip_comma.rs new file mode 100644 index 000000000..5ee7561bc --- /dev/null +++ b/crates/ra_assists/src/assists/flip_comma.rs @@ -0,0 +1,68 @@ +use hir::db::HirDatabase; +use ra_syntax::{algo::non_trivia_sibling, Direction, T}; + +use crate::{Assist, AssistCtx, AssistId}; + +pub(crate) fn flip_comma(mut ctx: AssistCtx) -> Option { + let comma = ctx.token_at_offset().find(|leaf| leaf.kind() == T![,])?; + let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; + let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; + + // Don't apply a "flip" in case of a last comma + // that typically comes before punctuation + if next.kind().is_punct() { + return None; + } + + ctx.add_action(AssistId("flip_comma"), "flip comma", |edit| { + edit.target(comma.text_range()); + edit.replace(prev.text_range(), next.to_string()); + edit.replace(next.text_range(), prev.to_string()); + }); + + ctx.build() +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::helpers::{check_assist, check_assist_target}; + + #[test] + fn flip_comma_works_for_function_parameters() { + check_assist( + flip_comma, + "fn foo(x: i32,<|> y: Result<(), ()>) {}", + "fn foo(y: Result<(), ()>,<|> x: i32) {}", + ) + } + + #[test] + fn flip_comma_target() { + check_assist_target(flip_comma, "fn foo(x: i32,<|> y: Result<(), ()>) {}", ",") + } + + #[test] + #[should_panic] + fn flip_comma_before_punct() { + // See https://github.com/rust-analyzer/rust-analyzer/issues/1619 + // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct + // declaration body. + check_assist_target( + flip_comma, + "pub enum Test { \ + A,<|> \ + }", + ",", + ); + + check_assist_target( + flip_comma, + "pub struct Test { \ + foo: usize,<|> \ + }", + ",", + ); + } +} diff --git a/crates/ra_assists/src/assists/inline_local_variable.rs b/crates/ra_assists/src/assists/inline_local_variable.rs new file mode 100644 index 000000000..eedb29199 --- /dev/null +++ b/crates/ra_assists/src/assists/inline_local_variable.rs @@ -0,0 +1,636 @@ +use hir::db::HirDatabase; +use ra_syntax::{ + ast::{self, AstNode, AstToken}, + TextRange, +}; + +use crate::assist_ctx::AssistBuilder; +use crate::{Assist, AssistCtx, AssistId}; + +pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx) -> Option { + let let_stmt = ctx.node_at_offset::()?; + let bind_pat = match let_stmt.pat()? { + ast::Pat::BindPat(pat) => pat, + _ => return None, + }; + if bind_pat.is_mutable() { + return None; + } + let initializer_expr = let_stmt.initializer()?; + let delete_range = if let Some(whitespace) = let_stmt + .syntax() + .next_sibling_or_token() + .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone())) + { + TextRange::from_to( + let_stmt.syntax().text_range().start(), + whitespace.syntax().text_range().end(), + ) + } else { + let_stmt.syntax().text_range() + }; + let analyzer = hir::SourceAnalyzer::new(ctx.db, ctx.frange.file_id, bind_pat.syntax(), None); + let refs = analyzer.find_all_refs(&bind_pat); + + let mut wrap_in_parens = vec![true; refs.len()]; + + for (i, desc) in refs.iter().enumerate() { + let usage_node = ctx + .covering_node_for_range(desc.range) + .ancestors() + .find_map(|node| ast::PathExpr::cast(node))?; + let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast); + let usage_parent = match usage_parent_option { + Some(u) => u, + None => { + wrap_in_parens[i] = false; + continue; + } + }; + + wrap_in_parens[i] = match (&initializer_expr, usage_parent) { + (ast::Expr::CallExpr(_), _) + | (ast::Expr::IndexExpr(_), _) + | (ast::Expr::MethodCallExpr(_), _) + | (ast::Expr::FieldExpr(_), _) + | (ast::Expr::TryExpr(_), _) + | (ast::Expr::RefExpr(_), _) + | (ast::Expr::Literal(_), _) + | (ast::Expr::TupleExpr(_), _) + | (ast::Expr::ArrayExpr(_), _) + | (ast::Expr::ParenExpr(_), _) + | (ast::Expr::PathExpr(_), _) + | (ast::Expr::BlockExpr(_), _) + | (_, ast::Expr::CallExpr(_)) + | (_, ast::Expr::TupleExpr(_)) + | (_, ast::Expr::ArrayExpr(_)) + | (_, ast::Expr::ParenExpr(_)) + | (_, ast::Expr::ForExpr(_)) + | (_, ast::Expr::WhileExpr(_)) + | (_, ast::Expr::BreakExpr(_)) + | (_, ast::Expr::ReturnExpr(_)) + | (_, ast::Expr::MatchExpr(_)) => false, + _ => true, + }; + } + + let init_str = initializer_expr.syntax().text().to_string(); + let init_in_paren = format!("({})", &init_str); + + ctx.add_action( + AssistId("inline_local_variable"), + "inline local variable", + move |edit: &mut AssistBuilder| { + edit.delete(delete_range); + for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { + if should_wrap { + edit.replace(desc.range, init_in_paren.clone()) + } else { + edit.replace(desc.range, init_str.clone()) + } + } + edit.set_cursor(delete_range.start()) + }, + ); + + ctx.build() +} + +#[cfg(test)] +mod tests { + use crate::helpers::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn test_inline_let_bind_literal_expr() { + check_assist( + inline_local_varialbe, + " +fn bar(a: usize) {} +fn foo() { + let a<|> = 1; + a + 1; + if a > 10 { + } + + while a > 10 { + + } + let b = a * 10; + bar(a); +}", + " +fn bar(a: usize) {} +fn foo() { + <|>1 + 1; + if 1 > 10 { + } + + while 1 > 10 { + + } + let b = 1 * 10; + bar(1); +}", + ); + } + + #[test] + fn test_inline_let_bind_bin_expr() { + check_assist( + inline_local_varialbe, + " +fn bar(a: usize) {} +fn foo() { + let a<|> = 1 + 1; + a + 1; + if a > 10 { + } + + while a > 10 { + + } + let b = a * 10; + bar(a); +}", + " +fn bar(a: usize) {} +fn foo() { + <|>(1 + 1) + 1; + if (1 + 1) > 10 { + } + + while (1 + 1) > 10 { + + } + let b = (1 + 1) * 10; + bar(1 + 1); +}", + ); + } + + #[test] + fn test_inline_let_bind_function_call_expr() { + check_assist( + inline_local_varialbe, + " +fn bar(a: usize) {} +fn foo() { + let a<|> = bar(1); + a + 1; + if a > 10 { + } + + while a > 10 { + + } + let b = a * 10; + bar(a); +}", + " +fn bar(a: usize) {} +fn foo() { + <|>bar(1) + 1; + if bar(1) > 10 { + } + + while bar(1) > 10 { + + } + let b = bar(1) * 10; + bar(bar(1)); +}", + ); + } + + #[test] + fn test_inline_let_bind_cast_expr() { + check_assist( + inline_local_varialbe, + " +fn bar(a: usize): usize { a } +fn foo() { + let a<|> = bar(1) as u64; + a + 1; + if a > 10 { + } + + while a > 10 { + + } + let b = a * 10; + bar(a); +}", + " +fn bar(a: usize): usize { a } +fn foo() { + <|>(bar(1) as u64) + 1; + if (bar(1) as u64) > 10 { + } + + while (bar(1) as u64) > 10 { + + } + let b = (bar(1) as u64) * 10; + bar(bar(1) as u64); +}", + ); + } + + #[test] + fn test_inline_let_bind_block_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let a<|> = { 10 + 1 }; + a + 1; + if a > 10 { + } + + while a > 10 { + + } + let b = a * 10; + bar(a); +}", + " +fn foo() { + <|>{ 10 + 1 } + 1; + if { 10 + 1 } > 10 { + } + + while { 10 + 1 } > 10 { + + } + let b = { 10 + 1 } * 10; + bar({ 10 + 1 }); +}", + ); + } + + #[test] + fn test_inline_let_bind_paren_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let a<|> = ( 10 + 1 ); + a + 1; + if a > 10 { + } + + while a > 10 { + + } + let b = a * 10; + bar(a); +}", + " +fn foo() { + <|>( 10 + 1 ) + 1; + if ( 10 + 1 ) > 10 { + } + + while ( 10 + 1 ) > 10 { + + } + let b = ( 10 + 1 ) * 10; + bar(( 10 + 1 )); +}", + ); + } + + #[test] + fn test_not_inline_mut_variable() { + check_assist_not_applicable( + inline_local_varialbe, + " +fn foo() { + let mut a<|> = 1 + 1; + a + 1; +}", + ); + } + + #[test] + fn test_call_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let a<|> = bar(10 + 1); + let b = a * 10; + let c = a as usize; +}", + " +fn foo() { + <|>let b = bar(10 + 1) * 10; + let c = bar(10 + 1) as usize; +}", + ); + } + + #[test] + fn test_index_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let x = vec![1, 2, 3]; + let a<|> = x[0]; + let b = a * 10; + let c = a as usize; +}", + " +fn foo() { + let x = vec![1, 2, 3]; + <|>let b = x[0] * 10; + let c = x[0] as usize; +}", + ); + } + + #[test] + fn test_method_call_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let bar = vec![1]; + let a<|> = bar.len(); + let b = a * 10; + let c = a as usize; +}", + " +fn foo() { + let bar = vec![1]; + <|>let b = bar.len() * 10; + let c = bar.len() as usize; +}", + ); + } + + #[test] + fn test_field_expr() { + check_assist( + inline_local_varialbe, + " +struct Bar { + foo: usize +} + +fn foo() { + let bar = Bar { foo: 1 }; + let a<|> = bar.foo; + let b = a * 10; + let c = a as usize; +}", + " +struct Bar { + foo: usize +} + +fn foo() { + let bar = Bar { foo: 1 }; + <|>let b = bar.foo * 10; + let c = bar.foo as usize; +}", + ); + } + + #[test] + fn test_try_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() -> Option { + let bar = Some(1); + let a<|> = bar?; + let b = a * 10; + let c = a as usize; + None +}", + " +fn foo() -> Option { + let bar = Some(1); + <|>let b = bar? * 10; + let c = bar? as usize; + None +}", + ); + } + + #[test] + fn test_ref_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let bar = 10; + let a<|> = &bar; + let b = a * 10; +}", + " +fn foo() { + let bar = 10; + <|>let b = &bar * 10; +}", + ); + } + + #[test] + fn test_tuple_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let a<|> = (10, 20); + let b = a[0]; +}", + " +fn foo() { + <|>let b = (10, 20)[0]; +}", + ); + } + + #[test] + fn test_array_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let a<|> = [1, 2, 3]; + let b = a.len(); +}", + " +fn foo() { + <|>let b = [1, 2, 3].len(); +}", + ); + } + + #[test] + fn test_paren() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let a<|> = (10 + 20); + let b = a * 10; + let c = a as usize; +}", + " +fn foo() { + <|>let b = (10 + 20) * 10; + let c = (10 + 20) as usize; +}", + ); + } + + #[test] + fn test_path_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let d = 10; + let a<|> = d; + let b = a * 10; + let c = a as usize; +}", + " +fn foo() { + let d = 10; + <|>let b = d * 10; + let c = d as usize; +}", + ); + } + + #[test] + fn test_block_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let a<|> = { 10 }; + let b = a * 10; + let c = a as usize; +}", + " +fn foo() { + <|>let b = { 10 } * 10; + let c = { 10 } as usize; +}", + ); + } + + #[test] + fn test_used_in_different_expr1() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let a<|> = 10 + 20; + let b = a * 10; + let c = (a, 20); + let d = [a, 10]; + let e = (a); +}", + " +fn foo() { + <|>let b = (10 + 20) * 10; + let c = (10 + 20, 20); + let d = [10 + 20, 10]; + let e = (10 + 20); +}", + ); + } + + #[test] + fn test_used_in_for_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let a<|> = vec![10, 20]; + for i in a {} +}", + " +fn foo() { + <|>for i in vec![10, 20] {} +}", + ); + } + + #[test] + fn test_used_in_while_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let a<|> = 1 > 0; + while a {} +}", + " +fn foo() { + <|>while 1 > 0 {} +}", + ); + } + + #[test] + fn test_used_in_break_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let a<|> = 1 + 1; + loop { + break a; + } +}", + " +fn foo() { + <|>loop { + break 1 + 1; + } +}", + ); + } + + #[test] + fn test_used_in_return_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let a<|> = 1 > 0; + return a; +}", + " +fn foo() { + <|>return 1 > 0; +}", + ); + } + + #[test] + fn test_used_in_match_expr() { + check_assist( + inline_local_varialbe, + " +fn foo() { + let a<|> = 1 > 0; + match a {} +}", + " +fn foo() { + <|>match 1 > 0 {} +}", + ); + } +} diff --git a/crates/ra_assists/src/assists/introduce_variable.rs b/crates/ra_assists/src/assists/introduce_variable.rs new file mode 100644 index 000000000..470ffe120 --- /dev/null +++ b/crates/ra_assists/src/assists/introduce_variable.rs @@ -0,0 +1,516 @@ +use format_buf::format; +use hir::db::HirDatabase; +use ra_syntax::{ + ast::{self, AstNode}, + SyntaxKind::{ + BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, + WHITESPACE, + }, + SyntaxNode, TextUnit, +}; +use test_utils::tested_by; + +use crate::{Assist, AssistCtx, AssistId}; + +pub(crate) fn introduce_variable(mut ctx: AssistCtx) -> Option { + if ctx.frange.range.is_empty() { + return None; + } + let node = ctx.covering_element(); + if node.kind() == COMMENT { + tested_by!(introduce_var_in_comment_is_not_applicable); + return None; + } + let expr = node.ancestors().find_map(valid_target_expr)?; + let (anchor_stmt, wrap_in_block) = anchor_stmt(expr.clone())?; + let indent = anchor_stmt.prev_sibling_or_token()?.as_token()?.clone(); + if indent.kind() != WHITESPACE { + return None; + } + ctx.add_action(AssistId("introduce_variable"), "introduce variable", move |edit| { + let mut buf = String::new(); + + let cursor_offset = if wrap_in_block { + buf.push_str("{ let var_name = "); + TextUnit::of_str("{ let ") + } else { + buf.push_str("let var_name = "); + TextUnit::of_str("let ") + }; + format!(buf, "{}", expr.syntax()); + let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); + let is_full_stmt = if let Some(expr_stmt) = &full_stmt { + Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) + } else { + false + }; + if is_full_stmt { + tested_by!(test_introduce_var_expr_stmt); + if !full_stmt.unwrap().has_semi() { + buf.push_str(";"); + } + edit.replace(expr.syntax().text_range(), buf); + } else { + buf.push_str(";"); + + // We want to maintain the indent level, + // but we do not want to duplicate possible + // extra newlines in the indent block + let text = indent.text(); + if text.starts_with('\n') { + buf.push_str("\n"); + buf.push_str(text.trim_start_matches('\n')); + } else { + buf.push_str(text); + } + + edit.target(expr.syntax().text_range()); + edit.replace(expr.syntax().text_range(), "var_name".to_string()); + edit.insert(anchor_stmt.text_range().start(), buf); + if wrap_in_block { + edit.insert(anchor_stmt.text_range().end(), " }"); + } + } + edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset); + }); + + ctx.build() +} + +/// Check whether the node is a valid expression which can be extracted to a variable. +/// In general that's true for any expression, but in some cases that would produce invalid code. +fn valid_target_expr(node: SyntaxNode) -> Option { + match node.kind() { + PATH_EXPR | LOOP_EXPR => None, + BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()), + RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()), + BLOCK_EXPR => { + ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from) + } + _ => ast::Expr::cast(node), + } +} + +/// Returns the syntax node which will follow the freshly introduced var +/// and a boolean indicating whether we have to wrap it within a { } block +/// to produce correct code. +/// It can be a statement, the last in a block expression or a wanna be block +/// expression like a lambda or match arm. +fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { + expr.syntax().ancestors().find_map(|node| { + if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) { + if expr.syntax() == &node { + tested_by!(test_introduce_var_last_expr); + return Some((node, false)); + } + } + + if let Some(parent) = node.parent() { + if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR { + return Some((node, true)); + } + } + + if ast::Stmt::cast(node.clone()).is_some() { + return Some((node, false)); + } + + None + }) +} + +#[cfg(test)] +mod tests { + use test_utils::covers; + + use crate::helpers::{ + check_assist_range, check_assist_range_not_applicable, check_assist_range_target, + }; + + use super::*; + + #[test] + fn test_introduce_var_simple() { + check_assist_range( + introduce_variable, + " +fn foo() { + foo(<|>1 + 1<|>); +}", + " +fn foo() { + let <|>var_name = 1 + 1; + foo(var_name); +}", + ); + } + + #[test] + fn introduce_var_in_comment_is_not_applicable() { + covers!(introduce_var_in_comment_is_not_applicable); + check_assist_range_not_applicable( + introduce_variable, + "fn main() { 1 + /* <|>comment<|> */ 1; }", + ); + } + + #[test] + fn test_introduce_var_expr_stmt() { + covers!(test_introduce_var_expr_stmt); + check_assist_range( + introduce_variable, + " +fn foo() { + <|>1 + 1<|>; +}", + " +fn foo() { + let <|>var_name = 1 + 1; +}", + ); + check_assist_range( + introduce_variable, + " +fn foo() { + <|>{ let x = 0; x }<|> + something_else(); +}", + " +fn foo() { + let <|>var_name = { let x = 0; x }; + something_else(); +}", + ); + } + + #[test] + fn test_introduce_var_part_of_expr_stmt() { + check_assist_range( + introduce_variable, + " +fn foo() { + <|>1<|> + 1; +}", + " +fn foo() { + let <|>var_name = 1; + var_name + 1; +}", + ); + } + + #[test] + fn test_introduce_var_last_expr() { + covers!(test_introduce_var_last_expr); + check_assist_range( + introduce_variable, + " +fn foo() { + bar(<|>1 + 1<|>) +}", + " +fn foo() { + let <|>var_name = 1 + 1; + bar(var_name) +}", + ); + check_assist_range( + introduce_variable, + " +fn foo() { + <|>bar(1 + 1)<|> +}", + " +fn foo() { + let <|>var_name = bar(1 + 1); + var_name +}", + ) + } + + #[test] + fn test_introduce_var_in_match_arm_no_block() { + check_assist_range( + introduce_variable, + " +fn main() { + let x = true; + let tuple = match x { + true => (<|>2 + 2<|>, true) + _ => (0, false) + }; +} +", + " +fn main() { + let x = true; + let tuple = match x { + true => { let <|>var_name = 2 + 2; (var_name, true) } + _ => (0, false) + }; +} +", + ); + } + + #[test] + fn test_introduce_var_in_match_arm_with_block() { + check_assist_range( + introduce_variable, + " +fn main() { + let x = true; + let tuple = match x { + true => { + let y = 1; + (<|>2 + y<|>, true) + } + _ => (0, false) + }; +} +", + " +fn main() { + let x = true; + let tuple = match x { + true => { + let y = 1; + let <|>var_name = 2 + y; + (var_name, true) + } + _ => (0, false) + }; +} +", + ); + } + + #[test] + fn test_introduce_var_in_closure_no_block() { + check_assist_range( + introduce_variable, + " +fn main() { + let lambda = |x: u32| <|>x * 2<|>; +} +", + " +fn main() { + let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; +} +", + ); + } + + #[test] + fn test_introduce_var_in_closure_with_block() { + check_assist_range( + introduce_variable, + " +fn main() { + let lambda = |x: u32| { <|>x * 2<|> }; +} +", + " +fn main() { + let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; +} +", + ); + } + + #[test] + fn test_introduce_var_path_simple() { + check_assist_range( + introduce_variable, + " +fn main() { + let o = <|>Some(true)<|>; +} +", + " +fn main() { + let <|>var_name = Some(true); + let o = var_name; +} +", + ); + } + + #[test] + fn test_introduce_var_path_method() { + check_assist_range( + introduce_variable, + " +fn main() { + let v = <|>bar.foo()<|>; +} +", + " +fn main() { + let <|>var_name = bar.foo(); + let v = var_name; +} +", + ); + } + + #[test] + fn test_introduce_var_return() { + check_assist_range( + introduce_variable, + " +fn foo() -> u32 { + <|>return 2 + 2<|>; +} +", + " +fn foo() -> u32 { + let <|>var_name = 2 + 2; + return var_name; +} +", + ); + } + + #[test] + fn test_introduce_var_does_not_add_extra_whitespace() { + check_assist_range( + introduce_variable, + " +fn foo() -> u32 { + + + <|>return 2 + 2<|>; +} +", + " +fn foo() -> u32 { + + + let <|>var_name = 2 + 2; + return var_name; +} +", + ); + + check_assist_range( + introduce_variable, + " +fn foo() -> u32 { + + <|>return 2 + 2<|>; +} +", + " +fn foo() -> u32 { + + let <|>var_name = 2 + 2; + return var_name; +} +", + ); + + check_assist_range( + introduce_variable, + " +fn foo() -> u32 { + let foo = 1; + + // bar + + + <|>return 2 + 2<|>; +} +", + " +fn foo() -> u32 { + let foo = 1; + + // bar + + + let <|>var_name = 2 + 2; + return var_name; +} +", + ); + } + + #[test] + fn test_introduce_var_break() { + check_assist_range( + introduce_variable, + " +fn main() { + let result = loop { + <|>break 2 + 2<|>; + }; +} +", + " +fn main() { + let result = loop { + let <|>var_name = 2 + 2; + break var_name; + }; +} +", + ); + } + + #[test] + fn test_introduce_var_for_cast() { + check_assist_range( + introduce_variable, + " +fn main() { + let v = <|>0f32 as u32<|>; +} +", + " +fn main() { + let <|>var_name = 0f32 as u32; + let v = var_name; +} +", + ); + } + + #[test] + fn test_introduce_var_for_return_not_applicable() { + check_assist_range_not_applicable(introduce_variable, "fn foo() { <|>return<|>; } "); + } + + #[test] + fn test_introduce_var_for_break_not_applicable() { + check_assist_range_not_applicable( + introduce_variable, + "fn main() { loop { <|>break<|>; }; }", + ); + } + + // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic + #[test] + fn introduce_var_target() { + check_assist_range_target( + introduce_variable, + "fn foo() -> u32 { <|>return 2 + 2<|>; }", + "2 + 2", + ); + + check_assist_range_target( + introduce_variable, + " +fn main() { + let x = true; + let tuple = match x { + true => (<|>2 + 2<|>, true) + _ => (0, false) + }; +} +", + "2 + 2", + ); + } +} diff --git a/crates/ra_assists/src/assists/merge_match_arms.rs b/crates/ra_assists/src/assists/merge_match_arms.rs new file mode 100644 index 000000000..3b6a99895 --- /dev/null +++ b/crates/ra_assists/src/assists/merge_match_arms.rs @@ -0,0 +1,188 @@ +use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit}; +use hir::db::HirDatabase; +use ra_syntax::ast::{AstNode, MatchArm}; + +pub(crate) fn merge_match_arms(mut ctx: AssistCtx) -> Option { + let current_arm = ctx.node_at_offset::()?; + + // We check if the following match arm matches this one. We could, but don't, + // compare to the previous match arm as well. + let next = current_arm.syntax().next_sibling(); + let next_arm = MatchArm::cast(next?)?; + + // Don't try to handle arms with guards for now - can add support for this later + if current_arm.guard().is_some() || next_arm.guard().is_some() { + return None; + } + + let current_expr = current_arm.expr()?; + let next_expr = next_arm.expr()?; + + // Check for match arm equality by comparing lengths and then string contents + if current_expr.syntax().text_range().len() != next_expr.syntax().text_range().len() { + return None; + } + if current_expr.syntax().text() != next_expr.syntax().text() { + return None; + } + + let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start(); + + ctx.add_action(AssistId("merge_match_arms"), "merge match arms", |edit| { + fn contains_placeholder(a: &MatchArm) -> bool { + a.pats().any(|x| match x { + ra_syntax::ast::Pat::PlaceholderPat(..) => true, + _ => false, + }) + } + + let pats = if contains_placeholder(¤t_arm) || contains_placeholder(&next_arm) { + "_".into() + } else { + let ps: Vec = current_arm + .pats() + .map(|x| x.syntax().to_string()) + .chain(next_arm.pats().map(|x| x.syntax().to_string())) + .collect(); + ps.join(" | ") + }; + + let arm = format!("{} => {}", pats, current_expr.syntax().text()); + let offset = TextUnit::from_usize(arm.len()) - cursor_to_end; + + let start = current_arm.syntax().text_range().start(); + let end = next_arm.syntax().text_range().end(); + + edit.target(current_arm.syntax().text_range()); + edit.replace(TextRange::from_to(start, end), arm); + edit.set_cursor(start + offset); + }); + + ctx.build() +} + +#[cfg(test)] +mod tests { + use super::merge_match_arms; + use crate::helpers::{check_assist, check_assist_not_applicable}; + + #[test] + fn merge_match_arms_single_patterns() { + check_assist( + merge_match_arms, + r#" + #[derive(Debug)] + enum X { A, B, C } + + fn main() { + let x = X::A; + let y = match x { + X::A => { 1i32<|> } + X::B => { 1i32 } + X::C => { 2i32 } + } + } + "#, + r#" + #[derive(Debug)] + enum X { A, B, C } + + fn main() { + let x = X::A; + let y = match x { + X::A | X::B => { 1i32<|> } + X::C => { 2i32 } + } + } + "#, + ); + } + + #[test] + fn merge_match_arms_multiple_patterns() { + check_assist( + merge_match_arms, + r#" + #[derive(Debug)] + enum X { A, B, C, D, E } + + fn main() { + let x = X::A; + let y = match x { + X::A | X::B => {<|> 1i32 }, + X::C | X::D => { 1i32 }, + X::E => { 2i32 }, + } + } + "#, + r#" + #[derive(Debug)] + enum X { A, B, C, D, E } + + fn main() { + let x = X::A; + let y = match x { + X::A | X::B | X::C | X::D => {<|> 1i32 }, + X::E => { 2i32 }, + } + } + "#, + ); + } + + #[test] + fn merge_match_arms_placeholder_pattern() { + check_assist( + merge_match_arms, + r#" + #[derive(Debug)] + enum X { A, B, C, D, E } + + fn main() { + let x = X::A; + let y = match x { + X::A => { 1i32 }, + X::B => { 2i<|>32 }, + _ => { 2i32 } + } + } + "#, + r#" + #[derive(Debug)] + enum X { A, B, C, D, E } + + fn main() { + let x = X::A; + let y = match x { + X::A => { 1i32 }, + _ => { 2i<|>32 } + } + } + "#, + ); + } + + #[test] + fn merge_match_arms_rejects_guards() { + check_assist_not_applicable( + merge_match_arms, + r#" + #[derive(Debug)] + enum X { + A(i32), + B, + C + } + + fn main() { + let x = X::A; + let y = match x { + X::A(a) if a > 5 => { <|>1i32 }, + X::B => { 1i32 }, + X::C => { 2i32 } + } + } + "#, + ); + } +} diff --git a/crates/ra_assists/src/assists/move_bounds.rs b/crates/ra_assists/src/assists/move_bounds.rs new file mode 100644 index 000000000..526de1d98 --- /dev/null +++ b/crates/ra_assists/src/assists/move_bounds.rs @@ -0,0 +1,135 @@ +use hir::db::HirDatabase; +use ra_syntax::{ + ast::{self, AstNode, NameOwner, TypeBoundsOwner}, + SyntaxElement, + SyntaxKind::*, + TextRange, +}; + +use crate::{ast_editor::AstBuilder, Assist, AssistCtx, AssistId}; + +pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx) -> Option { + let type_param_list = ctx.node_at_offset::()?; + + let mut type_params = type_param_list.type_params(); + if type_params.all(|p| p.type_bound_list().is_none()) { + return None; + } + + let parent = type_param_list.syntax().parent()?; + if parent.children_with_tokens().find(|it| it.kind() == WHERE_CLAUSE).is_some() { + return None; + } + + let anchor: SyntaxElement = match parent.kind() { + FN_DEF => ast::FnDef::cast(parent)?.body()?.syntax().clone().into(), + TRAIT_DEF => ast::TraitDef::cast(parent)?.item_list()?.syntax().clone().into(), + IMPL_BLOCK => ast::ImplBlock::cast(parent)?.item_list()?.syntax().clone().into(), + ENUM_DEF => ast::EnumDef::cast(parent)?.variant_list()?.syntax().clone().into(), + STRUCT_DEF => parent + .children_with_tokens() + .find(|it| it.kind() == RECORD_FIELD_DEF_LIST || it.kind() == SEMI)?, + _ => return None, + }; + + ctx.add_action( + AssistId("move_bounds_to_where_clause"), + "move_bounds_to_where_clause", + |edit| { + let type_params = type_param_list.type_params().collect::>(); + + for param in &type_params { + if let Some(bounds) = param.type_bound_list() { + let colon = param + .syntax() + .children_with_tokens() + .find(|it| it.kind() == COLON) + .unwrap(); + let start = colon.text_range().start(); + let end = bounds.syntax().text_range().end(); + edit.delete(TextRange::from_to(start, end)); + } + } + + let predicates = type_params.iter().filter_map(build_predicate); + let where_clause = AstBuilder::::from_predicates(predicates); + + let to_insert = match anchor.prev_sibling_or_token() { + Some(ref elem) if elem.kind() == WHITESPACE => { + format!("{} ", where_clause.syntax()) + } + _ => format!(" {}", where_clause.syntax()), + }; + edit.insert(anchor.text_range().start(), to_insert); + edit.target(type_param_list.syntax().text_range()); + }, + ); + + ctx.build() +} + +fn build_predicate(param: &ast::TypeParam) -> Option { + let path = AstBuilder::::from_name(param.name()?); + let predicate = + AstBuilder::::from_pieces(path, param.type_bound_list()?.bounds()); + Some(predicate) +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::helpers::check_assist; + + #[test] + fn move_bounds_to_where_clause_fn() { + check_assist( + move_bounds_to_where_clause, + r#" + fn fooF: FnOnce(T) -> T>() {} + "#, + r#" + fn fooF>() where T: u32, F: FnOnce(T) -> T {} + "#, + ); + } + + #[test] + fn move_bounds_to_where_clause_impl() { + check_assist( + move_bounds_to_where_clause, + r#" + implT> A {} + "#, + r#" + implT> A where U: u32 {} + "#, + ); + } + + #[test] + fn move_bounds_to_where_clause_struct() { + check_assist( + move_bounds_to_where_clause, + r#" + struct A<<|>T: Iterator> {} + "#, + r#" + struct A<<|>T> where T: Iterator {} + "#, + ); + } + + #[test] + fn move_bounds_to_where_clause_tuple_struct() { + check_assist( + move_bounds_to_where_clause, + r#" + struct Pair<<|>T: u32>(T, T); + "#, + r#" + struct Pair<<|>T>(T, T) where T: u32; + "#, + ); + } +} diff --git a/crates/ra_assists/src/assists/move_guard.rs b/crates/ra_assists/src/assists/move_guard.rs new file mode 100644 index 000000000..699221e33 --- /dev/null +++ b/crates/ra_assists/src/assists/move_guard.rs @@ -0,0 +1,261 @@ +use hir::db::HirDatabase; +use ra_syntax::{ + ast, + ast::{AstNode, AstToken, IfExpr, MatchArm}, + TextUnit, +}; + +use crate::{Assist, AssistCtx, AssistId}; + +pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx) -> Option { + let match_arm = ctx.node_at_offset::()?; + let guard = match_arm.guard()?; + let space_before_guard = guard.syntax().prev_sibling_or_token(); + + let guard_conditions = guard.expr()?; + let arm_expr = match_arm.expr()?; + let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); + + ctx.add_action(AssistId("move_guard_to_arm_body"), "move guard to arm body", |edit| { + edit.target(guard.syntax().text_range()); + let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { + Some(tok) => { + if let Some(_) = ast::Whitespace::cast(tok.clone()) { + let ele = tok.text_range(); + edit.delete(ele); + ele.len() + } else { + TextUnit::from(0) + } + } + _ => TextUnit::from(0), + }; + + edit.delete(guard.syntax().text_range()); + edit.replace_node_and_indent(arm_expr.syntax(), buf); + edit.set_cursor( + arm_expr.syntax().text_range().start() + TextUnit::from(3) - offseting_amount, + ); + }); + ctx.build() +} + +pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx) -> Option { + let match_arm: MatchArm = ctx.node_at_offset::()?; + let last_match_pat = match_arm.pats().last()?; + + let arm_body = match_arm.expr()?; + let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone())?; + let cond = if_expr.condition()?; + let then_block = if_expr.then_branch()?; + + // Not support if with else branch + if let Some(_) = if_expr.else_branch() { + return None; + } + // Not support moving if let to arm guard + if let Some(_) = cond.pat() { + return None; + } + + let buf = format!(" if {}", cond.syntax().text()); + + ctx.add_action( + AssistId("move_arm_cond_to_match_guard"), + "move condition to match guard", + |edit| { + edit.target(if_expr.syntax().text_range()); + let then_only_expr = then_block.block().and_then(|it| it.statements().next()).is_none(); + + match &then_block.block().and_then(|it| it.expr()) { + Some(then_expr) if then_only_expr => { + edit.replace(if_expr.syntax().text_range(), then_expr.syntax().text()) + } + _ => edit.replace(if_expr.syntax().text_range(), then_block.syntax().text()), + } + + edit.insert(last_match_pat.syntax().text_range().end(), buf); + edit.set_cursor(last_match_pat.syntax().text_range().end() + TextUnit::from(1)); + }, + ); + ctx.build() +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + + #[test] + fn move_guard_to_arm_body_target() { + check_assist_target( + move_guard_to_arm_body, + r#" + fn f() { + let t = 'a'; + let chars = "abcd"; + match t { + '\r' <|>if chars.clone().next() == Some('\n') => false, + _ => true + } + } + "#, + r#"if chars.clone().next() == Some('\n')"#, + ); + } + + #[test] + fn move_guard_to_arm_body_works() { + check_assist( + move_guard_to_arm_body, + r#" + fn f() { + let t = 'a'; + let chars = "abcd"; + match t { + '\r' <|>if chars.clone().next() == Some('\n') => false, + _ => true + } + } + "#, + r#" + fn f() { + let t = 'a'; + let chars = "abcd"; + match t { + '\r' => if chars.clone().next() == Some('\n') { <|>false }, + _ => true + } + } + "#, + ); + } + + #[test] + fn move_guard_to_arm_body_works_complex_match() { + check_assist( + move_guard_to_arm_body, + r#" + fn f() { + match x { + <|>y @ 4 | y @ 5 if y > 5 => true, + _ => false + } + } + "#, + r#" + fn f() { + match x { + y @ 4 | y @ 5 => if y > 5 { <|>true }, + _ => false + } + } + "#, + ); + } + + #[test] + fn move_arm_cond_to_match_guard_works() { + check_assist( + move_arm_cond_to_match_guard, + r#" + fn f() { + let t = 'a'; + let chars = "abcd"; + match t { + '\r' => if chars.clone().next() == Some('\n') { <|>false }, + _ => true + } + } + "#, + r#" + fn f() { + let t = 'a'; + let chars = "abcd"; + match t { + '\r' <|>if chars.clone().next() == Some('\n') => false, + _ => true + } + } + "#, + ); + } + + #[test] + fn move_arm_cond_to_match_guard_if_let_not_works() { + check_assist_not_applicable( + move_arm_cond_to_match_guard, + r#" + fn f() { + let t = 'a'; + let chars = "abcd"; + match t { + '\r' => if let Some(_) = chars.clone().next() { <|>false }, + _ => true + } + } + "#, + ); + } + + #[test] + fn move_arm_cond_to_match_guard_if_empty_body_works() { + check_assist( + move_arm_cond_to_match_guard, + r#" + fn f() { + let t = 'a'; + let chars = "abcd"; + match t { + '\r' => if chars.clone().next().is_some() { <|> }, + _ => true + } + } + "#, + r#" + fn f() { + let t = 'a'; + let chars = "abcd"; + match t { + '\r' <|>if chars.clone().next().is_some() => { }, + _ => true + } + } + "#, + ); + } + + #[test] + fn move_arm_cond_to_match_guard_if_multiline_body_works() { + check_assist( + move_arm_cond_to_match_guard, + r#" + fn f() { + let mut t = 'a'; + let chars = "abcd"; + match t { + '\r' => if chars.clone().next().is_some() { + t = 'e';<|> + false + }, + _ => true + } + } + "#, + r#" + fn f() { + let mut t = 'a'; + let chars = "abcd"; + match t { + '\r' <|>if chars.clone().next().is_some() => { + t = 'e'; + false + }, + _ => true + } + } + "#, + ); + } +} diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs new file mode 100644 index 000000000..965a64c98 --- /dev/null +++ b/crates/ra_assists/src/assists/raw_string.rs @@ -0,0 +1,370 @@ +use hir::db::HirDatabase; +use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit}; + +use crate::{Assist, AssistCtx, AssistId}; + +pub(crate) fn make_raw_string(mut ctx: AssistCtx) -> Option { + let literal = ctx.node_at_offset::()?; + if literal.token().kind() != ra_syntax::SyntaxKind::STRING { + return None; + } + ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { + edit.target(literal.syntax().text_range()); + edit.insert(literal.syntax().text_range().start(), "r"); + }); + ctx.build() +} + +fn find_usual_string_range(s: &str) -> Option { + Some(TextRange::from_to( + TextUnit::from(s.find('"')? as u32), + TextUnit::from(s.rfind('"')? as u32), + )) +} + +pub(crate) fn make_usual_string(mut ctx: AssistCtx) -> Option { + let literal = ctx.node_at_offset::()?; + if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { + return None; + } + let token = literal.token(); + let text = token.text().as_str(); + let usual_string_range = find_usual_string_range(text)?; + ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| { + edit.target(literal.syntax().text_range()); + // parse inside string to escape `"` + let start_of_inside = usual_string_range.start().to_usize() + 1; + let end_of_inside = usual_string_range.end().to_usize(); + let inside_str = &text[start_of_inside..end_of_inside]; + let escaped = inside_str.escape_default().to_string(); + edit.replace(literal.syntax().text_range(), format!("\"{}\"", escaped)); + }); + ctx.build() +} + +pub(crate) fn add_hash(mut ctx: AssistCtx) -> Option { + let literal = ctx.node_at_offset::()?; + if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { + return None; + } + ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| { + edit.target(literal.syntax().text_range()); + edit.insert(literal.syntax().text_range().start() + TextUnit::of_char('r'), "#"); + edit.insert(literal.syntax().text_range().end(), "#"); + }); + ctx.build() +} + +pub(crate) fn remove_hash(mut ctx: AssistCtx) -> Option { + let literal = ctx.node_at_offset::()?; + if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { + return None; + } + let token = literal.token(); + let text = token.text().as_str(); + if text.starts_with("r\"") { + // no hash to remove + return None; + } + ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| { + edit.target(literal.syntax().text_range()); + let result = &text[2..text.len() - 1]; + let result = if result.starts_with("\"") { + // no more hash, escape + let internal_str = &result[1..result.len() - 1]; + format!("\"{}\"", internal_str.escape_default().to_string()) + } else { + result.to_owned() + }; + edit.replace(literal.syntax().text_range(), format!("r{}", result)); + }); + ctx.build() +} + +#[cfg(test)] +mod test { + use super::*; + use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + + #[test] + fn make_raw_string_target() { + check_assist_target( + make_raw_string, + r#" + fn f() { + let s = <|>"random string"; + } + "#, + r#""random string""#, + ); + } + + #[test] + fn make_raw_string_works() { + check_assist( + make_raw_string, + r#" + fn f() { + let s = <|>"random string"; + } + "#, + r#" + fn f() { + let s = <|>r"random string"; + } + "#, + ) + } + + #[test] + fn make_raw_string_with_escaped_works() { + check_assist( + make_raw_string, + r#" + fn f() { + let s = <|>"random\nstring"; + } + "#, + r#" + fn f() { + let s = <|>r"random\nstring"; + } + "#, + ) + } + + #[test] + fn make_raw_string_not_works() { + check_assist_not_applicable( + make_raw_string, + r#" + fn f() { + let s = <|>r"random string"; + } + "#, + ); + } + + #[test] + fn add_hash_target() { + check_assist_target( + add_hash, + r#" + fn f() { + let s = <|>r"random string"; + } + "#, + r#"r"random string""#, + ); + } + + #[test] + fn add_hash_works() { + check_assist( + add_hash, + r#" + fn f() { + let s = <|>r"random string"; + } + "#, + r##" + fn f() { + let s = <|>r#"random string"#; + } + "##, + ) + } + + #[test] + fn add_more_hash_works() { + check_assist( + add_hash, + r##" + fn f() { + let s = <|>r#"random"string"#; + } + "##, + r###" + fn f() { + let s = <|>r##"random"string"##; + } + "###, + ) + } + + #[test] + fn add_hash_not_works() { + check_assist_not_applicable( + add_hash, + r#" + fn f() { + let s = <|>"random string"; + } + "#, + ); + } + + #[test] + fn remove_hash_target() { + check_assist_target( + remove_hash, + r##" + fn f() { + let s = <|>r#"random string"#; + } + "##, + r##"r#"random string"#"##, + ); + } + + #[test] + fn remove_hash_works() { + check_assist( + remove_hash, + r##" + fn f() { + let s = <|>r#"random string"#; + } + "##, + r#" + fn f() { + let s = <|>r"random string"; + } + "#, + ) + } + + #[test] + fn remove_hash_with_quote_works() { + check_assist( + remove_hash, + r##" + fn f() { + let s = <|>r#"random"str"ing"#; + } + "##, + r#" + fn f() { + let s = <|>r"random\"str\"ing"; + } + "#, + ) + } + + #[test] + fn remove_more_hash_works() { + check_assist( + remove_hash, + r###" + fn f() { + let s = <|>r##"random string"##; + } + "###, + r##" + fn f() { + let s = <|>r#"random string"#; + } + "##, + ) + } + + #[test] + fn remove_hash_not_works() { + check_assist_not_applicable( + remove_hash, + r#" + fn f() { + let s = <|>"random string"; + } + "#, + ); + } + + #[test] + fn remove_hash_no_hash_not_works() { + check_assist_not_applicable( + remove_hash, + r#" + fn f() { + let s = <|>r"random string"; + } + "#, + ); + } + + #[test] + fn make_usual_string_target() { + check_assist_target( + make_usual_string, + r##" + fn f() { + let s = <|>r#"random string"#; + } + "##, + r##"r#"random string"#"##, + ); + } + + #[test] + fn make_usual_string_works() { + check_assist( + make_usual_string, + r##" + fn f() { + let s = <|>r#"random string"#; + } + "##, + r#" + fn f() { + let s = <|>"random string"; + } + "#, + ) + } + + #[test] + fn make_usual_string_with_quote_works() { + check_assist( + make_usual_string, + r##" + fn f() { + let s = <|>r#"random"str"ing"#; + } + "##, + r#" + fn f() { + let s = <|>"random\"str\"ing"; + } + "#, + ) + } + + #[test] + fn make_usual_string_more_hash_works() { + check_assist( + make_usual_string, + r###" + fn f() { + let s = <|>r##"random string"##; + } + "###, + r##" + fn f() { + let s = <|>"random string"; + } + "##, + ) + } + + #[test] + fn make_usual_string_not_works() { + check_assist_not_applicable( + make_usual_string, + r#" + fn f() { + let s = <|>"random string"; + } + "#, + ); + } +} diff --git a/crates/ra_assists/src/assists/remove_dbg.rs b/crates/ra_assists/src/assists/remove_dbg.rs new file mode 100644 index 000000000..870133fda --- /dev/null +++ b/crates/ra_assists/src/assists/remove_dbg.rs @@ -0,0 +1,137 @@ +use crate::{Assist, AssistCtx, AssistId}; +use hir::db::HirDatabase; +use ra_syntax::{ + ast::{self, AstNode}, + TextUnit, T, +}; + +pub(crate) fn remove_dbg(mut ctx: AssistCtx) -> Option { + let macro_call = ctx.node_at_offset::()?; + + if !is_valid_macrocall(¯o_call, "dbg")? { + return None; + } + + let macro_range = macro_call.syntax().text_range(); + + // If the cursor is inside the macro call, we'll try to maintain the cursor + // position by subtracting the length of dbg!( from the start of the file + // range, otherwise we'll default to using the start of the macro call + let cursor_pos = { + let file_range = ctx.frange.range; + + let offset_start = file_range + .start() + .checked_sub(macro_range.start()) + .unwrap_or_else(|| TextUnit::from(0)); + + let dbg_size = TextUnit::of_str("dbg!("); + + if offset_start > dbg_size { + file_range.start() - dbg_size + } else { + macro_range.start() + } + }; + + let macro_content = { + let macro_args = macro_call.token_tree()?.syntax().clone(); + + let text = macro_args.text(); + let without_parens = TextUnit::of_char('(')..text.len() - TextUnit::of_char(')'); + text.slice(without_parens).to_string() + }; + + ctx.add_action(AssistId("remove_dbg"), "remove dbg!()", |edit| { + edit.target(macro_call.syntax().text_range()); + edit.replace(macro_range, macro_content); + edit.set_cursor(cursor_pos); + }); + + ctx.build() +} + +/// Verifies that the given macro_call actually matches the given name +/// and contains proper ending tokens +fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option { + let path = macro_call.path()?; + let name_ref = path.segment()?.name_ref()?; + + // Make sure it is actually a dbg-macro call, dbg followed by ! + let excl = path.syntax().next_sibling_or_token()?; + + if name_ref.text() != macro_name || excl.kind() != T![!] { + return None; + } + + let node = macro_call.token_tree()?.syntax().clone(); + let first_child = node.first_child_or_token()?; + let last_child = node.last_child_or_token()?; + + match (first_child.kind(), last_child.kind()) { + (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true), + _ => Some(false), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + + #[test] + fn test_remove_dbg() { + check_assist(remove_dbg, "<|>dbg!(1 + 1)", "<|>1 + 1"); + + check_assist(remove_dbg, "dbg!<|>((1 + 1))", "<|>(1 + 1)"); + + check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 <|>+ 1"); + + check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = <|>1 + 1"); + + check_assist( + remove_dbg, + " +fn foo(n: usize) { + if let Some(_) = dbg!(n.<|>checked_sub(4)) { + // ... + } +} +", + " +fn foo(n: usize) { + if let Some(_) = n.<|>checked_sub(4) { + // ... + } +} +", + ); + } + #[test] + fn test_remove_dbg_with_brackets_and_braces() { + check_assist(remove_dbg, "dbg![<|>1 + 1]", "<|>1 + 1"); + check_assist(remove_dbg, "dbg!{<|>1 + 1}", "<|>1 + 1"); + } + + #[test] + fn test_remove_dbg_not_applicable() { + check_assist_not_applicable(remove_dbg, "<|>vec![1, 2, 3]"); + check_assist_not_applicable(remove_dbg, "<|>dbg(5, 6, 7)"); + check_assist_not_applicable(remove_dbg, "<|>dbg!(5, 6, 7"); + } + + #[test] + fn remove_dbg_target() { + check_assist_target( + remove_dbg, + " +fn foo(n: usize) { + if let Some(_) = dbg!(n.<|>checked_sub(4)) { + // ... + } +} +", + "dbg!(n.checked_sub(4))", + ); + } +} diff --git a/crates/ra_assists/src/assists/replace_if_let_with_match.rs b/crates/ra_assists/src/assists/replace_if_let_with_match.rs new file mode 100644 index 000000000..401835c57 --- /dev/null +++ b/crates/ra_assists/src/assists/replace_if_let_with_match.rs @@ -0,0 +1,102 @@ +use format_buf::format; +use hir::db::HirDatabase; +use ra_fmt::extract_trivial_expression; +use ra_syntax::{ast, AstNode}; + +use crate::{Assist, AssistCtx, AssistId}; + +pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx) -> Option { + let if_expr: ast::IfExpr = ctx.node_at_offset()?; + let cond = if_expr.condition()?; + let pat = cond.pat()?; + let expr = cond.expr()?; + let then_block = if_expr.then_branch()?; + let else_block = match if_expr.else_branch()? { + ast::ElseBranch::Block(it) => it, + ast::ElseBranch::IfExpr(_) => return None, + }; + + ctx.add_action(AssistId("replace_if_let_with_match"), "replace with match", |edit| { + let match_expr = build_match_expr(expr, pat, then_block, else_block); + edit.target(if_expr.syntax().text_range()); + edit.replace_node_and_indent(if_expr.syntax(), match_expr); + edit.set_cursor(if_expr.syntax().text_range().start()) + }); + + ctx.build() +} + +fn build_match_expr( + expr: ast::Expr, + pat1: ast::Pat, + arm1: ast::BlockExpr, + arm2: ast::BlockExpr, +) -> String { + let mut buf = String::new(); + format!(buf, "match {} {{\n", expr.syntax().text()); + format!(buf, " {} => {}\n", pat1.syntax().text(), format_arm(&arm1)); + format!(buf, " _ => {}\n", format_arm(&arm2)); + buf.push_str("}"); + buf +} + +fn format_arm(block: &ast::BlockExpr) -> String { + match extract_trivial_expression(block) { + None => block.syntax().text().to_string(), + Some(e) => format!("{},", e.syntax().text()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::helpers::{check_assist, check_assist_target}; + + #[test] + fn test_replace_if_let_with_match_unwraps_simple_expressions() { + check_assist( + replace_if_let_with_match, + " +impl VariantData { + pub fn is_struct(&self) -> bool { + if <|>let VariantData::Struct(..) = *self { + true + } else { + false + } + } +} ", + " +impl VariantData { + pub fn is_struct(&self) -> bool { + <|>match *self { + VariantData::Struct(..) => true, + _ => false, + } + } +} ", + ) + } + + #[test] + fn replace_if_let_with_match_target() { + check_assist_target( + replace_if_let_with_match, + " +impl VariantData { + pub fn is_struct(&self) -> bool { + if <|>let VariantData::Struct(..) = *self { + true + } else { + false + } + } +} ", + "if let VariantData::Struct(..) = *self { + true + } else { + false + }", + ); + } +} diff --git a/crates/ra_assists/src/assists/split_import.rs b/crates/ra_assists/src/assists/split_import.rs new file mode 100644 index 000000000..2c1edddb9 --- /dev/null +++ b/crates/ra_assists/src/assists/split_import.rs @@ -0,0 +1,61 @@ +use std::iter::successors; + +use hir::db::HirDatabase; +use ra_syntax::{ast, AstNode, TextUnit, T}; + +use crate::{Assist, AssistCtx, AssistId}; + +pub(crate) fn split_import(mut ctx: AssistCtx) -> Option { + let colon_colon = ctx.token_at_offset().find(|leaf| leaf.kind() == T![::])?; + let path = ast::Path::cast(colon_colon.parent())?; + let top_path = successors(Some(path), |it| it.parent_path()).last()?; + + let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast); + if use_tree.is_none() { + return None; + } + + let l_curly = colon_colon.text_range().end(); + let r_curly = match top_path.syntax().parent().and_then(ast::UseTree::cast) { + Some(tree) => tree.syntax().text_range().end(), + None => top_path.syntax().text_range().end(), + }; + + ctx.add_action(AssistId("split_import"), "split import", |edit| { + edit.target(colon_colon.text_range()); + edit.insert(l_curly, "{"); + edit.insert(r_curly, "}"); + edit.set_cursor(l_curly + TextUnit::of_str("{")); + }); + + ctx.build() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::helpers::{check_assist, check_assist_target}; + + #[test] + fn test_split_import() { + check_assist( + split_import, + "use crate::<|>db::RootDatabase;", + "use crate::{<|>db::RootDatabase};", + ) + } + + #[test] + fn split_import_works_with_trees() { + check_assist( + split_import, + "use algo:<|>:visitor::{Visitor, visit}", + "use algo::{<|>visitor::{Visitor, visit}}", + ) + } + + #[test] + fn split_import_target() { + check_assist_target(split_import, "use algo::<|>visitor::{Visitor, visit}", "::"); + } +} diff --git a/crates/ra_assists/src/auto_import.rs b/crates/ra_assists/src/auto_import.rs deleted file mode 100644 index 5aae98546..000000000 --- a/crates/ra_assists/src/auto_import.rs +++ /dev/null @@ -1,939 +0,0 @@ -use hir::{self, db::HirDatabase}; -use ra_text_edit::TextEditBuilder; - -use crate::{ - assist_ctx::{Assist, AssistCtx}, - AssistId, -}; -use ra_syntax::{ - ast::{self, NameOwner}, - AstNode, Direction, SmolStr, - SyntaxKind::{PATH, PATH_SEGMENT}, - SyntaxNode, TextRange, T, -}; - -fn collect_path_segments_raw( - segments: &mut Vec, - mut path: ast::Path, -) -> Option { - let oldlen = segments.len(); - loop { - let mut children = path.syntax().children_with_tokens(); - let (first, second, third) = ( - children.next().map(|n| (n.clone(), n.kind())), - children.next().map(|n| (n.clone(), n.kind())), - children.next().map(|n| (n.clone(), n.kind())), - ); - match (first, second, third) { - (Some((subpath, PATH)), Some((_, T![::])), Some((segment, PATH_SEGMENT))) => { - path = ast::Path::cast(subpath.as_node()?.clone())?; - segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?); - } - (Some((segment, PATH_SEGMENT)), _, _) => { - segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?); - break; - } - (_, _, _) => return None, - } - } - // We need to reverse only the new added segments - let only_new_segments = segments.split_at_mut(oldlen).1; - only_new_segments.reverse(); - Some(segments.len() - oldlen) -} - -fn fmt_segments(segments: &[SmolStr]) -> String { - let mut buf = String::new(); - fmt_segments_raw(segments, &mut buf); - buf -} - -fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) { - let mut iter = segments.iter(); - if let Some(s) = iter.next() { - buf.push_str(s); - } - for s in iter { - buf.push_str("::"); - buf.push_str(s); - } -} - -// Returns the numeber of common segments. -fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize { - left.iter().zip(right).filter(|(l, r)| compare_path_segment(l, r)).count() -} - -fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool { - if let Some(kb) = b.kind() { - match kb { - ast::PathSegmentKind::Name(nameref_b) => a == nameref_b.text(), - ast::PathSegmentKind::SelfKw => a == "self", - ast::PathSegmentKind::SuperKw => a == "super", - ast::PathSegmentKind::CrateKw => a == "crate", - ast::PathSegmentKind::Type { .. } => false, // not allowed in imports - } - } else { - false - } -} - -fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool { - a == b.text() -} - -#[derive(Clone)] -enum ImportAction { - Nothing, - // Add a brand new use statement. - AddNewUse { - anchor: Option, // anchor node - add_after_anchor: bool, - }, - - // To split an existing use statement creating a nested import. - AddNestedImport { - // how may segments matched with the target path - common_segments: usize, - path_to_split: ast::Path, - // the first segment of path_to_split we want to add into the new nested list - first_segment_to_split: Option, - // Wether to add 'self' in addition to the target path - add_self: bool, - }, - // To add the target path to an existing nested import tree list. - AddInTreeList { - common_segments: usize, - // The UseTreeList where to add the target path - tree_list: ast::UseTreeList, - add_self: bool, - }, -} - -impl ImportAction { - fn add_new_use(anchor: Option, add_after_anchor: bool) -> Self { - ImportAction::AddNewUse { anchor, add_after_anchor } - } - - fn add_nested_import( - common_segments: usize, - path_to_split: ast::Path, - first_segment_to_split: Option, - add_self: bool, - ) -> Self { - ImportAction::AddNestedImport { - common_segments, - path_to_split, - first_segment_to_split, - add_self, - } - } - - fn add_in_tree_list( - common_segments: usize, - tree_list: ast::UseTreeList, - add_self: bool, - ) -> Self { - ImportAction::AddInTreeList { common_segments, tree_list, add_self } - } - - fn better(left: ImportAction, right: ImportAction) -> ImportAction { - if left.is_better(&right) { - left - } else { - right - } - } - - fn is_better(&self, other: &ImportAction) -> bool { - match (self, other) { - (ImportAction::Nothing, _) => true, - (ImportAction::AddInTreeList { .. }, ImportAction::Nothing) => false, - ( - ImportAction::AddNestedImport { common_segments: n, .. }, - ImportAction::AddInTreeList { common_segments: m, .. }, - ) => n > m, - ( - ImportAction::AddInTreeList { common_segments: n, .. }, - ImportAction::AddNestedImport { common_segments: m, .. }, - ) => n > m, - (ImportAction::AddInTreeList { .. }, _) => true, - (ImportAction::AddNestedImport { .. }, ImportAction::Nothing) => false, - (ImportAction::AddNestedImport { .. }, _) => true, - (ImportAction::AddNewUse { .. }, _) => false, - } - } -} - -// Find out the best ImportAction to import target path against current_use_tree. -// If current_use_tree has a nested import the function gets called recursively on every UseTree inside a UseTreeList. -fn walk_use_tree_for_best_action( - current_path_segments: &mut Vec, // buffer containing path segments - current_parent_use_tree_list: Option, // will be Some value if we are in a nested import - current_use_tree: ast::UseTree, // the use tree we are currently examinating - target: &[SmolStr], // the path we want to import -) -> ImportAction { - // We save the number of segments in the buffer so we can restore the correct segments - // before returning. Recursive call will add segments so we need to delete them. - let prev_len = current_path_segments.len(); - - let tree_list = current_use_tree.use_tree_list(); - let alias = current_use_tree.alias(); - - let path = match current_use_tree.path() { - Some(path) => path, - None => { - // If the use item don't have a path, it means it's broken (syntax error) - return ImportAction::add_new_use( - current_use_tree - .syntax() - .ancestors() - .find_map(ast::UseItem::cast) - .map(|it| it.syntax().clone()), - true, - ); - } - }; - - // This can happen only if current_use_tree is a direct child of a UseItem - if let Some(name) = alias.and_then(|it| it.name()) { - if compare_path_segment_with_name(&target[0], &name) { - return ImportAction::Nothing; - } - } - - collect_path_segments_raw(current_path_segments, path.clone()); - - // We compare only the new segments added in the line just above. - // The first prev_len segments were already compared in 'parent' recursive calls. - let left = target.split_at(prev_len).1; - let right = current_path_segments.split_at(prev_len).1; - let common = compare_path_segments(left, &right); - let mut action = match common { - 0 => ImportAction::add_new_use( - // e.g: target is std::fmt and we can have - // use foo::bar - // We add a brand new use statement - current_use_tree - .syntax() - .ancestors() - .find_map(ast::UseItem::cast) - .map(|it| it.syntax().clone()), - true, - ), - common if common == left.len() && left.len() == right.len() => { - // e.g: target is std::fmt and we can have - // 1- use std::fmt; - // 2- use std::fmt:{ ... } - if let Some(list) = tree_list { - // In case 2 we need to add self to the nested list - // unless it's already there - let has_self = list.use_trees().map(|it| it.path()).any(|p| { - p.and_then(|it| it.segment()) - .and_then(|it| it.kind()) - .filter(|k| *k == ast::PathSegmentKind::SelfKw) - .is_some() - }); - - if has_self { - ImportAction::Nothing - } else { - ImportAction::add_in_tree_list(current_path_segments.len(), list, true) - } - } else { - // Case 1 - ImportAction::Nothing - } - } - common if common != left.len() && left.len() == right.len() => { - // e.g: target is std::fmt and we have - // use std::io; - // We need to split. - let segments_to_split = current_path_segments.split_at(prev_len + common).1; - ImportAction::add_nested_import( - prev_len + common, - path, - Some(segments_to_split[0].clone()), - false, - ) - } - common if common == right.len() && left.len() > right.len() => { - // e.g: target is std::fmt and we can have - // 1- use std; - // 2- use std::{ ... }; - - // fallback action - let mut better_action = ImportAction::add_new_use( - current_use_tree - .syntax() - .ancestors() - .find_map(ast::UseItem::cast) - .map(|it| it.syntax().clone()), - true, - ); - if let Some(list) = tree_list { - // Case 2, check recursively if the path is already imported in the nested list - for u in list.use_trees() { - let child_action = walk_use_tree_for_best_action( - current_path_segments, - Some(list.clone()), - u, - target, - ); - if child_action.is_better(&better_action) { - better_action = child_action; - if let ImportAction::Nothing = better_action { - return better_action; - } - } - } - } else { - // Case 1, split adding self - better_action = ImportAction::add_nested_import(prev_len + common, path, None, true) - } - better_action - } - common if common == left.len() && left.len() < right.len() => { - // e.g: target is std::fmt and we can have - // use std::fmt::Debug; - let segments_to_split = current_path_segments.split_at(prev_len + common).1; - ImportAction::add_nested_import( - prev_len + common, - path, - Some(segments_to_split[0].clone()), - true, - ) - } - common if common < left.len() && common < right.len() => { - // e.g: target is std::fmt::nested::Debug - // use std::fmt::Display - let segments_to_split = current_path_segments.split_at(prev_len + common).1; - ImportAction::add_nested_import( - prev_len + common, - path, - Some(segments_to_split[0].clone()), - false, - ) - } - _ => unreachable!(), - }; - - // If we are inside a UseTreeList adding a use statement become adding to the existing - // tree list. - action = match (current_parent_use_tree_list, action.clone()) { - (Some(use_tree_list), ImportAction::AddNewUse { .. }) => { - ImportAction::add_in_tree_list(prev_len, use_tree_list, false) - } - (_, _) => action, - }; - - // We remove the segments added - current_path_segments.truncate(prev_len); - action -} - -fn best_action_for_target( - container: SyntaxNode, - anchor: SyntaxNode, - target: &[SmolStr], -) -> ImportAction { - let mut storage = Vec::with_capacity(16); // this should be the only allocation - let best_action = container - .children() - .filter_map(ast::UseItem::cast) - .filter_map(|it| it.use_tree()) - .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target)) - .fold(None, |best, a| match best { - Some(best) => Some(ImportAction::better(best, a)), - None => Some(a), - }); - - match best_action { - Some(action) => action, - None => { - // We have no action and no UseItem was found in container so we find - // another item and we use it as anchor. - // If there are no items above, we choose the target path itself as anchor. - // todo: we should include even whitespace blocks as anchor candidates - let anchor = container - .children() - .find(|n| n.text_range().start() < anchor.text_range().start()) - .or_else(|| Some(anchor)); - - ImportAction::add_new_use(anchor, false) - } - } -} - -fn make_assist(action: &ImportAction, target: &[SmolStr], edit: &mut TextEditBuilder) { - match action { - ImportAction::AddNewUse { anchor, add_after_anchor } => { - make_assist_add_new_use(anchor, *add_after_anchor, target, edit) - } - ImportAction::AddInTreeList { common_segments, tree_list, add_self } => { - // We know that the fist n segments already exists in the use statement we want - // to modify, so we want to add only the last target.len() - n segments. - let segments_to_add = target.split_at(*common_segments).1; - make_assist_add_in_tree_list(tree_list, segments_to_add, *add_self, edit) - } - ImportAction::AddNestedImport { - common_segments, - path_to_split, - first_segment_to_split, - add_self, - } => { - let segments_to_add = target.split_at(*common_segments).1; - make_assist_add_nested_import( - path_to_split, - first_segment_to_split, - segments_to_add, - *add_self, - edit, - ) - } - _ => {} - } -} - -fn make_assist_add_new_use( - anchor: &Option, - after: bool, - target: &[SmolStr], - edit: &mut TextEditBuilder, -) { - if let Some(anchor) = anchor { - let indent = ra_fmt::leading_indent(anchor); - let mut buf = String::new(); - if after { - buf.push_str("\n"); - if let Some(spaces) = &indent { - buf.push_str(spaces); - } - } - buf.push_str("use "); - fmt_segments_raw(target, &mut buf); - buf.push_str(";"); - if !after { - buf.push_str("\n\n"); - if let Some(spaces) = &indent { - buf.push_str(&spaces); - } - } - let position = if after { anchor.text_range().end() } else { anchor.text_range().start() }; - edit.insert(position, buf); - } -} - -fn make_assist_add_in_tree_list( - tree_list: &ast::UseTreeList, - target: &[SmolStr], - add_self: bool, - edit: &mut TextEditBuilder, -) { - let last = tree_list.use_trees().last(); - if let Some(last) = last { - let mut buf = String::new(); - let comma = last.syntax().siblings(Direction::Next).find(|n| n.kind() == T![,]); - let offset = if let Some(comma) = comma { - comma.text_range().end() - } else { - buf.push_str(","); - last.syntax().text_range().end() - }; - if add_self { - buf.push_str(" self") - } else { - buf.push_str(" "); - } - fmt_segments_raw(target, &mut buf); - edit.insert(offset, buf); - } else { - - } -} - -fn make_assist_add_nested_import( - path: &ast::Path, - first_segment_to_split: &Option, - target: &[SmolStr], - add_self: bool, - edit: &mut TextEditBuilder, -) { - let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast); - if let Some(use_tree) = use_tree { - let (start, add_colon_colon) = if let Some(first_segment_to_split) = first_segment_to_split - { - (first_segment_to_split.syntax().text_range().start(), false) - } else { - (use_tree.syntax().text_range().end(), true) - }; - let end = use_tree.syntax().text_range().end(); - - let mut buf = String::new(); - if add_colon_colon { - buf.push_str("::"); - } - buf.push_str("{ "); - if add_self { - buf.push_str("self, "); - } - fmt_segments_raw(target, &mut buf); - if !target.is_empty() { - buf.push_str(", "); - } - edit.insert(start, buf); - edit.insert(end, "}".to_string()); - } -} - -fn apply_auto_import( - container: &SyntaxNode, - path: &ast::Path, - target: &[SmolStr], - edit: &mut TextEditBuilder, -) { - let action = best_action_for_target(container.clone(), path.syntax().clone(), target); - make_assist(&action, target, edit); - if let Some(last) = path.segment() { - // Here we are assuming the assist will provide a correct use statement - // so we can delete the path qualifier - edit.delete(TextRange::from_to( - path.syntax().text_range().start(), - last.syntax().text_range().start(), - )); - } -} - -pub fn collect_hir_path_segments(path: &hir::Path) -> Option> { - let mut ps = Vec::::with_capacity(10); - match path.kind { - hir::PathKind::Abs => ps.push("".into()), - hir::PathKind::Crate => ps.push("crate".into()), - hir::PathKind::Plain => {} - hir::PathKind::Self_ => ps.push("self".into()), - hir::PathKind::Super => ps.push("super".into()), - hir::PathKind::Type(_) => return None, - } - for s in path.segments.iter() { - ps.push(s.name.to_string().into()); - } - Some(ps) -} - -// This function produces sequence of text edits into edit -// to import the target path in the most appropriate scope given -// the cursor position -pub fn auto_import_text_edit( - // Ideally the position of the cursor, used to - position: &SyntaxNode, - // The statement to use as anchor (last resort) - anchor: &SyntaxNode, - // The path to import as a sequence of strings - target: &[SmolStr], - edit: &mut TextEditBuilder, -) { - let container = position.ancestors().find_map(|n| { - if let Some(module) = ast::Module::cast(n.clone()) { - return module.item_list().map(|it| it.syntax().clone()); - } - ast::SourceFile::cast(n).map(|it| it.syntax().clone()) - }); - - if let Some(container) = container { - let action = best_action_for_target(container, anchor.clone(), target); - make_assist(&action, target, edit); - } -} - -pub(crate) fn auto_import(mut ctx: AssistCtx) -> Option { - let path: ast::Path = ctx.node_at_offset()?; - // We don't want to mess with use statements - if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { - return None; - } - - let hir_path = hir::Path::from_ast(path.clone())?; - let segments = collect_hir_path_segments(&hir_path)?; - if segments.len() < 2 { - return None; - } - - if let Some(module) = path.syntax().ancestors().find_map(ast::Module::cast) { - if let (Some(item_list), Some(name)) = (module.item_list(), module.name()) { - ctx.add_action( - AssistId("auto_import"), - format!("import {} in mod {}", fmt_segments(&segments), name.text()), - |edit| { - apply_auto_import( - item_list.syntax(), - &path, - &segments, - edit.text_edit_builder(), - ); - }, - ); - } - } else { - let current_file = path.syntax().ancestors().find_map(ast::SourceFile::cast)?; - ctx.add_action( - AssistId("auto_import"), - format!("import {} in the current file", fmt_segments(&segments)), - |edit| { - apply_auto_import( - current_file.syntax(), - &path, - &segments, - edit.text_edit_builder(), - ); - }, - ); - } - - ctx.build() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::helpers::{check_assist, check_assist_not_applicable}; - - #[test] - fn test_auto_import_add_use_no_anchor() { - check_assist( - auto_import, - " -std::fmt::Debug<|> - ", - " -use std::fmt::Debug; - -Debug<|> - ", - ); - } - #[test] - fn test_auto_import_add_use_no_anchor_with_item_below() { - check_assist( - auto_import, - " -std::fmt::Debug<|> - -fn main() { -} - ", - " -use std::fmt::Debug; - -Debug<|> - -fn main() { -} - ", - ); - } - - #[test] - fn test_auto_import_add_use_no_anchor_with_item_above() { - check_assist( - auto_import, - " -fn main() { -} - -std::fmt::Debug<|> - ", - " -use std::fmt::Debug; - -fn main() { -} - -Debug<|> - ", - ); - } - - #[test] - fn test_auto_import_add_use_no_anchor_2seg() { - check_assist( - auto_import, - " -std::fmt<|>::Debug - ", - " -use std::fmt; - -fmt<|>::Debug - ", - ); - } - - #[test] - fn test_auto_import_add_use() { - check_assist( - auto_import, - " -use stdx; - -impl std::fmt::Debug<|> for Foo { -} - ", - " -use stdx; -use std::fmt::Debug; - -impl Debug<|> for Foo { -} - ", - ); - } - - #[test] - fn test_auto_import_file_use_other_anchor() { - check_assist( - auto_import, - " -impl std::fmt::Debug<|> for Foo { -} - ", - " -use std::fmt::Debug; - -impl Debug<|> for Foo { -} - ", - ); - } - - #[test] - fn test_auto_import_add_use_other_anchor_indent() { - check_assist( - auto_import, - " - impl std::fmt::Debug<|> for Foo { - } - ", - " - use std::fmt::Debug; - - impl Debug<|> for Foo { - } - ", - ); - } - - #[test] - fn test_auto_import_split_different() { - check_assist( - auto_import, - " -use std::fmt; - -impl std::io<|> for Foo { -} - ", - " -use std::{ io, fmt}; - -impl io<|> for Foo { -} - ", - ); - } - - #[test] - fn test_auto_import_split_self_for_use() { - check_assist( - auto_import, - " -use std::fmt; - -impl std::fmt::Debug<|> for Foo { -} - ", - " -use std::fmt::{ self, Debug, }; - -impl Debug<|> for Foo { -} - ", - ); - } - - #[test] - fn test_auto_import_split_self_for_target() { - check_assist( - auto_import, - " -use std::fmt::Debug; - -impl std::fmt<|> for Foo { -} - ", - " -use std::fmt::{ self, Debug}; - -impl fmt<|> for Foo { -} - ", - ); - } - - #[test] - fn test_auto_import_add_to_nested_self_nested() { - check_assist( - auto_import, - " -use std::fmt::{Debug, nested::{Display}}; - -impl std::fmt::nested<|> for Foo { -} -", - " -use std::fmt::{Debug, nested::{Display, self}}; - -impl nested<|> for Foo { -} -", - ); - } - - #[test] - fn test_auto_import_add_to_nested_self_already_included() { - check_assist( - auto_import, - " -use std::fmt::{Debug, nested::{self, Display}}; - -impl std::fmt::nested<|> for Foo { -} -", - " -use std::fmt::{Debug, nested::{self, Display}}; - -impl nested<|> for Foo { -} -", - ); - } - - #[test] - fn test_auto_import_add_to_nested_nested() { - check_assist( - auto_import, - " -use std::fmt::{Debug, nested::{Display}}; - -impl std::fmt::nested::Debug<|> for Foo { -} -", - " -use std::fmt::{Debug, nested::{Display, Debug}}; - -impl Debug<|> for Foo { -} -", - ); - } - - #[test] - fn test_auto_import_split_common_target_longer() { - check_assist( - auto_import, - " -use std::fmt::Debug; - -impl std::fmt::nested::Display<|> for Foo { -} -", - " -use std::fmt::{ nested::Display, Debug}; - -impl Display<|> for Foo { -} -", - ); - } - - #[test] - fn test_auto_import_split_common_use_longer() { - check_assist( - auto_import, - " -use std::fmt::nested::Debug; - -impl std::fmt::Display<|> for Foo { -} -", - " -use std::fmt::{ Display, nested::Debug}; - -impl Display<|> for Foo { -} -", - ); - } - - #[test] - fn test_auto_import_alias() { - check_assist( - auto_import, - " -use std::fmt as foo; - -impl foo::Debug<|> for Foo { -} -", - " -use std::fmt as foo; - -impl Debug<|> for Foo { -} -", - ); - } - - #[test] - fn test_auto_import_not_applicable_one_segment() { - check_assist_not_applicable( - auto_import, - " -impl foo<|> for Foo { -} -", - ); - } - - #[test] - fn test_auto_import_not_applicable_in_use() { - check_assist_not_applicable( - auto_import, - " -use std::fmt<|>; -", - ); - } - - #[test] - fn test_auto_import_add_use_no_anchor_in_mod_mod() { - check_assist( - auto_import, - " -mod foo { - mod bar { - std::fmt::Debug<|> - } -} - ", - " -mod foo { - mod bar { - use std::fmt::Debug; - - Debug<|> - } -} - ", - ); - } -} diff --git a/crates/ra_assists/src/change_visibility.rs b/crates/ra_assists/src/change_visibility.rs deleted file mode 100644 index 60c74debc..000000000 --- a/crates/ra_assists/src/change_visibility.rs +++ /dev/null @@ -1,159 +0,0 @@ -use hir::db::HirDatabase; -use ra_syntax::{ - ast::{self, NameOwner, VisibilityOwner}, - AstNode, - SyntaxKind::{ - ATTR, COMMENT, ENUM_DEF, FN_DEF, IDENT, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY, - WHITESPACE, - }, - SyntaxNode, TextUnit, T, -}; - -use crate::{Assist, AssistCtx, AssistId}; - -pub(crate) fn change_visibility(ctx: AssistCtx) -> Option { - if let Some(vis) = ctx.node_at_offset::() { - return change_vis(ctx, vis); - } - add_vis(ctx) -} - -fn add_vis(mut ctx: AssistCtx) -> Option { - let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { - T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, - _ => false, - }); - - let (offset, target) = if let Some(keyword) = item_keyword { - let parent = keyword.parent(); - let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; - // Parent is not a definition, can't add visibility - if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { - return None; - } - // Already have visibility, do nothing - if parent.children().any(|child| child.kind() == VISIBILITY) { - return None; - } - (vis_offset(&parent), keyword.text_range()) - } else { - let ident = ctx.token_at_offset().find(|leaf| leaf.kind() == IDENT)?; - let field = ident.parent().ancestors().find_map(ast::RecordFieldDef::cast)?; - if field.name()?.syntax().text_range() != ident.text_range() && field.visibility().is_some() - { - return None; - } - (vis_offset(field.syntax()), ident.text_range()) - }; - - ctx.add_action(AssistId("change_visibility"), "make pub(crate)", |edit| { - edit.target(target); - edit.insert(offset, "pub(crate) "); - edit.set_cursor(offset); - }); - - ctx.build() -} - -fn vis_offset(node: &SyntaxNode) -> TextUnit { - node.children_with_tokens() - .skip_while(|it| match it.kind() { - WHITESPACE | COMMENT | ATTR => true, - _ => false, - }) - .next() - .map(|it| it.text_range().start()) - .unwrap_or_else(|| node.text_range().start()) -} - -fn change_vis(mut ctx: AssistCtx, vis: ast::Visibility) -> Option { - if vis.syntax().text() == "pub" { - ctx.add_action(AssistId("change_visibility"), "change to pub(crate)", |edit| { - edit.target(vis.syntax().text_range()); - edit.replace(vis.syntax().text_range(), "pub(crate)"); - edit.set_cursor(vis.syntax().text_range().start()) - }); - - return ctx.build(); - } - if vis.syntax().text() == "pub(crate)" { - ctx.add_action(AssistId("change_visibility"), "change to pub", |edit| { - edit.target(vis.syntax().text_range()); - edit.replace(vis.syntax().text_range(), "pub"); - edit.set_cursor(vis.syntax().text_range().start()); - }); - - return ctx.build(); - } - None -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::helpers::{check_assist, check_assist_target}; - - #[test] - fn change_visibility_adds_pub_crate_to_items() { - check_assist(change_visibility, "<|>fn foo() {}", "<|>pub(crate) fn foo() {}"); - check_assist(change_visibility, "f<|>n foo() {}", "<|>pub(crate) fn foo() {}"); - check_assist(change_visibility, "<|>struct Foo {}", "<|>pub(crate) struct Foo {}"); - check_assist(change_visibility, "<|>mod foo {}", "<|>pub(crate) mod foo {}"); - check_assist(change_visibility, "<|>trait Foo {}", "<|>pub(crate) trait Foo {}"); - check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}"); - check_assist( - change_visibility, - "unsafe f<|>n foo() {}", - "<|>pub(crate) unsafe fn foo() {}", - ); - } - - #[test] - fn change_visibility_works_with_struct_fields() { - check_assist( - change_visibility, - "struct S { <|>field: u32 }", - "struct S { <|>pub(crate) field: u32 }", - ) - } - - #[test] - fn change_visibility_pub_to_pub_crate() { - check_assist(change_visibility, "<|>pub fn foo() {}", "<|>pub(crate) fn foo() {}") - } - - #[test] - fn change_visibility_pub_crate_to_pub() { - check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "<|>pub fn foo() {}") - } - - #[test] - fn change_visibility_handles_comment_attrs() { - check_assist( - change_visibility, - " - /// docs - - // comments - - #[derive(Debug)] - <|>struct Foo; - ", - " - /// docs - - // comments - - #[derive(Debug)] - <|>pub(crate) struct Foo; - ", - ) - } - - #[test] - fn change_visibility_target() { - check_assist_target(change_visibility, "<|>fn foo() {}", "fn"); - check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)"); - check_assist_target(change_visibility, "struct S { <|>field: u32 }", "field"); - } -} diff --git a/crates/ra_assists/src/fill_match_arms.rs b/crates/ra_assists/src/fill_match_arms.rs deleted file mode 100644 index f59062bb9..000000000 --- a/crates/ra_assists/src/fill_match_arms.rs +++ /dev/null @@ -1,229 +0,0 @@ -use std::iter; - -use hir::{db::HirDatabase, Adt, HasSource}; -use ra_syntax::ast::{self, AstNode, NameOwner}; - -use crate::{ast_editor::AstBuilder, Assist, AssistCtx, AssistId}; - -pub(crate) fn fill_match_arms(mut ctx: AssistCtx) -> Option { - let match_expr = ctx.node_at_offset::()?; - let match_arm_list = match_expr.match_arm_list()?; - - // We already have some match arms, so we don't provide any assists. - // Unless if there is only one trivial match arm possibly created - // by match postfix complete. Trivial match arm is the catch all arm. - let mut existing_arms = match_arm_list.arms(); - if let Some(arm) = existing_arms.next() { - if !is_trivial(&arm) || existing_arms.next().is_some() { - return None; - } - }; - - let expr = match_expr.expr()?; - let enum_def = { - let file_id = ctx.frange.file_id; - let analyzer = hir::SourceAnalyzer::new(ctx.db, file_id, expr.syntax(), None); - resolve_enum_def(ctx.db, &analyzer, &expr)? - }; - let variant_list = enum_def.variant_list()?; - - ctx.add_action(AssistId("fill_match_arms"), "fill match arms", |edit| { - let variants = variant_list.variants(); - let arms = variants.filter_map(build_pat).map(|pat| { - AstBuilder::::from_pieces( - iter::once(pat), - &AstBuilder::::unit(), - ) - }); - let new_arm_list = AstBuilder::::from_arms(arms); - - edit.target(match_expr.syntax().text_range()); - edit.set_cursor(expr.syntax().text_range().start()); - edit.replace_node_and_indent(match_arm_list.syntax(), new_arm_list.syntax().text()); - }); - - ctx.build() -} - -fn is_trivial(arm: &ast::MatchArm) -> bool { - arm.pats().any(|pat| match pat { - ast::Pat::PlaceholderPat(..) => true, - _ => false, - }) -} - -fn resolve_enum_def( - db: &impl HirDatabase, - analyzer: &hir::SourceAnalyzer, - expr: &ast::Expr, -) -> Option { - let expr_ty = analyzer.type_of(db, &expr)?; - - analyzer.autoderef(db, expr_ty).find_map(|ty| match ty.as_adt() { - Some((Adt::Enum(e), _)) => Some(e.source(db).ast), - _ => None, - }) -} - -fn build_pat(var: ast::EnumVariant) -> Option { - let path = &AstBuilder::::from_pieces(var.parent_enum().name()?, var.name()?); - - let pat: ast::Pat = match var.kind() { - ast::StructKind::Tuple(field_list) => { - let pats = iter::repeat(AstBuilder::::placeholder().into()) - .take(field_list.fields().count()); - AstBuilder::::from_pieces(path, pats).into() - } - ast::StructKind::Named(field_list) => { - let pats = field_list - .fields() - .map(|f| AstBuilder::::from_name(&f.name().unwrap()).into()); - AstBuilder::::from_pieces(path, pats).into() - } - ast::StructKind::Unit => AstBuilder::::from_path(path).into(), - }; - - Some(pat) -} - -#[cfg(test)] -mod tests { - use crate::helpers::{check_assist, check_assist_target}; - - use super::fill_match_arms; - - #[test] - fn fill_match_arms_empty_body() { - check_assist( - fill_match_arms, - r#" - enum A { - As, - Bs, - Cs(String), - Ds(String, String), - Es{ x: usize, y: usize } - } - - fn main() { - let a = A::As; - match a<|> {} - } - "#, - r#" - enum A { - As, - Bs, - Cs(String), - Ds(String, String), - Es{ x: usize, y: usize } - } - - fn main() { - let a = A::As; - match <|>a { - A::As => (), - A::Bs => (), - A::Cs(_) => (), - A::Ds(_, _) => (), - A::Es{ x, y } => (), - } - } - "#, - ); - } - - #[test] - fn test_fill_match_arm_refs() { - check_assist( - fill_match_arms, - r#" - enum A { - As, - } - - fn foo(a: &A) { - match a<|> { - } - } - "#, - r#" - enum A { - As, - } - - fn foo(a: &A) { - match <|>a { - A::As => (), - } - } - "#, - ); - - check_assist( - fill_match_arms, - r#" - enum A { - Es{ x: usize, y: usize } - } - - fn foo(a: &mut A) { - match a<|> { - } - } - "#, - r#" - enum A { - Es{ x: usize, y: usize } - } - - fn foo(a: &mut A) { - match <|>a { - A::Es{ x, y } => (), - } - } - "#, - ); - } - - #[test] - fn fill_match_arms_target() { - check_assist_target( - fill_match_arms, - r#" - enum E { X, Y } - - fn main() { - match E::X<|> {} - } - "#, - "match E::X {}", - ); - } - - #[test] - fn fill_match_arms_trivial_arm() { - check_assist( - fill_match_arms, - r#" - enum E { X, Y } - - fn main() { - match E::X { - <|>_ => {}, - } - } - "#, - r#" - enum E { X, Y } - - fn main() { - match <|>E::X { - E::X => (), - E::Y => (), - } - } - "#, - ); - } -} diff --git a/crates/ra_assists/src/flip_binexpr.rs b/crates/ra_assists/src/flip_binexpr.rs deleted file mode 100644 index b55b36a8e..000000000 --- a/crates/ra_assists/src/flip_binexpr.rs +++ /dev/null @@ -1,141 +0,0 @@ -use hir::db::HirDatabase; -use ra_syntax::ast::{AstNode, BinExpr, BinOp}; - -use crate::{Assist, AssistCtx, AssistId}; - -/// Flip binary expression assist. -pub(crate) fn flip_binexpr(mut ctx: AssistCtx) -> Option { - let expr = ctx.node_at_offset::()?; - let lhs = expr.lhs()?.syntax().clone(); - let rhs = expr.rhs()?.syntax().clone(); - let op_range = expr.op_token()?.text_range(); - // The assist should be applied only if the cursor is on the operator - let cursor_in_range = ctx.frange.range.is_subrange(&op_range); - if !cursor_in_range { - return None; - } - let action: FlipAction = expr.op_kind()?.into(); - // The assist should not be applied for certain operators - if let FlipAction::DontFlip = action { - return None; - } - - ctx.add_action(AssistId("flip_binexpr"), "flip binary expression", |edit| { - edit.target(op_range); - if let FlipAction::FlipAndReplaceOp(new_op) = action { - edit.replace(op_range, new_op); - } - edit.replace(lhs.text_range(), rhs.text()); - edit.replace(rhs.text_range(), lhs.text()); - }); - - ctx.build() -} - -enum FlipAction { - // Flip the expression - Flip, - // Flip the expression and replace the operator with this string - FlipAndReplaceOp(&'static str), - // Do not flip the expression - DontFlip, -} - -impl From for FlipAction { - fn from(op_kind: BinOp) -> Self { - match op_kind { - BinOp::Assignment => FlipAction::DontFlip, - BinOp::AddAssign => FlipAction::DontFlip, - BinOp::DivAssign => FlipAction::DontFlip, - BinOp::MulAssign => FlipAction::DontFlip, - BinOp::RemAssign => FlipAction::DontFlip, - BinOp::ShrAssign => FlipAction::DontFlip, - BinOp::ShlAssign => FlipAction::DontFlip, - BinOp::SubAssign => FlipAction::DontFlip, - BinOp::BitOrAssign => FlipAction::DontFlip, - BinOp::BitAndAssign => FlipAction::DontFlip, - BinOp::BitXorAssign => FlipAction::DontFlip, - BinOp::GreaterTest => FlipAction::FlipAndReplaceOp("<"), - BinOp::GreaterEqualTest => FlipAction::FlipAndReplaceOp("<="), - BinOp::LesserTest => FlipAction::FlipAndReplaceOp(">"), - BinOp::LesserEqualTest => FlipAction::FlipAndReplaceOp(">="), - _ => FlipAction::Flip, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; - - #[test] - fn flip_binexpr_target_is_the_op() { - check_assist_target(flip_binexpr, "fn f() { let res = 1 ==<|> 2; }", "==") - } - - #[test] - fn flip_binexpr_not_applicable_for_assignment() { - check_assist_not_applicable(flip_binexpr, "fn f() { let mut _x = 1; _x +=<|> 2 }") - } - - #[test] - fn flip_binexpr_works_for_eq() { - check_assist( - flip_binexpr, - "fn f() { let res = 1 ==<|> 2; }", - "fn f() { let res = 2 ==<|> 1; }", - ) - } - - #[test] - fn flip_binexpr_works_for_gt() { - check_assist( - flip_binexpr, - "fn f() { let res = 1 ><|> 2; }", - "fn f() { let res = 2 <<|> 1; }", - ) - } - - #[test] - fn flip_binexpr_works_for_lteq() { - check_assist( - flip_binexpr, - "fn f() { let res = 1 <=<|> 2; }", - "fn f() { let res = 2 >=<|> 1; }", - ) - } - - #[test] - fn flip_binexpr_works_for_complex_expr() { - check_assist( - flip_binexpr, - "fn f() { let res = (1 + 1) ==<|> (2 + 2); }", - "fn f() { let res = (2 + 2) ==<|> (1 + 1); }", - ) - } - - #[test] - fn flip_binexpr_works_inside_match() { - check_assist( - flip_binexpr, - r#" - fn dyn_eq(&self, other: &dyn Diagnostic) -> bool { - match other.downcast_ref::() { - None => false, - Some(it) => it ==<|> self, - } - } - "#, - r#" - fn dyn_eq(&self, other: &dyn Diagnostic) -> bool { - match other.downcast_ref::() { - None => false, - Some(it) => self ==<|> it, - } - } - "#, - ) - } -} diff --git a/crates/ra_assists/src/flip_comma.rs b/crates/ra_assists/src/flip_comma.rs deleted file mode 100644 index 5ee7561bc..000000000 --- a/crates/ra_assists/src/flip_comma.rs +++ /dev/null @@ -1,68 +0,0 @@ -use hir::db::HirDatabase; -use ra_syntax::{algo::non_trivia_sibling, Direction, T}; - -use crate::{Assist, AssistCtx, AssistId}; - -pub(crate) fn flip_comma(mut ctx: AssistCtx) -> Option { - let comma = ctx.token_at_offset().find(|leaf| leaf.kind() == T![,])?; - let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; - let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; - - // Don't apply a "flip" in case of a last comma - // that typically comes before punctuation - if next.kind().is_punct() { - return None; - } - - ctx.add_action(AssistId("flip_comma"), "flip comma", |edit| { - edit.target(comma.text_range()); - edit.replace(prev.text_range(), next.to_string()); - edit.replace(next.text_range(), prev.to_string()); - }); - - ctx.build() -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::helpers::{check_assist, check_assist_target}; - - #[test] - fn flip_comma_works_for_function_parameters() { - check_assist( - flip_comma, - "fn foo(x: i32,<|> y: Result<(), ()>) {}", - "fn foo(y: Result<(), ()>,<|> x: i32) {}", - ) - } - - #[test] - fn flip_comma_target() { - check_assist_target(flip_comma, "fn foo(x: i32,<|> y: Result<(), ()>) {}", ",") - } - - #[test] - #[should_panic] - fn flip_comma_before_punct() { - // See https://github.com/rust-analyzer/rust-analyzer/issues/1619 - // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct - // declaration body. - check_assist_target( - flip_comma, - "pub enum Test { \ - A,<|> \ - }", - ",", - ); - - check_assist_target( - flip_comma, - "pub struct Test { \ - foo: usize,<|> \ - }", - ",", - ); - } -} diff --git a/crates/ra_assists/src/inline_local_variable.rs b/crates/ra_assists/src/inline_local_variable.rs deleted file mode 100644 index eedb29199..000000000 --- a/crates/ra_assists/src/inline_local_variable.rs +++ /dev/null @@ -1,636 +0,0 @@ -use hir::db::HirDatabase; -use ra_syntax::{ - ast::{self, AstNode, AstToken}, - TextRange, -}; - -use crate::assist_ctx::AssistBuilder; -use crate::{Assist, AssistCtx, AssistId}; - -pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx) -> Option { - let let_stmt = ctx.node_at_offset::()?; - let bind_pat = match let_stmt.pat()? { - ast::Pat::BindPat(pat) => pat, - _ => return None, - }; - if bind_pat.is_mutable() { - return None; - } - let initializer_expr = let_stmt.initializer()?; - let delete_range = if let Some(whitespace) = let_stmt - .syntax() - .next_sibling_or_token() - .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone())) - { - TextRange::from_to( - let_stmt.syntax().text_range().start(), - whitespace.syntax().text_range().end(), - ) - } else { - let_stmt.syntax().text_range() - }; - let analyzer = hir::SourceAnalyzer::new(ctx.db, ctx.frange.file_id, bind_pat.syntax(), None); - let refs = analyzer.find_all_refs(&bind_pat); - - let mut wrap_in_parens = vec![true; refs.len()]; - - for (i, desc) in refs.iter().enumerate() { - let usage_node = ctx - .covering_node_for_range(desc.range) - .ancestors() - .find_map(|node| ast::PathExpr::cast(node))?; - let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast); - let usage_parent = match usage_parent_option { - Some(u) => u, - None => { - wrap_in_parens[i] = false; - continue; - } - }; - - wrap_in_parens[i] = match (&initializer_expr, usage_parent) { - (ast::Expr::CallExpr(_), _) - | (ast::Expr::IndexExpr(_), _) - | (ast::Expr::MethodCallExpr(_), _) - | (ast::Expr::FieldExpr(_), _) - | (ast::Expr::TryExpr(_), _) - | (ast::Expr::RefExpr(_), _) - | (ast::Expr::Literal(_), _) - | (ast::Expr::TupleExpr(_), _) - | (ast::Expr::ArrayExpr(_), _) - | (ast::Expr::ParenExpr(_), _) - | (ast::Expr::PathExpr(_), _) - | (ast::Expr::BlockExpr(_), _) - | (_, ast::Expr::CallExpr(_)) - | (_, ast::Expr::TupleExpr(_)) - | (_, ast::Expr::ArrayExpr(_)) - | (_, ast::Expr::ParenExpr(_)) - | (_, ast::Expr::ForExpr(_)) - | (_, ast::Expr::WhileExpr(_)) - | (_, ast::Expr::BreakExpr(_)) - | (_, ast::Expr::ReturnExpr(_)) - | (_, ast::Expr::MatchExpr(_)) => false, - _ => true, - }; - } - - let init_str = initializer_expr.syntax().text().to_string(); - let init_in_paren = format!("({})", &init_str); - - ctx.add_action( - AssistId("inline_local_variable"), - "inline local variable", - move |edit: &mut AssistBuilder| { - edit.delete(delete_range); - for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { - if should_wrap { - edit.replace(desc.range, init_in_paren.clone()) - } else { - edit.replace(desc.range, init_str.clone()) - } - } - edit.set_cursor(delete_range.start()) - }, - ); - - ctx.build() -} - -#[cfg(test)] -mod tests { - use crate::helpers::{check_assist, check_assist_not_applicable}; - - use super::*; - - #[test] - fn test_inline_let_bind_literal_expr() { - check_assist( - inline_local_varialbe, - " -fn bar(a: usize) {} -fn foo() { - let a<|> = 1; - a + 1; - if a > 10 { - } - - while a > 10 { - - } - let b = a * 10; - bar(a); -}", - " -fn bar(a: usize) {} -fn foo() { - <|>1 + 1; - if 1 > 10 { - } - - while 1 > 10 { - - } - let b = 1 * 10; - bar(1); -}", - ); - } - - #[test] - fn test_inline_let_bind_bin_expr() { - check_assist( - inline_local_varialbe, - " -fn bar(a: usize) {} -fn foo() { - let a<|> = 1 + 1; - a + 1; - if a > 10 { - } - - while a > 10 { - - } - let b = a * 10; - bar(a); -}", - " -fn bar(a: usize) {} -fn foo() { - <|>(1 + 1) + 1; - if (1 + 1) > 10 { - } - - while (1 + 1) > 10 { - - } - let b = (1 + 1) * 10; - bar(1 + 1); -}", - ); - } - - #[test] - fn test_inline_let_bind_function_call_expr() { - check_assist( - inline_local_varialbe, - " -fn bar(a: usize) {} -fn foo() { - let a<|> = bar(1); - a + 1; - if a > 10 { - } - - while a > 10 { - - } - let b = a * 10; - bar(a); -}", - " -fn bar(a: usize) {} -fn foo() { - <|>bar(1) + 1; - if bar(1) > 10 { - } - - while bar(1) > 10 { - - } - let b = bar(1) * 10; - bar(bar(1)); -}", - ); - } - - #[test] - fn test_inline_let_bind_cast_expr() { - check_assist( - inline_local_varialbe, - " -fn bar(a: usize): usize { a } -fn foo() { - let a<|> = bar(1) as u64; - a + 1; - if a > 10 { - } - - while a > 10 { - - } - let b = a * 10; - bar(a); -}", - " -fn bar(a: usize): usize { a } -fn foo() { - <|>(bar(1) as u64) + 1; - if (bar(1) as u64) > 10 { - } - - while (bar(1) as u64) > 10 { - - } - let b = (bar(1) as u64) * 10; - bar(bar(1) as u64); -}", - ); - } - - #[test] - fn test_inline_let_bind_block_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let a<|> = { 10 + 1 }; - a + 1; - if a > 10 { - } - - while a > 10 { - - } - let b = a * 10; - bar(a); -}", - " -fn foo() { - <|>{ 10 + 1 } + 1; - if { 10 + 1 } > 10 { - } - - while { 10 + 1 } > 10 { - - } - let b = { 10 + 1 } * 10; - bar({ 10 + 1 }); -}", - ); - } - - #[test] - fn test_inline_let_bind_paren_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let a<|> = ( 10 + 1 ); - a + 1; - if a > 10 { - } - - while a > 10 { - - } - let b = a * 10; - bar(a); -}", - " -fn foo() { - <|>( 10 + 1 ) + 1; - if ( 10 + 1 ) > 10 { - } - - while ( 10 + 1 ) > 10 { - - } - let b = ( 10 + 1 ) * 10; - bar(( 10 + 1 )); -}", - ); - } - - #[test] - fn test_not_inline_mut_variable() { - check_assist_not_applicable( - inline_local_varialbe, - " -fn foo() { - let mut a<|> = 1 + 1; - a + 1; -}", - ); - } - - #[test] - fn test_call_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let a<|> = bar(10 + 1); - let b = a * 10; - let c = a as usize; -}", - " -fn foo() { - <|>let b = bar(10 + 1) * 10; - let c = bar(10 + 1) as usize; -}", - ); - } - - #[test] - fn test_index_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let x = vec![1, 2, 3]; - let a<|> = x[0]; - let b = a * 10; - let c = a as usize; -}", - " -fn foo() { - let x = vec![1, 2, 3]; - <|>let b = x[0] * 10; - let c = x[0] as usize; -}", - ); - } - - #[test] - fn test_method_call_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let bar = vec![1]; - let a<|> = bar.len(); - let b = a * 10; - let c = a as usize; -}", - " -fn foo() { - let bar = vec![1]; - <|>let b = bar.len() * 10; - let c = bar.len() as usize; -}", - ); - } - - #[test] - fn test_field_expr() { - check_assist( - inline_local_varialbe, - " -struct Bar { - foo: usize -} - -fn foo() { - let bar = Bar { foo: 1 }; - let a<|> = bar.foo; - let b = a * 10; - let c = a as usize; -}", - " -struct Bar { - foo: usize -} - -fn foo() { - let bar = Bar { foo: 1 }; - <|>let b = bar.foo * 10; - let c = bar.foo as usize; -}", - ); - } - - #[test] - fn test_try_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() -> Option { - let bar = Some(1); - let a<|> = bar?; - let b = a * 10; - let c = a as usize; - None -}", - " -fn foo() -> Option { - let bar = Some(1); - <|>let b = bar? * 10; - let c = bar? as usize; - None -}", - ); - } - - #[test] - fn test_ref_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let bar = 10; - let a<|> = &bar; - let b = a * 10; -}", - " -fn foo() { - let bar = 10; - <|>let b = &bar * 10; -}", - ); - } - - #[test] - fn test_tuple_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let a<|> = (10, 20); - let b = a[0]; -}", - " -fn foo() { - <|>let b = (10, 20)[0]; -}", - ); - } - - #[test] - fn test_array_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let a<|> = [1, 2, 3]; - let b = a.len(); -}", - " -fn foo() { - <|>let b = [1, 2, 3].len(); -}", - ); - } - - #[test] - fn test_paren() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let a<|> = (10 + 20); - let b = a * 10; - let c = a as usize; -}", - " -fn foo() { - <|>let b = (10 + 20) * 10; - let c = (10 + 20) as usize; -}", - ); - } - - #[test] - fn test_path_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let d = 10; - let a<|> = d; - let b = a * 10; - let c = a as usize; -}", - " -fn foo() { - let d = 10; - <|>let b = d * 10; - let c = d as usize; -}", - ); - } - - #[test] - fn test_block_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let a<|> = { 10 }; - let b = a * 10; - let c = a as usize; -}", - " -fn foo() { - <|>let b = { 10 } * 10; - let c = { 10 } as usize; -}", - ); - } - - #[test] - fn test_used_in_different_expr1() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let a<|> = 10 + 20; - let b = a * 10; - let c = (a, 20); - let d = [a, 10]; - let e = (a); -}", - " -fn foo() { - <|>let b = (10 + 20) * 10; - let c = (10 + 20, 20); - let d = [10 + 20, 10]; - let e = (10 + 20); -}", - ); - } - - #[test] - fn test_used_in_for_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let a<|> = vec![10, 20]; - for i in a {} -}", - " -fn foo() { - <|>for i in vec![10, 20] {} -}", - ); - } - - #[test] - fn test_used_in_while_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let a<|> = 1 > 0; - while a {} -}", - " -fn foo() { - <|>while 1 > 0 {} -}", - ); - } - - #[test] - fn test_used_in_break_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let a<|> = 1 + 1; - loop { - break a; - } -}", - " -fn foo() { - <|>loop { - break 1 + 1; - } -}", - ); - } - - #[test] - fn test_used_in_return_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let a<|> = 1 > 0; - return a; -}", - " -fn foo() { - <|>return 1 > 0; -}", - ); - } - - #[test] - fn test_used_in_match_expr() { - check_assist( - inline_local_varialbe, - " -fn foo() { - let a<|> = 1 > 0; - match a {} -}", - " -fn foo() { - <|>match 1 > 0 {} -}", - ); - } -} diff --git a/crates/ra_assists/src/introduce_variable.rs b/crates/ra_assists/src/introduce_variable.rs deleted file mode 100644 index 470ffe120..000000000 --- a/crates/ra_assists/src/introduce_variable.rs +++ /dev/null @@ -1,516 +0,0 @@ -use format_buf::format; -use hir::db::HirDatabase; -use ra_syntax::{ - ast::{self, AstNode}, - SyntaxKind::{ - BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, - WHITESPACE, - }, - SyntaxNode, TextUnit, -}; -use test_utils::tested_by; - -use crate::{Assist, AssistCtx, AssistId}; - -pub(crate) fn introduce_variable(mut ctx: AssistCtx) -> Option { - if ctx.frange.range.is_empty() { - return None; - } - let node = ctx.covering_element(); - if node.kind() == COMMENT { - tested_by!(introduce_var_in_comment_is_not_applicable); - return None; - } - let expr = node.ancestors().find_map(valid_target_expr)?; - let (anchor_stmt, wrap_in_block) = anchor_stmt(expr.clone())?; - let indent = anchor_stmt.prev_sibling_or_token()?.as_token()?.clone(); - if indent.kind() != WHITESPACE { - return None; - } - ctx.add_action(AssistId("introduce_variable"), "introduce variable", move |edit| { - let mut buf = String::new(); - - let cursor_offset = if wrap_in_block { - buf.push_str("{ let var_name = "); - TextUnit::of_str("{ let ") - } else { - buf.push_str("let var_name = "); - TextUnit::of_str("let ") - }; - format!(buf, "{}", expr.syntax()); - let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); - let is_full_stmt = if let Some(expr_stmt) = &full_stmt { - Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) - } else { - false - }; - if is_full_stmt { - tested_by!(test_introduce_var_expr_stmt); - if !full_stmt.unwrap().has_semi() { - buf.push_str(";"); - } - edit.replace(expr.syntax().text_range(), buf); - } else { - buf.push_str(";"); - - // We want to maintain the indent level, - // but we do not want to duplicate possible - // extra newlines in the indent block - let text = indent.text(); - if text.starts_with('\n') { - buf.push_str("\n"); - buf.push_str(text.trim_start_matches('\n')); - } else { - buf.push_str(text); - } - - edit.target(expr.syntax().text_range()); - edit.replace(expr.syntax().text_range(), "var_name".to_string()); - edit.insert(anchor_stmt.text_range().start(), buf); - if wrap_in_block { - edit.insert(anchor_stmt.text_range().end(), " }"); - } - } - edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset); - }); - - ctx.build() -} - -/// Check whether the node is a valid expression which can be extracted to a variable. -/// In general that's true for any expression, but in some cases that would produce invalid code. -fn valid_target_expr(node: SyntaxNode) -> Option { - match node.kind() { - PATH_EXPR | LOOP_EXPR => None, - BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()), - RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()), - BLOCK_EXPR => { - ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from) - } - _ => ast::Expr::cast(node), - } -} - -/// Returns the syntax node which will follow the freshly introduced var -/// and a boolean indicating whether we have to wrap it within a { } block -/// to produce correct code. -/// It can be a statement, the last in a block expression or a wanna be block -/// expression like a lambda or match arm. -fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { - expr.syntax().ancestors().find_map(|node| { - if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) { - if expr.syntax() == &node { - tested_by!(test_introduce_var_last_expr); - return Some((node, false)); - } - } - - if let Some(parent) = node.parent() { - if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR { - return Some((node, true)); - } - } - - if ast::Stmt::cast(node.clone()).is_some() { - return Some((node, false)); - } - - None - }) -} - -#[cfg(test)] -mod tests { - use test_utils::covers; - - use crate::helpers::{ - check_assist_range, check_assist_range_not_applicable, check_assist_range_target, - }; - - use super::*; - - #[test] - fn test_introduce_var_simple() { - check_assist_range( - introduce_variable, - " -fn foo() { - foo(<|>1 + 1<|>); -}", - " -fn foo() { - let <|>var_name = 1 + 1; - foo(var_name); -}", - ); - } - - #[test] - fn introduce_var_in_comment_is_not_applicable() { - covers!(introduce_var_in_comment_is_not_applicable); - check_assist_range_not_applicable( - introduce_variable, - "fn main() { 1 + /* <|>comment<|> */ 1; }", - ); - } - - #[test] - fn test_introduce_var_expr_stmt() { - covers!(test_introduce_var_expr_stmt); - check_assist_range( - introduce_variable, - " -fn foo() { - <|>1 + 1<|>; -}", - " -fn foo() { - let <|>var_name = 1 + 1; -}", - ); - check_assist_range( - introduce_variable, - " -fn foo() { - <|>{ let x = 0; x }<|> - something_else(); -}", - " -fn foo() { - let <|>var_name = { let x = 0; x }; - something_else(); -}", - ); - } - - #[test] - fn test_introduce_var_part_of_expr_stmt() { - check_assist_range( - introduce_variable, - " -fn foo() { - <|>1<|> + 1; -}", - " -fn foo() { - let <|>var_name = 1; - var_name + 1; -}", - ); - } - - #[test] - fn test_introduce_var_last_expr() { - covers!(test_introduce_var_last_expr); - check_assist_range( - introduce_variable, - " -fn foo() { - bar(<|>1 + 1<|>) -}", - " -fn foo() { - let <|>var_name = 1 + 1; - bar(var_name) -}", - ); - check_assist_range( - introduce_variable, - " -fn foo() { - <|>bar(1 + 1)<|> -}", - " -fn foo() { - let <|>var_name = bar(1 + 1); - var_name -}", - ) - } - - #[test] - fn test_introduce_var_in_match_arm_no_block() { - check_assist_range( - introduce_variable, - " -fn main() { - let x = true; - let tuple = match x { - true => (<|>2 + 2<|>, true) - _ => (0, false) - }; -} -", - " -fn main() { - let x = true; - let tuple = match x { - true => { let <|>var_name = 2 + 2; (var_name, true) } - _ => (0, false) - }; -} -", - ); - } - - #[test] - fn test_introduce_var_in_match_arm_with_block() { - check_assist_range( - introduce_variable, - " -fn main() { - let x = true; - let tuple = match x { - true => { - let y = 1; - (<|>2 + y<|>, true) - } - _ => (0, false) - }; -} -", - " -fn main() { - let x = true; - let tuple = match x { - true => { - let y = 1; - let <|>var_name = 2 + y; - (var_name, true) - } - _ => (0, false) - }; -} -", - ); - } - - #[test] - fn test_introduce_var_in_closure_no_block() { - check_assist_range( - introduce_variable, - " -fn main() { - let lambda = |x: u32| <|>x * 2<|>; -} -", - " -fn main() { - let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; -} -", - ); - } - - #[test] - fn test_introduce_var_in_closure_with_block() { - check_assist_range( - introduce_variable, - " -fn main() { - let lambda = |x: u32| { <|>x * 2<|> }; -} -", - " -fn main() { - let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; -} -", - ); - } - - #[test] - fn test_introduce_var_path_simple() { - check_assist_range( - introduce_variable, - " -fn main() { - let o = <|>Some(true)<|>; -} -", - " -fn main() { - let <|>var_name = Some(true); - let o = var_name; -} -", - ); - } - - #[test] - fn test_introduce_var_path_method() { - check_assist_range( - introduce_variable, - " -fn main() { - let v = <|>bar.foo()<|>; -} -", - " -fn main() { - let <|>var_name = bar.foo(); - let v = var_name; -} -", - ); - } - - #[test] - fn test_introduce_var_return() { - check_assist_range( - introduce_variable, - " -fn foo() -> u32 { - <|>return 2 + 2<|>; -} -", - " -fn foo() -> u32 { - let <|>var_name = 2 + 2; - return var_name; -} -", - ); - } - - #[test] - fn test_introduce_var_does_not_add_extra_whitespace() { - check_assist_range( - introduce_variable, - " -fn foo() -> u32 { - - - <|>return 2 + 2<|>; -} -", - " -fn foo() -> u32 { - - - let <|>var_name = 2 + 2; - return var_name; -} -", - ); - - check_assist_range( - introduce_variable, - " -fn foo() -> u32 { - - <|>return 2 + 2<|>; -} -", - " -fn foo() -> u32 { - - let <|>var_name = 2 + 2; - return var_name; -} -", - ); - - check_assist_range( - introduce_variable, - " -fn foo() -> u32 { - let foo = 1; - - // bar - - - <|>return 2 + 2<|>; -} -", - " -fn foo() -> u32 { - let foo = 1; - - // bar - - - let <|>var_name = 2 + 2; - return var_name; -} -", - ); - } - - #[test] - fn test_introduce_var_break() { - check_assist_range( - introduce_variable, - " -fn main() { - let result = loop { - <|>break 2 + 2<|>; - }; -} -", - " -fn main() { - let result = loop { - let <|>var_name = 2 + 2; - break var_name; - }; -} -", - ); - } - - #[test] - fn test_introduce_var_for_cast() { - check_assist_range( - introduce_variable, - " -fn main() { - let v = <|>0f32 as u32<|>; -} -", - " -fn main() { - let <|>var_name = 0f32 as u32; - let v = var_name; -} -", - ); - } - - #[test] - fn test_introduce_var_for_return_not_applicable() { - check_assist_range_not_applicable(introduce_variable, "fn foo() { <|>return<|>; } "); - } - - #[test] - fn test_introduce_var_for_break_not_applicable() { - check_assist_range_not_applicable( - introduce_variable, - "fn main() { loop { <|>break<|>; }; }", - ); - } - - // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic - #[test] - fn introduce_var_target() { - check_assist_range_target( - introduce_variable, - "fn foo() -> u32 { <|>return 2 + 2<|>; }", - "2 + 2", - ); - - check_assist_range_target( - introduce_variable, - " -fn main() { - let x = true; - let tuple = match x { - true => (<|>2 + 2<|>, true) - _ => (0, false) - }; -} -", - "2 + 2", - ); - } -} diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 756acf415..5e4e8bc92 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -17,6 +17,7 @@ use ra_syntax::{TextRange, TextUnit}; use ra_text_edit::TextEdit; pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; +pub use crate::assists::auto_import::auto_import_text_edit; /// Unique identifier of the assist, should not be shown to the user /// directly. @@ -46,7 +47,7 @@ where H: HirDatabase + 'static, { AssistCtx::with_ctx(db, range, false, |ctx| { - all_assists() + assists::all() .iter() .filter_map(|f| f(ctx.clone())) .map(|a| match a { @@ -68,7 +69,7 @@ where use std::cmp::Ordering; AssistCtx::with_ctx(db, range, true, |ctx| { - let mut a = all_assists() + let mut a = assists::all() .iter() .filter_map(|f| f(ctx.clone())) .map(|a| match a { @@ -86,51 +87,56 @@ where }) } -mod add_derive; -mod add_explicit_type; -mod add_impl; -mod flip_comma; -mod flip_binexpr; -mod change_visibility; -mod fill_match_arms; -mod merge_match_arms; -mod introduce_variable; -mod inline_local_variable; -mod raw_string; -mod replace_if_let_with_match; -mod split_import; -mod remove_dbg; -pub mod auto_import; -mod add_missing_impl_members; -mod move_guard; -mod move_bounds; - -fn all_assists() -> &'static [fn(AssistCtx) -> Option] { - &[ - add_derive::add_derive, - add_explicit_type::add_explicit_type, - add_impl::add_impl, - change_visibility::change_visibility, - fill_match_arms::fill_match_arms, - merge_match_arms::merge_match_arms, - flip_comma::flip_comma, - flip_binexpr::flip_binexpr, - introduce_variable::introduce_variable, - replace_if_let_with_match::replace_if_let_with_match, - split_import::split_import, - remove_dbg::remove_dbg, - auto_import::auto_import, - add_missing_impl_members::add_missing_impl_members, - add_missing_impl_members::add_missing_default_members, - inline_local_variable::inline_local_varialbe, - move_guard::move_guard_to_arm_body, - move_guard::move_arm_cond_to_match_guard, - move_bounds::move_bounds_to_where_clause, - raw_string::add_hash, - raw_string::make_raw_string, - raw_string::make_usual_string, - raw_string::remove_hash, - ] +mod assists { + use crate::{Assist, AssistCtx}; + use hir::db::HirDatabase; + + mod add_derive; + mod add_explicit_type; + mod add_impl; + mod flip_comma; + mod flip_binexpr; + mod change_visibility; + mod fill_match_arms; + mod merge_match_arms; + mod introduce_variable; + mod inline_local_variable; + mod raw_string; + mod replace_if_let_with_match; + mod split_import; + mod remove_dbg; + pub(crate) mod auto_import; + mod add_missing_impl_members; + mod move_guard; + mod move_bounds; + + pub(crate) fn all() -> &'static [fn(AssistCtx) -> Option] { + &[ + add_derive::add_derive, + add_explicit_type::add_explicit_type, + add_impl::add_impl, + change_visibility::change_visibility, + fill_match_arms::fill_match_arms, + merge_match_arms::merge_match_arms, + flip_comma::flip_comma, + flip_binexpr::flip_binexpr, + introduce_variable::introduce_variable, + replace_if_let_with_match::replace_if_let_with_match, + split_import::split_import, + remove_dbg::remove_dbg, + auto_import::auto_import, + add_missing_impl_members::add_missing_impl_members, + add_missing_impl_members::add_missing_default_members, + inline_local_variable::inline_local_varialbe, + move_guard::move_guard_to_arm_body, + move_guard::move_arm_cond_to_match_guard, + move_bounds::move_bounds_to_where_clause, + raw_string::add_hash, + raw_string::make_raw_string, + raw_string::make_usual_string, + raw_string::remove_hash, + ] + } } #[cfg(test)] diff --git a/crates/ra_assists/src/merge_match_arms.rs b/crates/ra_assists/src/merge_match_arms.rs deleted file mode 100644 index 3b6a99895..000000000 --- a/crates/ra_assists/src/merge_match_arms.rs +++ /dev/null @@ -1,188 +0,0 @@ -use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit}; -use hir::db::HirDatabase; -use ra_syntax::ast::{AstNode, MatchArm}; - -pub(crate) fn merge_match_arms(mut ctx: AssistCtx) -> Option { - let current_arm = ctx.node_at_offset::()?; - - // We check if the following match arm matches this one. We could, but don't, - // compare to the previous match arm as well. - let next = current_arm.syntax().next_sibling(); - let next_arm = MatchArm::cast(next?)?; - - // Don't try to handle arms with guards for now - can add support for this later - if current_arm.guard().is_some() || next_arm.guard().is_some() { - return None; - } - - let current_expr = current_arm.expr()?; - let next_expr = next_arm.expr()?; - - // Check for match arm equality by comparing lengths and then string contents - if current_expr.syntax().text_range().len() != next_expr.syntax().text_range().len() { - return None; - } - if current_expr.syntax().text() != next_expr.syntax().text() { - return None; - } - - let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start(); - - ctx.add_action(AssistId("merge_match_arms"), "merge match arms", |edit| { - fn contains_placeholder(a: &MatchArm) -> bool { - a.pats().any(|x| match x { - ra_syntax::ast::Pat::PlaceholderPat(..) => true, - _ => false, - }) - } - - let pats = if contains_placeholder(¤t_arm) || contains_placeholder(&next_arm) { - "_".into() - } else { - let ps: Vec = current_arm - .pats() - .map(|x| x.syntax().to_string()) - .chain(next_arm.pats().map(|x| x.syntax().to_string())) - .collect(); - ps.join(" | ") - }; - - let arm = format!("{} => {}", pats, current_expr.syntax().text()); - let offset = TextUnit::from_usize(arm.len()) - cursor_to_end; - - let start = current_arm.syntax().text_range().start(); - let end = next_arm.syntax().text_range().end(); - - edit.target(current_arm.syntax().text_range()); - edit.replace(TextRange::from_to(start, end), arm); - edit.set_cursor(start + offset); - }); - - ctx.build() -} - -#[cfg(test)] -mod tests { - use super::merge_match_arms; - use crate::helpers::{check_assist, check_assist_not_applicable}; - - #[test] - fn merge_match_arms_single_patterns() { - check_assist( - merge_match_arms, - r#" - #[derive(Debug)] - enum X { A, B, C } - - fn main() { - let x = X::A; - let y = match x { - X::A => { 1i32<|> } - X::B => { 1i32 } - X::C => { 2i32 } - } - } - "#, - r#" - #[derive(Debug)] - enum X { A, B, C } - - fn main() { - let x = X::A; - let y = match x { - X::A | X::B => { 1i32<|> } - X::C => { 2i32 } - } - } - "#, - ); - } - - #[test] - fn merge_match_arms_multiple_patterns() { - check_assist( - merge_match_arms, - r#" - #[derive(Debug)] - enum X { A, B, C, D, E } - - fn main() { - let x = X::A; - let y = match x { - X::A | X::B => {<|> 1i32 }, - X::C | X::D => { 1i32 }, - X::E => { 2i32 }, - } - } - "#, - r#" - #[derive(Debug)] - enum X { A, B, C, D, E } - - fn main() { - let x = X::A; - let y = match x { - X::A | X::B | X::C | X::D => {<|> 1i32 }, - X::E => { 2i32 }, - } - } - "#, - ); - } - - #[test] - fn merge_match_arms_placeholder_pattern() { - check_assist( - merge_match_arms, - r#" - #[derive(Debug)] - enum X { A, B, C, D, E } - - fn main() { - let x = X::A; - let y = match x { - X::A => { 1i32 }, - X::B => { 2i<|>32 }, - _ => { 2i32 } - } - } - "#, - r#" - #[derive(Debug)] - enum X { A, B, C, D, E } - - fn main() { - let x = X::A; - let y = match x { - X::A => { 1i32 }, - _ => { 2i<|>32 } - } - } - "#, - ); - } - - #[test] - fn merge_match_arms_rejects_guards() { - check_assist_not_applicable( - merge_match_arms, - r#" - #[derive(Debug)] - enum X { - A(i32), - B, - C - } - - fn main() { - let x = X::A; - let y = match x { - X::A(a) if a > 5 => { <|>1i32 }, - X::B => { 1i32 }, - X::C => { 2i32 } - } - } - "#, - ); - } -} diff --git a/crates/ra_assists/src/move_bounds.rs b/crates/ra_assists/src/move_bounds.rs deleted file mode 100644 index 526de1d98..000000000 --- a/crates/ra_assists/src/move_bounds.rs +++ /dev/null @@ -1,135 +0,0 @@ -use hir::db::HirDatabase; -use ra_syntax::{ - ast::{self, AstNode, NameOwner, TypeBoundsOwner}, - SyntaxElement, - SyntaxKind::*, - TextRange, -}; - -use crate::{ast_editor::AstBuilder, Assist, AssistCtx, AssistId}; - -pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx) -> Option { - let type_param_list = ctx.node_at_offset::()?; - - let mut type_params = type_param_list.type_params(); - if type_params.all(|p| p.type_bound_list().is_none()) { - return None; - } - - let parent = type_param_list.syntax().parent()?; - if parent.children_with_tokens().find(|it| it.kind() == WHERE_CLAUSE).is_some() { - return None; - } - - let anchor: SyntaxElement = match parent.kind() { - FN_DEF => ast::FnDef::cast(parent)?.body()?.syntax().clone().into(), - TRAIT_DEF => ast::TraitDef::cast(parent)?.item_list()?.syntax().clone().into(), - IMPL_BLOCK => ast::ImplBlock::cast(parent)?.item_list()?.syntax().clone().into(), - ENUM_DEF => ast::EnumDef::cast(parent)?.variant_list()?.syntax().clone().into(), - STRUCT_DEF => parent - .children_with_tokens() - .find(|it| it.kind() == RECORD_FIELD_DEF_LIST || it.kind() == SEMI)?, - _ => return None, - }; - - ctx.add_action( - AssistId("move_bounds_to_where_clause"), - "move_bounds_to_where_clause", - |edit| { - let type_params = type_param_list.type_params().collect::>(); - - for param in &type_params { - if let Some(bounds) = param.type_bound_list() { - let colon = param - .syntax() - .children_with_tokens() - .find(|it| it.kind() == COLON) - .unwrap(); - let start = colon.text_range().start(); - let end = bounds.syntax().text_range().end(); - edit.delete(TextRange::from_to(start, end)); - } - } - - let predicates = type_params.iter().filter_map(build_predicate); - let where_clause = AstBuilder::::from_predicates(predicates); - - let to_insert = match anchor.prev_sibling_or_token() { - Some(ref elem) if elem.kind() == WHITESPACE => { - format!("{} ", where_clause.syntax()) - } - _ => format!(" {}", where_clause.syntax()), - }; - edit.insert(anchor.text_range().start(), to_insert); - edit.target(type_param_list.syntax().text_range()); - }, - ); - - ctx.build() -} - -fn build_predicate(param: &ast::TypeParam) -> Option { - let path = AstBuilder::::from_name(param.name()?); - let predicate = - AstBuilder::::from_pieces(path, param.type_bound_list()?.bounds()); - Some(predicate) -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::helpers::check_assist; - - #[test] - fn move_bounds_to_where_clause_fn() { - check_assist( - move_bounds_to_where_clause, - r#" - fn fooF: FnOnce(T) -> T>() {} - "#, - r#" - fn fooF>() where T: u32, F: FnOnce(T) -> T {} - "#, - ); - } - - #[test] - fn move_bounds_to_where_clause_impl() { - check_assist( - move_bounds_to_where_clause, - r#" - implT> A {} - "#, - r#" - implT> A where U: u32 {} - "#, - ); - } - - #[test] - fn move_bounds_to_where_clause_struct() { - check_assist( - move_bounds_to_where_clause, - r#" - struct A<<|>T: Iterator> {} - "#, - r#" - struct A<<|>T> where T: Iterator {} - "#, - ); - } - - #[test] - fn move_bounds_to_where_clause_tuple_struct() { - check_assist( - move_bounds_to_where_clause, - r#" - struct Pair<<|>T: u32>(T, T); - "#, - r#" - struct Pair<<|>T>(T, T) where T: u32; - "#, - ); - } -} diff --git a/crates/ra_assists/src/move_guard.rs b/crates/ra_assists/src/move_guard.rs deleted file mode 100644 index 699221e33..000000000 --- a/crates/ra_assists/src/move_guard.rs +++ /dev/null @@ -1,261 +0,0 @@ -use hir::db::HirDatabase; -use ra_syntax::{ - ast, - ast::{AstNode, AstToken, IfExpr, MatchArm}, - TextUnit, -}; - -use crate::{Assist, AssistCtx, AssistId}; - -pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx) -> Option { - let match_arm = ctx.node_at_offset::()?; - let guard = match_arm.guard()?; - let space_before_guard = guard.syntax().prev_sibling_or_token(); - - let guard_conditions = guard.expr()?; - let arm_expr = match_arm.expr()?; - let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); - - ctx.add_action(AssistId("move_guard_to_arm_body"), "move guard to arm body", |edit| { - edit.target(guard.syntax().text_range()); - let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { - Some(tok) => { - if let Some(_) = ast::Whitespace::cast(tok.clone()) { - let ele = tok.text_range(); - edit.delete(ele); - ele.len() - } else { - TextUnit::from(0) - } - } - _ => TextUnit::from(0), - }; - - edit.delete(guard.syntax().text_range()); - edit.replace_node_and_indent(arm_expr.syntax(), buf); - edit.set_cursor( - arm_expr.syntax().text_range().start() + TextUnit::from(3) - offseting_amount, - ); - }); - ctx.build() -} - -pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx) -> Option { - let match_arm: MatchArm = ctx.node_at_offset::()?; - let last_match_pat = match_arm.pats().last()?; - - let arm_body = match_arm.expr()?; - let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone())?; - let cond = if_expr.condition()?; - let then_block = if_expr.then_branch()?; - - // Not support if with else branch - if let Some(_) = if_expr.else_branch() { - return None; - } - // Not support moving if let to arm guard - if let Some(_) = cond.pat() { - return None; - } - - let buf = format!(" if {}", cond.syntax().text()); - - ctx.add_action( - AssistId("move_arm_cond_to_match_guard"), - "move condition to match guard", - |edit| { - edit.target(if_expr.syntax().text_range()); - let then_only_expr = then_block.block().and_then(|it| it.statements().next()).is_none(); - - match &then_block.block().and_then(|it| it.expr()) { - Some(then_expr) if then_only_expr => { - edit.replace(if_expr.syntax().text_range(), then_expr.syntax().text()) - } - _ => edit.replace(if_expr.syntax().text_range(), then_block.syntax().text()), - } - - edit.insert(last_match_pat.syntax().text_range().end(), buf); - edit.set_cursor(last_match_pat.syntax().text_range().end() + TextUnit::from(1)); - }, - ); - ctx.build() -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; - - #[test] - fn move_guard_to_arm_body_target() { - check_assist_target( - move_guard_to_arm_body, - r#" - fn f() { - let t = 'a'; - let chars = "abcd"; - match t { - '\r' <|>if chars.clone().next() == Some('\n') => false, - _ => true - } - } - "#, - r#"if chars.clone().next() == Some('\n')"#, - ); - } - - #[test] - fn move_guard_to_arm_body_works() { - check_assist( - move_guard_to_arm_body, - r#" - fn f() { - let t = 'a'; - let chars = "abcd"; - match t { - '\r' <|>if chars.clone().next() == Some('\n') => false, - _ => true - } - } - "#, - r#" - fn f() { - let t = 'a'; - let chars = "abcd"; - match t { - '\r' => if chars.clone().next() == Some('\n') { <|>false }, - _ => true - } - } - "#, - ); - } - - #[test] - fn move_guard_to_arm_body_works_complex_match() { - check_assist( - move_guard_to_arm_body, - r#" - fn f() { - match x { - <|>y @ 4 | y @ 5 if y > 5 => true, - _ => false - } - } - "#, - r#" - fn f() { - match x { - y @ 4 | y @ 5 => if y > 5 { <|>true }, - _ => false - } - } - "#, - ); - } - - #[test] - fn move_arm_cond_to_match_guard_works() { - check_assist( - move_arm_cond_to_match_guard, - r#" - fn f() { - let t = 'a'; - let chars = "abcd"; - match t { - '\r' => if chars.clone().next() == Some('\n') { <|>false }, - _ => true - } - } - "#, - r#" - fn f() { - let t = 'a'; - let chars = "abcd"; - match t { - '\r' <|>if chars.clone().next() == Some('\n') => false, - _ => true - } - } - "#, - ); - } - - #[test] - fn move_arm_cond_to_match_guard_if_let_not_works() { - check_assist_not_applicable( - move_arm_cond_to_match_guard, - r#" - fn f() { - let t = 'a'; - let chars = "abcd"; - match t { - '\r' => if let Some(_) = chars.clone().next() { <|>false }, - _ => true - } - } - "#, - ); - } - - #[test] - fn move_arm_cond_to_match_guard_if_empty_body_works() { - check_assist( - move_arm_cond_to_match_guard, - r#" - fn f() { - let t = 'a'; - let chars = "abcd"; - match t { - '\r' => if chars.clone().next().is_some() { <|> }, - _ => true - } - } - "#, - r#" - fn f() { - let t = 'a'; - let chars = "abcd"; - match t { - '\r' <|>if chars.clone().next().is_some() => { }, - _ => true - } - } - "#, - ); - } - - #[test] - fn move_arm_cond_to_match_guard_if_multiline_body_works() { - check_assist( - move_arm_cond_to_match_guard, - r#" - fn f() { - let mut t = 'a'; - let chars = "abcd"; - match t { - '\r' => if chars.clone().next().is_some() { - t = 'e';<|> - false - }, - _ => true - } - } - "#, - r#" - fn f() { - let mut t = 'a'; - let chars = "abcd"; - match t { - '\r' <|>if chars.clone().next().is_some() => { - t = 'e'; - false - }, - _ => true - } - } - "#, - ); - } -} diff --git a/crates/ra_assists/src/raw_string.rs b/crates/ra_assists/src/raw_string.rs deleted file mode 100644 index 965a64c98..000000000 --- a/crates/ra_assists/src/raw_string.rs +++ /dev/null @@ -1,370 +0,0 @@ -use hir::db::HirDatabase; -use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit}; - -use crate::{Assist, AssistCtx, AssistId}; - -pub(crate) fn make_raw_string(mut ctx: AssistCtx) -> Option { - let literal = ctx.node_at_offset::()?; - if literal.token().kind() != ra_syntax::SyntaxKind::STRING { - return None; - } - ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { - edit.target(literal.syntax().text_range()); - edit.insert(literal.syntax().text_range().start(), "r"); - }); - ctx.build() -} - -fn find_usual_string_range(s: &str) -> Option { - Some(TextRange::from_to( - TextUnit::from(s.find('"')? as u32), - TextUnit::from(s.rfind('"')? as u32), - )) -} - -pub(crate) fn make_usual_string(mut ctx: AssistCtx) -> Option { - let literal = ctx.node_at_offset::()?; - if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { - return None; - } - let token = literal.token(); - let text = token.text().as_str(); - let usual_string_range = find_usual_string_range(text)?; - ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| { - edit.target(literal.syntax().text_range()); - // parse inside string to escape `"` - let start_of_inside = usual_string_range.start().to_usize() + 1; - let end_of_inside = usual_string_range.end().to_usize(); - let inside_str = &text[start_of_inside..end_of_inside]; - let escaped = inside_str.escape_default().to_string(); - edit.replace(literal.syntax().text_range(), format!("\"{}\"", escaped)); - }); - ctx.build() -} - -pub(crate) fn add_hash(mut ctx: AssistCtx) -> Option { - let literal = ctx.node_at_offset::()?; - if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { - return None; - } - ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| { - edit.target(literal.syntax().text_range()); - edit.insert(literal.syntax().text_range().start() + TextUnit::of_char('r'), "#"); - edit.insert(literal.syntax().text_range().end(), "#"); - }); - ctx.build() -} - -pub(crate) fn remove_hash(mut ctx: AssistCtx) -> Option { - let literal = ctx.node_at_offset::()?; - if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { - return None; - } - let token = literal.token(); - let text = token.text().as_str(); - if text.starts_with("r\"") { - // no hash to remove - return None; - } - ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| { - edit.target(literal.syntax().text_range()); - let result = &text[2..text.len() - 1]; - let result = if result.starts_with("\"") { - // no more hash, escape - let internal_str = &result[1..result.len() - 1]; - format!("\"{}\"", internal_str.escape_default().to_string()) - } else { - result.to_owned() - }; - edit.replace(literal.syntax().text_range(), format!("r{}", result)); - }); - ctx.build() -} - -#[cfg(test)] -mod test { - use super::*; - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; - - #[test] - fn make_raw_string_target() { - check_assist_target( - make_raw_string, - r#" - fn f() { - let s = <|>"random string"; - } - "#, - r#""random string""#, - ); - } - - #[test] - fn make_raw_string_works() { - check_assist( - make_raw_string, - r#" - fn f() { - let s = <|>"random string"; - } - "#, - r#" - fn f() { - let s = <|>r"random string"; - } - "#, - ) - } - - #[test] - fn make_raw_string_with_escaped_works() { - check_assist( - make_raw_string, - r#" - fn f() { - let s = <|>"random\nstring"; - } - "#, - r#" - fn f() { - let s = <|>r"random\nstring"; - } - "#, - ) - } - - #[test] - fn make_raw_string_not_works() { - check_assist_not_applicable( - make_raw_string, - r#" - fn f() { - let s = <|>r"random string"; - } - "#, - ); - } - - #[test] - fn add_hash_target() { - check_assist_target( - add_hash, - r#" - fn f() { - let s = <|>r"random string"; - } - "#, - r#"r"random string""#, - ); - } - - #[test] - fn add_hash_works() { - check_assist( - add_hash, - r#" - fn f() { - let s = <|>r"random string"; - } - "#, - r##" - fn f() { - let s = <|>r#"random string"#; - } - "##, - ) - } - - #[test] - fn add_more_hash_works() { - check_assist( - add_hash, - r##" - fn f() { - let s = <|>r#"random"string"#; - } - "##, - r###" - fn f() { - let s = <|>r##"random"string"##; - } - "###, - ) - } - - #[test] - fn add_hash_not_works() { - check_assist_not_applicable( - add_hash, - r#" - fn f() { - let s = <|>"random string"; - } - "#, - ); - } - - #[test] - fn remove_hash_target() { - check_assist_target( - remove_hash, - r##" - fn f() { - let s = <|>r#"random string"#; - } - "##, - r##"r#"random string"#"##, - ); - } - - #[test] - fn remove_hash_works() { - check_assist( - remove_hash, - r##" - fn f() { - let s = <|>r#"random string"#; - } - "##, - r#" - fn f() { - let s = <|>r"random string"; - } - "#, - ) - } - - #[test] - fn remove_hash_with_quote_works() { - check_assist( - remove_hash, - r##" - fn f() { - let s = <|>r#"random"str"ing"#; - } - "##, - r#" - fn f() { - let s = <|>r"random\"str\"ing"; - } - "#, - ) - } - - #[test] - fn remove_more_hash_works() { - check_assist( - remove_hash, - r###" - fn f() { - let s = <|>r##"random string"##; - } - "###, - r##" - fn f() { - let s = <|>r#"random string"#; - } - "##, - ) - } - - #[test] - fn remove_hash_not_works() { - check_assist_not_applicable( - remove_hash, - r#" - fn f() { - let s = <|>"random string"; - } - "#, - ); - } - - #[test] - fn remove_hash_no_hash_not_works() { - check_assist_not_applicable( - remove_hash, - r#" - fn f() { - let s = <|>r"random string"; - } - "#, - ); - } - - #[test] - fn make_usual_string_target() { - check_assist_target( - make_usual_string, - r##" - fn f() { - let s = <|>r#"random string"#; - } - "##, - r##"r#"random string"#"##, - ); - } - - #[test] - fn make_usual_string_works() { - check_assist( - make_usual_string, - r##" - fn f() { - let s = <|>r#"random string"#; - } - "##, - r#" - fn f() { - let s = <|>"random string"; - } - "#, - ) - } - - #[test] - fn make_usual_string_with_quote_works() { - check_assist( - make_usual_string, - r##" - fn f() { - let s = <|>r#"random"str"ing"#; - } - "##, - r#" - fn f() { - let s = <|>"random\"str\"ing"; - } - "#, - ) - } - - #[test] - fn make_usual_string_more_hash_works() { - check_assist( - make_usual_string, - r###" - fn f() { - let s = <|>r##"random string"##; - } - "###, - r##" - fn f() { - let s = <|>"random string"; - } - "##, - ) - } - - #[test] - fn make_usual_string_not_works() { - check_assist_not_applicable( - make_usual_string, - r#" - fn f() { - let s = <|>"random string"; - } - "#, - ); - } -} diff --git a/crates/ra_assists/src/remove_dbg.rs b/crates/ra_assists/src/remove_dbg.rs deleted file mode 100644 index 870133fda..000000000 --- a/crates/ra_assists/src/remove_dbg.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::{Assist, AssistCtx, AssistId}; -use hir::db::HirDatabase; -use ra_syntax::{ - ast::{self, AstNode}, - TextUnit, T, -}; - -pub(crate) fn remove_dbg(mut ctx: AssistCtx) -> Option { - let macro_call = ctx.node_at_offset::()?; - - if !is_valid_macrocall(¯o_call, "dbg")? { - return None; - } - - let macro_range = macro_call.syntax().text_range(); - - // If the cursor is inside the macro call, we'll try to maintain the cursor - // position by subtracting the length of dbg!( from the start of the file - // range, otherwise we'll default to using the start of the macro call - let cursor_pos = { - let file_range = ctx.frange.range; - - let offset_start = file_range - .start() - .checked_sub(macro_range.start()) - .unwrap_or_else(|| TextUnit::from(0)); - - let dbg_size = TextUnit::of_str("dbg!("); - - if offset_start > dbg_size { - file_range.start() - dbg_size - } else { - macro_range.start() - } - }; - - let macro_content = { - let macro_args = macro_call.token_tree()?.syntax().clone(); - - let text = macro_args.text(); - let without_parens = TextUnit::of_char('(')..text.len() - TextUnit::of_char(')'); - text.slice(without_parens).to_string() - }; - - ctx.add_action(AssistId("remove_dbg"), "remove dbg!()", |edit| { - edit.target(macro_call.syntax().text_range()); - edit.replace(macro_range, macro_content); - edit.set_cursor(cursor_pos); - }); - - ctx.build() -} - -/// Verifies that the given macro_call actually matches the given name -/// and contains proper ending tokens -fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option { - let path = macro_call.path()?; - let name_ref = path.segment()?.name_ref()?; - - // Make sure it is actually a dbg-macro call, dbg followed by ! - let excl = path.syntax().next_sibling_or_token()?; - - if name_ref.text() != macro_name || excl.kind() != T![!] { - return None; - } - - let node = macro_call.token_tree()?.syntax().clone(); - let first_child = node.first_child_or_token()?; - let last_child = node.last_child_or_token()?; - - match (first_child.kind(), last_child.kind()) { - (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true), - _ => Some(false), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; - - #[test] - fn test_remove_dbg() { - check_assist(remove_dbg, "<|>dbg!(1 + 1)", "<|>1 + 1"); - - check_assist(remove_dbg, "dbg!<|>((1 + 1))", "<|>(1 + 1)"); - - check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 <|>+ 1"); - - check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = <|>1 + 1"); - - check_assist( - remove_dbg, - " -fn foo(n: usize) { - if let Some(_) = dbg!(n.<|>checked_sub(4)) { - // ... - } -} -", - " -fn foo(n: usize) { - if let Some(_) = n.<|>checked_sub(4) { - // ... - } -} -", - ); - } - #[test] - fn test_remove_dbg_with_brackets_and_braces() { - check_assist(remove_dbg, "dbg![<|>1 + 1]", "<|>1 + 1"); - check_assist(remove_dbg, "dbg!{<|>1 + 1}", "<|>1 + 1"); - } - - #[test] - fn test_remove_dbg_not_applicable() { - check_assist_not_applicable(remove_dbg, "<|>vec![1, 2, 3]"); - check_assist_not_applicable(remove_dbg, "<|>dbg(5, 6, 7)"); - check_assist_not_applicable(remove_dbg, "<|>dbg!(5, 6, 7"); - } - - #[test] - fn remove_dbg_target() { - check_assist_target( - remove_dbg, - " -fn foo(n: usize) { - if let Some(_) = dbg!(n.<|>checked_sub(4)) { - // ... - } -} -", - "dbg!(n.checked_sub(4))", - ); - } -} diff --git a/crates/ra_assists/src/replace_if_let_with_match.rs b/crates/ra_assists/src/replace_if_let_with_match.rs deleted file mode 100644 index 401835c57..000000000 --- a/crates/ra_assists/src/replace_if_let_with_match.rs +++ /dev/null @@ -1,102 +0,0 @@ -use format_buf::format; -use hir::db::HirDatabase; -use ra_fmt::extract_trivial_expression; -use ra_syntax::{ast, AstNode}; - -use crate::{Assist, AssistCtx, AssistId}; - -pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx) -> Option { - let if_expr: ast::IfExpr = ctx.node_at_offset()?; - let cond = if_expr.condition()?; - let pat = cond.pat()?; - let expr = cond.expr()?; - let then_block = if_expr.then_branch()?; - let else_block = match if_expr.else_branch()? { - ast::ElseBranch::Block(it) => it, - ast::ElseBranch::IfExpr(_) => return None, - }; - - ctx.add_action(AssistId("replace_if_let_with_match"), "replace with match", |edit| { - let match_expr = build_match_expr(expr, pat, then_block, else_block); - edit.target(if_expr.syntax().text_range()); - edit.replace_node_and_indent(if_expr.syntax(), match_expr); - edit.set_cursor(if_expr.syntax().text_range().start()) - }); - - ctx.build() -} - -fn build_match_expr( - expr: ast::Expr, - pat1: ast::Pat, - arm1: ast::BlockExpr, - arm2: ast::BlockExpr, -) -> String { - let mut buf = String::new(); - format!(buf, "match {} {{\n", expr.syntax().text()); - format!(buf, " {} => {}\n", pat1.syntax().text(), format_arm(&arm1)); - format!(buf, " _ => {}\n", format_arm(&arm2)); - buf.push_str("}"); - buf -} - -fn format_arm(block: &ast::BlockExpr) -> String { - match extract_trivial_expression(block) { - None => block.syntax().text().to_string(), - Some(e) => format!("{},", e.syntax().text()), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::helpers::{check_assist, check_assist_target}; - - #[test] - fn test_replace_if_let_with_match_unwraps_simple_expressions() { - check_assist( - replace_if_let_with_match, - " -impl VariantData { - pub fn is_struct(&self) -> bool { - if <|>let VariantData::Struct(..) = *self { - true - } else { - false - } - } -} ", - " -impl VariantData { - pub fn is_struct(&self) -> bool { - <|>match *self { - VariantData::Struct(..) => true, - _ => false, - } - } -} ", - ) - } - - #[test] - fn replace_if_let_with_match_target() { - check_assist_target( - replace_if_let_with_match, - " -impl VariantData { - pub fn is_struct(&self) -> bool { - if <|>let VariantData::Struct(..) = *self { - true - } else { - false - } - } -} ", - "if let VariantData::Struct(..) = *self { - true - } else { - false - }", - ); - } -} diff --git a/crates/ra_assists/src/split_import.rs b/crates/ra_assists/src/split_import.rs deleted file mode 100644 index 2c1edddb9..000000000 --- a/crates/ra_assists/src/split_import.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::iter::successors; - -use hir::db::HirDatabase; -use ra_syntax::{ast, AstNode, TextUnit, T}; - -use crate::{Assist, AssistCtx, AssistId}; - -pub(crate) fn split_import(mut ctx: AssistCtx) -> Option { - let colon_colon = ctx.token_at_offset().find(|leaf| leaf.kind() == T![::])?; - let path = ast::Path::cast(colon_colon.parent())?; - let top_path = successors(Some(path), |it| it.parent_path()).last()?; - - let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast); - if use_tree.is_none() { - return None; - } - - let l_curly = colon_colon.text_range().end(); - let r_curly = match top_path.syntax().parent().and_then(ast::UseTree::cast) { - Some(tree) => tree.syntax().text_range().end(), - None => top_path.syntax().text_range().end(), - }; - - ctx.add_action(AssistId("split_import"), "split import", |edit| { - edit.target(colon_colon.text_range()); - edit.insert(l_curly, "{"); - edit.insert(r_curly, "}"); - edit.set_cursor(l_curly + TextUnit::of_str("{")); - }); - - ctx.build() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::helpers::{check_assist, check_assist_target}; - - #[test] - fn test_split_import() { - check_assist( - split_import, - "use crate::<|>db::RootDatabase;", - "use crate::{<|>db::RootDatabase};", - ) - } - - #[test] - fn split_import_works_with_trees() { - check_assist( - split_import, - "use algo:<|>:visitor::{Visitor, visit}", - "use algo::{<|>visitor::{Visitor, visit}}", - ) - } - - #[test] - fn split_import_target() { - check_assist_target(split_import, "use algo::<|>visitor::{Visitor, visit}", "::"); - } -} diff --git a/crates/ra_ide_api/src/completion/complete_scope.rs b/crates/ra_ide_api/src/completion/complete_scope.rs index c1f48b026..cb70a1f21 100644 --- a/crates/ra_ide_api/src/completion/complete_scope.rs +++ b/crates/ra_ide_api/src/completion/complete_scope.rs @@ -1,4 +1,4 @@ -use ra_assists::auto_import; +use ra_assists::auto_import_text_edit; use ra_syntax::{ast, AstNode, SmolStr}; use ra_text_edit::TextEditBuilder; use rustc_hash::FxHashMap; @@ -23,7 +23,7 @@ pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) { let edit = { let mut builder = TextEditBuilder::default(); builder.replace(ctx.source_range(), name.to_string()); - auto_import::auto_import_text_edit( + auto_import_text_edit( &ctx.token.parent(), &ctx.token.parent(), &path, -- cgit v1.2.3 From 69689625ce4465f2d008d6543553d0d91d53dca4 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 25 Sep 2019 14:35:26 +0300 Subject: move ast builder to a separate file --- .../src/assists/add_missing_impl_members.rs | 5 +- crates/ra_assists/src/assists/fill_match_arms.rs | 2 +- crates/ra_assists/src/assists/move_bounds.rs | 2 +- crates/ra_assists/src/ast_builder.rs | 229 ++++++++++++++++++++ crates/ra_assists/src/ast_editor.rs | 231 +-------------------- crates/ra_assists/src/lib.rs | 1 + crates/ra_ide_api/src/diagnostics.rs | 2 +- 7 files changed, 237 insertions(+), 235 deletions(-) create mode 100644 crates/ra_assists/src/ast_builder.rs diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/assists/add_missing_impl_members.rs index cbeb7054f..2894bdd8a 100644 --- a/crates/ra_assists/src/assists/add_missing_impl_members.rs +++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs @@ -4,10 +4,7 @@ use ra_syntax::{ SmolStr, }; -use crate::{ - ast_editor::{AstBuilder, AstEditor}, - Assist, AssistCtx, AssistId, -}; +use crate::{ast_builder::AstBuilder, ast_editor::AstEditor, Assist, AssistCtx, AssistId}; #[derive(PartialEq)] enum AddMissingImplMembersMode { diff --git a/crates/ra_assists/src/assists/fill_match_arms.rs b/crates/ra_assists/src/assists/fill_match_arms.rs index f59062bb9..771aa625f 100644 --- a/crates/ra_assists/src/assists/fill_match_arms.rs +++ b/crates/ra_assists/src/assists/fill_match_arms.rs @@ -3,7 +3,7 @@ use std::iter; use hir::{db::HirDatabase, Adt, HasSource}; use ra_syntax::ast::{self, AstNode, NameOwner}; -use crate::{ast_editor::AstBuilder, Assist, AssistCtx, AssistId}; +use crate::{ast_builder::AstBuilder, Assist, AssistCtx, AssistId}; pub(crate) fn fill_match_arms(mut ctx: AssistCtx) -> Option { let match_expr = ctx.node_at_offset::()?; diff --git a/crates/ra_assists/src/assists/move_bounds.rs b/crates/ra_assists/src/assists/move_bounds.rs index 526de1d98..aa9036fed 100644 --- a/crates/ra_assists/src/assists/move_bounds.rs +++ b/crates/ra_assists/src/assists/move_bounds.rs @@ -6,7 +6,7 @@ use ra_syntax::{ TextRange, }; -use crate::{ast_editor::AstBuilder, Assist, AssistCtx, AssistId}; +use crate::{ast_builder::AstBuilder, Assist, AssistCtx, AssistId}; pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx) -> Option { let type_param_list = ctx.node_at_offset::()?; diff --git a/crates/ra_assists/src/ast_builder.rs b/crates/ra_assists/src/ast_builder.rs new file mode 100644 index 000000000..e4ea1fca9 --- /dev/null +++ b/crates/ra_assists/src/ast_builder.rs @@ -0,0 +1,229 @@ +use itertools::Itertools; + +use hir::Name; +use ra_syntax::{ast, AstNode, SourceFile}; + +pub struct AstBuilder { + _phantom: std::marker::PhantomData, +} + +impl AstBuilder { + pub fn from_name(name: &Name) -> ast::RecordField { + ast_node_from_file_text(&format!("fn f() {{ S {{ {}: (), }} }}", name)) + } + + fn from_text(text: &str) -> ast::RecordField { + ast_node_from_file_text(&format!("fn f() {{ S {{ {}, }} }}", text)) + } + + pub fn from_pieces(name: &ast::NameRef, expr: Option<&ast::Expr>) -> ast::RecordField { + match expr { + Some(expr) => Self::from_text(&format!("{}: {}", name.syntax(), expr.syntax())), + None => Self::from_text(&name.syntax().to_string()), + } + } +} + +impl AstBuilder { + fn from_text(text: &str) -> ast::Block { + ast_node_from_file_text(&format!("fn f() {}", text)) + } + + pub fn single_expr(e: &ast::Expr) -> ast::Block { + Self::from_text(&format!("{{ {} }}", e.syntax())) + } +} + +impl AstBuilder { + fn from_text(text: &str) -> ast::Expr { + ast_node_from_file_text(&format!("const C: () = {};", text)) + } + + pub fn unit() -> ast::Expr { + Self::from_text("()") + } + + pub fn unimplemented() -> ast::Expr { + Self::from_text("unimplemented!()") + } +} + +impl AstBuilder { + pub fn new(text: &str) -> ast::NameRef { + ast_node_from_file_text(&format!("fn f() {{ {}; }}", text)) + } +} + +impl AstBuilder { + fn from_text(text: &str) -> ast::Path { + ast_node_from_file_text(text) + } + + pub fn from_name(name: ast::Name) -> ast::Path { + let name = name.syntax().to_string(); + Self::from_text(name.as_str()) + } + + pub fn from_pieces(enum_name: ast::Name, var_name: ast::Name) -> ast::Path { + Self::from_text(&format!("{}::{}", enum_name.syntax(), var_name.syntax())) + } +} + +impl AstBuilder { + fn from_text(text: &str) -> ast::BindPat { + ast_node_from_file_text(&format!("fn f({}: ())", text)) + } + + pub fn from_name(name: &ast::Name) -> ast::BindPat { + Self::from_text(name.text()) + } +} + +impl AstBuilder { + fn from_text(text: &str) -> ast::PlaceholderPat { + ast_node_from_file_text(&format!("fn f({}: ())", text)) + } + + pub fn placeholder() -> ast::PlaceholderPat { + Self::from_text("_") + } +} + +impl AstBuilder { + fn from_text(text: &str) -> ast::TupleStructPat { + ast_node_from_file_text(&format!("fn f({}: ())", text)) + } + + pub fn from_pieces( + path: &ast::Path, + pats: impl Iterator, + ) -> ast::TupleStructPat { + let pats_str = pats.map(|p| p.syntax().to_string()).collect::>().join(", "); + Self::from_text(&format!("{}({})", path.syntax(), pats_str)) + } +} + +impl AstBuilder { + fn from_text(text: &str) -> ast::RecordPat { + ast_node_from_file_text(&format!("fn f({}: ())", text)) + } + + pub fn from_pieces(path: &ast::Path, pats: impl Iterator) -> ast::RecordPat { + let pats_str = pats.map(|p| p.syntax().to_string()).collect::>().join(", "); + Self::from_text(&format!("{}{{ {} }}", path.syntax(), pats_str)) + } +} + +impl AstBuilder { + fn from_text(text: &str) -> ast::PathPat { + ast_node_from_file_text(&format!("fn f({}: ())", text)) + } + + pub fn from_path(path: &ast::Path) -> ast::PathPat { + let path_str = path.syntax().text().to_string(); + Self::from_text(path_str.as_str()) + } +} + +impl AstBuilder { + fn from_text(text: &str) -> ast::MatchArm { + ast_node_from_file_text(&format!("fn f() {{ match () {{{}}} }}", text)) + } + + pub fn from_pieces(pats: impl Iterator, expr: &ast::Expr) -> ast::MatchArm { + let pats_str = pats.map(|p| p.syntax().to_string()).join(" | "); + Self::from_text(&format!("{} => {}", pats_str, expr.syntax())) + } +} + +impl AstBuilder { + fn from_text(text: &str) -> ast::MatchArmList { + ast_node_from_file_text(&format!("fn f() {{ match () {{{}}} }}", text)) + } + + pub fn from_arms(arms: impl Iterator) -> ast::MatchArmList { + let arms_str = arms.map(|arm| format!("\n {}", arm.syntax())).join(","); + Self::from_text(&format!("{},\n", arms_str)) + } +} + +impl AstBuilder { + fn from_text(text: &str) -> ast::WherePred { + ast_node_from_file_text(&format!("fn f() where {} {{ }}", text)) + } + + pub fn from_pieces( + path: ast::Path, + bounds: impl Iterator, + ) -> ast::WherePred { + let bounds = bounds.map(|b| b.syntax().to_string()).collect::>().join(" + "); + Self::from_text(&format!("{}: {}", path.syntax(), bounds)) + } +} + +impl AstBuilder { + fn from_text(text: &str) -> ast::WhereClause { + ast_node_from_file_text(&format!("fn f() where {} {{ }}", text)) + } + + pub fn from_predicates(preds: impl Iterator) -> ast::WhereClause { + let preds = preds.map(|p| p.syntax().to_string()).collect::>().join(", "); + Self::from_text(preds.as_str()) + } +} + +fn ast_node_from_file_text(text: &str) -> N { + let parse = SourceFile::parse(text); + let res = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); + res +} + +pub(crate) mod tokens { + use once_cell::sync::Lazy; + use ra_syntax::{AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken, T}; + + static SOURCE_FILE: Lazy> = Lazy::new(|| SourceFile::parse(",\n; ;")); + + pub(crate) fn comma() -> SyntaxToken { + SOURCE_FILE + .tree() + .syntax() + .descendants_with_tokens() + .filter_map(|it| it.into_token()) + .find(|it| it.kind() == T![,]) + .unwrap() + } + + pub(crate) fn single_space() -> SyntaxToken { + SOURCE_FILE + .tree() + .syntax() + .descendants_with_tokens() + .filter_map(|it| it.into_token()) + .find(|it| it.kind() == WHITESPACE && it.text().as_str() == " ") + .unwrap() + } + + #[allow(unused)] + pub(crate) fn single_newline() -> SyntaxToken { + SOURCE_FILE + .tree() + .syntax() + .descendants_with_tokens() + .filter_map(|it| it.into_token()) + .find(|it| it.kind() == WHITESPACE && it.text().as_str() == "\n") + .unwrap() + } + + pub(crate) struct WsBuilder(SourceFile); + + impl WsBuilder { + pub(crate) fn new(text: &str) -> WsBuilder { + WsBuilder(SourceFile::parse(text).ok().unwrap()) + } + pub(crate) fn ws(&self) -> SyntaxToken { + self.0.syntax().first_child_or_token().unwrap().into_token().unwrap() + } + } + +} diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs index a710edce8..81621afef 100644 --- a/crates/ra_assists/src/ast_editor.rs +++ b/crates/ra_assists/src/ast_editor.rs @@ -1,18 +1,18 @@ use std::{iter, ops::RangeInclusive}; use arrayvec::ArrayVec; -use itertools::Itertools; -use hir::Name; use ra_fmt::leading_indent; use ra_syntax::{ algo::{insert_children, replace_children}, - ast, AstNode, Direction, InsertPosition, SourceFile, SyntaxElement, + ast, AstNode, Direction, InsertPosition, SyntaxElement, SyntaxKind::*, T, }; use ra_text_edit::TextEditBuilder; +use crate::ast_builder::tokens; + pub struct AstEditor { original_ast: N, ast: N, @@ -240,228 +240,3 @@ impl AstEditor { self.ast = self.replace_children(replace_range, to_insert.into_iter()) } } - -pub struct AstBuilder { - _phantom: std::marker::PhantomData, -} - -impl AstBuilder { - pub fn from_name(name: &Name) -> ast::RecordField { - ast_node_from_file_text(&format!("fn f() {{ S {{ {}: (), }} }}", name)) - } - - fn from_text(text: &str) -> ast::RecordField { - ast_node_from_file_text(&format!("fn f() {{ S {{ {}, }} }}", text)) - } - - pub fn from_pieces(name: &ast::NameRef, expr: Option<&ast::Expr>) -> ast::RecordField { - match expr { - Some(expr) => Self::from_text(&format!("{}: {}", name.syntax(), expr.syntax())), - None => Self::from_text(&name.syntax().to_string()), - } - } -} - -impl AstBuilder { - fn from_text(text: &str) -> ast::Block { - ast_node_from_file_text(&format!("fn f() {}", text)) - } - - pub fn single_expr(e: &ast::Expr) -> ast::Block { - Self::from_text(&format!("{{ {} }}", e.syntax())) - } -} - -impl AstBuilder { - fn from_text(text: &str) -> ast::Expr { - ast_node_from_file_text(&format!("const C: () = {};", text)) - } - - pub fn unit() -> ast::Expr { - Self::from_text("()") - } - - pub fn unimplemented() -> ast::Expr { - Self::from_text("unimplemented!()") - } -} - -impl AstBuilder { - pub fn new(text: &str) -> ast::NameRef { - ast_node_from_file_text(&format!("fn f() {{ {}; }}", text)) - } -} - -impl AstBuilder { - fn from_text(text: &str) -> ast::Path { - ast_node_from_file_text(text) - } - - pub fn from_name(name: ast::Name) -> ast::Path { - let name = name.syntax().to_string(); - Self::from_text(name.as_str()) - } - - pub fn from_pieces(enum_name: ast::Name, var_name: ast::Name) -> ast::Path { - Self::from_text(&format!("{}::{}", enum_name.syntax(), var_name.syntax())) - } -} - -impl AstBuilder { - fn from_text(text: &str) -> ast::BindPat { - ast_node_from_file_text(&format!("fn f({}: ())", text)) - } - - pub fn from_name(name: &ast::Name) -> ast::BindPat { - Self::from_text(name.text()) - } -} - -impl AstBuilder { - fn from_text(text: &str) -> ast::PlaceholderPat { - ast_node_from_file_text(&format!("fn f({}: ())", text)) - } - - pub fn placeholder() -> ast::PlaceholderPat { - Self::from_text("_") - } -} - -impl AstBuilder { - fn from_text(text: &str) -> ast::TupleStructPat { - ast_node_from_file_text(&format!("fn f({}: ())", text)) - } - - pub fn from_pieces( - path: &ast::Path, - pats: impl Iterator, - ) -> ast::TupleStructPat { - let pats_str = pats.map(|p| p.syntax().to_string()).collect::>().join(", "); - Self::from_text(&format!("{}({})", path.syntax(), pats_str)) - } -} - -impl AstBuilder { - fn from_text(text: &str) -> ast::RecordPat { - ast_node_from_file_text(&format!("fn f({}: ())", text)) - } - - pub fn from_pieces(path: &ast::Path, pats: impl Iterator) -> ast::RecordPat { - let pats_str = pats.map(|p| p.syntax().to_string()).collect::>().join(", "); - Self::from_text(&format!("{}{{ {} }}", path.syntax(), pats_str)) - } -} - -impl AstBuilder { - fn from_text(text: &str) -> ast::PathPat { - ast_node_from_file_text(&format!("fn f({}: ())", text)) - } - - pub fn from_path(path: &ast::Path) -> ast::PathPat { - let path_str = path.syntax().text().to_string(); - Self::from_text(path_str.as_str()) - } -} - -impl AstBuilder { - fn from_text(text: &str) -> ast::MatchArm { - ast_node_from_file_text(&format!("fn f() {{ match () {{{}}} }}", text)) - } - - pub fn from_pieces(pats: impl Iterator, expr: &ast::Expr) -> ast::MatchArm { - let pats_str = pats.map(|p| p.syntax().to_string()).join(" | "); - Self::from_text(&format!("{} => {}", pats_str, expr.syntax())) - } -} - -impl AstBuilder { - fn from_text(text: &str) -> ast::MatchArmList { - ast_node_from_file_text(&format!("fn f() {{ match () {{{}}} }}", text)) - } - - pub fn from_arms(arms: impl Iterator) -> ast::MatchArmList { - let arms_str = arms.map(|arm| format!("\n {}", arm.syntax())).join(","); - Self::from_text(&format!("{},\n", arms_str)) - } -} - -impl AstBuilder { - fn from_text(text: &str) -> ast::WherePred { - ast_node_from_file_text(&format!("fn f() where {} {{ }}", text)) - } - - pub fn from_pieces( - path: ast::Path, - bounds: impl Iterator, - ) -> ast::WherePred { - let bounds = bounds.map(|b| b.syntax().to_string()).collect::>().join(" + "); - Self::from_text(&format!("{}: {}", path.syntax(), bounds)) - } -} - -impl AstBuilder { - fn from_text(text: &str) -> ast::WhereClause { - ast_node_from_file_text(&format!("fn f() where {} {{ }}", text)) - } - - pub fn from_predicates(preds: impl Iterator) -> ast::WhereClause { - let preds = preds.map(|p| p.syntax().to_string()).collect::>().join(", "); - Self::from_text(preds.as_str()) - } -} - -fn ast_node_from_file_text(text: &str) -> N { - let parse = SourceFile::parse(text); - let res = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); - res -} - -mod tokens { - use once_cell::sync::Lazy; - use ra_syntax::{AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken, T}; - - static SOURCE_FILE: Lazy> = Lazy::new(|| SourceFile::parse(",\n; ;")); - - pub(crate) fn comma() -> SyntaxToken { - SOURCE_FILE - .tree() - .syntax() - .descendants_with_tokens() - .filter_map(|it| it.into_token()) - .find(|it| it.kind() == T![,]) - .unwrap() - } - - pub(crate) fn single_space() -> SyntaxToken { - SOURCE_FILE - .tree() - .syntax() - .descendants_with_tokens() - .filter_map(|it| it.into_token()) - .find(|it| it.kind() == WHITESPACE && it.text().as_str() == " ") - .unwrap() - } - - #[allow(unused)] - pub(crate) fn single_newline() -> SyntaxToken { - SOURCE_FILE - .tree() - .syntax() - .descendants_with_tokens() - .filter_map(|it| it.into_token()) - .find(|it| it.kind() == WHITESPACE && it.text().as_str() == "\n") - .unwrap() - } - - pub(crate) struct WsBuilder(SourceFile); - - impl WsBuilder { - pub(crate) fn new(text: &str) -> WsBuilder { - WsBuilder(SourceFile::parse(text).ok().unwrap()) - } - pub(crate) fn ws(&self) -> SyntaxToken { - self.0.syntax().first_child_or_token().unwrap().into_token().unwrap() - } - } - -} diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 5e4e8bc92..71b017076 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -8,6 +8,7 @@ mod assist_ctx; mod marks; pub mod ast_editor; +pub mod ast_builder; use itertools::Itertools; diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs index 93e1e7c2d..30b95a215 100644 --- a/crates/ra_ide_api/src/diagnostics.rs +++ b/crates/ra_ide_api/src/diagnostics.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use hir::diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}; use itertools::Itertools; -use ra_assists::ast_editor::{AstBuilder, AstEditor}; +use ra_assists::{ast_builder::AstBuilder, ast_editor::AstEditor}; use ra_db::SourceDatabase; use ra_prof::profile; use ra_syntax::{ -- cgit v1.2.3