Unnamed repository; edit this file 'description' to name the repository.
fix: Improve prettify_macro_expansion()
- Add basic tests for prettify_macro_expansion Example --- ```rust const _: () = { loop {break} foo() }; ``` **Before this PR** ```rust const _:() = { loop{ break }foo() }; ``` **After this PR** ```rust const _: () = { loop { break } foo() }; ``` --- ```rust const _: () = { match 2 { tmp => foo!(), }; }; ``` **Before this PR** ```rust const _:() = { match 2 { tmp => foo!(), }; }; ``` **After this PR** ```rust const _: () = { match 2 { tmp => foo!(), }; }; ```
A4-Tacks 4 weeks ago
parent 4171602 · commit 6e03313
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ide-assists/src/handlers/inline_macro.rs2
-rw-r--r--crates/ide-completion/src/completions/item_list/trait_impl.rs2
-rw-r--r--crates/ide/src/expand_macro.rs15
-rw-r--r--crates/mbe/src/tests.rs2
-rw-r--r--crates/syntax-bridge/Cargo.toml1
-rw-r--r--crates/syntax-bridge/src/prettify_macro_expansion.rs328
7 files changed, 316 insertions, 35 deletions
diff --git a/Cargo.lock b/Cargo.lock
index e6575c28c1..92acf544ee 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2744,6 +2744,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..1cf5402db8 100644
--- a/crates/ide-assists/src/handlers/inline_macro.rs
+++ b/crates/ide-assists/src/handlers/inline_macro.rs
@@ -258,7 +258,7 @@ macro_rules! whitespace {
if true {}
};
}
-fn f() { if true{}; }
+fn f() { if true {}; }
"#,
)
}
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..fe58786991 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,7 @@ 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 +592,7 @@ struct Foo {}
"#,
expect![[r#"
proc_macros::DeriveIdentity
- struct Foo{}"#]],
+ struct Foo {}"#]],
);
}
@@ -612,7 +610,7 @@ struct Foo {}
expect![[r#"
proc_macros::DeriveIdentity
#[derive(proc_macros::DeriveIdentity)]
- struct Foo{}"#]],
+ struct Foo {}"#]],
);
}
@@ -628,7 +626,7 @@ struct Foo {}
"#,
expect![[r#"
proc_macros::DeriveIdentity
- struct Foo{}"#]],
+ struct Foo {}"#]],
);
check(
r#"
@@ -640,7 +638,7 @@ struct Foo {}
"#,
expect![[r#"
proc_macros::DeriveIdentity
- struct Foo{}"#]],
+ struct Foo {}"#]],
);
}
@@ -783,7 +781,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..43f270800d 100644
--- a/crates/syntax-bridge/src/prettify_macro_expansion.rs
+++ b/crates/syntax-bridge/src/prettify_macro_expansion.rs
@@ -45,17 +45,23 @@ 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 {
- mods.push((Position::after(node.clone()), PrettifyWsKind::Indent(indent)));
- }
- if node.parent().is_some() {
- mods.push((Position::after(node), PrettifyWsKind::Newline));
+ 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 {
+ if indent > 0 {
+ mods.push((Position::after(node.clone()), PrettifyWsKind::Indent(indent)));
+ }
+ if node.parent().is_some() {
+ mods.push((Position::after(node), PrettifyWsKind::Newline));
+ }
}
continue;
}
@@ -77,16 +83,12 @@ 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));
}
@@ -98,16 +100,10 @@ pub fn prettify_macro_expansion(
}
mods.push(do_nl(before, tok));
}
- R_CURLY => {
- if indent > 0 {
- mods.push(do_indent(after, tok, indent));
- }
- mods.push(do_nl(after, 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) => {
@@ -126,6 +122,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));
}
@@ -161,3 +163,283 @@ 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 = source_file.syntax_node().clone_for_update();
+
+ remove_whitespaces(&syn);
+ 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) {
+ let mut to_remove = vec![];
+ node.preorder_with_tokens().for_each(|it| match it {
+ WalkEvent::Enter(NodeOrToken::Token(tok)) if tok.kind().is_trivia() => {
+ to_remove.push(tok);
+ }
+ _ => (),
+ });
+ to_remove.iter().for_each(ted::remove)
+ }
+ }
+
+ #[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 {}
+ 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 {}
+ 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(),
+ _ => {},
+ };
+ };
+ "#]],
+ );
+ }
+}