1use hir::{HasSource, HasVisibility, ModuleDef, PathResolution, ScopeDef, db::HirDatabase};
2use ide_db::FileId;
3use syntax::{
4 AstNode, TextRange,
5 ast::{self, HasVisibility as _, edit_in_place::HasVisibilityEdit, make},
6};
7
8use crate::{AssistContext, AssistId, Assists};
9
10pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
34 add_vis_to_referenced_module_def(acc, ctx)
35}
36
37fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
38 let path: ast::Path = ctx.find_node_at_offset()?;
39 let qualifier = path.qualifier()?;
40 let name_ref = path.segment()?.name_ref()?;
41 let qualifier_res = ctx.sema.resolve_path(&qualifier)?;
42 let PathResolution::Def(ModuleDef::Module(module)) = qualifier_res else {
43 return None;
44 };
45 let (_, def) = module
46 .scope(ctx.db(), None)
47 .into_iter()
48 .find(|(name, _)| name.as_str() == name_ref.text().trim_start_matches("r#"))?;
49 let ScopeDef::ModuleDef(def) = def else {
50 return None;
51 };
52
53 let current_module = ctx.sema.scope(path.syntax())?.module();
54 let target_module = def.module(ctx.db())?;
55
56 if def.visibility(ctx.db()).is_visible_from(ctx.db(), current_module.into()) {
57 return None;
58 };
59
60 let (vis_owner, target, target_file, target_name) = target_data_for_def(ctx.db(), def)?;
61
62 let missing_visibility = if current_module.krate(ctx.db()) == target_module.krate(ctx.db()) {
63 make::visibility_pub_crate()
64 } else {
65 make::visibility_pub()
66 };
67
68 let assist_label = match target_name {
69 None => format!("Change visibility to {missing_visibility}"),
70 Some(name) => {
71 format!(
72 "Change visibility of {} to {missing_visibility}",
73 name.display(ctx.db(), current_module.krate(ctx.db()).edition(ctx.db()))
74 )
75 }
76 };
77
78 acc.add(AssistId::quick_fix("fix_visibility"), assist_label, target, |edit| {
79 edit.edit_file(target_file);
80
81 let vis_owner = edit.make_mut(vis_owner);
82 vis_owner.set_visibility(Some(missing_visibility.clone_for_update()));
83
84 if let Some((cap, vis)) = ctx.config.snippet_cap.zip(vis_owner.visibility()) {
85 edit.add_tabstop_before(cap, vis);
86 }
87 })
88}
89
90fn target_data_for_def(
91 db: &dyn HirDatabase,
92 def: hir::ModuleDef,
93) -> Option<(ast::AnyHasVisibility, TextRange, FileId, Option<hir::Name>)> {
94 fn offset_target_and_file_id<S, Ast>(
95 db: &dyn HirDatabase,
96 x: S,
97 ) -> Option<(ast::AnyHasVisibility, TextRange, FileId)>
98 where
99 S: HasSource<Ast = Ast>,
100 Ast: AstNode + ast::HasVisibility,
101 {
102 let source = x.source(db)?;
103 let in_file_syntax = source.syntax();
104 let file_id = in_file_syntax.file_id;
105 let range = in_file_syntax.value.text_range();
106 Some((
107 ast::AnyHasVisibility::new(source.value),
108 range,
109 file_id.original_file(db).file_id(db),
110 ))
111 }
112
113 let target_name;
114 let (offset, target, target_file) = match def {
115 hir::ModuleDef::Function(f) => {
116 target_name = Some(f.name(db));
117 offset_target_and_file_id(db, f)?
118 }
119 hir::ModuleDef::Adt(adt) => {
120 target_name = Some(adt.name(db));
121 match adt {
122 hir::Adt::Struct(s) => offset_target_and_file_id(db, s)?,
123 hir::Adt::Union(u) => offset_target_and_file_id(db, u)?,
124 hir::Adt::Enum(e) => offset_target_and_file_id(db, e)?,
125 }
126 }
127 hir::ModuleDef::Const(c) => {
128 target_name = c.name(db);
129 offset_target_and_file_id(db, c)?
130 }
131 hir::ModuleDef::Static(s) => {
132 target_name = Some(s.name(db));
133 offset_target_and_file_id(db, s)?
134 }
135 hir::ModuleDef::Trait(t) => {
136 target_name = Some(t.name(db));
137 offset_target_and_file_id(db, t)?
138 }
139 hir::ModuleDef::TypeAlias(t) => {
140 target_name = Some(t.name(db));
141 offset_target_and_file_id(db, t)?
142 }
143 hir::ModuleDef::Module(m) => {
144 target_name = m.name(db);
145 let in_file_source = m.declaration_source(db)?;
146 let file_id = in_file_source.file_id.original_file(db);
147 let range = in_file_source.value.syntax().text_range();
148 (ast::AnyHasVisibility::new(in_file_source.value), range, file_id.file_id(db))
149 }
150 hir::ModuleDef::Macro(_) => return None,
152 hir::ModuleDef::Variant(_) | hir::ModuleDef::BuiltinType(_) => return None,
154 };
155
156 Some((offset, target, target_file, target_name))
157}
158
159#[cfg(test)]
160mod tests {
161 use crate::tests::{check_assist, check_assist_not_applicable};
162
163 use super::*;
164
165 #[test]
166 fn fix_visibility_of_fn() {
167 check_assist(
168 fix_visibility,
169 r"mod foo { fn foo() {} }
170 fn main() { foo::foo$0() } ",
171 r"mod foo { $0pub(crate) fn foo() {} }
172 fn main() { foo::foo() } ",
173 );
174 check_assist_not_applicable(
175 fix_visibility,
176 r"mod foo { pub fn foo() {} }
177 fn main() { foo::foo$0() } ",
178 )
179 }
180
181 #[test]
182 fn fix_visibility_of_adt_in_submodule() {
183 check_assist(
184 fix_visibility,
185 r"mod foo { struct Foo; }
186 fn main() { foo::Foo$0 } ",
187 r"mod foo { $0pub(crate) struct Foo; }
188 fn main() { foo::Foo } ",
189 );
190 check_assist_not_applicable(
191 fix_visibility,
192 r"mod foo { pub struct Foo; }
193 fn main() { foo::Foo$0 } ",
194 );
195 check_assist(
196 fix_visibility,
197 r"mod foo { enum Foo; }
198 fn main() { foo::Foo$0 } ",
199 r"mod foo { $0pub(crate) enum Foo; }
200 fn main() { foo::Foo } ",
201 );
202 check_assist_not_applicable(
203 fix_visibility,
204 r"mod foo { pub enum Foo; }
205 fn main() { foo::Foo$0 } ",
206 );
207 check_assist(
208 fix_visibility,
209 r"mod foo { union Foo; }
210 fn main() { foo::Foo$0 } ",
211 r"mod foo { $0pub(crate) union Foo; }
212 fn main() { foo::Foo } ",
213 );
214 check_assist_not_applicable(
215 fix_visibility,
216 r"mod foo { pub union Foo; }
217 fn main() { foo::Foo$0 } ",
218 );
219 }
220
221 #[test]
222 fn fix_visibility_of_adt_in_other_file() {
223 check_assist(
224 fix_visibility,
225 r"
226//- /main.rs
227mod foo;
228fn main() { foo::Foo$0 }
229
230//- /foo.rs
231struct Foo;
232",
233 r"$0pub(crate) struct Foo;
234",
235 );
236 }
237
238 #[test]
239 fn fix_visibility_of_enum_variant_field() {
240 check_assist_not_applicable(
243 fix_visibility,
244 r"mod foo { pub enum Foo { Bar { bar: () } } }
245 fn main() { foo::Foo::Bar { $0bar: () }; } ",
246 );
247 check_assist_not_applicable(
248 fix_visibility,
249 r"
250//- /lib.rs
251mod foo;
252fn main() { foo::Foo::Bar { $0bar: () }; }
253//- /foo.rs
254pub enum Foo { Bar { bar: () } }
255",
256 );
257 check_assist_not_applicable(
258 fix_visibility,
259 r"mod foo { pub struct Foo { pub bar: (), } }
260 fn main() { foo::Foo { $0bar: () }; } ",
261 );
262 check_assist_not_applicable(
263 fix_visibility,
264 r"
265//- /lib.rs
266mod foo;
267fn main() { foo::Foo { $0bar: () }; }
268//- /foo.rs
269pub struct Foo { pub bar: () }
270",
271 );
272 }
273
274 #[test]
275 fn fix_visibility_of_const() {
276 check_assist(
277 fix_visibility,
278 r"mod foo { const FOO: () = (); }
279 fn main() { foo::FOO$0 } ",
280 r"mod foo { $0pub(crate) const FOO: () = (); }
281 fn main() { foo::FOO } ",
282 );
283 check_assist_not_applicable(
284 fix_visibility,
285 r"mod foo { pub const FOO: () = (); }
286 fn main() { foo::FOO$0 } ",
287 );
288 }
289
290 #[test]
291 fn fix_visibility_of_static() {
292 check_assist(
293 fix_visibility,
294 r"mod foo { static FOO: () = (); }
295 fn main() { foo::FOO$0 } ",
296 r"mod foo { $0pub(crate) static FOO: () = (); }
297 fn main() { foo::FOO } ",
298 );
299 check_assist_not_applicable(
300 fix_visibility,
301 r"mod foo { pub static FOO: () = (); }
302 fn main() { foo::FOO$0 } ",
303 );
304 }
305
306 #[test]
307 fn fix_visibility_of_trait() {
308 check_assist(
309 fix_visibility,
310 r"mod foo { trait Foo { fn foo(&self) {} } }
311 fn main() { let x: &dyn foo::$0Foo; } ",
312 r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } }
313 fn main() { let x: &dyn foo::Foo; } ",
314 );
315 check_assist_not_applicable(
316 fix_visibility,
317 r"mod foo { pub trait Foo { fn foo(&self) {} } }
318 fn main() { let x: &dyn foo::Foo$0; } ",
319 );
320 }
321
322 #[test]
323 fn fix_visibility_of_type_alias() {
324 check_assist(
325 fix_visibility,
326 r"mod foo { type Foo = (); }
327 fn main() { let x: foo::Foo$0; } ",
328 r"mod foo { $0pub(crate) type Foo = (); }
329 fn main() { let x: foo::Foo; } ",
330 );
331 check_assist_not_applicable(
332 fix_visibility,
333 r"mod foo { pub type Foo = (); }
334 fn main() { let x: foo::Foo$0; } ",
335 );
336 }
337
338 #[test]
339 fn fix_visibility_of_module() {
340 check_assist(
341 fix_visibility,
342 r"mod foo { mod bar { fn bar() {} } }
343 fn main() { foo::bar$0::bar(); } ",
344 r"mod foo { $0pub(crate) mod bar { fn bar() {} } }
345 fn main() { foo::bar::bar(); } ",
346 );
347
348 check_assist(
349 fix_visibility,
350 r"
351//- /main.rs
352mod foo;
353fn main() { foo::bar$0::baz(); }
354
355//- /foo.rs
356mod bar {
357 pub fn baz() {}
358}
359",
360 r"$0pub(crate) mod bar {
361 pub fn baz() {}
362}
363",
364 );
365
366 check_assist_not_applicable(
367 fix_visibility,
368 r"mod foo { pub mod bar { pub fn bar() {} } }
369 fn main() { foo::bar$0::bar(); } ",
370 );
371 }
372
373 #[test]
374 fn fix_visibility_of_inline_module_in_other_file() {
375 check_assist(
376 fix_visibility,
377 r"
378//- /main.rs
379mod foo;
380fn main() { foo::bar$0::baz(); }
381
382//- /foo.rs
383mod bar;
384//- /foo/bar.rs
385pub fn baz() {}
386",
387 r"$0pub(crate) mod bar;
388",
389 );
390 }
391
392 #[test]
393 fn fix_visibility_of_module_declaration_in_other_file() {
394 check_assist(
395 fix_visibility,
396 r"
397//- /main.rs
398mod foo;
399fn main() { foo::bar$0>::baz(); }
400
401//- /foo.rs
402mod bar {
403 pub fn baz() {}
404}
405",
406 r"$0pub(crate) mod bar {
407 pub fn baz() {}
408}
409",
410 );
411 }
412
413 #[test]
414 fn adds_pub_when_target_is_in_another_crate() {
415 check_assist(
416 fix_visibility,
417 r"
418//- /main.rs crate:a deps:foo
419foo::Bar$0
420//- /lib.rs crate:foo
421struct Bar;
422",
423 r"$0pub struct Bar;
424",
425 )
426 }
427
428 #[test]
429 fn replaces_pub_crate_with_pub() {
430 check_assist(
431 fix_visibility,
432 r"
433//- /main.rs crate:a deps:foo
434foo::Bar$0
435//- /lib.rs crate:foo
436pub(crate) struct Bar;
437",
438 r"$0pub struct Bar;
439",
440 );
441 }
442
443 #[test]
444 fn fix_visibility_of_reexport() {
445 check_assist(
448 fix_visibility,
449 r#"
450mod foo {
451 use bar::Baz;
452 mod bar { pub(super) struct Baz; }
453}
454foo::Baz$0
455"#,
456 r#"
457mod foo {
458 use bar::Baz;
459 mod bar { $0pub(crate) struct Baz; }
460}
461foo::Baz
462"#,
463 )
464 }
465}