Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/change_visibility.rs')
-rw-r--r--crates/ide-assists/src/handlers/change_visibility.rs216
1 files changed, 216 insertions, 0 deletions
diff --git a/crates/ide-assists/src/handlers/change_visibility.rs b/crates/ide-assists/src/handlers/change_visibility.rs
new file mode 100644
index 0000000000..5f15e923ad
--- /dev/null
+++ b/crates/ide-assists/src/handlers/change_visibility.rs
@@ -0,0 +1,216 @@
+use syntax::{
+ ast::{self, HasName, HasVisibility},
+ AstNode,
+ SyntaxKind::{
+ CONST, ENUM, FN, MACRO_DEF, MODULE, STATIC, STRUCT, TRAIT, TYPE_ALIAS, USE, VISIBILITY,
+ },
+ T,
+};
+
+use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: change_visibility
+//
+// Adds or changes existing visibility specifier.
+//
+// ```
+// $0fn frobnicate() {}
+// ```
+// ->
+// ```
+// pub(crate) fn frobnicate() {}
+// ```
+pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+ if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
+ return change_vis(acc, vis);
+ }
+ add_vis(acc, ctx)
+}
+
+fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+ let item_keyword = ctx.token_at_offset().find(|leaf| {
+ matches!(
+ leaf.kind(),
+ T![const]
+ | T![static]
+ | T![fn]
+ | T![mod]
+ | T![struct]
+ | T![enum]
+ | T![trait]
+ | T![type]
+ | T![use]
+ | T![macro]
+ )
+ });
+
+ let (offset, target) = if let Some(keyword) = item_keyword {
+ let parent = keyword.parent()?;
+ let def_kws =
+ vec![CONST, STATIC, TYPE_ALIAS, FN, MODULE, STRUCT, ENUM, TRAIT, USE, MACRO_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 if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() {
+ let field = field_name.syntax().ancestors().find_map(ast::RecordField::cast)?;
+ if field.name()? != field_name {
+ cov_mark::hit!(change_visibility_field_false_positive);
+ return None;
+ }
+ if field.visibility().is_some() {
+ return None;
+ }
+ (vis_offset(field.syntax()), field_name.syntax().text_range())
+ } else if let Some(field) = ctx.find_node_at_offset::<ast::TupleField>() {
+ if field.visibility().is_some() {
+ return None;
+ }
+ (vis_offset(field.syntax()), field.syntax().text_range())
+ } else {
+ return None;
+ };
+
+ acc.add(
+ AssistId("change_visibility", AssistKind::RefactorRewrite),
+ "Change visibility to pub(crate)",
+ target,
+ |edit| {
+ edit.insert(offset, "pub(crate) ");
+ },
+ )
+}
+
+fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
+ if vis.syntax().text() == "pub" {
+ let target = vis.syntax().text_range();
+ return acc.add(
+ AssistId("change_visibility", AssistKind::RefactorRewrite),
+ "Change Visibility to pub(crate)",
+ target,
+ |edit| {
+ edit.replace(vis.syntax().text_range(), "pub(crate)");
+ },
+ );
+ }
+ if vis.syntax().text() == "pub(crate)" {
+ let target = vis.syntax().text_range();
+ return acc.add(
+ AssistId("change_visibility", AssistKind::RefactorRewrite),
+ "Change visibility to pub",
+ target,
+ |edit| {
+ edit.replace(vis.syntax().text_range(), "pub");
+ },
+ );
+ }
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+ use super::*;
+
+ #[test]
+ fn change_visibility_adds_pub_crate_to_items() {
+ check_assist(change_visibility, "$0fn foo() {}", "pub(crate) fn foo() {}");
+ check_assist(change_visibility, "f$0n foo() {}", "pub(crate) fn foo() {}");
+ check_assist(change_visibility, "$0struct Foo {}", "pub(crate) struct Foo {}");
+ check_assist(change_visibility, "$0mod foo {}", "pub(crate) mod foo {}");
+ check_assist(change_visibility, "$0trait Foo {}", "pub(crate) trait Foo {}");
+ check_assist(change_visibility, "m$0od {}", "pub(crate) mod {}");
+ check_assist(change_visibility, "unsafe f$0n foo() {}", "pub(crate) unsafe fn foo() {}");
+ check_assist(change_visibility, "$0macro foo() {}", "pub(crate) macro foo() {}");
+ check_assist(change_visibility, "$0use foo;", "pub(crate) use foo;");
+ }
+
+ #[test]
+ fn change_visibility_works_with_struct_fields() {
+ check_assist(
+ change_visibility,
+ r"struct S { $0field: u32 }",
+ r"struct S { pub(crate) field: u32 }",
+ );
+ check_assist(change_visibility, r"struct S ( $0u32 )", r"struct S ( pub(crate) u32 )");
+ }
+
+ #[test]
+ fn change_visibility_field_false_positive() {
+ cov_mark::check!(change_visibility_field_false_positive);
+ check_assist_not_applicable(
+ change_visibility,
+ r"struct S { field: [(); { let $0x = ();}] }",
+ )
+ }
+
+ #[test]
+ fn change_visibility_pub_to_pub_crate() {
+ check_assist(change_visibility, "$0pub fn foo() {}", "pub(crate) fn foo() {}")
+ }
+
+ #[test]
+ fn change_visibility_pub_crate_to_pub() {
+ check_assist(change_visibility, "$0pub(crate) fn foo() {}", "pub fn foo() {}")
+ }
+
+ #[test]
+ fn change_visibility_const() {
+ check_assist(change_visibility, "$0const FOO = 3u8;", "pub(crate) const FOO = 3u8;");
+ }
+
+ #[test]
+ fn change_visibility_static() {
+ check_assist(change_visibility, "$0static FOO = 3u8;", "pub(crate) static FOO = 3u8;");
+ }
+
+ #[test]
+ fn change_visibility_type_alias() {
+ check_assist(change_visibility, "$0type T = ();", "pub(crate) type T = ();");
+ }
+
+ #[test]
+ fn change_visibility_handles_comment_attrs() {
+ check_assist(
+ change_visibility,
+ r"
+ /// docs
+
+ // comments
+
+ #[derive(Debug)]
+ $0struct Foo;
+ ",
+ r"
+ /// docs
+
+ // comments
+
+ #[derive(Debug)]
+ pub(crate) struct Foo;
+ ",
+ )
+ }
+
+ #[test]
+ fn not_applicable_for_enum_variants() {
+ check_assist_not_applicable(
+ change_visibility,
+ r"mod foo { pub enum Foo {Foo1} }
+ fn main() { foo::Foo::Foo1$0 } ",
+ );
+ }
+
+ #[test]
+ fn change_visibility_target() {
+ check_assist_target(change_visibility, "$0fn foo() {}", "fn");
+ check_assist_target(change_visibility, "pub(crate)$0 fn foo() {}", "pub(crate)");
+ check_assist_target(change_visibility, "struct S { $0field: u32 }", "field");
+ }
+}