Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/syntax-bridge/src/prettify_macro_expansion.rs')
-rw-r--r--crates/syntax-bridge/src/prettify_macro_expansion.rs359
1 files changed, 322 insertions, 37 deletions
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(),
+ _ => {},
+ };
+ };
+ "#]],
+ );
+ }
+}