1 use hir::{db::HirDatabase, HasSource, HasVisibility, ModuleDef, PathResolution, ScopeDef};
2 use ide_db::base_db::FileId;
4 ast::{self, HasVisibility as _},
5 AstNode, TextRange, TextSize,
8 use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
10 // FIXME: this really should be a fix for diagnostic, rather than an assist.
12 // Assist: fix_visibility
14 // Makes inaccessible item public.
27 // $0pub(crate) fn frobnicate() {}
33 pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
34 add_vis_to_referenced_module_def(acc, ctx)
35 .or_else(|| add_vis_to_referenced_record_field(acc, ctx))
38 fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
39 let path: ast::Path = ctx.find_node_at_offset()?;
40 let qualifier = path.qualifier()?;
41 let name_ref = path.segment()?.name_ref()?;
42 let qualifier_res = ctx.sema.resolve_path(&qualifier)?;
43 let PathResolution::Def(ModuleDef::Module(module)) = qualifier_res else { return None; };
45 .scope(ctx.db(), None)
47 .find(|(name, _)| name.to_smol_str() == name_ref.text().as_str())?;
48 let ScopeDef::ModuleDef(def) = def else { return None; };
50 let current_module = ctx.sema.scope(path.syntax())?.module();
51 let target_module = def.module(ctx.db())?;
53 if def.visibility(ctx.db()).is_visible_from(ctx.db(), current_module.into()) {
57 let (offset, current_visibility, target, target_file, target_name) =
58 target_data_for_def(ctx.db(), def)?;
60 let missing_visibility =
61 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
63 let assist_label = match target_name {
64 None => format!("Change visibility to {missing_visibility}"),
65 Some(name) => format!("Change visibility of {name} to {missing_visibility}"),
68 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
69 builder.edit_file(target_file);
70 match ctx.config.snippet_cap {
71 Some(cap) => match current_visibility {
72 Some(current_visibility) => builder.replace_snippet(
74 current_visibility.syntax().text_range(),
75 format!("$0{missing_visibility}"),
77 None => builder.insert_snippet(cap, offset, format!("$0{missing_visibility} ")),
79 None => match current_visibility {
80 Some(current_visibility) => {
81 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
83 None => builder.insert(offset, format!("{missing_visibility} ")),
89 fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
90 let record_field: ast::RecordExprField = ctx.find_node_at_offset()?;
91 let (record_field_def, _, _) = ctx.sema.resolve_record_field(&record_field)?;
93 let current_module = ctx.sema.scope(record_field.syntax())?.module();
94 let visibility = record_field_def.visibility(ctx.db());
95 if visibility.is_visible_from(ctx.db(), current_module.into()) {
99 let parent = record_field_def.parent_def(ctx.db());
100 let parent_name = parent.name(ctx.db());
101 let target_module = parent.module(ctx.db());
103 let in_file_source = record_field_def.source(ctx.db())?;
104 let (offset, current_visibility, target) = match in_file_source.value {
105 hir::FieldSource::Named(it) => {
107 (vis_offset(s), it.visibility(), s.text_range())
109 hir::FieldSource::Pos(it) => {
111 (vis_offset(s), it.visibility(), s.text_range())
115 let missing_visibility =
116 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
117 let target_file = in_file_source.file_id.original_file(ctx.db());
119 let target_name = record_field_def.name(ctx.db());
121 format!("Change visibility of {parent_name}.{target_name} to {missing_visibility}");
123 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
124 builder.edit_file(target_file);
125 match ctx.config.snippet_cap {
126 Some(cap) => match current_visibility {
127 Some(current_visibility) => builder.replace_snippet(
129 current_visibility.syntax().text_range(),
130 format!("$0{missing_visibility}"),
132 None => builder.insert_snippet(cap, offset, format!("$0{missing_visibility} ")),
134 None => match current_visibility {
135 Some(current_visibility) => {
136 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
138 None => builder.insert(offset, format!("{missing_visibility} ")),
144 fn target_data_for_def(
145 db: &dyn HirDatabase,
147 ) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
148 fn offset_target_and_file_id<S, Ast>(
149 db: &dyn HirDatabase,
151 ) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId)>
153 S: HasSource<Ast = Ast>,
154 Ast: AstNode + ast::HasVisibility,
156 let source = x.source(db)?;
157 let in_file_syntax = source.syntax();
158 let file_id = in_file_syntax.file_id;
159 let syntax = in_file_syntax.value;
160 let current_visibility = source.value.visibility();
165 file_id.original_file(db.upcast()),
170 let (offset, current_visibility, target, target_file) = match def {
171 hir::ModuleDef::Function(f) => {
172 target_name = Some(f.name(db));
173 offset_target_and_file_id(db, f)?
175 hir::ModuleDef::Adt(adt) => {
176 target_name = Some(adt.name(db));
178 hir::Adt::Struct(s) => offset_target_and_file_id(db, s)?,
179 hir::Adt::Union(u) => offset_target_and_file_id(db, u)?,
180 hir::Adt::Enum(e) => offset_target_and_file_id(db, e)?,
183 hir::ModuleDef::Const(c) => {
184 target_name = c.name(db);
185 offset_target_and_file_id(db, c)?
187 hir::ModuleDef::Static(s) => {
188 target_name = Some(s.name(db));
189 offset_target_and_file_id(db, s)?
191 hir::ModuleDef::Trait(t) => {
192 target_name = Some(t.name(db));
193 offset_target_and_file_id(db, t)?
195 hir::ModuleDef::TypeAlias(t) => {
196 target_name = Some(t.name(db));
197 offset_target_and_file_id(db, t)?
199 hir::ModuleDef::Module(m) => {
200 target_name = m.name(db);
201 let in_file_source = m.declaration_source(db)?;
202 let file_id = in_file_source.file_id.original_file(db.upcast());
203 let syntax = in_file_source.value.syntax();
204 (vis_offset(syntax), in_file_source.value.visibility(), syntax.text_range(), file_id)
207 hir::ModuleDef::Macro(_) => return None,
208 // Enum variants can't be private, we can't modify builtin types
209 hir::ModuleDef::Variant(_) | hir::ModuleDef::BuiltinType(_) => return None,
212 Some((offset, current_visibility, target, target_file, target_name))
217 use crate::tests::{check_assist, check_assist_not_applicable};
222 fn fix_visibility_of_fn() {
225 r"mod foo { fn foo() {} }
226 fn main() { foo::foo$0() } ",
227 r"mod foo { $0pub(crate) fn foo() {} }
228 fn main() { foo::foo() } ",
230 check_assist_not_applicable(
232 r"mod foo { pub fn foo() {} }
233 fn main() { foo::foo$0() } ",
238 fn fix_visibility_of_adt_in_submodule() {
241 r"mod foo { struct Foo; }
242 fn main() { foo::Foo$0 } ",
243 r"mod foo { $0pub(crate) struct Foo; }
244 fn main() { foo::Foo } ",
246 check_assist_not_applicable(
248 r"mod foo { pub struct Foo; }
249 fn main() { foo::Foo$0 } ",
253 r"mod foo { enum Foo; }
254 fn main() { foo::Foo$0 } ",
255 r"mod foo { $0pub(crate) enum Foo; }
256 fn main() { foo::Foo } ",
258 check_assist_not_applicable(
260 r"mod foo { pub enum Foo; }
261 fn main() { foo::Foo$0 } ",
265 r"mod foo { union Foo; }
266 fn main() { foo::Foo$0 } ",
267 r"mod foo { $0pub(crate) union Foo; }
268 fn main() { foo::Foo } ",
270 check_assist_not_applicable(
272 r"mod foo { pub union Foo; }
273 fn main() { foo::Foo$0 } ",
278 fn fix_visibility_of_adt_in_other_file() {
284 fn main() { foo::Foo$0 }
289 r"$0pub(crate) struct Foo;
295 fn fix_visibility_of_struct_field() {
298 r"mod foo { pub struct Foo { bar: (), } }
299 fn main() { foo::Foo { $0bar: () }; } ",
300 r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
301 fn main() { foo::Foo { bar: () }; } ",
308 fn main() { foo::Foo { $0bar: () }; }
310 pub struct Foo { bar: () }
312 r"pub struct Foo { $0pub(crate) bar: () }
315 check_assist_not_applicable(
317 r"mod foo { pub struct Foo { pub bar: (), } }
318 fn main() { foo::Foo { $0bar: () }; } ",
320 check_assist_not_applicable(
325 fn main() { foo::Foo { $0bar: () }; }
327 pub struct Foo { pub bar: () }
333 fn fix_visibility_of_enum_variant_field() {
334 // Enum variants, as well as their fields, always get the enum's visibility. In fact, rustc
335 // rejects any visibility specifiers on them, so this assist should never fire on them.
336 check_assist_not_applicable(
338 r"mod foo { pub enum Foo { Bar { bar: () } } }
339 fn main() { foo::Foo::Bar { $0bar: () }; } ",
341 check_assist_not_applicable(
346 fn main() { foo::Foo::Bar { $0bar: () }; }
348 pub enum Foo { Bar { bar: () } }
351 check_assist_not_applicable(
353 r"mod foo { pub struct Foo { pub bar: (), } }
354 fn main() { foo::Foo { $0bar: () }; } ",
356 check_assist_not_applicable(
361 fn main() { foo::Foo { $0bar: () }; }
363 pub struct Foo { pub bar: () }
369 fn fix_visibility_of_union_field() {
372 r"mod foo { pub union Foo { bar: (), } }
373 fn main() { foo::Foo { $0bar: () }; } ",
374 r"mod foo { pub union Foo { $0pub(crate) bar: (), } }
375 fn main() { foo::Foo { bar: () }; } ",
382 fn main() { foo::Foo { $0bar: () }; }
384 pub union Foo { bar: () }
386 r"pub union Foo { $0pub(crate) bar: () }
389 check_assist_not_applicable(
391 r"mod foo { pub union Foo { pub bar: (), } }
392 fn main() { foo::Foo { $0bar: () }; } ",
394 check_assist_not_applicable(
399 fn main() { foo::Foo { $0bar: () }; }
401 pub union Foo { pub bar: () }
407 fn fix_visibility_of_const() {
410 r"mod foo { const FOO: () = (); }
411 fn main() { foo::FOO$0 } ",
412 r"mod foo { $0pub(crate) const FOO: () = (); }
413 fn main() { foo::FOO } ",
415 check_assist_not_applicable(
417 r"mod foo { pub const FOO: () = (); }
418 fn main() { foo::FOO$0 } ",
423 fn fix_visibility_of_static() {
426 r"mod foo { static FOO: () = (); }
427 fn main() { foo::FOO$0 } ",
428 r"mod foo { $0pub(crate) static FOO: () = (); }
429 fn main() { foo::FOO } ",
431 check_assist_not_applicable(
433 r"mod foo { pub static FOO: () = (); }
434 fn main() { foo::FOO$0 } ",
439 fn fix_visibility_of_trait() {
442 r"mod foo { trait Foo { fn foo(&self) {} } }
443 fn main() { let x: &dyn foo::$0Foo; } ",
444 r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } }
445 fn main() { let x: &dyn foo::Foo; } ",
447 check_assist_not_applicable(
449 r"mod foo { pub trait Foo { fn foo(&self) {} } }
450 fn main() { let x: &dyn foo::Foo$0; } ",
455 fn fix_visibility_of_type_alias() {
458 r"mod foo { type Foo = (); }
459 fn main() { let x: foo::Foo$0; } ",
460 r"mod foo { $0pub(crate) type Foo = (); }
461 fn main() { let x: foo::Foo; } ",
463 check_assist_not_applicable(
465 r"mod foo { pub type Foo = (); }
466 fn main() { let x: foo::Foo$0; } ",
471 fn fix_visibility_of_module() {
474 r"mod foo { mod bar { fn bar() {} } }
475 fn main() { foo::bar$0::bar(); } ",
476 r"mod foo { $0pub(crate) mod bar { fn bar() {} } }
477 fn main() { foo::bar::bar(); } ",
485 fn main() { foo::bar$0::baz(); }
492 r"$0pub(crate) mod bar {
498 check_assist_not_applicable(
500 r"mod foo { pub mod bar { pub fn bar() {} } }
501 fn main() { foo::bar$0::bar(); } ",
506 fn fix_visibility_of_inline_module_in_other_file() {
512 fn main() { foo::bar$0::baz(); }
519 r"$0pub(crate) mod bar;
525 fn fix_visibility_of_module_declaration_in_other_file() {
531 fn main() { foo::bar$0>::baz(); }
538 r"$0pub(crate) mod bar {
546 fn adds_pub_when_target_is_in_another_crate() {
550 //- /main.rs crate:a deps:foo
552 //- /lib.rs crate:foo
561 fn replaces_pub_crate_with_pub() {
565 //- /main.rs crate:a deps:foo
567 //- /lib.rs crate:foo
568 pub(crate) struct Bar;
576 //- /main.rs crate:a deps:foo
578 foo::Foo { $0bar: () };
580 //- /lib.rs crate:foo
581 pub struct Foo { pub(crate) bar: () }
583 r"pub struct Foo { $0pub bar: () }
589 fn fix_visibility_of_reexport() {
590 // FIXME: broken test, this should fix visibility of the re-export
591 // rather than the struct.
597 mod bar { pub(super) struct Baz; }
604 mod bar { $0pub(crate) struct Baz; }