mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-27 12:29:21 +00:00
internal: scalable module structure for fixits
This commit is contained in:
parent
db8fbb99ce
commit
fa7fc0e5cb
9 changed files with 695 additions and 769 deletions
|
@ -375,7 +375,7 @@ mod tests {
|
||||||
assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
|
assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_expect(ra_fixture: &str, expect: Expect) {
|
pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) {
|
||||||
let (analysis, file_id) = fixture::file(ra_fixture);
|
let (analysis, file_id) = fixture::file(ra_fixture);
|
||||||
let diagnostics = analysis
|
let diagnostics = analysis
|
||||||
.diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
|
.diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
|
||||||
|
@ -383,374 +383,6 @@ mod tests {
|
||||||
expect.assert_debug_eq(&diagnostics)
|
expect.assert_debug_eq(&diagnostics)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_wrap_return_type_option() {
|
|
||||||
check_fix(
|
|
||||||
r#"
|
|
||||||
//- /main.rs crate:main deps:core
|
|
||||||
use core::option::Option::{self, Some, None};
|
|
||||||
|
|
||||||
fn div(x: i32, y: i32) -> Option<i32> {
|
|
||||||
if y == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
x / y$0
|
|
||||||
}
|
|
||||||
//- /core/lib.rs crate:core
|
|
||||||
pub mod result {
|
|
||||||
pub enum Result<T, E> { Ok(T), Err(E) }
|
|
||||||
}
|
|
||||||
pub mod option {
|
|
||||||
pub enum Option<T> { Some(T), None }
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
r#"
|
|
||||||
use core::option::Option::{self, Some, None};
|
|
||||||
|
|
||||||
fn div(x: i32, y: i32) -> Option<i32> {
|
|
||||||
if y == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(x / y)
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_wrap_return_type() {
|
|
||||||
check_fix(
|
|
||||||
r#"
|
|
||||||
//- /main.rs crate:main deps:core
|
|
||||||
use core::result::Result::{self, Ok, Err};
|
|
||||||
|
|
||||||
fn div(x: i32, y: i32) -> Result<i32, ()> {
|
|
||||||
if y == 0 {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
x / y$0
|
|
||||||
}
|
|
||||||
//- /core/lib.rs crate:core
|
|
||||||
pub mod result {
|
|
||||||
pub enum Result<T, E> { Ok(T), Err(E) }
|
|
||||||
}
|
|
||||||
pub mod option {
|
|
||||||
pub enum Option<T> { Some(T), None }
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
r#"
|
|
||||||
use core::result::Result::{self, Ok, Err};
|
|
||||||
|
|
||||||
fn div(x: i32, y: i32) -> Result<i32, ()> {
|
|
||||||
if y == 0 {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
Ok(x / y)
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_wrap_return_type_handles_generic_functions() {
|
|
||||||
check_fix(
|
|
||||||
r#"
|
|
||||||
//- /main.rs crate:main deps:core
|
|
||||||
use core::result::Result::{self, Ok, Err};
|
|
||||||
|
|
||||||
fn div<T>(x: T) -> Result<T, i32> {
|
|
||||||
if x == 0 {
|
|
||||||
return Err(7);
|
|
||||||
}
|
|
||||||
$0x
|
|
||||||
}
|
|
||||||
//- /core/lib.rs crate:core
|
|
||||||
pub mod result {
|
|
||||||
pub enum Result<T, E> { Ok(T), Err(E) }
|
|
||||||
}
|
|
||||||
pub mod option {
|
|
||||||
pub enum Option<T> { Some(T), None }
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
r#"
|
|
||||||
use core::result::Result::{self, Ok, Err};
|
|
||||||
|
|
||||||
fn div<T>(x: T) -> Result<T, i32> {
|
|
||||||
if x == 0 {
|
|
||||||
return Err(7);
|
|
||||||
}
|
|
||||||
Ok(x)
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_wrap_return_type_handles_type_aliases() {
|
|
||||||
check_fix(
|
|
||||||
r#"
|
|
||||||
//- /main.rs crate:main deps:core
|
|
||||||
use core::result::Result::{self, Ok, Err};
|
|
||||||
|
|
||||||
type MyResult<T> = Result<T, ()>;
|
|
||||||
|
|
||||||
fn div(x: i32, y: i32) -> MyResult<i32> {
|
|
||||||
if y == 0 {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
x $0/ y
|
|
||||||
}
|
|
||||||
//- /core/lib.rs crate:core
|
|
||||||
pub mod result {
|
|
||||||
pub enum Result<T, E> { Ok(T), Err(E) }
|
|
||||||
}
|
|
||||||
pub mod option {
|
|
||||||
pub enum Option<T> { Some(T), None }
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
r#"
|
|
||||||
use core::result::Result::{self, Ok, Err};
|
|
||||||
|
|
||||||
type MyResult<T> = Result<T, ()>;
|
|
||||||
|
|
||||||
fn div(x: i32, y: i32) -> MyResult<i32> {
|
|
||||||
if y == 0 {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
Ok(x / y)
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
|
|
||||||
check_no_diagnostics(
|
|
||||||
r#"
|
|
||||||
//- /main.rs crate:main deps:core
|
|
||||||
use core::result::Result::{self, Ok, Err};
|
|
||||||
|
|
||||||
fn foo() -> Result<(), i32> { 0 }
|
|
||||||
|
|
||||||
//- /core/lib.rs crate:core
|
|
||||||
pub mod result {
|
|
||||||
pub enum Result<T, E> { Ok(T), Err(E) }
|
|
||||||
}
|
|
||||||
pub mod option {
|
|
||||||
pub enum Option<T> { Some(T), None }
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
|
|
||||||
check_no_diagnostics(
|
|
||||||
r#"
|
|
||||||
//- /main.rs crate:main deps:core
|
|
||||||
use core::result::Result::{self, Ok, Err};
|
|
||||||
|
|
||||||
enum SomeOtherEnum { Ok(i32), Err(String) }
|
|
||||||
|
|
||||||
fn foo() -> SomeOtherEnum { 0 }
|
|
||||||
|
|
||||||
//- /core/lib.rs crate:core
|
|
||||||
pub mod result {
|
|
||||||
pub enum Result<T, E> { Ok(T), Err(E) }
|
|
||||||
}
|
|
||||||
pub mod option {
|
|
||||||
pub enum Option<T> { Some(T), None }
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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_unresolved_module_diagnostic() {
|
|
||||||
check_expect(
|
|
||||||
r#"mod foo;"#,
|
|
||||||
expect![[r#"
|
|
||||||
[
|
|
||||||
Diagnostic {
|
|
||||||
message: "unresolved module",
|
|
||||||
range: 0..8,
|
|
||||||
severity: Error,
|
|
||||||
fix: Some(
|
|
||||||
Assist {
|
|
||||||
id: AssistId(
|
|
||||||
"create_module",
|
|
||||||
QuickFix,
|
|
||||||
),
|
|
||||||
label: "Create module",
|
|
||||||
group: None,
|
|
||||||
target: 0..8,
|
|
||||||
source_change: Some(
|
|
||||||
SourceChange {
|
|
||||||
source_file_edits: {},
|
|
||||||
file_system_edits: [
|
|
||||||
CreateFile {
|
|
||||||
dst: AnchoredPathBuf {
|
|
||||||
anchor: FileId(
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
path: "foo.rs",
|
|
||||||
},
|
|
||||||
initial_contents: "",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
is_snippet: false,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
unused: false,
|
|
||||||
code: Some(
|
|
||||||
DiagnosticCode(
|
|
||||||
"unresolved-module",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
"#]],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unresolved_macro_range() {
|
fn test_unresolved_macro_range() {
|
||||||
check_expect(
|
check_expect(
|
||||||
|
@ -890,53 +522,6 @@ mod a {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_add_field_from_usage() {
|
|
||||||
check_fix(
|
|
||||||
r"
|
|
||||||
fn main() {
|
|
||||||
Foo { bar: 3, baz$0: false};
|
|
||||||
}
|
|
||||||
struct Foo {
|
|
||||||
bar: i32
|
|
||||||
}
|
|
||||||
",
|
|
||||||
r"
|
|
||||||
fn main() {
|
|
||||||
Foo { bar: 3, baz: false};
|
|
||||||
}
|
|
||||||
struct Foo {
|
|
||||||
bar: i32,
|
|
||||||
baz: bool
|
|
||||||
}
|
|
||||||
",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_add_field_in_other_file_from_usage() {
|
|
||||||
check_fix(
|
|
||||||
r#"
|
|
||||||
//- /main.rs
|
|
||||||
mod foo;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
foo::Foo { bar: 3, $0baz: false};
|
|
||||||
}
|
|
||||||
//- /foo.rs
|
|
||||||
struct Foo {
|
|
||||||
bar: i32
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
r#"
|
|
||||||
struct Foo {
|
|
||||||
bar: i32,
|
|
||||||
pub(crate) baz: bool
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_disabled_diagnostics() {
|
fn test_disabled_diagnostics() {
|
||||||
let mut config = DiagnosticsConfig::default();
|
let mut config = DiagnosticsConfig::default();
|
||||||
|
@ -954,120 +539,6 @@ struct Foo {
|
||||||
assert!(!diagnostics.is_empty());
|
assert!(!diagnostics.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rename_incorrect_case() {
|
|
||||||
check_fix(
|
|
||||||
r#"
|
|
||||||
pub struct test_struct$0 { one: i32 }
|
|
||||||
|
|
||||||
pub fn some_fn(val: test_struct) -> test_struct {
|
|
||||||
test_struct { one: val.one + 1 }
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
r#"
|
|
||||||
pub struct TestStruct { one: i32 }
|
|
||||||
|
|
||||||
pub fn some_fn(val: TestStruct) -> TestStruct {
|
|
||||||
TestStruct { one: val.one + 1 }
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
check_fix(
|
|
||||||
r#"
|
|
||||||
pub fn some_fn(NonSnakeCase$0: u8) -> u8 {
|
|
||||||
NonSnakeCase
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
r#"
|
|
||||||
pub fn some_fn(non_snake_case: u8) -> u8 {
|
|
||||||
non_snake_case
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
check_fix(
|
|
||||||
r#"
|
|
||||||
pub fn SomeFn$0(val: u8) -> u8 {
|
|
||||||
if val != 0 { SomeFn(val - 1) } else { val }
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
r#"
|
|
||||||
pub fn some_fn(val: u8) -> u8 {
|
|
||||||
if val != 0 { some_fn(val - 1) } else { val }
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
check_fix(
|
|
||||||
r#"
|
|
||||||
fn some_fn() {
|
|
||||||
let whatAWeird_Formatting$0 = 10;
|
|
||||||
another_func(whatAWeird_Formatting);
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
r#"
|
|
||||||
fn some_fn() {
|
|
||||||
let what_a_weird_formatting = 10;
|
|
||||||
another_func(what_a_weird_formatting);
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_uppercase_const_no_diagnostics() {
|
|
||||||
check_no_diagnostics(
|
|
||||||
r#"
|
|
||||||
fn foo() {
|
|
||||||
const ANOTHER_ITEM$0: &str = "some_item";
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rename_incorrect_case_struct_method() {
|
|
||||||
check_fix(
|
|
||||||
r#"
|
|
||||||
pub struct TestStruct;
|
|
||||||
|
|
||||||
impl TestStruct {
|
|
||||||
pub fn SomeFn$0() -> TestStruct {
|
|
||||||
TestStruct
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
r#"
|
|
||||||
pub struct TestStruct;
|
|
||||||
|
|
||||||
impl TestStruct {
|
|
||||||
pub fn some_fn() -> TestStruct {
|
|
||||||
TestStruct
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_single_incorrect_case_diagnostic_in_function_name_issue_6970() {
|
|
||||||
let input = r#"fn FOO$0() {}"#;
|
|
||||||
let expected = r#"fn foo() {}"#;
|
|
||||||
|
|
||||||
let (analysis, file_position) = fixture::position(input);
|
|
||||||
let diagnostics = analysis
|
|
||||||
.diagnostics(
|
|
||||||
&DiagnosticsConfig::default(),
|
|
||||||
AssistResolveStrategy::All,
|
|
||||||
file_position.file_id,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(diagnostics.len(), 1);
|
|
||||||
|
|
||||||
check_fix(input, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unlinked_file_prepend_first_item() {
|
fn unlinked_file_prepend_first_item() {
|
||||||
cov_mark::check!(unlinked_file_prepend_before_first_item);
|
cov_mark::check!(unlinked_file_prepend_before_first_item);
|
||||||
|
|
|
@ -1,32 +1,18 @@
|
||||||
//! Provides a way to attach fixes to the diagnostics.
|
//! Provides a way to attach fixes to the diagnostics.
|
||||||
//! The same module also has all curret custom fixes for the diagnostics implemented.
|
//! The same module also has all curret custom fixes for the diagnostics implemented.
|
||||||
|
mod change_case;
|
||||||
|
mod create_field;
|
||||||
mod fill_missing_fields;
|
mod fill_missing_fields;
|
||||||
|
mod remove_semicolon;
|
||||||
|
mod replace_with_find_map;
|
||||||
|
mod unresolved_module;
|
||||||
|
mod wrap_tail_expr;
|
||||||
|
|
||||||
use hir::{
|
use hir::{diagnostics::Diagnostic, Semantics};
|
||||||
db::AstDatabase,
|
|
||||||
diagnostics::{
|
|
||||||
Diagnostic, IncorrectCase, MissingOkOrSomeInTailExpr, NoSuchField, RemoveThisSemicolon,
|
|
||||||
ReplaceFilterMapNextWithFindMap, UnresolvedModule,
|
|
||||||
},
|
|
||||||
HasSource, HirDisplay, InFile, Semantics, VariantDef,
|
|
||||||
};
|
|
||||||
use ide_assists::AssistResolveStrategy;
|
use ide_assists::AssistResolveStrategy;
|
||||||
use ide_db::{
|
use ide_db::RootDatabase;
|
||||||
base_db::{AnchoredPathBuf, FileId},
|
|
||||||
source_change::{FileSystemEdit, SourceChange},
|
|
||||||
RootDatabase,
|
|
||||||
};
|
|
||||||
use syntax::{
|
|
||||||
ast::{self, edit::IndentLevel, make, ArgListOwner},
|
|
||||||
AstNode, TextRange,
|
|
||||||
};
|
|
||||||
use text_edit::TextEdit;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::Assist;
|
||||||
diagnostics::{fix, unresolved_fix},
|
|
||||||
references::rename::rename_with_semantics,
|
|
||||||
Assist, FilePosition,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A [Diagnostic] that potentially has a fix available.
|
/// A [Diagnostic] that potentially has a fix available.
|
||||||
///
|
///
|
||||||
|
@ -43,216 +29,3 @@ pub(crate) trait DiagnosticWithFix: Diagnostic {
|
||||||
_resolve: &AssistResolveStrategy,
|
_resolve: &AssistResolveStrategy,
|
||||||
) -> Option<Assist>;
|
) -> Option<Assist>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiagnosticWithFix for UnresolvedModule {
|
|
||||||
fn fix(
|
|
||||||
&self,
|
|
||||||
sema: &Semantics<RootDatabase>,
|
|
||||||
_resolve: &AssistResolveStrategy,
|
|
||||||
) -> Option<Assist> {
|
|
||||||
let root = sema.db.parse_or_expand(self.file)?;
|
|
||||||
let unresolved_module = self.decl.to_node(&root);
|
|
||||||
Some(fix(
|
|
||||||
"create_module",
|
|
||||||
"Create module",
|
|
||||||
FileSystemEdit::CreateFile {
|
|
||||||
dst: AnchoredPathBuf {
|
|
||||||
anchor: self.file.original_file(sema.db),
|
|
||||||
path: self.candidate.clone(),
|
|
||||||
},
|
|
||||||
initial_contents: "".to_string(),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
unresolved_module.syntax().text_range(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiagnosticWithFix for NoSuchField {
|
|
||||||
fn fix(
|
|
||||||
&self,
|
|
||||||
sema: &Semantics<RootDatabase>,
|
|
||||||
_resolve: &AssistResolveStrategy,
|
|
||||||
) -> Option<Assist> {
|
|
||||||
let root = sema.db.parse_or_expand(self.file)?;
|
|
||||||
missing_record_expr_field_fix(
|
|
||||||
&sema,
|
|
||||||
self.file.original_file(sema.db),
|
|
||||||
&self.field.to_node(&root),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
|
|
||||||
fn fix(
|
|
||||||
&self,
|
|
||||||
sema: &Semantics<RootDatabase>,
|
|
||||||
_resolve: &AssistResolveStrategy,
|
|
||||||
) -> Option<Assist> {
|
|
||||||
let root = sema.db.parse_or_expand(self.file)?;
|
|
||||||
let tail_expr = self.expr.to_node(&root);
|
|
||||||
let tail_expr_range = tail_expr.syntax().text_range();
|
|
||||||
let replacement = format!("{}({})", self.required, tail_expr.syntax());
|
|
||||||
let edit = TextEdit::replace(tail_expr_range, replacement);
|
|
||||||
let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
|
|
||||||
let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" };
|
|
||||||
Some(fix("wrap_tail_expr", name, source_change, tail_expr_range))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiagnosticWithFix for RemoveThisSemicolon {
|
|
||||||
fn fix(
|
|
||||||
&self,
|
|
||||||
sema: &Semantics<RootDatabase>,
|
|
||||||
_resolve: &AssistResolveStrategy,
|
|
||||||
) -> Option<Assist> {
|
|
||||||
let root = sema.db.parse_or_expand(self.file)?;
|
|
||||||
|
|
||||||
let semicolon = self
|
|
||||||
.expr
|
|
||||||
.to_node(&root)
|
|
||||||
.syntax()
|
|
||||||
.parent()
|
|
||||||
.and_then(ast::ExprStmt::cast)
|
|
||||||
.and_then(|expr| expr.semicolon_token())?
|
|
||||||
.text_range();
|
|
||||||
|
|
||||||
let edit = TextEdit::delete(semicolon);
|
|
||||||
let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
|
|
||||||
|
|
||||||
Some(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiagnosticWithFix for IncorrectCase {
|
|
||||||
fn fix(
|
|
||||||
&self,
|
|
||||||
sema: &Semantics<RootDatabase>,
|
|
||||||
resolve: &AssistResolveStrategy,
|
|
||||||
) -> Option<Assist> {
|
|
||||||
let root = sema.db.parse_or_expand(self.file)?;
|
|
||||||
let name_node = self.ident.to_node(&root);
|
|
||||||
|
|
||||||
let name_node = InFile::new(self.file, name_node.syntax());
|
|
||||||
let frange = name_node.original_file_range(sema.db);
|
|
||||||
let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
|
|
||||||
|
|
||||||
let label = format!("Rename to {}", self.suggested_text);
|
|
||||||
let mut res = unresolved_fix("change_case", &label, frange.range);
|
|
||||||
if resolve.should_resolve(&res.id) {
|
|
||||||
let source_change = rename_with_semantics(sema, file_position, &self.suggested_text);
|
|
||||||
res.source_change = Some(source_change.ok().unwrap_or_default());
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
|
|
||||||
fn fix(
|
|
||||||
&self,
|
|
||||||
sema: &Semantics<RootDatabase>,
|
|
||||||
_resolve: &AssistResolveStrategy,
|
|
||||||
) -> Option<Assist> {
|
|
||||||
let root = sema.db.parse_or_expand(self.file)?;
|
|
||||||
let next_expr = self.next_expr.to_node(&root);
|
|
||||||
let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
|
|
||||||
|
|
||||||
let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?;
|
|
||||||
let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range();
|
|
||||||
let filter_map_args = filter_map_call.arg_list()?;
|
|
||||||
|
|
||||||
let range_to_replace =
|
|
||||||
TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end());
|
|
||||||
let replacement = format!("find_map{}", filter_map_args.syntax().text());
|
|
||||||
let trigger_range = next_expr.syntax().text_range();
|
|
||||||
|
|
||||||
let edit = TextEdit::replace(range_to_replace, replacement);
|
|
||||||
|
|
||||||
let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
|
|
||||||
|
|
||||||
Some(fix(
|
|
||||||
"replace_with_find_map",
|
|
||||||
"Replace filter_map(..).next() with find_map()",
|
|
||||||
source_change,
|
|
||||||
trigger_range,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn missing_record_expr_field_fix(
|
|
||||||
sema: &Semantics<RootDatabase>,
|
|
||||||
usage_file_id: FileId,
|
|
||||||
record_expr_field: &ast::RecordExprField,
|
|
||||||
) -> Option<Assist> {
|
|
||||||
let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
|
|
||||||
let def_id = sema.resolve_variant(record_lit)?;
|
|
||||||
let module;
|
|
||||||
let def_file_id;
|
|
||||||
let record_fields = match def_id {
|
|
||||||
VariantDef::Struct(s) => {
|
|
||||||
module = s.module(sema.db);
|
|
||||||
let source = s.source(sema.db)?;
|
|
||||||
def_file_id = source.file_id;
|
|
||||||
let fields = source.value.field_list()?;
|
|
||||||
record_field_list(fields)?
|
|
||||||
}
|
|
||||||
VariantDef::Union(u) => {
|
|
||||||
module = u.module(sema.db);
|
|
||||||
let source = u.source(sema.db)?;
|
|
||||||
def_file_id = source.file_id;
|
|
||||||
source.value.record_field_list()?
|
|
||||||
}
|
|
||||||
VariantDef::Variant(e) => {
|
|
||||||
module = e.module(sema.db);
|
|
||||||
let source = e.source(sema.db)?;
|
|
||||||
def_file_id = source.file_id;
|
|
||||||
let fields = source.value.field_list()?;
|
|
||||||
record_field_list(fields)?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let def_file_id = def_file_id.original_file(sema.db);
|
|
||||||
|
|
||||||
let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
|
|
||||||
if new_field_type.is_unknown() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let new_field = make::record_field(
|
|
||||||
None,
|
|
||||||
make::name(&record_expr_field.field_name()?.text()),
|
|
||||||
make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
|
|
||||||
);
|
|
||||||
|
|
||||||
let last_field = record_fields.fields().last()?;
|
|
||||||
let last_field_syntax = last_field.syntax();
|
|
||||||
let indent = IndentLevel::from_node(last_field_syntax);
|
|
||||||
|
|
||||||
let mut new_field = new_field.to_string();
|
|
||||||
if usage_file_id != def_file_id {
|
|
||||||
new_field = format!("pub(crate) {}", new_field);
|
|
||||||
}
|
|
||||||
new_field = format!("\n{}{}", indent, new_field);
|
|
||||||
|
|
||||||
let needs_comma = !last_field_syntax.to_string().ends_with(',');
|
|
||||||
if needs_comma {
|
|
||||||
new_field = format!(",{}", new_field);
|
|
||||||
}
|
|
||||||
|
|
||||||
let source_change = SourceChange::from_text_edit(
|
|
||||||
def_file_id,
|
|
||||||
TextEdit::insert(last_field_syntax.text_range().end(), new_field),
|
|
||||||
);
|
|
||||||
return Some(fix(
|
|
||||||
"create_field",
|
|
||||||
"Create field",
|
|
||||||
source_change,
|
|
||||||
record_expr_field.syntax().text_range(),
|
|
||||||
));
|
|
||||||
|
|
||||||
fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
|
|
||||||
match field_def_list {
|
|
||||||
ast::FieldList::RecordFieldList(it) => Some(it),
|
|
||||||
ast::FieldList::TupleFieldList(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
155
crates/ide/src/diagnostics/fixes/change_case.rs
Normal file
155
crates/ide/src/diagnostics/fixes/change_case.rs
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
use hir::{db::AstDatabase, diagnostics::IncorrectCase, InFile, Semantics};
|
||||||
|
use ide_assists::{Assist, AssistResolveStrategy};
|
||||||
|
use ide_db::{base_db::FilePosition, RootDatabase};
|
||||||
|
use syntax::AstNode;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
diagnostics::{unresolved_fix, DiagnosticWithFix},
|
||||||
|
references::rename::rename_with_semantics,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl DiagnosticWithFix for IncorrectCase {
|
||||||
|
fn fix(
|
||||||
|
&self,
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
resolve: &AssistResolveStrategy,
|
||||||
|
) -> Option<Assist> {
|
||||||
|
let root = sema.db.parse_or_expand(self.file)?;
|
||||||
|
let name_node = self.ident.to_node(&root);
|
||||||
|
|
||||||
|
let name_node = InFile::new(self.file, name_node.syntax());
|
||||||
|
let frange = name_node.original_file_range(sema.db);
|
||||||
|
let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
|
||||||
|
|
||||||
|
let label = format!("Rename to {}", self.suggested_text);
|
||||||
|
let mut res = unresolved_fix("change_case", &label, frange.range);
|
||||||
|
if resolve.should_resolve(&res.id) {
|
||||||
|
let source_change = rename_with_semantics(sema, file_position, &self.suggested_text);
|
||||||
|
res.source_change = Some(source_change.ok().unwrap_or_default());
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod change_case {
|
||||||
|
use crate::{
|
||||||
|
diagnostics::tests::{check_fix, check_no_diagnostics},
|
||||||
|
fixture, AssistResolveStrategy, DiagnosticsConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rename_incorrect_case() {
|
||||||
|
check_fix(
|
||||||
|
r#"
|
||||||
|
pub struct test_struct$0 { one: i32 }
|
||||||
|
|
||||||
|
pub fn some_fn(val: test_struct) -> test_struct {
|
||||||
|
test_struct { one: val.one + 1 }
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
pub struct TestStruct { one: i32 }
|
||||||
|
|
||||||
|
pub fn some_fn(val: TestStruct) -> TestStruct {
|
||||||
|
TestStruct { one: val.one + 1 }
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
check_fix(
|
||||||
|
r#"
|
||||||
|
pub fn some_fn(NonSnakeCase$0: u8) -> u8 {
|
||||||
|
NonSnakeCase
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
pub fn some_fn(non_snake_case: u8) -> u8 {
|
||||||
|
non_snake_case
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
check_fix(
|
||||||
|
r#"
|
||||||
|
pub fn SomeFn$0(val: u8) -> u8 {
|
||||||
|
if val != 0 { SomeFn(val - 1) } else { val }
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
pub fn some_fn(val: u8) -> u8 {
|
||||||
|
if val != 0 { some_fn(val - 1) } else { val }
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
check_fix(
|
||||||
|
r#"
|
||||||
|
fn some_fn() {
|
||||||
|
let whatAWeird_Formatting$0 = 10;
|
||||||
|
another_func(whatAWeird_Formatting);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
fn some_fn() {
|
||||||
|
let what_a_weird_formatting = 10;
|
||||||
|
another_func(what_a_weird_formatting);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_uppercase_const_no_diagnostics() {
|
||||||
|
check_no_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn foo() {
|
||||||
|
const ANOTHER_ITEM$0: &str = "some_item";
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rename_incorrect_case_struct_method() {
|
||||||
|
check_fix(
|
||||||
|
r#"
|
||||||
|
pub struct TestStruct;
|
||||||
|
|
||||||
|
impl TestStruct {
|
||||||
|
pub fn SomeFn$0() -> TestStruct {
|
||||||
|
TestStruct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
pub struct TestStruct;
|
||||||
|
|
||||||
|
impl TestStruct {
|
||||||
|
pub fn some_fn() -> TestStruct {
|
||||||
|
TestStruct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_incorrect_case_diagnostic_in_function_name_issue_6970() {
|
||||||
|
let input = r#"fn FOO$0() {}"#;
|
||||||
|
let expected = r#"fn foo() {}"#;
|
||||||
|
|
||||||
|
let (analysis, file_position) = fixture::position(input);
|
||||||
|
let diagnostics = analysis
|
||||||
|
.diagnostics(
|
||||||
|
&DiagnosticsConfig::default(),
|
||||||
|
AssistResolveStrategy::All,
|
||||||
|
file_position.file_id,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(diagnostics.len(), 1);
|
||||||
|
|
||||||
|
check_fix(input, expected);
|
||||||
|
}
|
||||||
|
}
|
157
crates/ide/src/diagnostics/fixes/create_field.rs
Normal file
157
crates/ide/src/diagnostics/fixes/create_field.rs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
use hir::{db::AstDatabase, diagnostics::NoSuchField, HasSource, HirDisplay, Semantics};
|
||||||
|
use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase};
|
||||||
|
use syntax::{
|
||||||
|
ast::{self, edit::IndentLevel, make},
|
||||||
|
AstNode,
|
||||||
|
};
|
||||||
|
use text_edit::TextEdit;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
diagnostics::{fix, DiagnosticWithFix},
|
||||||
|
Assist, AssistResolveStrategy,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl DiagnosticWithFix for NoSuchField {
|
||||||
|
fn fix(
|
||||||
|
&self,
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
_resolve: &AssistResolveStrategy,
|
||||||
|
) -> Option<Assist> {
|
||||||
|
let root = sema.db.parse_or_expand(self.file)?;
|
||||||
|
missing_record_expr_field_fix(
|
||||||
|
&sema,
|
||||||
|
self.file.original_file(sema.db),
|
||||||
|
&self.field.to_node(&root),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn missing_record_expr_field_fix(
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
usage_file_id: FileId,
|
||||||
|
record_expr_field: &ast::RecordExprField,
|
||||||
|
) -> Option<Assist> {
|
||||||
|
let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
|
||||||
|
let def_id = sema.resolve_variant(record_lit)?;
|
||||||
|
let module;
|
||||||
|
let def_file_id;
|
||||||
|
let record_fields = match def_id {
|
||||||
|
hir::VariantDef::Struct(s) => {
|
||||||
|
module = s.module(sema.db);
|
||||||
|
let source = s.source(sema.db)?;
|
||||||
|
def_file_id = source.file_id;
|
||||||
|
let fields = source.value.field_list()?;
|
||||||
|
record_field_list(fields)?
|
||||||
|
}
|
||||||
|
hir::VariantDef::Union(u) => {
|
||||||
|
module = u.module(sema.db);
|
||||||
|
let source = u.source(sema.db)?;
|
||||||
|
def_file_id = source.file_id;
|
||||||
|
source.value.record_field_list()?
|
||||||
|
}
|
||||||
|
hir::VariantDef::Variant(e) => {
|
||||||
|
module = e.module(sema.db);
|
||||||
|
let source = e.source(sema.db)?;
|
||||||
|
def_file_id = source.file_id;
|
||||||
|
let fields = source.value.field_list()?;
|
||||||
|
record_field_list(fields)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let def_file_id = def_file_id.original_file(sema.db);
|
||||||
|
|
||||||
|
let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
|
||||||
|
if new_field_type.is_unknown() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let new_field = make::record_field(
|
||||||
|
None,
|
||||||
|
make::name(&record_expr_field.field_name()?.text()),
|
||||||
|
make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
|
||||||
|
);
|
||||||
|
|
||||||
|
let last_field = record_fields.fields().last()?;
|
||||||
|
let last_field_syntax = last_field.syntax();
|
||||||
|
let indent = IndentLevel::from_node(last_field_syntax);
|
||||||
|
|
||||||
|
let mut new_field = new_field.to_string();
|
||||||
|
if usage_file_id != def_file_id {
|
||||||
|
new_field = format!("pub(crate) {}", new_field);
|
||||||
|
}
|
||||||
|
new_field = format!("\n{}{}", indent, new_field);
|
||||||
|
|
||||||
|
let needs_comma = !last_field_syntax.to_string().ends_with(',');
|
||||||
|
if needs_comma {
|
||||||
|
new_field = format!(",{}", new_field);
|
||||||
|
}
|
||||||
|
|
||||||
|
let source_change = SourceChange::from_text_edit(
|
||||||
|
def_file_id,
|
||||||
|
TextEdit::insert(last_field_syntax.text_range().end(), new_field),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Some(fix(
|
||||||
|
"create_field",
|
||||||
|
"Create field",
|
||||||
|
source_change,
|
||||||
|
record_expr_field.syntax().text_range(),
|
||||||
|
));
|
||||||
|
|
||||||
|
fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
|
||||||
|
match field_def_list {
|
||||||
|
ast::FieldList::RecordFieldList(it) => Some(it),
|
||||||
|
ast::FieldList::TupleFieldList(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::diagnostics::tests::check_fix;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_field_from_usage() {
|
||||||
|
check_fix(
|
||||||
|
r"
|
||||||
|
fn main() {
|
||||||
|
Foo { bar: 3, baz$0: false};
|
||||||
|
}
|
||||||
|
struct Foo {
|
||||||
|
bar: i32
|
||||||
|
}
|
||||||
|
",
|
||||||
|
r"
|
||||||
|
fn main() {
|
||||||
|
Foo { bar: 3, baz: false};
|
||||||
|
}
|
||||||
|
struct Foo {
|
||||||
|
bar: i32,
|
||||||
|
baz: bool
|
||||||
|
}
|
||||||
|
",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_field_in_other_file_from_usage() {
|
||||||
|
check_fix(
|
||||||
|
r#"
|
||||||
|
//- /main.rs
|
||||||
|
mod foo;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
foo::Foo { bar: 3, $0baz: false};
|
||||||
|
}
|
||||||
|
//- /foo.rs
|
||||||
|
struct Foo {
|
||||||
|
bar: i32
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
struct Foo {
|
||||||
|
bar: i32,
|
||||||
|
pub(crate) baz: bool
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
31
crates/ide/src/diagnostics/fixes/remove_semicolon.rs
Normal file
31
crates/ide/src/diagnostics/fixes/remove_semicolon.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use hir::{db::AstDatabase, diagnostics::RemoveThisSemicolon, Semantics};
|
||||||
|
use ide_assists::{Assist, AssistResolveStrategy};
|
||||||
|
use ide_db::{source_change::SourceChange, RootDatabase};
|
||||||
|
use syntax::{ast, AstNode};
|
||||||
|
use text_edit::TextEdit;
|
||||||
|
|
||||||
|
use crate::diagnostics::{fix, DiagnosticWithFix};
|
||||||
|
|
||||||
|
impl DiagnosticWithFix for RemoveThisSemicolon {
|
||||||
|
fn fix(
|
||||||
|
&self,
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
_resolve: &AssistResolveStrategy,
|
||||||
|
) -> Option<Assist> {
|
||||||
|
let root = sema.db.parse_or_expand(self.file)?;
|
||||||
|
|
||||||
|
let semicolon = self
|
||||||
|
.expr
|
||||||
|
.to_node(&root)
|
||||||
|
.syntax()
|
||||||
|
.parent()
|
||||||
|
.and_then(ast::ExprStmt::cast)
|
||||||
|
.and_then(|expr| expr.semicolon_token())?
|
||||||
|
.text_range();
|
||||||
|
|
||||||
|
let edit = TextEdit::delete(semicolon);
|
||||||
|
let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
|
||||||
|
|
||||||
|
Some(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon))
|
||||||
|
}
|
||||||
|
}
|
42
crates/ide/src/diagnostics/fixes/replace_with_find_map.rs
Normal file
42
crates/ide/src/diagnostics/fixes/replace_with_find_map.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use hir::{db::AstDatabase, diagnostics::ReplaceFilterMapNextWithFindMap, Semantics};
|
||||||
|
use ide_assists::{Assist, AssistResolveStrategy};
|
||||||
|
use ide_db::{source_change::SourceChange, RootDatabase};
|
||||||
|
use syntax::{
|
||||||
|
ast::{self, ArgListOwner},
|
||||||
|
AstNode, TextRange,
|
||||||
|
};
|
||||||
|
use text_edit::TextEdit;
|
||||||
|
|
||||||
|
use crate::diagnostics::{fix, DiagnosticWithFix};
|
||||||
|
|
||||||
|
impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
|
||||||
|
fn fix(
|
||||||
|
&self,
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
_resolve: &AssistResolveStrategy,
|
||||||
|
) -> Option<Assist> {
|
||||||
|
let root = sema.db.parse_or_expand(self.file)?;
|
||||||
|
let next_expr = self.next_expr.to_node(&root);
|
||||||
|
let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
|
||||||
|
|
||||||
|
let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?;
|
||||||
|
let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range();
|
||||||
|
let filter_map_args = filter_map_call.arg_list()?;
|
||||||
|
|
||||||
|
let range_to_replace =
|
||||||
|
TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end());
|
||||||
|
let replacement = format!("find_map{}", filter_map_args.syntax().text());
|
||||||
|
let trigger_range = next_expr.syntax().text_range();
|
||||||
|
|
||||||
|
let edit = TextEdit::replace(range_to_replace, replacement);
|
||||||
|
|
||||||
|
let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
|
||||||
|
|
||||||
|
Some(fix(
|
||||||
|
"replace_with_find_map",
|
||||||
|
"Replace filter_map(..).next() with find_map()",
|
||||||
|
source_change,
|
||||||
|
trigger_range,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
87
crates/ide/src/diagnostics/fixes/unresolved_module.rs
Normal file
87
crates/ide/src/diagnostics/fixes/unresolved_module.rs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
use hir::{db::AstDatabase, diagnostics::UnresolvedModule, Semantics};
|
||||||
|
use ide_assists::{Assist, AssistResolveStrategy};
|
||||||
|
use ide_db::{base_db::AnchoredPathBuf, source_change::FileSystemEdit, RootDatabase};
|
||||||
|
use syntax::AstNode;
|
||||||
|
|
||||||
|
use crate::diagnostics::{fix, DiagnosticWithFix};
|
||||||
|
|
||||||
|
impl DiagnosticWithFix for UnresolvedModule {
|
||||||
|
fn fix(
|
||||||
|
&self,
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
_resolve: &AssistResolveStrategy,
|
||||||
|
) -> Option<Assist> {
|
||||||
|
let root = sema.db.parse_or_expand(self.file)?;
|
||||||
|
let unresolved_module = self.decl.to_node(&root);
|
||||||
|
Some(fix(
|
||||||
|
"create_module",
|
||||||
|
"Create module",
|
||||||
|
FileSystemEdit::CreateFile {
|
||||||
|
dst: AnchoredPathBuf {
|
||||||
|
anchor: self.file.original_file(sema.db),
|
||||||
|
path: self.candidate.clone(),
|
||||||
|
},
|
||||||
|
initial_contents: "".to_string(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
unresolved_module.syntax().text_range(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use expect_test::expect;
|
||||||
|
|
||||||
|
use crate::diagnostics::tests::check_expect;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unresolved_module_diagnostic() {
|
||||||
|
check_expect(
|
||||||
|
r#"mod foo;"#,
|
||||||
|
expect![[r#"
|
||||||
|
[
|
||||||
|
Diagnostic {
|
||||||
|
message: "unresolved module",
|
||||||
|
range: 0..8,
|
||||||
|
severity: Error,
|
||||||
|
fix: Some(
|
||||||
|
Assist {
|
||||||
|
id: AssistId(
|
||||||
|
"create_module",
|
||||||
|
QuickFix,
|
||||||
|
),
|
||||||
|
label: "Create module",
|
||||||
|
group: None,
|
||||||
|
target: 0..8,
|
||||||
|
source_change: Some(
|
||||||
|
SourceChange {
|
||||||
|
source_file_edits: {},
|
||||||
|
file_system_edits: [
|
||||||
|
CreateFile {
|
||||||
|
dst: AnchoredPathBuf {
|
||||||
|
anchor: FileId(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
path: "foo.rs",
|
||||||
|
},
|
||||||
|
initial_contents: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
is_snippet: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
unused: false,
|
||||||
|
code: Some(
|
||||||
|
DiagnosticCode(
|
||||||
|
"unresolved-module",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
211
crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs
Normal file
211
crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
use hir::{db::AstDatabase, diagnostics::MissingOkOrSomeInTailExpr, Semantics};
|
||||||
|
use ide_assists::{Assist, AssistResolveStrategy};
|
||||||
|
use ide_db::{source_change::SourceChange, RootDatabase};
|
||||||
|
use syntax::AstNode;
|
||||||
|
use text_edit::TextEdit;
|
||||||
|
|
||||||
|
use crate::diagnostics::{fix, DiagnosticWithFix};
|
||||||
|
|
||||||
|
impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
|
||||||
|
fn fix(
|
||||||
|
&self,
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
_resolve: &AssistResolveStrategy,
|
||||||
|
) -> Option<Assist> {
|
||||||
|
let root = sema.db.parse_or_expand(self.file)?;
|
||||||
|
let tail_expr = self.expr.to_node(&root);
|
||||||
|
let tail_expr_range = tail_expr.syntax().text_range();
|
||||||
|
let replacement = format!("{}({})", self.required, tail_expr.syntax());
|
||||||
|
let edit = TextEdit::replace(tail_expr_range, replacement);
|
||||||
|
let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
|
||||||
|
let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" };
|
||||||
|
Some(fix("wrap_tail_expr", name, source_change, tail_expr_range))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::diagnostics::tests::{check_fix, check_no_diagnostics};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrap_return_type_option() {
|
||||||
|
check_fix(
|
||||||
|
r#"
|
||||||
|
//- /main.rs crate:main deps:core
|
||||||
|
use core::option::Option::{self, Some, None};
|
||||||
|
|
||||||
|
fn div(x: i32, y: i32) -> Option<i32> {
|
||||||
|
if y == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
x / y$0
|
||||||
|
}
|
||||||
|
//- /core/lib.rs crate:core
|
||||||
|
pub mod result {
|
||||||
|
pub enum Result<T, E> { Ok(T), Err(E) }
|
||||||
|
}
|
||||||
|
pub mod option {
|
||||||
|
pub enum Option<T> { Some(T), None }
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
use core::option::Option::{self, Some, None};
|
||||||
|
|
||||||
|
fn div(x: i32, y: i32) -> Option<i32> {
|
||||||
|
if y == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(x / y)
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrap_return_type() {
|
||||||
|
check_fix(
|
||||||
|
r#"
|
||||||
|
//- /main.rs crate:main deps:core
|
||||||
|
use core::result::Result::{self, Ok, Err};
|
||||||
|
|
||||||
|
fn div(x: i32, y: i32) -> Result<i32, ()> {
|
||||||
|
if y == 0 {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
x / y$0
|
||||||
|
}
|
||||||
|
//- /core/lib.rs crate:core
|
||||||
|
pub mod result {
|
||||||
|
pub enum Result<T, E> { Ok(T), Err(E) }
|
||||||
|
}
|
||||||
|
pub mod option {
|
||||||
|
pub enum Option<T> { Some(T), None }
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
use core::result::Result::{self, Ok, Err};
|
||||||
|
|
||||||
|
fn div(x: i32, y: i32) -> Result<i32, ()> {
|
||||||
|
if y == 0 {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
Ok(x / y)
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrap_return_type_handles_generic_functions() {
|
||||||
|
check_fix(
|
||||||
|
r#"
|
||||||
|
//- /main.rs crate:main deps:core
|
||||||
|
use core::result::Result::{self, Ok, Err};
|
||||||
|
|
||||||
|
fn div<T>(x: T) -> Result<T, i32> {
|
||||||
|
if x == 0 {
|
||||||
|
return Err(7);
|
||||||
|
}
|
||||||
|
$0x
|
||||||
|
}
|
||||||
|
//- /core/lib.rs crate:core
|
||||||
|
pub mod result {
|
||||||
|
pub enum Result<T, E> { Ok(T), Err(E) }
|
||||||
|
}
|
||||||
|
pub mod option {
|
||||||
|
pub enum Option<T> { Some(T), None }
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
use core::result::Result::{self, Ok, Err};
|
||||||
|
|
||||||
|
fn div<T>(x: T) -> Result<T, i32> {
|
||||||
|
if x == 0 {
|
||||||
|
return Err(7);
|
||||||
|
}
|
||||||
|
Ok(x)
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrap_return_type_handles_type_aliases() {
|
||||||
|
check_fix(
|
||||||
|
r#"
|
||||||
|
//- /main.rs crate:main deps:core
|
||||||
|
use core::result::Result::{self, Ok, Err};
|
||||||
|
|
||||||
|
type MyResult<T> = Result<T, ()>;
|
||||||
|
|
||||||
|
fn div(x: i32, y: i32) -> MyResult<i32> {
|
||||||
|
if y == 0 {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
x $0/ y
|
||||||
|
}
|
||||||
|
//- /core/lib.rs crate:core
|
||||||
|
pub mod result {
|
||||||
|
pub enum Result<T, E> { Ok(T), Err(E) }
|
||||||
|
}
|
||||||
|
pub mod option {
|
||||||
|
pub enum Option<T> { Some(T), None }
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
use core::result::Result::{self, Ok, Err};
|
||||||
|
|
||||||
|
type MyResult<T> = Result<T, ()>;
|
||||||
|
|
||||||
|
fn div(x: i32, y: i32) -> MyResult<i32> {
|
||||||
|
if y == 0 {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
Ok(x / y)
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
|
||||||
|
check_no_diagnostics(
|
||||||
|
r#"
|
||||||
|
//- /main.rs crate:main deps:core
|
||||||
|
use core::result::Result::{self, Ok, Err};
|
||||||
|
|
||||||
|
fn foo() -> Result<(), i32> { 0 }
|
||||||
|
|
||||||
|
//- /core/lib.rs crate:core
|
||||||
|
pub mod result {
|
||||||
|
pub enum Result<T, E> { Ok(T), Err(E) }
|
||||||
|
}
|
||||||
|
pub mod option {
|
||||||
|
pub enum Option<T> { Some(T), None }
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
|
||||||
|
check_no_diagnostics(
|
||||||
|
r#"
|
||||||
|
//- /main.rs crate:main deps:core
|
||||||
|
use core::result::Result::{self, Ok, Err};
|
||||||
|
|
||||||
|
enum SomeOtherEnum { Ok(i32), Err(String) }
|
||||||
|
|
||||||
|
fn foo() -> SomeOtherEnum { 0 }
|
||||||
|
|
||||||
|
//- /core/lib.rs crate:core
|
||||||
|
pub mod result {
|
||||||
|
pub enum Result<T, E> { Ok(T), Err(E) }
|
||||||
|
}
|
||||||
|
pub mod option {
|
||||||
|
pub enum Option<T> { Some(T), None }
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -347,9 +347,8 @@ struct TidyDocs {
|
||||||
|
|
||||||
impl TidyDocs {
|
impl TidyDocs {
|
||||||
fn visit(&mut self, path: &Path, text: &str) {
|
fn visit(&mut self, path: &Path, text: &str) {
|
||||||
// Test hopefully don't really need comments, and for assists we already
|
// Tests and diagnostic fixes don't need module level comments.
|
||||||
// have special comments which are source of doc tests and user docs.
|
if is_exclude_dir(path, &["tests", "test_data", "fixes"]) {
|
||||||
if is_exclude_dir(path, &["tests", "test_data"]) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue