hir_expand/
prettify_macro_expansion_.rs

1//! Pretty printing of macros output.
2
3use base_db::Crate;
4use rustc_hash::FxHashMap;
5use syntax::NodeOrToken;
6use syntax::{SyntaxNode, ast::make};
7
8use crate::{db::ExpandDatabase, span_map::ExpansionSpanMap};
9
10/// Inserts whitespace and replaces `$crate` in macro expansions.
11#[expect(deprecated)]
12pub fn prettify_macro_expansion(
13    db: &dyn ExpandDatabase,
14    syn: SyntaxNode,
15    span_map: &ExpansionSpanMap,
16    target_crate_id: Crate,
17) -> SyntaxNode {
18    // Because `syntax_bridge::prettify_macro_expansion::prettify_macro_expansion()` clones subtree for `syn`,
19    // that means it will be offsetted to the beginning.
20    let span_offset = syn.text_range().start();
21    let target_crate = target_crate_id.data(db);
22    let mut syntax_ctx_id_to_dollar_crate_replacement = FxHashMap::default();
23    syntax_bridge::prettify_macro_expansion::prettify_macro_expansion(
24        syn,
25        &mut |dollar_crate| {
26            let ctx = span_map.span_at(dollar_crate.text_range().start() + span_offset).ctx;
27            let replacement =
28                syntax_ctx_id_to_dollar_crate_replacement.entry(ctx).or_insert_with(|| {
29                    let macro_call_id = ctx
30                        .outer_expn(db)
31                        .expect("`$crate` cannot come from `SyntaxContextId::ROOT`");
32                    let macro_call = db.lookup_intern_macro_call(macro_call_id.into());
33                    let macro_def_crate = macro_call.def.krate;
34                    // First, if this is the same crate as the macro, nothing will work but `crate`.
35                    // If not, if the target trait has the macro's crate as a dependency, using the dependency name
36                    // will work in inserted code and match the user's expectation.
37                    // If not, the crate's display name is what the dependency name is likely to be once such dependency
38                    // is inserted, and also understandable to the user.
39                    // Lastly, if nothing else found, resort to leaving `$crate`.
40                    if target_crate_id == macro_def_crate {
41                        make::tokens::crate_kw()
42                    } else if let Some(dep) =
43                        target_crate.dependencies.iter().find(|dep| dep.crate_id == macro_def_crate)
44                    {
45                        make::tokens::ident(dep.name.as_str())
46                    } else if let Some(crate_name) = &macro_def_crate.extra_data(db).display_name {
47                        make::tokens::ident(crate_name.crate_name().as_str())
48                    } else {
49                        dollar_crate.clone()
50                    }
51                });
52            if replacement.text() == "$crate" {
53                // The parent may have many children, and looking for the token may yield incorrect results.
54                return None;
55            }
56            // We need to `clone_subtree()` but rowan doesn't provide such operation for tokens.
57            let parent = replacement.parent().unwrap().clone_subtree().clone_for_update();
58            parent
59                .children_with_tokens()
60                .filter_map(NodeOrToken::into_token)
61                .find(|it| it.kind() == replacement.kind())
62        },
63        |_| (),
64    )
65}