1 pub use CommentStyle
::*;
4 use rustc_span
::source_map
::SourceMap
;
5 use rustc_span
::{BytePos, CharPos, FileName, Pos}
;
12 #[derive(Clone, Copy, PartialEq, Debug)]
13 pub enum CommentStyle
{
14 /// No code on either side of each line of the comment
16 /// Code exists to the left of the comment
18 /// Code before /* foo */ and after the comment
20 /// Just a manual blank line "\n\n", for layout
26 pub style
: CommentStyle
,
27 pub lines
: Vec
<String
>,
31 pub fn is_line_doc_comment(s
: &str) -> bool
{
32 let res
= (s
.starts_with("///") && *s
.as_bytes().get(3).unwrap_or(&b' '
) != b'
/'
)
33 || s
.starts_with("//!");
34 debug
!("is {:?} a doc comment? {}", s
, res
);
38 pub fn is_block_doc_comment(s
: &str) -> bool
{
39 // Prevent `/**/` from being parsed as a doc comment
40 let res
= ((s
.starts_with("/**") && *s
.as_bytes().get(3).unwrap_or(&b' '
) != b'
*'
)
41 || s
.starts_with("/*!"))
43 debug
!("is {:?} a doc comment? {}", s
, res
);
47 // FIXME(#64197): Try to privatize this again.
48 pub fn is_doc_comment(s
: &str) -> bool
{
49 (s
.starts_with("///") && is_line_doc_comment(s
))
50 || s
.starts_with("//!")
51 || (s
.starts_with("/**") && is_block_doc_comment(s
))
52 || s
.starts_with("/*!")
55 pub fn doc_comment_style(comment
: &str) -> ast
::AttrStyle
{
56 assert
!(is_doc_comment(comment
));
57 if comment
.starts_with("//!") || comment
.starts_with("/*!") {
64 pub fn strip_doc_comment_decoration(comment
: &str) -> String
{
65 /// remove whitespace-only lines from the start/end of lines
66 fn vertical_trim(lines
: Vec
<String
>) -> Vec
<String
> {
68 let mut j
= lines
.len();
69 // first line of all-stars should be omitted
70 if !lines
.is_empty() && lines
[0].chars().all(|c
| c
== '
*'
) {
74 while i
< j
&& lines
[i
].trim().is_empty() {
77 // like the first, a last line of all stars should be omitted
78 if j
> i
&& lines
[j
- 1].chars().skip(1).all(|c
| c
== '
*'
) {
82 while j
> i
&& lines
[j
- 1].trim().is_empty() {
89 /// remove a "[ \t]*\*" block from each line, if possible
90 fn horizontal_trim(lines
: Vec
<String
>) -> Vec
<String
> {
91 let mut i
= usize::MAX
;
92 let mut can_trim
= true;
96 for (j
, c
) in line
.chars().enumerate() {
97 if j
> i
|| !"* \t".contains(c
) {
120 lines
.iter().map(|line
| (&line
[i
+ 1..line
.len()]).to_string()).collect()
126 // one-line comments lose their prefix
127 const ONELINERS
: &[&str] = &["///!", "///", "//!", "//"];
129 for prefix
in ONELINERS
{
130 if comment
.starts_with(*prefix
) {
131 return (&comment
[prefix
.len()..]).to_string();
135 if comment
.starts_with("/*") {
137 comment
[3..comment
.len() - 2].lines().map(|s
| s
.to_string()).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 /// Returns `None` if the first `col` chars of `s` contain a non-whitespace char.
149 /// Otherwise returns `Some(k)` where `k` is first char offset after that leading
150 /// whitespace. Note that `k` may be outside bounds of `s`.
151 fn all_whitespace(s
: &str, col
: CharPos
) -> Option
<usize> {
153 for (i
, ch
) in s
.char_indices().take(col
.to_usize()) {
154 if !ch
.is_whitespace() {
157 idx
= i
+ ch
.len_utf8();
162 fn trim_whitespace_prefix(s
: &str, col
: CharPos
) -> &str {
164 match all_whitespace(&s
, col
) {
176 fn split_block_comment_into_lines(text
: &str, col
: CharPos
) -> Vec
<String
> {
177 let mut res
: Vec
<String
> = vec
![];
178 let mut lines
= text
.lines();
179 // just push the first line
180 res
.extend(lines
.next().map(|it
| it
.to_string()));
181 // for other lines, strip common whitespace prefix
183 res
.push(trim_whitespace_prefix(line
, col
).to_string())
188 // it appears this function is called only from pprust... that's
189 // probably not a good thing.
190 pub fn gather_comments(sm
: &SourceMap
, path
: FileName
, src
: String
) -> Vec
<Comment
> {
191 let sm
= SourceMap
::new(sm
.path_mapping().clone());
192 let source_file
= sm
.new_source_file(path
, src
);
193 let text
= (*source_file
.src
.as_ref().unwrap()).clone();
195 let text
: &str = text
.as_str();
196 let start_bpos
= source_file
.start_pos
;
198 let mut comments
: Vec
<Comment
> = Vec
::new();
199 let mut code_to_the_left
= false;
201 if let Some(shebang_len
) = rustc_lexer
::strip_shebang(text
) {
202 comments
.push(Comment
{
204 lines
: vec
![text
[..shebang_len
].to_string()],
210 for token
in rustc_lexer
::tokenize(&text
[pos
..]) {
211 let token_text
= &text
[pos
..pos
+ token
.len
];
213 rustc_lexer
::TokenKind
::Whitespace
=> {
214 if let Some(mut idx
) = token_text
.find('
\n'
) {
215 code_to_the_left
= false;
216 while let Some(next_newline
) = &token_text
[idx
+ 1..].find('
\n'
) {
217 idx
= idx
+ 1 + next_newline
;
218 comments
.push(Comment
{
221 pos
: start_bpos
+ BytePos((pos
+ idx
) as u32),
226 rustc_lexer
::TokenKind
::BlockComment { terminated: _ }
=> {
227 if !is_block_doc_comment(token_text
) {
228 let code_to_the_right
= match text
[pos
+ token
.len
..].chars().next() {
229 Some('
\r'
| '
\n'
) => false,
232 let style
= match (code_to_the_left
, code_to_the_right
) {
234 (false, false) => Isolated
,
235 (true, false) => Trailing
,
238 // Count the number of chars since the start of the line by rescanning.
239 let pos_in_file
= start_bpos
+ BytePos(pos
as u32);
240 let line_begin_in_file
= source_file
.line_begin_pos(pos_in_file
);
241 let line_begin_pos
= (line_begin_in_file
- start_bpos
).to_usize();
242 let col
= CharPos(text
[line_begin_pos
..pos
].chars().count());
244 let lines
= split_block_comment_into_lines(token_text
, col
);
245 comments
.push(Comment { style, lines, pos: pos_in_file }
)
248 rustc_lexer
::TokenKind
::LineComment
=> {
249 if !is_doc_comment(token_text
) {
250 comments
.push(Comment
{
251 style
: if code_to_the_left { Trailing }
else { Isolated }
,
252 lines
: vec
![token_text
.to_string()],
253 pos
: start_bpos
+ BytePos(pos
as u32),
258 code_to_the_left
= true;