Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/generate_is_empty_from_len.rs')
| -rw-r--r-- | crates/ide-assists/src/handlers/generate_is_empty_from_len.rs | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs b/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs new file mode 100644 index 0000000000..1ad1cea43f --- /dev/null +++ b/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs @@ -0,0 +1,291 @@ +use hir::{known, HasSource, Name}; +use syntax::{ + ast::{self, HasName}, + AstNode, +}; + +use crate::{ + assist_context::{AssistContext, Assists}, + AssistId, AssistKind, +}; + +// Assist: generate_is_empty_from_len +// +// Generates is_empty implementation from the len method. +// +// ``` +// struct MyStruct { data: Vec<String> } +// +// impl MyStruct { +// #[must_use] +// p$0ub fn len(&self) -> usize { +// self.data.len() +// } +// } +// ``` +// -> +// ``` +// struct MyStruct { data: Vec<String> } +// +// impl MyStruct { +// #[must_use] +// pub fn len(&self) -> usize { +// self.data.len() +// } +// +// #[must_use] +// pub fn is_empty(&self) -> bool { +// self.len() == 0 +// } +// } +// ``` +pub(crate) fn generate_is_empty_from_len(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let fn_node = ctx.find_node_at_offset::<ast::Fn>()?; + let fn_name = fn_node.name()?; + + if fn_name.text() != "len" { + cov_mark::hit!(len_function_not_present); + return None; + } + + if fn_node.param_list()?.params().next().is_some() { + cov_mark::hit!(len_function_with_parameters); + return None; + } + + let impl_ = fn_node.syntax().ancestors().find_map(ast::Impl::cast)?; + let len_fn = get_impl_method(ctx, &impl_, &known::len)?; + if !len_fn.ret_type(ctx.sema.db).is_usize() { + cov_mark::hit!(len_fn_different_return_type); + return None; + } + + if get_impl_method(ctx, &impl_, &known::is_empty).is_some() { + cov_mark::hit!(is_empty_already_implemented); + return None; + } + + let node = len_fn.source(ctx.sema.db)?; + let range = node.syntax().value.text_range(); + + acc.add( + AssistId("generate_is_empty_from_len", AssistKind::Generate), + "Generate a is_empty impl from a len function", + range, + |builder| { + let code = r#" + + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + }"# + .to_string(); + builder.insert(range.end(), code) + }, + ) +} + +fn get_impl_method( + ctx: &AssistContext, + impl_: &ast::Impl, + fn_name: &Name, +) -> Option<hir::Function> { + let db = ctx.sema.db; + let impl_def: hir::Impl = ctx.sema.to_def(impl_)?; + + let scope = ctx.sema.scope(impl_.syntax())?; + let ty = impl_def.self_ty(db); + let traits_in_scope = scope.visible_traits(); + ty.iterate_method_candidates(db, &scope, &traits_in_scope, None, Some(fn_name), |func| { + Some(func) + }) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn len_function_not_present() { + cov_mark::check!(len_function_not_present); + check_assist_not_applicable( + generate_is_empty_from_len, + r#" +struct MyStruct { data: Vec<String> } + +impl MyStruct { + p$0ub fn test(&self) -> usize { + self.data.len() + } + } +"#, + ); + } + + #[test] + fn len_function_with_parameters() { + cov_mark::check!(len_function_with_parameters); + check_assist_not_applicable( + generate_is_empty_from_len, + r#" +struct MyStruct { data: Vec<String> } + +impl MyStruct { + #[must_use] + p$0ub fn len(&self, _i: bool) -> usize { + self.data.len() + } +} +"#, + ); + } + + #[test] + fn is_empty_already_implemented() { + cov_mark::check!(is_empty_already_implemented); + check_assist_not_applicable( + generate_is_empty_from_len, + r#" +struct MyStruct { data: Vec<String> } + +impl MyStruct { + #[must_use] + p$0ub fn len(&self) -> usize { + self.data.len() + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} +"#, + ); + } + + #[test] + fn len_fn_different_return_type() { + cov_mark::check!(len_fn_different_return_type); + check_assist_not_applicable( + generate_is_empty_from_len, + r#" +struct MyStruct { data: Vec<String> } + +impl MyStruct { + #[must_use] + p$0ub fn len(&self) -> u32 { + self.data.len() + } +} +"#, + ); + } + + #[test] + fn generate_is_empty() { + check_assist( + generate_is_empty_from_len, + r#" +struct MyStruct { data: Vec<String> } + +impl MyStruct { + #[must_use] + p$0ub fn len(&self) -> usize { + self.data.len() + } +} +"#, + r#" +struct MyStruct { data: Vec<String> } + +impl MyStruct { + #[must_use] + pub fn len(&self) -> usize { + self.data.len() + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} +"#, + ); + } + + #[test] + fn multiple_functions_in_impl() { + check_assist( + generate_is_empty_from_len, + r#" +struct MyStruct { data: Vec<String> } + +impl MyStruct { + #[must_use] + pub fn new() -> Self { + Self { data: 0 } + } + + #[must_use] + p$0ub fn len(&self) -> usize { + self.data.len() + } + + pub fn work(&self) -> Option<usize> { + + } +} +"#, + r#" +struct MyStruct { data: Vec<String> } + +impl MyStruct { + #[must_use] + pub fn new() -> Self { + Self { data: 0 } + } + + #[must_use] + pub fn len(&self) -> usize { + self.data.len() + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn work(&self) -> Option<usize> { + + } +} +"#, + ); + } + + #[test] + fn multiple_impls() { + check_assist_not_applicable( + generate_is_empty_from_len, + r#" +struct MyStruct { data: Vec<String> } + +impl MyStruct { + #[must_use] + p$0ub fn len(&self) -> usize { + self.data.len() + } +} + +impl MyStruct { + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} +"#, + ); + } +} |