1 use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
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.
21 // m::frobnicate$0() {}
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 path_res = ctx.sema.resolve_path(&path)?;
41 let def = match path_res {
42 PathResolution::Def(def) => def,
46 let current_module = ctx.sema.scope(path.syntax())?.module();
47 let target_module = def.module(ctx.db())?;
49 if def.visibility(ctx.db()).is_visible_from(ctx.db(), current_module.into()) {
53 let (offset, current_visibility, target, target_file, target_name) =
54 target_data_for_def(ctx.db(), def)?;
56 let missing_visibility =
57 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
59 let assist_label = match target_name {
60 None => format!("Change visibility to {}", missing_visibility),
61 Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
64 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
65 builder.edit_file(target_file);
66 match ctx.config.snippet_cap {
67 Some(cap) => match current_visibility {
68 Some(current_visibility) => builder.replace_snippet(
70 current_visibility.syntax().text_range(),
71 format!("$0{}", missing_visibility),
73 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
75 None => match current_visibility {
76 Some(current_visibility) => {
77 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
79 None => builder.insert(offset, format!("{} ", missing_visibility)),
85 fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
86 let record_field: ast::RecordExprField = ctx.find_node_at_offset()?;
87 let (record_field_def, _, _) = ctx.sema.resolve_record_field(&record_field)?;
89 let current_module = ctx.sema.scope(record_field.syntax())?.module();
90 let visibility = record_field_def.visibility(ctx.db());
91 if visibility.is_visible_from(ctx.db(), current_module.into()) {
95 let parent = record_field_def.parent_def(ctx.db());
96 let parent_name = parent.name(ctx.db());
97 let target_module = parent.module(ctx.db());
99 let in_file_source = record_field_def.source(ctx.db())?;
100 let (offset, current_visibility, target) = match in_file_source.value {
101 hir::FieldSource::Named(it) => {
103 (vis_offset(s), it.visibility(), s.text_range())
105 hir::FieldSource::Pos(it) => {
107 (vis_offset(s), it.visibility(), s.text_range())
111 let missing_visibility =
112 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
113 let target_file = in_file_source.file_id.original_file(ctx.db());
115 let target_name = record_field_def.name(ctx.db());
117 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
119 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
120 builder.edit_file(target_file);
121 match ctx.config.snippet_cap {
122 Some(cap) => match current_visibility {
123 Some(current_visibility) => builder.replace_snippet(
125 current_visibility.syntax().text_range(),
126 format!("$0{}", missing_visibility),
128 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
130 None => match current_visibility {
131 Some(current_visibility) => {
132 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
134 None => builder.insert(offset, format!("{} ", missing_visibility)),
140 fn target_data_for_def(
141 db: &dyn HirDatabase,
143 ) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
144 fn offset_target_and_file_id<S, Ast>(
145 db: &dyn HirDatabase,
147 ) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId)>
149 S: HasSource<Ast = Ast>,
150 Ast: AstNode + ast::HasVisibility,
152 let source = x.source(db)?;
153 let in_file_syntax = source.syntax();
154 let file_id = in_file_syntax.file_id;
155 let syntax = in_file_syntax.value;
156 let current_visibility = source.value.visibility();
161 file_id.original_file(db.upcast()),
166 let (offset, current_visibility, target, target_file) = match def {
167 hir::ModuleDef::Function(f) => {
168 target_name = Some(f.name(db));
169 offset_target_and_file_id(db, f)?
171 hir::ModuleDef::Adt(adt) => {
172 target_name = Some(adt.name(db));
174 hir::Adt::Struct(s) => offset_target_and_file_id(db, s)?,
175 hir::Adt::Union(u) => offset_target_and_file_id(db, u)?,
176 hir::Adt::Enum(e) => offset_target_and_file_id(db, e)?,
179 hir::ModuleDef::Const(c) => {
180 target_name = c.name(db);
181 offset_target_and_file_id(db, c)?
183 hir::ModuleDef::Static(s) => {
184 target_name = Some(s.name(db));
185 offset_target_and_file_id(db, s)?
187 hir::ModuleDef::Trait(t) => {
188 target_name = Some(t.name(db));
189 offset_target_and_file_id(db, t)?
191 hir::ModuleDef::TypeAlias(t) => {
192 target_name = Some(t.name(db));
193 offset_target_and_file_id(db, t)?
195 hir::ModuleDef::Module(m) => {
196 target_name = m.name(db);
197 let in_file_source = m.declaration_source(db)?;
198 let file_id = in_file_source.file_id.original_file(db.upcast());
199 let syntax = in_file_source.value.syntax();
200 (vis_offset(syntax), in_file_source.value.visibility(), syntax.text_range(), file_id)
203 hir::ModuleDef::Macro(_) => return None,
204 // Enum variants can't be private, we can't modify builtin types
205 hir::ModuleDef::Variant(_) | hir::ModuleDef::BuiltinType(_) => return None,
208 Some((offset, current_visibility, target, target_file, target_name))
213 use crate::tests::{check_assist, check_assist_not_applicable};
218 fn fix_visibility_of_fn() {
221 r"mod foo { fn foo() {} }
222 fn main() { foo::foo$0() } ",
223 r"mod foo { $0pub(crate) fn foo() {} }
224 fn main() { foo::foo() } ",
226 check_assist_not_applicable(
228 r"mod foo { pub fn foo() {} }
229 fn main() { foo::foo$0() } ",
234 fn fix_visibility_of_adt_in_submodule() {
237 r"mod foo { struct Foo; }
238 fn main() { foo::Foo$0 } ",
239 r"mod foo { $0pub(crate) struct Foo; }
240 fn main() { foo::Foo } ",
242 check_assist_not_applicable(
244 r"mod foo { pub struct Foo; }
245 fn main() { foo::Foo$0 } ",
249 r"mod foo { enum Foo; }
250 fn main() { foo::Foo$0 } ",
251 r"mod foo { $0pub(crate) enum Foo; }
252 fn main() { foo::Foo } ",
254 check_assist_not_applicable(
256 r"mod foo { pub enum Foo; }
257 fn main() { foo::Foo$0 } ",
261 r"mod foo { union Foo; }
262 fn main() { foo::Foo$0 } ",
263 r"mod foo { $0pub(crate) union Foo; }
264 fn main() { foo::Foo } ",
266 check_assist_not_applicable(
268 r"mod foo { pub union Foo; }
269 fn main() { foo::Foo$0 } ",
274 fn fix_visibility_of_adt_in_other_file() {
280 fn main() { foo::Foo$0 }
285 r"$0pub(crate) struct Foo;
291 fn fix_visibility_of_struct_field() {
294 r"mod foo { pub struct Foo { bar: (), } }
295 fn main() { foo::Foo { $0bar: () }; } ",
296 r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
297 fn main() { foo::Foo { bar: () }; } ",
304 fn main() { foo::Foo { $0bar: () }; }
306 pub struct Foo { bar: () }
308 r"pub struct Foo { $0pub(crate) bar: () }
311 check_assist_not_applicable(
313 r"mod foo { pub struct Foo { pub bar: (), } }
314 fn main() { foo::Foo { $0bar: () }; } ",
316 check_assist_not_applicable(
321 fn main() { foo::Foo { $0bar: () }; }
323 pub struct Foo { pub bar: () }
329 fn fix_visibility_of_enum_variant_field() {
330 // Enum variants, as well as their fields, always get the enum's visibility. In fact, rustc
331 // rejects any visibility specifiers on them, so this assist should never fire on them.
332 check_assist_not_applicable(
334 r"mod foo { pub enum Foo { Bar { bar: () } } }
335 fn main() { foo::Foo::Bar { $0bar: () }; } ",
337 check_assist_not_applicable(
342 fn main() { foo::Foo::Bar { $0bar: () }; }
344 pub enum Foo { Bar { bar: () } }
347 check_assist_not_applicable(
349 r"mod foo { pub struct Foo { pub bar: (), } }
350 fn main() { foo::Foo { $0bar: () }; } ",
352 check_assist_not_applicable(
357 fn main() { foo::Foo { $0bar: () }; }
359 pub struct Foo { pub bar: () }
365 fn fix_visibility_of_union_field() {
368 r"mod foo { pub union Foo { bar: (), } }
369 fn main() { foo::Foo { $0bar: () }; } ",
370 r"mod foo { pub union Foo { $0pub(crate) bar: (), } }
371 fn main() { foo::Foo { bar: () }; } ",
378 fn main() { foo::Foo { $0bar: () }; }
380 pub union Foo { bar: () }
382 r"pub union Foo { $0pub(crate) bar: () }
385 check_assist_not_applicable(
387 r"mod foo { pub union Foo { pub bar: (), } }
388 fn main() { foo::Foo { $0bar: () }; } ",
390 check_assist_not_applicable(
395 fn main() { foo::Foo { $0bar: () }; }
397 pub union Foo { pub bar: () }
403 fn fix_visibility_of_const() {
406 r"mod foo { const FOO: () = (); }
407 fn main() { foo::FOO$0 } ",
408 r"mod foo { $0pub(crate) const FOO: () = (); }
409 fn main() { foo::FOO } ",
411 check_assist_not_applicable(
413 r"mod foo { pub const FOO: () = (); }
414 fn main() { foo::FOO$0 } ",
419 fn fix_visibility_of_static() {
422 r"mod foo { static FOO: () = (); }
423 fn main() { foo::FOO$0 } ",
424 r"mod foo { $0pub(crate) static FOO: () = (); }
425 fn main() { foo::FOO } ",
427 check_assist_not_applicable(
429 r"mod foo { pub static FOO: () = (); }
430 fn main() { foo::FOO$0 } ",
435 fn fix_visibility_of_trait() {
438 r"mod foo { trait Foo { fn foo(&self) {} } }
439 fn main() { let x: &dyn foo::$0Foo; } ",
440 r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } }
441 fn main() { let x: &dyn foo::Foo; } ",
443 check_assist_not_applicable(
445 r"mod foo { pub trait Foo { fn foo(&self) {} } }
446 fn main() { let x: &dyn foo::Foo$0; } ",
451 fn fix_visibility_of_type_alias() {
454 r"mod foo { type Foo = (); }
455 fn main() { let x: foo::Foo$0; } ",
456 r"mod foo { $0pub(crate) type Foo = (); }
457 fn main() { let x: foo::Foo; } ",
459 check_assist_not_applicable(
461 r"mod foo { pub type Foo = (); }
462 fn main() { let x: foo::Foo$0; } ",
467 fn fix_visibility_of_module() {
470 r"mod foo { mod bar { fn bar() {} } }
471 fn main() { foo::bar$0::bar(); } ",
472 r"mod foo { $0pub(crate) mod bar { fn bar() {} } }
473 fn main() { foo::bar::bar(); } ",
481 fn main() { foo::bar$0::baz(); }
488 r"$0pub(crate) mod bar {
494 check_assist_not_applicable(
496 r"mod foo { pub mod bar { pub fn bar() {} } }
497 fn main() { foo::bar$0::bar(); } ",
502 fn fix_visibility_of_inline_module_in_other_file() {
508 fn main() { foo::bar$0::baz(); }
515 r"$0pub(crate) mod bar;
521 fn fix_visibility_of_module_declaration_in_other_file() {
527 fn main() { foo::bar$0>::baz(); }
534 r"$0pub(crate) mod bar {
542 fn adds_pub_when_target_is_in_another_crate() {
546 //- /main.rs crate:a deps:foo
548 //- /lib.rs crate:foo
557 fn replaces_pub_crate_with_pub() {
561 //- /main.rs crate:a deps:foo
563 //- /lib.rs crate:foo
564 pub(crate) struct Bar;
572 //- /main.rs crate:a deps:foo
574 foo::Foo { $0bar: () };
576 //- /lib.rs crate:foo
577 pub struct Foo { pub(crate) bar: () }
579 r"pub struct Foo { $0pub bar: () }
585 fn fix_visibility_of_reexport() {
586 // FIXME: broken test, this should fix visibility of the re-export
587 // rather than the struct.
593 mod bar { pub(super) struct Baz; }
600 mod bar { $0pub(crate) struct Baz; }