rust/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs

218 lines
4.4 KiB
Rust
Raw Normal View History

2021-05-17 03:40:34 -05:00
use hir::{db::AstDatabase, diagnostics::MissingFields, Semantics};
use ide_assists::AssistResolveStrategy;
use ide_db::{source_change::SourceChange, RootDatabase};
use syntax::{algo, ast::make, AstNode};
use text_edit::TextEdit;
use crate::{
2021-05-17 18:11:07 -05:00
diagnostics::{fix, fixes::DiagnosticWithFixes},
2021-05-17 03:40:34 -05:00
Assist,
};
2021-05-17 18:11:07 -05:00
impl DiagnosticWithFixes for MissingFields {
fn fixes(
2021-05-17 03:40:34 -05:00
&self,
sema: &Semantics<RootDatabase>,
_resolve: &AssistResolveStrategy,
2021-05-17 18:11:07 -05:00
) -> Option<Vec<Assist>> {
2021-05-17 03:40:34 -05:00
// Note that although we could add a diagnostics to
// fill the missing tuple field, e.g :
// `struct A(usize);`
// `let a = A { 0: () }`
// but it is uncommon usage and it should not be encouraged.
if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
return None;
}
let root = sema.db.parse_or_expand(self.file)?;
let field_list_parent = self.field_list_parent.to_node(&root);
let old_field_list = field_list_parent.record_expr_field_list()?;
let new_field_list = old_field_list.clone_for_update();
for f in self.missed_fields.iter() {
let field =
make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()))
.clone_for_update();
new_field_list.add_field(field);
}
let edit = {
let mut builder = TextEdit::builder();
2021-06-12 22:54:16 -05:00
algo::diff(old_field_list.syntax(), new_field_list.syntax())
2021-05-17 03:40:34 -05:00
.into_text_edit(&mut builder);
builder.finish()
};
2021-05-17 18:11:07 -05:00
Some(vec![fix(
2021-05-17 03:40:34 -05:00
"fill_missing_fields",
"Fill struct fields",
SourceChange::from_text_edit(self.file.original_file(sema.db), edit),
2021-06-12 22:54:16 -05:00
sema.original_range(field_list_parent.syntax()).range,
2021-05-17 18:11:07 -05:00
)])
2021-05-17 03:40:34 -05:00
}
}
#[cfg(test)]
mod tests {
use crate::diagnostics::tests::{check_fix, check_no_diagnostics};
#[test]
fn test_fill_struct_fields_empty() {
check_fix(
r#"
struct TestStruct { one: i32, two: i64 }
fn test_fn() {
let s = TestStruct {$0};
}
"#,
r#"
struct TestStruct { one: i32, two: i64 }
fn test_fn() {
let s = TestStruct { one: (), two: () };
}
"#,
);
}
#[test]
fn test_fill_struct_fields_self() {
check_fix(
r#"
struct TestStruct { one: i32 }
impl TestStruct {
fn test_fn() { let s = Self {$0}; }
}
"#,
r#"
struct TestStruct { one: i32 }
impl TestStruct {
fn test_fn() { let s = Self { one: () }; }
}
"#,
);
}
#[test]
fn test_fill_struct_fields_enum() {
check_fix(
r#"
enum Expr {
Bin { lhs: Box<Expr>, rhs: Box<Expr> }
}
impl Expr {
fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
Expr::Bin {$0 }
}
}
"#,
r#"
enum Expr {
Bin { lhs: Box<Expr>, rhs: Box<Expr> }
}
impl Expr {
fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
Expr::Bin { lhs: (), rhs: () }
}
}
"#,
);
}
#[test]
fn test_fill_struct_fields_partial() {
check_fix(
r#"
struct TestStruct { one: i32, two: i64 }
fn test_fn() {
let s = TestStruct{ two: 2$0 };
}
"#,
r"
struct TestStruct { one: i32, two: i64 }
fn test_fn() {
let s = TestStruct{ two: 2, one: () };
}
",
);
}
#[test]
fn test_fill_struct_fields_raw_ident() {
check_fix(
r#"
struct TestStruct { r#type: u8 }
fn test_fn() {
TestStruct { $0 };
}
"#,
r"
struct TestStruct { r#type: u8 }
fn test_fn() {
TestStruct { r#type: () };
}
",
);
}
#[test]
fn test_fill_struct_fields_no_diagnostic() {
check_no_diagnostics(
r#"
struct TestStruct { one: i32, two: i64 }
fn test_fn() {
let one = 1;
let s = TestStruct{ one, two: 2 };
}
"#,
);
}
#[test]
fn test_fill_struct_fields_no_diagnostic_on_spread() {
check_no_diagnostics(
r#"
struct TestStruct { one: i32, two: i64 }
fn test_fn() {
let one = 1;
let s = TestStruct{ ..a };
}
"#,
);
}
#[test]
fn test_fill_struct_fields_blank_line() {
check_fix(
r#"
struct S { a: (), b: () }
fn f() {
S {
$0
};
}
"#,
r#"
struct S { a: (), b: () }
fn f() {
S {
a: (),
b: (),
};
}
2021-05-17 03:40:34 -05:00
"#,
);
}
}