|
| 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 | +// Format comments. |
| 12 | + |
| 13 | +use string::{StringFormat, rewrite_string}; |
| 14 | +use utils::make_indent; |
| 15 | + |
| 16 | +pub fn rewrite_comment(orig: &str, block_style: bool, width: usize, offset: usize) -> String { |
| 17 | + let s = orig.trim(); |
| 18 | + |
| 19 | + // Edge case: block comments. Let's not trim their lines (for now). |
| 20 | + let opener = if block_style { "/* " } else { "// " }; |
| 21 | + let closer = if block_style { " */" } else { "" }; |
| 22 | + let line_start = if block_style { " * " } else { "// " }; |
| 23 | + |
| 24 | + let max_chars = width.checked_sub(closer.len()).unwrap_or(1) |
| 25 | + .checked_sub(opener.len()).unwrap_or(1); |
| 26 | + |
| 27 | + let fmt = StringFormat { |
| 28 | + opener: "", |
| 29 | + closer: "", |
| 30 | + line_start: line_start, |
| 31 | + line_end: "", |
| 32 | + width: max_chars, |
| 33 | + offset: offset + opener.len() - line_start.len(), |
| 34 | + trim_end: true |
| 35 | + }; |
| 36 | + |
| 37 | + let indent_str = make_indent(offset); |
| 38 | + let line_breaks = s.chars().filter(|&c| c == '\n').count(); |
| 39 | + |
| 40 | + let (_, mut s) = s.lines().enumerate() |
| 41 | + .map(|(i, mut line)| { |
| 42 | + line = line.trim(); |
| 43 | + |
| 44 | + // Drop old closer. |
| 45 | + if i == line_breaks && line.ends_with("*/") && !line.starts_with("//") { |
| 46 | + line = &line[..(line.len() - 2)]; |
| 47 | + } |
| 48 | + |
| 49 | + line.trim_right_matches(' ') |
| 50 | + }) |
| 51 | + .map(left_trim_comment_line) |
| 52 | + .fold((true, opener.to_owned()), |(first, mut acc), line| { |
| 53 | + if !first { |
| 54 | + acc.push('\n'); |
| 55 | + acc.push_str(&indent_str); |
| 56 | + acc.push_str(line_start); |
| 57 | + } |
| 58 | + |
| 59 | + if line.len() > max_chars { |
| 60 | + acc.push_str(&rewrite_string(line, &fmt)); |
| 61 | + } else { |
| 62 | + acc.push_str(line); |
| 63 | + } |
| 64 | + |
| 65 | + (false, acc) |
| 66 | + }); |
| 67 | + |
| 68 | + s.push_str(closer); |
| 69 | + |
| 70 | + s |
| 71 | +} |
| 72 | + |
| 73 | +fn left_trim_comment_line<'a>(line: &'a str) -> &'a str { |
| 74 | + if line.starts_with("/* ") || line.starts_with("// ") { |
| 75 | + &line[3..] |
| 76 | + } else if line.starts_with("/*") || line.starts_with("* ") || line.starts_with("//") { |
| 77 | + &line[2..] |
| 78 | + } else if line.starts_with("*") { |
| 79 | + &line[1..] |
| 80 | + } else { |
| 81 | + line |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +#[test] |
| 86 | +fn format_comments() { |
| 87 | + assert_eq!("/* test */", rewrite_comment(" //test", true, 100, 100)); |
| 88 | + assert_eq!("// comment\n// on a", rewrite_comment("// comment on a", false, 10, 0)); |
| 89 | + |
| 90 | + assert_eq!("// A multi line comment\n // between args.", |
| 91 | + rewrite_comment("// A multi line comment\n // between args.", |
| 92 | + false, |
| 93 | + 60, |
| 94 | + 12)); |
| 95 | + |
| 96 | + let input = "// comment"; |
| 97 | + let expected_output = "/* com\n \ |
| 98 | + * men\n \ |
| 99 | + * t */"; |
| 100 | + assert_eq!(expected_output, rewrite_comment(input, true, 9, 69)); |
| 101 | +} |
| 102 | + |
| 103 | + |
| 104 | +pub trait FindUncommented { |
| 105 | + fn find_uncommented(&self, pat: &str) -> Option<usize>; |
| 106 | +} |
| 107 | + |
| 108 | +impl FindUncommented for str { |
| 109 | + fn find_uncommented(&self, pat: &str) -> Option<usize> { |
| 110 | + let mut needle_iter = pat.chars(); |
| 111 | + let mut possible_comment = false; |
| 112 | + |
| 113 | + for (i, b) in self.char_indices() { |
| 114 | + match needle_iter.next() { |
| 115 | + Some(c) => { |
| 116 | + if b != c { |
| 117 | + needle_iter = pat.chars(); |
| 118 | + } |
| 119 | + }, |
| 120 | + None => return Some(i - pat.len()) |
| 121 | + } |
| 122 | + |
| 123 | + if possible_comment && (b == '/' || b == '*') { |
| 124 | + return find_comment_end(&self[(i-1)..]) |
| 125 | + .and_then(|end| { |
| 126 | + self[(end + i - 1)..].find_uncommented(pat) |
| 127 | + .map(|idx| idx + end + i - 1) |
| 128 | + }); |
| 129 | + } |
| 130 | + |
| 131 | + possible_comment = b == '/'; |
| 132 | + } |
| 133 | + |
| 134 | + // Handle case where the pattern is a suffix of the search string |
| 135 | + match needle_iter.next() { |
| 136 | + Some(_) => None, |
| 137 | + None => Some(self.len() - pat.len()) |
| 138 | + } |
| 139 | + } |
| 140 | +} |
| 141 | + |
| 142 | +#[test] |
| 143 | +fn test_find_uncommented() { |
| 144 | + fn check(haystack: &str, needle: &str, expected: Option<usize>) { |
| 145 | + println!("haystack {:?}, needle: {:?}", haystack, needle); |
| 146 | + assert_eq!(expected, haystack.find_uncommented(needle)); |
| 147 | + } |
| 148 | + |
| 149 | + check("/*/ */test", "test", Some(6)); |
| 150 | + check("//test\ntest", "test", Some(7)); |
| 151 | + check("/* comment only */", "whatever", None); |
| 152 | + check("/* comment */ some text /* more commentary */ result", "result", Some(46)); |
| 153 | + check("sup // sup", "p", Some(2)); |
| 154 | + check("sup", "x", None); |
| 155 | + check("π? /**/ π is nice!", "π is nice", Some(9)); |
| 156 | + check("/*sup yo? \n sup*/ sup", "p", Some(20)); |
| 157 | + check("hel/*lohello*/lo", "hello", None); |
| 158 | + check("acb", "ab", None); |
| 159 | +} |
| 160 | + |
| 161 | +// Returns the first byte position after the first comment. The given string |
| 162 | +// is expected to be prefixed by a comment, including delimiters. |
| 163 | +// Good: "/* /* inner */ outer */ code();" |
| 164 | +// Bad: "code(); // hello\n world!" |
| 165 | +pub fn find_comment_end(s: &str) -> Option<usize> { |
| 166 | + if s.starts_with("//") { |
| 167 | + s.find('\n').map(|idx| idx + 1) |
| 168 | + } else { |
| 169 | + // Block comment |
| 170 | + let mut levels = 0; |
| 171 | + let mut prev_char = 'a'; |
| 172 | + |
| 173 | + for (i, mut c) in s.char_indices() { |
| 174 | + if c == '*' && prev_char == '/' { |
| 175 | + levels += 1; |
| 176 | + c = 'a'; // Invalidate prev_char |
| 177 | + } else if c == '/' && prev_char == '*' { |
| 178 | + levels -= 1; |
| 179 | + |
| 180 | + if levels == 0 { |
| 181 | + return Some(i + 1); |
| 182 | + } |
| 183 | + c = 'a'; |
| 184 | + } |
| 185 | + |
| 186 | + prev_char = c; |
| 187 | + } |
| 188 | + |
| 189 | + None |
| 190 | + } |
| 191 | +} |
| 192 | + |
| 193 | +#[test] |
| 194 | +fn comment_end() { |
| 195 | + assert_eq!(Some(6), find_comment_end("// hi\n")); |
| 196 | + assert_eq!(Some(9), find_comment_end("/* sup */ ")); |
| 197 | + assert_eq!(Some(9), find_comment_end("/*/**/ */ ")); |
| 198 | + assert_eq!(Some(6), find_comment_end("/*/ */ weird!")); |
| 199 | + assert_eq!(None, find_comment_end("/* hi /* test */")); |
| 200 | + assert_eq!(None, find_comment_end("// hi /* test */")); |
| 201 | + assert_eq!(Some(9), find_comment_end("// hi /*\n.")); |
| 202 | +} |
0 commit comments