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_block_doc_comment;
17 use parse::lexer::{StringReader, TokenAndSpan};
18 use parse::lexer::{is_whitespace, Reader};
26 #[derive(Clone, Copy, PartialEq)]
27 pub enum CommentStyle {
28 /// No code on either side of each line of the comment
30 /// Code exists to the left of the comment
32 /// Code before /* foo */ and after the comment
34 /// Just a manual blank line "\n\n", for layout
40 pub style: CommentStyle,
41 pub lines: Vec<String>,
45 pub fn is_doc_comment(s: &str) -> bool {
46 (s.starts_with("///") && super::is_doc_comment(s)) ||
47 s.starts_with("//!") ||
48 (s.starts_with("/**") && is_block_doc_comment(s)) ||
52 pub fn doc_comment_style(comment: &str) -> ast::AttrStyle {
53 assert!(is_doc_comment(comment));
54 if comment.starts_with("//!") || comment.starts_with("/*!") {
61 pub fn strip_doc_comment_decoration(comment: &str) -> String {
62 /// remove whitespace-only lines from the start/end of lines
63 fn vertical_trim(lines: Vec<String>) -> Vec<String> {
65 let mut j = lines.len();
66 // first line of all-stars should be omitted
67 if !lines.is_empty() &&
68 lines[0].chars().all(|c| c == '*') {
71 while i < j && lines[i].trim().is_empty() {
74 // like the first, a last line of all stars should be omitted
75 if j > i && lines[j - 1]
81 while j > i && lines[j - 1].trim().is_empty() {
84 lines[i..j].iter().cloned().collect()
87 /// remove a "[ \t]*\*" block from each line, if possible
88 fn horizontal_trim(lines: Vec<String> ) -> Vec<String> {
89 let mut i = usize::MAX;
90 let mut can_trim = true;
93 for (j, c) in line.chars().enumerate() {
94 if j > i || !"* \t".contains(c) {
117 lines.iter().map(|line| {
118 (&line[i + 1..line.len()]).to_string()
125 // one-line comments lose their prefix
126 const ONELINERS: &'static [&'static str] = &["///!", "///", "//!", "//"];
127 for prefix in ONELINERS {
128 if comment.starts_with(*prefix) {
129 return (&comment[prefix.len()..]).to_string();
133 if comment.starts_with("/*") {
134 let lines = comment[3..comment.len() - 2]
136 .map(|s| s.to_string())
137 .collect::<Vec<String> >();
139 let lines = vertical_trim(lines);
140 let lines = horizontal_trim(lines);
142 return lines.join("\n");
145 panic!("not a doc-comment: {}", comment);
148 fn push_blank_line_comment(rdr: &StringReader, comments: &mut Vec<Comment>) {
149 debug!(">>> blank-line comment");
150 comments.push(Comment {
157 fn consume_whitespace_counting_blank_lines(rdr: &mut StringReader,
158 comments: &mut Vec<Comment>) {
159 while is_whitespace(rdr.curr) && !rdr.is_eof() {
160 if rdr.col == CharPos(0) && rdr.curr_is('\n') {
161 push_blank_line_comment(rdr, &mut *comments);
168 fn read_shebang_comment(rdr: &mut StringReader, code_to_the_left: bool,
169 comments: &mut Vec<Comment>) {
170 debug!(">>> shebang comment");
171 let p = rdr.last_pos;
172 debug!("<<< shebang comment");
173 comments.push(Comment {
174 style: if code_to_the_left { Trailing } else { Isolated },
175 lines: vec!(rdr.read_one_line_comment()),
180 fn read_line_comments(rdr: &mut StringReader, code_to_the_left: bool,
181 comments: &mut Vec<Comment>) {
182 debug!(">>> line comments");
183 let p = rdr.last_pos;
184 let mut lines: Vec<String> = Vec::new();
185 while rdr.curr_is('/') && rdr.nextch_is('/') {
186 let line = rdr.read_one_line_comment();
188 // Doc comments are not put in comments.
189 if is_doc_comment(&line[..]) {
193 rdr.consume_non_eol_whitespace();
195 debug!("<<< line comments");
196 if !lines.is_empty() {
197 comments.push(Comment {
198 style: if code_to_the_left { Trailing } else { Isolated },
205 /// Returns None if the first col chars of s contain a non-whitespace char.
206 /// Otherwise returns Some(k) where k is first char offset after that leading
207 /// whitespace. Note k may be outside bounds of s.
208 fn all_whitespace(s: &str, col: CharPos) -> Option<usize> {
210 let mut col = col.to_usize();
211 let mut cursor: usize = 0;
212 while col > 0 && cursor < len {
213 let ch = char_at(s, cursor);
214 if !ch.is_whitespace() {
217 cursor += ch.len_utf8();
223 fn trim_whitespace_prefix_and_push_line(lines: &mut Vec<String> ,
224 s: String, col: CharPos) {
226 let s1 = match all_whitespace(&s[..], col) {
229 (&s[col..len]).to_string()
236 debug!("pushing line: {}", s1);
240 fn read_block_comment(rdr: &mut StringReader,
241 code_to_the_left: bool,
242 comments: &mut Vec<Comment> ) {
243 debug!(">>> block comment");
244 let p = rdr.last_pos;
245 let mut lines: Vec<String> = Vec::new();
250 let mut curr_line = String::from("/*");
252 // doc-comments are not really comments, they are attributes
253 if (rdr.curr_is('*') && !rdr.nextch_is('*')) || rdr.curr_is('!') {
254 while !(rdr.curr_is('*') && rdr.nextch_is('/')) && !rdr.is_eof() {
255 curr_line.push(rdr.curr.unwrap());
259 curr_line.push_str("*/");
263 if is_block_doc_comment(&curr_line[..]) {
266 assert!(!curr_line.contains('\n'));
267 lines.push(curr_line);
269 let mut level: isize = 1;
271 debug!("=== block comment level {}", level);
273 panic!(rdr.fatal("unterminated block comment"));
275 if rdr.curr_is('\n') {
276 trim_whitespace_prefix_and_push_line(&mut lines,
279 curr_line = String::new();
282 curr_line.push(rdr.curr.unwrap());
283 if rdr.curr_is('/') && rdr.nextch_is('*') {
289 if rdr.curr_is('*') && rdr.nextch_is('/') {
294 } else { rdr.bump(); }
298 if !curr_line.is_empty() {
299 trim_whitespace_prefix_and_push_line(&mut lines,
305 let mut style = if code_to_the_left { Trailing } else { Isolated };
306 rdr.consume_non_eol_whitespace();
307 if !rdr.is_eof() && !rdr.curr_is('\n') && lines.len() == 1 {
310 debug!("<<< block comment");
311 comments.push(Comment {style: style, lines: lines, pos: p});
315 fn consume_comment(rdr: &mut StringReader,
316 code_to_the_left: bool,
317 comments: &mut Vec<Comment> ) {
318 debug!(">>> consume comment");
319 if rdr.curr_is('/') && rdr.nextch_is('/') {
320 read_line_comments(rdr, code_to_the_left, comments);
321 } else if rdr.curr_is('/') && rdr.nextch_is('*') {
322 read_block_comment(rdr, code_to_the_left, comments);
323 } else if rdr.curr_is('#') && rdr.nextch_is('!') {
324 read_shebang_comment(rdr, code_to_the_left, comments);
326 debug!("<<< consume comment");
335 // it appears this function is called only from pprust... that's
336 // probably not a good thing.
337 pub fn gather_comments_and_literals(span_diagnostic: &errors::Handler,
340 -> (Vec<Comment>, Vec<Literal>) {
341 let mut src = Vec::new();
342 srdr.read_to_end(&mut src).unwrap();
343 let src = String::from_utf8(src).unwrap();
344 let cm = CodeMap::new();
345 let filemap = cm.new_filemap(path, src);
346 let mut rdr = lexer::StringReader::new_raw(span_diagnostic, filemap);
348 let mut comments: Vec<Comment> = Vec::new();
349 let mut literals: Vec<Literal> = Vec::new();
350 let mut first_read: bool = true;
351 while !rdr.is_eof() {
353 let mut code_to_the_left = !first_read;
354 rdr.consume_non_eol_whitespace();
355 if rdr.curr_is('\n') {
356 code_to_the_left = false;
357 consume_whitespace_counting_blank_lines(&mut rdr, &mut comments);
359 while rdr.peeking_at_comment() {
360 consume_comment(&mut rdr, code_to_the_left, &mut comments);
361 consume_whitespace_counting_blank_lines(&mut rdr, &mut comments);
367 let bstart = rdr.last_pos;
369 //discard, and look ahead; we're working with internal state
370 let TokenAndSpan { tok, sp } = rdr.peek();
372 rdr.with_str_from(bstart, |s| {
373 debug!("tok lit: {}", s);
374 literals.push(Literal {lit: s.to_string(), pos: sp.lo});
377 debug!("tok: {}", pprust::token_to_string(&tok));
389 #[test] fn test_block_doc_comment_1() {
390 let comment = "/**\n * Test \n ** Test\n * Test\n*/";
391 let stripped = strip_doc_comment_decoration(comment);
392 assert_eq!(stripped, " Test \n* Test\n Test");
395 #[test] fn test_block_doc_comment_2() {
396 let comment = "/**\n * Test\n * Test\n*/";
397 let stripped = strip_doc_comment_decoration(comment);
398 assert_eq!(stripped, " Test\n Test");
401 #[test] fn test_block_doc_comment_3() {
402 let comment = "/**\n let a: *i32;\n *a = 5;\n*/";
403 let stripped = strip_doc_comment_decoration(comment);
404 assert_eq!(stripped, " let a: *i32;\n *a = 5;");
407 #[test] fn test_block_doc_comment_4() {
408 let comment = "/*******************\n test\n *********************/";
409 let stripped = strip_doc_comment_decoration(comment);
410 assert_eq!(stripped, " test");
413 #[test] fn test_line_doc_comment() {
414 let stripped = strip_doc_comment_decoration("/// test");
415 assert_eq!(stripped, " test");
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");