Implement assist "Reorder field names"
This commit is contained in:
parent
176f7f6117
commit
730a927c5e
5 changed files with 219 additions and 2 deletions
207
crates/ra_assists/src/handlers/reorder_fields.rs
Normal file
207
crates/ra_assists/src/handlers/reorder_fields.rs
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
|
||||||
|
use ra_ide_db::RootDatabase;
|
||||||
|
use ra_syntax::ast::{Name, Pat};
|
||||||
|
use ra_syntax::{
|
||||||
|
ast,
|
||||||
|
ast::{Path, RecordField, RecordLit, RecordPat},
|
||||||
|
AstNode,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
assist_ctx::{Assist, AssistCtx},
|
||||||
|
AssistId,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) fn reorder_fields(ctx: AssistCtx) -> Option<Assist> {
|
||||||
|
reorder_struct(ctx.clone()).or_else(|| reorder_struct_pat(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reorder_struct(ctx: AssistCtx) -> Option<Assist> {
|
||||||
|
let record: RecordLit = ctx.find_node_at_offset()?;
|
||||||
|
reorder(ctx, &record, &record.path()?, field_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field_name(r: &RecordField) -> String {
|
||||||
|
r.name_ref()
|
||||||
|
.map(|name| name.syntax().text())
|
||||||
|
.unwrap_or_else(|| r.expr().unwrap().syntax().text())
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reorder_struct_pat(ctx: AssistCtx) -> Option<Assist> {
|
||||||
|
let record: RecordPat = ctx.find_node_at_offset()?;
|
||||||
|
reorder(ctx, &record, &record.path()?, field_pat_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field_pat_name(field: &Pat) -> String {
|
||||||
|
field.syntax().children().find_map(Name::cast).map(|n| n.to_string()).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reorder<R: AstNode, F: AstNode + Eq + Clone>(
|
||||||
|
ctx: AssistCtx,
|
||||||
|
record: &R,
|
||||||
|
path: &Path,
|
||||||
|
field_name: fn(&F) -> String,
|
||||||
|
) -> Option<Assist> {
|
||||||
|
let ranks = compute_fields_ranks(path, &ctx)?;
|
||||||
|
let fields: Vec<F> = get_fields(record);
|
||||||
|
let sorted_fields: Vec<F> =
|
||||||
|
sort_by_rank(&fields, |f| *ranks.get(&field_name(f)).unwrap_or(&usize::max_value()));
|
||||||
|
|
||||||
|
if sorted_fields == fields {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", |edit| {
|
||||||
|
for (old, new) in fields.into_iter().zip(sorted_fields) {
|
||||||
|
edit.replace_ast(old, new);
|
||||||
|
}
|
||||||
|
edit.target(record.syntax().text_range())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_fields<R: AstNode, F: AstNode>(record: &R) -> Vec<F> {
|
||||||
|
record.syntax().children().flat_map(|n1| n1.children()).filter_map(|n3| F::cast(n3)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_by_rank<F: AstNode + Clone>(fields: &[F], get_rank: impl FnMut(&F) -> usize) -> Vec<F> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option<HashMap<String, usize>> {
|
||||||
|
Some(
|
||||||
|
struct_definition(path, ctx.sema)?
|
||||||
|
.fields(ctx.db)
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, field)| (field.name(ctx.db).to_string(), idx))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::helpers::{check_assist, check_assist_not_applicable};
|
||||||
|
|
||||||
|
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};
|
||||||
|
const test: Foo = <|>Foo {foo: 1, bar: 0}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 {
|
||||||
|
<|>Foo { ref mut bar, baz: 0, .. } => (),
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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();
|
||||||
|
<|>Foo {
|
||||||
|
foo,
|
||||||
|
bar: foo.clone(),
|
||||||
|
extra: "Extra field",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -129,6 +129,7 @@ mod handlers {
|
||||||
mod replace_unwrap_with_match;
|
mod replace_unwrap_with_match;
|
||||||
mod split_import;
|
mod split_import;
|
||||||
mod add_from_impl_for_enum;
|
mod add_from_impl_for_enum;
|
||||||
|
mod reorder_fields;
|
||||||
|
|
||||||
pub(crate) fn all() -> &'static [AssistHandler] {
|
pub(crate) fn all() -> &'static [AssistHandler] {
|
||||||
&[
|
&[
|
||||||
|
@ -170,6 +171,7 @@ mod handlers {
|
||||||
// These are manually sorted for better priorities
|
// These are manually sorted for better priorities
|
||||||
add_missing_impl_members::add_missing_impl_members,
|
add_missing_impl_members::add_missing_impl_members,
|
||||||
add_missing_impl_members::add_missing_default_members,
|
add_missing_impl_members::add_missing_default_members,
|
||||||
|
reorder_fields::reorder_fields,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -689,9 +689,10 @@ impl ExprCollector<'_> {
|
||||||
Pat::Missing
|
Pat::Missing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: implement
|
// FIXME: implement
|
||||||
ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing,
|
ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing,
|
||||||
|
// FIXME: implement
|
||||||
|
ast::Pat::RecordFieldPat(_) => Pat::Missing,
|
||||||
};
|
};
|
||||||
let ptr = AstPtr::new(&pat);
|
let ptr = AstPtr::new(&pat);
|
||||||
self.alloc_pat(pattern, Either::Left(ptr))
|
self.alloc_pat(pattern, Either::Left(ptr))
|
||||||
|
|
|
@ -3256,6 +3256,7 @@ pub enum Pat {
|
||||||
RangePat(RangePat),
|
RangePat(RangePat),
|
||||||
LiteralPat(LiteralPat),
|
LiteralPat(LiteralPat),
|
||||||
MacroPat(MacroPat),
|
MacroPat(MacroPat),
|
||||||
|
RecordFieldPat(RecordFieldPat),
|
||||||
}
|
}
|
||||||
impl From<OrPat> for Pat {
|
impl From<OrPat> for Pat {
|
||||||
fn from(node: OrPat) -> Pat { Pat::OrPat(node) }
|
fn from(node: OrPat) -> Pat { Pat::OrPat(node) }
|
||||||
|
@ -3302,12 +3303,15 @@ impl From<LiteralPat> for Pat {
|
||||||
impl From<MacroPat> for Pat {
|
impl From<MacroPat> for Pat {
|
||||||
fn from(node: MacroPat) -> Pat { Pat::MacroPat(node) }
|
fn from(node: MacroPat) -> Pat { Pat::MacroPat(node) }
|
||||||
}
|
}
|
||||||
|
impl From<RecordFieldPat> for Pat {
|
||||||
|
fn from(node: RecordFieldPat) -> Pat { Pat::RecordFieldPat(node) }
|
||||||
|
}
|
||||||
impl AstNode for Pat {
|
impl AstNode for Pat {
|
||||||
fn can_cast(kind: SyntaxKind) -> bool {
|
fn can_cast(kind: SyntaxKind) -> bool {
|
||||||
match kind {
|
match kind {
|
||||||
OR_PAT | PAREN_PAT | REF_PAT | BOX_PAT | BIND_PAT | PLACEHOLDER_PAT | DOT_DOT_PAT
|
OR_PAT | PAREN_PAT | REF_PAT | BOX_PAT | BIND_PAT | PLACEHOLDER_PAT | DOT_DOT_PAT
|
||||||
| PATH_PAT | RECORD_PAT | TUPLE_STRUCT_PAT | TUPLE_PAT | SLICE_PAT | RANGE_PAT
|
| PATH_PAT | RECORD_PAT | TUPLE_STRUCT_PAT | TUPLE_PAT | SLICE_PAT | RANGE_PAT
|
||||||
| LITERAL_PAT | MACRO_PAT => true,
|
| LITERAL_PAT | MACRO_PAT | RECORD_FIELD_PAT => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3328,6 +3332,7 @@ impl AstNode for Pat {
|
||||||
RANGE_PAT => Pat::RangePat(RangePat { syntax }),
|
RANGE_PAT => Pat::RangePat(RangePat { syntax }),
|
||||||
LITERAL_PAT => Pat::LiteralPat(LiteralPat { syntax }),
|
LITERAL_PAT => Pat::LiteralPat(LiteralPat { syntax }),
|
||||||
MACRO_PAT => Pat::MacroPat(MacroPat { syntax }),
|
MACRO_PAT => Pat::MacroPat(MacroPat { syntax }),
|
||||||
|
RECORD_FIELD_PAT => Pat::RecordFieldPat(RecordFieldPat { syntax }),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
Some(res)
|
Some(res)
|
||||||
|
@ -3349,6 +3354,7 @@ impl AstNode for Pat {
|
||||||
Pat::RangePat(it) => &it.syntax,
|
Pat::RangePat(it) => &it.syntax,
|
||||||
Pat::LiteralPat(it) => &it.syntax,
|
Pat::LiteralPat(it) => &it.syntax,
|
||||||
Pat::MacroPat(it) => &it.syntax,
|
Pat::MacroPat(it) => &it.syntax,
|
||||||
|
Pat::RecordFieldPat(it) => &it.syntax,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -741,6 +741,7 @@ pub(crate) const AST_SRC: AstSrc = AstSrc {
|
||||||
RangePat,
|
RangePat,
|
||||||
LiteralPat,
|
LiteralPat,
|
||||||
MacroPat,
|
MacroPat,
|
||||||
|
RecordFieldPat,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RecordInnerPat {
|
enum RecordInnerPat {
|
||||||
|
|
Loading…
Add table
Reference in a new issue