2020-06-29 10:07:52 -05:00
|
|
|
use itertools::Itertools;
|
|
|
|
use rustc_hash::FxHashMap;
|
2020-04-09 17:35:43 -05:00
|
|
|
|
|
|
|
use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
|
|
|
|
use ra_ide_db::RootDatabase;
|
2020-05-06 11:45:35 -05:00
|
|
|
use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
|
|
|
|
|
2020-06-28 17:36:05 -05:00
|
|
|
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
2020-04-09 17:35:43 -05:00
|
|
|
|
2020-04-11 12:39:10 -05:00
|
|
|
// Assist: reorder_fields
|
|
|
|
//
|
|
|
|
// Reorder the fields of record literals and record patterns in the same order as in
|
|
|
|
// the definition.
|
|
|
|
//
|
|
|
|
// ```
|
|
|
|
// struct Foo {foo: i32, bar: i32};
|
|
|
|
// const test: Foo = <|>Foo {bar: 0, foo: 1}
|
|
|
|
// ```
|
|
|
|
// ->
|
|
|
|
// ```
|
|
|
|
// struct Foo {foo: i32, bar: i32};
|
2020-04-11 13:32:48 -05:00
|
|
|
// const test: Foo = Foo {foo: 1, bar: 0}
|
2020-04-11 12:39:10 -05:00
|
|
|
// ```
|
|
|
|
//
|
2020-05-06 11:45:35 -05:00
|
|
|
pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
2020-07-30 09:21:30 -05:00
|
|
|
reorder::<ast::RecordExpr>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
|
2020-04-09 17:35:43 -05:00
|
|
|
}
|
|
|
|
|
2020-05-06 11:45:35 -05:00
|
|
|
fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
2020-04-11 10:04:25 -05:00
|
|
|
let record = ctx.find_node_at_offset::<R>()?;
|
2020-05-06 11:45:35 -05:00
|
|
|
let path = record.syntax().children().find_map(ast::Path::cast)?;
|
2020-04-09 17:35:43 -05:00
|
|
|
|
2020-04-11 10:04:25 -05:00
|
|
|
let ranks = compute_fields_ranks(&path, &ctx)?;
|
2020-04-09 17:35:43 -05:00
|
|
|
|
2020-04-11 10:04:25 -05:00
|
|
|
let fields = get_fields(&record.syntax());
|
|
|
|
let sorted_fields = sorted_by_rank(&fields, |node| {
|
|
|
|
*ranks.get(&get_field_name(node)).unwrap_or(&usize::max_value())
|
|
|
|
});
|
2020-04-09 17:35:43 -05:00
|
|
|
|
|
|
|
if sorted_fields == fields {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2020-05-06 05:51:28 -05:00
|
|
|
let target = record.syntax().text_range();
|
2020-06-28 17:36:05 -05:00
|
|
|
acc.add(
|
2020-07-02 16:48:35 -05:00
|
|
|
AssistId("reorder_fields", AssistKind::RefactorRewrite),
|
2020-06-28 17:36:05 -05:00
|
|
|
"Reorder record fields",
|
|
|
|
target,
|
|
|
|
|edit| {
|
|
|
|
for (old, new) in fields.iter().zip(&sorted_fields) {
|
|
|
|
algo::diff(old, new).into_text_edit(edit.text_edit_builder());
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
2020-04-09 17:35:43 -05:00
|
|
|
}
|
|
|
|
|
2020-04-11 10:04:25 -05:00
|
|
|
fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> {
|
|
|
|
match node.kind() {
|
2020-07-30 09:21:30 -05:00
|
|
|
RECORD_EXPR => vec![RECORD_EXPR_FIELD],
|
2020-07-31 13:09:09 -05:00
|
|
|
RECORD_PAT => vec![RECORD_PAT_FIELD, IDENT_PAT],
|
2020-04-11 10:04:25 -05:00
|
|
|
_ => vec![],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_field_name(node: &SyntaxNode) -> String {
|
2020-04-11 16:33:17 -05:00
|
|
|
let res = match_ast! {
|
|
|
|
match node {
|
2020-07-30 09:21:30 -05:00
|
|
|
ast::RecordExprField(field) => field.field_name().map(|it| it.to_string()),
|
2020-07-31 12:54:16 -05:00
|
|
|
ast::RecordPatField(field) => field.field_name().map(|it| it.to_string()),
|
2020-04-11 16:33:17 -05:00
|
|
|
_ => None,
|
2020-04-11 10:04:25 -05:00
|
|
|
}
|
2020-04-11 16:33:17 -05:00
|
|
|
};
|
|
|
|
res.unwrap_or_default()
|
2020-04-11 10:04:25 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_fields(record: &SyntaxNode) -> Vec<SyntaxNode> {
|
|
|
|
let kinds = get_fields_kind(record);
|
|
|
|
record.children().flat_map(|n| n.children()).filter(|n| kinds.contains(&n.kind())).collect()
|
2020-04-09 17:35:43 -05:00
|
|
|
}
|
|
|
|
|
2020-04-11 10:04:25 -05:00
|
|
|
fn sorted_by_rank(
|
|
|
|
fields: &[SyntaxNode],
|
|
|
|
get_rank: impl Fn(&SyntaxNode) -> usize,
|
|
|
|
) -> Vec<SyntaxNode> {
|
2020-04-09 17:35:43 -05:00
|
|
|
fields.iter().cloned().sorted_by_key(get_rank).collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<Struct> {
|
|
|
|
match sema.resolve_path(path) {
|
|
|
|
Some(PathResolution::Def(ModuleDef::Adt(Adt::Struct(s)))) => Some(s),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-29 10:07:52 -05:00
|
|
|
fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
|
2020-04-09 17:35:43 -05:00
|
|
|
Some(
|
2020-05-06 11:45:35 -05:00
|
|
|
struct_definition(path, &ctx.sema)?
|
2020-07-01 02:14:23 -05:00
|
|
|
.fields(ctx.db())
|
2020-04-09 17:35:43 -05:00
|
|
|
.iter()
|
|
|
|
.enumerate()
|
2020-07-01 02:14:23 -05:00
|
|
|
.map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
|
2020-04-09 17:35:43 -05:00
|
|
|
.collect(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2020-05-06 03:16:55 -05:00
|
|
|
use crate::tests::{check_assist, check_assist_not_applicable};
|
2020-04-09 17:35:43 -05:00
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn not_applicable_if_sorted() {
|
|
|
|
check_assist_not_applicable(
|
|
|
|
reorder_fields,
|
|
|
|
r#"
|
|
|
|
struct Foo {
|
|
|
|
foo: i32,
|
|
|
|
bar: i32,
|
|
|
|
}
|
|
|
|
|
|
|
|
const test: Foo = <|>Foo { foo: 0, bar: 0 };
|
|
|
|
"#,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn trivial_empty_fields() {
|
|
|
|
check_assist_not_applicable(
|
|
|
|
reorder_fields,
|
|
|
|
r#"
|
|
|
|
struct Foo {};
|
|
|
|
const test: Foo = <|>Foo {}
|
|
|
|
"#,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn reorder_struct_fields() {
|
|
|
|
check_assist(
|
|
|
|
reorder_fields,
|
|
|
|
r#"
|
|
|
|
struct Foo {foo: i32, bar: i32};
|
|
|
|
const test: Foo = <|>Foo {bar: 0, foo: 1}
|
|
|
|
"#,
|
|
|
|
r#"
|
|
|
|
struct Foo {foo: i32, bar: i32};
|
2020-05-20 15:55:37 -05:00
|
|
|
const test: Foo = Foo {foo: 1, bar: 0}
|
2020-04-09 17:35:43 -05:00
|
|
|
"#,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn reorder_struct_pattern() {
|
|
|
|
check_assist(
|
|
|
|
reorder_fields,
|
|
|
|
r#"
|
|
|
|
struct Foo { foo: i64, bar: i64, baz: i64 }
|
|
|
|
|
|
|
|
fn f(f: Foo) -> {
|
|
|
|
match f {
|
|
|
|
<|>Foo { baz: 0, ref mut bar, .. } => (),
|
|
|
|
_ => ()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
r#"
|
|
|
|
struct Foo { foo: i64, bar: i64, baz: i64 }
|
|
|
|
|
|
|
|
fn f(f: Foo) -> {
|
|
|
|
match f {
|
2020-05-20 15:55:37 -05:00
|
|
|
Foo { ref mut bar, baz: 0, .. } => (),
|
2020-04-09 17:35:43 -05:00
|
|
|
_ => ()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn reorder_with_extra_field() {
|
|
|
|
check_assist(
|
|
|
|
reorder_fields,
|
|
|
|
r#"
|
|
|
|
struct Foo {
|
|
|
|
foo: String,
|
|
|
|
bar: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Foo {
|
|
|
|
fn new() -> Foo {
|
|
|
|
let foo = String::new();
|
|
|
|
<|>Foo {
|
|
|
|
bar: foo.clone(),
|
|
|
|
extra: "Extra field",
|
|
|
|
foo,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
r#"
|
|
|
|
struct Foo {
|
|
|
|
foo: String,
|
|
|
|
bar: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Foo {
|
|
|
|
fn new() -> Foo {
|
|
|
|
let foo = String::new();
|
2020-05-20 15:55:37 -05:00
|
|
|
Foo {
|
2020-04-09 17:35:43 -05:00
|
|
|
foo,
|
|
|
|
bar: foo.clone(),
|
|
|
|
extra: "Extra field",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|