1 use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
2 use ide_db::base_db::FileId;
4 ast::{self, VisibilityOwner},
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 let vis = target_module.visibility_of(ctx.db(), &def)?;
50 if vis.is_visible_from(ctx.db(), current_module.into()) {
54 let (offset, current_visibility, target, target_file, target_name) =
55 target_data_for_def(ctx.db(), def)?;
57 let missing_visibility =
58 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
60 let assist_label = match target_name {
61 None => format!("Change visibility to {}", missing_visibility),
62 Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
65 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
66 builder.edit_file(target_file);
67 match ctx.config.snippet_cap {
68 Some(cap) => match current_visibility {
69 Some(current_visibility) => builder.replace_snippet(
71 current_visibility.syntax().text_range(),
72 format!("$0{}", missing_visibility),
74 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
76 None => match current_visibility {
77 Some(current_visibility) => {
78 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
80 None => builder.insert(offset, format!("{} ", missing_visibility)),
86 fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
87 let record_field: ast::RecordExprField = ctx.find_node_at_offset()?;
88 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?;
90 let current_module = ctx.sema.scope(record_field.syntax()).module()?;
91 let visibility = record_field_def.visibility(ctx.db());
92 if visibility.is_visible_from(ctx.db(), current_module.into()) {
96 let parent = record_field_def.parent_def(ctx.db());
97 let parent_name = parent.name(ctx.db());
98 let target_module = parent.module(ctx.db());
100 let in_file_source = record_field_def.source(ctx.db())?;
101 let (offset, current_visibility, target) = match in_file_source.value {
102 hir::FieldSource::Named(it) => {
104 (vis_offset(s), it.visibility(), s.text_range())
106 hir::FieldSource::Pos(it) => {
108 (vis_offset(s), it.visibility(), s.text_range())
112 let missing_visibility =
113 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
114 let target_file = in_file_source.file_id.original_file(ctx.db());
116 let target_name = record_field_def.name(ctx.db());
118 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
120 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
121 builder.edit_file(target_file);
122 match ctx.config.snippet_cap {
123 Some(cap) => match current_visibility {
124 Some(current_visibility) => builder.replace_snippet(
126 current_visibility.syntax().text_range(),
127 format!("$0{}", missing_visibility),
129 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
131 None => match current_visibility {
132 Some(current_visibility) => {
133 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
135 None => builder.insert(offset, format!("{} ", missing_visibility)),
141 fn target_data_for_def(
142 db: &dyn HirDatabase,
144 ) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
145 fn offset_target_and_file_id<S, Ast>(
146 db: &dyn HirDatabase,
148 ) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId)>
150 S: HasSource<Ast = Ast>,
151 Ast: AstNode + ast::VisibilityOwner,
153 let source = x.source(db)?;
154 let in_file_syntax = source.syntax();
155 let file_id = in_file_syntax.file_id;
156 let syntax = in_file_syntax.value;
157 let current_visibility = source.value.visibility();
162 file_id.original_file(db.upcast()),
167 let (offset, current_visibility, target, target_file) = match def {
168 hir::ModuleDef::Function(f) => {
169 target_name = Some(f.name(db));
170 offset_target_and_file_id(db, f)?
172 hir::ModuleDef::Adt(adt) => {
173 target_name = Some(adt.name(db));
175 hir::Adt::Struct(s) => offset_target_and_file_id(db, s)?,
176 hir::Adt::Union(u) => offset_target_and_file_id(db, u)?,
177 hir::Adt::Enum(e) => offset_target_and_file_id(db, e)?,
180 hir::ModuleDef::Const(c) => {
181 target_name = c.name(db);
182 offset_target_and_file_id(db, c)?
184 hir::ModuleDef::Static(s) => {
185 target_name = s.name(db);
186 offset_target_and_file_id(db, s)?
188 hir::ModuleDef::Trait(t) => {
189 target_name = Some(t.name(db));
190 offset_target_and_file_id(db, t)?
192 hir::ModuleDef::TypeAlias(t) => {
193 target_name = Some(t.name(db));
194 offset_target_and_file_id(db, t)?
196 hir::ModuleDef::Module(m) => {
197 target_name = m.name(db);
198 let in_file_source = m.declaration_source(db)?;
199 let file_id = in_file_source.file_id.original_file(db.upcast());
200 let syntax = in_file_source.value.syntax();
201 (vis_offset(syntax), in_file_source.value.visibility(), syntax.text_range(), file_id)
203 // Enum variants can't be private, we can't modify builtin types
204 hir::ModuleDef::Variant(_) | hir::ModuleDef::BuiltinType(_) => return None,
207 Some((offset, current_visibility, target, target_file, target_name))
212 use crate::tests::{check_assist, check_assist_not_applicable};
217 fn fix_visibility_of_fn() {
220 r"mod foo { fn foo() {} }
221 fn main() { foo::foo$0() } ",
222 r"mod foo { $0pub(crate) fn foo() {} }
223 fn main() { foo::foo() } ",
225 check_assist_not_applicable(
227 r"mod foo { pub fn foo() {} }
228 fn main() { foo::foo$0() } ",
233 fn fix_visibility_of_adt_in_submodule() {
236 r"mod foo { struct Foo; }
237 fn main() { foo::Foo$0 } ",
238 r"mod foo { $0pub(crate) struct Foo; }
239 fn main() { foo::Foo } ",
241 check_assist_not_applicable(
243 r"mod foo { pub struct Foo; }
244 fn main() { foo::Foo$0 } ",
248 r"mod foo { enum Foo; }
249 fn main() { foo::Foo$0 } ",
250 r"mod foo { $0pub(crate) enum Foo; }
251 fn main() { foo::Foo } ",
253 check_assist_not_applicable(
255 r"mod foo { pub enum Foo; }
256 fn main() { foo::Foo$0 } ",
260 r"mod foo { union Foo; }
261 fn main() { foo::Foo$0 } ",
262 r"mod foo { $0pub(crate) union Foo; }
263 fn main() { foo::Foo } ",
265 check_assist_not_applicable(
267 r"mod foo { pub union Foo; }
268 fn main() { foo::Foo$0 } ",
273 fn fix_visibility_of_adt_in_other_file() {
279 fn main() { foo::Foo$0 }
284 r"$0pub(crate) struct Foo;
290 fn fix_visibility_of_struct_field() {
293 r"mod foo { pub struct Foo { bar: (), } }
294 fn main() { foo::Foo { $0bar: () }; } ",
295 r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
296 fn main() { foo::Foo { bar: () }; } ",
303 fn main() { foo::Foo { $0bar: () }; }
305 pub struct Foo { bar: () }
307 r"pub struct Foo { $0pub(crate) bar: () }
310 check_assist_not_applicable(
312 r"mod foo { pub struct Foo { pub bar: (), } }
313 fn main() { foo::Foo { $0bar: () }; } ",
315 check_assist_not_applicable(
320 fn main() { foo::Foo { $0bar: () }; }
322 pub struct Foo { pub bar: () }
328 fn fix_visibility_of_enum_variant_field() {
329 // Enum variants, as well as their fields, always get the enum's visibility. In fact, rustc
330 // rejects any visibility specifiers on them, so this assist should never fire on them.
331 check_assist_not_applicable(
333 r"mod foo { pub enum Foo { Bar { bar: () } } }
334 fn main() { foo::Foo::Bar { $0bar: () }; } ",
336 check_assist_not_applicable(
341 fn main() { foo::Foo::Bar { $0bar: () }; }
343 pub enum Foo { Bar { bar: () } }
346 check_assist_not_applicable(
348 r"mod foo { pub struct Foo { pub bar: (), } }
349 fn main() { foo::Foo { $0bar: () }; } ",
351 check_assist_not_applicable(
356 fn main() { foo::Foo { $0bar: () }; }
358 pub struct Foo { pub bar: () }
365 // FIXME reenable this test when `Semantics::resolve_record_field` works with union fields
366 fn fix_visibility_of_union_field() {
369 r"mod foo { pub union Foo { bar: (), } }
370 fn main() { foo::Foo { $0bar: () }; } ",
371 r"mod foo { pub union Foo { $0pub(crate) bar: (), } }
372 fn main() { foo::Foo { bar: () }; } ",
379 fn main() { foo::Foo { $0bar: () }; }
381 pub union Foo { bar: () }
383 r"pub union Foo { $0pub(crate) bar: () }
386 check_assist_not_applicable(
388 r"mod foo { pub union Foo { pub bar: (), } }
389 fn main() { foo::Foo { $0bar: () }; } ",
391 check_assist_not_applicable(
396 fn main() { foo::Foo { $0bar: () }; }
398 pub union Foo { pub bar: () }
404 fn fix_visibility_of_const() {
407 r"mod foo { const FOO: () = (); }
408 fn main() { foo::FOO$0 } ",
409 r"mod foo { $0pub(crate) const FOO: () = (); }
410 fn main() { foo::FOO } ",
412 check_assist_not_applicable(
414 r"mod foo { pub const FOO: () = (); }
415 fn main() { foo::FOO$0 } ",
420 fn fix_visibility_of_static() {
423 r"mod foo { static FOO: () = (); }
424 fn main() { foo::FOO$0 } ",
425 r"mod foo { $0pub(crate) static FOO: () = (); }
426 fn main() { foo::FOO } ",
428 check_assist_not_applicable(
430 r"mod foo { pub static FOO: () = (); }
431 fn main() { foo::FOO$0 } ",
436 fn fix_visibility_of_trait() {
439 r"mod foo { trait Foo { fn foo(&self) {} } }
440 fn main() { let x: &dyn foo::$0Foo; } ",
441 r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } }
442 fn main() { let x: &dyn foo::Foo; } ",
444 check_assist_not_applicable(
446 r"mod foo { pub trait Foo { fn foo(&self) {} } }
447 fn main() { let x: &dyn foo::Foo$0; } ",
452 fn fix_visibility_of_type_alias() {
455 r"mod foo { type Foo = (); }
456 fn main() { let x: foo::Foo$0; } ",
457 r"mod foo { $0pub(crate) type Foo = (); }
458 fn main() { let x: foo::Foo; } ",
460 check_assist_not_applicable(
462 r"mod foo { pub type Foo = (); }
463 fn main() { let x: foo::Foo$0; } ",
468 fn fix_visibility_of_module() {
471 r"mod foo { mod bar { fn bar() {} } }
472 fn main() { foo::bar$0::bar(); } ",
473 r"mod foo { $0pub(crate) mod bar { fn bar() {} } }
474 fn main() { foo::bar::bar(); } ",
482 fn main() { foo::bar$0::baz(); }
489 r"$0pub(crate) mod bar {
495 check_assist_not_applicable(
497 r"mod foo { pub mod bar { pub fn bar() {} } }
498 fn main() { foo::bar$0::bar(); } ",
503 fn fix_visibility_of_inline_module_in_other_file() {
509 fn main() { foo::bar$0::baz(); }
516 r"$0pub(crate) mod bar;
522 fn fix_visibility_of_module_declaration_in_other_file() {
528 fn main() { foo::bar$0>::baz(); }
535 r"$0pub(crate) mod bar {
543 fn adds_pub_when_target_is_in_another_crate() {
547 //- /main.rs crate:a deps:foo
549 //- /lib.rs crate:foo
558 fn replaces_pub_crate_with_pub() {
562 //- /main.rs crate:a deps:foo
564 //- /lib.rs crate:foo
565 pub(crate) struct Bar;
573 //- /main.rs crate:a deps:foo
575 foo::Foo { $0bar: () };
577 //- /lib.rs crate:foo
578 pub struct Foo { pub(crate) bar: () }
580 r"pub struct Foo { $0pub bar: () }
587 // FIXME handle reexports properly
588 fn fix_visibility_of_reexport() {
594 mod bar { pub(super) struct Baz; }
600 $0pub(crate) use bar::Baz;
601 mod bar { pub(super) struct Baz; }