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::*;
14 use codemap::{BytePos, CharPos, CodeMap, Pos};
16 use parse::lexer::{is_whitespace, Reader};
17 use parse::lexer::{StringReader, TokenAndSpan};
18 use parse::lexer::is_block_doc_comment;
24 use std::string::String;
27 #[deriving(Clone, PartialEq)]
28 pub enum CommentStyle {
29 /// No code on either side of each line of the comment
31 /// Code exists to the left of the comment
33 /// Code before /* foo */ and after the comment
35 /// Just a manual blank line "\n\n", for layout
39 impl Copy for CommentStyle {}
43 pub style: CommentStyle,
44 pub lines: Vec<String>,
48 pub fn is_doc_comment(s: &str) -> bool {
49 (s.starts_with("///") && super::is_doc_comment(s)) ||
50 s.starts_with("//!") ||
51 (s.starts_with("/**") && is_block_doc_comment(s)) ||
55 pub fn doc_comment_style(comment: &str) -> ast::AttrStyle {
56 assert!(is_doc_comment(comment));
57 if comment.starts_with("//!") || comment.starts_with("/*!") {
64 pub fn strip_doc_comment_decoration(comment: &str) -> String {
65 /// remove whitespace-only lines from the start/end of lines
66 fn vertical_trim(lines: Vec<String> ) -> Vec<String> {
68 let mut j = lines.len();
69 // first line of all-stars should be omitted
71 lines[0].chars().all(|c| c == '*') {
74 while i < j && lines[i].trim().is_empty() {
77 // like the first, a last line of all stars should be omitted
78 if j > i && lines[j - 1]
84 while j > i && lines[j - 1].trim().is_empty() {
87 return lines.slice(i, j).iter().map(|x| (*x).clone()).collect();
90 /// remove a "[ \t]*\*" block from each line, if possible
91 fn horizontal_trim(lines: Vec<String> ) -> Vec<String> {
92 let mut i = uint::MAX;
93 let mut can_trim = true;
95 for line in lines.iter() {
96 for (j, c) in line.chars().enumerate() {
97 if j > i || !"* \t".contains_char(c) {
120 lines.iter().map(|line| {
121 line.slice(i + 1, line.len()).to_string()
128 // one-line comments lose their prefix
129 static ONLINERS: &'static [&'static str] = &["///!", "///", "//!", "//"];
130 for prefix in ONLINERS.iter() {
131 if comment.starts_with(*prefix) {
132 return comment.slice_from(prefix.len()).to_string();
136 if comment.starts_with("/*") {
137 let lines = comment.slice(3u, comment.len() - 2u)
139 .map(|s| s.to_string())
140 .collect::<Vec<String> >();
142 let lines = vertical_trim(lines);
143 let lines = horizontal_trim(lines);
145 return lines.connect("\n");
148 panic!("not a doc-comment: {}", comment);
151 fn push_blank_line_comment(rdr: &StringReader, comments: &mut Vec<Comment>) {
152 debug!(">>> blank-line comment");
153 comments.push(Comment {
160 fn consume_whitespace_counting_blank_lines(rdr: &mut StringReader,
161 comments: &mut Vec<Comment>) {
162 while is_whitespace(rdr.curr) && !rdr.is_eof() {
163 if rdr.col == CharPos(0u) && rdr.curr_is('\n') {
164 push_blank_line_comment(rdr, &mut *comments);
171 fn read_shebang_comment(rdr: &mut StringReader, code_to_the_left: bool,
172 comments: &mut Vec<Comment>) {
173 debug!(">>> shebang comment");
174 let p = rdr.last_pos;
175 debug!("<<< shebang comment");
176 comments.push(Comment {
177 style: if code_to_the_left { Trailing } else { Isolated },
178 lines: vec!(rdr.read_one_line_comment()),
183 fn read_line_comments(rdr: &mut StringReader, code_to_the_left: bool,
184 comments: &mut Vec<Comment>) {
185 debug!(">>> line comments");
186 let p = rdr.last_pos;
187 let mut lines: Vec<String> = Vec::new();
188 while rdr.curr_is('/') && rdr.nextch_is('/') {
189 let line = rdr.read_one_line_comment();
191 // Doc comments are not put in comments.
192 if is_doc_comment(line.as_slice()) {
196 rdr.consume_non_eol_whitespace();
198 debug!("<<< line comments");
199 if !lines.is_empty() {
200 comments.push(Comment {
201 style: if code_to_the_left { Trailing } else { Isolated },
208 /// Returns None if the first col chars of s contain a non-whitespace char.
209 /// Otherwise returns Some(k) where k is first char offset after that leading
210 /// whitespace. Note k may be outside bounds of s.
211 fn all_whitespace(s: &str, col: CharPos) -> Option<uint> {
213 let mut col = col.to_uint();
214 let mut cursor: uint = 0;
215 while col > 0 && cursor < len {
216 let r: str::CharRange = s.char_range_at(cursor);
217 if !r.ch.is_whitespace() {
226 fn trim_whitespace_prefix_and_push_line(lines: &mut Vec<String> ,
227 s: String, col: CharPos) {
229 let s1 = match all_whitespace(s.as_slice(), col) {
232 s.slice(col, len).to_string()
239 debug!("pushing line: {}", s1);
243 fn read_block_comment(rdr: &mut StringReader,
244 code_to_the_left: bool,
245 comments: &mut Vec<Comment> ) {
246 debug!(">>> block comment");
247 let p = rdr.last_pos;
248 let mut lines: Vec<String> = Vec::new();
253 let mut curr_line = String::from_str("/*");
255 // doc-comments are not really comments, they are attributes
256 if (rdr.curr_is('*') && !rdr.nextch_is('*')) || rdr.curr_is('!') {
257 while !(rdr.curr_is('*') && rdr.nextch_is('/')) && !rdr.is_eof() {
258 curr_line.push(rdr.curr.unwrap());
262 curr_line.push_str("*/");
266 if is_block_doc_comment(curr_line.as_slice()) {
269 assert!(!curr_line.contains_char('\n'));
270 lines.push(curr_line);
272 let mut level: int = 1;
274 debug!("=== block comment level {}", level);
276 rdr.fatal("unterminated block comment");
278 if rdr.curr_is('\n') {
279 trim_whitespace_prefix_and_push_line(&mut lines,
282 curr_line = String::new();
285 curr_line.push(rdr.curr.unwrap());
286 if rdr.curr_is('/') && rdr.nextch_is('*') {
292 if rdr.curr_is('*') && rdr.nextch_is('/') {
297 } else { rdr.bump(); }
301 if curr_line.len() != 0 {
302 trim_whitespace_prefix_and_push_line(&mut lines,
308 let mut style = if code_to_the_left { Trailing } else { Isolated };
309 rdr.consume_non_eol_whitespace();
310 if !rdr.is_eof() && !rdr.curr_is('\n') && lines.len() == 1u {
313 debug!("<<< block comment");
314 comments.push(Comment {style: style, lines: lines, pos: p});
318 fn consume_comment(rdr: &mut StringReader,
319 code_to_the_left: bool,
320 comments: &mut Vec<Comment> ) {
321 debug!(">>> consume comment");
322 if rdr.curr_is('/') && rdr.nextch_is('/') {
323 read_line_comments(rdr, code_to_the_left, comments);
324 } else if rdr.curr_is('/') && rdr.nextch_is('*') {
325 read_block_comment(rdr, code_to_the_left, comments);
326 } else if rdr.curr_is('#') && rdr.nextch_is('!') {
327 read_shebang_comment(rdr, code_to_the_left, comments);
329 debug!("<<< consume comment");
338 // it appears this function is called only from pprust... that's
339 // probably not a good thing.
340 pub fn gather_comments_and_literals(span_diagnostic: &diagnostic::SpanHandler,
342 srdr: &mut io::Reader)
343 -> (Vec<Comment>, Vec<Literal>) {
344 let src = srdr.read_to_end().unwrap();
345 let src = String::from_utf8(src).unwrap();
346 let cm = CodeMap::new();
347 let filemap = cm.new_filemap(path, src);
348 let mut rdr = lexer::StringReader::new_raw(span_diagnostic, filemap);
350 let mut comments: Vec<Comment> = Vec::new();
351 let mut literals: Vec<Literal> = Vec::new();
352 let mut first_read: bool = true;
353 while !rdr.is_eof() {
355 let mut code_to_the_left = !first_read;
356 rdr.consume_non_eol_whitespace();
357 if rdr.curr_is('\n') {
358 code_to_the_left = false;
359 consume_whitespace_counting_blank_lines(&mut rdr, &mut comments);
361 while rdr.peeking_at_comment() {
362 consume_comment(&mut rdr, code_to_the_left, &mut comments);
363 consume_whitespace_counting_blank_lines(&mut rdr, &mut comments);
369 let bstart = rdr.last_pos;
371 //discard, and look ahead; we're working with internal state
372 let TokenAndSpan { tok, sp } = rdr.peek();
374 rdr.with_str_from(bstart, |s| {
375 debug!("tok lit: {}", s);
376 literals.push(Literal {lit: s.to_string(), pos: sp.lo});
379 debug!("tok: {}", pprust::token_to_string(&tok));
391 #[test] fn test_block_doc_comment_1() {
392 let comment = "/**\n * Test \n ** Test\n * Test\n*/";
393 let stripped = strip_doc_comment_decoration(comment);
394 assert_eq!(stripped, " Test \n* Test\n Test");
397 #[test] fn test_block_doc_comment_2() {
398 let comment = "/**\n * Test\n * Test\n*/";
399 let stripped = strip_doc_comment_decoration(comment);
400 assert_eq!(stripped, " Test\n Test");
403 #[test] fn test_block_doc_comment_3() {
404 let comment = "/**\n let a: *int;\n *a = 5;\n*/";
405 let stripped = strip_doc_comment_decoration(comment);
406 assert_eq!(stripped, " let a: *int;\n *a = 5;");
409 #[test] fn test_block_doc_comment_4() {
410 let comment = "/*******************\n test\n *********************/";
411 let stripped = strip_doc_comment_decoration(comment);
412 assert_eq!(stripped, " test");
415 #[test] fn test_line_doc_comment() {
416 let stripped = strip_doc_comment_decoration("/// test");
417 assert_eq!(stripped, " test");
418 let stripped = strip_doc_comment_decoration("///! test");
419 assert_eq!(stripped, " test");
420 let stripped = strip_doc_comment_decoration("// test");
421 assert_eq!(stripped, " test");
422 let stripped = strip_doc_comment_decoration("// test");
423 assert_eq!(stripped, " test");
424 let stripped = strip_doc_comment_decoration("///test");
425 assert_eq!(stripped, "test");
426 let stripped = strip_doc_comment_decoration("///!test");
427 assert_eq!(stripped, "test");
428 let stripped = strip_doc_comment_decoration("//test");
429 assert_eq!(stripped, "test");