]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs
Rollup merge of #99460 - JanBeh:PR_asref_asmut_docs, r=joshtriplett
[rust.git] / src / tools / rust-analyzer / 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, 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 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::HasVisibility,
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 = Some(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         // FIXME
203         hir::ModuleDef::Macro(_) => return None,
204         // Enum variants can't be private, we can't modify builtin types
205         hir::ModuleDef::Variant(_) | hir::ModuleDef::BuiltinType(_) => return None,
206     };
207
208     Some((offset, current_visibility, target, target_file, target_name))
209 }
210
211 #[cfg(test)]
212 mod tests {
213     use crate::tests::{check_assist, check_assist_not_applicable};
214
215     use super::*;
216
217     #[test]
218     fn fix_visibility_of_fn() {
219         check_assist(
220             fix_visibility,
221             r"mod foo { fn foo() {} }
222               fn main() { foo::foo$0() } ",
223             r"mod foo { $0pub(crate) fn foo() {} }
224               fn main() { foo::foo() } ",
225         );
226         check_assist_not_applicable(
227             fix_visibility,
228             r"mod foo { pub fn foo() {} }
229               fn main() { foo::foo$0() } ",
230         )
231     }
232
233     #[test]
234     fn fix_visibility_of_adt_in_submodule() {
235         check_assist(
236             fix_visibility,
237             r"mod foo { struct Foo; }
238               fn main() { foo::Foo$0 } ",
239             r"mod foo { $0pub(crate) struct Foo; }
240               fn main() { foo::Foo } ",
241         );
242         check_assist_not_applicable(
243             fix_visibility,
244             r"mod foo { pub struct Foo; }
245               fn main() { foo::Foo$0 } ",
246         );
247         check_assist(
248             fix_visibility,
249             r"mod foo { enum Foo; }
250               fn main() { foo::Foo$0 } ",
251             r"mod foo { $0pub(crate) enum Foo; }
252               fn main() { foo::Foo } ",
253         );
254         check_assist_not_applicable(
255             fix_visibility,
256             r"mod foo { pub enum Foo; }
257               fn main() { foo::Foo$0 } ",
258         );
259         check_assist(
260             fix_visibility,
261             r"mod foo { union Foo; }
262               fn main() { foo::Foo$0 } ",
263             r"mod foo { $0pub(crate) union Foo; }
264               fn main() { foo::Foo } ",
265         );
266         check_assist_not_applicable(
267             fix_visibility,
268             r"mod foo { pub union Foo; }
269               fn main() { foo::Foo$0 } ",
270         );
271     }
272
273     #[test]
274     fn fix_visibility_of_adt_in_other_file() {
275         check_assist(
276             fix_visibility,
277             r"
278 //- /main.rs
279 mod foo;
280 fn main() { foo::Foo$0 }
281
282 //- /foo.rs
283 struct Foo;
284 ",
285             r"$0pub(crate) struct Foo;
286 ",
287         );
288     }
289
290     #[test]
291     fn fix_visibility_of_struct_field() {
292         check_assist(
293             fix_visibility,
294             r"mod foo { pub struct Foo { bar: (), } }
295               fn main() { foo::Foo { $0bar: () }; } ",
296             r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
297               fn main() { foo::Foo { bar: () }; } ",
298         );
299         check_assist(
300             fix_visibility,
301             r"
302 //- /lib.rs
303 mod foo;
304 fn main() { foo::Foo { $0bar: () }; }
305 //- /foo.rs
306 pub struct Foo { bar: () }
307 ",
308             r"pub struct Foo { $0pub(crate) bar: () }
309 ",
310         );
311         check_assist_not_applicable(
312             fix_visibility,
313             r"mod foo { pub struct Foo { pub bar: (), } }
314               fn main() { foo::Foo { $0bar: () }; } ",
315         );
316         check_assist_not_applicable(
317             fix_visibility,
318             r"
319 //- /lib.rs
320 mod foo;
321 fn main() { foo::Foo { $0bar: () }; }
322 //- /foo.rs
323 pub struct Foo { pub bar: () }
324 ",
325         );
326     }
327
328     #[test]
329     fn fix_visibility_of_enum_variant_field() {
330         // Enum variants, as well as their fields, always get the enum's visibility. In fact, rustc
331         // rejects any visibility specifiers on them, so this assist should never fire on them.
332         check_assist_not_applicable(
333             fix_visibility,
334             r"mod foo { pub enum Foo { Bar { bar: () } } }
335               fn main() { foo::Foo::Bar { $0bar: () }; } ",
336         );
337         check_assist_not_applicable(
338             fix_visibility,
339             r"
340 //- /lib.rs
341 mod foo;
342 fn main() { foo::Foo::Bar { $0bar: () }; }
343 //- /foo.rs
344 pub enum Foo { Bar { bar: () } }
345 ",
346         );
347         check_assist_not_applicable(
348             fix_visibility,
349             r"mod foo { pub struct Foo { pub bar: (), } }
350               fn main() { foo::Foo { $0bar: () }; } ",
351         );
352         check_assist_not_applicable(
353             fix_visibility,
354             r"
355 //- /lib.rs
356 mod foo;
357 fn main() { foo::Foo { $0bar: () }; }
358 //- /foo.rs
359 pub struct Foo { pub bar: () }
360 ",
361         );
362     }
363
364     #[test]
365     fn fix_visibility_of_union_field() {
366         check_assist(
367             fix_visibility,
368             r"mod foo { pub union Foo { bar: (), } }
369               fn main() { foo::Foo { $0bar: () }; } ",
370             r"mod foo { pub union Foo { $0pub(crate) bar: (), } }
371               fn main() { foo::Foo { bar: () }; } ",
372         );
373         check_assist(
374             fix_visibility,
375             r"
376 //- /lib.rs
377 mod foo;
378 fn main() { foo::Foo { $0bar: () }; }
379 //- /foo.rs
380 pub union Foo { bar: () }
381 ",
382             r"pub union Foo { $0pub(crate) bar: () }
383 ",
384         );
385         check_assist_not_applicable(
386             fix_visibility,
387             r"mod foo { pub union Foo { pub bar: (), } }
388               fn main() { foo::Foo { $0bar: () }; } ",
389         );
390         check_assist_not_applicable(
391             fix_visibility,
392             r"
393 //- /lib.rs
394 mod foo;
395 fn main() { foo::Foo { $0bar: () }; }
396 //- /foo.rs
397 pub union Foo { pub bar: () }
398 ",
399         );
400     }
401
402     #[test]
403     fn fix_visibility_of_const() {
404         check_assist(
405             fix_visibility,
406             r"mod foo { const FOO: () = (); }
407               fn main() { foo::FOO$0 } ",
408             r"mod foo { $0pub(crate) const FOO: () = (); }
409               fn main() { foo::FOO } ",
410         );
411         check_assist_not_applicable(
412             fix_visibility,
413             r"mod foo { pub const FOO: () = (); }
414               fn main() { foo::FOO$0 } ",
415         );
416     }
417
418     #[test]
419     fn fix_visibility_of_static() {
420         check_assist(
421             fix_visibility,
422             r"mod foo { static FOO: () = (); }
423               fn main() { foo::FOO$0 } ",
424             r"mod foo { $0pub(crate) static FOO: () = (); }
425               fn main() { foo::FOO } ",
426         );
427         check_assist_not_applicable(
428             fix_visibility,
429             r"mod foo { pub static FOO: () = (); }
430               fn main() { foo::FOO$0 } ",
431         );
432     }
433
434     #[test]
435     fn fix_visibility_of_trait() {
436         check_assist(
437             fix_visibility,
438             r"mod foo { trait Foo { fn foo(&self) {} } }
439               fn main() { let x: &dyn foo::$0Foo; } ",
440             r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } }
441               fn main() { let x: &dyn foo::Foo; } ",
442         );
443         check_assist_not_applicable(
444             fix_visibility,
445             r"mod foo { pub trait Foo { fn foo(&self) {} } }
446               fn main() { let x: &dyn foo::Foo$0; } ",
447         );
448     }
449
450     #[test]
451     fn fix_visibility_of_type_alias() {
452         check_assist(
453             fix_visibility,
454             r"mod foo { type Foo = (); }
455               fn main() { let x: foo::Foo$0; } ",
456             r"mod foo { $0pub(crate) type Foo = (); }
457               fn main() { let x: foo::Foo; } ",
458         );
459         check_assist_not_applicable(
460             fix_visibility,
461             r"mod foo { pub type Foo = (); }
462               fn main() { let x: foo::Foo$0; } ",
463         );
464     }
465
466     #[test]
467     fn fix_visibility_of_module() {
468         check_assist(
469             fix_visibility,
470             r"mod foo { mod bar { fn bar() {} } }
471               fn main() { foo::bar$0::bar(); } ",
472             r"mod foo { $0pub(crate) mod bar { fn bar() {} } }
473               fn main() { foo::bar::bar(); } ",
474         );
475
476         check_assist(
477             fix_visibility,
478             r"
479 //- /main.rs
480 mod foo;
481 fn main() { foo::bar$0::baz(); }
482
483 //- /foo.rs
484 mod bar {
485     pub fn baz() {}
486 }
487 ",
488             r"$0pub(crate) mod bar {
489     pub fn baz() {}
490 }
491 ",
492         );
493
494         check_assist_not_applicable(
495             fix_visibility,
496             r"mod foo { pub mod bar { pub fn bar() {} } }
497               fn main() { foo::bar$0::bar(); } ",
498         );
499     }
500
501     #[test]
502     fn fix_visibility_of_inline_module_in_other_file() {
503         check_assist(
504             fix_visibility,
505             r"
506 //- /main.rs
507 mod foo;
508 fn main() { foo::bar$0::baz(); }
509
510 //- /foo.rs
511 mod bar;
512 //- /foo/bar.rs
513 pub fn baz() {}
514 ",
515             r"$0pub(crate) mod bar;
516 ",
517         );
518     }
519
520     #[test]
521     fn fix_visibility_of_module_declaration_in_other_file() {
522         check_assist(
523             fix_visibility,
524             r"
525 //- /main.rs
526 mod foo;
527 fn main() { foo::bar$0>::baz(); }
528
529 //- /foo.rs
530 mod bar {
531     pub fn baz() {}
532 }
533 ",
534             r"$0pub(crate) mod bar {
535     pub fn baz() {}
536 }
537 ",
538         );
539     }
540
541     #[test]
542     fn adds_pub_when_target_is_in_another_crate() {
543         check_assist(
544             fix_visibility,
545             r"
546 //- /main.rs crate:a deps:foo
547 foo::Bar$0
548 //- /lib.rs crate:foo
549 struct Bar;
550 ",
551             r"$0pub struct Bar;
552 ",
553         )
554     }
555
556     #[test]
557     fn replaces_pub_crate_with_pub() {
558         check_assist(
559             fix_visibility,
560             r"
561 //- /main.rs crate:a deps:foo
562 foo::Bar$0
563 //- /lib.rs crate:foo
564 pub(crate) struct Bar;
565 ",
566             r"$0pub struct Bar;
567 ",
568         );
569         check_assist(
570             fix_visibility,
571             r"
572 //- /main.rs crate:a deps:foo
573 fn main() {
574     foo::Foo { $0bar: () };
575 }
576 //- /lib.rs crate:foo
577 pub struct Foo { pub(crate) bar: () }
578 ",
579             r"pub struct Foo { $0pub bar: () }
580 ",
581         );
582     }
583
584     #[test]
585     fn fix_visibility_of_reexport() {
586         // FIXME: broken test, this should fix visibility of the re-export
587         // rather than the struct.
588         check_assist(
589             fix_visibility,
590             r#"
591 mod foo {
592     use bar::Baz;
593     mod bar { pub(super) struct Baz; }
594 }
595 foo::Baz$0
596 "#,
597             r#"
598 mod foo {
599     use bar::Baz;
600     mod bar { $0pub(crate) struct Baz; }
601 }
602 foo::Baz
603 "#,
604         )
605     }
606 }