1 use crate::ast::AttrStyle;
2 use rustc_span::source_map::SourceMap;
3 use rustc_span::{BytePos, CharPos, FileName, Pos, Symbol};
8 #[derive(Clone, Copy, PartialEq, Debug)]
9 pub enum CommentStyle {
10 /// No code on either side of each line of the comment
12 /// Code exists to the left of the comment
14 /// Code before /* foo */ and after the comment
16 /// Just a manual blank line "\n\n", for layout
22 pub style: CommentStyle,
23 pub lines: Vec<String>,
27 /// For a full line comment string returns its doc comment style if it's a doc comment
28 /// and returns `None` if it's a regular comment.
29 pub fn line_doc_comment_style(line_comment: &str) -> Option<AttrStyle> {
30 let line_comment = line_comment.as_bytes();
31 assert!(line_comment.starts_with(b"//"));
32 match line_comment.get(2) {
33 // `//!` is an inner line doc comment.
34 Some(b'!') => Some(AttrStyle::Inner),
35 Some(b'/') => match line_comment.get(3) {
36 // `////` (more than 3 slashes) is not considered a doc comment.
38 // Otherwise `///` is an outer line doc comment.
39 _ => Some(AttrStyle::Outer),
45 /// For a full block comment string returns its doc comment style if it's a doc comment
46 /// and returns `None` if it's a regular comment.
47 pub fn block_doc_comment_style(block_comment: &str, terminated: bool) -> Option<AttrStyle> {
48 let block_comment = block_comment.as_bytes();
49 assert!(block_comment.starts_with(b"/*"));
50 assert!(!terminated || block_comment.ends_with(b"*/"));
51 match block_comment.get(2) {
52 // `/*!` is an inner block doc comment.
53 Some(b'!') => Some(AttrStyle::Inner),
54 Some(b'*') => match block_comment.get(3) {
55 // `/***` (more than 2 stars) is not considered a doc comment.
57 // `/**/` is not considered a doc comment.
58 Some(b'/') if block_comment.len() == 4 => None,
59 // Otherwise `/**` is an outer block doc comment.
60 _ => Some(AttrStyle::Outer),
66 /// Makes a doc string more presentable to users.
67 /// Used by rustdoc and perhaps other tools, but not by rustc.
68 pub fn beautify_doc_string(data: Symbol) -> String {
69 /// remove whitespace-only lines from the start/end of lines
70 fn vertical_trim(lines: Vec<String>) -> Vec<String> {
72 let mut j = lines.len();
73 // first line of all-stars should be omitted
74 if !lines.is_empty() && lines[0].chars().all(|c| c == '*') {
78 while i < j && lines[i].trim().is_empty() {
81 // like the first, a last line of all stars should be omitted
82 if j > i && lines[j - 1].chars().skip(1).all(|c| c == '*') {
86 while j > i && lines[j - 1].trim().is_empty() {
93 /// remove a "[ \t]*\*" block from each line, if possible
94 fn horizontal_trim(lines: Vec<String>) -> Vec<String> {
95 let mut i = usize::MAX;
96 let mut can_trim = true;
100 for (j, c) in line.chars().enumerate() {
101 if j > i || !"* \t".contains(c) {
124 lines.iter().map(|line| (&line[i + 1..line.len()]).to_string()).collect()
130 let data = data.as_str();
131 if data.contains('\n') {
132 let lines = data.lines().map(|s| s.to_string()).collect::<Vec<String>>();
133 let lines = vertical_trim(lines);
134 let lines = horizontal_trim(lines);
141 /// Returns `None` if the first `col` chars of `s` contain a non-whitespace char.
142 /// Otherwise returns `Some(k)` where `k` is first char offset after that leading
143 /// whitespace. Note that `k` may be outside bounds of `s`.
144 fn all_whitespace(s: &str, col: CharPos) -> Option<usize> {
146 for (i, ch) in s.char_indices().take(col.to_usize()) {
147 if !ch.is_whitespace() {
150 idx = i + ch.len_utf8();
155 fn trim_whitespace_prefix(s: &str, col: CharPos) -> &str {
157 match all_whitespace(&s, col) {
169 fn split_block_comment_into_lines(text: &str, col: CharPos) -> Vec<String> {
170 let mut res: Vec<String> = vec![];
171 let mut lines = text.lines();
172 // just push the first line
173 res.extend(lines.next().map(|it| it.to_string()));
174 // for other lines, strip common whitespace prefix
176 res.push(trim_whitespace_prefix(line, col).to_string())
181 // it appears this function is called only from pprust... that's
182 // probably not a good thing.
183 pub fn gather_comments(sm: &SourceMap, path: FileName, src: String) -> Vec<Comment> {
184 let sm = SourceMap::new(sm.path_mapping().clone());
185 let source_file = sm.new_source_file(path, src);
186 let text = (*source_file.src.as_ref().unwrap()).clone();
188 let text: &str = text.as_str();
189 let start_bpos = source_file.start_pos;
191 let mut comments: Vec<Comment> = Vec::new();
192 let mut code_to_the_left = false;
194 if let Some(shebang_len) = rustc_lexer::strip_shebang(text) {
195 comments.push(Comment {
196 style: CommentStyle::Isolated,
197 lines: vec![text[..shebang_len].to_string()],
203 for token in rustc_lexer::tokenize(&text[pos..]) {
204 let token_text = &text[pos..pos + token.len];
206 rustc_lexer::TokenKind::Whitespace => {
207 if let Some(mut idx) = token_text.find('\n') {
208 code_to_the_left = false;
209 while let Some(next_newline) = &token_text[idx + 1..].find('\n') {
210 idx = idx + 1 + next_newline;
211 comments.push(Comment {
212 style: CommentStyle::BlankLine,
214 pos: start_bpos + BytePos((pos + idx) as u32),
219 rustc_lexer::TokenKind::BlockComment { terminated } => {
220 if block_doc_comment_style(token_text, terminated).is_none() {
221 let code_to_the_right = match text[pos + token.len..].chars().next() {
222 Some('\r' | '\n') => false,
225 let style = match (code_to_the_left, code_to_the_right) {
226 (_, true) => CommentStyle::Mixed,
227 (false, false) => CommentStyle::Isolated,
228 (true, false) => CommentStyle::Trailing,
231 // Count the number of chars since the start of the line by rescanning.
232 let pos_in_file = start_bpos + BytePos(pos as u32);
233 let line_begin_in_file = source_file.line_begin_pos(pos_in_file);
234 let line_begin_pos = (line_begin_in_file - start_bpos).to_usize();
235 let col = CharPos(text[line_begin_pos..pos].chars().count());
237 let lines = split_block_comment_into_lines(token_text, col);
238 comments.push(Comment { style, lines, pos: pos_in_file })
241 rustc_lexer::TokenKind::LineComment => {
242 if line_doc_comment_style(token_text).is_none() {
243 comments.push(Comment {
244 style: if code_to_the_left {
245 CommentStyle::Trailing
247 CommentStyle::Isolated
249 lines: vec![token_text.to_string()],
250 pos: start_bpos + BytePos(pos as u32),
255 code_to_the_left = true;