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.
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.
17 use string::{StringFormat, rewrite_string};
19 pub fn rewrite_comment(orig: &str,
27 // Edge case: block comments. Let's not trim their lines (for now).
28 let (opener, closer, line_start) = if block_style {
30 } else if !config.normalise_comments {
31 if orig.starts_with("/**") {
32 ("/** ", " **/", " ** ")
33 } else if orig.starts_with("/*!") {
34 ("/*! ", " */", " * ")
35 } else if orig.starts_with("/*") {
37 } else if orig.starts_with("///") {
39 } else if orig.starts_with("//!") {
44 } else if orig.starts_with("///") || orig.starts_with("/**") {
46 } else if orig.starts_with("//!") || orig.starts_with("/*!") {
52 let max_chars = width.checked_sub(closer.len() + opener.len()).unwrap_or(1);
54 let fmt = StringFormat {
57 line_start: line_start,
60 offset: offset + (opener.len() - line_start.len()),
65 let indent_str = offset.to_string(config);
66 let line_breaks = s.chars().filter(|&c| c == '\n').count();
70 .map(|(i, mut line)| {
73 if i == line_breaks && line.ends_with("*/") && !line.starts_with("//") {
74 line = &line[..(line.len() - 2)];
79 .map(left_trim_comment_line)
88 let mut result = opener.to_owned();
96 result.push_str(&indent_str);
97 result.push_str(line_start);
100 if config.wrap_comments && line.len() > max_chars {
101 let rewrite = try_opt!(rewrite_string(line, &fmt));
102 result.push_str(&rewrite);
105 // Remove space if this is an empty comment or a doc comment.
108 result.push_str(line);
112 result.push_str(closer);
113 if result == opener {
121 fn left_trim_comment_line(line: &str) -> &str {
122 if line.starts_with("//! ") || line.starts_with("/// ") || line.starts_with("/*! ") ||
123 line.starts_with("/** ") {
125 } else if line.starts_with("/* ") || line.starts_with("// ") || line.starts_with("//!") ||
126 line.starts_with("///") || line.starts_with("** ") || line.starts_with("/*!") ||
127 line.starts_with("/**") {
129 } else if line.starts_with("/*") || line.starts_with("* ") || line.starts_with("//") ||
130 line.starts_with("**") {
132 } else if line.starts_with("*") {
139 pub trait FindUncommented {
140 fn find_uncommented(&self, pat: &str) -> Option<usize>;
143 impl FindUncommented for str {
144 fn find_uncommented(&self, pat: &str) -> Option<usize> {
145 let mut needle_iter = pat.chars();
146 for (kind, (i, b)) in CharClasses::new(self.char_indices()) {
147 match needle_iter.next() {
149 return Some(i - pat.len());
153 CodeCharKind::Normal if b == c => {}
155 needle_iter = pat.chars();
162 // Handle case where the pattern is a suffix of the search string
163 match needle_iter.next() {
165 None => Some(self.len() - pat.len()),
170 // Returns the first byte position after the first comment. The given string
171 // is expected to be prefixed by a comment, including delimiters.
172 // Good: "/* /* inner */ outer */ code();"
173 // Bad: "code(); // hello\n world!"
174 pub fn find_comment_end(s: &str) -> Option<usize> {
175 let mut iter = CharClasses::new(s.char_indices());
176 for (kind, (i, _c)) in &mut iter {
177 if kind == CodeCharKind::Normal {
182 // Handle case where the comment ends at the end of s.
183 if iter.status == CharClassesStatus::Normal {
190 /// Returns true if text contains any comment.
191 pub fn contains_comment(text: &str) -> bool {
192 CharClasses::new(text.chars()).any(|(kind, _)| kind == CodeCharKind::Comment)
195 struct CharClasses<T>
199 base: iter::Peekable<T>,
200 status: CharClassesStatus,
204 fn get_char(&self) -> char;
207 impl RichChar for char {
208 fn get_char(&self) -> char {
213 impl RichChar for (usize, char) {
214 fn get_char(&self) -> char {
219 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
220 enum CharClassesStatus {
226 // The u32 is the nesting deepness of the comment
228 // Status when the '/' has been consumed, but not yet the '*', deepness is
229 // the new deepness (after the comment opening).
230 BlockCommentOpening(u32),
231 // Status when the '*' has been consumed, but not yet the '/', deepness is
232 // the new deepness (after the comment closing).
233 BlockCommentClosing(u32),
237 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
238 pub enum CodeCharKind {
243 impl<T> CharClasses<T>
247 fn new(base: T) -> CharClasses<T> {
249 base: base.peekable(),
250 status: CharClassesStatus::Normal,
255 impl<T> Iterator for CharClasses<T>
259 type Item = (CodeCharKind, T::Item);
261 fn next(&mut self) -> Option<(CodeCharKind, T::Item)> {
262 let item = try_opt!(self.base.next());
263 let chr = item.get_char();
264 self.status = match self.status {
265 CharClassesStatus::LitString => {
267 '"' => CharClassesStatus::Normal,
268 '\\' => CharClassesStatus::LitStringEscape,
269 _ => CharClassesStatus::LitString,
272 CharClassesStatus::LitStringEscape => CharClassesStatus::LitString,
273 CharClassesStatus::LitChar => {
275 '\\' => CharClassesStatus::LitCharEscape,
276 '\'' => CharClassesStatus::Normal,
277 _ => CharClassesStatus::LitChar,
280 CharClassesStatus::LitCharEscape => CharClassesStatus::LitChar,
281 CharClassesStatus::Normal => {
283 '"' => CharClassesStatus::LitString,
284 '\'' => CharClassesStatus::LitChar,
286 match self.base.peek() {
287 Some(next) if next.get_char() == '*' => {
288 self.status = CharClassesStatus::BlockCommentOpening(1);
289 return Some((CodeCharKind::Comment, item));
291 Some(next) if next.get_char() == '/' => {
292 self.status = CharClassesStatus::LineComment;
293 return Some((CodeCharKind::Comment, item));
295 _ => CharClassesStatus::Normal,
298 _ => CharClassesStatus::Normal,
301 CharClassesStatus::BlockComment(deepness) => {
303 // This is the closing '/'
304 assert_eq!(chr, '/');
305 self.status = CharClassesStatus::Normal;
306 return Some((CodeCharKind::Comment, item));
308 self.status = match self.base.peek() {
309 Some(next) if next.get_char() == '/' && chr == '*' => {
310 CharClassesStatus::BlockCommentClosing(deepness - 1)
312 Some(next) if next.get_char() == '*' && chr == '/' => {
313 CharClassesStatus::BlockCommentOpening(deepness + 1)
315 _ => CharClassesStatus::BlockComment(deepness),
317 return Some((CodeCharKind::Comment, item));
319 CharClassesStatus::BlockCommentOpening(deepness) => {
320 assert_eq!(chr, '*');
321 self.status = CharClassesStatus::BlockComment(deepness);
322 return Some((CodeCharKind::Comment, item));
324 CharClassesStatus::BlockCommentClosing(deepness) => {
325 assert_eq!(chr, '/');
326 self.status = if deepness == 0 {
327 CharClassesStatus::Normal
329 CharClassesStatus::BlockComment(deepness)
331 return Some((CodeCharKind::Comment, item));
333 CharClassesStatus::LineComment => {
334 self.status = match chr {
335 '\n' => CharClassesStatus::Normal,
336 _ => CharClassesStatus::LineComment,
338 return Some((CodeCharKind::Comment, item));
341 Some((CodeCharKind::Normal, item))
345 /// Iterator over an alternating sequence of functional and commented parts of
346 /// a string. The first item is always a, possibly zero length, subslice of
347 /// functional text. Line style comments contain their ending newlines.
348 pub struct CommentCodeSlices<'a> {
350 last_slice_kind: CodeCharKind,
351 last_slice_end: usize,
354 impl<'a> CommentCodeSlices<'a> {
355 pub fn new(slice: &'a str) -> CommentCodeSlices<'a> {
358 last_slice_kind: CodeCharKind::Comment,
364 impl<'a> Iterator for CommentCodeSlices<'a> {
365 type Item = (CodeCharKind, usize, &'a str);
367 fn next(&mut self) -> Option<Self::Item> {
368 if self.last_slice_end == self.slice.len() {
372 let mut sub_slice_end = self.last_slice_end;
373 let mut first_whitespace = None;
374 let subslice = &self.slice[self.last_slice_end..];
375 let mut iter = CharClasses::new(subslice.char_indices());
377 for (kind, (i, c)) in &mut iter {
378 let is_comment_connector = self.last_slice_kind == CodeCharKind::Normal &&
379 &subslice[..2] == "//" &&
380 [' ', '\t'].contains(&c);
382 if is_comment_connector && first_whitespace.is_none() {
383 first_whitespace = Some(i);
386 if kind == self.last_slice_kind && !is_comment_connector {
387 let last_index = match first_whitespace {
391 sub_slice_end = self.last_slice_end + last_index;
395 if !is_comment_connector {
396 first_whitespace = None;
400 if let (None, true) = (iter.next(), sub_slice_end == self.last_slice_end) {
401 // This was the last subslice.
402 sub_slice_end = match first_whitespace {
403 Some(i) => self.last_slice_end + i,
404 None => self.slice.len(),
408 let kind = match self.last_slice_kind {
409 CodeCharKind::Comment => CodeCharKind::Normal,
410 CodeCharKind::Normal => CodeCharKind::Comment,
414 &self.slice[self.last_slice_end..sub_slice_end]);
415 self.last_slice_end = sub_slice_end;
416 self.last_slice_kind = kind;
424 use super::{CharClasses, CodeCharKind, contains_comment, rewrite_comment, FindUncommented,
430 let mut iter = CharClasses::new("//\n\n".chars());
432 assert_eq!((CodeCharKind::Comment, '/'), iter.next().unwrap());
433 assert_eq!((CodeCharKind::Comment, '/'), iter.next().unwrap());
434 assert_eq!((CodeCharKind::Comment, '\n'), iter.next().unwrap());
435 assert_eq!((CodeCharKind::Normal, '\n'), iter.next().unwrap());
436 assert_eq!(None, iter.next());
440 fn comment_code_slices() {
441 let input = "code(); /* test */ 1 + 1";
442 let mut iter = CommentCodeSlices::new(input);
444 assert_eq!((CodeCharKind::Normal, 0, "code(); "), iter.next().unwrap());
445 assert_eq!((CodeCharKind::Comment, 8, "/* test */"),
446 iter.next().unwrap());
447 assert_eq!((CodeCharKind::Normal, 18, " 1 + 1"), iter.next().unwrap());
448 assert_eq!(None, iter.next());
452 fn comment_code_slices_two() {
453 let input = "// comment\n test();";
454 let mut iter = CommentCodeSlices::new(input);
456 assert_eq!((CodeCharKind::Normal, 0, ""), iter.next().unwrap());
457 assert_eq!((CodeCharKind::Comment, 0, "// comment\n"),
458 iter.next().unwrap());
459 assert_eq!((CodeCharKind::Normal, 11, " test();"),
460 iter.next().unwrap());
461 assert_eq!(None, iter.next());
465 fn comment_code_slices_three() {
466 let input = "1 // comment\n // comment2\n\n";
467 let mut iter = CommentCodeSlices::new(input);
469 assert_eq!((CodeCharKind::Normal, 0, "1 "), iter.next().unwrap());
470 assert_eq!((CodeCharKind::Comment, 2, "// comment\n // comment2\n"),
471 iter.next().unwrap());
472 assert_eq!((CodeCharKind::Normal, 29, "\n"), iter.next().unwrap());
473 assert_eq!(None, iter.next());
477 #[cfg_attr(rustfmt, rustfmt_skip)]
478 fn format_comments() {
479 let mut config: ::config::Config = Default::default();
480 config.wrap_comments = true;
481 assert_eq!("/* test */", rewrite_comment(" //test", true, 100, Indent::new(0, 100),
483 assert_eq!("// comment\n// on a", rewrite_comment("// comment on a", false, 10,
484 Indent::empty(), &config).unwrap());
486 assert_eq!("// A multi line comment\n // between args.",
487 rewrite_comment("// A multi line comment\n // between args.",
493 let input = "// comment";
498 assert_eq!(expected, rewrite_comment(input, true, 9, Indent::new(0, 69), &config).unwrap());
500 assert_eq!("/* trimmed */", rewrite_comment("/* trimmed */", true, 100,
501 Indent::new(0, 100), &config).unwrap());
504 // This is probably intended to be a non-test fn, but it is not used. I'm
505 // keeping it around unless it helps us test stuff.
506 fn uncommented(text: &str) -> String {
507 CharClasses::new(text.chars())
508 .filter_map(|(s, c)| {
510 CodeCharKind::Normal => Some(c),
511 CodeCharKind::Comment => None,
518 fn test_uncommented() {
519 assert_eq!(&uncommented("abc/*...*/"), "abc");
520 assert_eq!(&uncommented("// .... /* \n../* /* *** / */ */a/* // */c\n"),
522 assert_eq!(&uncommented("abc \" /* */\" qsdf"), "abc \" /* */\" qsdf");
526 fn test_contains_comment() {
527 assert_eq!(contains_comment("abc"), false);
528 assert_eq!(contains_comment("abc // qsdf"), true);
529 assert_eq!(contains_comment("abc /* kqsdf"), true);
530 assert_eq!(contains_comment("abc \" /* */\" qsdf"), false);
534 fn test_find_uncommented() {
535 fn check(haystack: &str, needle: &str, expected: Option<usize>) {
536 assert_eq!(expected, haystack.find_uncommented(needle));
539 check("/*/ */test", "test", Some(6));
540 check("//test\ntest", "test", Some(7));
541 check("/* comment only */", "whatever", None);
542 check("/* comment */ some text /* more commentary */ result",
545 check("sup // sup", "p", Some(2));
546 check("sup", "x", None);
547 check(r#"π? /**/ π is nice!"#, r#"π is nice"#, Some(9));
548 check("/*sup yo? \n sup*/ sup", "p", Some(20));
549 check("hel/*lohello*/lo", "hello", None);
550 check("acb", "ab", None);
551 check(",/*A*/ ", ",", Some(0));
552 check("abc", "abc", Some(0));
553 check("/* abc */", "abc", None);
554 check("/**/abc/* */", "abc", Some(4));
555 check("\"/* abc */\"", "abc", Some(4));
556 check("\"/* abc", "abc", Some(4));