diff options
author | Timo Freiberg <[email protected]> | 2020-05-06 23:10:56 +0100 |
---|---|---|
committer | Timo Freiberg <[email protected]> | 2020-05-10 15:51:12 +0100 |
commit | 66db88d226704a75a2fa38782ee3b82fd7829d5e (patch) | |
tree | e569045d0889a9def6a5e316cd9c9a5cea6eebca /crates/ra_assists/src/handlers | |
parent | 7568d0770934345be183670652e8064c06c05caf (diff) |
Trigger change_visibility assist when on an invisible struct field
Union fields apparently don't work :(
Diffstat (limited to 'crates/ra_assists/src/handlers')
-rw-r--r-- | crates/ra_assists/src/handlers/change_visibility.rs | 309 |
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 | ||
11 | use hir::{db::HirDatabase, HasSource, PathResolution}; | 11 | use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; |
12 | use test_utils::tested_by; | 12 | use test_utils::tested_by; |
13 | 13 | ||
14 | use crate::{AssistContext, AssistId, Assists}; | 14 | use crate::{AssistContext, AssistId, Assists}; |
15 | use 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 | ||
34 | fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 37 | fn 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 | ||
77 | fn add_missing_vis(ctx: AssistCtx) -> Option<Assist> { | 80 | fn 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 | |||
113 | fn 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 | |||
154 | fn 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 | |||
149 | fn offset_and_target<S, Ast>(db: &dyn HirDatabase, x: S) -> (TextSize, TextRange) | ||
150 | where | ||
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 | ||
159 | fn vis_offset(node: &SyntaxNode) -> TextSize { | 217 | fn 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)"); |