aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers/fix_visibility.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists/src/handlers/fix_visibility.rs')
-rw-r--r--crates/assists/src/handlers/fix_visibility.rs607
1 files changed, 607 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/fix_visibility.rs b/crates/assists/src/handlers/fix_visibility.rs
new file mode 100644
index 000000000..7cd76ea06
--- /dev/null
+++ b/crates/assists/src/handlers/fix_visibility.rs
@@ -0,0 +1,607 @@
1use base_db::FileId;
2use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
3use syntax::{ast, AstNode, TextRange, TextSize};
4
5use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
6use ast::VisibilityOwner;
7
8// FIXME: this really should be a fix for diagnostic, rather than an assist.
9
10// Assist: fix_visibility
11//
12// Makes inaccessible item public.
13//
14// ```
15// mod m {
16// fn frobnicate() {}
17// }
18// fn main() {
19// m::frobnicate<|>() {}
20// }
21// ```
22// ->
23// ```
24// mod m {
25// $0pub(crate) fn frobnicate() {}
26// }
27// fn main() {
28// m::frobnicate() {}
29// }
30// ```
31pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 add_vis_to_referenced_module_def(acc, ctx)
33 .or_else(|| add_vis_to_referenced_record_field(acc, ctx))
34}
35
36fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
37 let path: ast::Path = ctx.find_node_at_offset()?;
38 let path_res = ctx.sema.resolve_path(&path)?;
39 let def = match path_res {
40 PathResolution::Def(def) => def,
41 _ => return None,
42 };
43
44 let current_module = ctx.sema.scope(&path.syntax()).module()?;
45 let target_module = def.module(ctx.db())?;
46
47 let vis = target_module.visibility_of(ctx.db(), &def)?;
48 if vis.is_visible_from(ctx.db(), current_module.into()) {
49 return None;
50 };
51
52 let (offset, current_visibility, target, target_file, target_name) =
53 target_data_for_def(ctx.db(), def)?;
54
55 let missing_visibility =
56 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
57
58 let assist_label = match target_name {
59 None => format!("Change visibility to {}", missing_visibility),
60 Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
61 };
62
63 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
64 builder.edit_file(target_file);
65 match ctx.config.snippet_cap {
66 Some(cap) => match current_visibility {
67 Some(current_visibility) => builder.replace_snippet(
68 cap,
69 current_visibility.syntax().text_range(),
70 format!("$0{}", missing_visibility),
71 ),
72 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
73 },
74 None => match current_visibility {
75 Some(current_visibility) => {
76 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
77 }
78 None => builder.insert(offset, format!("{} ", missing_visibility)),
79 },
80 }
81 })
82}
83
84fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
85 let record_field: ast::RecordExprField = ctx.find_node_at_offset()?;
86 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?;
87
88 let current_module = ctx.sema.scope(record_field.syntax()).module()?;
89 let visibility = record_field_def.visibility(ctx.db());
90 if visibility.is_visible_from(ctx.db(), current_module.into()) {
91 return None;
92 }
93
94 let parent = record_field_def.parent_def(ctx.db());
95 let parent_name = parent.name(ctx.db());
96 let target_module = parent.module(ctx.db());
97
98 let in_file_source = record_field_def.source(ctx.db());
99 let (offset, current_visibility, target) = match in_file_source.value {
100 hir::FieldSource::Named(it) => {
101 let s = it.syntax();
102 (vis_offset(s), it.visibility(), s.text_range())
103 }
104 hir::FieldSource::Pos(it) => {
105 let s = it.syntax();
106 (vis_offset(s), it.visibility(), s.text_range())
107 }
108 };
109
110 let missing_visibility =
111 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
112 let target_file = in_file_source.file_id.original_file(ctx.db());
113
114 let target_name = record_field_def.name(ctx.db());
115 let assist_label =
116 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
117
118 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
119 builder.edit_file(target_file);
120 match ctx.config.snippet_cap {
121 Some(cap) => match current_visibility {
122 Some(current_visibility) => builder.replace_snippet(
123 cap,
124 current_visibility.syntax().text_range(),
125 format!("$0{}", missing_visibility),
126 ),
127 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
128 },
129 None => match current_visibility {
130 Some(current_visibility) => {
131 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
132 }
133 None => builder.insert(offset, format!("{} ", missing_visibility)),
134 },
135 }
136 })
137}
138
139fn target_data_for_def(
140 db: &dyn HirDatabase,
141 def: hir::ModuleDef,
142) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
143 fn offset_target_and_file_id<S, Ast>(
144 db: &dyn HirDatabase,
145 x: S,
146 ) -> (TextSize, Option<ast::Visibility>, TextRange, FileId)
147 where
148 S: HasSource<Ast = Ast>,
149 Ast: AstNode + ast::VisibilityOwner,
150 {
151 let source = x.source(db);
152 let in_file_syntax = source.syntax();
153 let file_id = in_file_syntax.file_id;
154 let syntax = in_file_syntax.value;
155 let current_visibility = source.value.visibility();
156 (
157 vis_offset(syntax),
158 current_visibility,
159 syntax.text_range(),
160 file_id.original_file(db.upcast()),
161 )
162 }
163
164 let target_name;
165 let (offset, current_visibility, target, target_file) = match def {
166 hir::ModuleDef::Function(f) => {
167 target_name = Some(f.name(db));
168 offset_target_and_file_id(db, f)
169 }
170 hir::ModuleDef::Adt(adt) => {
171 target_name = Some(adt.name(db));
172 match adt {
173 hir::Adt::Struct(s) => offset_target_and_file_id(db, s),
174 hir::Adt::Union(u) => offset_target_and_file_id(db, u),
175 hir::Adt::Enum(e) => offset_target_and_file_id(db, e),
176 }
177 }
178 hir::ModuleDef::Const(c) => {
179 target_name = c.name(db);
180 offset_target_and_file_id(db, c)
181 }
182 hir::ModuleDef::Static(s) => {
183 target_name = s.name(db);
184 offset_target_and_file_id(db, s)
185 }
186 hir::ModuleDef::Trait(t) => {
187 target_name = Some(t.name(db));
188 offset_target_and_file_id(db, t)
189 }
190 hir::ModuleDef::TypeAlias(t) => {
191 target_name = Some(t.name(db));
192 offset_target_and_file_id(db, t)
193 }
194 hir::ModuleDef::Module(m) => {
195 target_name = m.name(db);
196 let in_file_source = m.declaration_source(db)?;
197 let file_id = in_file_source.file_id.original_file(db.upcast());
198 let syntax = in_file_source.value.syntax();
199 (vis_offset(syntax), in_file_source.value.visibility(), syntax.text_range(), file_id)
200 }
201 // Enum variants can't be private, we can't modify builtin types
202 hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None,
203 };
204
205 Some((offset, current_visibility, target, target_file, target_name))
206}
207
208#[cfg(test)]
209mod tests {
210 use crate::tests::{check_assist, check_assist_not_applicable};
211
212 use super::*;
213
214 #[test]
215 fn fix_visibility_of_fn() {
216 check_assist(
217 fix_visibility,
218 r"mod foo { fn foo() {} }
219 fn main() { foo::foo<|>() } ",
220 r"mod foo { $0pub(crate) fn foo() {} }
221 fn main() { foo::foo() } ",
222 );
223 check_assist_not_applicable(
224 fix_visibility,
225 r"mod foo { pub fn foo() {} }
226 fn main() { foo::foo<|>() } ",
227 )
228 }
229
230 #[test]
231 fn fix_visibility_of_adt_in_submodule() {
232 check_assist(
233 fix_visibility,
234 r"mod foo { struct Foo; }
235 fn main() { foo::Foo<|> } ",
236 r"mod foo { $0pub(crate) struct Foo; }
237 fn main() { foo::Foo } ",
238 );
239 check_assist_not_applicable(
240 fix_visibility,
241 r"mod foo { pub struct Foo; }
242 fn main() { foo::Foo<|> } ",
243 );
244 check_assist(
245 fix_visibility,
246 r"mod foo { enum Foo; }
247 fn main() { foo::Foo<|> } ",
248 r"mod foo { $0pub(crate) enum Foo; }
249 fn main() { foo::Foo } ",
250 );
251 check_assist_not_applicable(
252 fix_visibility,
253 r"mod foo { pub enum Foo; }
254 fn main() { foo::Foo<|> } ",
255 );
256 check_assist(
257 fix_visibility,
258 r"mod foo { union Foo; }
259 fn main() { foo::Foo<|> } ",
260 r"mod foo { $0pub(crate) union Foo; }
261 fn main() { foo::Foo } ",
262 );
263 check_assist_not_applicable(
264 fix_visibility,
265 r"mod foo { pub union Foo; }
266 fn main() { foo::Foo<|> } ",
267 );
268 }
269
270 #[test]
271 fn fix_visibility_of_adt_in_other_file() {
272 check_assist(
273 fix_visibility,
274 r"
275//- /main.rs
276mod foo;
277fn main() { foo::Foo<|> }
278
279//- /foo.rs
280struct Foo;
281",
282 r"$0pub(crate) struct Foo;
283",
284 );
285 }
286
287 #[test]
288 fn fix_visibility_of_struct_field() {
289 check_assist(
290 fix_visibility,
291 r"mod foo { pub struct Foo { bar: (), } }
292 fn main() { foo::Foo { <|>bar: () }; } ",
293 r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
294 fn main() { foo::Foo { bar: () }; } ",
295 );
296 check_assist(
297 fix_visibility,
298 r"
299//- /lib.rs
300mod foo;
301fn main() { foo::Foo { <|>bar: () }; }
302//- /foo.rs
303pub struct Foo { bar: () }
304",
305 r"pub struct Foo { $0pub(crate) bar: () }
306",
307 );
308 check_assist_not_applicable(
309 fix_visibility,
310 r"mod foo { pub struct Foo { pub bar: (), } }
311 fn main() { foo::Foo { <|>bar: () }; } ",
312 );
313 check_assist_not_applicable(
314 fix_visibility,
315 r"
316//- /lib.rs
317mod foo;
318fn main() { foo::Foo { <|>bar: () }; }
319//- /foo.rs
320pub struct Foo { pub bar: () }
321",
322 );
323 }
324
325 #[test]
326 fn fix_visibility_of_enum_variant_field() {
327 check_assist(
328 fix_visibility,
329 r"mod foo { pub enum Foo { Bar { bar: () } } }
330 fn main() { foo::Foo::Bar { <|>bar: () }; } ",
331 r"mod foo { pub enum Foo { Bar { $0pub(crate) bar: () } } }
332 fn main() { foo::Foo::Bar { bar: () }; } ",
333 );
334 check_assist(
335 fix_visibility,
336 r"
337//- /lib.rs
338mod foo;
339fn main() { foo::Foo::Bar { <|>bar: () }; }
340//- /foo.rs
341pub enum Foo { Bar { bar: () } }
342",
343 r"pub enum Foo { Bar { $0pub(crate) 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 { <|>bar: () }; } ",
350 );
351 check_assist_not_applicable(
352 fix_visibility,
353 r"
354//- /lib.rs
355mod foo;
356fn main() { foo::Foo { <|>bar: () }; }
357//- /foo.rs
358pub 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 { <|>bar: () }; } ",
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
378mod foo;
379fn main() { foo::Foo { <|>bar: () }; }
380//- /foo.rs
381pub 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 { <|>bar: () }; } ",
390 );
391 check_assist_not_applicable(
392 fix_visibility,
393 r"
394//- /lib.rs
395mod foo;
396fn main() { foo::Foo { <|>bar: () }; }
397//- /foo.rs
398pub 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<|> } ",
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<|> } ",
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<|> } ",
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<|> } ",
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::<|>Foo; } ",
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<|>; } ",
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<|>; } ",
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<|>; } ",
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<|>::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
481mod foo;
482fn main() { foo::bar<|>::baz(); }
483
484//- /foo.rs
485mod 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<|>::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
508mod foo;
509fn main() { foo::bar<|>::baz(); }
510
511//- /foo.rs
512mod bar;
513//- /foo/bar.rs
514pub 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
527mod foo;
528fn main() { foo::bar<|>>::baz(); }
529
530//- /foo.rs
531mod 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
548foo::Bar<|>
549//- /lib.rs crate:foo
550struct 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
563foo::Bar<|>
564//- /lib.rs crate:foo
565pub(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
574fn main() {
575 foo::Foo { <|>bar: () };
576}
577//- /lib.rs crate:foo
578pub 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<|>
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}