aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs309
1 files changed, 248 insertions, 61 deletions
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
index 573f94a46..40cf4b422 100644
--- a/crates/ra_assists/src/handlers/change_visibility.rs
+++ b/crates/ra_assists/src/handlers/change_visibility.rs
@@ -8,10 +8,11 @@ use ra_syntax::{
8 SyntaxNode, TextRange, TextSize, T, 8 SyntaxNode, TextRange, TextSize, T,
9}; 9};
10 10
11use hir::{db::HirDatabase, HasSource, PathResolution}; 11use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
12use test_utils::tested_by; 12use test_utils::tested_by;
13 13
14use crate::{AssistContext, AssistId, Assists}; 14use crate::{AssistContext, AssistId, Assists};
15use ra_db::FileId;
15 16
16// Assist: change_visibility 17// Assist: change_visibility
17// 18//
@@ -29,6 +30,8 @@ pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Optio
29 return change_vis(acc, vis); 30 return change_vis(acc, vis);
30 } 31 }
31 add_vis(acc, ctx) 32 add_vis(acc, ctx)
33 .or_else(|| add_vis_to_referenced_module_def(acc, ctx))
34 .or_else(|| add_vis_to_referenced_record_field(acc, ctx))
32} 35}
33 36
34fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 37fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
@@ -74,86 +77,141 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
74 }) 77 })
75} 78}
76 79
77fn add_missing_vis(ctx: AssistCtx) -> Option<Assist> { 80fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
78 let path: ast::Path = ctx.find_node_at_offset()?; 81 let path: ast::Path = ctx.find_node_at_offset()?;
79 let path_res = dbg!(ctx.sema.resolve_path(&path))?; 82 let path_res = ctx.sema.resolve_path(&path)?;
80 let def = match path_res { 83 let def = match path_res {
81 PathResolution::Def(def) => def, 84 PathResolution::Def(def) => def,
82 _ => return None, 85 _ => return None,
83 }; 86 };
84 dbg!(&def);
85 87
86 let current_module = dbg!(ctx.sema.scope(&path.syntax()).module())?; 88 let current_module = ctx.sema.scope(&path.syntax()).module()?;
87 let target_module = dbg!(def.module(ctx.db))?; 89 let target_module = def.module(ctx.db)?;
88 90
89 let vis = dbg!(target_module.visibility_of(ctx.db, &def))?; 91 let vis = target_module.visibility_of(ctx.db, &def)?;
90 if vis.is_visible_from(ctx.db, current_module.into()) { 92 if vis.is_visible_from(ctx.db, current_module.into()) {
91 return None; 93 return None;
92 }; 94 };
93 let target_name;
94 95
95 let (offset, target) = match def { 96 let (offset, target, target_file, target_name) = target_data_for_def(ctx.db, def)?;
97
98 let missing_visibility =
99 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
100
101 let assist_label = match target_name {
102 None => format!("Change visibility to {}", missing_visibility),
103 Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
104 };
105
106 acc.add(AssistId("change_visibility"), assist_label, target, |edit| {
107 edit.set_file(target_file);
108 edit.insert(offset, format!("{} ", missing_visibility));
109 edit.set_cursor(offset);
110 })
111}
112
113fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
114 let record_field: ast::RecordField = ctx.find_node_at_offset()?;
115 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?;
116
117 let current_module = ctx.sema.scope(record_field.syntax()).module()?;
118 let visibility = record_field_def.visibility(ctx.db);
119 if visibility.is_visible_from(ctx.db, current_module.into()) {
120 return None;
121 }
122
123 let parent = record_field_def.parent_def(ctx.db);
124 let parent_name = parent.name(ctx.db);
125 let target_module = parent.module(ctx.db);
126
127 let in_file_source = record_field_def.source(ctx.db);
128 let (offset, target) = match in_file_source.value {
129 hir::FieldSource::Named(it) => {
130 let s = it.syntax();
131 (vis_offset(s), s.text_range())
132 }
133 hir::FieldSource::Pos(it) => {
134 let s = it.syntax();
135 (vis_offset(s), s.text_range())
136 }
137 };
138
139 let missing_visibility =
140 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
141 let target_file = in_file_source.file_id.original_file(ctx.db);
142
143 let target_name = record_field_def.name(ctx.db);
144 let assist_label =
145 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
146
147 acc.add(AssistId("change_visibility"), assist_label, target, |edit| {
148 edit.set_file(target_file);
149 edit.insert(offset, format!("{} ", missing_visibility));
150 edit.set_cursor(offset)
151 })
152}
153
154fn target_data_for_def(
155 db: &dyn HirDatabase,
156 def: hir::ModuleDef,
157) -> Option<(TextSize, TextRange, FileId, Option<hir::Name>)> {
158 fn offset_target_and_file_id<S, Ast>(
159 db: &dyn HirDatabase,
160 x: S,
161 ) -> (TextSize, TextRange, FileId)
162 where
163 S: HasSource<Ast = Ast>,
164 Ast: AstNode,
165 {
166 let source = x.source(db);
167 let in_file_syntax = source.syntax();
168 let file_id = in_file_syntax.file_id;
169 let syntax = in_file_syntax.value;
170 (vis_offset(syntax), syntax.text_range(), file_id.original_file(db.upcast()))
171 }
172
173 let target_name;
174 let (offset, target, target_file) = match def {
96 hir::ModuleDef::Function(f) => { 175 hir::ModuleDef::Function(f) => {
97 target_name = Some(f.name(ctx.db)); 176 target_name = Some(f.name(db));
98 offset_and_target(ctx.db, f) 177 offset_target_and_file_id(db, f)
99 } 178 }
100 hir::ModuleDef::Adt(adt) => { 179 hir::ModuleDef::Adt(adt) => {
101 target_name = Some(adt.name(ctx.db)); 180 target_name = Some(adt.name(db));
102 match adt { 181 match adt {
103 hir::Adt::Struct(s) => offset_and_target(ctx.db, s), 182 hir::Adt::Struct(s) => offset_target_and_file_id(db, s),
104 hir::Adt::Union(u) => offset_and_target(ctx.db, u), 183 hir::Adt::Union(u) => offset_target_and_file_id(db, u),
105 hir::Adt::Enum(e) => offset_and_target(ctx.db, e), 184 hir::Adt::Enum(e) => offset_target_and_file_id(db, e),
106 } 185 }
107 } 186 }
108 hir::ModuleDef::Const(c) => { 187 hir::ModuleDef::Const(c) => {
109 target_name = c.name(ctx.db); 188 target_name = c.name(db);
110 offset_and_target(ctx.db, c) 189 offset_target_and_file_id(db, c)
111 } 190 }
112 hir::ModuleDef::Static(s) => { 191 hir::ModuleDef::Static(s) => {
113 target_name = s.name(ctx.db); 192 target_name = s.name(db);
114 offset_and_target(ctx.db, s) 193 offset_target_and_file_id(db, s)
115 } 194 }
116 hir::ModuleDef::Trait(t) => { 195 hir::ModuleDef::Trait(t) => {
117 target_name = Some(t.name(ctx.db)); 196 target_name = Some(t.name(db));
118 offset_and_target(ctx.db, t) 197 offset_target_and_file_id(db, t)
119 } 198 }
120 hir::ModuleDef::TypeAlias(t) => { 199 hir::ModuleDef::TypeAlias(t) => {
121 target_name = Some(t.name(ctx.db)); 200 target_name = Some(t.name(db));
122 offset_and_target(ctx.db, t) 201 offset_target_and_file_id(db, t)
123 } 202 }
124 hir::ModuleDef::Module(m) => { 203 hir::ModuleDef::Module(m) => {
125 target_name = m.name(ctx.db); 204 target_name = m.name(db);
126 let source = dbg!(m.declaration_source(ctx.db))?.value; 205 let in_file_source = m.declaration_source(db)?;
127 let syntax = source.syntax(); 206 let file_id = in_file_source.file_id.original_file(db.upcast());
128 (vis_offset(syntax), syntax.text_range()) 207 let syntax = in_file_source.value.syntax();
208 (vis_offset(syntax), syntax.text_range(), file_id)
129 } 209 }
130 // Enum variants can't be private, we can't modify builtin types 210 // Enum variants can't be private, we can't modify builtin types
131 hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None, 211 hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None,
132 }; 212 };
133 213
134 // FIXME if target is in another crate, add `pub` instead of `pub(crate)` 214 Some((offset, target, target_file, target_name))
135
136 let assist_label = match target_name {
137 None => "Change visibility to pub(crate)".to_string(),
138 Some(name) => format!("Change visibility of {} to pub(crate)", name),
139 };
140 let target_file = target_module.definition_source(ctx.db).file_id.original_file(ctx.db);
141
142 ctx.add_assist(AssistId("change_visibility"), assist_label, target, |edit| {
143 edit.set_file(target_file);
144 edit.insert(offset, "pub(crate) ");
145 edit.set_cursor(offset);
146 })
147}
148
149fn offset_and_target<S, Ast>(db: &dyn HirDatabase, x: S) -> (TextSize, TextRange)
150where
151 S: HasSource<Ast = Ast>,
152 Ast: AstNode,
153{
154 let source = x.source(db);
155 let syntax = source.syntax().value;
156 (vis_offset(syntax), syntax.text_range())
157} 215}
158 216
159fn vis_offset(node: &SyntaxNode) -> TextSize { 217fn vis_offset(node: &SyntaxNode) -> TextSize {
@@ -350,7 +408,6 @@ mod tests {
350 } 408 }
351 409
352 #[test] 410 #[test]
353 // FIXME this requires a separate implementation, struct fields are not a ast::Path
354 fn change_visibility_of_struct_field_via_path() { 411 fn change_visibility_of_struct_field_via_path() {
355 check_assist( 412 check_assist(
356 change_visibility, 413 change_visibility,
@@ -359,11 +416,108 @@ mod tests {
359 r"mod foo { pub struct Foo { <|>pub(crate) bar: (), } } 416 r"mod foo { pub struct Foo { <|>pub(crate) bar: (), } }
360 fn main() { foo::Foo { bar: () }; } ", 417 fn main() { foo::Foo { bar: () }; } ",
361 ); 418 );
419 check_assist(
420 change_visibility,
421 r"//- /lib.rs
422 mod foo;
423 fn main() { foo::Foo { <|>bar: () }; }
424 //- /foo.rs
425 pub struct Foo { bar: () }
426 ",
427 r"pub struct Foo { <|>pub(crate) bar: () }
428
429",
430 );
431 check_assist_not_applicable(
432 change_visibility,
433 r"mod foo { pub struct Foo { pub bar: (), } }
434 fn main() { foo::Foo { <|>bar: () }; } ",
435 );
436 check_assist_not_applicable(
437 change_visibility,
438 r"//- /lib.rs
439 mod foo;
440 fn main() { foo::Foo { <|>bar: () }; }
441 //- /foo.rs
442 pub struct Foo { pub bar: () }
443 ",
444 );
445 }
446
447 #[test]
448 fn change_visibility_of_enum_variant_field_via_path() {
449 check_assist(
450 change_visibility,
451 r"mod foo { pub enum Foo { Bar { bar: () } } }
452 fn main() { foo::Foo::Bar { <|>bar: () }; } ",
453 r"mod foo { pub enum Foo { Bar { <|>pub(crate) bar: () } } }
454 fn main() { foo::Foo::Bar { bar: () }; } ",
455 );
456 check_assist(
457 change_visibility,
458 r"//- /lib.rs
459 mod foo;
460 fn main() { foo::Foo::Bar { <|>bar: () }; }
461 //- /foo.rs
462 pub enum Foo { Bar { bar: () } }
463 ",
464 r"pub enum Foo { Bar { <|>pub(crate) bar: () } }
465
466",
467 );
362 check_assist_not_applicable( 468 check_assist_not_applicable(
363 change_visibility, 469 change_visibility,
364 r"mod foo { pub struct Foo { pub bar: (), } } 470 r"mod foo { pub struct Foo { pub bar: (), } }
365 fn main() { foo::Foo { <|>bar: () }; } ", 471 fn main() { foo::Foo { <|>bar: () }; } ",
366 ); 472 );
473 check_assist_not_applicable(
474 change_visibility,
475 r"//- /lib.rs
476 mod foo;
477 fn main() { foo::Foo { <|>bar: () }; }
478 //- /foo.rs
479 pub struct Foo { pub bar: () }
480 ",
481 );
482 }
483
484 #[test]
485 #[ignore]
486 // FIXME reenable this test when `Semantics::resolve_record_field` works with union fields
487 fn change_visibility_of_union_field_via_path() {
488 check_assist(
489 change_visibility,
490 r"mod foo { pub union Foo { bar: (), } }
491 fn main() { foo::Foo { <|>bar: () }; } ",
492 r"mod foo { pub union Foo { <|>pub(crate) bar: (), } }
493 fn main() { foo::Foo { bar: () }; } ",
494 );
495 check_assist(
496 change_visibility,
497 r"//- /lib.rs
498 mod foo;
499 fn main() { foo::Foo { <|>bar: () }; }
500 //- /foo.rs
501 pub union Foo { bar: () }
502 ",
503 r"pub union Foo { <|>pub(crate) bar: () }
504
505",
506 );
507 check_assist_not_applicable(
508 change_visibility,
509 r"mod foo { pub union Foo { pub bar: (), } }
510 fn main() { foo::Foo { <|>bar: () }; } ",
511 );
512 check_assist_not_applicable(
513 change_visibility,
514 r"//- /lib.rs
515 mod foo;
516 fn main() { foo::Foo { <|>bar: () }; }
517 //- /foo.rs
518 pub union Foo { pub bar: () }
519 ",
520 );
367 } 521 }
368 522
369 #[test] 523 #[test]
@@ -500,25 +654,58 @@ mod tests {
500 fn change_visibility_of_module_declaration_in_other_file_via_path() { 654 fn change_visibility_of_module_declaration_in_other_file_via_path() {
501 check_assist( 655 check_assist(
502 change_visibility, 656 change_visibility,
503 r" 657 r"//- /main.rs
504 //- /main.rs 658 mod foo;
505 mod foo; 659 fn main() { foo::bar<|>>::baz(); }
506 fn main() { foo::bar<|>>::baz(); }
507 660
508 //- /foo.rs 661 //- /foo.rs
509 mod bar { 662 mod bar {
510 pub fn baz() {} 663 pub fn baz() {}
511 } 664 }",
512 ",
513 r"<|>pub(crate) mod bar { 665 r"<|>pub(crate) mod bar {
514 pub fn baz() {} 666 pub fn baz() {}
515} 667}
516
517", 668",
518 ); 669 );
519 } 670 }
520 671
521 #[test] 672 #[test]
673 #[ignore]
674 // FIXME handle reexports properly
675 fn change_visibility_of_reexport() {
676 check_assist(
677 change_visibility,
678 r"
679 mod foo {
680 use bar::Baz;
681 mod bar { pub(super) struct Baz; }
682 }
683 foo::Baz<|>
684 ",
685 r"
686 mod foo {
687 <|>pub(crate) use bar::Baz;
688 mod bar { pub(super) struct Baz; }
689 }
690 foo::Baz
691 ",
692 )
693 }
694
695 #[test]
696 fn adds_pub_when_target_is_in_another_crate() {
697 check_assist(
698 change_visibility,
699 r"//- /main.rs crate:a deps:foo
700 foo::Bar<|>
701 //- /lib.rs crate:foo
702 struct Bar;",
703 r"<|>pub struct Bar;
704",
705 )
706 }
707
708 #[test]
522 fn change_visibility_target() { 709 fn change_visibility_target() {
523 check_assist_target(change_visibility, "<|>fn foo() {}", "fn"); 710 check_assist_target(change_visibility, "<|>fn foo() {}", "fn");
524 check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)"); 711 check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)");