Skip to content

Commit 4b0f9c5

Browse files
committed
Auto merge of rust-lang#12334 - fasterthanlime:rust-langgh-11635, r=Veykril
Generate enum variant assist So, this is kind of a weird PR! I'm a complete newcomer to the `rust-analyzer` codebase, and so I browsed the "good first issue" tag, and found rust-lang#11635. Then I found two separate folks had taken stabs at it, most recently `@maartenflippo` — and there had been a review 3 days ago, but no activity in a little while, and the PR needed to be rebased since the crates were renamed from `snake_case` to `kebab-case`. So to get acquainted with the codebase I typed this PR by hand, looking at the diff in rust-lang#11995, and I also added a doc-test (that passes). I haven't taken into account the comments `@Veykril` left in rust-lang#11995, but I don't want to steal any of `@maartenflippo's` thunder! Closing this PR is perfectly fine. Or Maarten could use it as a "restart point"? Or I could finish it up, whichever feels best to everyone. I think what remains to be done in this PR, at least, is: * [x] Only disable the "generate function" assist if the name is `PascalCase` * [x] Only enable the "generate variant" assistant if the name is `PascalCase` * [x] Simplify with `adt.source()` as mentioned here: rust-lang/rust-analyzer#11995 (comment) * [ ] Add more tests for edge cases? Are there cases where simply adding one more indent level than the enum's indent level is not good enough? Some nested trickery I'm not thinking of right now? Anyway. This PR can go in any direction. You can tell me "no, tackle your own issue!" And I'll go do that and still be happy I got to take a look at rust-analyzer some by doing this. Or you can tell me "okay, now _you_ finish it", and I guess I'll try and finish it :) Closes rust-lang#11635
2 parents 65a213c + ae2c0db commit 4b0f9c5

File tree

4 files changed

+272
-0
lines changed

4 files changed

+272
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
use hir::HasSource;
2+
use ide_db::assists::{AssistId, AssistKind};
3+
use syntax::{
4+
ast::{self, edit::IndentLevel},
5+
AstNode, TextSize,
6+
};
7+
8+
use crate::assist_context::{AssistContext, Assists};
9+
10+
// Assist: generate_enum_variant
11+
//
12+
// Adds a variant to an enum.
13+
//
14+
// ```
15+
// enum Countries {
16+
// Ghana,
17+
// }
18+
//
19+
// fn main() {
20+
// let country = Countries::Lesotho$0;
21+
// }
22+
// ```
23+
// ->
24+
// ```
25+
// enum Countries {
26+
// Ghana,
27+
// Lesotho,
28+
// }
29+
//
30+
// fn main() {
31+
// let country = Countries::Lesotho;
32+
// }
33+
// ```
34+
pub(crate) fn generate_enum_variant(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35+
let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
36+
let path = path_expr.path()?;
37+
38+
if ctx.sema.resolve_path(&path).is_some() {
39+
// No need to generate anything if the path resolves
40+
return None;
41+
}
42+
43+
let name_ref = path.segment()?.name_ref()?;
44+
if name_ref.text().starts_with(char::is_lowercase) {
45+
// Don't suggest generating variant if the name starts with a lowercase letter
46+
return None;
47+
}
48+
49+
if let Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e)))) =
50+
ctx.sema.resolve_path(&path.qualifier()?)
51+
{
52+
let target = path.syntax().text_range();
53+
return add_variant_to_accumulator(acc, ctx, target, e, &name_ref);
54+
}
55+
56+
None
57+
}
58+
59+
fn add_variant_to_accumulator(
60+
acc: &mut Assists,
61+
ctx: &AssistContext,
62+
target: syntax::TextRange,
63+
adt: hir::Enum,
64+
name_ref: &ast::NameRef,
65+
) -> Option<()> {
66+
let adt_ast = adt.source(ctx.db())?.original_ast_node(ctx.db())?.value;
67+
let enum_indent = IndentLevel::from_node(&adt_ast.syntax());
68+
69+
let variant_list = adt_ast.variant_list()?;
70+
let offset = variant_list.syntax().text_range().end() - TextSize::of('}');
71+
let empty_enum = variant_list.variants().next().is_none();
72+
73+
acc.add(
74+
AssistId("generate_enum_variant", AssistKind::Generate),
75+
"Generate variant",
76+
target,
77+
|builder| {
78+
let text = format!(
79+
"{maybe_newline}{indent_1}{name},\n{enum_indent}",
80+
maybe_newline = if empty_enum { "\n" } else { "" },
81+
indent_1 = IndentLevel(1),
82+
name = name_ref,
83+
enum_indent = enum_indent
84+
);
85+
builder.insert(offset, text)
86+
},
87+
)
88+
}
89+
90+
#[cfg(test)]
91+
mod tests {
92+
use crate::tests::{check_assist, check_assist_not_applicable};
93+
94+
use super::*;
95+
96+
#[test]
97+
fn generate_basic_enum_variant_in_empty_enum() {
98+
check_assist(
99+
generate_enum_variant,
100+
r"
101+
enum Foo {}
102+
fn main() {
103+
Foo::Bar$0
104+
}
105+
",
106+
r"
107+
enum Foo {
108+
Bar,
109+
}
110+
fn main() {
111+
Foo::Bar
112+
}
113+
",
114+
)
115+
}
116+
117+
#[test]
118+
fn generate_basic_enum_variant_in_non_empty_enum() {
119+
check_assist(
120+
generate_enum_variant,
121+
r"
122+
enum Foo {
123+
Bar,
124+
}
125+
fn main() {
126+
Foo::Baz$0
127+
}
128+
",
129+
r"
130+
enum Foo {
131+
Bar,
132+
Baz,
133+
}
134+
fn main() {
135+
Foo::Baz
136+
}
137+
",
138+
)
139+
}
140+
141+
#[test]
142+
fn not_applicable_for_existing_variant() {
143+
check_assist_not_applicable(
144+
generate_enum_variant,
145+
r"
146+
enum Foo {
147+
Bar,
148+
}
149+
fn main() {
150+
Foo::Bar$0
151+
}
152+
",
153+
)
154+
}
155+
156+
#[test]
157+
fn not_applicable_for_lowercase() {
158+
check_assist_not_applicable(
159+
generate_enum_variant,
160+
r"
161+
enum Foo {
162+
Bar,
163+
}
164+
fn main() {
165+
Foo::new$0
166+
}
167+
",
168+
)
169+
}
170+
171+
#[test]
172+
fn indentation_level_is_correct() {
173+
check_assist(
174+
generate_enum_variant,
175+
r"
176+
mod m {
177+
enum Foo {
178+
Bar,
179+
}
180+
}
181+
fn main() {
182+
m::Foo::Baz$0
183+
}
184+
",
185+
r"
186+
mod m {
187+
enum Foo {
188+
Bar,
189+
Baz,
190+
}
191+
}
192+
fn main() {
193+
m::Foo::Baz
194+
}
195+
",
196+
)
197+
}
198+
}

