]> git.proxmox.com Git - rustc.git/blame - src/libsyntax/parse/lexer/comments.rs
New upstream version 1.14.0+dfsg1
[rustc.git] / src / libsyntax / parse / lexer / comments.rs
CommitLineData
1a4d82fc
JJ
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.
4//
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.
10
11pub use self::CommentStyle::*;
12
13use ast;
3157f602
XL
14use codemap::CodeMap;
15use syntax_pos::{BytePos, CharPos, Pos};
9cc50fc6 16use errors;
1a4d82fc 17use parse::lexer::is_block_doc_comment;
d9579d0f 18use parse::lexer::{StringReader, TokenAndSpan};
54a0048b 19use parse::lexer::{is_pattern_whitespace, Reader};
1a4d82fc
JJ
20use parse::lexer;
21use print::pprust;
d9579d0f 22use str::char_at;
1a4d82fc 23
c34b1796 24use std::io::Read;
85aaf69f 25use std::usize;
1a4d82fc 26
c30ab7b3 27#[derive(Clone, Copy, PartialEq, Debug)]
1a4d82fc
JJ
28pub enum CommentStyle {
29 /// No code on either side of each line of the comment
30 Isolated,
31 /// Code exists to the left of the comment
32 Trailing,
33 /// Code before /* foo */ and after the comment
34 Mixed,
35 /// Just a manual blank line "\n\n", for layout
36 BlankLine,
37}
38
39#[derive(Clone)]
40pub struct Comment {
41 pub style: CommentStyle,
42 pub lines: Vec<String>,
43 pub pos: BytePos,
44}
45
46pub fn is_doc_comment(s: &str) -> bool {
9cc50fc6
SL
47 (s.starts_with("///") && super::is_doc_comment(s)) || s.starts_with("//!") ||
48 (s.starts_with("/**") && is_block_doc_comment(s)) || s.starts_with("/*!")
1a4d82fc
JJ
49}
50
51pub fn doc_comment_style(comment: &str) -> ast::AttrStyle {
52 assert!(is_doc_comment(comment));
53 if comment.starts_with("//!") || comment.starts_with("/*!") {
b039eaaf 54 ast::AttrStyle::Inner
1a4d82fc 55 } else {
b039eaaf 56 ast::AttrStyle::Outer
1a4d82fc
JJ
57 }
58}
59
60pub fn strip_doc_comment_decoration(comment: &str) -> String {
61 /// remove whitespace-only lines from the start/end of lines
85aaf69f
SL
62 fn vertical_trim(lines: Vec<String>) -> Vec<String> {
63 let mut i = 0;
1a4d82fc
JJ
64 let mut j = lines.len();
65 // first line of all-stars should be omitted
9cc50fc6 66 if !lines.is_empty() && lines[0].chars().all(|c| c == '*') {
1a4d82fc
JJ
67 i += 1;
68 }
69 while i < j && lines[i].trim().is_empty() {
70 i += 1;
71 }
72 // like the first, a last line of all stars should be omitted
9cc50fc6
SL
73 if j > i &&
74 lines[j - 1]
75 .chars()
76 .skip(1)
77 .all(|c| c == '*') {
1a4d82fc
JJ
78 j -= 1;
79 }
80 while j > i && lines[j - 1].trim().is_empty() {
81 j -= 1;
82 }
85aaf69f 83 lines[i..j].iter().cloned().collect()
1a4d82fc
JJ
84 }
85
86 /// remove a "[ \t]*\*" block from each line, if possible
9cc50fc6 87 fn horizontal_trim(lines: Vec<String>) -> Vec<String> {
85aaf69f 88 let mut i = usize::MAX;
1a4d82fc
JJ
89 let mut can_trim = true;
90 let mut first = true;
85aaf69f 91 for line in &lines {
1a4d82fc 92 for (j, c) in line.chars().enumerate() {
c34b1796 93 if j > i || !"* \t".contains(c) {
1a4d82fc
JJ
94 can_trim = false;
95 break;
96 }
97 if c == '*' {
98 if first {
99 i = j;
100 first = false;
101 } else if i != j {
102 can_trim = false;
103 }
104 break;
105 }
106 }
107 if i > line.len() {
108 can_trim = false;
109 }
110 if !can_trim {
111 break;
112 }
113 }
114
115 if can_trim {
9cc50fc6
SL
116 lines.iter()
117 .map(|line| (&line[i + 1..line.len()]).to_string())
118 .collect()
1a4d82fc
JJ
119 } else {
120 lines
121 }
122 }
123
124 // one-line comments lose their prefix
c34b1796
AL
125 const ONELINERS: &'static [&'static str] = &["///!", "///", "//!", "//"];
126 for prefix in ONELINERS {
1a4d82fc
JJ
127 if comment.starts_with(*prefix) {
128 return (&comment[prefix.len()..]).to_string();
129 }
130 }
131
132 if comment.starts_with("/*") {
85aaf69f 133 let lines = comment[3..comment.len() - 2]
9cc50fc6
SL
134 .lines()
135 .map(|s| s.to_string())
136 .collect::<Vec<String>>();
1a4d82fc
JJ
137
138 let lines = vertical_trim(lines);
139 let lines = horizontal_trim(lines);
140
c1a9b12d 141 return lines.join("\n");
1a4d82fc
JJ
142 }
143
144 panic!("not a doc-comment: {}", comment);
145}
146
147fn push_blank_line_comment(rdr: &StringReader, comments: &mut Vec<Comment>) {
148 debug!(">>> blank-line comment");
149 comments.push(Comment {
150 style: BlankLine,
151 lines: Vec::new(),
c30ab7b3 152 pos: rdr.pos,
1a4d82fc
JJ
153 });
154}
155
9cc50fc6 156fn consume_whitespace_counting_blank_lines(rdr: &mut StringReader, comments: &mut Vec<Comment>) {
c30ab7b3
SL
157 while is_pattern_whitespace(rdr.ch) && !rdr.is_eof() {
158 if rdr.ch_is('\n') {
1a4d82fc
JJ
159 push_blank_line_comment(rdr, &mut *comments);
160 }
161 rdr.bump();
162 }
163}
164
9cc50fc6
SL
165fn read_shebang_comment(rdr: &mut StringReader,
166 code_to_the_left: bool,
1a4d82fc
JJ
167 comments: &mut Vec<Comment>) {
168 debug!(">>> shebang comment");
c30ab7b3 169 let p = rdr.pos;
1a4d82fc
JJ
170 debug!("<<< shebang comment");
171 comments.push(Comment {
172 style: if code_to_the_left { Trailing } else { Isolated },
9cc50fc6
SL
173 lines: vec![rdr.read_one_line_comment()],
174 pos: p,
1a4d82fc
JJ
175 });
176}
177
9cc50fc6
SL
178fn read_line_comments(rdr: &mut StringReader,
179 code_to_the_left: bool,
1a4d82fc
JJ
180 comments: &mut Vec<Comment>) {
181 debug!(">>> line comments");
c30ab7b3 182 let p = rdr.pos;
1a4d82fc 183 let mut lines: Vec<String> = Vec::new();
c30ab7b3 184 while rdr.ch_is('/') && rdr.nextch_is('/') {
1a4d82fc
JJ
185 let line = rdr.read_one_line_comment();
186 debug!("{}", line);
187 // Doc comments are not put in comments.
85aaf69f 188 if is_doc_comment(&line[..]) {
1a4d82fc
JJ
189 break;
190 }
191 lines.push(line);
192 rdr.consume_non_eol_whitespace();
193 }
194 debug!("<<< line comments");
195 if !lines.is_empty() {
196 comments.push(Comment {
197 style: if code_to_the_left { Trailing } else { Isolated },
198 lines: lines,
9cc50fc6 199 pos: p,
1a4d82fc
JJ
200 });
201 }
202}
203
204/// Returns None if the first col chars of s contain a non-whitespace char.
205/// Otherwise returns Some(k) where k is first char offset after that leading
206/// whitespace. Note k may be outside bounds of s.
85aaf69f 207fn all_whitespace(s: &str, col: CharPos) -> Option<usize> {
1a4d82fc 208 let len = s.len();
85aaf69f
SL
209 let mut col = col.to_usize();
210 let mut cursor: usize = 0;
1a4d82fc 211 while col > 0 && cursor < len {
d9579d0f 212 let ch = char_at(s, cursor);
c34b1796 213 if !ch.is_whitespace() {
1a4d82fc
JJ
214 return None;
215 }
c34b1796 216 cursor += ch.len_utf8();
1a4d82fc
JJ
217 col -= 1;
218 }
219 return Some(cursor);
220}
221
9cc50fc6 222fn trim_whitespace_prefix_and_push_line(lines: &mut Vec<String>, s: String, col: CharPos) {
1a4d82fc 223 let len = s.len();
85aaf69f 224 let s1 = match all_whitespace(&s[..], col) {
1a4d82fc
JJ
225 Some(col) => {
226 if col < len {
227 (&s[col..len]).to_string()
228 } else {
229 "".to_string()
230 }
231 }
232 None => s,
233 };
234 debug!("pushing line: {}", s1);
235 lines.push(s1);
236}
237
238fn read_block_comment(rdr: &mut StringReader,
239 code_to_the_left: bool,
9cc50fc6 240 comments: &mut Vec<Comment>) {
1a4d82fc 241 debug!(">>> block comment");
c30ab7b3 242 let p = rdr.pos;
1a4d82fc
JJ
243 let mut lines: Vec<String> = Vec::new();
244 let col = rdr.col;
245 rdr.bump();
246 rdr.bump();
247
d9579d0f 248 let mut curr_line = String::from("/*");
1a4d82fc
JJ
249
250 // doc-comments are not really comments, they are attributes
c30ab7b3
SL
251 if (rdr.ch_is('*') && !rdr.nextch_is('*')) || rdr.ch_is('!') {
252 while !(rdr.ch_is('*') && rdr.nextch_is('/')) && !rdr.is_eof() {
253 curr_line.push(rdr.ch.unwrap());
1a4d82fc
JJ
254 rdr.bump();
255 }
256 if !rdr.is_eof() {
257 curr_line.push_str("*/");
258 rdr.bump();
259 rdr.bump();
260 }
85aaf69f 261 if is_block_doc_comment(&curr_line[..]) {
9cc50fc6 262 return;
1a4d82fc 263 }
c34b1796 264 assert!(!curr_line.contains('\n'));
1a4d82fc
JJ
265 lines.push(curr_line);
266 } else {
85aaf69f 267 let mut level: isize = 1;
1a4d82fc
JJ
268 while level > 0 {
269 debug!("=== block comment level {}", level);
270 if rdr.is_eof() {
92a42be0 271 panic!(rdr.fatal("unterminated block comment"));
1a4d82fc 272 }
c30ab7b3 273 if rdr.ch_is('\n') {
9cc50fc6 274 trim_whitespace_prefix_and_push_line(&mut lines, curr_line, col);
1a4d82fc
JJ
275 curr_line = String::new();
276 rdr.bump();
277 } else {
c30ab7b3
SL
278 curr_line.push(rdr.ch.unwrap());
279 if rdr.ch_is('/') && rdr.nextch_is('*') {
1a4d82fc
JJ
280 rdr.bump();
281 rdr.bump();
282 curr_line.push('*');
283 level += 1;
284 } else {
c30ab7b3 285 if rdr.ch_is('*') && rdr.nextch_is('/') {
1a4d82fc
JJ
286 rdr.bump();
287 rdr.bump();
288 curr_line.push('/');
289 level -= 1;
9cc50fc6
SL
290 } else {
291 rdr.bump();
292 }
1a4d82fc
JJ
293 }
294 }
295 }
9346a6ac 296 if !curr_line.is_empty() {
9cc50fc6 297 trim_whitespace_prefix_and_push_line(&mut lines, curr_line, col);
1a4d82fc
JJ
298 }
299 }
300
9cc50fc6
SL
301 let mut style = if code_to_the_left {
302 Trailing
303 } else {
304 Isolated
305 };
1a4d82fc 306 rdr.consume_non_eol_whitespace();
c30ab7b3 307 if !rdr.is_eof() && !rdr.ch_is('\n') && lines.len() == 1 {
1a4d82fc
JJ
308 style = Mixed;
309 }
310 debug!("<<< block comment");
9cc50fc6
SL
311 comments.push(Comment {
312 style: style,
313 lines: lines,
314 pos: p,
315 });
1a4d82fc
JJ
316}
317
318
c30ab7b3
SL
319fn consume_comment(rdr: &mut StringReader,
320 comments: &mut Vec<Comment>,
321 code_to_the_left: &mut bool,
322 anything_to_the_left: &mut bool) {
1a4d82fc 323 debug!(">>> consume comment");
c30ab7b3
SL
324 if rdr.ch_is('/') && rdr.nextch_is('/') {
325 read_line_comments(rdr, *code_to_the_left, comments);
326 *code_to_the_left = false;
327 *anything_to_the_left = false;
328 } else if rdr.ch_is('/') && rdr.nextch_is('*') {
329 read_block_comment(rdr, *code_to_the_left, comments);
330 *anything_to_the_left = true;
331 } else if rdr.ch_is('#') && rdr.nextch_is('!') {
332 read_shebang_comment(rdr, *code_to_the_left, comments);
333 *code_to_the_left = false;
334 *anything_to_the_left = false;
9cc50fc6
SL
335 } else {
336 panic!();
337 }
1a4d82fc
JJ
338 debug!("<<< consume comment");
339}
340
341#[derive(Clone)]
342pub struct Literal {
343 pub lit: String,
344 pub pos: BytePos,
345}
346
347// it appears this function is called only from pprust... that's
348// probably not a good thing.
9cc50fc6 349pub fn gather_comments_and_literals(span_diagnostic: &errors::Handler,
1a4d82fc 350 path: String,
c34b1796 351 srdr: &mut Read)
9cc50fc6 352 -> (Vec<Comment>, Vec<Literal>) {
c34b1796
AL
353 let mut src = Vec::new();
354 srdr.read_to_end(&mut src).unwrap();
1a4d82fc
JJ
355 let src = String::from_utf8(src).unwrap();
356 let cm = CodeMap::new();
3157f602 357 let filemap = cm.new_filemap(path, None, src);
1a4d82fc
JJ
358 let mut rdr = lexer::StringReader::new_raw(span_diagnostic, filemap);
359
360 let mut comments: Vec<Comment> = Vec::new();
361 let mut literals: Vec<Literal> = Vec::new();
c30ab7b3
SL
362 let mut code_to_the_left = false; // Only code
363 let mut anything_to_the_left = false; // Code or comments
1a4d82fc
JJ
364 while !rdr.is_eof() {
365 loop {
c30ab7b3 366 // Eat all the whitespace and count blank lines.
1a4d82fc 367 rdr.consume_non_eol_whitespace();
c30ab7b3
SL
368 if rdr.ch_is('\n') {
369 if anything_to_the_left {
370 rdr.bump(); // The line is not blank, do not count.
371 }
1a4d82fc 372 consume_whitespace_counting_blank_lines(&mut rdr, &mut comments);
c30ab7b3
SL
373 code_to_the_left = false;
374 anything_to_the_left = false;
1a4d82fc 375 }
c30ab7b3
SL
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);
380 } else {
381 break
1a4d82fc 382 }
1a4d82fc
JJ
383 }
384
c30ab7b3 385 let bstart = rdr.pos;
1a4d82fc 386 rdr.next_token();
9cc50fc6 387 // discard, and look ahead; we're working with internal state
1a4d82fc
JJ
388 let TokenAndSpan { tok, sp } = rdr.peek();
389 if tok.is_lit() {
390 rdr.with_str_from(bstart, |s| {
391 debug!("tok lit: {}", s);
9cc50fc6
SL
392 literals.push(Literal {
393 lit: s.to_string(),
394 pos: sp.lo,
395 });
1a4d82fc
JJ
396 })
397 } else {
398 debug!("tok: {}", pprust::token_to_string(&tok));
399 }
c30ab7b3
SL
400 code_to_the_left = true;
401 anything_to_the_left = true;
1a4d82fc
JJ
402 }
403
404 (comments, literals)
405}
406
407#[cfg(test)]
d9579d0f 408mod tests {
1a4d82fc
JJ
409 use super::*;
410
9cc50fc6
SL
411 #[test]
412 fn test_block_doc_comment_1() {
1a4d82fc
JJ
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");
416 }
417
9cc50fc6
SL
418 #[test]
419 fn test_block_doc_comment_2() {
1a4d82fc
JJ
420 let comment = "/**\n * Test\n * Test\n*/";
421 let stripped = strip_doc_comment_decoration(comment);
422 assert_eq!(stripped, " Test\n Test");
423 }
424
9cc50fc6
SL
425 #[test]
426 fn test_block_doc_comment_3() {
85aaf69f 427 let comment = "/**\n let a: *i32;\n *a = 5;\n*/";
1a4d82fc 428 let stripped = strip_doc_comment_decoration(comment);
85aaf69f 429 assert_eq!(stripped, " let a: *i32;\n *a = 5;");
1a4d82fc
JJ
430 }
431
9cc50fc6
SL
432 #[test]
433 fn test_block_doc_comment_4() {
1a4d82fc
JJ
434 let comment = "/*******************\n test\n *********************/";
435 let stripped = strip_doc_comment_decoration(comment);
436 assert_eq!(stripped, " test");
437 }
438
9cc50fc6
SL
439 #[test]
440 fn test_line_doc_comment() {
1a4d82fc
JJ
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");
455 }
456}