]>
Commit | Line | Data |
---|---|---|
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 | ||
11 | pub use self::CommentStyle::*; | |
12 | ||
13 | use ast; | |
3157f602 XL |
14 | use codemap::CodeMap; |
15 | use syntax_pos::{BytePos, CharPos, Pos}; | |
9cc50fc6 | 16 | use errors; |
1a4d82fc | 17 | use parse::lexer::is_block_doc_comment; |
d9579d0f | 18 | use parse::lexer::{StringReader, TokenAndSpan}; |
54a0048b | 19 | use parse::lexer::{is_pattern_whitespace, Reader}; |
1a4d82fc JJ |
20 | use parse::lexer; |
21 | use print::pprust; | |
d9579d0f | 22 | use str::char_at; |
1a4d82fc | 23 | |
c34b1796 | 24 | use std::io::Read; |
85aaf69f | 25 | use std::usize; |
1a4d82fc | 26 | |
c30ab7b3 | 27 | #[derive(Clone, Copy, PartialEq, Debug)] |
1a4d82fc JJ |
28 | pub 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)] | |
40 | pub struct Comment { | |
41 | pub style: CommentStyle, | |
42 | pub lines: Vec<String>, | |
43 | pub pos: BytePos, | |
44 | } | |
45 | ||
46 | pub 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 | ||
51 | pub 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 | ||
60 | pub 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 | ||
147 | fn 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 | 156 | fn 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 |
165 | fn 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 |
178 | fn 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 | 207 | fn 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 | 222 | fn 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 | ||
238 | fn 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 |
319 | fn 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)] | |
342 | pub 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 | 349 | pub 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 | 408 | mod 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 | } |