+ ast::ExprKind::AddrOf(..)
+ | ast::ExprKind::Assign(..)
+ | ast::ExprKind::AssignOp(..)
+ | ast::ExprKind::Await(..)
+ | ast::ExprKind::Box(..)
+ | ast::ExprKind::Break(..)
+ | ast::ExprKind::Cast(..)
+ | ast::ExprKind::Continue(..)
+ | ast::ExprKind::Err
+ | ast::ExprKind::Field(..)
+ | ast::ExprKind::InlineAsm(..)
+ | ast::ExprKind::LlvmInlineAsm(..)
+ | ast::ExprKind::Let(..)
+ | ast::ExprKind::Path(..)
+ | ast::ExprKind::Range(..)
+ | ast::ExprKind::Repeat(..)
+ | ast::ExprKind::Ret(..)
+ | ast::ExprKind::Tup(..)
+ | ast::ExprKind::Type(..)
+ | ast::ExprKind::Yield(None)
+ | ast::ExprKind::Underscore => false,
+ }
+}
+
+/// Removes trailing spaces from the specified snippet. We do not remove spaces
+/// inside strings or comments.
+pub(crate) fn remove_trailing_white_spaces(text: &str) -> String {
+ let mut buffer = String::with_capacity(text.len());
+ let mut space_buffer = String::with_capacity(128);
+ for (char_kind, c) in CharClasses::new(text.chars()) {
+ match c {
+ '\n' => {
+ if char_kind == FullCodeCharKind::InString {
+ buffer.push_str(&space_buffer);
+ }
+ space_buffer.clear();
+ buffer.push('\n');
+ }
+ _ if c.is_whitespace() => {
+ space_buffer.push(c);
+ }
+ _ => {
+ if !space_buffer.is_empty() {
+ buffer.push_str(&space_buffer);
+ space_buffer.clear();
+ }
+ buffer.push(c);
+ }
+ }
+ }
+ buffer
+}
+
+/// Indent each line according to the specified `indent`.
+/// e.g.
+///
+/// ```rust,compile_fail
+/// foo!{
+/// x,
+/// y,
+/// foo(
+/// a,
+/// b,
+/// c,
+/// ),
+/// }
+/// ```
+///
+/// will become
+///
+/// ```rust,compile_fail
+/// foo!{
+/// x,
+/// y,
+/// foo(
+/// a,
+/// b,
+/// c,
+/// ),
+/// }
+/// ```
+pub(crate) fn trim_left_preserve_layout(
+ orig: &str,
+ indent: Indent,
+ config: &Config,
+) -> Option<String> {
+ let mut lines = LineClasses::new(orig);
+ let first_line = lines.next().map(|(_, s)| s.trim_end().to_owned())?;
+ let mut trimmed_lines = Vec::with_capacity(16);
+
+ let mut veto_trim = false;
+ let min_prefix_space_width = lines
+ .filter_map(|(kind, line)| {
+ let mut trimmed = true;
+ let prefix_space_width = if is_empty_line(&line) {
+ None
+ } else {
+ Some(get_prefix_space_width(config, &line))
+ };
+
+ // just InString{Commented} in order to allow the start of a string to be indented
+ let new_veto_trim_value = (kind == FullCodeCharKind::InString
+ || (config.version() == Version::Two
+ && kind == FullCodeCharKind::InStringCommented))
+ && !line.ends_with('\\');
+ let line = if veto_trim || new_veto_trim_value {
+ veto_trim = new_veto_trim_value;
+ trimmed = false;
+ line
+ } else {
+ line.trim().to_owned()
+ };
+ trimmed_lines.push((trimmed, line, prefix_space_width));
+
+ // Because there is a veto against trimming and indenting lines within a string,
+ // such lines should not be taken into account when computing the minimum.
+ match kind {
+ FullCodeCharKind::InStringCommented | FullCodeCharKind::EndStringCommented
+ if config.version() == Version::Two =>
+ {
+ None
+ }
+ FullCodeCharKind::InString | FullCodeCharKind::EndString => None,
+ _ => prefix_space_width,
+ }
+ })
+ .min()?;
+
+ Some(
+ first_line
+ + "\n"
+ + &trimmed_lines
+ .iter()
+ .map(
+ |&(trimmed, ref line, prefix_space_width)| match prefix_space_width {
+ _ if !trimmed => line.to_owned(),
+ Some(original_indent_width) => {
+ let new_indent_width = indent.width()
+ + original_indent_width.saturating_sub(min_prefix_space_width);
+ let new_indent = Indent::from_width(config, new_indent_width);
+ format!("{}{}", new_indent.to_string(config), line)
+ }
+ None => String::new(),
+ },
+ )
+ .collect::<Vec<_>>()
+ .join("\n"),
+ )
+}
+
+/// Based on the given line, determine if the next line can be indented or not.
+/// This allows to preserve the indentation of multi-line literals.
+pub(crate) fn indent_next_line(kind: FullCodeCharKind, _line: &str, config: &Config) -> bool {
+ !(kind.is_string() || (config.version() == Version::Two && kind.is_commented_string()))
+}
+
+pub(crate) fn is_empty_line(s: &str) -> bool {
+ s.is_empty() || s.chars().all(char::is_whitespace)
+}
+
+fn get_prefix_space_width(config: &Config, s: &str) -> usize {
+ let mut width = 0;
+ for c in s.chars() {
+ match c {
+ ' ' => width += 1,
+ '\t' => width += config.tab_spaces(),
+ _ => return width,
+ }
+ }
+ width
+}
+
+pub(crate) trait NodeIdExt {
+ fn root() -> Self;
+}
+
+impl NodeIdExt for NodeId {
+ fn root() -> NodeId {
+ NodeId::placeholder_from_expn_id(ExpnId::root())
+ }
+}
+
+pub(crate) fn unicode_str_width(s: &str) -> usize {
+ s.width()
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_remove_trailing_white_spaces() {
+ let s = " r#\"\n test\n \"#";
+ assert_eq!(remove_trailing_white_spaces(&s), s);
+ }
+
+ #[test]
+ fn test_trim_left_preserve_layout() {
+ let s = "aaa\n\tbbb\n ccc";
+ let config = Config::default();
+ let indent = Indent::new(4, 0);
+ assert_eq!(
+ trim_left_preserve_layout(&s, indent, &config),
+ Some("aaa\n bbb\n ccc".to_string())
+ );