]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/fix_visibility.rs
Support `if let` match guards
[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     if def.visibility(ctx.db()).is_visible_from(ctx.db(), current_module.into()) {
50         return None;
51     };
52
53     let (offset, current_visibility, target, target_file, target_name) =
54         target_data_for_def(ctx.db(), def)?;
55
56     let missing_visibility =
57         if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
58
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),
62     };
63
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(
69                     cap,
70                     current_visibility.syntax().text_range(),
71                     format!("$0{}", missing_visibility),
72                 ),
73                 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
74             },
75             None => match current_visibility {
76                 Some(current_visibility) => {
77                     builder.replace(current_visibility.syntax().text_range(), missing_visibility)
78                 }
79                 None => builder.insert(offset, format!("{} ", missing_visibility)),
80             },
81         }
82     })
83 }
84
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)?;
88
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()) {
92         return None;
93     }
94
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());
98
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) => {
102             let s = it.syntax();
103             (vis_offset(s), it.visibility(), s.text_range())
104         }
105         hir::FieldSource::Pos(it) => {
106             let s = it.syntax();
107             (vis_offset(s), it.visibility(), s.text_range())
108         }
109     };
110
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());
114
115     let target_name = record_field_def.name(ctx.db());
116     let assist_label =
117         format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
118
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(
124                     cap,
125                     current_visibility.syntax().text_range(),
126                     format!("$0{}", missing_visibility),
127                 ),
128                 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
129             },
130             None => match current_visibility {
131                 Some(current_visibility) => {
132                     builder.replace(current_visibility.syntax().text_range(), missing_visibility)
133                 }
134                 None => builder.insert(offset, format!("{} ", missing_visibility)),
135             },
136         }
137     })
138 }
139
140 fn target_data_for_def(
141     db: &dyn HirDatabase,
142     def: hir::ModuleDef,
143 ) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
144     fn offset_target_and_file_id<S, Ast>(
145         db: &dyn HirDatabase,
146         x: S,
147     ) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId)>
148     where
149         S: HasSource<Ast = Ast>,
150         Ast: AstNode + ast::VisibilityOwner,
151     {
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();
157         Some((
158             vis_offset(syntax),
159             current_visibility,
160             syntax.text_range(),
161             file_id.original_file(db.upcast()),
162         ))
163     }
164
165     let target_name;
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)?
170         }
171         hir::ModuleDef::Adt(adt) => {
172             target_name = Some(adt.name(db));
173             match adt {
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)?,
177             }
178         }
179         hir::ModuleDef::Const(c) => {
180             target_name = c.name(db);
181             offset_target_and_file_id(db, c)?
182         }
183         hir::ModuleDef::Static(s) => {
184             target_name = s.name(db);
185             offset_target_and_file_id(db, s)?
186         }
187         hir::ModuleDef::Trait(t) => {
188             target_name = Some(t.name(db));
189             offset_target_and_file_id(db, t)?
190         }
191         hir::ModuleDef::TypeAlias(t) => {
192             target_name = Some(t.name(db));
193             offset_target_and_file_id(db, t)?
194         }
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)
201         }
202         // Enum variants can't be private, we can't modify builtin types
203         hir::ModuleDef::Variant(_) | hir::ModuleDef::BuiltinType(_) => return None,
204     };
205
206     Some((offset, current_visibility, target, target_file, target_name))
207 }
208
209 #[cfg(test)]
210 mod tests {
211     use crate::tests::{check_assist, check_assist_not_applicable};
212
213     use super::*;
214
215     #[test]
216     fn fix_visibility_of_fn() {
217         check_assist(
218             fix_visibility,
219             r"mod foo { fn foo() {} }
220               fn main() { foo::foo$0() } ",
221             r"mod foo { $0pub(crate) fn foo() {} }
222               fn main() { foo::foo() } ",
223         );
224         check_assist_not_applicable(
225             fix_visibility,
226             r"mod foo { pub fn foo() {} }
227               fn main() { foo::foo$0() } ",
228         )
229     }
230
231     #[test]
232     fn fix_visibility_of_adt_in_submodule() {
233         check_assist(
234             fix_visibility,
235             r"mod foo { struct Foo; }
236               fn main() { foo::Foo$0 } ",
237             r"mod foo { $0pub(crate) struct Foo; }
238               fn main() { foo::Foo } ",
239         );
240         check_assist_not_applicable(
241             fix_visibility,
242             r"mod foo { pub struct Foo; }
243               fn main() { foo::Foo$0 } ",
244         );
245         check_assist(
246             fix_visibility,
247             r"mod foo { enum Foo; }
248               fn main() { foo::Foo$0 } ",
249             r"mod foo { $0pub(crate) enum Foo; }
250               fn main() { foo::Foo } ",
251         );
252         check_assist_not_applicable(
253             fix_visibility,
254             r"mod foo { pub enum Foo; }
255               fn main() { foo::Foo$0 } ",
256         );
257         check_assist(
258             fix_visibility,
259             r"mod foo { union Foo; }
260               fn main() { foo::Foo$0 } ",
261             r"mod foo { $0pub(crate) union Foo; }
262               fn main() { foo::Foo } ",
263         );
264         check_assist_not_applicable(
265             fix_visibility,
266             r"mod foo { pub union Foo; }
267               fn main() { foo::Foo$0 } ",
268         );
269     }
270
271     #[test]
272     fn fix_visibility_of_adt_in_other_file() {
273         check_assist(
274             fix_visibility,
275             r"
276 //- /main.rs
277 mod foo;
278 fn main() { foo::Foo$0 }
279
280 //- /foo.rs
281 struct Foo;
282 ",
283             r"$0pub(crate) struct Foo;
284 ",
285         );
286     }
287
288     #[test]
289     fn fix_visibility_of_struct_field() {
290         check_assist(
291             fix_visibility,
292             r"mod foo { pub struct Foo { bar: (), } }
293               fn main() { foo::Foo { $0bar: () }; } ",
294             r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
295               fn main() { foo::Foo { bar: () }; } ",
296         );
297         check_assist(
298             fix_visibility,
299             r"
300 //- /lib.rs
301 mod foo;
302 fn main() { foo::Foo { $0bar: () }; }
303 //- /foo.rs
304 pub struct Foo { bar: () }
305 ",
306             r"pub struct Foo { $0pub(crate) bar: () }
307 ",
308         );
309         check_assist_not_applicable(
310             fix_visibility,
311             r"mod foo { pub struct Foo { pub bar: (), } }
312               fn main() { foo::Foo { $0bar: () }; } ",
313         );
314         check_assist_not_applicable(
315             fix_visibility,
316             r"
317 //- /lib.rs
318 mod foo;
319 fn main() { foo::Foo { $0bar: () }; }
320 //- /foo.rs
321 pub struct Foo { pub bar: () }
322 ",
323         );
324     }
325
326     #[test]
327     fn fix_visibility_of_enum_variant_field() {
328         // Enum variants, as well as their fields, always get the enum's visibility. In fact, rustc
329         // rejects any visibility specifiers on them, so this assist should never fire on them.
330         check_assist_not_applicable(
331             fix_visibility,
332             r"mod foo { pub enum Foo { Bar { bar: () } } }
333               fn main() { foo::Foo::Bar { $0bar: () }; } ",
334         );
335         check_assist_not_applicable(
336             fix_visibility,
337             r"
338 //- /lib.rs
339 mod foo;
340 fn main() { foo::Foo::Bar { $0bar: () }; }
341 //- /foo.rs
342 pub enum Foo { Bar { bar: () } }
343 ",
344         );
345         check_assist_not_applicable(
346             fix_visibility,
347             r"mod foo { pub struct Foo { pub bar: (), } }
348               fn main() { foo::Foo { $0bar: () }; } ",
349         );
350         check_assist_not_applicable(
351             fix_visibility,
352             r"
353 //- /lib.rs
354 mod foo;
355 fn main() { foo::Foo { $0bar: () }; }
356 //- /foo.rs
357 pub struct Foo { pub bar: () }
358 ",
359         );
360     }
361
362     #[test]
363     fn fix_visibility_of_union_field() {
364         check_assist(
365             fix_visibility,
366             r"mod foo { pub union Foo { bar: (), } }
367               fn main() { foo::Foo { $0bar: () }; } ",
368             r"mod foo { pub union Foo { $0pub(crate) bar: (), } }
369               fn main() { foo::Foo { bar: () }; } ",
370         );
371         check_assist(
372             fix_visibility,
373             r"
374 //- /lib.rs
375 mod foo;
376 fn main() { foo::Foo { $0bar: () }; }
377 //- /foo.rs
378 pub union Foo { bar: () }
379 ",
380             r"pub union Foo { $0pub(crate) bar: () }
381 ",
382         );
383         check_assist_not_applicable(
384             fix_visibility,
385             r"mod foo { pub union Foo { pub bar: (), } }
386               fn main() { foo::Foo { $0bar: () }; } ",
387         );
388         check_assist_not_applicable(
389             fix_visibility,
390             r"
391 //- /lib.rs
392 mod foo;
393 fn main() { foo::Foo { $0bar: () }; }
394 //- /foo.rs
395 pub union Foo { pub bar: () }
396 ",
397         );
398     }
399
400     #[test]
401     fn fix_visibility_of_const() {
402         check_assist(
403             fix_visibility,
404             r"mod foo { const FOO: () = (); }
405               fn main() { foo::FOO$0 } ",
406             r"mod foo { $0pub(crate) const FOO: () = (); }
407               fn main() { foo::FOO } ",
408         );
409         check_assist_not_applicable(
410             fix_visibility,
411             r"mod foo { pub const FOO: () = (); }
412               fn main() { foo::FOO$0 } ",
413         );
414     }
415
416     #[test]
417     fn fix_visibility_of_static() {
418         check_assist(
419             fix_visibility,
420             r"mod foo { static FOO: () = (); }
421               fn main() { foo::FOO$0 } ",
422             r"mod foo { $0pub(crate) static FOO: () = (); }
423               fn main() { foo::FOO } ",
424         );
425         check_assist_not_applicable(
426             fix_visibility,
427             r"mod foo { pub static FOO: () = (); }
428               fn main() { foo::FOO$0 } ",
429         );
430     }
431
432     #[test]
433     fn fix_visibility_of_trait() {
434         check_assist(
435             fix_visibility,
436             r"mod foo { trait Foo { fn foo(&self) {} } }
437               fn main() { let x: &dyn foo::$0Foo; } ",
438             r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } }
439               fn main() { let x: &dyn foo::Foo; } ",
440         );
441         check_assist_not_applicable(
442             fix_visibility,
443             r"mod foo { pub trait Foo { fn foo(&self) {} } }
444               fn main() { let x: &dyn foo::Foo$0; } ",
445         );
446     }
447
448     #[test]
449     fn fix_visibility_of_type_alias() {
450         check_assist(
451             fix_visibility,
452             r"mod foo { type Foo = (); }
453               fn main() { let x: foo::Foo$0; } ",
454             r"mod foo { $0pub(crate) type Foo = (); }
455               fn main() { let x: foo::Foo; } ",
456         );
457         check_assist_not_applicable(
458             fix_visibility,
459             r"mod foo { pub type Foo = (); }
460               fn main() { let x: foo::Foo$0; } ",
461         );
462     }
463
464     #[test]
465     fn fix_visibility_of_module() {
466         check_assist(
467             fix_visibility,
468             r"mod foo { mod bar { fn bar() {} } }
469               fn main() { foo::bar$0::bar(); } ",
470             r"mod foo { $0pub(crate) mod bar { fn bar() {} } }
471               fn main() { foo::bar::bar(); } ",
472         );
473
474         check_assist(
475             fix_visibility,
476             r"
477 //- /main.rs
478 mod foo;
479 fn main() { foo::bar$0::baz(); }
480
481 //- /foo.rs
482 mod bar {
483     pub fn baz() {}
484 }
485 ",
486             r"$0pub(crate) mod bar {
487     pub fn baz() {}
488 }
489 ",
490         );
491
492         check_assist_not_applicable(
493             fix_visibility,
494             r"mod foo { pub mod bar { pub fn bar() {} } }
495               fn main() { foo::bar$0::bar(); } ",
496         );
497     }
498
499     #[test]
500     fn fix_visibility_of_inline_module_in_other_file() {
501         check_assist(
502             fix_visibility,
503             r"
504 //- /main.rs
505 mod foo;
506 fn main() { foo::bar$0::baz(); }
507
508 //- /foo.rs
509 mod bar;
510 //- /foo/bar.rs
511 pub fn baz() {}
512 ",
513             r"$0pub(crate) mod bar;
514 ",
515         );
516     }
517
518     #[test]
519     fn fix_visibility_of_module_declaration_in_other_file() {
520         check_assist(
521             fix_visibility,
522             r"
523 //- /main.rs
524 mod foo;
525 fn main() { foo::bar$0>::baz(); }
526
527 //- /foo.rs
528 mod bar {
529     pub fn baz() {}
530 }
531 ",
532             r"$0pub(crate) mod bar {
533     pub fn baz() {}
534 }
535 ",
536         );
537     }
538
539     #[test]
540     fn adds_pub_when_target_is_in_another_crate() {
541         check_assist(
542             fix_visibility,
543             r"
544 //- /main.rs crate:a deps:foo
545 foo::Bar$0
546 //- /lib.rs crate:foo
547 struct Bar;
548 ",
549             r"$0pub struct Bar;
550 ",
551         )
552     }
553
554     #[test]
555     fn replaces_pub_crate_with_pub() {
556         check_assist(
557             fix_visibility,
558             r"
559 //- /main.rs crate:a deps:foo
560 foo::Bar$0
561 //- /lib.rs crate:foo
562 pub(crate) struct Bar;
563 ",
564             r"$0pub struct Bar;
565 ",
566         );
567         check_assist(
568             fix_visibility,
569             r"
570 //- /main.rs crate:a deps:foo
571 fn main() {
572     foo::Foo { $0bar: () };
573 }
574 //- /lib.rs crate:foo
575 pub struct Foo { pub(crate) bar: () }
576 ",
577             r"pub struct Foo { $0pub bar: () }
578 ",
579         );
580     }
581
582     #[test]
583     fn fix_visibility_of_reexport() {
584         // FIXME: broken test, this should fix visibility of the re-export
585         // rather than the struct.
586         check_assist(
587             fix_visibility,
588             r#"
589 mod foo {
590     use bar::Baz;
591     mod bar { pub(super) struct Baz; }
592 }
593 foo::Baz$0
594 "#,
595             r#"
596 mod foo {
597     use bar::Baz;
598     mod bar { $0pub(crate) struct Baz; }
599 }
600 foo::Baz
601 "#,
602         )
603     }
604 }