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