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