]> git.lizzy.rs Git - rust.git/blobdiff - src/lists.rs
Preserve trailing comma on macro call when using mixed layout
[rust.git] / src / lists.rs
index 89382ab76ecdffc644f9f5045a839bb1195c1d38..a14d1907a8c169ecb912b05a660be47ff45af72f 100644 (file)
@@ -8,54 +8,20 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+//! Format list-like expressions and items.
+
 use std::cmp;
 use std::iter::Peekable;
 
-use syntax::codemap::{BytePos, CodeMap};
+use config::lists::*;
+use syntax::codemap::BytePos;
 
 use comment::{find_comment_end, rewrite_comment, FindUncommented};
 use config::{Config, IndentStyle};
 use rewrite::RewriteContext;
 use shape::{Indent, Shape};
-use utils::{first_line_width, last_line_width, mk_sp, starts_with_newline};
-
-/// Formatting tactic for lists. This will be cast down to a
-/// `DefinitiveListTactic` depending on the number and length of the items and
-/// their comments.
-#[derive(Eq, PartialEq, Debug, Copy, Clone)]
-pub enum ListTactic {
-    // One item per row.
-    Vertical,
-    // All items on one row.
-    Horizontal,
-    // Try Horizontal layout, if that fails then vertical.
-    HorizontalVertical,
-    // HorizontalVertical with a soft limit of n characters.
-    LimitedHorizontalVertical(usize),
-    // Pack as many items as possible per row over (possibly) many rows.
-    Mixed,
-}
-
-impl_enum_serialize_and_deserialize!(ListTactic, Vertical, Horizontal, HorizontalVertical, Mixed);
-
-#[derive(Eq, PartialEq, Debug, Copy, Clone)]
-pub enum SeparatorTactic {
-    Always,
-    Never,
-    Vertical,
-}
-
-impl_enum_serialize_and_deserialize!(SeparatorTactic, Always, Never, Vertical);
-
-impl SeparatorTactic {
-    pub fn from_bool(b: bool) -> SeparatorTactic {
-        if b {
-            SeparatorTactic::Always
-        } else {
-            SeparatorTactic::Never
-        }
-    }
-}
+use utils::{count_newlines, first_line_width, last_line_width, mk_sp, starts_with_newline};
+use visitor::SnippetProvider;
 
 pub struct ListFormatting<'a> {
     pub tactic: DefinitiveListTactic,
