Skip to content

Commit 75011bb

Browse files
bors[bot]Jonas Schievinkjonas-schievink
authored
Merge #8210
8210: Implement "Extract type alias" assist r=jonas-schievink a=jonas-schievink Co-authored-by: Jonas Schievink <[email protected]> Co-authored-by: Jonas Schievink <[email protected]>
2 parents aca9004 + 3c6c1c9 commit 75011bb

File tree

5 files changed

+179
-6
lines changed

5 files changed

+179
-6
lines changed

crates/ide_assists/src/assist_context.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use ide_db::{
1313
RootDatabase,
1414
};
1515
use syntax::{
16-
algo::{self, find_node_at_offset, SyntaxRewriter},
16+
algo::{self, find_node_at_offset, find_node_at_range, SyntaxRewriter},
1717
AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxNodePtr,
1818
SyntaxToken, TextRange, TextSize, TokenAtOffset,
1919
};
@@ -89,6 +89,9 @@ impl<'a> AssistContext<'a> {
8989
pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
9090
find_node_at_offset(self.source_file.syntax(), self.offset())
9191
}
92+
pub(crate) fn find_node_at_range<N: AstNode>(&self) -> Option<N> {
93+
find_node_at_range(self.source_file.syntax(), self.frange.range)
94+
}
9295
pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
9396
self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset())
9497
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use syntax::ast::{self, AstNode};
2+
3+
use crate::{AssistContext, AssistId, AssistKind, Assists};
4+
5+
// Assist: extract_type_alias
6+
//
7+
// Extracts the selected type as a type alias.
8+
//
9+
// ```
10+
// struct S {
11+
// field: $0(u8, u8, u8)$0,
12+
// }
13+
// ```
14+
// ->
15+
// ```
16+
// type $0Type = (u8, u8, u8);
17+
//
18+
// struct S {
19+
// field: Type,
20+
// }
21+
// ```
22+
pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
23+
if ctx.frange.range.is_empty() {
24+
return None;
25+
}
26+
27+
let node = ctx.find_node_at_range::<ast::Type>()?;
28+
let insert = ctx.find_node_at_offset::<ast::Item>()?.syntax().text_range().start();
29+
let target = node.syntax().text_range();
30+
31+
acc.add(
32+
AssistId("extract_type_alias", AssistKind::RefactorExtract),
33+
"Extract type as type alias",
34+
target,
35+
|builder| {
36+
builder.edit_file(ctx.frange.file_id);
37+
builder.replace(target, "Type");
38+
match ctx.config.snippet_cap {
39+
Some(cap) => {
40+
builder.insert_snippet(cap, insert, format!("type $0Type = {};\n\n", node));
41+
}
42+
None => {
43+
builder.insert(insert, format!("type Type = {};\n\n", node));
44+
}
45+
}
46+
},
47+
)
48+
}
49+
50+
#[cfg(test)]
51+
mod tests {
52+
use crate::tests::{check_assist, check_assist_not_applicable};
53+
54+
use super::*;
55+
56+
#[test]
57+
fn test_not_applicable_without_selection() {
58+
check_assist_not_applicable(
59+
extract_type_alias,
60+
r"
61+
struct S {
62+
field: $0(u8, u8, u8),
63+
}
64+
",
65+
);
66+
}
67+
68+
#[test]
69+
fn test_simple_types() {
70+
check_assist(
71+
extract_type_alias,
72+
r"
73+
struct S {
74+
field: $0u8$0,
75+
}
76+
",
77+
r#"
78+
type $0Type = u8;
79+
80+
struct S {
81+
field: Type,
82+
}
83+
"#,
84+
);
85+
}
86+
87+
#[test]
88+
fn test_generic_type_arg() {
89+
check_assist(
90+
extract_type_alias,
91+
r"
92+
fn generic<T>() {}
93+
94+
fn f() {
95+
generic::<$0()$0>();
96+
}
97+
",
98+
r#"
99+
fn generic<T>() {}
100+
101+
type $0Type = ();
102+
103+
fn f() {
104+
generic::<Type>();
105+
}
106+
"#,
107+
);
108+
}
109+
110+
#[test]
111+
fn test_inner_type_arg() {
112+
check_assist(
113+
extract_type_alias,
114+
r"
115+
struct Vec<T> {}
116+
struct S {
117+
v: Vec<Vec<$0Vec<u8>$0>>,
118+
}
119+
",
120+
r#"
121+
struct Vec<T> {}
122+
type $0Type = Vec<u8>;
123+
124+
struct S {
125+
v: Vec<Vec<Type>>,
126+
}
127+
"#,
128+
);
129+
}
130+
131+
#[test]
132+
fn test_extract_inner_type() {
133+
check_assist(
134+
extract_type_alias,
135+
r"
136+
struct S {
137+
field: ($0u8$0,),
138+
}
139+
",
140+
r#"
141+
type $0Type = u8;
142+
143+
struct S {
144+
field: (Type,),
145+
}
146+
"#,
147+
);
148+
}
149+
}

