1 pub use CommentStyle::*;
4 use crate::source_map::SourceMap;
5 use crate::parse::lexer::{is_block_doc_comment, is_pattern_whitespace};
6 use crate::parse::lexer::{self, ParseSess, StringReader, TokenAndSpan};
7 use crate::print::pprust;
9 use syntax_pos::{BytePos, CharPos, Pos, FileName};
15 #[derive(Clone, Copy, PartialEq, Debug)]
16 pub enum CommentStyle {
17 /// No code on either side of each line of the comment
19 /// Code exists to the left of the comment
21 /// Code before /* foo */ and after the comment
23 /// Just a manual blank line "\n\n", for layout
29 pub style: CommentStyle,
30 pub lines: Vec<String>,
34 fn is_doc_comment(s: &str) -> bool {
35 (s.starts_with("///") && super::is_doc_comment(s)) || s.starts_with("//!") ||
36 (s.starts_with("/**") && is_block_doc_comment(s)) || s.starts_with("/*!")
39 pub fn doc_comment_style(comment: &str) -> ast::AttrStyle {
40 assert!(is_doc_comment(comment));
41 if comment.starts_with("//!") || comment.starts_with("/*!") {
48 pub fn strip_doc_comment_decoration(comment: &str) -> String {
49 /// remove whitespace-only lines from the start/end of lines
50 fn vertical_trim(lines: Vec<String>) -> Vec<String> {
52 let mut j = lines.len();
53 // first line of all-stars should be omitted
54 if !lines.is_empty() && lines[0].chars().all(|c| c == '*') {
58 while i < j && lines[i].trim().is_empty() {
61 // like the first, a last line of all stars should be omitted
70 while j > i && lines[j - 1].trim().is_empty() {
77 /// remove a "[ \t]*\*" block from each line, if possible
78 fn horizontal_trim(lines: Vec<String>) -> Vec<String> {
79 let mut i = usize::MAX;
80 let mut can_trim = true;
84 for (j, c) in line.chars().enumerate() {
85 if j > i || !"* \t".contains(c) {
109 .map(|line| (&line[i + 1..line.len()]).to_string())
116 // one-line comments lose their prefix
117 const ONELINERS: &[&str] = &["///!", "///", "//!", "//"];
119 for prefix in ONELINERS {
120 if comment.starts_with(*prefix) {
121 return (&comment[prefix.len()..]).to_string();
125 if comment.starts_with("/*") {
126 let lines = comment[3..comment.len() - 2]
128 .map(|s| s.to_string())
129 .collect::<Vec<String>>();
131 let lines = vertical_trim(lines);
132 let lines = horizontal_trim(lines);
134 return lines.join("\n");
137 panic!("not a doc-comment: {}", comment);
140 fn push_blank_line_comment(rdr: &StringReader<'_>, comments: &mut Vec<Comment>) {
141 debug!(">>> blank-line comment");
142 comments.push(Comment {
149 fn consume_whitespace_counting_blank_lines(
150 rdr: &mut StringReader<'_>,
151 comments: &mut Vec<Comment>
153 while is_pattern_whitespace(rdr.ch) && !rdr.is_eof() {
155 push_blank_line_comment(rdr, &mut *comments);
161 fn read_shebang_comment(rdr: &mut StringReader<'_>,
162 code_to_the_left: bool,
163 comments: &mut Vec<Comment>) {
164 debug!(">>> shebang comment");
166 debug!("<<< shebang comment");
167 comments.push(Comment {
168 style: if code_to_the_left { Trailing } else { Isolated },
169 lines: vec![rdr.read_one_line_comment()],
174 fn read_line_comments(rdr: &mut StringReader<'_>,
175 code_to_the_left: bool,
176 comments: &mut Vec<Comment>) {
177 debug!(">>> line comments");
179 let mut lines: Vec<String> = Vec::new();
180 while rdr.ch_is('/') && rdr.nextch_is('/') {
181 let line = rdr.read_one_line_comment();
183 // Doc comments are not put in comments.
184 if is_doc_comment(&line[..]) {
188 rdr.consume_non_eol_whitespace();
190 debug!("<<< line comments");
191 if !lines.is_empty() {
192 comments.push(Comment {
193 style: if code_to_the_left { Trailing } else { Isolated },
200 /// Returns `None` if the first `col` chars of `s` contain a non-whitespace char.
201 /// Otherwise returns `Some(k)` where `k` is first char offset after that leading
202 /// whitespace. Note that `k` may be outside bounds of `s`.
203 fn all_whitespace(s: &str, col: CharPos) -> Option<usize> {
205 for (i, ch) in s.char_indices().take(col.to_usize()) {
206 if !ch.is_whitespace() {
209 idx = i + ch.len_utf8();
214 fn trim_whitespace_prefix_and_push_line(lines: &mut Vec<String>, s: String, col: CharPos) {
216 let s1 = match all_whitespace(&s[..], col) {
219 s[col..len].to_string()
226 debug!("pushing line: {}", s1);
230 fn read_block_comment(rdr: &mut StringReader<'_>,
231 code_to_the_left: bool,
232 comments: &mut Vec<Comment>) {
233 debug!(">>> block comment");
235 let mut lines: Vec<String> = Vec::new();
237 // Count the number of chars since the start of the line by rescanning.
238 let src_index = rdr.src_index(rdr.source_file.line_begin_pos(rdr.pos));
239 let end_src_index = rdr.src_index(rdr.pos);
240 assert!(src_index <= end_src_index,
241 "src_index={}, end_src_index={}, line_begin_pos={}",
242 src_index, end_src_index, rdr.source_file.line_begin_pos(rdr.pos).to_u32());
244 let col = CharPos(rdr.src[src_index..end_src_index].chars().count());
249 let mut curr_line = String::from("/*");
251 // doc-comments are not really comments, they are attributes
252 if (rdr.ch_is('*') && !rdr.nextch_is('*')) || rdr.ch_is('!') {
253 while !(rdr.ch_is('*') && rdr.nextch_is('/')) && !rdr.is_eof() {
254 curr_line.push(rdr.ch.unwrap());
258 curr_line.push_str("*/");
262 if is_block_doc_comment(&curr_line[..]) {
265 assert!(!curr_line.contains('\n'));
266 lines.push(curr_line);
268 let mut level: isize = 1;
270 debug!("=== block comment level {}", level);
272 rdr.fatal("unterminated block comment").raise();
275 trim_whitespace_prefix_and_push_line(&mut lines, curr_line, col);
276 curr_line = String::new();
279 curr_line.push(rdr.ch.unwrap());
280 if rdr.ch_is('/') && rdr.nextch_is('*') {
286 if rdr.ch_is('*') && rdr.nextch_is('/') {
297 if !curr_line.is_empty() {
298 trim_whitespace_prefix_and_push_line(&mut lines, curr_line, col);
302 let mut style = if code_to_the_left {
307 rdr.consume_non_eol_whitespace();
308 if !rdr.is_eof() && !rdr.ch_is('\n') && lines.len() == 1 {
311 debug!("<<< block comment");
312 comments.push(Comment {
320 fn consume_comment(rdr: &mut StringReader<'_>,
321 comments: &mut Vec<Comment>,
322 code_to_the_left: &mut bool,
323 anything_to_the_left: &mut bool) {
324 debug!(">>> consume comment");
325 if rdr.ch_is('/') && rdr.nextch_is('/') {
326 read_line_comments(rdr, *code_to_the_left, comments);
327 *code_to_the_left = false;
328 *anything_to_the_left = false;
329 } else if rdr.ch_is('/') && rdr.nextch_is('*') {
330 read_block_comment(rdr, *code_to_the_left, comments);
331 *anything_to_the_left = true;
332 } else if rdr.ch_is('#') && rdr.nextch_is('!') {
333 read_shebang_comment(rdr, *code_to_the_left, comments);
334 *code_to_the_left = false;
335 *anything_to_the_left = false;
339 debug!("<<< consume comment");
348 // it appears this function is called only from pprust... that's
349 // probably not a good thing.
350 pub fn gather_comments_and_literals(sess: &ParseSess, path: FileName, srdr: &mut dyn Read)
351 -> (Vec<Comment>, Vec<Literal>)
353 let mut src = String::new();
354 srdr.read_to_string(&mut src).unwrap();
355 let cm = SourceMap::new(sess.source_map().path_mapping().clone());
356 let source_file = cm.new_source_file(path, src);
357 let mut rdr = lexer::StringReader::new_raw(sess, source_file, None);
359 let mut comments: Vec<Comment> = Vec::new();
360 let mut literals: Vec<Literal> = Vec::new();
361 let mut code_to_the_left = false; // Only code
362 let mut anything_to_the_left = false; // Code or comments
364 while !rdr.is_eof() {
366 // Eat all the whitespace and count blank lines.
367 rdr.consume_non_eol_whitespace();
369 if anything_to_the_left {
370 rdr.bump(); // The line is not blank, do not count.
372 consume_whitespace_counting_blank_lines(&mut rdr, &mut comments);
373 code_to_the_left = false;
374 anything_to_the_left = false;
376 // Eat one comment group
377 if rdr.peeking_at_comment() {
378 consume_comment(&mut rdr, &mut comments,
379 &mut code_to_the_left, &mut anything_to_the_left);
385 let bstart = rdr.pos;
387 // discard, and look ahead; we're working with internal state
388 let TokenAndSpan { tok, sp } = rdr.peek();
390 rdr.with_str_from(bstart, |s| {
391 debug!("tok lit: {}", s);
392 literals.push(Literal {
398 debug!("tok: {}", pprust::token_to_string(&tok));
400 code_to_the_left = true;
401 anything_to_the_left = true;
412 fn test_block_doc_comment_1() {
413 let comment = "/**\n * Test \n ** Test\n * Test\n*/";
414 let stripped = strip_doc_comment_decoration(comment);
415 assert_eq!(stripped, " Test \n* Test\n Test");
419 fn test_block_doc_comment_2() {
420 let comment = "/**\n * Test\n * Test\n*/";
421 let stripped = strip_doc_comment_decoration(comment);
422 assert_eq!(stripped, " Test\n Test");
426 fn test_block_doc_comment_3() {
427 let comment = "/**\n let a: *i32;\n *a = 5;\n*/";
428 let stripped = strip_doc_comment_decoration(comment);
429 assert_eq!(stripped, " let a: *i32;\n *a = 5;");
433 fn test_block_doc_comment_4() {
434 let comment = "/*******************\n test\n *********************/";
435 let stripped = strip_doc_comment_decoration(comment);
436 assert_eq!(stripped, " test");
440 fn test_line_doc_comment() {
441 let stripped = strip_doc_comment_decoration("/// test");
442 assert_eq!(stripped, " test");
443 let stripped = strip_doc_comment_decoration("///! test");
444 assert_eq!(stripped, " test");
445 let stripped = strip_doc_comment_decoration("// test");
446 assert_eq!(stripped, " test");
447 let stripped = strip_doc_comment_decoration("// test");
448 assert_eq!(stripped, " test");
449 let stripped = strip_doc_comment_decoration("///test");
450 assert_eq!(stripped, "test");
451 let stripped = strip_doc_comment_decoration("///!test");
452 assert_eq!(stripped, "test");
453 let stripped = strip_doc_comment_decoration("//test");
454 assert_eq!(stripped, "test");