1 use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
5 SyntaxKind::{ATTR, COMMENT, WHITESPACE},
6 SyntaxNode, TextRange, TextSize,
9 use crate::{AssistContext, AssistId, Assists};
11 // FIXME: this really should be a fix for diagnostic, rather than an assist.
13 // Assist: fix_visibility
15 // Makes inaccessible item public.
22 // m::frobnicate<|>() {}
28 // $0pub(crate) fn frobnicate() {}
34 pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35 add_vis_to_referenced_module_def(acc, ctx)
36 .or_else(|| add_vis_to_referenced_record_field(acc, ctx))
39 fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let path: ast::Path = ctx.find_node_at_offset()?;
41 let path_res = ctx.sema.resolve_path(&path)?;
42 let def = match path_res {
43 PathResolution::Def(def) => def,
47 let current_module = ctx.sema.scope(&path.syntax()).module()?;
48 let target_module = def.module(ctx.db)?;
50 let vis = target_module.visibility_of(ctx.db, &def)?;
51 if vis.is_visible_from(ctx.db, current_module.into()) {
55 let (offset, target, target_file, target_name) = 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"), assist_label, target, |builder| {
66 builder.edit_file(target_file);
67 match ctx.config.snippet_cap {
68 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
69 None => builder.insert(offset, format!("{} ", missing_visibility)),
74 fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
75 let record_field: ast::RecordField = ctx.find_node_at_offset()?;
76 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?;
78 let current_module = ctx.sema.scope(record_field.syntax()).module()?;
79 let visibility = record_field_def.visibility(ctx.db);
80 if visibility.is_visible_from(ctx.db, current_module.into()) {
84 let parent = record_field_def.parent_def(ctx.db);
85 let parent_name = parent.name(ctx.db);
86 let target_module = parent.module(ctx.db);
88 let in_file_source = record_field_def.source(ctx.db);
89 let (offset, target) = match in_file_source.value {
90 hir::FieldSource::Named(it) => {
92 (vis_offset(s), s.text_range())
94 hir::FieldSource::Pos(it) => {
96 (vis_offset(s), s.text_range())
100 let missing_visibility =
101 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
102 let target_file = in_file_source.file_id.original_file(ctx.db);
104 let target_name = record_field_def.name(ctx.db);
106 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
108 acc.add(AssistId("fix_visibility"), assist_label, target, |builder| {
109 builder.edit_file(target_file);
110 match ctx.config.snippet_cap {
111 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
112 None => builder.insert(offset, format!("{} ", missing_visibility)),
117 fn target_data_for_def(
118 db: &dyn HirDatabase,
120 ) -> Option<(TextSize, TextRange, FileId, Option<hir::Name>)> {
121 fn offset_target_and_file_id<S, Ast>(
122 db: &dyn HirDatabase,
124 ) -> (TextSize, TextRange, FileId)
126 S: HasSource<Ast = Ast>,
129 let source = x.source(db);
130 let in_file_syntax = source.syntax();
131 let file_id = in_file_syntax.file_id;
132 let syntax = in_file_syntax.value;
133 (vis_offset(syntax), syntax.text_range(), file_id.original_file(db.upcast()))
137 let (offset, target, target_file) = match def {
138 hir::ModuleDef::Function(f) => {
139 target_name = Some(f.name(db));
140 offset_target_and_file_id(db, f)
142 hir::ModuleDef::Adt(adt) => {
143 target_name = Some(adt.name(db));
145 hir::Adt::Struct(s) => offset_target_and_file_id(db, s),
146 hir::Adt::Union(u) => offset_target_and_file_id(db, u),
147 hir::Adt::Enum(e) => offset_target_and_file_id(db, e),
150 hir::ModuleDef::Const(c) => {
151 target_name = c.name(db);
152 offset_target_and_file_id(db, c)
154 hir::ModuleDef::Static(s) => {
155 target_name = s.name(db);
156 offset_target_and_file_id(db, s)
158 hir::ModuleDef::Trait(t) => {
159 target_name = Some(t.name(db));
160 offset_target_and_file_id(db, t)
162 hir::ModuleDef::TypeAlias(t) => {
163 target_name = Some(t.name(db));
164 offset_target_and_file_id(db, t)
166 hir::ModuleDef::Module(m) => {
167 target_name = m.name(db);
168 let in_file_source = m.declaration_source(db)?;
169 let file_id = in_file_source.file_id.original_file(db.upcast());
170 let syntax = in_file_source.value.syntax();
171 (vis_offset(syntax), syntax.text_range(), file_id)
173 // Enum variants can't be private, we can't modify builtin types
174 hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None,
177 Some((offset, target, target_file, target_name))
180 fn vis_offset(node: &SyntaxNode) -> TextSize {
181 node.children_with_tokens()
182 .skip_while(|it| match it.kind() {
183 WHITESPACE | COMMENT | ATTR => true,
187 .map(|it| it.text_range().start())
188 .unwrap_or_else(|| node.text_range().start())
193 use crate::tests::{check_assist, check_assist_not_applicable};
198 fn fix_visibility_of_fn() {
201 r"mod foo { fn foo() {} }
202 fn main() { foo::foo<|>() } ",
203 r"mod foo { $0pub(crate) fn foo() {} }
204 fn main() { foo::foo() } ",
206 check_assist_not_applicable(
208 r"mod foo { pub fn foo() {} }
209 fn main() { foo::foo<|>() } ",
214 fn fix_visibility_of_adt_in_submodule() {
217 r"mod foo { struct Foo; }
218 fn main() { foo::Foo<|> } ",
219 r"mod foo { $0pub(crate) struct Foo; }
220 fn main() { foo::Foo } ",
222 check_assist_not_applicable(
224 r"mod foo { pub struct Foo; }
225 fn main() { foo::Foo<|> } ",
229 r"mod foo { enum Foo; }
230 fn main() { foo::Foo<|> } ",
231 r"mod foo { $0pub(crate) enum Foo; }
232 fn main() { foo::Foo } ",
234 check_assist_not_applicable(
236 r"mod foo { pub enum Foo; }
237 fn main() { foo::Foo<|> } ",
241 r"mod foo { union Foo; }
242 fn main() { foo::Foo<|> } ",
243 r"mod foo { $0pub(crate) union Foo; }
244 fn main() { foo::Foo } ",
246 check_assist_not_applicable(
248 r"mod foo { pub union Foo; }
249 fn main() { foo::Foo<|> } ",
254 fn fix_visibility_of_adt_in_other_file() {
260 fn main() { foo::Foo<|> }
265 r"$0pub(crate) struct Foo;
271 fn fix_visibility_of_struct_field() {
274 r"mod foo { pub struct Foo { bar: (), } }
275 fn main() { foo::Foo { <|>bar: () }; } ",
276 r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
277 fn main() { foo::Foo { bar: () }; } ",
284 fn main() { foo::Foo { <|>bar: () }; }
286 pub struct Foo { bar: () }
288 r"pub struct Foo { $0pub(crate) bar: () }
291 check_assist_not_applicable(
293 r"mod foo { pub struct Foo { pub bar: (), } }
294 fn main() { foo::Foo { <|>bar: () }; } ",
296 check_assist_not_applicable(
301 fn main() { foo::Foo { <|>bar: () }; }
303 pub struct Foo { pub bar: () }
309 fn fix_visibility_of_enum_variant_field() {
312 r"mod foo { pub enum Foo { Bar { bar: () } } }
313 fn main() { foo::Foo::Bar { <|>bar: () }; } ",
314 r"mod foo { pub enum Foo { Bar { $0pub(crate) bar: () } } }
315 fn main() { foo::Foo::Bar { bar: () }; } ",
322 fn main() { foo::Foo::Bar { <|>bar: () }; }
324 pub enum Foo { Bar { bar: () } }
326 r"pub enum Foo { Bar { $0pub(crate) bar: () } }
329 check_assist_not_applicable(
331 r"mod foo { pub struct Foo { pub bar: (), } }
332 fn main() { foo::Foo { <|>bar: () }; } ",
334 check_assist_not_applicable(
339 fn main() { foo::Foo { <|>bar: () }; }
341 pub struct Foo { pub bar: () }
348 // FIXME reenable this test when `Semantics::resolve_record_field` works with union fields
349 fn fix_visibility_of_union_field() {
352 r"mod foo { pub union Foo { bar: (), } }
353 fn main() { foo::Foo { <|>bar: () }; } ",
354 r"mod foo { pub union Foo { $0pub(crate) bar: (), } }
355 fn main() { foo::Foo { bar: () }; } ",
362 fn main() { foo::Foo { <|>bar: () }; }
364 pub union Foo { bar: () }
366 r"pub union Foo { $0pub(crate) bar: () }
369 check_assist_not_applicable(
371 r"mod foo { pub union Foo { pub bar: (), } }
372 fn main() { foo::Foo { <|>bar: () }; } ",
374 check_assist_not_applicable(
379 fn main() { foo::Foo { <|>bar: () }; }
381 pub union Foo { pub bar: () }
387 fn fix_visibility_of_const() {
390 r"mod foo { const FOO: () = (); }
391 fn main() { foo::FOO<|> } ",
392 r"mod foo { $0pub(crate) const FOO: () = (); }
393 fn main() { foo::FOO } ",
395 check_assist_not_applicable(
397 r"mod foo { pub const FOO: () = (); }
398 fn main() { foo::FOO<|> } ",
403 fn fix_visibility_of_static() {
406 r"mod foo { static FOO: () = (); }
407 fn main() { foo::FOO<|> } ",
408 r"mod foo { $0pub(crate) static FOO: () = (); }
409 fn main() { foo::FOO } ",
411 check_assist_not_applicable(
413 r"mod foo { pub static FOO: () = (); }
414 fn main() { foo::FOO<|> } ",
419 fn fix_visibility_of_trait() {
422 r"mod foo { trait Foo { fn foo(&self) {} } }
423 fn main() { let x: &dyn foo::<|>Foo; } ",
424 r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } }
425 fn main() { let x: &dyn foo::Foo; } ",
427 check_assist_not_applicable(
429 r"mod foo { pub trait Foo { fn foo(&self) {} } }
430 fn main() { let x: &dyn foo::Foo<|>; } ",
435 fn fix_visibility_of_type_alias() {
438 r"mod foo { type Foo = (); }
439 fn main() { let x: foo::Foo<|>; } ",
440 r"mod foo { $0pub(crate) type Foo = (); }
441 fn main() { let x: foo::Foo; } ",
443 check_assist_not_applicable(
445 r"mod foo { pub type Foo = (); }
446 fn main() { let x: foo::Foo<|>; } ",
451 fn fix_visibility_of_module() {
454 r"mod foo { mod bar { fn bar() {} } }
455 fn main() { foo::bar<|>::bar(); } ",
456 r"mod foo { $0pub(crate) mod bar { fn bar() {} } }
457 fn main() { foo::bar::bar(); } ",
465 fn main() { foo::bar<|>::baz(); }
472 r"$0pub(crate) mod bar {
478 check_assist_not_applicable(
480 r"mod foo { pub mod bar { pub fn bar() {} } }
481 fn main() { foo::bar<|>::bar(); } ",
486 fn fix_visibility_of_inline_module_in_other_file() {
492 fn main() { foo::bar<|>::baz(); }
499 r"$0pub(crate) mod bar;
505 fn fix_visibility_of_module_declaration_in_other_file() {
511 fn main() { foo::bar<|>>::baz(); }
518 r"$0pub(crate) mod bar {
526 fn adds_pub_when_target_is_in_another_crate() {
530 //- /main.rs crate:a deps:foo
532 //- /lib.rs crate:foo
542 // FIXME handle reexports properly
543 fn fix_visibility_of_reexport() {
549 mod bar { pub(super) struct Baz; }
555 $0pub(crate) use bar::Baz;
556 mod bar { pub(super) struct Baz; }