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};
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 pub 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();
245 let mut curr_line = String::from("/*");
247 // doc-comments are not really comments, they are attributes
248 if (rdr.ch_is('*') && !rdr.nextch_is('*')) || rdr.ch_is('!') {
249 while !(rdr.ch_is('*') && rdr.nextch_is('/')) && !rdr.is_eof() {
250 curr_line.push(rdr.ch.unwrap());
254 curr_line.push_str("*/");
258 if is_block_doc_comment(&curr_line[..]) {
261 assert!(!curr_line.contains('\n'));
262 lines.push(curr_line);
264 let mut level: isize = 1;
266 debug!("=== block comment level {}", level);
268 panic!(rdr.fatal("unterminated block comment"));
271 trim_whitespace_prefix_and_push_line(&mut lines, curr_line, col);
272 curr_line = String::new();
275 curr_line.push(rdr.ch.unwrap());
276 if rdr.ch_is('/') && rdr.nextch_is('*') {
282 if rdr.ch_is('*') && rdr.nextch_is('/') {
293 if !curr_line.is_empty() {
294 trim_whitespace_prefix_and_push_line(&mut lines, curr_line, col);
298 let mut style = if code_to_the_left {
303 rdr.consume_non_eol_whitespace();
304 if !rdr.is_eof() && !rdr.ch_is('\n') && lines.len() == 1 {
307 debug!("<<< block comment");
308 comments.push(Comment {
316 fn consume_comment(rdr: &mut StringReader,
317 comments: &mut Vec<Comment>,
318 code_to_the_left: &mut bool,
319 anything_to_the_left: &mut bool) {
320 debug!(">>> consume comment");
321 if rdr.ch_is('/') && rdr.nextch_is('/') {
322 read_line_comments(rdr, *code_to_the_left, comments);
323 *code_to_the_left = false;
324 *anything_to_the_left = false;
325 } else if rdr.ch_is('/') && rdr.nextch_is('*') {
326 read_block_comment(rdr, *code_to_the_left, comments);
327 *anything_to_the_left = true;
328 } else if rdr.ch_is('#') && rdr.nextch_is('!') {
329 read_shebang_comment(rdr, *code_to_the_left, comments);
330 *code_to_the_left = false;
331 *anything_to_the_left = false;
335 debug!("<<< consume comment");
344 // it appears this function is called only from pprust... that's
345 // probably not a good thing.
346 pub fn gather_comments_and_literals(sess: &ParseSess, path: String, srdr: &mut Read)
347 -> (Vec<Comment>, Vec<Literal>) {
348 let mut src = Vec::new();
349 srdr.read_to_end(&mut src).unwrap();
350 let src = String::from_utf8(src).unwrap();
351 let cm = CodeMap::new(sess.codemap().path_mapping().clone());
352 let filemap = cm.new_filemap(path, src);
353 let mut rdr = lexer::StringReader::new_raw(sess, filemap);
355 let mut comments: Vec<Comment> = Vec::new();
356 let mut literals: Vec<Literal> = Vec::new();
357 let mut code_to_the_left = false; // Only code
358 let mut anything_to_the_left = false; // Code or comments
359 while !rdr.is_eof() {
361 // Eat all the whitespace and count blank lines.
362 rdr.consume_non_eol_whitespace();
364 if anything_to_the_left {
365 rdr.bump(); // The line is not blank, do not count.
367 consume_whitespace_counting_blank_lines(&mut rdr, &mut comments);
368 code_to_the_left = false;
369 anything_to_the_left = false;
371 // Eat one comment group
372 if rdr.peeking_at_comment() {
373 consume_comment(&mut rdr, &mut comments,
374 &mut code_to_the_left, &mut anything_to_the_left);
380 let bstart = rdr.pos;
382 // discard, and look ahead; we're working with internal state
383 let TokenAndSpan { tok, sp } = rdr.peek();
385 rdr.with_str_from(bstart, |s| {
386 debug!("tok lit: {}", s);
387 literals.push(Literal {
393 debug!("tok: {}", pprust::token_to_string(&tok));
395 code_to_the_left = true;
396 anything_to_the_left = true;
407 fn test_block_doc_comment_1() {
408 let comment = "/**\n * Test \n ** Test\n * Test\n*/";
409 let stripped = strip_doc_comment_decoration(comment);
410 assert_eq!(stripped, " Test \n* Test\n Test");
414 fn test_block_doc_comment_2() {
415 let comment = "/**\n * Test\n * Test\n*/";
416 let stripped = strip_doc_comment_decoration(comment);
417 assert_eq!(stripped, " Test\n Test");
421 fn test_block_doc_comment_3() {
422 let comment = "/**\n let a: *i32;\n *a = 5;\n*/";
423 let stripped = strip_doc_comment_decoration(comment);
424 assert_eq!(stripped, " let a: *i32;\n *a = 5;");
428 fn test_block_doc_comment_4() {
429 let comment = "/*******************\n test\n *********************/";
430 let stripped = strip_doc_comment_decoration(comment);
431 assert_eq!(stripped, " test");
435 fn test_line_doc_comment() {
436 let stripped = strip_doc_comment_decoration("/// test");
437 assert_eq!(stripped, " test");
438 let stripped = strip_doc_comment_decoration("///! test");
439 assert_eq!(stripped, " test");
440 let stripped = strip_doc_comment_decoration("// test");
441 assert_eq!(stripped, " test");
442 let stripped = strip_doc_comment_decoration("// test");
443 assert_eq!(stripped, " test");
444 let stripped = strip_doc_comment_decoration("///test");
445 assert_eq!(stripped, "test");
446 let stripped = strip_doc_comment_decoration("///!test");
447 assert_eq!(stripped, "test");
448 let stripped = strip_doc_comment_decoration("//test");
449 assert_eq!(stripped, "test");