Skip to content

Commit 66db88d

Browse files
committed
Trigger change_visibility assist when on an invisible struct field
Union fields apparently don't work :(
1 parent 7568d07 commit 66db88d

File tree

1 file changed

+248
-61
lines changed

1 file changed

+248
-61
lines changed

crates/ra_assists/src/handlers/change_visibility.rs

Lines changed: 248 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ use ra_syntax::{
88
SyntaxNode, TextRange, TextSize, T,
99
};
1010

11-
use hir::{db::HirDatabase, HasSource, PathResolution};
11+
use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
1212
use test_utils::tested_by;
1313

1414
use crate::{AssistContext, AssistId, Assists};
15+
use ra_db::FileId;
1516

1617
// Assist: change_visibility
1718
//
@@ -29,6 +30,8 @@ pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Optio
2930
return change_vis(acc, vis);
3031
}
3132
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))
3235
}
3336

3437
fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
@@ -74,86 +77,141 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
7477
})
7578
}
7679

77-
fn add_missing_vis(ctx: AssistCtx) -> Option<Assist> {
80+
fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
7881
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)?;
8083
let def = match path_res {
8184
PathResolution::Def(def) => def,
8285
_ => return None,
8386
};
84-
dbg!(&def);
8587

86-
let current_module = dbg!(ctx.sema.scope(&path.syntax()).module())?;
87-
let target_module = dbg!(def.module(ctx.db))?;
88+
let current_module = ctx.sema.scope(&path.syntax()).module()?;
89+
let target_module = def.module(ctx.db)?;
8890

89-
let vis = dbg!(target_module.visibility_of(ctx.db, &def))?;
91+
let vis = target_module.visibility_of(ctx.db, &def)?;
9092
if vis.is_visible_from(ctx.db, current_module.into()) {
9193
return None;
9294
};
93-
let target_name;
9495

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 {
96175
hir::ModuleDef::Function(f) => {
97-
target_name = Some(f.name(ctx.db));
98-
offset_and_target(ctx.db, f)
176+
target_name = Some(f.name(db));
177+
offset_target_and_file_id(db, f)
99178
}
100179
hir::ModuleDef::Adt(adt) => {
101-
target_name = Some(adt.name(ctx.db));
180+
target_name = Some(adt.name(db));
102181
match adt {
103-
hir::Adt::Struct(s) => offset_and_target(ctx.db, s),
104-
hir::Adt::Union(u) => offset_and_target(ctx.db, u),
105-
hir::Adt::Enum(e) => offset_and_target(ctx.db, e),
182+
hir::Adt::Struct(s) => offset_target_and_file_id(db, s),
183+
hir::Adt::Union(u) => offset_target_and_file_id(db, u),
184+
hir::Adt::Enum(e) => offset_target_and_file_id(db, e),
106185
}
107186
}
108187
hir::ModuleDef::Const(c) => {
109-
target_name = c.name(ctx.db);
110-
offset_and_target(ctx.db, c)
188+
target_name = c.name(db);
189+
offset_target_and_file_id(db, c)
111190
}
112191
hir::ModuleDef::Static(s) => {
113-
target_name = s.name(ctx.db);
114-
offset_and_target(ctx.db, s)
192+
target_name = s.name(db);
193+
offset_target_and_file_id(db, s)
115194
}
116195
hir::ModuleDef::Trait(t) => {
117-
target_name = Some(t.name(ctx.db));
118-
offset_and_target(ctx.db, t)
196+
target_name = Some(t.name(db));
197+
offset_target_and_file_id(db, t)
119198
}
120199
hir::ModuleDef::TypeAlias(t) => {
121-
target_name = Some(t.name(ctx.db));
122-
offset_and_target(ctx.db, t)
200+
target_name = Some(t.name(db));
201+
offset_target_and_file_id(db, t)
123202
}
124203
hir::ModuleDef::Module(m) => {
125-
target_name = m.name(ctx.db);
126-
let source = dbg!(m.declaration_source(ctx.db))?.value;
127-
let syntax = source.syntax();
128-
(vis_offset(syntax), syntax.text_range())
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), syntax.text_range(), file_id)
129209
}
130210
// Enum variants can't be private, we can't modify builtin types
131211
hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None,
132212
};
133213

134-
// FIXME if target is in another crate, add `pub` instead of `pub(crate)`
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())
214+
Some((offset, target, target_file, target_name))
157215
}
158216

159217
fn vis_offset(node: &SyntaxNode) -> TextSize {
@@ -350,7 +408,6 @@ mod tests {
350408
}
351409

352410
#[test]
353-
// FIXME this requires a separate implementation, struct fields are not a ast::Path
354411
fn change_visibility_of_struct_field_via_path() {
355412
check_assist(
356413
change_visibility,
@@ -359,11 +416,108 @@ mod tests {
359416
r"mod foo { pub struct Foo { <|>pub(crate) bar: (), } }
360417
fn main() { foo::Foo { bar: () }; } ",
361418
);
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+
);
362468
check_assist_not_applicable(
363469
change_visibility,
364470
r"mod foo { pub struct Foo { pub bar: (), } }
365471
fn main() { foo::Foo { <|>bar: () }; } ",
366472
);
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+
);
367521
}
368522

369523
#[test]
@@ -500,24 +654,57 @@ mod tests {
500654
fn change_visibility_of_module_declaration_in_other_file_via_path() {
501655
check_assist(
502656
change_visibility,
503-
r"
504-
//- /main.rs
505-
mod foo;
506-
fn main() { foo::bar<|>>::baz(); }
657+
r"//- /main.rs
658+
mod foo;
659+
fn main() { foo::bar<|>>::baz(); }
507660
508-
//- /foo.rs
509-
mod bar {
510-
pub fn baz() {}
511-
}
512-
",
661+
//- /foo.rs
662+
mod bar {
663+
pub fn baz() {}
664+
}",
513665
r"<|>pub(crate) mod bar {
514666
pub fn baz() {}
515667
}
516-
517668
",
518669
);
519670
}
520671

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+
521708
#[test]
522709
fn change_visibility_target() {
523710
check_assist_target(change_visibility, "<|>fn foo() {}", "fn");

0 commit comments

Comments
 (0)