]> git.lizzy.rs Git - rust.git/blob - crates/ra_assists/src/handlers/fix_visibility.rs
Centralize fixture parsing for assists
[rust.git] / crates / ra_assists / src / handlers / fix_visibility.rs
1 use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
2 use ra_db::FileId;
3 use ra_syntax::{
4     ast, AstNode,
5     SyntaxKind::{ATTR, COMMENT, WHITESPACE},
6     SyntaxNode, TextRange, TextSize,
7 };
8
9 use crate::{AssistContext, AssistId, Assists};
10
11 // FIXME: this really should be a fix for diagnostic, rather than an assist.
12
13 // Assist: fix_visibility
14 //
15 // Makes inaccessible item public.
16 //
17 // ```
18 // mod m {
19 //     fn frobnicate() {}
20 // }
21 // fn main() {
22 //     m::frobnicate<|>() {}
23 // }
24 // ```
25 // ->
26 // ```
27 // mod m {
28 //     $0pub(crate) fn frobnicate() {}
29 // }
30 // fn main() {
31 //     m::frobnicate() {}
32 // }
33 // ```
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))
37 }
38
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,
44         _ => return None,
45     };
46
47     let current_module = ctx.sema.scope(&path.syntax()).module()?;
48     let target_module = def.module(ctx.db)?;
49
50     let vis = target_module.visibility_of(ctx.db, &def)?;
51     if vis.is_visible_from(ctx.db, current_module.into()) {
52         return None;
53     };
54
55     let (offset, target, target_file, target_name) = 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"), 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)),
70         }
71     })
72 }
73
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)?;
77
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()) {
81         return None;
82     }
83
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);
87
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) => {
91             let s = it.syntax();
92             (vis_offset(s), s.text_range())
93         }
94         hir::FieldSource::Pos(it) => {
95             let s = it.syntax();
96             (vis_offset(s), s.text_range())
97         }
98     };
99
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);
103
104     let target_name = record_field_def.name(ctx.db);
105     let assist_label =
106         format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
107
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)),
113         }
114     })
115 }
116
117 fn target_data_for_def(
118     db: &dyn HirDatabase,
119     def: hir::ModuleDef,
120 ) -> Option<(TextSize, TextRange, FileId, Option<hir::Name>)> {
121     fn offset_target_and_file_id<S, Ast>(
122         db: &dyn HirDatabase,
123         x: S,
124     ) -> (TextSize, TextRange, FileId)
125     where
126         S: HasSource<Ast = Ast>,
127         Ast: AstNode,
128     {
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()))
134     }
135
136     let target_name;
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)
141         }
142         hir::ModuleDef::Adt(adt) => {
143             target_name = Some(adt.name(db));
144             match adt {
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),
148             }
149         }
150         hir::ModuleDef::Const(c) => {
151             target_name = c.name(db);
152             offset_target_and_file_id(db, c)
153         }
154         hir::ModuleDef::Static(s) => {
155             target_name = s.name(db);
156             offset_target_and_file_id(db, s)
157         }
158         hir::ModuleDef::Trait(t) => {
159             target_name = Some(t.name(db));
160             offset_target_and_file_id(db, t)
161         }
162         hir::ModuleDef::TypeAlias(t) => {
163             target_name = Some(t.name(db));
164             offset_target_and_file_id(db, t)
165         }
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)
172         }
173         // Enum variants can't be private, we can't modify builtin types
174         hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None,
175     };
176
177     Some((offset, target, target_file, target_name))
178 }
179
180 fn vis_offset(node: &SyntaxNode) -> TextSize {
181     node.children_with_tokens()
182         .skip_while(|it| match it.kind() {
183             WHITESPACE | COMMENT | ATTR => true,
184             _ => false,
185         })
186         .next()
187         .map(|it| it.text_range().start())
188         .unwrap_or_else(|| node.text_range().start())
189 }
190
191 #[cfg(test)]
192 mod tests {
193     use crate::tests::{check_assist, check_assist_not_applicable};
194
195     use super::*;
196
197     #[test]
198     fn fix_visibility_of_fn() {
199         check_assist(
200             fix_visibility,
201             r"mod foo { fn foo() {} }
202               fn main() { foo::foo<|>() } ",
203             r"mod foo { $0pub(crate) fn foo() {} }
204               fn main() { foo::foo() } ",
205         );
206         check_assist_not_applicable(
207             fix_visibility,
208             r"mod foo { pub fn foo() {} }
209               fn main() { foo::foo<|>() } ",
210         )
211     }
212
213     #[test]
214     fn fix_visibility_of_adt_in_submodule() {
215         check_assist(
216             fix_visibility,
217             r"mod foo { struct Foo; }
218               fn main() { foo::Foo<|> } ",
219             r"mod foo { $0pub(crate) struct Foo; }
220               fn main() { foo::Foo } ",
221         );
222         check_assist_not_applicable(
223             fix_visibility,
224             r"mod foo { pub struct Foo; }
225               fn main() { foo::Foo<|> } ",
226         );
227         check_assist(
228             fix_visibility,
229             r"mod foo { enum Foo; }
230               fn main() { foo::Foo<|> } ",
231             r"mod foo { $0pub(crate) enum Foo; }
232               fn main() { foo::Foo } ",
233         );
234         check_assist_not_applicable(
235             fix_visibility,
236             r"mod foo { pub enum Foo; }
237               fn main() { foo::Foo<|> } ",
238         );
239         check_assist(
240             fix_visibility,
241             r"mod foo { union Foo; }
242               fn main() { foo::Foo<|> } ",
243             r"mod foo { $0pub(crate) union Foo; }
244               fn main() { foo::Foo } ",
245         );
246         check_assist_not_applicable(
247             fix_visibility,
248             r"mod foo { pub union Foo; }
249               fn main() { foo::Foo<|> } ",
250         );
251     }
252
253     #[test]
254     fn fix_visibility_of_adt_in_other_file() {
255         check_assist(
256             fix_visibility,
257             r"
258 //- /main.rs
259 mod foo;
260 fn main() { foo::Foo<|> }
261
262 //- /foo.rs
263 struct Foo;
264 ",
265             r"$0pub(crate) struct Foo;
266 ",
267         );
268     }
269
270     #[test]
271     fn fix_visibility_of_struct_field() {
272         check_assist(
273             fix_visibility,
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: () }; } ",
278         );
279         check_assist(
280             fix_visibility,
281             r"
282 //- /lib.rs
283 mod foo;
284 fn main() { foo::Foo { <|>bar: () }; }
285 //- /foo.rs
286 pub struct Foo { bar: () }
287 ",
288             r"pub struct Foo { $0pub(crate) bar: () }
289 ",
290         );
291         check_assist_not_applicable(
292             fix_visibility,
293             r"mod foo { pub struct Foo { pub bar: (), } }
294               fn main() { foo::Foo { <|>bar: () }; } ",
295         );
296         check_assist_not_applicable(
297             fix_visibility,
298             r"
299 //- /lib.rs
300 mod foo;
301 fn main() { foo::Foo { <|>bar: () }; }
302 //- /foo.rs
303 pub struct Foo { pub bar: () }
304 ",
305         );
306     }
307
308     #[test]
309     fn fix_visibility_of_enum_variant_field() {
310         check_assist(
311             fix_visibility,
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: () }; } ",
316         );
317         check_assist(
318             fix_visibility,
319             r"
320 //- /lib.rs
321 mod foo;
322 fn main() { foo::Foo::Bar { <|>bar: () }; }
323 //- /foo.rs
324 pub enum Foo { Bar { bar: () } }
325 ",
326             r"pub enum Foo { Bar { $0pub(crate) bar: () } }
327 ",
328         );
329         check_assist_not_applicable(
330             fix_visibility,
331             r"mod foo { pub struct Foo { pub bar: (), } }
332               fn main() { foo::Foo { <|>bar: () }; } ",
333         );
334         check_assist_not_applicable(
335             fix_visibility,
336             r"
337 //- /lib.rs
338 mod foo;
339 fn main() { foo::Foo { <|>bar: () }; }
340 //- /foo.rs
341 pub struct Foo { pub bar: () }
342 ",
343         );
344     }
345
346     #[test]
347     #[ignore]
348     // FIXME reenable this test when `Semantics::resolve_record_field` works with union fields
349     fn fix_visibility_of_union_field() {
350         check_assist(
351             fix_visibility,
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: () }; } ",
356         );
357         check_assist(
358             fix_visibility,
359             r"
360 //- /lib.rs
361 mod foo;
362 fn main() { foo::Foo { <|>bar: () }; }
363 //- /foo.rs
364 pub union Foo { bar: () }
365 ",
366             r"pub union Foo { $0pub(crate) bar: () }
367 ",
368         );
369         check_assist_not_applicable(
370             fix_visibility,
371             r"mod foo { pub union Foo { pub bar: (), } }
372               fn main() { foo::Foo { <|>bar: () }; } ",
373         );
374         check_assist_not_applicable(
375             fix_visibility,
376             r"
377 //- /lib.rs
378 mod foo;
379 fn main() { foo::Foo { <|>bar: () }; }
380 //- /foo.rs
381 pub union Foo { pub bar: () }
382 ",
383         );
384     }
385
386     #[test]
387     fn fix_visibility_of_const() {
388         check_assist(
389             fix_visibility,
390             r"mod foo { const FOO: () = (); }
391               fn main() { foo::FOO<|> } ",
392             r"mod foo { $0pub(crate) const FOO: () = (); }
393               fn main() { foo::FOO } ",
394         );
395         check_assist_not_applicable(
396             fix_visibility,
397             r"mod foo { pub const FOO: () = (); }
398               fn main() { foo::FOO<|> } ",
399         );
400     }
401
402     #[test]
403     fn fix_visibility_of_static() {
404         check_assist(
405             fix_visibility,
406             r"mod foo { static FOO: () = (); }
407               fn main() { foo::FOO<|> } ",
408             r"mod foo { $0pub(crate) static FOO: () = (); }
409               fn main() { foo::FOO } ",
410         );
411         check_assist_not_applicable(
412             fix_visibility,
413             r"mod foo { pub static FOO: () = (); }
414               fn main() { foo::FOO<|> } ",
415         );
416     }
417
418     #[test]
419     fn fix_visibility_of_trait() {
420         check_assist(
421             fix_visibility,
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; } ",
426         );
427         check_assist_not_applicable(
428             fix_visibility,
429             r"mod foo { pub trait Foo { fn foo(&self) {} } }
430               fn main() { let x: &dyn foo::Foo<|>; } ",
431         );
432     }
433
434     #[test]
435     fn fix_visibility_of_type_alias() {
436         check_assist(
437             fix_visibility,
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; } ",
442         );
443         check_assist_not_applicable(
444             fix_visibility,
445             r"mod foo { pub type Foo = (); }
446               fn main() { let x: foo::Foo<|>; } ",
447         );
448     }
449
450     #[test]
451     fn fix_visibility_of_module() {
452         check_assist(
453             fix_visibility,
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(); } ",
458         );
459
460         check_assist(
461             fix_visibility,
462             r"
463 //- /main.rs
464 mod foo;
465 fn main() { foo::bar<|>::baz(); }
466
467 //- /foo.rs
468 mod bar {
469     pub fn baz() {}
470 }
471 ",
472             r"$0pub(crate) mod bar {
473     pub fn baz() {}
474 }
475 ",
476         );
477
478         check_assist_not_applicable(
479             fix_visibility,
480             r"mod foo { pub mod bar { pub fn bar() {} } }
481               fn main() { foo::bar<|>::bar(); } ",
482         );
483     }
484
485     #[test]
486     fn fix_visibility_of_inline_module_in_other_file() {
487         check_assist(
488             fix_visibility,
489             r"
490 //- /main.rs
491 mod foo;
492 fn main() { foo::bar<|>::baz(); }
493
494 //- /foo.rs
495 mod bar;
496 //- /foo/bar.rs
497 pub fn baz() {}
498 ",
499             r"$0pub(crate) mod bar;
500 ",
501         );
502     }
503
504     #[test]
505     fn fix_visibility_of_module_declaration_in_other_file() {
506         check_assist(
507             fix_visibility,
508             r"
509 //- /main.rs
510 mod foo;
511 fn main() { foo::bar<|>>::baz(); }
512
513 //- /foo.rs
514 mod bar {
515     pub fn baz() {}
516 }
517 ",
518             r"$0pub(crate) mod bar {
519     pub fn baz() {}
520 }
521 ",
522         );
523     }
524
525     #[test]
526     fn adds_pub_when_target_is_in_another_crate() {
527         check_assist(
528             fix_visibility,
529             r"
530 //- /main.rs crate:a deps:foo
531 foo::Bar<|>
532 //- /lib.rs crate:foo
533 struct Bar;
534 ",
535             r"$0pub struct Bar;
536 ",
537         )
538     }
539
540     #[test]
541     #[ignore]
542     // FIXME handle reexports properly
543     fn fix_visibility_of_reexport() {
544         check_assist(
545             fix_visibility,
546             r"
547             mod foo {
548                 use bar::Baz;
549                 mod bar { pub(super) struct Baz; }
550             }
551             foo::Baz<|>
552             ",
553             r"
554             mod foo {
555                 $0pub(crate) use bar::Baz;
556                 mod bar { pub(super) struct Baz; }
557             }
558             foo::Baz
559             ",
560         )
561     }
562 }