crates/ide-assists/src/handlers/generate_function.rs

+46
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ fn gen_fn(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
7171
get_fn_target(ctx, &target_module, call.clone())?
7272
}
7373
Some(hir::PathResolution::Def(hir::ModuleDef::Adt(adt))) => {
74+
if let hir::Adt::Enum(_) = adt {
75+
// Don't suggest generating function if the name starts with an uppercase letter
76+
if name_ref.text().starts_with(char::is_uppercase) {
77+
return None;
78+
}
79+
}
80+
7481
let current_module = ctx.sema.scope(call.syntax())?.module();
7582
let module = adt.module(ctx.sema.db);
7683
target_module = if current_module == module { None } else { Some(module) };
@@ -1735,6 +1742,45 @@ fn main() {
17351742
fn foo(value: usize) ${0:-> _} {
17361743
todo!()
17371744
}
1745+
",
1746+
)
1747+
}
1748+
1749+
#[test]
1750+
fn not_applicable_for_enum_variant() {
1751+
check_assist_not_applicable(
1752+
generate_function,
1753+
r"
1754+
enum Foo {}
1755+
fn main() {
1756+
Foo::Bar$0(true)
1757+
}
1758+
",
1759+
);
1760+
}
1761+
1762+
#[test]
1763+
fn applicable_for_enum_method() {
1764+
check_assist(
1765+
generate_function,
1766+
r"
1767+
enum Foo {}
1768+
fn main() {
1769+
Foo::new$0();
1770+
}
1771+
",
1772+
r"
1773+
enum Foo {}
1774+
fn main() {
1775+
Foo::new();
1776+
}
1777+
impl Foo {
1778+
1779+
1780+
fn new() ${0:-> _} {
1781+
todo!()
1782+
}
1783+
}
17381784
",
17391785
)
17401786
}

crates/ide-assists/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ mod handlers {
139139
mod generate_documentation_template;
140140
mod generate_enum_is_method;
141141
mod generate_enum_projection_method;
142+
mod generate_enum_variant;
142143
mod generate_from_impl_for_enum;
143144
mod generate_function;
144145
mod generate_getter;
@@ -227,6 +228,7 @@ mod handlers {
227228
generate_enum_is_method::generate_enum_is_method,
228229
generate_enum_projection_method::generate_enum_as_method,
229230
generate_enum_projection_method::generate_enum_try_into_method,
231+
generate_enum_variant::generate_enum_variant,
230232
generate_from_impl_for_enum::generate_from_impl_for_enum,
231233
generate_function::generate_function,
232234
generate_impl::generate_impl,

crates/ide-assists/src/tests/generated.rs

+26
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,32 @@ impl Value {
10061006
)
10071007
}
10081008

1009+
#[test]
1010+
fn doctest_generate_enum_variant() {
1011+
check_doc_test(
1012+
"generate_enum_variant",
1013+
r#####"
1014+
enum Countries {
1015+
Ghana,
1016+
}
1017+
1018+
fn main() {
1019+
let country = Countries::Lesotho$0;
1020+
}
1021+
"#####,
1022+
r#####"
1023+
enum Countries {
1024+
Ghana,
1025+
Lesotho,
1026+
}
1027+
1028+
fn main() {
1029+
let country = Countries::Lesotho;
1030+
}
1031+
"#####,
1032+
)
1033+
}
1034+
10091035
#[test]
10101036
fn doctest_generate_from_impl_for_enum() {
10111037
check_doc_test(

0 commit comments

Comments
 (0)