crates/ide_assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ mod handlers {
121121
mod expand_glob_import;
122122
mod extract_function;
123123
mod extract_struct_from_enum_variant;
124+
mod extract_type_alias;
124125
mod extract_variable;
125126
mod fill_match_arms;
126127
mod fix_visibility;
@@ -187,6 +188,7 @@ mod handlers {
187188
early_return::convert_to_guarded_return,
188189
expand_glob_import::expand_glob_import,
189190
extract_struct_from_enum_variant::extract_struct_from_enum_variant,
191+
extract_type_alias::extract_type_alias,
190192
fill_match_arms::fill_match_arms,
191193
fix_visibility::fix_visibility,
192194
flip_binexpr::flip_binexpr,

crates/ide_assists/src/tests/generated.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,25 @@ enum A { One(One) }
328328
)
329329
}
330330

331+
#[test]
332+
fn doctest_extract_type_alias() {
333+
check_doc_test(
334+
"extract_type_alias",
335+
r#####"
336+
struct S {
337+
field: $0(u8, u8, u8)$0,
338+
}
339+
"#####,
340+
r#####"
341+
type $0Type = (u8, u8, u8);
342+
343+
struct S {
344+
field: Type,
345+
}
346+
"#####,
347+
)
348+
}
349+
331350
#[test]
332351
fn doctest_extract_variable() {
333352
check_doc_test(

editors/code/src/snippets.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ async function editorFromUri(uri: vscode.Uri): Promise<vscode.TextEditor | undef
2929
}
3030

3131
export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vscode.TextEdit[]) {
32-
let selection: vscode.Selection | undefined = undefined;
32+
const selections: vscode.Selection[] = [];
3333
let lineDelta = 0;
3434
await editor.edit((builder) => {
3535
for (const indel of edits) {
@@ -44,18 +44,18 @@ export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vs
4444
indel.range.start.character + placeholderStart
4545
: prefix.length - lastNewline - 1;
4646
const endColumn = startColumn + placeholderLength;
47-
selection = new vscode.Selection(
47+
selections.push(new vscode.Selection(
4848
new vscode.Position(startLine, startColumn),
4949
new vscode.Position(startLine, endColumn),
50-
);
50+
));
5151
builder.replace(indel.range, newText);
5252
} else {
53-
lineDelta = countLines(indel.newText) - (indel.range.end.line - indel.range.start.line);
5453
builder.replace(indel.range, indel.newText);
5554
}
55+
lineDelta = countLines(indel.newText) - (indel.range.end.line - indel.range.start.line);
5656
}
5757
});
58-
if (selection) editor.selection = selection;
58+
if (selections.length > 0) editor.selections = selections;
5959
}
6060

6161
function parseSnippet(snip: string): [string, [number, number]] | undefined {

0 commit comments

Comments
 (0)