1 // Copyright 2012-2014 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.
11 pub use self::CommentStyle::*;
15 use syntax_pos::{BytePos, CharPos, Pos, FileName};
16 use parse::lexer::{is_block_doc_comment, is_pattern_whitespace};
17 use parse::lexer::{self, ParseSess, StringReader, TokenAndSpan};
24 #[derive(Clone, Copy, PartialEq, Debug)]
25 pub enum CommentStyle {
26 /// No code on either side of each line of the comment
28 /// Code exists to the left of the comment
30 /// Code before /* foo */ and after the comment
32 /// Just a manual blank line "\n\n", for layout
38 pub style: CommentStyle,
39 pub lines: Vec<String>,
43 fn is_doc_comment(s: &str) -> bool {
44 (s.starts_with("///") && super::is_doc_comment(s)) || s.starts_with("//!") ||
45 (s.starts_with("/**") && is_block_doc_comment(s)) || s.starts_with("/*!")
48 pub fn doc_comment_style(comment: &str) -> ast::AttrStyle {
49 assert!(is_doc_comment(comment));
50 if comment.starts_with("//!") || comment.starts_with("/*!") {
57 pub fn strip_doc_comment_decoration(comment: &str) -> String {
58 /// remove whitespace-only lines from the start/end of lines
59 fn vertical_trim(lines: Vec<String>) -> Vec<String> {
61 let mut j = lines.len();
62 // first line of all-stars should be omitted
63 if !lines.is_empty() && lines[0].chars().all(|c| c == '*') {
66 while i < j && lines[i].trim().is_empty() {
69 // like the first, a last line of all stars should be omitted
77 while j > i && lines[j - 1].trim().is_empty() {
83 /// remove a "[ \t]*\*" block from each line, if possible
84 fn horizontal_trim(lines: Vec<String>) -> Vec<String> {
85 let mut i = usize::MAX;
86 let mut can_trim = true;
89 for (j, c) in line.chars().enumerate() {
90 if j > i || !"* \t".contains(c) {
114 .map(|line| (&line[i + 1..line.len()]).to_string())
121 // one-line comments lose their prefix
122 const ONELINERS: &'static [&'static str] = &["///!", "///", "//!", "//"];
123 for prefix in ONELINERS {
124 if comment.starts_with(*prefix) {
125 return (&comment[prefix.len()..]).to_string();
129 if comment.starts_with("/*") {
130 let lines = comment[3..comment.len() - 2]
132 .map(|s| s.to_string())
133 .collect::<Vec<String>>();
135 let lines = vertical_trim(lines);
136 let lines = horizontal_trim(lines);
138 return lines.join("\n");
141 panic!("not a doc-comment: {}", comment);
144 fn push_blank_line_comment(rdr: &StringReader, comments: &mut Vec<Comment>) {
145 debug!(">>> blank-line comment");
146 comments.push(Comment {
153 fn consume_whitespace_counting_blank_lines(rdr: &mut StringReader, comments: &mut Vec<Comment>) {
154 while is_pattern_whitespace(rdr.ch) && !rdr.is_eof() {
156 push_blank_line_comment(rdr, &mut *comments);
162 fn read_shebang_comment(rdr: &mut StringReader,
163 code_to_the_left: bool,
164 comments: &mut Vec<Comment>) {
165 debug!(">>> shebang comment");
167 debug!("<<< shebang comment");
168 comments.push(Comment {
169 style: if code_to_the_left { Trailing } else { Isolated },
170 lines: vec![rdr.read_one_line_comment()],
175 fn read_line_comments(rdr: &mut StringReader,
176 code_to_the_left: bool,
177 comments: &mut Vec<Comment>) {
178 debug!(">>> line comments");
180 let mut lines: Vec<String> = Vec::new();
181 while rdr.ch_is('/') && rdr.nextch_is('/') {
182 let line = rdr.read_one_line_comment();
184 // Doc comments are not put in comments.
185 if is_doc_comment(&line[..]) {
189 rdr.consume_non_eol_whitespace();
191 debug!("<<< line comments");
192 if !lines.is_empty() {
193 comments.push(Comment {
194 style: if code_to_the_left { Trailing } else { Isolated },
201 /// Returns None if the first col chars of s contain a non-whitespace char.
202 /// Otherwise returns Some(k) where k is first char offset after that leading
203 /// whitespace. Note k may be outside bounds of s.
204 fn all_whitespace(s: &str, col: CharPos) -> Option<usize> {
206 let mut col = col.to_usize();
207 let mut cursor: usize = 0;
208 while col > 0 && cursor < len {
209 let ch = char_at(s, cursor);
210 if !ch.is_whitespace() {
213 cursor += ch.len_utf8();
219 fn trim_whitespace_prefix_and_push_line(lines: &mut Vec<String>, s: String, col: CharPos) {
221 let s1 = match all_whitespace(&s[..], col) {
224 (&s[col..len]).to_string()
231 debug!("pushing line: {}", s1);
235 fn read_block_comment(rdr: &mut StringReader,
236 code_to_the_left: bool,
237 comments: &mut Vec<Comment>) {
238 debug!(">>> block comment");
240 let mut lines: Vec<String> = Vec::new();
242 // Count the number of chars since the start of the line by rescanning.
243 let mut src_index = rdr.src_index(rdr.filemap.line_begin_pos(rdr.pos));
244 let end_src_index = rdr.src_index(rdr.pos);
245 assert!(src_index <= end_src_index,
246 "src_index={}, end_src_index={}, line_begin_pos={}",
247 src_index, end_src_index, rdr.filemap.line_begin_pos(rdr.pos).to_u32());
249 while src_index < end_src_index {
250 let c = char_at(&rdr.src, src_index);
251 src_index += c.len_utf8();
254 let col = CharPos(n);
259 let mut curr_line = String::from("/*");
261 // doc-comments are not really comments, they are attributes
262 if (rdr.ch_is('*') && !rdr.nextch_is('*')) || rdr.ch_is('!') {
263 while !(rdr.ch_is('*') && rdr.nextch_is('/')) && !rdr.is_eof() {
264 curr_line.push(rdr.ch.unwrap());
268 curr_line.push_str("*/");
272 if is_block_doc_comment(&curr_line[..]) {
275 assert!(!curr_line.contains('\n'));
276 lines.push(curr_line);
278 let mut level: isize = 1;
280 debug!("=== block comment level {}", level);
282 rdr.fatal("unterminated block comment").raise();
285 trim_whitespace_prefix_and_push_line(&mut lines, curr_line, col);
286 curr_line = String::new();
289 curr_line.push(rdr.ch.unwrap());
290 if rdr.ch_is('/') && rdr.nextch_is('*') {
296 if rdr.ch_is('*') && rdr.nextch_is('/') {
307 if !curr_line.is_empty() {
308 trim_whitespace_prefix_and_push_line(&mut lines, curr_line, col);
312 let mut style = if code_to_the_left {
317 rdr.consume_non_eol_whitespace();
318 if !rdr.is_eof() && !rdr.ch_is('\n') && lines.len() == 1 {
321 debug!("<<< block comment");
322 comments.push(Comment {
330 fn consume_comment(rdr: &mut StringReader,
331 comments: &mut Vec<Comment>,
332 code_to_the_left: &mut bool,
333 anything_to_the_left: &mut bool) {
334 debug!(">>> consume comment");
335 if rdr.ch_is('/') && rdr.nextch_is('/') {
336 read_line_comments(rdr, *code_to_the_left, comments);
337 *code_to_the_left = false;
338 *anything_to_the_left = false;
339 } else if rdr.ch_is('/') && rdr.nextch_is('*') {
340 read_block_comment(rdr, *code_to_the_left, comments);
341 *anything_to_the_left = true;
342 } else if rdr.ch_is('#') && rdr.nextch_is('!') {
343 read_shebang_comment(rdr, *code_to_the_left, comments);
344 *code_to_the_left = false;
345 *anything_to_the_left = false;
349 debug!("<<< consume comment");
358 // it appears this function is called only from pprust... that's
359 // probably not a good thing.
360 pub fn gather_comments_and_literals(sess: &ParseSess, path: FileName, srdr: &mut dyn Read)
361 -> (Vec<Comment>, Vec<Literal>) {
362 let mut src = Vec::new();
363 srdr.read_to_end(&mut src).unwrap();
364 let src = String::from_utf8(src).unwrap();
365 let cm = CodeMap::new(sess.codemap().path_mapping().clone());
366 let filemap = cm.new_filemap(path, src);
367 let mut rdr = lexer::StringReader::new_raw(sess, filemap, None);
369 let mut comments: Vec<Comment> = Vec::new();
370 let mut literals: Vec<Literal> = Vec::new();
371 let mut code_to_the_left = false; // Only code
372 let mut anything_to_the_left = false; // Code or comments
373 while !rdr.is_eof() {
375 // Eat all the whitespace and count blank lines.
376 rdr.consume_non_eol_whitespace();
378 if anything_to_the_left {
379 rdr.bump(); // The line is not blank, do not count.
381 consume_whitespace_counting_blank_lines(&mut rdr, &mut comments);
382 code_to_the_left = false;
383 anything_to_the_left = false;
385 // Eat one comment group
386 if rdr.peeking_at_comment() {
387 consume_comment(&mut rdr, &mut comments,
388 &mut code_to_the_left, &mut anything_to_the_left);
394 let bstart = rdr.pos;
396 // discard, and look ahead; we're working with internal state
397 let TokenAndSpan { tok, sp } = rdr.peek();
399 rdr.with_str_from(bstart, |s| {
400 debug!("tok lit: {}", s);
401 literals.push(Literal {
407 debug!("tok: {}", pprust::token_to_string(&tok));
409 code_to_the_left = true;
410 anything_to_the_left = true;
421 fn test_block_doc_comment_1() {
422 let comment = "/**\n * Test \n ** Test\n * Test\n*/";
423 let stripped = strip_doc_comment_decoration(comment);
424 assert_eq!(stripped, " Test \n* Test\n Test");
428 fn test_block_doc_comment_2() {
429 let comment = "/**\n * Test\n * Test\n*/";
430 let stripped = strip_doc_comment_decoration(comment);
431 assert_eq!(stripped, " Test\n Test");
435 fn test_block_doc_comment_3() {
436 let comment = "/**\n let a: *i32;\n *a = 5;\n*/";
437 let stripped = strip_doc_comment_decoration(comment);
438 assert_eq!(stripped, " let a: *i32;\n *a = 5;");
442 fn test_block_doc_comment_4() {
443 let comment = "/*******************\n test\n *********************/";
444 let stripped = strip_doc_comment_decoration(comment);
445 assert_eq!(stripped, " test");
449 fn test_line_doc_comment() {
450 let stripped = strip_doc_comment_decoration("/// test");
451 assert_eq!(stripped, " test");
452 let stripped = strip_doc_comment_decoration("///! test");
453 assert_eq!(stripped, " test");
454 let stripped = strip_doc_comment_decoration("// test");
455 assert_eq!(stripped, " test");
456 let stripped = strip_doc_comment_decoration("// test");
457 assert_eq!(stripped, " test");
458 let stripped = strip_doc_comment_decoration("///test");
459 assert_eq!(stripped, "test");
460 let stripped = strip_doc_comment_decoration("///!test");
461 assert_eq!(stripped, "test");
462 let stripped = strip_doc_comment_decoration("//test");
463 assert_eq!(stripped, "test");