]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/fix_visibility.rs
6c7824e551018c91d3bca64f830e5253babde39b
[rust.git] / crates / ide_assists / src / handlers / fix_visibility.rs
1 use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
2 use ide_db::base_db::FileId;
3 use syntax::{
4     ast::{self, VisibilityOwner},
5     AstNode, TextRange, TextSize,
6 };
7
8 use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
9
10 // FIXME: this really should be a fix for diagnostic, rather than an assist.
11
12 // Assist: fix_visibility
13 //
14 // Makes inaccessible item public.
15 //
16 // ```
17 // mod m {
18 //     fn frobnicate() {}
19 // }
20 // fn main() {
21 //     m::frobnicate$0() {}
22 // }
23 // ```
24 // ->
25 // ```
26 // mod m {
27 //     $0pub(crate) fn frobnicate() {}
28 // }
29 // fn main() {
30 //     m::frobnicate() {}
31 // }
32 // ```
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))
36 }
37
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,
43         _ => return None,
44     };
45
46     let current_module = ctx.sema.scope(&path.syntax()).module()?;
47     let target_module = def.module(ctx.db())?;
48
49     let vis = target_module.visibility_of(ctx.db(), &def)?;
50     if vis.is_visible_from(ctx.db(), current_module.into()) {
51         return None;
52     };
53
54     let (offset, current_visibility, target, target_file, target_name) =
55         target_data_for_def(ctx.db(), def)?;
56
57     let missing_visibility =
58         if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
59
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),
63     };
64
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(
70                     cap,
71                     current_visibility.syntax().text_range(),
72                     format!("$0{}", missing_visibility),
73                 ),
74                 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
75             },
76             None => match current_visibility {
77                 Some(current_visibility) => {
78                     builder.replace(current_visibility.syntax().text_range(), missing_visibility)
79                 }
80                 None => builder.insert(offset, format!("{} ", missing_visibility)),
81             },
82         }
83     })
84 }
85
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)?;
89
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()) {
93         return None;
94     }
95
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());
99
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) => {
103             let s = it.syntax();
104             (vis_offset(s), it.visibility(), s.text_range())
105         }
106         hir::FieldSource::Pos(it) => {
107             let s = it.syntax();
108             (vis_offset(s), it.visibility(), s.text_range())
109         }
110     };
111
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());
115
116     let target_name = record_field_def.name(ctx.db());
117     let assist_label =
118         format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
119
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(
125                     cap,
126                     current_visibility.syntax().text_range(),
127                     format!("$0{}", missing_visibility),
128                 ),
129                 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
130             },
131             None => match current_visibility {
132                 Some(current_visibility) => {
133                     builder.replace(current_visibility.syntax().text_range(), missing_visibility)
134                 }
135                 None => builder.insert(offset, format!("{} ", missing_visibility)),
136             },
137         }
138     })
139 }
140
141 fn target_data_for_def(
142     db: &dyn HirDatabase,
143     def: hir::ModuleDef,
144 ) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
145     fn offset_target_and_file_id<S, Ast>(
146         db: &dyn HirDatabase,
147         x: S,
148     ) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId)>
149     where
150         S: HasSource<Ast = Ast>,
151         Ast: AstNode + ast::VisibilityOwner,
152     {
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();
158         Some((
159             vis_offset(syntax),
160             current_visibility,
161             syntax.text_range(),
162             file_id.original_file(db.upcast()),
163         ))
164     }
165
166     let target_name;
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)?
171         }
172         hir::ModuleDef::Adt(adt) => {
173             target_name = Some(adt.name(db));
174             match adt {
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)?,
178             }
179         }
180         hir::ModuleDef::Const(c) => {
181             target_name = c.name(db);
182             offset_target_and_file_id(db, c)?
183         }
184         hir::ModuleDef::Static(s) => {
185             target_name = s.name(db);
186             offset_target_and_file_id(db, s)?
187         }
188         hir::ModuleDef::Trait(t) => {
189             target_name = Some(t.name(db));
190             offset_target_and_file_id(db, t)?
191         }
192         hir::ModuleDef::TypeAlias(t) => {
193             target_name = Some(t.name(db));
194             offset_target_and_file_id(db, t)?
195         }
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)
202         }
203         // Enum variants can't be private, we can't modify builtin types
204         hir::ModuleDef::Variant(_) | hir::ModuleDef::BuiltinType(_) => return None,
205     };
206
207     Some((offset, current_visibility, target, target_file, target_name))
208 }
209
210 #[cfg(test)]
211 mod tests {
212     use crate::tests::{check_assist, check_assist_not_applicable};
213
214     use super::*;
215
216     #[test]
217     fn fix_visibility_of_fn() {
218         check_assist(
219             fix_visibility,
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() } ",
224         );
225         check_assist_not_applicable(
226             fix_visibility,
227             r"mod foo { pub fn foo() {} }
228               fn main() { foo::foo$0() } ",
229         )
230     }
231
232     #[test]
233     fn fix_visibility_of_adt_in_submodule() {
234         check_assist(
235             fix_visibility,
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 } ",
240         );
241         check_assist_not_applicable(
242             fix_visibility,
243             r"mod foo { pub struct Foo; }
244               fn main() { foo::Foo$0 } ",
245         );
246         check_assist(
247             fix_visibility,
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 } ",
252         );
253         check_assist_not_applicable(
254             fix_visibility,
255             r"mod foo { pub enum Foo; }
256               fn main() { foo::Foo$0 } ",
257         );
258         check_assist(
259             fix_visibility,
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 } ",
264         );
265         check_assist_not_applicable(
266             fix_visibility,
267             r"mod foo { pub union Foo; }
268               fn main() { foo::Foo$0 } ",
269         );
270     }
271
272     #[test]
273     fn fix_visibility_of_adt_in_other_file() {
274         check_assist(
275             fix_visibility,
276             r"
277 //- /main.rs
278 mod foo;
279 fn main() { foo::Foo$0 }
280
281 //- /foo.rs
282 struct Foo;
283 ",
284             r"$0pub(crate) struct Foo;
285 ",
286         );
287     }
288
289     #[test]
290     fn fix_visibility_of_struct_field() {
291         check_assist(
292             fix_visibility,
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: () }; } ",
297         );
298         check_assist(
299             fix_visibility,
300             r"
301 //- /lib.rs
302 mod foo;
303 fn main() { foo::Foo { $0bar: () }; }
304 //- /foo.rs
305 pub struct Foo { bar: () }
306 ",
307             r"pub struct Foo { $0pub(crate) bar: () }
308 ",
309         );
310         check_assist_not_applicable(
311             fix_visibility,
312             r"mod foo { pub struct Foo { pub bar: (), } }
313               fn main() { foo::Foo { $0bar: () }; } ",
314         );
315         check_assist_not_applicable(
316             fix_visibility,
317             r"
318 //- /lib.rs
319 mod foo;
320 fn main() { foo::Foo { $0bar: () }; }
321 //- /foo.rs
322 pub struct Foo { pub bar: () }
323 ",
324         );
325     }
326
327     #[test]
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(
332             fix_visibility,
333             r"mod foo { pub enum Foo { Bar { bar: () } } }
334               fn main() { foo::Foo::Bar { $0bar: () }; } ",
335         );
336         check_assist_not_applicable(
337             fix_visibility,
338             r"
339 //- /lib.rs
340 mod foo;
341 fn main() { foo::Foo::Bar { $0bar: () }; }
342 //- /foo.rs
343 pub enum Foo { Bar { bar: () } }
344 ",
345         );
346         check_assist_not_applicable(
347             fix_visibility,
348             r"mod foo { pub struct Foo { pub bar: (), } }
349               fn main() { foo::Foo { $0bar: () }; } ",
350         );
351         check_assist_not_applicable(
352             fix_visibility,
353             r"
354 //- /lib.rs
355 mod foo;
356 fn main() { foo::Foo { $0bar: () }; }
357 //- /foo.rs
358 pub struct Foo { pub bar: () }
359 ",
360         );
361     }
362
363     #[test]
364     #[ignore]
365     // FIXME reenable this test when `Semantics::resolve_record_field` works with union fields
366     fn fix_visibility_of_union_field() {
367         check_assist(
368             fix_visibility,
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: () }; } ",
373         );
374         check_assist(
375             fix_visibility,
376             r"
377 //- /lib.rs
378 mod foo;
379 fn main() { foo::Foo { $0bar: () }; }
380 //- /foo.rs
381 pub union Foo { bar: () }
382 ",
383             r"pub union Foo { $0pub(crate) bar: () }
384 ",
385         );
386         check_assist_not_applicable(
387             fix_visibility,
388             r"mod foo { pub union Foo { pub bar: (), } }
389               fn main() { foo::Foo { $0bar: () }; } ",
390         );
391         check_assist_not_applicable(
392             fix_visibility,
393             r"
394 //- /lib.rs
395 mod foo;
396 fn main() { foo::Foo { $0bar: () }; }
397 //- /foo.rs
398 pub union Foo { pub bar: () }
399 ",
400         );
401     }
402
403     #[test]
404     fn fix_visibility_of_const() {
405         check_assist(
406             fix_visibility,
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 } ",
411         );
412         check_assist_not_applicable(
413             fix_visibility,
414             r"mod foo { pub const FOO: () = (); }
415               fn main() { foo::FOO$0 } ",
416         );
417     }
418
419     #[test]
420     fn fix_visibility_of_static() {
421         check_assist(
422             fix_visibility,
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 } ",
427         );
428         check_assist_not_applicable(
429             fix_visibility,
430             r"mod foo { pub static FOO: () = (); }
431               fn main() { foo::FOO$0 } ",
432         );
433     }
434
435     #[test]
436     fn fix_visibility_of_trait() {
437         check_assist(
438             fix_visibility,
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; } ",
443         );
444         check_assist_not_applicable(
445             fix_visibility,
446             r"mod foo { pub trait Foo { fn foo(&self) {} } }
447               fn main() { let x: &dyn foo::Foo$0; } ",
448         );
449     }
450
451     #[test]
452     fn fix_visibility_of_type_alias() {
453         check_assist(
454             fix_visibility,
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; } ",
459         );
460         check_assist_not_applicable(
461             fix_visibility,
462             r"mod foo { pub type Foo = (); }
463               fn main() { let x: foo::Foo$0; } ",
464         );
465     }
466
467     #[test]
468     fn fix_visibility_of_module() {
469         check_assist(
470             fix_visibility,
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(); } ",
475         );
476
477         check_assist(
478             fix_visibility,
479             r"
480 //- /main.rs
481 mod foo;
482 fn main() { foo::bar$0::baz(); }
483
484 //- /foo.rs
485 mod bar {
486     pub fn baz() {}
487 }
488 ",
489             r"$0pub(crate) mod bar {
490     pub fn baz() {}
491 }
492 ",
493         );
494
495         check_assist_not_applicable(
496             fix_visibility,
497             r"mod foo { pub mod bar { pub fn bar() {} } }
498               fn main() { foo::bar$0::bar(); } ",
499         );
500     }
501
502     #[test]
503     fn fix_visibility_of_inline_module_in_other_file() {
504         check_assist(
505             fix_visibility,
506             r"
507 //- /main.rs
508 mod foo;
509 fn main() { foo::bar$0::baz(); }
510
511 //- /foo.rs
512 mod bar;
513 //- /foo/bar.rs
514 pub fn baz() {}
515 ",
516             r"$0pub(crate) mod bar;
517 ",
518         );
519     }
520
521     #[test]
522     fn fix_visibility_of_module_declaration_in_other_file() {
523         check_assist(
524             fix_visibility,
525             r"
526 //- /main.rs
527 mod foo;
528 fn main() { foo::bar$0>::baz(); }
529
530 //- /foo.rs
531 mod bar {
532     pub fn baz() {}
533 }
534 ",
535             r"$0pub(crate) mod bar {
536     pub fn baz() {}
537 }
538 ",
539         );
540     }
541
542     #[test]
543     fn adds_pub_when_target_is_in_another_crate() {
544         check_assist(
545             fix_visibility,
546             r"
547 //- /main.rs crate:a deps:foo
548 foo::Bar$0
549 //- /lib.rs crate:foo
550 struct Bar;
551 ",
552             r"$0pub struct Bar;
553 ",
554         )
555     }
556
557     #[test]
558     fn replaces_pub_crate_with_pub() {
559         check_assist(
560             fix_visibility,
561             r"
562 //- /main.rs crate:a deps:foo
563 foo::Bar$0
564 //- /lib.rs crate:foo
565 pub(crate) struct Bar;
566 ",
567             r"$0pub struct Bar;
568 ",
569         );
570         check_assist(
571             fix_visibility,
572             r"
573 //- /main.rs crate:a deps:foo
574 fn main() {
575     foo::Foo { $0bar: () };
576 }
577 //- /lib.rs crate:foo
578 pub struct Foo { pub(crate) bar: () }
579 ",
580             r"pub struct Foo { $0pub bar: () }
581 ",
582         );
583     }
584
585     #[test]
586     #[ignore]
587     // FIXME handle reexports properly
588     fn fix_visibility_of_reexport() {
589         check_assist(
590             fix_visibility,
591             r"
592             mod foo {
593                 use bar::Baz;
594                 mod bar { pub(super) struct Baz; }
595             }
596             foo::Baz$0
597             ",
598             r"
599             mod foo {
600                 $0pub(crate) use bar::Baz;
601                 mod bar { pub(super) struct Baz; }
602             }
603             foo::Baz
604             ",
605         )
606     }
607 }