]> git.proxmox.com Git - rustc.git/blame - src/libsyntax/parse/lexer/comments.rs
New upstream version 1.38.0+dfsg1
[rustc.git] / src / libsyntax / parse / lexer / comments.rs
CommitLineData
9fa01778
XL
1pub use CommentStyle::*;
2
3use crate::ast;
4use crate::source_map::SourceMap;
416331ca
XL
5use crate::parse::lexer::is_block_doc_comment;
6use crate::parse::lexer::ParseSess;
1a4d82fc 7
ff7c6d11 8use syntax_pos::{BytePos, CharPos, Pos, FileName};
1a4d82fc 9
85aaf69f 10use std::usize;
1a4d82fc 11
416331ca
XL
12#[cfg(test)]
13mod tests;
14
c30ab7b3 15#[derive(Clone, Copy, PartialEq, Debug)]
1a4d82fc
JJ
16pub enum CommentStyle {
17 /// No code on either side of each line of the comment
18 Isolated,
19 /// Code exists to the left of the comment
20 Trailing,
21 /// Code before /* foo */ and after the comment
22 Mixed,
23 /// Just a manual blank line "\n\n", for layout
24 BlankLine,
25}
26
27#[derive(Clone)]
28pub struct Comment {
29 pub style: CommentStyle,
30 pub lines: Vec<String>,
31 pub pos: BytePos,
32}
33
94b46f34 34fn is_doc_comment(s: &str) -> bool {
9cc50fc6
SL
35 (s.starts_with("///") && super::is_doc_comment(s)) || s.starts_with("//!") ||
36 (s.starts_with("/**") && is_block_doc_comment(s)) || s.starts_with("/*!")
1a4d82fc
JJ
37}
38
39pub fn doc_comment_style(comment: &str) -> ast::AttrStyle {
40 assert!(is_doc_comment(comment));
41 if comment.starts_with("//!") || comment.starts_with("/*!") {
b039eaaf 42 ast::AttrStyle::Inner
1a4d82fc 43 } else {
b039eaaf 44 ast::AttrStyle::Outer
1a4d82fc
JJ
45 }
46}
47
48pub fn strip_doc_comment_decoration(comment: &str) -> String {
49 /// remove whitespace-only lines from the start/end of lines
85aaf69f
SL
50 fn vertical_trim(lines: Vec<String>) -> Vec<String> {
51 let mut i = 0;
1a4d82fc
JJ
52 let mut j = lines.len();
53 // first line of all-stars should be omitted
9cc50fc6 54 if !lines.is_empty() && lines[0].chars().all(|c| c == '*') {
1a4d82fc
JJ
55 i += 1;
56 }
b7449926 57
1a4d82fc
JJ
58 while i < j && lines[i].trim().is_empty() {
59 i += 1;
60 }
61 // like the first, a last line of all stars should be omitted
9cc50fc6
SL
62 if j > i &&
63 lines[j - 1]
64 .chars()
65 .skip(1)
66 .all(|c| c == '*') {
1a4d82fc
JJ
67 j -= 1;
68 }
b7449926 69
1a4d82fc
JJ
70 while j > i && lines[j - 1].trim().is_empty() {
71 j -= 1;
72 }
b7449926 73
7cac9316 74 lines[i..j].to_vec()
1a4d82fc
JJ
75 }
76
77 /// remove a "[ \t]*\*" block from each line, if possible
9cc50fc6 78 fn horizontal_trim(lines: Vec<String>) -> Vec<String> {
85aaf69f 79 let mut i = usize::MAX;
1a4d82fc
JJ
80 let mut can_trim = true;
81 let mut first = true;
b7449926 82
85aaf69f 83 for line in &lines {
1a4d82fc 84 for (j, c) in line.chars().enumerate() {
c34b1796 85 if j > i || !"* \t".contains(c) {
1a4d82fc
JJ
86 can_trim = false;
87 break;
88 }
89 if c == '*' {
90 if first {
91 i = j;
92 first = false;
93 } else if i != j {
94 can_trim = false;
95 }
96 break;
97 }
98 }
2c00a5a8 99 if i >= line.len() {
1a4d82fc
JJ
100 can_trim = false;
101 }
102 if !can_trim {
103 break;
104 }
105 }
106
107 if can_trim {
9cc50fc6
SL
108 lines.iter()
109 .map(|line| (&line[i + 1..line.len()]).to_string())
110 .collect()
1a4d82fc
JJ
111 } else {
112 lines
113 }
114 }
115
116 // one-line comments lose their prefix
b7449926
XL
117 const ONELINERS: &[&str] = &["///!", "///", "//!", "//"];
118
c34b1796 119 for prefix in ONELINERS {
1a4d82fc
JJ
120 if comment.starts_with(*prefix) {
121 return (&comment[prefix.len()..]).to_string();
122 }
123 }
124
125 if comment.starts_with("/*") {
85aaf69f 126 let lines = comment[3..comment.len() - 2]
9cc50fc6
SL
127 .lines()
128 .map(|s| s.to_string())
129 .collect::<Vec<String>>();
1a4d82fc
JJ
130
131 let lines = vertical_trim(lines);
132 let lines = horizontal_trim(lines);
133
c1a9b12d 134 return lines.join("\n");
1a4d82fc
JJ
135 }
136
137 panic!("not a doc-comment: {}", comment);
138}
139
9fa01778
XL
140/// Returns `None` if the first `col` chars of `s` contain a non-whitespace char.
141/// Otherwise returns `Some(k)` where `k` is first char offset after that leading
142/// whitespace. Note that `k` may be outside bounds of `s`.
85aaf69f 143fn all_whitespace(s: &str, col: CharPos) -> Option<usize> {
0731742a
XL
144 let mut idx = 0;
145 for (i, ch) in s.char_indices().take(col.to_usize()) {
c34b1796 146 if !ch.is_whitespace() {
1a4d82fc
JJ
147 return None;
148 }
0731742a 149 idx = i + ch.len_utf8();
1a4d82fc 150 }
0731742a 151 Some(idx)
1a4d82fc
JJ
152}
153
416331ca 154fn trim_whitespace_prefix(s: &str, col: CharPos) -> &str {
1a4d82fc 155 let len = s.len();
416331ca
XL
156 match all_whitespace(&s, col) {
157 Some(col) => if col < len { &s[col..] } else { "" },
1a4d82fc 158 None => s,
1a4d82fc 159 }
1a4d82fc
JJ
160}
161
416331ca
XL
162fn split_block_comment_into_lines(
163 text: &str,
164 col: CharPos,
165) -> Vec<String> {
166 let mut res: Vec<String> = vec![];
167 let mut lines = text.lines();
168 // just push the first line
169 res.extend(lines.next().map(|it| it.to_string()));
170 // for other lines, strip common whitespace prefix
171 for line in lines {
172 res.push(trim_whitespace_prefix(line, col).to_string())
173 }
174 res
1a4d82fc
JJ
175}
176
1a4d82fc
JJ
177// it appears this function is called only from pprust... that's
178// probably not a good thing.
416331ca 179pub fn gather_comments(sess: &ParseSess, path: FileName, src: String) -> Vec<Comment> {
b7449926
XL
180 let cm = SourceMap::new(sess.source_map().path_mapping().clone());
181 let source_file = cm.new_source_file(path, src);
416331ca 182 let text = (*source_file.src.as_ref().unwrap()).clone();
1a4d82fc 183
416331ca
XL
184 let text: &str = text.as_str();
185 let start_bpos = source_file.start_pos;
186 let mut pos = 0;
1a4d82fc 187 let mut comments: Vec<Comment> = Vec::new();
416331ca 188 let mut code_to_the_left = false;
b7449926 189
416331ca
XL
190 if let Some(shebang_len) = rustc_lexer::strip_shebang(text) {
191 comments.push(Comment {
192 style: Isolated,
193 lines: vec![text[..shebang_len].to_string()],
194 pos: start_bpos,
195 });
196 pos += shebang_len;
197 }
198
199 for token in rustc_lexer::tokenize(&text[pos..]) {
200 let token_text = &text[pos..pos + token.len];
201 match token.kind {
202 rustc_lexer::TokenKind::Whitespace => {
203 if let Some(mut idx) = token_text.find('\n') {
204 code_to_the_left = false;
205 while let Some(next_newline) = &token_text[idx + 1..].find('\n') {
206 idx = idx + 1 + next_newline;
207 comments.push(Comment {
208 style: BlankLine,
209 lines: vec![],
210 pos: start_bpos + BytePos((pos + idx) as u32),
211 });
212 }
213 }
214 }
215 rustc_lexer::TokenKind::BlockComment { terminated: _ } => {
216 if !is_block_doc_comment(token_text) {
217 let code_to_the_right = match text[pos + token.len..].chars().next() {
218 Some('\r') | Some('\n') => false,
219 _ => true,
220 };
221 let style = match (code_to_the_left, code_to_the_right) {
222 (true, true) | (false, true) => Mixed,
223 (false, false) => Isolated,
224 (true, false) => Trailing,
225 };
226
227 // Count the number of chars since the start of the line by rescanning.
228 let pos_in_file = start_bpos + BytePos(pos as u32);
229 let line_begin_in_file = source_file.line_begin_pos(pos_in_file);
230 let line_begin_pos = (line_begin_in_file - start_bpos).to_usize();
231 let col = CharPos(text[line_begin_pos..pos].chars().count());
232
233 let lines = split_block_comment_into_lines(token_text, col);
234 comments.push(Comment { style, lines, pos: pos_in_file })
c30ab7b3 235 }
1a4d82fc 236 }
416331ca
XL
237 rustc_lexer::TokenKind::LineComment => {
238 if !is_doc_comment(token_text) {
239 comments.push(Comment {
240 style: if code_to_the_left { Trailing } else { Isolated },
241 lines: vec![token_text.to_string()],
242 pos: start_bpos + BytePos(pos as u32),
243 })
244 }
245 }
246 _ => {
247 code_to_the_left = true;
1a4d82fc 248 }
1a4d82fc 249 }
416331ca 250 pos += token.len;
1a4d82fc
JJ
251 }
252
48663c56 253 comments
1a4d82fc 254}