Skip to main content

rustfmt_nightly/
header.rs

1//! headers are sets of consecutive keywords and tokens, such as
2//! `pub const unsafe fn foo` and `pub(crate) unsafe trait Bar`.
3//!
4//! This module contains general logic for formatting such headers,
5//! where they are always placed on a single line except when there
6//! are comments between parts of the header.
7
8use std::borrow::Cow;
9
10use rustc_ast as ast;
11use rustc_span::Span;
12use rustc_span::symbol::Ident;
13use tracing::debug;
14
15use crate::comment::{combine_strs_with_missing_comments, contains_comment};
16use crate::rewrite::RewriteContext;
17use crate::shape::Shape;
18use crate::utils::rewrite_ident;
19
20pub(crate) fn format_header(
21    context: &RewriteContext<'_>,
22    shape: Shape,
23    parts: Vec<HeaderPart<'_>>,
24) -> String {
25    debug!(?parts, "format_header");
26    let shape = shape.infinite_width();
27
28    // Empty `HeaderPart`s are ignored.
29    let mut parts = parts.into_iter().filter(|x| !x.snippet.is_empty());
30    let Some(part) = parts.next() else {
31        return String::new();
32    };
33
34    let mut result = part.snippet.into_owned();
35    let mut span = part.span;
36
37    for part in parts {
38        debug!(?result, "before combine");
39        let comments_span = span.between(part.span);
40        let comments_snippet = context.snippet(comments_span);
41        result = if contains_comment(comments_snippet) {
42            // FIXME(fee1-dead): preserve (potentially misaligned) comments instead of reformatting
43            // them. Revisit this once we have a strategy for properly dealing with them.
44            format!("{result}{comments_snippet}{}", part.snippet)
45        } else {
46            combine_strs_with_missing_comments(
47                context,
48                &result,
49                &part.snippet,
50                comments_span,
51                shape,
52                true,
53            )
54            .unwrap_or_else(|_| format!("{} {}", &result, part.snippet))
55        };
56        debug!(?result);
57        span = part.span;
58    }
59
60    result
61}
62
63#[derive(Debug)]
64pub(crate) struct HeaderPart<'a> {
65    /// snippet of this part without surrounding space
66    snippet: Cow<'a, str>,
67    span: Span,
68}
69
70impl<'a> HeaderPart<'a> {
71    pub(crate) fn new(snippet: impl Into<Cow<'a, str>>, span: Span) -> Self {
72        Self {
73            snippet: snippet.into(),
74            span,
75        }
76    }
77
78    pub(crate) fn ident(context: &'a RewriteContext<'_>, ident: Ident) -> Self {
79        Self::new(rewrite_ident(context, ident), ident.span)
80    }
81
82    pub(crate) fn visibility(context: &RewriteContext<'_>, vis: &ast::Visibility) -> Self {
83        let snippet = match vis.kind {
84            ast::VisibilityKind::Public => Cow::from("pub"),
85            ast::VisibilityKind::Inherited => Cow::from(""),
86            ast::VisibilityKind::Restricted { ref path, .. } => {
87                let ast::Path { ref segments, .. } = **path;
88                let mut segments_iter =
89                    segments.iter().map(|seg| rewrite_ident(context, seg.ident));
90                if path.is_global() {
91                    segments_iter
92                        .next()
93                        .expect("Non-global path in pub(restricted)?");
94                }
95                let is_keyword = |s: &str| s == "crate" || s == "self" || s == "super";
96                let path = segments_iter.collect::<Vec<_>>().join("::");
97                let in_str = if is_keyword(&path) { "" } else { "in " };
98
99                // FIXME(fee1-dead): comments around parens
100                Cow::from(format!("pub({}{})", in_str, path))
101            }
102        };
103
104        Self::new(snippet, vis.span)
105    }
106}