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