]> git.lizzy.rs Git - rust.git/blob - src/comment.rs
Merge pull request #1925 from topecongiro/enhance-comment
[rust.git] / src / comment.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 // Formatting and tools for comments.
12
13 use std::{self, iter};
14
15 use syntax::codemap::Span;
16
17 use {Indent, Shape};
18 use config::Config;
19 use rewrite::RewriteContext;
20 use string::{rewrite_string, StringFormat};
21 use utils::{first_line_width, last_line_width, wrap_str};
22
23 fn is_custom_comment(comment: &str) -> bool {
24     if !comment.starts_with("//") {
25         false
26     } else {
27         if let Some(c) = comment.chars().nth(2) {
28             !c.is_alphanumeric() && !c.is_whitespace()
29         } else {
30             false
31         }
32     }
33 }
34
35 #[derive(PartialEq, Eq)]
36 pub enum CommentStyle<'a> {
37     DoubleSlash,
38     TripleSlash,
39     Doc,
40     SingleBullet,
41     DoubleBullet,
42     Exclamation,
43     Custom(&'a str),
44 }
45
46 fn custom_opener(s: &str) -> &str {
47     s.lines().next().map_or("", |first_line| {
48         first_line
49             .find(' ')
50             .map_or(first_line, |space_index| &first_line[0..space_index + 1])
51     })
52 }
53
54 impl<'a> CommentStyle<'a> {
55     pub fn opener(&self) -> &'a str {
56         match *self {
57             CommentStyle::DoubleSlash => "// ",
58             CommentStyle::TripleSlash => "/// ",
59             CommentStyle::Doc => "//! ",
60             CommentStyle::SingleBullet => "/* ",
61             CommentStyle::DoubleBullet => "/** ",
62             CommentStyle::Exclamation => "/*! ",
63             CommentStyle::Custom(opener) => opener,
64         }
65     }
66
67     pub fn closer(&self) -> &'a str {
68         match *self {
69             CommentStyle::DoubleSlash |
70             CommentStyle::TripleSlash |
71             CommentStyle::Custom(..) |
72             CommentStyle::Doc => "",
73             CommentStyle::DoubleBullet => " **/",
74             CommentStyle::SingleBullet | CommentStyle::Exclamation => " */",
75         }
76     }
77
78     pub fn line_start(&self) -> &'a str {
79         match *self {
80             CommentStyle::DoubleSlash => "// ",
81             CommentStyle::TripleSlash => "/// ",
82             CommentStyle::Doc => "//! ",
83             CommentStyle::SingleBullet | CommentStyle::Exclamation => " * ",
84             CommentStyle::DoubleBullet => " ** ",
85             CommentStyle::Custom(opener) => opener,
86         }
87     }
88
89     pub fn to_str_tuplet(&self) -> (&'a str, &'a str, &'a str) {
90         (self.opener(), self.closer(), self.line_start())
91     }
92
93     pub fn line_with_same_comment_style(&self, line: &str, normalize_comments: bool) -> bool {
94         match *self {
95             CommentStyle::DoubleSlash | CommentStyle::TripleSlash | CommentStyle::Doc => {
96                 line.trim_left().starts_with(self.line_start().trim_left()) ||
97                     comment_style(line, normalize_comments) == *self
98             }
99             CommentStyle::DoubleBullet | CommentStyle::SingleBullet | CommentStyle::Exclamation => {
100                 line.trim_left().starts_with(self.closer().trim_left()) ||
101                     line.trim_left().starts_with(self.line_start().trim_left()) ||
102                     comment_style(line, normalize_comments) == *self
103             }
104             CommentStyle::Custom(opener) => line.trim_left().starts_with(opener.trim_right()),
105         }
106     }
107 }
108
109 fn comment_style(orig: &str, normalize_comments: bool) -> CommentStyle {
110     if !normalize_comments {
111         if orig.starts_with("/**") && !orig.starts_with("/**/") {
112             CommentStyle::DoubleBullet
113         } else if orig.starts_with("/*!") {
114             CommentStyle::Exclamation
115         } else if orig.starts_with("/*") {
116             CommentStyle::SingleBullet
117         } else if orig.starts_with("///") && orig.chars().nth(3).map_or(true, |c| c != '/') {
118             CommentStyle::TripleSlash
119         } else if orig.starts_with("//!") {
120             CommentStyle::Doc
121         } else if is_custom_comment(orig) {
122             CommentStyle::Custom(custom_opener(orig))
123         } else {
124             CommentStyle::DoubleSlash
125         }
126     } else if (orig.starts_with("///") && orig.chars().nth(3).map_or(true, |c| c != '/')) ||
127         (orig.starts_with("/**") && !orig.starts_with("/**/"))
128     {
129         CommentStyle::TripleSlash
130     } else if orig.starts_with("//!") || orig.starts_with("/*!") {
131         CommentStyle::Doc
132     } else if is_custom_comment(orig) {
133         CommentStyle::Custom(custom_opener(orig))
134     } else {
135         CommentStyle::DoubleSlash
136     }
137 }
138
139 pub fn combine_strs_with_missing_comments(
140     context: &RewriteContext,
141     prev_str: &str,
142     next_str: &str,
143     span: Span,
144     shape: Shape,
145     allow_extend: bool,
146 ) -> Option<String> {
147     let mut allow_one_line = !prev_str.contains('\n') && !next_str.contains('\n');
148     let first_sep = if prev_str.is_empty() || next_str.is_empty() {
149         ""
150     } else {
151         " "
152     };
153     let mut one_line_width =
154         last_line_width(prev_str) + first_line_width(next_str) + first_sep.len();
155
156     let indent_str = shape.indent.to_string(context.config);
157     let missing_comment = try_opt!(rewrite_missing_comment(span, shape, context));
158
159     if missing_comment.is_empty() {
160         if allow_extend && prev_str.len() + first_sep.len() + next_str.len() <= shape.width {
161             return Some(format!("{}{}{}", prev_str, first_sep, next_str));
162         } else {
163             let sep = if prev_str.is_empty() {
164                 String::new()
165             } else {
166                 String::from("\n") + &indent_str
167             };
168             return Some(format!("{}{}{}", prev_str, sep, next_str));
169         }
170     }
171
172     // We have a missing comment between the first expression and the second expression.
173
174     // Peek the the original source code and find out whether there is a newline between the first
175     // expression and the second expression or the missing comment. We will preserve the orginal
176     // layout whenever possible.
177     let original_snippet = context.snippet(span);
178     let prefer_same_line = if let Some(pos) = original_snippet.chars().position(|c| c == '/') {
179         !original_snippet[..pos].contains('\n')
180     } else {
181         !original_snippet.contains('\n')
182     };
183
184     one_line_width -= first_sep.len();
185     let first_sep = if prev_str.is_empty() || missing_comment.is_empty() {
186         String::new()
187     } else {
188         let one_line_width = last_line_width(prev_str) + first_line_width(&missing_comment) + 1;
189         if prefer_same_line && one_line_width <= shape.width {
190             String::from(" ")
191         } else {
192             format!("\n{}", indent_str)
193         }
194     };
195     let second_sep = if missing_comment.is_empty() || next_str.is_empty() {
196         String::new()
197     } else {
198         if missing_comment.starts_with("//") {
199             format!("\n{}", indent_str)
200         } else {
201             one_line_width += missing_comment.len() + first_sep.len() + 1;
202             allow_one_line &= !missing_comment.starts_with("//") && !missing_comment.contains('\n');
203             if prefer_same_line && allow_one_line && one_line_width <= shape.width {
204                 String::from(" ")
205             } else {
206                 format!("\n{}", indent_str)
207             }
208         }
209     };
210     Some(format!(
211         "{}{}{}{}{}",
212         prev_str,
213         first_sep,
214         missing_comment,
215         second_sep,
216         next_str,
217     ))
218 }
219
220 pub fn rewrite_comment(
221     orig: &str,
222     block_style: bool,
223     shape: Shape,
224     config: &Config,
225 ) -> Option<String> {
226     // If there are lines without a starting sigil, we won't format them correctly
227     // so in that case we won't even re-align (if !config.normalize_comments()) and
228     // we should stop now.
229     let num_bare_lines = orig.lines()
230         .map(|line| line.trim())
231         .filter(|l| {
232             !(l.starts_with('*') || l.starts_with("//") || l.starts_with("/*"))
233         })
234         .count();
235     if num_bare_lines > 0 && !config.normalize_comments() {
236         return Some(orig.to_owned());
237     }
238     if !config.normalize_comments() && !config.wrap_comments() {
239         return light_rewrite_comment(orig, shape.indent, config);
240     }
241
242     identify_comment(orig, block_style, shape, config)
243 }
244
245 fn identify_comment(
246     orig: &str,
247     block_style: bool,
248     shape: Shape,
249     config: &Config,
250 ) -> Option<String> {
251     let style = comment_style(orig, false);
252     let first_group = orig.lines()
253         .take_while(|l| style.line_with_same_comment_style(l, false))
254         .collect::<Vec<_>>()
255         .join("\n");
256     let rest = orig.lines()
257         .skip(first_group.lines().count())
258         .collect::<Vec<_>>()
259         .join("\n");
260
261     let first_group_str = try_opt!(rewrite_comment_inner(
262         &first_group,
263         block_style,
264         style,
265         shape,
266         config,
267     ));
268     if rest.is_empty() {
269         Some(first_group_str)
270     } else {
271         identify_comment(&rest, block_style, shape, config).map(|rest_str| {
272             format!(
273                 "{}\n{}{}",
274                 first_group_str,
275                 shape.indent.to_string(config),
276                 rest_str
277             )
278         })
279     }
280 }
281
282 fn rewrite_comment_inner(
283     orig: &str,
284     block_style: bool,
285     style: CommentStyle,
286     shape: Shape,
287     config: &Config,
288 ) -> Option<String> {
289     let (opener, closer, line_start) = if block_style {
290         CommentStyle::SingleBullet.to_str_tuplet()
291     } else {
292         comment_style(orig, config.normalize_comments()).to_str_tuplet()
293     };
294
295     let max_chars = shape
296         .width
297         .checked_sub(closer.len() + opener.len())
298         .unwrap_or(1);
299     let indent_str = shape.indent.to_string(config);
300     let fmt = StringFormat {
301         opener: "",
302         closer: "",
303         line_start: line_start,
304         line_end: "",
305         shape: Shape::legacy(max_chars, shape.indent + (opener.len() - line_start.len())),
306         trim_end: true,
307         config: config,
308     };
309
310     let line_breaks = orig.trim_right().chars().filter(|&c| c == '\n').count();
311     let lines = orig.lines()
312         .enumerate()
313         .map(|(i, mut line)| {
314             line = line.trim();
315             // Drop old closer.
316             if i == line_breaks && line.ends_with("*/") && !line.starts_with("//") {
317                 line = &line[..(line.len() - 2)].trim_right();
318             }
319
320             line
321         })
322         .map(|s| left_trim_comment_line(s, &style))
323         .map(|line| if orig.starts_with("/*") && line_breaks == 0 {
324             line.trim_left()
325         } else {
326             line
327         });
328
329     let mut result = opener.to_owned();
330     for line in lines {
331         if result == opener {
332             if line.is_empty() {
333                 continue;
334             }
335         } else {
336             result.push('\n');
337             result.push_str(&indent_str);
338             result.push_str(line_start);
339         }
340
341         if config.wrap_comments() && line.len() > max_chars {
342             let rewrite = rewrite_string(line, &fmt).unwrap_or(line.to_owned());
343             result.push_str(&rewrite);
344         } else {
345             if line.is_empty() && result.ends_with(' ') {
346                 // Remove space if this is an empty comment or a doc comment.
347                 result.pop();
348             }
349             result.push_str(line);
350         }
351     }
352
353     result.push_str(closer);
354     if result == opener && result.ends_with(' ') {
355         // Trailing space.
356         result.pop();
357     }
358
359     Some(result)
360 }
361
362 /// Given the span, rewrite the missing comment inside it if available.
363 /// Note that the given span must only include comments (or leading/trailing whitespaces).
364 pub fn rewrite_missing_comment(
365     span: Span,
366     shape: Shape,
367     context: &RewriteContext,
368 ) -> Option<String> {
369     let missing_snippet = context.snippet(span);
370     let trimmed_snippet = missing_snippet.trim();
371     if !trimmed_snippet.is_empty() {
372         rewrite_comment(trimmed_snippet, false, shape, context.config)
373     } else {
374         Some(String::new())
375     }
376 }
377
378 /// Recover the missing comments in the specified span, if available.
379 /// The layout of the comments will be preserved as long as it does not break the code
380 /// and its total width does not exceed the max width.
381 pub fn recover_missing_comment_in_span(
382     span: Span,
383     shape: Shape,
384     context: &RewriteContext,
385     used_width: usize,
386 ) -> Option<String> {
387     let missing_comment = try_opt!(rewrite_missing_comment(span, shape, context));
388     if missing_comment.is_empty() {
389         Some(String::new())
390     } else {
391         let missing_snippet = context.snippet(span);
392         let pos = missing_snippet.chars().position(|c| c == '/').unwrap_or(0);
393         // 1 = ` `
394         let total_width = missing_comment.len() + used_width + 1;
395         let force_new_line_before_comment =
396             missing_snippet[..pos].contains('\n') || total_width > context.config.max_width();
397         let sep = if force_new_line_before_comment {
398             format!("\n{}", shape.indent.to_string(context.config))
399         } else {
400             String::from(" ")
401         };
402         Some(format!("{}{}", sep, missing_comment))
403     }
404 }
405
406 /// Trims whitespace and aligns to indent, but otherwise does not change comments.
407 fn light_rewrite_comment(orig: &str, offset: Indent, config: &Config) -> Option<String> {
408     let lines: Vec<&str> = orig.lines()
409         .map(|l| {
410             // This is basically just l.trim(), but in the case that a line starts
411             // with `*` we want to leave one space before it, so it aligns with the
412             // `*` in `/*`.
413             let first_non_whitespace = l.find(|c| !char::is_whitespace(c));
414             if let Some(fnw) = first_non_whitespace {
415                 if l.as_bytes()[fnw] == '*' as u8 && fnw > 0 {
416                     &l[fnw - 1..]
417                 } else {
418                     &l[fnw..]
419                 }
420             } else {
421                 ""
422             }.trim_right()
423         })
424         .collect();
425     Some(lines.join(&format!("\n{}", offset.to_string(config))))
426 }
427
428 /// Trims comment characters and possibly a single space from the left of a string.
429 /// Does not trim all whitespace.
430 fn left_trim_comment_line<'a>(line: &'a str, style: &CommentStyle) -> &'a str {
431     if line.starts_with("//! ") || line.starts_with("/// ") || line.starts_with("/*! ") ||
432         line.starts_with("/** ")
433     {
434         &line[4..]
435     } else if let &CommentStyle::Custom(opener) = style {
436         if line.starts_with(opener) {
437             &line[opener.len()..]
438         } else {
439             &line[opener.trim_right().len()..]
440         }
441     } else if line.starts_with("/* ") || line.starts_with("// ") || line.starts_with("//!") ||
442         line.starts_with("///") || line.starts_with("** ") ||
443         line.starts_with("/*!") ||
444         (line.starts_with("/**") && !line.starts_with("/**/"))
445     {
446         &line[3..]
447     } else if line.starts_with("/*") || line.starts_with("* ") || line.starts_with("//") ||
448         line.starts_with("**")
449     {
450         &line[2..]
451     } else if line.starts_with('*') {
452         &line[1..]
453     } else {
454         line
455     }
456 }
457
458 pub trait FindUncommented {
459     fn find_uncommented(&self, pat: &str) -> Option<usize>;
460 }
461
462 impl FindUncommented for str {
463     fn find_uncommented(&self, pat: &str) -> Option<usize> {
464         let mut needle_iter = pat.chars();
465         for (kind, (i, b)) in CharClasses::new(self.char_indices()) {
466             match needle_iter.next() {
467                 None => {
468                     return Some(i - pat.len());
469                 }
470                 Some(c) => match kind {
471                     FullCodeCharKind::Normal if b == c => {}
472                     _ => {
473                         needle_iter = pat.chars();
474                     }
475                 },
476             }
477         }
478
479         // Handle case where the pattern is a suffix of the search string
480         match needle_iter.next() {
481             Some(_) => None,
482             None => Some(self.len() - pat.len()),
483         }
484     }
485 }
486
487 // Returns the first byte position after the first comment. The given string
488 // is expected to be prefixed by a comment, including delimiters.
489 // Good: "/* /* inner */ outer */ code();"
490 // Bad:  "code(); // hello\n world!"
491 pub fn find_comment_end(s: &str) -> Option<usize> {
492     let mut iter = CharClasses::new(s.char_indices());
493     for (kind, (i, _c)) in &mut iter {
494         if kind == FullCodeCharKind::Normal {
495             return Some(i);
496         }
497     }
498
499     // Handle case where the comment ends at the end of s.
500     if iter.status == CharClassesStatus::Normal {
501         Some(s.len())
502     } else {
503         None
504     }
505 }
506
507 /// Returns true if text contains any comment.
508 pub fn contains_comment(text: &str) -> bool {
509     CharClasses::new(text.chars()).any(|(kind, _)| kind.is_comment())
510 }
511
512 struct CharClasses<T>
513 where
514     T: Iterator,
515     T::Item: RichChar,
516 {
517     base: iter::Peekable<T>,
518     status: CharClassesStatus,
519 }
520
521 trait RichChar {
522     fn get_char(&self) -> char;
523 }
524
525 impl RichChar for char {
526     fn get_char(&self) -> char {
527         *self
528     }
529 }
530
531 impl RichChar for (usize, char) {
532     fn get_char(&self) -> char {
533         self.1
534     }
535 }
536
537 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
538 enum CharClassesStatus {
539     Normal,
540     LitString,
541     LitStringEscape,
542     LitChar,
543     LitCharEscape,
544     // The u32 is the nesting deepness of the comment
545     BlockComment(u32),
546     // Status when the '/' has been consumed, but not yet the '*', deepness is
547     // the new deepness (after the comment opening).
548     BlockCommentOpening(u32),
549     // Status when the '*' has been consumed, but not yet the '/', deepness is
550     // the new deepness (after the comment closing).
551     BlockCommentClosing(u32),
552     LineComment,
553 }
554
555 /// Distinguish between functional part of code and comments
556 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
557 pub enum CodeCharKind {
558     Normal,
559     Comment,
560 }
561
562 /// Distinguish between functional part of code and comments,
563 /// describing opening and closing of comments for ease when chunking
564 /// code from tagged characters
565 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
566 enum FullCodeCharKind {
567     Normal,
568     /// The first character of a comment, there is only one for a comment (always '/')
569     StartComment,
570     /// Any character inside a comment including the second character of comment
571     /// marks ("//", "/*")
572     InComment,
573     /// Last character of a comment, '\n' for a line comment, '/' for a block comment.
574     EndComment,
575 }
576
577 impl FullCodeCharKind {
578     fn is_comment(&self) -> bool {
579         match *self {
580             FullCodeCharKind::Normal => false,
581             FullCodeCharKind::StartComment |
582             FullCodeCharKind::InComment |
583             FullCodeCharKind::EndComment => true,
584         }
585     }
586
587     fn to_codecharkind(&self) -> CodeCharKind {
588         if self.is_comment() {
589             CodeCharKind::Comment
590         } else {
591             CodeCharKind::Normal
592         }
593     }
594 }
595
596 impl<T> CharClasses<T>
597 where
598     T: Iterator,
599     T::Item: RichChar,
600 {
601     fn new(base: T) -> CharClasses<T> {
602         CharClasses {
603             base: base.peekable(),
604             status: CharClassesStatus::Normal,
605         }
606     }
607 }
608
609 impl<T> Iterator for CharClasses<T>
610 where
611     T: Iterator,
612     T::Item: RichChar,
613 {
614     type Item = (FullCodeCharKind, T::Item);
615
616     fn next(&mut self) -> Option<(FullCodeCharKind, T::Item)> {
617         let item = try_opt!(self.base.next());
618         let chr = item.get_char();
619         self.status = match self.status {
620             CharClassesStatus::LitString => match chr {
621                 '"' => CharClassesStatus::Normal,
622                 '\\' => CharClassesStatus::LitStringEscape,
623                 _ => CharClassesStatus::LitString,
624             },
625             CharClassesStatus::LitStringEscape => CharClassesStatus::LitString,
626             CharClassesStatus::LitChar => match chr {
627                 '\\' => CharClassesStatus::LitCharEscape,
628                 '\'' => CharClassesStatus::Normal,
629                 _ => CharClassesStatus::LitChar,
630             },
631             CharClassesStatus::LitCharEscape => CharClassesStatus::LitChar,
632             CharClassesStatus::Normal => match chr {
633                 '"' => CharClassesStatus::LitString,
634                 '\'' => CharClassesStatus::LitChar,
635                 '/' => match self.base.peek() {
636                     Some(next) if next.get_char() == '*' => {
637                         self.status = CharClassesStatus::BlockCommentOpening(1);
638                         return Some((FullCodeCharKind::StartComment, item));
639                     }
640                     Some(next) if next.get_char() == '/' => {
641                         self.status = CharClassesStatus::LineComment;
642                         return Some((FullCodeCharKind::StartComment, item));
643                     }
644                     _ => CharClassesStatus::Normal,
645                 },
646                 _ => CharClassesStatus::Normal,
647             },
648             CharClassesStatus::BlockComment(deepness) => {
649                 assert!(deepness != 0);
650                 self.status = match self.base.peek() {
651                     Some(next) if next.get_char() == '/' && chr == '*' => {
652                         CharClassesStatus::BlockCommentClosing(deepness - 1)
653                     }
654                     Some(next) if next.get_char() == '*' && chr == '/' => {
655                         CharClassesStatus::BlockCommentOpening(deepness + 1)
656                     }
657                     _ => CharClassesStatus::BlockComment(deepness),
658                 };
659                 return Some((FullCodeCharKind::InComment, item));
660             }
661             CharClassesStatus::BlockCommentOpening(deepness) => {
662                 assert_eq!(chr, '*');
663                 self.status = CharClassesStatus::BlockComment(deepness);
664                 return Some((FullCodeCharKind::InComment, item));
665             }
666             CharClassesStatus::BlockCommentClosing(deepness) => {
667                 assert_eq!(chr, '/');
668                 if deepness == 0 {
669                     self.status = CharClassesStatus::Normal;
670                     return Some((FullCodeCharKind::EndComment, item));
671                 } else {
672                     self.status = CharClassesStatus::BlockComment(deepness);
673                     return Some((FullCodeCharKind::InComment, item));
674                 }
675             }
676             CharClassesStatus::LineComment => match chr {
677                 '\n' => {
678                     self.status = CharClassesStatus::Normal;
679                     return Some((FullCodeCharKind::EndComment, item));
680                 }
681                 _ => {
682                     self.status = CharClassesStatus::LineComment;
683                     return Some((FullCodeCharKind::InComment, item));
684                 }
685             },
686         };
687         Some((FullCodeCharKind::Normal, item))
688     }
689 }
690
691 /// Iterator over functional and commented parts of a string. Any part of a string is either
692 /// functional code, either *one* block comment, either *one* line comment. Whitespace between
693 /// comments is functional code. Line comments contain their ending newlines.
694 struct UngroupedCommentCodeSlices<'a> {
695     slice: &'a str,
696     iter: iter::Peekable<CharClasses<std::str::CharIndices<'a>>>,
697 }
698
699 impl<'a> UngroupedCommentCodeSlices<'a> {
700     fn new(code: &'a str) -> UngroupedCommentCodeSlices<'a> {
701         UngroupedCommentCodeSlices {
702             slice: code,
703             iter: CharClasses::new(code.char_indices()).peekable(),
704         }
705     }
706 }
707
708 impl<'a> Iterator for UngroupedCommentCodeSlices<'a> {
709     type Item = (CodeCharKind, usize, &'a str);
710
711     fn next(&mut self) -> Option<Self::Item> {
712         let (kind, (start_idx, _)) = try_opt!(self.iter.next());
713         match kind {
714             FullCodeCharKind::Normal => {
715                 // Consume all the Normal code
716                 while let Some(&(FullCodeCharKind::Normal, (_, _))) = self.iter.peek() {
717                     let _ = self.iter.next();
718                 }
719             }
720             FullCodeCharKind::StartComment => {
721                 // Consume the whole comment
722                 while let Some((FullCodeCharKind::InComment, (_, _))) = self.iter.next() {}
723             }
724             _ => panic!(),
725         }
726         let slice = match self.iter.peek() {
727             Some(&(_, (end_idx, _))) => &self.slice[start_idx..end_idx],
728             None => &self.slice[start_idx..],
729         };
730         Some((
731             if kind.is_comment() {
732                 CodeCharKind::Comment
733             } else {
734                 CodeCharKind::Normal
735             },
736             start_idx,
737             slice,
738         ))
739     }
740 }
741
742
743
744
745 /// Iterator over an alternating sequence of functional and commented parts of
746 /// a string. The first item is always a, possibly zero length, subslice of
747 /// functional text. Line style comments contain their ending newlines.
748 pub struct CommentCodeSlices<'a> {
749     slice: &'a str,
750     last_slice_kind: CodeCharKind,
751     last_slice_end: usize,
752 }
753
754 impl<'a> CommentCodeSlices<'a> {
755     pub fn new(slice: &'a str) -> CommentCodeSlices<'a> {
756         CommentCodeSlices {
757             slice: slice,
758             last_slice_kind: CodeCharKind::Comment,
759             last_slice_end: 0,
760         }
761     }
762 }
763
764 impl<'a> Iterator for CommentCodeSlices<'a> {
765     type Item = (CodeCharKind, usize, &'a str);
766
767     fn next(&mut self) -> Option<Self::Item> {
768         if self.last_slice_end == self.slice.len() {
769             return None;
770         }
771
772         let mut sub_slice_end = self.last_slice_end;
773         let mut first_whitespace = None;
774         let subslice = &self.slice[self.last_slice_end..];
775         let mut iter = CharClasses::new(subslice.char_indices());
776
777         for (kind, (i, c)) in &mut iter {
778             let is_comment_connector = self.last_slice_kind == CodeCharKind::Normal &&
779                 &subslice[..2] == "//" &&
780                 [' ', '\t'].contains(&c);
781
782             if is_comment_connector && first_whitespace.is_none() {
783                 first_whitespace = Some(i);
784             }
785
786             if kind.to_codecharkind() == self.last_slice_kind && !is_comment_connector {
787                 let last_index = match first_whitespace {
788                     Some(j) => j,
789                     None => i,
790                 };
791                 sub_slice_end = self.last_slice_end + last_index;
792                 break;
793             }
794
795             if !is_comment_connector {
796                 first_whitespace = None;
797             }
798         }
799
800         if let (None, true) = (iter.next(), sub_slice_end == self.last_slice_end) {
801             // This was the last subslice.
802             sub_slice_end = match first_whitespace {
803                 Some(i) => self.last_slice_end + i,
804                 None => self.slice.len(),
805             };
806         }
807
808         let kind = match self.last_slice_kind {
809             CodeCharKind::Comment => CodeCharKind::Normal,
810             CodeCharKind::Normal => CodeCharKind::Comment,
811         };
812         let res = (
813             kind,
814             self.last_slice_end,
815             &self.slice[self.last_slice_end..sub_slice_end],
816         );
817         self.last_slice_end = sub_slice_end;
818         self.last_slice_kind = kind;
819
820         Some(res)
821     }
822 }
823
824 /// Checks is `new` didn't miss any comment from `span`, if it removed any, return previous text
825 /// (if it fits in the width/offset, else return None), else return `new`
826 pub fn recover_comment_removed(
827     new: String,
828     span: Span,
829     context: &RewriteContext,
830     shape: Shape,
831 ) -> Option<String> {
832     let snippet = context.snippet(span);
833     if changed_comment_content(&snippet, &new) {
834         // We missed some comments
835         // Keep previous formatting if it satisfies the constrains
836         wrap_str(snippet, context.config.max_width(), shape)
837     } else {
838         Some(new)
839     }
840 }
841
842 /// Return true if the two strings of code have the same payload of comments.
843 /// The payload of comments is everything in the string except:
844 ///     - actual code (not comments)
845 ///     - comment start/end marks
846 ///     - whitespace
847 ///     - '*' at the beginning of lines in block comments
848 fn changed_comment_content(orig: &str, new: &str) -> bool {
849     // Cannot write this as a fn since we cannot return types containing closures
850     let code_comment_content = |code| {
851         let slices = UngroupedCommentCodeSlices::new(code);
852         slices
853             .filter(|&(ref kind, _, _)| *kind == CodeCharKind::Comment)
854             .flat_map(|(_, _, s)| CommentReducer::new(s))
855     };
856     let res = code_comment_content(orig).ne(code_comment_content(new));
857     debug!(
858         "comment::changed_comment_content: {}\norig: '{}'\nnew: '{}'\nraw_old: {}\nraw_new: {}",
859         res,
860         orig,
861         new,
862         code_comment_content(orig).collect::<String>(),
863         code_comment_content(new).collect::<String>()
864     );
865     res
866 }
867
868
869 /// Iterator over the 'payload' characters of a comment.
870 /// It skips whitespace, comment start/end marks, and '*' at the beginning of lines.
871 /// The comment must be one comment, ie not more than one start mark (no multiple line comments,
872 /// for example).
873 struct CommentReducer<'a> {
874     is_block: bool,
875     at_start_line: bool,
876     iter: std::str::Chars<'a>,
877 }
878
879 impl<'a> CommentReducer<'a> {
880     fn new(comment: &'a str) -> CommentReducer<'a> {
881         let is_block = comment.starts_with("/*");
882         let comment = remove_comment_header(comment);
883         CommentReducer {
884             is_block: is_block,
885             at_start_line: false, // There are no supplementary '*' on the first line
886             iter: comment.chars(),
887         }
888     }
889 }
890
891 impl<'a> Iterator for CommentReducer<'a> {
892     type Item = char;
893     fn next(&mut self) -> Option<Self::Item> {
894         loop {
895             let mut c = try_opt!(self.iter.next());
896             if self.is_block && self.at_start_line {
897                 while c.is_whitespace() {
898                     c = try_opt!(self.iter.next());
899                 }
900                 // Ignore leading '*'
901                 if c == '*' {
902                     c = try_opt!(self.iter.next());
903                 }
904             } else {
905                 if c == '\n' {
906                     self.at_start_line = true;
907                 }
908             }
909             if !c.is_whitespace() {
910                 return Some(c);
911             }
912         }
913     }
914 }
915
916
917 fn remove_comment_header(comment: &str) -> &str {
918     if comment.starts_with("///") || comment.starts_with("//!") {
919         &comment[3..]
920     } else if comment.starts_with("//") {
921         &comment[2..]
922     } else if (comment.starts_with("/**") && !comment.starts_with("/**/")) ||
923         comment.starts_with("/*!")
924     {
925         &comment[3..comment.len() - 2]
926     } else {
927         assert!(
928             comment.starts_with("/*"),
929             format!("string '{}' is not a comment", comment)
930         );
931         &comment[2..comment.len() - 2]
932     }
933 }
934
935 #[cfg(test)]
936 mod test {
937     use super::{contains_comment, rewrite_comment, CharClasses, CodeCharKind, CommentCodeSlices,
938                 FindUncommented, FullCodeCharKind};
939     use {Indent, Shape};
940
941     #[test]
942     fn char_classes() {
943         let mut iter = CharClasses::new("//\n\n".chars());
944
945         assert_eq!((FullCodeCharKind::StartComment, '/'), iter.next().unwrap());
946         assert_eq!((FullCodeCharKind::InComment, '/'), iter.next().unwrap());
947         assert_eq!((FullCodeCharKind::EndComment, '\n'), iter.next().unwrap());
948         assert_eq!((FullCodeCharKind::Normal, '\n'), iter.next().unwrap());
949         assert_eq!(None, iter.next());
950     }
951
952     #[test]
953     fn comment_code_slices() {
954         let input = "code(); /* test */ 1 + 1";
955         let mut iter = CommentCodeSlices::new(input);
956
957         assert_eq!((CodeCharKind::Normal, 0, "code(); "), iter.next().unwrap());
958         assert_eq!(
959             (CodeCharKind::Comment, 8, "/* test */"),
960             iter.next().unwrap()
961         );
962         assert_eq!((CodeCharKind::Normal, 18, " 1 + 1"), iter.next().unwrap());
963         assert_eq!(None, iter.next());
964     }
965
966     #[test]
967     fn comment_code_slices_two() {
968         let input = "// comment\n    test();";
969         let mut iter = CommentCodeSlices::new(input);
970
971         assert_eq!((CodeCharKind::Normal, 0, ""), iter.next().unwrap());
972         assert_eq!(
973             (CodeCharKind::Comment, 0, "// comment\n"),
974             iter.next().unwrap()
975         );
976         assert_eq!(
977             (CodeCharKind::Normal, 11, "    test();"),
978             iter.next().unwrap()
979         );
980         assert_eq!(None, iter.next());
981     }
982
983     #[test]
984     fn comment_code_slices_three() {
985         let input = "1 // comment\n    // comment2\n\n";
986         let mut iter = CommentCodeSlices::new(input);
987
988         assert_eq!((CodeCharKind::Normal, 0, "1 "), iter.next().unwrap());
989         assert_eq!(
990             (CodeCharKind::Comment, 2, "// comment\n    // comment2\n"),
991             iter.next().unwrap()
992         );
993         assert_eq!((CodeCharKind::Normal, 29, "\n"), iter.next().unwrap());
994         assert_eq!(None, iter.next());
995     }
996
997     #[test]
998     #[cfg_attr(rustfmt, rustfmt_skip)]
999     fn format_comments() {
1000         let mut config: ::config::Config = Default::default();
1001         config.set().wrap_comments(true);
1002         config.set().normalize_comments(true);
1003
1004         let comment = rewrite_comment(" //test",
1005                                       true,
1006                                       Shape::legacy(100, Indent::new(0, 100)),
1007                                       &config).unwrap();
1008         assert_eq!("/* test */", comment);
1009
1010         let comment = rewrite_comment("// comment on a",
1011                                       false,
1012                                       Shape::legacy(10, Indent::empty()),
1013                                       &config).unwrap();
1014         assert_eq!("// comment\n// on a", comment);
1015
1016         let comment = rewrite_comment("//  A multi line comment\n             // between args.",
1017                                       false,
1018                                       Shape::legacy(60, Indent::new(0, 12)),
1019                                       &config).unwrap();
1020         assert_eq!("//  A multi line comment\n            // between args.", comment);
1021
1022         let input = "// comment";
1023         let expected =
1024             "/* comment */";
1025         let comment = rewrite_comment(input,
1026                                       true,
1027                                       Shape::legacy(9, Indent::new(0, 69)),
1028                                       &config).unwrap();
1029         assert_eq!(expected, comment);
1030
1031         let comment = rewrite_comment("/*   trimmed    */",
1032                                       true,
1033                                       Shape::legacy(100, Indent::new(0, 100)),
1034                                       &config).unwrap();
1035         assert_eq!("/* trimmed */", comment);
1036     }
1037
1038     // This is probably intended to be a non-test fn, but it is not used. I'm
1039     // keeping it around unless it helps us test stuff.
1040     fn uncommented(text: &str) -> String {
1041         CharClasses::new(text.chars())
1042             .filter_map(|(s, c)| match s {
1043                 FullCodeCharKind::Normal => Some(c),
1044                 _ => None,
1045             })
1046             .collect()
1047     }
1048
1049     #[test]
1050     fn test_uncommented() {
1051         assert_eq!(&uncommented("abc/*...*/"), "abc");
1052         assert_eq!(
1053             &uncommented("// .... /* \n../* /* *** / */ */a/* // */c\n"),
1054             "..ac\n"
1055         );
1056         assert_eq!(&uncommented("abc \" /* */\" qsdf"), "abc \" /* */\" qsdf");
1057     }
1058
1059     #[test]
1060     fn test_contains_comment() {
1061         assert_eq!(contains_comment("abc"), false);
1062         assert_eq!(contains_comment("abc // qsdf"), true);
1063         assert_eq!(contains_comment("abc /* kqsdf"), true);
1064         assert_eq!(contains_comment("abc \" /* */\" qsdf"), false);
1065     }
1066
1067     #[test]
1068     fn test_find_uncommented() {
1069         fn check(haystack: &str, needle: &str, expected: Option<usize>) {
1070             assert_eq!(expected, haystack.find_uncommented(needle));
1071         }
1072
1073         check("/*/ */test", "test", Some(6));
1074         check("//test\ntest", "test", Some(7));
1075         check("/* comment only */", "whatever", None);
1076         check(
1077             "/* comment */ some text /* more commentary */ result",
1078             "result",
1079             Some(46),
1080         );
1081         check("sup // sup", "p", Some(2));
1082         check("sup", "x", None);
1083         check(r#"π? /**/ π is nice!"#, r#"π is nice"#, Some(9));
1084         check("/*sup yo? \n sup*/ sup", "p", Some(20));
1085         check("hel/*lohello*/lo", "hello", None);
1086         check("acb", "ab", None);
1087         check(",/*A*/ ", ",", Some(0));
1088         check("abc", "abc", Some(0));
1089         check("/* abc */", "abc", None);
1090         check("/**/abc/* */", "abc", Some(4));
1091         check("\"/* abc */\"", "abc", Some(4));
1092         check("\"/* abc", "abc", Some(4));
1093     }
1094 }