Skip to content

Commit 7fadc78

Browse files
bors[bot]matklad
andauthored
Merge #6242
6242: Diagnost shorthand in patterns r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
2 parents 8090799 + 3086fb2 commit 7fadc78

File tree

2 files changed

+210
-91
lines changed

2 files changed

+210
-91
lines changed

crates/ide/src/diagnostics.rs

Lines changed: 4 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! original files. So we need to map the ranges.
66
77
mod fixes;
8+
mod field_shorthand;
89

910
use std::cell::RefCell;
1011

@@ -80,7 +81,7 @@ pub(crate) fn diagnostics(
8081

8182
for node in parse.tree().syntax().descendants() {
8283
check_unnecessary_braces_in_use_statement(&mut res, file_id, &node);
83-
check_struct_shorthand_initialization(&mut res, file_id, &node);
84+
field_shorthand::check(&mut res, file_id, &node);
8485
}
8586
let res = RefCell::new(res);
8687
let sink_builder = DiagnosticSinkBuilder::new()
@@ -188,42 +189,6 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
188189
None
189190
}
190191

191-
fn check_struct_shorthand_initialization(
192-
acc: &mut Vec<Diagnostic>,
193-
file_id: FileId,
194-
node: &SyntaxNode,
195-
) -> Option<()> {
196-
let record_lit = ast::RecordExpr::cast(node.clone())?;
197-
let record_field_list = record_lit.record_expr_field_list()?;
198-
for record_field in record_field_list.fields() {
199-
if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) {
200-
let field_name = name_ref.syntax().text().to_string();
201-
let field_expr = expr.syntax().text().to_string();
202-
let field_name_is_tup_index = name_ref.as_tuple_field().is_some();
203-
if field_name == field_expr && !field_name_is_tup_index {
204-
let mut edit_builder = TextEdit::builder();
205-
edit_builder.delete(record_field.syntax().text_range());
206-
edit_builder.insert(record_field.syntax().text_range().start(), field_name);
207-
let edit = edit_builder.finish();
208-
209-
let field_range = record_field.syntax().text_range();
210-
acc.push(Diagnostic {
211-
// name: None,
212-
range: field_range,
213-
message: "Shorthand struct initialization".to_string(),
214-
severity: Severity::WeakWarning,
215-
fix: Some(Fix::new(
216-
"Use struct shorthand initialization",
217-
SourceFileEdit { file_id, edit }.into(),
218-
field_range,
219-
)),
220-
});
221-
}
222-
}
223-
}
224-
Some(())
225-
}
226-
227192
#[cfg(test)]
228193
mod tests {
229194
use expect_test::{expect, Expect};
@@ -237,7 +202,7 @@ mod tests {
237202
/// * a diagnostic is produced
238203
/// * this diagnostic fix trigger range touches the input cursor position
239204
/// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
240-
fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
205+
pub(super) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
241206
let after = trim_indent(ra_fixture_after);
242207

243208
let (analysis, file_position) = fixture::position(ra_fixture_before);
@@ -319,7 +284,7 @@ mod tests {
319284

320285
/// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics
321286
/// apply to the file containing the cursor.
322-
fn check_no_diagnostics(ra_fixture: &str) {
287+
pub(crate) fn check_no_diagnostics(ra_fixture: &str) {
323288
let (analysis, files) = fixture::files(ra_fixture);
324289
let diagnostics = files
325290
.into_iter()
@@ -719,58 +684,6 @@ mod a {
719684
);
720685
}
721686

722-
#[test]
723-
fn test_check_struct_shorthand_initialization() {
724-
check_no_diagnostics(
725-
r#"
726-
struct A { a: &'static str }
727-
fn main() { A { a: "hello" } }
728-
"#,
729-
);
730-
check_no_diagnostics(
731-
r#"
732-
struct A(usize);
733-
fn main() { A { 0: 0 } }
734-
"#,
735-
);
736-
737-
check_fix(
738-
r#"
739-
struct A { a: &'static str }
740-
fn main() {
741-
let a = "haha";
742-
A { a<|>: a }
743-
}
744-
"#,
745-
r#"
746-
struct A { a: &'static str }
747-
fn main() {
748-
let a = "haha";
749-
A { a }
750-
}
751-
"#,
752-
);
753-
754-
check_fix(
755-
r#"
756-
struct A { a: &'static str, b: &'static str }
757-
fn main() {
758-
let a = "haha";
759-
let b = "bb";
760-
A { a<|>: a, b }
761-
}
762-
"#,
763-
r#"
764-
struct A { a: &'static str, b: &'static str }
765-
fn main() {
766-
let a = "haha";
767-
let b = "bb";
768-
A { a, b }
769-
}
770-
"#,
771-
);
772-
}
773-
774687
#[test]
775688
fn test_add_field_from_usage() {
776689
check_fix(
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
//! Suggests shortening `Foo { field: field }` to `Foo { field }` in both
2+
//! expressions and patterns.
3+
4+
use base_db::FileId;
5+
use ide_db::source_change::SourceFileEdit;
6+
use syntax::{ast, match_ast, AstNode, SyntaxNode};
7+
use text_edit::TextEdit;
8+
9+
use crate::{Diagnostic, Fix, Severity};
10+
11+
pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) {
12+
match_ast! {
13+
match node {
14+
ast::RecordExpr(it) => check_expr_field_shorthand(acc, file_id, it),
15+
ast::RecordPat(it) => check_pat_field_shorthand(acc, file_id, it),
16+
_ => ()
17+
}
18+
};
19+
}
20+
21+
fn check_expr_field_shorthand(
22+
acc: &mut Vec<Diagnostic>,
23+
file_id: FileId,
24+
record_expr: ast::RecordExpr,
25+
) {
26+
let record_field_list = match record_expr.record_expr_field_list() {
27+
Some(it) => it,
28+
None => return,
29+
};
30+
for record_field in record_field_list.fields() {
31+
let (name_ref, expr) = match record_field.name_ref().zip(record_field.expr()) {
32+
Some(it) => it,
33+
None => continue,
34+
};
35+
36+
let field_name = name_ref.syntax().text().to_string();
37+
let field_expr = expr.syntax().text().to_string();
38+
let field_name_is_tup_index = name_ref.as_tuple_field().is_some();
39+
if field_name != field_expr || field_name_is_tup_index {
40+
continue;
41+
}
42+
43+
let mut edit_builder = TextEdit::builder();
44+
edit_builder.delete(record_field.syntax().text_range());
45+
edit_builder.insert(record_field.syntax().text_range().start(), field_name);
46+
let edit = edit_builder.finish();
47+
48+
let field_range = record_field.syntax().text_range();
49+
acc.push(Diagnostic {
50+
// name: None,
51+
range: field_range,
52+
message: "Shorthand struct initialization".to_string(),
53+
severity: Severity::WeakWarning,
54+
fix: Some(Fix::new(
55+
"Use struct shorthand initialization",
56+
SourceFileEdit { file_id, edit }.into(),
57+
field_range,
58+
)),
59+
});
60+
}
61+
}
62+
63+
fn check_pat_field_shorthand(
64+
acc: &mut Vec<Diagnostic>,
65+
file_id: FileId,
66+
record_pat: ast::RecordPat,
67+
) {
68+
let record_pat_field_list = match record_pat.record_pat_field_list() {
69+
Some(it) => it,
70+
None => return,
71+
};
72+
for record_pat_field in record_pat_field_list.fields() {
73+
let (name_ref, pat) = match record_pat_field.name_ref().zip(record_pat_field.pat()) {
74+
Some(it) => it,
75+
None => continue,
76+
};
77+
78+
let field_name = name_ref.syntax().text().to_string();
79+
let field_pat = pat.syntax().text().to_string();
80+
let field_name_is_tup_index = name_ref.as_tuple_field().is_some();
81+
if field_name != field_pat || field_name_is_tup_index {
82+
continue;
83+
}
84+
85+
let mut edit_builder = TextEdit::builder();
86+
edit_builder.delete(record_pat_field.syntax().text_range());
87+
edit_builder.insert(record_pat_field.syntax().text_range().start(), field_name);
88+
let edit = edit_builder.finish();
89+
90+
let field_range = record_pat_field.syntax().text_range();
91+
acc.push(Diagnostic {
92+
// name: None,
93+
range: field_range,
94+
message: "Shorthand struct pattern".to_string(),
95+
severity: Severity::WeakWarning,
96+
fix: Some(Fix::new(
97+
"Use struct field shorthand",
98+
SourceFileEdit { file_id, edit }.into(),
99+
field_range,
100+
)),
101+
});
102+
}
103+
}
104+
105+
#[cfg(test)]
106+
mod tests {
107+
use crate::diagnostics::tests::{check_fix, check_no_diagnostics};
108+
109+
#[test]
110+
fn test_check_expr_field_shorthand() {
111+
check_no_diagnostics(
112+
r#"
113+
struct A { a: &'static str }
114+
fn main() { A { a: "hello" } }
115+
"#,
116+
);
117+
check_no_diagnostics(
118+
r#"
119+
struct A(usize);
120+
fn main() { A { 0: 0 } }
121+
"#,
122+
);
123+
124+
check_fix(
125+
r#"
126+
struct A { a: &'static str }
127+
fn main() {
128+
let a = "haha";
129+
A { a<|>: a }
130+
}
131+
"#,
132+
r#"
133+
struct A { a: &'static str }
134+
fn main() {
135+
let a = "haha";
136+
A { a }
137+
}
138+
"#,
139+
);
140+
141+
check_fix(
142+
r#"
143+
struct A { a: &'static str, b: &'static str }
144+
fn main() {
145+
let a = "haha";
146+
let b = "bb";
147+
A { a<|>: a, b }
148+
}
149+
"#,
150+
r#"
151+
struct A { a: &'static str, b: &'static str }
152+
fn main() {
153+
let a = "haha";
154+
let b = "bb";
155+
A { a, b }
156+
}
157+
"#,
158+
);
159+
}
160+
161+
#[test]
162+
fn test_check_pat_field_shorthand() {
163+
check_no_diagnostics(
164+
r#"
165+
struct A { a: &'static str }
166+
fn f(a: A) { let A { a: hello } = a; }
167+
"#,
168+
);
169+
check_no_diagnostics(
170+
r#"
171+
struct A(usize);
172+
fn f(a: A) { let A { 0: 0 } = a; }
173+
"#,
174+
);
175+
176+
check_fix(
177+
r#"
178+
struct A { a: &'static str }
179+
fn f(a: A) {
180+
let A { a<|>: a } = a;
181+
}
182+
"#,
183+
r#"
184+
struct A { a: &'static str }
185+
fn f(a: A) {
186+
let A { a } = a;
187+
}
188+
"#,
189+
);
190+
191+
check_fix(
192+
r#"
193+
struct A { a: &'static str, b: &'static str }
194+
fn f(a: A) {
195+
let A { a<|>: a, b } = a;
196+
}
197+
"#,
198+
r#"
199+
struct A { a: &'static str, b: &'static str }
200+
fn f(a: A) {
201+
let A { a, b } = a;
202+
}
203+
"#,
204+
);
205+
}
206+
}

0 commit comments

Comments
 (0)