]> git.lizzy.rs Git - rust.git/blob - src/comment.rs
Merge pull request #1578 from topecongiro/poor/macro
[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::{StringFormat, rewrite_string};
21 use utils::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 pub fn rewrite_comment(orig: &str,
36                        block_style: bool,
37                        shape: Shape,
38                        config: &Config)
39                        -> Option<String> {
40     // If there are lines without a starting sigil, we won't format them correctly
41     // so in that case we won't even re-align (if !config.normalize_comments()) and
42     // we should stop now.
43     let num_bare_lines = orig.lines()
44         .map(|line| line.trim())
45         .filter(|l| !(l.starts_with('*') || l.starts_with("//") || l.starts_with("/*")))
46         .count();
47     if num_bare_lines > 0 && !config.normalize_comments() {
48         return Some(orig.to_owned());
49     }
50
51     if !config.normalize_comments() && !config.wrap_comments() {
52         return light_rewrite_comment(orig, shape.indent, config);
53     }
54
55     let (opener, closer, line_start) = if block_style {
56         ("/* ", " */", " * ")
57     } else if !config.normalize_comments() {
58         if orig.starts_with("/**") && !orig.starts_with("/**/") {
59             ("/** ", " **/", " ** ")
60         } else if orig.starts_with("/*!") {
61             ("/*! ", " */", " * ")
62         } else if orig.starts_with("/*") {
63             ("/* ", " */", " * ")
64         } else if orig.starts_with("///") {
65             ("/// ", "", "/// ")
66         } else if orig.starts_with("//!") {
67             ("//! ", "", "//! ")
68         } else {
69             ("// ", "", "// ")
70         }
71     } else if orig.starts_with("///") || (orig.starts_with("/**") && !orig.starts_with("/**/")) {
72         ("/// ", "", "/// ")
73     } else if orig.starts_with("//!") || orig.starts_with("/*!") {
74         ("//! ", "", "//! ")
75     } else if is_custom_comment(orig) {
76         if orig.chars().nth(3) == Some(' ') {
77             (&orig[0..4], "", &orig[0..4])
78         } else {
79             (&orig[0..3], "", &orig[0..3])
80         }
81     } else {
82         ("// ", "", "// ")
83     };
84
85     let max_chars = shape
86         .width
87         .checked_sub(closer.len() + opener.len())
88         .unwrap_or(1);
89     let indent_str = shape.indent.to_string(config);
90     let fmt = StringFormat {
91         opener: "",
92         closer: "",
93         line_start: line_start,
94         line_end: "",
95         shape: Shape::legacy(max_chars, shape.indent + (opener.len() - line_start.len())),
96         trim_end: true,
97         config: config,
98     };
99
100     let line_breaks = orig.trim_right().chars().filter(|&c| c == '\n').count();
101     let lines = orig.lines()
102         .enumerate()
103         .map(|(i, mut line)| {
104             line = line.trim();
105             // Drop old closer.
106             if i == line_breaks && line.ends_with("*/") && !line.starts_with("//") {
107                 line = &line[..(line.len() - 2)].trim_right();
108             }
109
110             line
111         })
112         .map(left_trim_comment_line)
113         .map(|line| if orig.starts_with("/*") && line_breaks == 0 {
114                  line.trim_left()
115              } else {
116                  line
117              });
118
119     let mut result = opener.to_owned();
120     for line in lines {
121         if result == opener {
122             if line.is_empty() {
123                 continue;
124             }
125         } else {
126             result.push('\n');
127             result.push_str(&indent_str);
128             result.push_str(line_start);
129         }
130
131         if config.wrap_comments() && line.len() > max_chars {
132             let rewrite = rewrite_string(line, &fmt).unwrap_or(line.to_owned());
133             result.push_str(&rewrite);
134         } else {
135             if line.is_empty() {
136                 // Remove space if this is an empty comment or a doc comment.
137                 result.pop();
138             }
139             result.push_str(line);
140         }
141     }
142
143     result.push_str(closer);
144     if result == opener {
145         // Trailing space.
146         result.pop();
147     }
148
149     Some(result)
150 }
151
152 /// Trims whitespace and aligns to indent, but otherwise does not change comments.
153 fn light_rewrite_comment(orig: &str, offset: Indent, config: &Config) -> Option<String> {
154     let lines: Vec<&str> = orig.lines()
155         .map(|l| {
156             // This is basically just l.trim(), but in the case that a line starts
157             // with `*` we want to leave one space before it, so it aligns with the
158             // `*` in `/*`.
159             let first_non_whitespace = l.find(|c| !char::is_whitespace(c));
160             if let Some(fnw) = first_non_whitespace {
161                 if l.as_bytes()[fnw] == '*' as u8 && fnw > 0 {
162                     &l[fnw - 1..]
163                 } else {
164                     &l[fnw..]
165                 }
166             } else {
167                 ""
168             }.trim_right()
169         })
170         .collect();
171     Some(lines.join(&format!("\n{}", offset.to_string(config))))
172 }
173
174 /// Trims comment characters and possibly a single space from the left of a string.
175 /// Does not trim all whitespace.
176 fn left_trim_comment_line(line: &str) -> &str {
177     if line.starts_with("//! ") || line.starts_with("/// ") || line.starts_with("/*! ") ||
178        line.starts_with("/** ") {
179         &line[4..]
180     } else if is_custom_comment(line) {
181         if line.len() > 3 && line.chars().nth(3) == Some(' ') {
182             &line[4..]
183         } else {
184             &line[3..]
185         }
186     } else if line.starts_with("/* ") || line.starts_with("// ") || line.starts_with("//!") ||
187               line.starts_with("///") ||
188               line.starts_with("** ") || line.starts_with("/*!") ||
189               (line.starts_with("/**") && !line.starts_with("/**/")) {
190         &line[3..]
191     } else if line.starts_with("/*") || line.starts_with("* ") || line.starts_with("//") ||
192               line.starts_with("**") {
193         &line[2..]
194     } else if line.starts_with('*') {
195         &line[1..]
196     } else {
197         line
198     }
199 }
200
201 pub trait FindUncommented {
202     fn find_uncommented(&self, pat: &str) -> Option<usize>;
203 }
204
205 impl FindUncommented for str {
206     fn find_uncommented(&self, pat: &str) -> Option<usize> {
207         let mut needle_iter = pat.chars();
208         for (kind, (i, b)) in CharClasses::new(self.char_indices()) {
209             match needle_iter.next() {
210                 None => {
211                     return Some(i - pat.len());
212                 }
213                 Some(c) => {
214                     match kind {
215                         FullCodeCharKind::Normal if b == c => {}
216                         _ => {
217                             needle_iter = pat.chars();
218                         }
219                     }
220                 }
221             }
222         }
223
224         // Handle case where the pattern is a suffix of the search string
225         match needle_iter.next() {
226             Some(_) => None,
227             None => Some(self.len() - pat.len()),
228         }
229     }
230 }
231
232 // Returns the first byte position after the first comment. The given string
233 // is expected to be prefixed by a comment, including delimiters.
234 // Good: "/* /* inner */ outer */ code();"
235 // Bad:  "code(); // hello\n world!"
236 pub fn find_comment_end(s: &str) -> Option<usize> {
237     let mut iter = CharClasses::new(s.char_indices());
238     for (kind, (i, _c)) in &mut iter {
239         if kind == FullCodeCharKind::Normal {
240             return Some(i);
241         }
242     }
243
244     // Handle case where the comment ends at the end of s.
245     if iter.status == CharClassesStatus::Normal {
246         Some(s.len())
247     } else {
248         None
249     }
250 }
251
252 /// Returns true if text contains any comment.
253 pub fn contains_comment(text: &str) -> bool {
254     CharClasses::new(text.chars()).any(|(kind, _)| kind.is_comment())
255 }
256
257 struct CharClasses<T>
258     where T: Iterator,
259           T::Item: RichChar
260 {
261     base: iter::Peekable<T>,
262     status: CharClassesStatus,
263 }
264
265 trait RichChar {
266     fn get_char(&self) -> char;
267 }
268
269 impl RichChar for char {
270     fn get_char(&self) -> char {
271         *self
272     }
273 }
274
275 impl RichChar for (usize, char) {
276     fn get_char(&self) -> char {
277         self.1
278     }
279 }
280
281 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
282 enum CharClassesStatus {
283     Normal,
284     LitString,
285     LitStringEscape,
286     LitChar,
287     LitCharEscape,
288     // The u32 is the nesting deepness of the comment
289     BlockComment(u32),
290     // Status when the '/' has been consumed, but not yet the '*', deepness is
291     // the new deepness (after the comment opening).
292     BlockCommentOpening(u32),
293     // Status when the '*' has been consumed, but not yet the '/', deepness is
294     // the new deepness (after the comment closing).
295     BlockCommentClosing(u32),
296     LineComment,
297 }
298
299 /// Distinguish between functional part of code and comments
300 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
301 pub enum CodeCharKind {
302     Normal,
303     Comment,
304 }
305
306 /// Distinguish between functional part of code and comments,
307 /// describing opening and closing of comments for ease when chunking
308 /// code from tagged characters
309 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
310 enum FullCodeCharKind {
311     Normal,
312     /// The first character of a comment, there is only one for a comment (always '/')
313     StartComment,
314     /// Any character inside a comment including the second character of comment
315     /// marks ("//", "/*")
316     InComment,
317     /// Last character of a comment, '\n' for a line comment, '/' for a block comment.
318     EndComment,
319 }
320
321 impl FullCodeCharKind {
322     fn is_comment(&self) -> bool {
323         match *self {
324             FullCodeCharKind::Normal => false,
325             FullCodeCharKind::StartComment |
326             FullCodeCharKind::InComment |
327             FullCodeCharKind::EndComment => true,
328         }
329     }
330
331     fn to_codecharkind(&self) -> CodeCharKind {
332         if self.is_comment() {
333             CodeCharKind::Comment
334         } else {
335             CodeCharKind::Normal
336         }
337     }
338 }
339
340 impl<T> CharClasses<T>
341     where T: Iterator,
342           T::Item: RichChar
343 {
344     fn new(base: T) -> CharClasses<T> {
345         CharClasses {
346             base: base.peekable(),
347             status: CharClassesStatus::Normal,
348         }
349     }
350 }
351
352 impl<T> Iterator for CharClasses<T>
353     where T: Iterator,
354           T::Item: RichChar
355 {
356     type Item = (FullCodeCharKind, T::Item);
357
358     fn next(&mut self) -> Option<(FullCodeCharKind, T::Item)> {
359         let item = try_opt!(self.base.next());
360         let chr = item.get_char();
361         self.status = match self.status {
362             CharClassesStatus::LitString => {
363                 match chr {
364                     '"' => CharClassesStatus::Normal,
365                     '\\' => CharClassesStatus::LitStringEscape,
366                     _ => CharClassesStatus::LitString,
367                 }
368             }
369             CharClassesStatus::LitStringEscape => CharClassesStatus::LitString,
370             CharClassesStatus::LitChar => {
371                 match chr {
372                     '\\' => CharClassesStatus::LitCharEscape,
373                     '\'' => CharClassesStatus::Normal,
374                     _ => CharClassesStatus::LitChar,
375                 }
376             }
377             CharClassesStatus::LitCharEscape => CharClassesStatus::LitChar,
378             CharClassesStatus::Normal => {
379                 match chr {
380                     '"' => CharClassesStatus::LitString,
381                     '\'' => CharClassesStatus::LitChar,
382                     '/' => {
383                         match self.base.peek() {
384                             Some(next) if next.get_char() == '*' => {
385                                 self.status = CharClassesStatus::BlockCommentOpening(1);
386                                 return Some((FullCodeCharKind::StartComment, item));
387                             }
388                             Some(next) if next.get_char() == '/' => {
389                                 self.status = CharClassesStatus::LineComment;
390                                 return Some((FullCodeCharKind::StartComment, item));
391                             }
392                             _ => CharClassesStatus::Normal,
393                         }
394                     }
395                     _ => CharClassesStatus::Normal,
396                 }
397             }
398             CharClassesStatus::BlockComment(deepness) => {
399                 assert!(deepness != 0);
400                 self.status = match self.base.peek() {
401                     Some(next) if next.get_char() == '/' && chr == '*' => {
402                         CharClassesStatus::BlockCommentClosing(deepness - 1)
403                     }
404                     Some(next) if next.get_char() == '*' && chr == '/' => {
405                         CharClassesStatus::BlockCommentOpening(deepness + 1)
406                     }
407                     _ => CharClassesStatus::BlockComment(deepness),
408                 };
409                 return Some((FullCodeCharKind::InComment, item));
410             }
411             CharClassesStatus::BlockCommentOpening(deepness) => {
412                 assert_eq!(chr, '*');
413                 self.status = CharClassesStatus::BlockComment(deepness);
414                 return Some((FullCodeCharKind::InComment, item));
415             }
416             CharClassesStatus::BlockCommentClosing(deepness) => {
417                 assert_eq!(chr, '/');
418                 if deepness == 0 {
419                     self.status = CharClassesStatus::Normal;
420                     return Some((FullCodeCharKind::EndComment, item));
421                 } else {
422                     self.status = CharClassesStatus::BlockComment(deepness);
423                     return Some((FullCodeCharKind::InComment, item));
424                 }
425             }
426             CharClassesStatus::LineComment => {
427                 match chr {
428                     '\n' => {
429                         self.status = CharClassesStatus::Normal;
430                         return Some((FullCodeCharKind::EndComment, item));
431                     }
432                     _ => {
433                         self.status = CharClassesStatus::LineComment;
434                         return Some((FullCodeCharKind::InComment, item));
435                     }
436                 }
437             }
438         };
439         Some((FullCodeCharKind::Normal, item))
440     }
441 }
442
443 /// Iterator over functional and commented parts of a string. Any part of a string is either
444 /// functional code, either *one* block comment, either *one* line comment. Whitespace between
445 /// comments is functional code. Line comments contain their ending newlines.
446 struct UngroupedCommentCodeSlices<'a> {
447     slice: &'a str,
448     iter: iter::Peekable<CharClasses<std::str::CharIndices<'a>>>,
449 }
450
451 impl<'a> UngroupedCommentCodeSlices<'a> {
452     fn new(code: &'a str) -> UngroupedCommentCodeSlices<'a> {
453         UngroupedCommentCodeSlices {
454             slice: code,
455             iter: CharClasses::new(code.char_indices()).peekable(),
456         }
457     }
458 }
459
460 impl<'a> Iterator for UngroupedCommentCodeSlices<'a> {
461     type Item = (CodeCharKind, usize, &'a str);
462
463     fn next(&mut self) -> Option<Self::Item> {
464         let (kind, (start_idx, _)) = try_opt!(self.iter.next());
465         match kind {
466             FullCodeCharKind::Normal => {
467                 // Consume all the Normal code
468                 while let Some(&(FullCodeCharKind::Normal, (_, _))) = self.iter.peek() {
469                     let _ = self.iter.next();
470                 }
471             }
472             FullCodeCharKind::StartComment => {
473                 // Consume the whole comment
474                 while let Some((FullCodeCharKind::InComment, (_, _))) = self.iter.next() {}
475             }
476             _ => panic!(),
477         }
478         let slice = match self.iter.peek() {
479             Some(&(_, (end_idx, _))) => &self.slice[start_idx..end_idx],
480             None => &self.slice[start_idx..],
481         };
482         Some((if kind.is_comment() {
483                   CodeCharKind::Comment
484               } else {
485                   CodeCharKind::Normal
486               },
487               start_idx,
488               slice))
489     }
490 }
491
492
493
494
495 /// Iterator over an alternating sequence of functional and commented parts of
496 /// a string. The first item is always a, possibly zero length, subslice of
497 /// functional text. Line style comments contain their ending newlines.
498 pub struct CommentCodeSlices<'a> {
499     slice: &'a str,
500     last_slice_kind: CodeCharKind,
501     last_slice_end: usize,
502 }
503
504 impl<'a> CommentCodeSlices<'a> {
505     pub fn new(slice: &'a str) -> CommentCodeSlices<'a> {
506         CommentCodeSlices {
507             slice: slice,
508             last_slice_kind: CodeCharKind::Comment,
509             last_slice_end: 0,
510         }
511     }
512 }
513
514 impl<'a> Iterator for CommentCodeSlices<'a> {
515     type Item = (CodeCharKind, usize, &'a str);
516
517     fn next(&mut self) -> Option<Self::Item> {
518         if self.last_slice_end == self.slice.len() {
519             return None;
520         }
521
522         let mut sub_slice_end = self.last_slice_end;
523         let mut first_whitespace = None;
524         let subslice = &self.slice[self.last_slice_end..];
525         let mut iter = CharClasses::new(subslice.char_indices());
526
527         for (kind, (i, c)) in &mut iter {
528             let is_comment_connector = self.last_slice_kind == CodeCharKind::Normal &&
529                                        &subslice[..2] == "//" &&
530                                        [' ', '\t'].contains(&c);
531
532             if is_comment_connector && first_whitespace.is_none() {
533                 first_whitespace = Some(i);
534             }
535
536             if kind.to_codecharkind() == self.last_slice_kind && !is_comment_connector {
537                 let last_index = match first_whitespace {
538                     Some(j) => j,
539                     None => i,
540                 };
541                 sub_slice_end = self.last_slice_end + last_index;
542                 break;
543             }
544
545             if !is_comment_connector {
546                 first_whitespace = None;
547             }
548         }
549
550         if let (None, true) = (iter.next(), sub_slice_end == self.last_slice_end) {
551             // This was the last subslice.
552             sub_slice_end = match first_whitespace {
553                 Some(i) => self.last_slice_end + i,
554                 None => self.slice.len(),
555             };
556         }
557
558         let kind = match self.last_slice_kind {
559             CodeCharKind::Comment => CodeCharKind::Normal,
560             CodeCharKind::Normal => CodeCharKind::Comment,
561         };
562         let res = (kind, self.last_slice_end, &self.slice[self.last_slice_end..sub_slice_end]);
563         self.last_slice_end = sub_slice_end;
564         self.last_slice_kind = kind;
565
566         Some(res)
567     }
568 }
569
570 /// Checks is `new` didn't miss any comment from `span`, if it removed any, return previous text
571 /// (if it fits in the width/offset, else return None), else return `new`
572 pub fn recover_comment_removed(new: String,
573                                span: Span,
574                                context: &RewriteContext,
575                                shape: Shape)
576                                -> Option<String> {
577     let snippet = context.snippet(span);
578     if changed_comment_content(&snippet, &new) {
579         // We missed some comments
580         // Keep previous formatting if it satisfies the constrains
581         wrap_str(snippet, context.config.max_width(), shape)
582     } else {
583         Some(new)
584     }
585 }
586
587 /// Return true if the two strings of code have the same payload of comments.
588 /// The payload of comments is everything in the string except:
589 ///     - actual code (not comments)
590 ///     - comment start/end marks
591 ///     - whitespace
592 ///     - '*' at the beginning of lines in block comments
593 fn changed_comment_content(orig: &str, new: &str) -> bool {
594     // Cannot write this as a fn since we cannot return types containing closures
595     let code_comment_content = |code| {
596         let slices = UngroupedCommentCodeSlices::new(code);
597         slices
598             .filter(|&(ref kind, _, _)| *kind == CodeCharKind::Comment)
599             .flat_map(|(_, _, s)| CommentReducer::new(s))
600     };
601     let res = code_comment_content(orig).ne(code_comment_content(new));
602     debug!("comment::changed_comment_content: {}\norig: '{}'\nnew: '{}'\nraw_old: {}\nraw_new: {}",
603            res,
604            orig,
605            new,
606            code_comment_content(orig).collect::<String>(),
607            code_comment_content(new).collect::<String>());
608     res
609 }
610
611
612 /// Iterator over the 'payload' characters of a comment.
613 /// It skips whitespace, comment start/end marks, and '*' at the beginning of lines.
614 /// The comment must be one comment, ie not more than one start mark (no multiple line comments,
615 /// for example).
616 struct CommentReducer<'a> {
617     is_block: bool,
618     at_start_line: bool,
619     iter: std::str::Chars<'a>,
620 }
621
622 impl<'a> CommentReducer<'a> {
623     fn new(comment: &'a str) -> CommentReducer<'a> {
624         let is_block = comment.starts_with("/*");
625         let comment = remove_comment_header(comment);
626         CommentReducer {
627             is_block: is_block,
628             at_start_line: false, // There are no supplementary '*' on the first line
629             iter: comment.chars(),
630         }
631     }
632 }
633
634 impl<'a> Iterator for CommentReducer<'a> {
635     type Item = char;
636     fn next(&mut self) -> Option<Self::Item> {
637         loop {
638             let mut c = try_opt!(self.iter.next());
639             if self.is_block && self.at_start_line {
640                 while c.is_whitespace() {
641                     c = try_opt!(self.iter.next());
642                 }
643                 // Ignore leading '*'
644                 if c == '*' {
645                     c = try_opt!(self.iter.next());
646                 }
647             } else {
648                 if c == '\n' {
649                     self.at_start_line = true;
650                 }
651             }
652             if !c.is_whitespace() {
653                 return Some(c);
654             }
655         }
656     }
657 }
658
659
660 fn remove_comment_header(comment: &str) -> &str {
661     if comment.starts_with("///") || comment.starts_with("//!") {
662         &comment[3..]
663     } else if comment.starts_with("//") {
664         &comment[2..]
665     } else if (comment.starts_with("/**") && !comment.starts_with("/**/")) ||
666               comment.starts_with("/*!") {
667         &comment[3..comment.len() - 2]
668     } else {
669         assert!(comment.starts_with("/*"),
670                 format!("string '{}' is not a comment", comment));
671         &comment[2..comment.len() - 2]
672     }
673 }
674
675 #[cfg(test)]
676 mod test {
677     use super::{CharClasses, CodeCharKind, FullCodeCharKind, contains_comment, rewrite_comment,
678                 FindUncommented, CommentCodeSlices};
679     use {Indent, Shape};
680
681     #[test]
682     fn char_classes() {
683         let mut iter = CharClasses::new("//\n\n".chars());
684
685         assert_eq!((FullCodeCharKind::StartComment, '/'), iter.next().unwrap());
686         assert_eq!((FullCodeCharKind::InComment, '/'), iter.next().unwrap());
687         assert_eq!((FullCodeCharKind::EndComment, '\n'), iter.next().unwrap());
688         assert_eq!((FullCodeCharKind::Normal, '\n'), iter.next().unwrap());
689         assert_eq!(None, iter.next());
690     }
691
692     #[test]
693     fn comment_code_slices() {
694         let input = "code(); /* test */ 1 + 1";
695         let mut iter = CommentCodeSlices::new(input);
696
697         assert_eq!((CodeCharKind::Normal, 0, "code(); "), iter.next().unwrap());
698         assert_eq!((CodeCharKind::Comment, 8, "/* test */"),
699                    iter.next().unwrap());
700         assert_eq!((CodeCharKind::Normal, 18, " 1 + 1"), iter.next().unwrap());
701         assert_eq!(None, iter.next());
702     }
703
704     #[test]
705     fn comment_code_slices_two() {
706         let input = "// comment\n    test();";
707         let mut iter = CommentCodeSlices::new(input);
708
709         assert_eq!((CodeCharKind::Normal, 0, ""), iter.next().unwrap());
710         assert_eq!((CodeCharKind::Comment, 0, "// comment\n"),
711                    iter.next().unwrap());
712         assert_eq!((CodeCharKind::Normal, 11, "    test();"),
713                    iter.next().unwrap());
714         assert_eq!(None, iter.next());
715     }
716
717     #[test]
718     fn comment_code_slices_three() {
719         let input = "1 // comment\n    // comment2\n\n";
720         let mut iter = CommentCodeSlices::new(input);
721
722         assert_eq!((CodeCharKind::Normal, 0, "1 "), iter.next().unwrap());
723         assert_eq!((CodeCharKind::Comment, 2, "// comment\n    // comment2\n"),
724                    iter.next().unwrap());
725         assert_eq!((CodeCharKind::Normal, 29, "\n"), iter.next().unwrap());
726         assert_eq!(None, iter.next());
727     }
728
729     #[test]
730     #[cfg_attr(rustfmt, rustfmt_skip)]
731     fn format_comments() {
732         let mut config: ::config::Config = Default::default();
733         config.set().wrap_comments(true);
734         config.set().normalize_comments(true);
735
736         let comment = rewrite_comment(" //test",
737                                       true,
738                                       Shape::legacy(100, Indent::new(0, 100)),
739                                       &config).unwrap();
740         assert_eq!("/* test */", comment);
741
742         let comment = rewrite_comment("// comment on a",
743                                       false,
744                                       Shape::legacy(10, Indent::empty()),
745                                       &config).unwrap();
746         assert_eq!("// comment\n// on a", comment);
747
748         let comment = rewrite_comment("//  A multi line comment\n             // between args.",
749                                       false,
750                                       Shape::legacy(60, Indent::new(0, 12)),
751                                       &config).unwrap();
752         assert_eq!("//  A multi line comment\n            // between args.", comment);
753
754         let input = "// comment";
755         let expected =
756             "/* comment */";
757         let comment = rewrite_comment(input,
758                                       true,
759                                       Shape::legacy(9, Indent::new(0, 69)),
760                                       &config).unwrap();
761         assert_eq!(expected, comment);
762
763         let comment = rewrite_comment("/*   trimmed    */",
764                                       true,
765                                       Shape::legacy(100, Indent::new(0, 100)),
766                                       &config).unwrap();
767         assert_eq!("/* trimmed */", comment);
768     }
769
770     // This is probably intended to be a non-test fn, but it is not used. I'm
771     // keeping it around unless it helps us test stuff.
772     fn uncommented(text: &str) -> String {
773         CharClasses::new(text.chars())
774             .filter_map(|(s, c)| match s {
775                             FullCodeCharKind::Normal => Some(c),
776                             _ => None,
777                         })
778             .collect()
779     }
780
781     #[test]
782     fn test_uncommented() {
783         assert_eq!(&uncommented("abc/*...*/"), "abc");
784         assert_eq!(&uncommented("// .... /* \n../* /* *** / */ */a/* // */c\n"),
785                    "..ac\n");
786         assert_eq!(&uncommented("abc \" /* */\" qsdf"), "abc \" /* */\" qsdf");
787     }
788
789     #[test]
790     fn test_contains_comment() {
791         assert_eq!(contains_comment("abc"), false);
792         assert_eq!(contains_comment("abc // qsdf"), true);
793         assert_eq!(contains_comment("abc /* kqsdf"), true);
794         assert_eq!(contains_comment("abc \" /* */\" qsdf"), false);
795     }
796
797     #[test]
798     fn test_find_uncommented() {
799         fn check(haystack: &str, needle: &str, expected: Option<usize>) {
800             assert_eq!(expected, haystack.find_uncommented(needle));
801         }
802
803         check("/*/ */test", "test", Some(6));
804         check("//test\ntest", "test", Some(7));
805         check("/* comment only */", "whatever", None);
806         check("/* comment */ some text /* more commentary */ result",
807               "result",
808               Some(46));
809         check("sup // sup", "p", Some(2));
810         check("sup", "x", None);
811         check(r#"π? /**/ π is nice!"#, r#"π is nice"#, Some(9));
812         check("/*sup yo? \n sup*/ sup", "p", Some(20));
813         check("hel/*lohello*/lo", "hello", None);
814         check("acb", "ab", None);
815         check(",/*A*/ ", ",", Some(0));
816         check("abc", "abc", Some(0));
817         check("/* abc */", "abc", None);
818         check("/**/abc/* */", "abc", Some(4));
819         check("\"/* abc */\"", "abc", Some(4));
820         check("\"/* abc", "abc", Some(4));
821     }
822 }