From fc34403018079ea053f26d0a31b7517053c7dd8c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 13 Aug 2020 17:33:38 +0200 Subject: Rename ra_assists -> assists --- .../src/handlers/replace_if_let_with_match.rs | 257 +++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 crates/assists/src/handlers/replace_if_let_with_match.rs (limited to 'crates/assists/src/handlers/replace_if_let_with_match.rs') diff --git a/crates/assists/src/handlers/replace_if_let_with_match.rs b/crates/assists/src/handlers/replace_if_let_with_match.rs new file mode 100644 index 000000000..79097621e --- /dev/null +++ b/crates/assists/src/handlers/replace_if_let_with_match.rs @@ -0,0 +1,257 @@ +use syntax::{ + ast::{ + self, + edit::{AstNodeEdit, IndentLevel}, + make, + }, + AstNode, +}; + +use crate::{ + utils::{unwrap_trivial_block, TryEnum}, + AssistContext, AssistId, AssistKind, Assists, +}; + +// Assist: replace_if_let_with_match +// +// Replaces `if let` with an else branch with a `match` expression. +// +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// <|>if let Action::Move { distance } = action { +// foo(distance) +// } else { +// bar() +// } +// } +// ``` +// -> +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// match action { +// Action::Move { distance } => foo(distance), +// _ => bar(), +// } +// } +// ``` +pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let if_expr: ast::IfExpr = ctx.find_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, + }; + + let target = if_expr.syntax().text_range(); + acc.add( + AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite), + "Replace with match", + target, + move |edit| { + let match_expr = { + let then_arm = { + let then_block = then_block.reset_indent().indent(IndentLevel(1)); + let then_expr = unwrap_trivial_block(then_block); + make::match_arm(vec![pat.clone()], then_expr) + }; + let else_arm = { + let pattern = ctx + .sema + .type_of_pat(&pat) + .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty)) + .map(|it| it.sad_pattern()) + .unwrap_or_else(|| make::wildcard_pat().into()); + let else_expr = unwrap_trivial_block(else_block); + make::match_arm(vec![pattern], else_expr) + }; + let match_expr = + make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])); + match_expr.indent(IndentLevel::from_node(if_expr.syntax())) + }; + + edit.replace_ast::(if_expr.into(), match_expr); + }, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::tests::{check_assist, check_assist_target}; + + #[test] + fn test_replace_if_let_with_match_unwraps_simple_expressions() { + check_assist( + replace_if_let_with_match, + r#" +impl VariantData { + pub fn is_struct(&self) -> bool { + if <|>let VariantData::Struct(..) = *self { + true + } else { + false + } + } +} "#, + r#" +impl VariantData { + pub fn is_struct(&self) -> bool { + match *self { + VariantData::Struct(..) => true, + _ => false, + } + } +} "#, + ) + } + + #[test] + fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() { + check_assist( + replace_if_let_with_match, + r#" +fn foo() { + if <|>let VariantData::Struct(..) = a { + bar( + 123 + ) + } else { + false + } +} "#, + r#" +fn foo() { + match a { + VariantData::Struct(..) => { + bar( + 123 + ) + } + _ => false, + } +} "#, + ) + } + + #[test] + fn replace_if_let_with_match_target() { + check_assist_target( + replace_if_let_with_match, + r#" +impl VariantData { + pub fn is_struct(&self) -> bool { + if <|>let VariantData::Struct(..) = *self { + true + } else { + false + } + } +} "#, + "if let VariantData::Struct(..) = *self { + true + } else { + false + }", + ); + } + + #[test] + fn special_case_option() { + check_assist( + replace_if_let_with_match, + r#" +enum Option { Some(T), None } +use Option::*; + +fn foo(x: Option) { + <|>if let Some(x) = x { + println!("{}", x) + } else { + println!("none") + } +} + "#, + r#" +enum Option { Some(T), None } +use Option::*; + +fn foo(x: Option) { + match x { + Some(x) => println!("{}", x), + None => println!("none"), + } +} + "#, + ); + } + + #[test] + fn special_case_result() { + check_assist( + replace_if_let_with_match, + r#" +enum Result { Ok(T), Err(E) } +use Result::*; + +fn foo(x: Result) { + <|>if let Ok(x) = x { + println!("{}", x) + } else { + println!("none") + } +} + "#, + r#" +enum Result { Ok(T), Err(E) } +use Result::*; + +fn foo(x: Result) { + match x { + Ok(x) => println!("{}", x), + Err(_) => println!("none"), + } +} + "#, + ); + } + + #[test] + fn nested_indent() { + check_assist( + replace_if_let_with_match, + r#" +fn main() { + if true { + <|>if let Ok(rel_path) = path.strip_prefix(root_path) { + let rel_path = RelativePathBuf::from_path(rel_path).ok()?; + Some((*id, rel_path)) + } else { + None + } + } +} +"#, + r#" +fn main() { + if true { + match path.strip_prefix(root_path) { + Ok(rel_path) => { + let rel_path = RelativePathBuf::from_path(rel_path).ok()?; + Some((*id, rel_path)) + } + _ => None, + } + } +} +"#, + ) + } +} -- cgit v1.2.3