8210: Implement "Extract type alias" assist r=jonas-schievink a=jonas-schievink



Co-authored-by: Jonas Schievink <jonas.schievink@ferrous-systems.com>
Co-authored-by: Jonas Schievink <jonasschievink@gmail.com>
This commit is contained in:
bors[bot] 2021-03-31 12:26:57 +00:00 committed by GitHub
commit 75011bbccb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 179 additions and 6 deletions

View File

@ -13,7 +13,7 @@
RootDatabase,
};
use syntax::{
algo::{self, find_node_at_offset, SyntaxRewriter},
algo::{self, find_node_at_offset, find_node_at_range, SyntaxRewriter},
AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxNodePtr,
SyntaxToken, TextRange, TextSize, TokenAtOffset,
};
@ -89,6 +89,9 @@ pub(crate) fn find_token_at_offset<T: AstToken>(&self) -> Option<T> {
pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
find_node_at_offset(self.source_file.syntax(), self.offset())
}
pub(crate) fn find_node_at_range<N: AstNode>(&self) -> Option<N> {
find_node_at_range(self.source_file.syntax(), self.frange.range)
}
pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset())
}

View File

@ -0,0 +1,149 @@
use syntax::ast::{self, AstNode};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: extract_type_alias
//
// Extracts the selected type as a type alias.
//
// ```
// struct S {
// field: $0(u8, u8, u8)$0,
// }
// ```
// ->
// ```
// type $0Type = (u8, u8, u8);
//
// struct S {
// field: Type,
// }
// ```
pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
if ctx.frange.range.is_empty() {
return None;
}
let node = ctx.find_node_at_range::<ast::Type>()?;
let insert = ctx.find_node_at_offset::<ast::Item>()?.syntax().text_range().start();
let target = node.syntax().text_range();
acc.add(
AssistId("extract_type_alias", AssistKind::RefactorExtract),
"Extract type as type alias",
target,
|builder| {
builder.edit_file(ctx.frange.file_id);
builder.replace(target, "Type");
match ctx.config.snippet_cap {
Some(cap) => {
builder.insert_snippet(cap, insert, format!("type $0Type = {};\n\n", node));
}
None => {
builder.insert(insert, format!("type Type = {};\n\n", node));
}
}
},
)
}
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_not_applicable};
use super::*;
#[test]
fn test_not_applicable_without_selection() {
check_assist_not_applicable(
extract_type_alias,
r"
struct S {
field: $0(u8, u8, u8),
}
",
);
}
#[test]
fn test_simple_types() {
check_assist(
extract_type_alias,
r"
struct S {
field: $0u8$0,
}
",
r#"
type $0Type = u8;
struct S {
field: Type,
}
"#,
);
}
#[test]
fn test_generic_type_arg() {
check_assist(
extract_type_alias,
r"
fn generic<T>() {}
fn f() {
generic::<$0()$0>();
}
",
r#"
fn generic<T>() {}
type $0Type = ();
fn f() {
generic::<Type>();
}
"#,
);
}
#[test]
fn test_inner_type_arg() {
check_assist(
extract_type_alias,
r"
struct Vec<T> {}
struct S {
v: Vec<Vec<$0Vec<u8>$0>>,
}
",
r#"
struct Vec<T> {}
type $0Type = Vec<u8>;
struct S {
v: Vec<Vec<Type>>,
}
"#,
);
}
#[test]
fn test_extract_inner_type() {
check_assist(
extract_type_alias,
r"
struct S {
field: ($0u8$0,),
}
",
r#"
type $0Type = u8;
struct S {
field: (Type,),
}
"#,
);
}
}

View File

@ -121,6 +121,7 @@ mod handlers {
mod expand_glob_import;
mod extract_function;
mod extract_struct_from_enum_variant;
mod extract_type_alias;
mod extract_variable;
mod fill_match_arms;
mod fix_visibility;
@ -187,6 +188,7 @@ pub(crate) fn all() -> &'static [Handler] {
early_return::convert_to_guarded_return,
expand_glob_import::expand_glob_import,
extract_struct_from_enum_variant::extract_struct_from_enum_variant,
extract_type_alias::extract_type_alias,
fill_match_arms::fill_match_arms,
fix_visibility::fix_visibility,
flip_binexpr::flip_binexpr,

View File

@ -328,6 +328,25 @@ enum A { One(One) }
)
}
#[test]
fn doctest_extract_type_alias() {
check_doc_test(
"extract_type_alias",
r#####"
struct S {
field: $0(u8, u8, u8)$0,
}
"#####,
r#####"
type $0Type = (u8, u8, u8);
struct S {
field: Type,
}
"#####,
)
}
#[test]
fn doctest_extract_variable() {
check_doc_test(

View File

@ -29,7 +29,7 @@ async function editorFromUri(uri: vscode.Uri): Promise<vscode.TextEditor | undef
}
export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vscode.TextEdit[]) {
let selection: vscode.Selection | undefined = undefined;
const selections: vscode.Selection[] = [];
let lineDelta = 0;
await editor.edit((builder) => {
for (const indel of edits) {
@ -44,18 +44,18 @@ export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vs
indel.range.start.character + placeholderStart
: prefix.length - lastNewline - 1;
const endColumn = startColumn + placeholderLength;
selection = new vscode.Selection(
selections.push(new vscode.Selection(
new vscode.Position(startLine, startColumn),
new vscode.Position(startLine, endColumn),
);
));
builder.replace(indel.range, newText);
} else {
lineDelta = countLines(indel.newText) - (indel.range.end.line - indel.range.start.line);
builder.replace(indel.range, indel.newText);
}
lineDelta = countLines(indel.newText) - (indel.range.end.line - indel.range.start.line);
}
});
if (selection) editor.selection = selection;
if (selections.length > 0) editor.selections = selections;
}
function parseSnippet(snip: string): [string, [number, number]] | undefined {