Auto merge of #11971 - jonas-schievink:on-type-fmt-assignments, r=jonas-schievink
feat: Add trailing `;` when typing `=` in assignment ![Peek 2022-04-12 19-41](https://user-images.githubusercontent.com/1786438/163022079-1ed114ef-7c75-490f-a8ed-731a13f0b44d.gif) This does have a false positive to keep in mind, it will add a trailing `;` in the following snippet too, which is probably not desired: ```rust fn is_zero(i: i32) -> bool { i $0 0 } ``` However, that function is unlikely to be written from the "inside out" like that, so it might be acceptable. Typically `=` is only inserted last when the author realizes that an existing expression should be assigned to some variable.
This commit is contained in:
commit
63573d47aa
@ -39,8 +39,11 @@
|
||||
// Some features trigger on typing certain characters:
|
||||
//
|
||||
// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
|
||||
// - typing `=` between two expressions adds `;` when in statement position
|
||||
// - typing `=` to turn an assignment into an equality comparison removes `;` when in expression position
|
||||
// - typing `.` in a chain method call auto-indents
|
||||
// - typing `{` in front of an expression inserts a closing `}` after the expression
|
||||
// - typing `{` in a use item adds a closing `}` in the right place
|
||||
//
|
||||
// VS Code::
|
||||
//
|
||||
@ -166,11 +169,37 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
|
||||
if !stdx::always!(file.syntax().text().char_at(offset) == Some('=')) {
|
||||
return None;
|
||||
}
|
||||
let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
|
||||
if let_stmt.semicolon_token().is_some() {
|
||||
return None;
|
||||
|
||||
if let Some(edit) = let_stmt(file, offset) {
|
||||
return Some(edit);
|
||||
}
|
||||
if let Some(expr) = let_stmt.initializer() {
|
||||
if let Some(edit) = assign_expr(file, offset) {
|
||||
return Some(edit);
|
||||
}
|
||||
if let Some(edit) = assign_to_eq(file, offset) {
|
||||
return Some(edit);
|
||||
}
|
||||
|
||||
return None;
|
||||
|
||||
fn assign_expr(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
|
||||
let binop: ast::BinExpr = find_node_at_offset(file.syntax(), offset)?;
|
||||
if !matches!(binop.op_kind(), Some(ast::BinaryOp::Assignment { op: None })) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Parent must be `ExprStmt` or `StmtList` for `;` to be valid.
|
||||
if let Some(expr_stmt) = ast::ExprStmt::cast(binop.syntax().parent()?) {
|
||||
if expr_stmt.semicolon_token().is_some() {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
if !ast::StmtList::can_cast(binop.syntax().parent()?.kind()) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let expr = binop.rhs()?;
|
||||
let expr_range = expr.syntax().text_range();
|
||||
if expr_range.contains(offset) && offset != expr_range.start() {
|
||||
return None;
|
||||
@ -178,11 +207,45 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
|
||||
if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
let offset = expr.syntax().text_range().end();
|
||||
Some(TextEdit::insert(offset, ";".to_string()))
|
||||
}
|
||||
|
||||
/// `a =$0 b;` removes the semicolon if an expression is valid in this context.
|
||||
fn assign_to_eq(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
|
||||
let binop: ast::BinExpr = find_node_at_offset(file.syntax(), offset)?;
|
||||
if !matches!(binop.op_kind(), Some(ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated: false })))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let expr_stmt = ast::ExprStmt::cast(binop.syntax().parent()?)?;
|
||||
let semi = expr_stmt.semicolon_token()?;
|
||||
|
||||
if expr_stmt.syntax().next_sibling().is_some() {
|
||||
// Not the last statement in the list.
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(TextEdit::delete(semi.text_range()))
|
||||
}
|
||||
|
||||
fn let_stmt(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
|
||||
let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
|
||||
if let_stmt.semicolon_token().is_some() {
|
||||
return None;
|
||||
}
|
||||
let expr = let_stmt.initializer()?;
|
||||
let expr_range = expr.syntax().text_range();
|
||||
if expr_range.contains(offset) && offset != expr_range.start() {
|
||||
return None;
|
||||
}
|
||||
if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') {
|
||||
return None;
|
||||
}
|
||||
let offset = let_stmt.syntax().text_range().end();
|
||||
Some(TextEdit::insert(offset, ";".to_string()))
|
||||
}
|
||||
let offset = let_stmt.syntax().text_range().end();
|
||||
Some(TextEdit::insert(offset, ";".to_string()))
|
||||
}
|
||||
|
||||
/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
|
||||
@ -286,7 +349,7 @@ fn type_char_noop(char_typed: char, ra_fixture_before: &str) {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_on_eq_typed() {
|
||||
fn test_semi_after_let() {
|
||||
// do_check(r"
|
||||
// fn foo() {
|
||||
// let foo =$0
|
||||
@ -322,6 +385,117 @@ fn foo() {
|
||||
// ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_semi_after_assign() {
|
||||
type_char(
|
||||
'=',
|
||||
r#"
|
||||
fn f() {
|
||||
i $0 0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn f() {
|
||||
i = 0;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
type_char(
|
||||
'=',
|
||||
r#"
|
||||
fn f() {
|
||||
i $0 0
|
||||
i
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn f() {
|
||||
i = 0;
|
||||
i
|
||||
}
|
||||
"#,
|
||||
);
|
||||
type_char_noop(
|
||||
'=',
|
||||
r#"
|
||||
fn f(x: u8) {
|
||||
if x $0
|
||||
}
|
||||
"#,
|
||||
);
|
||||
type_char_noop(
|
||||
'=',
|
||||
r#"
|
||||
fn f(x: u8) {
|
||||
if x $0 {}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
type_char_noop(
|
||||
'=',
|
||||
r#"
|
||||
fn f(x: u8) {
|
||||
if x $0 0 {}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
type_char_noop(
|
||||
'=',
|
||||
r#"
|
||||
fn f() {
|
||||
g(i $0 0);
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assign_to_eq() {
|
||||
type_char(
|
||||
'=',
|
||||
r#"
|
||||
fn f(a: u8) {
|
||||
a =$0 0;
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn f(a: u8) {
|
||||
a == 0
|
||||
}
|
||||
"#,
|
||||
);
|
||||
type_char(
|
||||
'=',
|
||||
r#"
|
||||
fn f(a: u8) {
|
||||
a $0= 0;
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn f(a: u8) {
|
||||
a == 0
|
||||
}
|
||||
"#,
|
||||
);
|
||||
type_char_noop(
|
||||
'=',
|
||||
r#"
|
||||
fn f(a: u8) {
|
||||
let e = a =$0 0;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
type_char_noop(
|
||||
'=',
|
||||
r#"
|
||||
fn f(a: u8) {
|
||||
let e = a =$0 0;
|
||||
e
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indents_new_chain_call() {
|
||||
type_char(
|
||||
|
Loading…
Reference in New Issue
Block a user