1use hir::{HasSource, HasVisibility, ModuleDef, PathResolution, ScopeDef, db::HirDatabase};
2use ide_db::FileId;
3use syntax::{
4 AstNode, TextRange,
5 ast::{self, HasVisibility as _, syntax_factory::SyntaxFactory},
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 make = SyntaxFactory::without_mappings();
63
64 let missing_visibility = if current_module.krate(ctx.db()) == target_module.krate(ctx.db()) {
65 make.visibility_pub_crate()
66 } else {
67 make.visibility_pub()
68 };
69
70 let assist_label = match target_name {
71 None => format!("Change visibility to {missing_visibility}"),
72 Some(name) => {
73 format!(
74 "Change visibility of {} to {missing_visibility}",
75 name.display(ctx.db(), current_module.krate(ctx.db()).edition(ctx.db()))
76 )
77 }
78 };
79
80 acc.add(AssistId::quick_fix("fix_visibility"), assist_label, target, |builder| {
81 let editor = builder.make_editor(vis_owner.syntax());
82
83 if let Some(current_visibility) = vis_owner.visibility() {
84 editor.replace(current_visibility.syntax(), missing_visibility.syntax());
85 } else {
86 let vis_before = vis_owner
87 .syntax()
88 .children_with_tokens()
89 .find(|it| {
90 !matches!(
91 it.kind(),
92 syntax::SyntaxKind::WHITESPACE
93 | syntax::SyntaxKind::COMMENT
94 | syntax::SyntaxKind::ATTR
95 )
96 })
97 .unwrap_or_else(|| vis_owner.syntax().first_child_or_token().unwrap());
98
99 editor.insert_all(
100 syntax::syntax_editor::Position::before(vis_before),
101 vec![missing_visibility.syntax().clone().into(), make.whitespace(" ").into()],
102 );
103 }
104
105 if let Some(cap) = ctx.config.snippet_cap {
106 editor.add_annotation(missing_visibility.syntax(), builder.make_tabstop_before(cap));
107 }
108
109 builder.add_file_edits(target_file, editor);
110 })
111}
112
113fn target_data_for_def(
114 db: &dyn HirDatabase,
115 def: hir::ModuleDef,
116) -> Option<(ast::AnyHasVisibility, TextRange, FileId, Option<hir::Name>)> {
117 fn offset_target_and_file_id<S, Ast>(
118 db: &dyn HirDatabase,
119 x: S,
120 ) -> Option<(ast::AnyHasVisibility, TextRange, FileId)>
121 where
122 S: HasSource<Ast = Ast>,
123 Ast: AstNode + ast::HasVisibility,
124 {
125 let source = x.source(db)?;
126 let in_file_syntax = source.syntax();
127 let file_id = in_file_syntax.file_id;
128 let range = in_file_syntax.value.text_range();
129 Some((
130 ast::AnyHasVisibility::new(source.value),
131 range,
132 file_id.original_file(db).file_id(db),
133 ))
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 = Some(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);
170 let range = in_file_source.value.syntax().text_range();
171 (ast::AnyHasVisibility::new(in_file_source.value), range, file_id.file_id(db))
172 }
173 hir::ModuleDef::Macro(_) => return None,
175 hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None,
177 };
178
179 Some((offset, target, target_file, target_name))
180}
181
182#[cfg(test)]
183mod tests {
184 use crate::tests::{check_assist, check_assist_not_applicable};
185
186 use super::*;
187
188 #[test]
189 fn fix_visibility_of_fn() {
190 check_assist(
191 fix_visibility,
192 r"mod foo { fn foo() {} }
193 fn main() { foo::foo$0() } ",
194 r"mod foo { $0pub(crate) fn foo() {} }
195 fn main() { foo::foo() } ",
196 );
197 check_assist_not_applicable(
198 fix_visibility,
199 r"mod foo { pub fn foo() {} }
200 fn main() { foo::foo$0() } ",
201 )
202 }
203
204 #[test]
205 fn fix_visibility_of_adt_in_submodule() {
206 check_assist(
207 fix_visibility,
208 r"mod foo { struct Foo; }
209 fn main() { foo::Foo$0 } ",
210 r"mod foo { $0pub(crate) struct Foo; }
211 fn main() { foo::Foo } ",
212 );
213 check_assist_not_applicable(
214 fix_visibility,
215 r"mod foo { pub struct Foo; }
216 fn main() { foo::Foo$0 } ",
217 );
218 check_assist(
219 fix_visibility,
220 r"mod foo { enum Foo; }
221 fn main() { foo::Foo$0 } ",
222 r"mod foo { $0pub(crate) enum Foo; }
223 fn main() { foo::Foo } ",
224 );
225 check_assist_not_applicable(
226 fix_visibility,
227 r"mod foo { pub enum Foo; }
228 fn main() { foo::Foo$0 } ",
229 );
230 check_assist(
231 fix_visibility,
232 r"mod foo { union Foo; }
233 fn main() { foo::Foo$0 } ",
234 r"mod foo { $0pub(crate) union Foo; }
235 fn main() { foo::Foo } ",
236 );
237 check_assist_not_applicable(
238 fix_visibility,
239 r"mod foo { pub union Foo; }
240 fn main() { foo::Foo$0 } ",
241 );
242 }
243
244 #[test]
245 fn fix_visibility_of_adt_in_other_file() {
246 check_assist(
247 fix_visibility,
248 r"
249//- /main.rs
250mod foo;
251fn main() { foo::Foo$0 }
252
253//- /foo.rs
254struct Foo;
255",
256 r"$0pub(crate) struct Foo;
257",
258 );
259 }
260
261 #[test]
262 fn fix_visibility_of_enum_variant_field() {
263 check_assist_not_applicable(
266 fix_visibility,
267 r"mod foo { pub enum Foo { Bar { bar: () } } }
268 fn main() { foo::Foo::Bar { $0bar: () }; } ",
269 );
270 check_assist_not_applicable(
271 fix_visibility,
272 r"
273//- /lib.rs
274mod foo;
275fn main() { foo::Foo::Bar { $0bar: () }; }
276//- /foo.rs
277pub enum Foo { Bar { bar: () } }
278",
279 );
280 check_assist_not_applicable(
281 fix_visibility,
282 r"mod foo { pub struct Foo { pub bar: (), } }
283 fn main() { foo::Foo { $0bar: () }; } ",
284 );
285 check_assist_not_applicable(
286 fix_visibility,
287 r"
288//- /lib.rs
289mod foo;
290fn main() { foo::Foo { $0bar: () }; }
291//- /foo.rs
292pub struct Foo { pub bar: () }
293",
294 );
295 }
296
297 #[test]
298 fn fix_visibility_of_const() {
299 check_assist(
300 fix_visibility,
301 r"mod foo { const FOO: () = (); }
302 fn main() { foo::FOO$0 } ",
303 r"mod foo { $0pub(crate) const FOO: () = (); }
304 fn main() { foo::FOO } ",
305 );
306 check_assist_not_applicable(
307 fix_visibility,
308 r"mod foo { pub const FOO: () = (); }
309 fn main() { foo::FOO$0 } ",
310 );
311 }
312
313 #[test]
314 fn fix_visibility_of_static() {
315 check_assist(
316 fix_visibility,
317 r"mod foo { static FOO: () = (); }
318 fn main() { foo::FOO$0 } ",
319 r"mod foo { $0pub(crate) static FOO: () = (); }
320 fn main() { foo::FOO } ",
321 );
322 check_assist_not_applicable(
323 fix_visibility,
324 r"mod foo { pub static FOO: () = (); }
325 fn main() { foo::FOO$0 } ",
326 );
327 }
328
329 #[test]
330 fn fix_visibility_of_trait() {
331 check_assist(
332 fix_visibility,
333 r"mod foo { trait Foo { fn foo(&self) {} } }
334 fn main() { let x: &dyn foo::$0Foo; } ",
335 r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } }
336 fn main() { let x: &dyn foo::Foo; } ",
337 );
338 check_assist_not_applicable(
339 fix_visibility,
340 r"mod foo { pub trait Foo { fn foo(&self) {} } }
341 fn main() { let x: &dyn foo::Foo$0; } ",
342 );
343 }
344
345 #[test]
346 fn fix_visibility_of_type_alias() {
347 check_assist(
348 fix_visibility,
349 r"mod foo { type Foo = (); }
350 fn main() { let x: foo::Foo$0; } ",
351 r"mod foo { $0pub(crate) type Foo = (); }
352 fn main() { let x: foo::Foo; } ",
353 );
354 check_assist_not_applicable(
355 fix_visibility,
356 r"mod foo { pub type Foo = (); }
357 fn main() { let x: foo::Foo$0; } ",
358 );
359 }
360
361 #[test]
362 fn fix_visibility_of_module() {
363 check_assist(
364 fix_visibility,
365 r"mod foo { mod bar { fn bar() {} } }
366 fn main() { foo::bar$0::bar(); } ",
367 r"mod foo { $0pub(crate) mod bar { fn bar() {} } }
368 fn main() { foo::bar::bar(); } ",
369 );
370
371 check_assist(
372 fix_visibility,
373 r"
374//- /main.rs
375mod foo;
376fn main() { foo::bar$0::baz(); }
377
378//- /foo.rs
379mod bar {
380 pub fn baz() {}
381}
382",
383 r"$0pub(crate) mod bar {
384 pub fn baz() {}
385}
386",
387 );
388
389 check_assist_not_applicable(
390 fix_visibility,
391 r"mod foo { pub mod bar { pub fn bar() {} } }
392 fn main() { foo::bar$0::bar(); } ",
393 );
394 }
395
396 #[test]
397 fn fix_visibility_of_inline_module_in_other_file() {
398 check_assist(
399 fix_visibility,
400 r"
401//- /main.rs
402mod foo;
403fn main() { foo::bar$0::baz(); }
404
405//- /foo.rs
406mod bar;
407//- /foo/bar.rs
408pub fn baz() {}
409",
410 r"$0pub(crate) mod bar;
411",
412 );
413 }
414
415 #[test]
416 fn fix_visibility_of_module_declaration_in_other_file() {
417 check_assist(
418 fix_visibility,
419 r"
420//- /main.rs
421mod foo;
422fn main() { foo::bar$0>::baz(); }
423
424//- /foo.rs
425mod bar {
426 pub fn baz() {}
427}
428",
429 r"$0pub(crate) mod bar {
430 pub fn baz() {}
431}
432",
433 );
434 }
435
436 #[test]
437 fn adds_pub_when_target_is_in_another_crate() {
438 check_assist(
439 fix_visibility,
440 r"
441//- /main.rs crate:a deps:foo
442foo::Bar$0
443//- /lib.rs crate:foo
444struct Bar;
445",
446 r"$0pub struct Bar;
447",
448 )
449 }
450
451 #[test]
452 fn replaces_pub_crate_with_pub() {
453 check_assist(
454 fix_visibility,
455 r"
456//- /main.rs crate:a deps:foo
457foo::Bar$0
458//- /lib.rs crate:foo
459pub(crate) struct Bar;
460",
461 r"$0pub struct Bar;
462",
463 );
464 }
465
466 #[test]
467 fn fix_visibility_of_reexport() {
468 check_assist(
471 fix_visibility,
472 r#"
473mod foo {
474 use bar::Baz;
475 mod bar { pub(super) struct Baz; }
476}
477foo::Baz$0
478"#,
479 r#"
480mod foo {
481 use bar::Baz;
482 mod bar { $0pub(crate) struct Baz; }
483}
484foo::Baz
485"#,
486 )
487 }
488}