@@ -90,7 +56,7 @@ fn as_ref(&self) -> &ListItem {
     }
 }
 
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Debug, Copy, Clone)]
 pub enum ListItemCommentStyle {
     // Try to keep the comment on the same line with the item.
     SameLine,
@@ -100,6 +66,7 @@ pub enum ListItemCommentStyle {
     None,
 }
 
+#[derive(Debug, Clone)]
 pub struct ListItem {
     // None for comments mean that they are not present.
     pub pre_comment: Option<String>,
@@ -134,13 +101,17 @@ pub fn is_multiline(&self) -> bool {
                 .map_or(false, |s| s.contains('\n'))
     }
 
-    pub fn has_comment(&self) -> bool {
+    pub fn has_single_line_comment(&self) -> bool {
         self.pre_comment
             .as_ref()
-            .map_or(false, |comment| comment.starts_with("//"))
+            .map_or(false, |comment| comment.trim_left().starts_with("//"))
             || self.post_comment
                 .as_ref()
-                .map_or(false, |comment| comment.starts_with("//"))
+                .map_or(false, |comment| comment.trim_left().starts_with("//"))
+    }
+
+    pub fn has_comment(&self) -> bool {
+        self.pre_comment.is_some() || self.post_comment.is_some()
     }
 
     pub fn from_str<S: Into<String>>(s: S) -> ListItem {
@@ -152,22 +123,17 @@ pub fn from_str<S: Into<String>>(s: S) -> ListItem {
             new_lines: false,
         }
     }
-}
 
-/// The definitive formatting tactic for lists.
-#[derive(Eq, PartialEq, Debug, Copy, Clone)]
-pub enum DefinitiveListTactic {
-    Vertical,
-    Horizontal,
-    Mixed,
-}
-
-impl DefinitiveListTactic {
-    pub fn ends_with_newline(&self, indent_style: IndentStyle) -> bool {
-        match indent_style {
-            IndentStyle::Block => *self != DefinitiveListTactic::Horizontal,
-            IndentStyle::Visual => false,
+    // true if the item causes something to be written.
+    fn is_substantial(&self) -> bool {
+        fn empty(s: &Option<String>) -> bool {
+            match *s {
+                Some(ref s) if !s.is_empty() => false,
+                _ => true,
+            }
         }
+
+        !(empty(&self.pre_comment) && empty(&self.item) && empty(&self.post_comment))
     }
 }
 
@@ -189,32 +155,6 @@ pub fn len(&self) -> usize {
     }
 }
 
-/// Where to put separator.
-#[derive(Eq, PartialEq, Debug, Copy, Clone)]
-pub enum SeparatorPlace {
-    Front,
-    Back,
-}
-
-impl_enum_serialize_and_deserialize!(SeparatorPlace, Front, Back);
-
-impl SeparatorPlace {
-    pub fn is_front(&self) -> bool {
-        *self == SeparatorPlace::Front
-    }
-
-    pub fn is_back(&self) -> bool {
-        *self == SeparatorPlace::Back
-    }
-
-    pub fn from_tactic(default: SeparatorPlace, tactic: DefinitiveListTactic) -> SeparatorPlace {
-        match tactic {
-            DefinitiveListTactic::Vertical => default,
-            _ => SeparatorPlace::Back,
-        }
-    }
-}
-
 pub fn definitive_tactic<I, T>(
     items: I,
     tactic: ListTactic,
@@ -228,15 +168,14 @@ pub fn definitive_tactic<I, T>(
     let pre_line_comments = items
         .clone()
         .into_iter()
-        .any(|item| item.as_ref().has_comment());
+        .any(|item| item.as_ref().has_single_line_comment());
 
     let limit = match tactic {
         _ if pre_line_comments => return DefinitiveListTactic::Vertical,
-        ListTactic::Mixed => return DefinitiveListTactic::Mixed,
         ListTactic::Horizontal => return DefinitiveListTactic::Horizontal,
         ListTactic::Vertical => return DefinitiveListTactic::Vertical,
         ListTactic::LimitedHorizontalVertical(limit) => ::std::cmp::min(width, limit),
-        ListTactic::HorizontalVertical => width,
+        ListTactic::Mixed | ListTactic::HorizontalVertical => width,
     };
 
     let (sep_count, total_width) = calculate_width(items.clone());
@@ -248,7 +187,10 @@ pub fn definitive_tactic<I, T>(
     {
         DefinitiveListTactic::Horizontal
     } else {
-        DefinitiveListTactic::Vertical
+        match tactic {
+            ListTactic::Mixed => DefinitiveListTactic::Mixed,
+            _ => DefinitiveListTactic::Vertical,
+        }
     }
 }
 
@@ -269,7 +211,8 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
     let cloned_items = items.clone();
     let mut iter = items.into_iter().enumerate().peekable();
     let mut item_max_width: Option<usize> = None;
-    let mut sep_place = SeparatorPlace::from_tactic(formatting.separator_place, tactic);
+    let sep_place =
+        SeparatorPlace::from_tactic(formatting.separator_place, tactic, formatting.separator);
 
     let mut line_len = 0;
     let indent_str = &formatting.shape.indent.to_string(formatting.config);
@@ -278,7 +221,10 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
         let inner_item = item.item.as_ref()?;
         let first = i == 0;
         let last = iter.peek().is_none();
-        let mut separate = !last || trailing_separator;
+        let mut separate = match sep_place {
+            SeparatorPlace::Front => !first,
+            SeparatorPlace::Back => !last || trailing_separator,
+        };
         let item_sep_len = if separate { sep_len } else { 0 };
 
         // Item string may be multi-line. Its length (used for block comment alignment)
@@ -293,11 +239,29 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
             item_last_line_width -= indent_str.len();
         }
 
+        if !item.is_substantial() {
+            continue;
+        }
+
         match tactic {
             DefinitiveListTactic::Horizontal if !first => {
                 result.push(' ');
             }
-            DefinitiveListTactic::Vertical if !first => {
+            DefinitiveListTactic::SpecialMacro(num_args_before) => {
+                if i == 0 {
+                    // Nothing
+                } else if i < num_args_before {
+                    result.push(' ');
+                } else if i <= num_args_before + 1 {
+                    result.push('\n');
+                    result.push_str(indent_str);
+                } else {
+                    result.push(' ');
+                }
+            }
+            DefinitiveListTactic::Vertical
+                if !first && !inner_item.is_empty() && !result.is_empty() =>
+            {
                 result.push('\n');
                 result.push_str(indent_str);
             }
@@ -310,15 +274,12 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
                     result.push_str(indent_str);
                     line_len = 0;
                     if formatting.ends_with_newline {
-                        if last {
-                            separate = true;
-                        } else {
-                            trailing_separator = true;
-                        }
+                        trailing_separator = true;
                     }
-                    sep_place = formatting.separator_place;
-                } else {
-                    sep_place = SeparatorPlace::Back;
+                }
+
+                if last && formatting.ends_with_newline {
+                    separate = formatting.trailing_separator != SeparatorTactic::Never;
                 }
 
                 if line_len > 0 {
@@ -340,26 +301,28 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
                 rewrite_comment(comment, block_mode, formatting.shape, formatting.config)?;
             result.push_str(&comment);
 
-            if tactic == DefinitiveListTactic::Vertical {
-                // We cannot keep pre-comments on the same line if the comment if normalized.
-                let keep_comment = if formatting.config.normalize_comments()
-                    || item.pre_comment_style == ListItemCommentStyle::DifferentLine
-                {
-                    false
+            if !inner_item.is_empty() {
+                if tactic == DefinitiveListTactic::Vertical {
+                    // We cannot keep pre-comments on the same line if the comment if normalized.
+                    let keep_comment = if formatting.config.normalize_comments()
+                        || item.pre_comment_style == ListItemCommentStyle::DifferentLine
+                    {
+                        false
+                    } else {
+                        // We will try to keep the comment on the same line with the item here.
+                        // 1 = ` `
+                        let total_width = total_item_width(item) + item_sep_len + 1;
+                        total_width <= formatting.shape.width
+                    };
+                    if keep_comment {
+                        result.push(' ');
+                    } else {
+                        result.push('\n');
+                        result.push_str(indent_str);
+                    }
                 } else {
-                    // We will try to keep the comment on the same line with the item here.
-                    // 1 = ` `
-                    let total_width = total_item_width(item) + item_sep_len + 1;
-                    total_width <= formatting.shape.width
-                };
-                if keep_comment {
                     result.push(' ');
-                } else {
-                    result.push('\n');
-                    result.push_str(indent_str);
                 }
-            } else {
-                result.push(' ');
             }
             item_max_width = None;
         }
@@ -368,7 +331,7 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
             result.push_str(formatting.separator.trim());
             result.push(' ');
         }
-        result.push_str(&inner_item[..]);
+        result.push_str(inner_item);
 
         // Post-comments
         if tactic != DefinitiveListTactic::Vertical && item.post_comment.is_some() {
@@ -401,7 +364,9 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
                         formatting.config.max_width(),
                     ));
                 }
-                let overhead = if let Some(max_width) = *item_max_width {
+                let overhead = if starts_with_newline(comment) {
+                    0
+                } else if let Some(max_width) = *item_max_width {
                     max_width + 2
                 } else {
                     // 1 = space between item and comment.
@@ -416,12 +381,17 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
                     || comment.trim().contains('\n')
                     || comment.trim().len() > width;
 
-                rewrite_comment(comment, block_style, comment_shape, formatting.config)
+                rewrite_comment(
+                    comment.trim_left(),
+                    block_style,
+                    comment_shape,
+                    formatting.config,
+                )
             };
 
             let mut formatted_comment = rewrite_post_comment(&mut item_max_width)?;
 
-            if !starts_with_newline(&formatted_comment) {
+            if !starts_with_newline(comment) {
                 let mut comment_alignment =
                     post_comment_alignment(item_max_width, inner_item.len());
                 if first_line_width(&formatted_comment) + last_line_width(&result)
@@ -439,6 +409,9 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
                 {
                     result.push(' ');
                 }
+            } else {
+                result.push('\n');
+                result.push_str(indent_str);
             }
             if formatted_comment.contains('\n') {
                 item_max_width = None;
@@ -501,7 +474,7 @@ pub struct ListItems<'a, I, F1, F2, F3>
 where
     I: Iterator,
 {
-    codemap: &'a CodeMap,
+    snippet_provider: &'a SnippetProvider<'a>,
     inner: Peekable<I>,
     get_lo: F1,
     get_hi: F2,
@@ -528,7 +501,7 @@ fn next(&mut self) -> Option<Self::Item> {
         self.inner.next().map(|item| {
             let mut new_lines = false;
             // Pre-comment
-            let pre_snippet = self.codemap
+            let pre_snippet = self.snippet_provider
                 .span_to_snippet(mk_sp(self.prev_span_end, (self.get_lo)(&item)))
                 .unwrap();
             let trimmed_pre_snippet = pre_snippet.trim();
@@ -566,7 +539,7 @@ fn next(&mut self) -> Option<Self::Item> {
                 Some(next_item) => (self.get_lo)(next_item),
                 None => self.next_span_start,
             };
-            let post_snippet = self.codemap
+            let post_snippet = self.snippet_provider
                 .span_to_snippet(mk_sp((self.get_hi)(&item), next_start))
                 .unwrap();
 
@@ -575,7 +548,7 @@ fn next(&mut self) -> Option<Self::Item> {
                     let mut block_open_index = post_snippet.find("/*");
                     // check if it really is a block comment (and not `//*` or a nested comment)
                     if let Some(i) = block_open_index {
-                        match post_snippet.find("/") {
+                        match post_snippet.find('/') {
                             Some(j) if j < i => block_open_index = None,
                             _ if i > 0 && &post_snippet[i - 1..i] == "/" => block_open_index = None,
                             _ => (),
@@ -601,15 +574,13 @@ fn next(&mut self) -> Option<Self::Item> {
                             (_, Some(j)) if j > separator_index => j + 1,
                             _ => post_snippet.len(),
                         }
-                    } else {
+                    } else if let Some(newline_index) = newline_index {
                         // Match arms may not have trailing comma. In any case, for match arms,
                         // we will assume that the post comment belongs to the next arm if they
                         // do not end with trailing comma.
-                        if let Some(newline_index) = newline_index {
-                            newline_index + 1
-                        } else {
-                            0
-                        }
+                        newline_index + 1
+                    } else {
+                        0
                     }
                 }
                 None => post_snippet
@@ -634,7 +605,7 @@ fn next(&mut self) -> Option<Self::Item> {
                 // From the end of the first line of comments to the next non-whitespace char.
                 let test_snippet = &test_snippet[..first];
 
-                if test_snippet.chars().filter(|c| c == &'\n').count() > 1 {
+                if count_newlines(test_snippet) > 1 {
                     // There were multiple line breaks which got trimmed to nothing.
                     new_lines = true;
                 }
@@ -646,6 +617,8 @@ fn next(&mut self) -> Option<Self::Item> {
 
             let post_snippet_trimmed = if post_snippet.starts_with(|c| c == ',' || c == ':') {
                 post_snippet[1..].trim_matches(white_space)
+            } else if post_snippet.starts_with(self.separator) {
+                post_snippet[self.separator.len()..].trim_matches(white_space)
             } else if post_snippet.ends_with(',') {
                 post_snippet[..(post_snippet.len() - 1)].trim_matches(white_space)
             } else {
@@ -659,23 +632,24 @@ fn next(&mut self) -> Option<Self::Item> {
             };
 
             ListItem {
-                pre_comment: pre_comment,
-                pre_comment_style: pre_comment_style,
+                pre_comment,
+                pre_comment_style,
                 item: if self.inner.peek().is_none() && self.leave_last {
                     None
                 } else {
                     (self.get_item_string)(&item)
                 },
-                post_comment: post_comment,
-                new_lines: new_lines,
+                post_comment,
+                new_lines,
             }
         })
     }
 }
 
+#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))]
 // Creates an iterator over a list's items with associated comments.
 pub fn itemize_list<'a, T, I, F1, F2, F3>(
-    codemap: &'a CodeMap,
+    snippet_provider: &'a SnippetProvider,
     inner: I,
     terminator: &'a str,
     separator: &'a str,
@@ -693,16 +667,16 @@ pub fn itemize_list<'a, T, I, F1, F2, F3>(
     F3: Fn(&T) -> Option<String>,
 {
     ListItems {
-        codemap: codemap,
+        snippet_provider,
         inner: inner.peekable(),
-        get_lo: get_lo,
-        get_hi: get_hi,
-        get_item_string: get_item_string,
-        prev_span_end: prev_span_end,
-        next_span_start: next_span_start,
-        terminator: terminator,
-        separator: separator,
-        leave_last: leave_last,
+        get_lo,
+        get_hi,
+        get_item_string,
+        prev_span_end,
+        next_span_start,
+        terminator,
+        separator,
+        leave_last,
     }
 }
 
@@ -761,7 +735,7 @@ pub fn struct_lit_shape(
     };
     let shape_width = shape.width.checked_sub(prefix_width + suffix_width);
     if let Some(w) = shape_width {
-        let shape_width = cmp::min(w, context.config.struct_lit_width());
+        let shape_width = cmp::min(w, context.config.width_heuristics().struct_lit_width);
         Some((Some(Shape::legacy(shape_width, shape.indent)), v_shape))
     } else {
         Some((None, v_shape))
@@ -777,7 +751,8 @@ pub fn struct_lit_tactic(
     if let Some(h_shape) = h_shape {
         let prelim_tactic = match (context.config.indent_style(), items.len()) {
             (IndentStyle::Visual, 1) => ListTactic::HorizontalVertical,
-            _ => context.config.struct_lit_multiline_style().to_list_tactic(),
+            _ if context.config.struct_lit_single_line() => ListTactic::HorizontalVertical,
+            _ => ListTactic::Vertical,
         };
         definitive_tactic(items, prelim_tactic, Separator::Comma, h_shape.width)
     } else {
@@ -809,7 +784,7 @@ pub fn struct_lit_formatting<'a>(
     let ends_with_newline = context.config.indent_style() != IndentStyle::Visual
         && tactic == DefinitiveListTactic::Vertical;
     ListFormatting {
-        tactic: tactic,
+        tactic,
         separator: ",",
         trailing_separator: if force_no_trailing_comma {
             SeparatorTactic::Never
@@ -817,8 +792,8 @@ pub fn struct_lit_formatting<'a>(
             context.config.trailing_comma()
         },
         separator_place: SeparatorPlace::Back,
-        shape: shape,
-        ends_with_newline: ends_with_newline,
+        shape,
+        ends_with_newline,
         preserve_newline: true,
         config: context.config,
     }