Unnamed repository; edit this file 'description' to name the repository.
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | crates/ide-assists/src/handlers/inline_macro.rs | 29 | ||||
| -rw-r--r-- | crates/ide-completion/src/completions/item_list/trait_impl.rs | 2 | ||||
| -rw-r--r-- | crates/ide/src/expand_macro.rs | 17 | ||||
| -rw-r--r-- | crates/mbe/src/tests.rs | 2 | ||||
| -rw-r--r-- | crates/syntax-bridge/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/syntax-bridge/src/prettify_macro_expansion.rs | 359 | ||||
| -rw-r--r-- | crates/syntax/src/ast/edit.rs | 4 |
8 files changed, 354 insertions, 61 deletions
diff --git a/Cargo.lock b/Cargo.lock index 0b3fd2b8ea..604bc345c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2745,6 +2745,7 @@ dependencies = [ name = "syntax-bridge" version = "0.0.0" dependencies = [ + "expect-test", "intern", "parser", "rustc-hash 2.1.1", diff --git a/crates/ide-assists/src/handlers/inline_macro.rs b/crates/ide-assists/src/handlers/inline_macro.rs index 280bd7f2ca..ef9b5d093f 100644 --- a/crates/ide-assists/src/handlers/inline_macro.rs +++ b/crates/ide-assists/src/handlers/inline_macro.rs @@ -1,6 +1,6 @@ use hir::db::ExpandDatabase; use ide_db::syntax_helpers::prettify_macro_expansion; -use syntax::ast::{self, AstNode}; +use syntax::ast::{self, AstNode, edit::AstNodeEdit}; use crate::{AssistContext, AssistId, Assists}; @@ -46,12 +46,15 @@ pub(crate) fn inline_macro(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option "Inline macro".to_owned(), text_range, |builder| { + let editor = builder.make_editor(unexpanded.syntax()); let expanded = ctx.sema.parse_or_expand(macro_call.into()); let span_map = ctx.sema.db.expansion_span_map(macro_call); // Don't call `prettify_macro_expansion()` outside the actual assist action; it does some heavy rowan tree manipulation, // which can be very costly for big macros when it is done *even without the assist being invoked*. let expanded = prettify_macro_expansion(ctx.db(), expanded, &span_map, target_crate_id); - builder.replace(text_range, expanded.to_string()) + let expanded = ast::edit::indent(&expanded, unexpanded.indent_level()); + editor.replace(unexpanded.syntax(), expanded); + builder.add_file_edits(ctx.vfs_file_id(), editor); }, ) } @@ -207,15 +210,15 @@ macro_rules! num { inline_macro, r#" macro_rules! foo { - () => {foo!()} + ($t:tt) => {foo!(1)} } -fn f() { let result = foo$0!(); } +fn f() { let result = foo$0!(0); } "#, r#" macro_rules! foo { - () => {foo!()} + ($t:tt) => {foo!(1)} } -fn f() { let result = foo!(); } +fn f() { let result = foo!(1); } "#, ); } @@ -258,7 +261,7 @@ macro_rules! whitespace { if true {} }; } -fn f() { if true{}; } +fn f() { if true {}; } "#, ) } @@ -297,12 +300,12 @@ macro_rules! foo { } fn main() { cfg_if!{ - if #[cfg(test)]{ - 1; - }else { - 1; - } -}; + if #[cfg(test)]{ + 1; + }else { + 1; + } + }; } "#, ); diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs index 4072f05a41..9bac34885c 100644 --- a/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -1356,7 +1356,7 @@ noop! { struct Test; impl Foo for Test { - fn foo(&mut self,bar:i64,baz: &mut u32) -> Result<(),u32> { + fn foo(&mut self,bar: i64,baz: &mut u32) -> Result<(),u32> { $0 } } diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs index 6ac4fa1fba..cc322d2b9e 100644 --- a/crates/ide/src/expand_macro.rs +++ b/crates/ide/src/expand_macro.rs @@ -357,7 +357,7 @@ fn main() { "#, expect![[r#" bar! - for _ in 0..42{}"#]], + for _ in 0..42 {}"#]], ); } @@ -433,9 +433,9 @@ fn main() { expect![[r#" match_ast! { - if let Some(it) = ast::TraitDef::cast(container.clone()){} - else if let Some(it) = ast::ImplDef::cast(container.clone()){} - else { + if let Some(it) = ast::TraitDef::cast(container.clone()){ + }else if let Some(it) = ast::ImplDef::cast(container.clone()){ + }else { { continue } @@ -594,7 +594,7 @@ struct Foo {} "#, expect![[r#" proc_macros::DeriveIdentity - struct Foo{}"#]], + struct Foo {}"#]], ); } @@ -612,7 +612,7 @@ struct Foo {} expect![[r#" proc_macros::DeriveIdentity #[derive(proc_macros::DeriveIdentity)] - struct Foo{}"#]], + struct Foo {}"#]], ); } @@ -628,7 +628,7 @@ struct Foo {} "#, expect![[r#" proc_macros::DeriveIdentity - struct Foo{}"#]], + struct Foo {}"#]], ); check( r#" @@ -640,7 +640,7 @@ struct Foo {} "#, expect![[r#" proc_macros::DeriveIdentity - struct Foo{}"#]], + struct Foo {}"#]], ); } @@ -783,7 +783,6 @@ foo(); macro_rules! foo { () => { fn item(){} - }; } foo();"#]], diff --git a/crates/mbe/src/tests.rs b/crates/mbe/src/tests.rs index 4a1af31656..271dfc877b 100644 --- a/crates/mbe/src/tests.rs +++ b/crates/mbe/src/tests.rs @@ -237,7 +237,7 @@ fn expr_2021() { PUNCH ; [alone] 0:Root[0000, 0]@39..40#ROOT2024 _; - (const { + (const { 1 });"#]], ); diff --git a/crates/syntax-bridge/Cargo.toml b/crates/syntax-bridge/Cargo.toml index b0fd40ff59..c928f23e02 100644 --- a/crates/syntax-bridge/Cargo.toml +++ b/crates/syntax-bridge/Cargo.toml @@ -25,6 +25,7 @@ span = { path = "../span", version = "0.0.0", default-features = false} intern.workspace = true [dev-dependencies] +expect-test = "1.5.1" test-utils.workspace = true [features] diff --git a/crates/syntax-bridge/src/prettify_macro_expansion.rs b/crates/syntax-bridge/src/prettify_macro_expansion.rs index 2f932e0458..678dd143f9 100644 --- a/crates/syntax-bridge/src/prettify_macro_expansion.rs +++ b/crates/syntax-bridge/src/prettify_macro_expansion.rs @@ -3,8 +3,7 @@ use syntax::{ NodeOrToken, SyntaxKind::{self, *}, SyntaxNode, SyntaxToken, T, WalkEvent, - ast::make, - ted::{self, Position}, + syntax_editor::{Position, SyntaxEditor}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -29,7 +28,7 @@ pub fn prettify_macro_expansion( let mut last: Option<SyntaxKind> = None; let mut mods = Vec::new(); let mut dollar_crate_replacements = Vec::new(); - let syn = syn.clone_subtree().clone_for_update(); + let (editor, syn) = SyntaxEditor::new(syn); let before = Position::before; let after = Position::after; @@ -45,17 +44,21 @@ pub fn prettify_macro_expansion( for event in syn.preorder_with_tokens() { let token = match event { WalkEvent::Enter(NodeOrToken::Token(token)) => token, - WalkEvent::Leave(NodeOrToken::Node(node)) - if matches!( - node.kind(), - ATTR | MATCH_ARM | STRUCT | ENUM | UNION | FN | IMPL | MACRO_RULES - ) => - { - if indent > 0 { + WalkEvent::Leave(NodeOrToken::Node(node)) => { + let is_last_child = + node.parent().is_some_and(|parent| parent.last_child().as_ref() == Some(&node)); + let is_always_newline = matches!(node.kind(), ATTR); + let is_non_last_newline = match node.kind() { + MATCH_ARM | STRUCT | ENUM | UNION | FN | IMPL | MACRO_RULES | EXTERN_BLOCK + | EXTERN_CRATE | MODULE => true, + EXPR_STMT if Some(R_CURLY) == node.last_token().map(|it| it.kind()) => true, + _ => false, + }; + if (!is_last_child && is_non_last_newline) || is_always_newline { mods.push((Position::after(node.clone()), PrettifyWsKind::Indent(indent))); - } - if node.parent().is_some() { - mods.push((Position::after(node), PrettifyWsKind::Newline)); + if node.parent().is_some() { + mods.push((Position::after(node), PrettifyWsKind::Newline)); + } } continue; } @@ -77,44 +80,36 @@ pub fn prettify_macro_expansion( match tok.kind() { k if is_text(k) - && is_next(|it| !it.is_punct() || matches!(it, T![_] | T![#]), false) => + && is_next(|it| !it.is_punct() || matches!(it, T![_] | T![#] | L_CURLY), false) => { mods.push(do_ws(after, tok)); } L_CURLY if is_next(|it| it != R_CURLY, true) => { indent += 1; - if is_last(is_text, false) { - mods.push(do_ws(before, tok)); - } - mods.push(do_indent(after, tok, indent)); mods.push(do_nl(after, tok)); } R_CURLY if is_last(|it| it != L_CURLY, true) => { indent = indent.saturating_sub(1); - if indent > 0 { - mods.push(do_indent(before, tok, indent)); - } + mods.push(do_indent(before, tok, indent)); mods.push(do_nl(before, tok)); } - R_CURLY => { - if indent > 0 { - mods.push(do_indent(after, tok, indent)); - } - mods.push(do_nl(after, tok)); + R_CURLY if is_next(|it| it == T![else], false) => { + mods.push(do_indent(before, tok, indent)); + mods.push(do_nl(before, tok)); } LIFETIME_IDENT if is_next(is_text, true) => { mods.push(do_ws(after, tok)); } - AS_KW | DYN_KW | IMPL_KW | CONST_KW | MUT_KW => { + AS_KW | DYN_KW | IMPL_KW | CONST_KW | MUT_KW | LET_KW | MATCH_KW => { mods.push(do_ws(after, tok)); } T![;] if is_next(|it| it != R_CURLY, true) => { - if indent > 0 { - mods.push(do_indent(after, tok, indent)); + mods.push(do_indent(after, tok, indent)); + if tok.text_range().end() != syn.text_range().end() { + mods.push(do_nl(after, tok)); } - mods.push(do_nl(after, tok)); } T![=] if is_next(|it| it == T![>], false) => { // FIXME: this branch is for `=>` in macro_rules!, which is currently parsed as @@ -126,6 +121,12 @@ pub fn prettify_macro_expansion( mods.push(do_ws(before, tok)); mods.push(do_ws(after, tok)); } + T![:] if is_next(|it| it != T![:], false) && is_last(|it| it != T![:], false) => { + // XXX: Why input included WHITESPACE? + if is_next(|it| it != SyntaxKind::WHITESPACE, false) { + mods.push(do_ws(after, tok)); + } + } T![!] if is_last(|it| it == MACRO_RULES_KW, false) && is_next(is_text, false) => { mods.push(do_ws(after, tok)); } @@ -137,27 +138,311 @@ pub fn prettify_macro_expansion( inspect_mods(&mods); for (pos, insert) in mods { - ted::insert_raw( + editor.insert( pos, match insert { - PrettifyWsKind::Space => make::tokens::single_space(), - PrettifyWsKind::Indent(indent) => make::tokens::whitespace(&" ".repeat(4 * indent)), - PrettifyWsKind::Newline => make::tokens::single_newline(), + PrettifyWsKind::Space => editor.make().whitespace(" "), + PrettifyWsKind::Indent(0) => continue, + PrettifyWsKind::Indent(indent) => editor.make().whitespace(&" ".repeat(4 * indent)), + PrettifyWsKind::Newline => editor.make().whitespace("\n"), }, ); } for (old, new) in dollar_crate_replacements { - ted::replace(old, new); + editor.replace(old, new); } if let Some(it) = syn.last_token().filter(|it| it.kind() == SyntaxKind::WHITESPACE) { - ted::remove(it); + editor.delete(it); } - syn + editor.finish().new_root().clone() } fn is_text(k: SyntaxKind) -> bool { // Consider all keywords in all editions. k.is_any_identifier() || k.is_literal() || k == UNDERSCORE } + +#[cfg(test)] +mod tests { + use super::*; + use expect_test::{Expect, expect}; + + #[expect(deprecated)] + fn check_pretty(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) { + let ra_fixture = stdx::trim_indent(ra_fixture); + let source_file = syntax::ast::SourceFile::parse(&ra_fixture, span::Edition::CURRENT); + let syn = remove_whitespaces(&source_file.syntax_node()); + + let pretty = prettify_macro_expansion(syn, &mut |_| None, |_| ()); + let mut pretty = pretty.to_string(); + if pretty.contains('\n') { + pretty.push('\n'); + } + expect.assert_eq(&pretty); + + fn remove_whitespaces(node: &SyntaxNode) -> SyntaxNode { + let (editor, node) = SyntaxEditor::new(node.clone()); + node.preorder_with_tokens().for_each(|it| match it { + WalkEvent::Enter(NodeOrToken::Token(tok)) if tok.kind().is_trivia() => { + editor.delete(tok); + } + _ => (), + }); + editor.finish().new_root().clone() + } + } + + #[test] + fn test_colon() { + check_pretty( + r#" + const X: i32 = x::y::z; + macro_rules! foo { + () => { + $crate::foo::bar!() + }; + } + "#, + expect![[r#" + const X: i32 = x::y::z; + macro_rules! foo { + () => { + $crate::foo::bar!() + }; + } + "#]], + ); + } + + #[test] + fn test_curly_indent() { + check_pretty( + r#" + const _: () = { + { + 2; + 3 + } + }; + "#, + expect![[r#" + const _: () = { + { + 2; + 3 + } + }; + "#]], + ); + } + + #[test] + fn test_pats() { + check_pretty( + r#" + const _: () = { + let x = 2; + let mut y = 3; + let ref mut z @ 0..5 = 4; + let (x, ref y) = (5, 6); + let (Foo { x, y }, Bar(z, t)); + let (&mut x, (y | y)); + match () {} + }; + "#, + expect![[r#" + const _: () = { + let x = 2; + let mut y = 3; + let ref mut [email protected] = 4; + let (x,ref y) = (5,6); + let (Foo { + x,y + },Bar(z,t)); + let (&mut x,(y|y)); + match (){} + }; + "#]], + ); + } + + #[test] + fn test_attrs() { + check_pretty( + r#" + #[attr1] + #[attr2] + const _: () = {}; + #[attr1] + const _: () = { + #[attr2] + {} + }; + "#, + expect![[r#" + #[attr1] + #[attr2] + const _: () = {}; + #[attr1] + const _: () = { + #[attr2] + {} + }; + "#]], + ); + } + + #[test] + fn test_items() { + check_pretty( + r#" + fn foo() {} + struct Foo {} + struct Foo; + enum Foo {} + impl Foo {} + const _: () = {}; + static S: () = {}; + extern {} + mod x {} + mod x; + type X = 2; + use a; + use b::{c, d}; + macro_rules! foo { () => {}; } + "#, + expect![[r#" + fn foo(){} + struct Foo {} + struct Foo; + + enum Foo {} + impl Foo {} + const _: () = {}; + static S: () = {}; + extern {} + mod x {} + mod x; + + type X = 2; + use a; + use b::{ + c,d + }; + macro_rules! foo { + () => {}; + } + "#]], + ); + } + + #[test] + fn test_exprs() { + check_pretty( + r#" + const _: () = { + let _ = 1+2; + let _ = !true && false; + let _ = foo() + !bar() + dbg!(2) + *x; + let _ = async move || {}; + let _ = async move {}; + let _ = x.await; + 'lab: for _ in 0..5 { + loop { } + break 'lab expr; + if let pat = expr { + foo() + } else if true { + bar() + } else {} + if true {} else if true {} else {} + fun() + } + }; + "#, + expect![[r#" + const _: () = { + let _ = 1+2; + let _ = !true&&false; + let _ = foo()+!bar()+dbg!(2)+*x; + let _ = async move||{}; + let _ = async move {}; + let _ = x.await; + 'lab: for _ in 0..5 { + loop {} + break 'lab expr; + if let pat = expr { + foo() + }else if true { + bar() + }else {} + if true { + }else if true { + }else {} + fun() + } + }; + "#]], + ); + } + + #[test] + fn test_match_arm() { + check_pretty( + r#" + const _: () = { + match 2 { + tmp => foo!(), + }; + }; + "#, + expect![[r#" + const _: () = { + match 2 { + tmp => foo!(), + }; + }; + "#]], + ); + + check_pretty( + r#" + const _: () = { + match 2 { + tmp => {} + }; + }; + "#, + expect![[r#" + const _: () = { + match 2 { + tmp => {} + }; + }; + "#]], + ); + + check_pretty( + r#" + const _: () = { + match 2 { + 1 => {} + 2 => foo(), + _ => {}, + }; + }; + "#, + expect![[r#" + const _: () = { + match 2 { + 1 => {} + 2 => foo(), + _ => {}, + }; + }; + "#]], + ); + } +} diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs index b20aa90d06..0ffcbd212b 100644 --- a/crates/syntax/src/ast/edit.rs +++ b/crates/syntax/src/ast/edit.rs @@ -252,6 +252,10 @@ impl ast::IdentPat { } } +pub fn indent(node: &SyntaxNode, level: IndentLevel) -> SyntaxNode { + level.clone_increase_indent(node) +} + #[test] fn test_increase_indent() { let arm_list = { |