aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/handlers/fix_visibility.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/handlers/fix_visibility.rs')
-rw-r--r--crates/ide_assists/src/handlers/fix_visibility.rs607
1 files changed, 607 insertions, 0 deletions
diff --git a/crates/ide_assists/src/handlers/fix_visibility.rs b/crates/ide_assists/src/handlers/fix_visibility.rs
new file mode 100644
index 000000000..6c7824e55
--- /dev/null
+++ b/crates/ide_assists/src/handlers/fix_visibility.rs
@@ -0,0 +1,607 @@
1use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
2use ide_db::base_db::FileId;
3use syntax::{
4 ast::{self, VisibilityOwner},
5 AstNode, TextRange, TextSize,
6};
7
8use 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// ```
33pub(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
38fn 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 let vis = target_module.visibility_of(ctx.db(), &def)?;
50 if vis.is_visible_from(ctx.db(), current_module.into()) {
51 return None;
52 };
53
54 let (offset, current_visibility, target, target_file, target_name) =
55 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", AssistKind::QuickFix), assist_label, target, |builder| {
66 builder.edit_file(target_file);
67 match ctx.config.snippet_cap {
68 Some(cap) => match current_visibility {
69 Some(current_visibility) => builder.replace_snippet(
70 cap,
71 current_visibility.syntax().text_range(),
72 format!("$0{}", missing_visibility),
73 ),
74 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
75 },
76 None => match current_visibility {
77 Some(current_visibility) => {
78 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
79 }
80 None => builder.insert(offset, format!("{} ", missing_visibility)),
81 },
82 }
83 })
84}
85
86fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
87 let record_field: ast::RecordExprField = ctx.find_node_at_offset()?;
88 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?;
89
90 let current_module = ctx.sema.scope(record_field.syntax()).module()?;
91 let visibility = record_field_def.visibility(ctx.db());
92 if visibility.is_visible_from(ctx.db(), current_module.into()) {
93 return None;
94 }
95
96 let parent = record_field_def.parent_def(ctx.db());
97 let parent_name = parent.name(ctx.db());
98 let target_module = parent.module(ctx.db());
99
100 let in_file_source = record_field_def.source(ctx.db())?;
101 let (offset, current_visibility, target) = match in_file_source.value {
102 hir::FieldSource::Named(it) => {
103 let s = it.syntax();
104 (vis_offset(s), it.visibility(), s.text_range())
105 }
106 hir::FieldSource::Pos(it) => {
107 let s = it.syntax();
108 (vis_offset(s), it.visibility(), s.text_range())
109 }
110 };
111
112 let missing_visibility =
113 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
114 let target_file = in_file_source.file_id.original_file(ctx.db());
115
116 let target_name = record_field_def.name(ctx.db());
117 let assist_label =
118 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
119
120 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
121 builder.edit_file(target_file);
122 match ctx.config.snippet_cap {
123 Some(cap) => match current_visibility {
124 Some(current_visibility) => builder.replace_snippet(
125 cap,
126 current_visibility.syntax().text_range(),
127 format!("$0{}", missing_visibility),
128 ),
129 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
130 },
131 None => match current_visibility {
132 Some(current_visibility) => {
133 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
134 }
135 None => builder.insert(offset, format!("{} ", missing_visibility)),
136 },
137 }
138 })
139}
140
141fn target_data_for_def(
142 db: &dyn HirDatabase,
143 def: hir::ModuleDef,
144) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
145 fn offset_target_and_file_id<S, Ast>(
146 db: &dyn HirDatabase,
147 x: S,
148 ) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId)>
149 where
150 S: HasSource<Ast = Ast>,
151 Ast: AstNode + ast::VisibilityOwner,
152 {
153 let source = x.source(db)?;
154 let in_file_syntax = source.syntax();
155 let file_id = in_file_syntax.file_id;
156 let syntax = in_file_syntax.value;
157 let current_visibility = source.value.visibility();
158 Some((
159 vis_offset(syntax),
160 current_visibility,
161 syntax.text_range(),
162 file_id.original_file(db.upcast()),
163 ))
164 }
165
166 let target_name;
167 let (offset, current_visibility, target, target_file) = match def {
168 hir::ModuleDef::Function(f) => {
169 target_name = Some(f.name(db));
170 offset_target_and_file_id(db, f)?
171 }
172 hir::ModuleDef::Adt(adt) => {
173 target_name = Some(adt.name(db));
174 match adt {
175 hir::Adt::Struct(s) => offset_target_and_file_id(db, s)?,
176 hir::Adt::Union(u) => offset_target_and_file_id(db, u)?,
177 hir::Adt::Enum(e) => offset_target_and_file_id(db, e)?,
178 }
179 }
180 hir::ModuleDef::Const(c) => {
181 target_name = c.name(db);
182 offset_target_and_file_id(db, c)?
183 }
184 hir::ModuleDef::Static(s) => {
185 target_name = s.name(db);
186 offset_target_and_file_id(db, s)?
187 }
188 hir::ModuleDef::Trait(t) => {
189 target_name = Some(t.name(db));
190 offset_target_and_file_id(db, t)?
191 }
192 hir::ModuleDef::TypeAlias(t) => {
193 target_name = Some(t.name(db));
194 offset_target_and_file_id(db, t)?
195 }
196 hir::ModuleDef::Module(m) => {
197 target_name = m.name(db);
198 let in_file_source = m.declaration_source(db)?;
199 let file_id = in_file_source.file_id.original_file(db.upcast());
200 let syntax = in_file_source.value.syntax();
201 (vis_offset(syntax), in_file_source.value.visibility(), syntax.text_range(), file_id)
202 }
203 // Enum variants can't be private, we can't modify builtin types
204 hir::ModuleDef::Variant(_) | hir::ModuleDef::BuiltinType(_) => return None,
205 };
206
207 Some((offset, current_visibility, target, target_file, target_name))
208}
209
210#[cfg(test)]
211mod tests {
212 use crate::tests::{check_assist, check_assist_not_applicable};
213
214 use super::*;
215
216 #[test]
217 fn fix_visibility_of_fn() {
218 check_assist(
219 fix_visibility,
220 r"mod foo { fn foo() {} }
221 fn main() { foo::foo$0() } ",
222 r"mod foo { $0pub(crate) fn foo() {} }
223 fn main() { foo::foo() } ",
224 );
225 check_assist_not_applicable(
226 fix_visibility,
227 r"mod foo { pub fn foo() {} }
228 fn main() { foo::foo$0() } ",
229 )
230 }
231
232 #[test]
233 fn fix_visibility_of_adt_in_submodule() {
234 check_assist(
235 fix_visibility,
236 r"mod foo { struct Foo; }
237 fn main() { foo::Foo$0 } ",
238 r"mod foo { $0pub(crate) struct Foo; }
239 fn main() { foo::Foo } ",
240 );
241 check_assist_not_applicable(
242 fix_visibility,
243 r"mod foo { pub struct Foo; }
244 fn main() { foo::Foo$0 } ",
245 );
246 check_assist(
247 fix_visibility,
248 r"mod foo { enum Foo; }
249 fn main() { foo::Foo$0 } ",
250 r"mod foo { $0pub(crate) enum Foo; }
251 fn main() { foo::Foo } ",
252 );
253 check_assist_not_applicable(
254 fix_visibility,
255 r"mod foo { pub enum Foo; }
256 fn main() { foo::Foo$0 } ",
257 );
258 check_assist(
259 fix_visibility,
260 r"mod foo { union Foo; }
261 fn main() { foo::Foo$0 } ",
262 r"mod foo { $0pub(crate) union Foo; }
263 fn main() { foo::Foo } ",
264 );
265 check_assist_not_applicable(
266 fix_visibility,
267 r"mod foo { pub union Foo; }
268 fn main() { foo::Foo$0 } ",
269 );
270 }
271
272 #[test]
273 fn fix_visibility_of_adt_in_other_file() {
274 check_assist(
275 fix_visibility,
276 r"
277//- /main.rs
278mod foo;
279fn main() { foo::Foo$0 }
280
281//- /foo.rs
282struct Foo;
283",
284 r"$0pub(crate) struct Foo;
285",
286 );
287 }
288
289 #[test]
290 fn fix_visibility_of_struct_field() {
291 check_assist(
292 fix_visibility,
293 r"mod foo { pub struct Foo { bar: (), } }
294 fn main() { foo::Foo { $0bar: () }; } ",
295 r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
296 fn main() { foo::Foo { bar: () }; } ",
297 );
298 check_assist(
299 fix_visibility,
300 r"
301//- /lib.rs
302mod foo;
303fn main() { foo::Foo { $0bar: () }; }
304//- /foo.rs
305pub struct Foo { bar: () }
306",
307 r"pub struct Foo { $0pub(crate) bar: () }
308",
309 );
310 check_assist_not_applicable(
311 fix_visibility,
312 r"mod foo { pub struct Foo { pub bar: (), } }
313 fn main() { foo::Foo { $0bar: () }; } ",
314 );
315 check_assist_not_applicable(
316 fix_visibility,
317 r"
318//- /lib.rs
319mod foo;
320fn main() { foo::Foo { $0bar: () }; }
321//- /foo.rs
322pub struct Foo { pub bar: () }
323",
324 );
325 }
326
327 #[test]
328 fn fix_visibility_of_enum_variant_field() {
329 // Enum variants, as well as their fields, always get the enum's visibility. In fact, rustc
330 // rejects any visibility specifiers on them, so this assist should never fire on them.
331 check_assist_not_applicable(
332 fix_visibility,
333 r"mod foo { pub enum Foo { Bar { bar: () } } }
334 fn main() { foo::Foo::Bar { $0bar: () }; } ",
335 );
336 check_assist_not_applicable(
337 fix_visibility,
338 r"
339//- /lib.rs
340mod foo;
341fn main() { foo::Foo::Bar { $0bar: () }; }
342//- /foo.rs
343pub enum Foo { Bar { 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 { $0bar: () }; } ",
350 );
351 check_assist_not_applicable(
352 fix_visibility,
353 r"
354//- /lib.rs
355mod foo;
356fn main() { foo::Foo { $0bar: () }; }
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 { $0bar: () }; } ",
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 { $0bar: () }; }
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 { $0bar: () }; } ",
390 );
391 check_assist_not_applicable(
392 fix_visibility,
393 r"
394//- /lib.rs
395mod foo;
396fn main() { foo::Foo { $0bar: () }; }
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$0 } ",
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$0 } ",
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$0 } ",
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$0 } ",
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::$0Foo; } ",
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$0; } ",
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$0; } ",
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$0; } ",
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$0::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$0::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$0::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$0::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$0>::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$0
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$0
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 { $0bar: () };
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$0
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}