1 use rustc_span::{BytePos, Pos, Span}; 2 3 use crate::comment::{is_last_comment_block, rewrite_comment, CodeCharKind, CommentCodeSlices}; 4 use crate::config::file_lines::FileLines; 5 use crate::config::FileName; 6 use crate::config::Version; 7 use crate::coverage::transform_missing_snippet; 8 use crate::shape::{Indent, Shape}; 9 use crate::source_map::LineRangeUtils; 10 use crate::utils::{count_lf_crlf, count_newlines, last_line_width, mk_sp}; 11 use crate::visitor::FmtVisitor; 12 13 struct SnippetStatus { 14 /// An offset to the current line from the beginning of the original snippet. 15 line_start: usize, 16 /// A length of trailing whitespaces on the current line. 17 last_wspace: Option<usize>, 18 /// The current line number. 19 cur_line: usize, 20 } 21 22 impl SnippetStatus { new(cur_line: usize) -> Self23 fn new(cur_line: usize) -> Self { 24 SnippetStatus { 25 line_start: 0, 26 last_wspace: None, 27 cur_line, 28 } 29 } 30 } 31 32 impl<'a> FmtVisitor<'a> { output_at_start(&self) -> bool33 fn output_at_start(&self) -> bool { 34 self.buffer.is_empty() 35 } 36 format_missing(&mut self, end: BytePos)37 pub(crate) fn format_missing(&mut self, end: BytePos) { 38 // HACK(topecongiro): we use `format_missing()` to extract a missing comment between 39 // a macro (or similar) and a trailing semicolon. Here we just try to avoid calling 40 // `format_missing_inner` in the common case where there is no such comment. 41 // This is a hack, ideally we should fix a possible bug in `format_missing_inner` 42 // or refactor `visit_mac` and `rewrite_macro`, but this should suffice to fix the 43 // issue (#2727). 44 let missing_snippet = self.snippet(mk_sp(self.last_pos, end)); 45 if missing_snippet.trim() == ";" { 46 self.push_str(";"); 47 self.last_pos = end; 48 return; 49 } 50 self.format_missing_inner(end, |this, last_snippet, _| this.push_str(last_snippet)) 51 } 52 format_missing_with_indent(&mut self, end: BytePos)53 pub(crate) fn format_missing_with_indent(&mut self, end: BytePos) { 54 self.format_missing_indent(end, true) 55 } 56 format_missing_no_indent(&mut self, end: BytePos)57 pub(crate) fn format_missing_no_indent(&mut self, end: BytePos) { 58 self.format_missing_indent(end, false) 59 } 60 format_missing_indent(&mut self, end: BytePos, should_indent: bool)61 fn format_missing_indent(&mut self, end: BytePos, should_indent: bool) { 62 let config = self.config; 63 self.format_missing_inner(end, |this, last_snippet, snippet| { 64 this.push_str(last_snippet.trim_end()); 65 if last_snippet == snippet && !this.output_at_start() { 66 // No new lines in the snippet. 67 this.push_str("\n"); 68 } 69 if should_indent { 70 let indent = this.block_indent.to_string(config); 71 this.push_str(&indent); 72 } 73 }) 74 } 75 format_missing_inner<F: Fn(&mut FmtVisitor<'_>, &str, &str)>( &mut self, end: BytePos, process_last_snippet: F, )76 fn format_missing_inner<F: Fn(&mut FmtVisitor<'_>, &str, &str)>( 77 &mut self, 78 end: BytePos, 79 process_last_snippet: F, 80 ) { 81 let start = self.last_pos; 82 83 if start == end { 84 // Do nothing if this is the beginning of the file. 85 if !self.output_at_start() { 86 process_last_snippet(self, "", ""); 87 } 88 return; 89 } 90 91 assert!( 92 start < end, 93 "Request to format inverted span: {}", 94 self.parse_sess.span_to_debug_info(mk_sp(start, end)), 95 ); 96 97 self.last_pos = end; 98 let span = mk_sp(start, end); 99 let snippet = self.snippet(span); 100 101 // Do nothing for spaces in the beginning of the file 102 if start == BytePos(0) && end.0 as usize == snippet.len() && snippet.trim().is_empty() { 103 return; 104 } 105 106 if snippet.trim().is_empty() && !out_of_file_lines_range!(self, span) { 107 // Keep vertical spaces within range. 108 self.push_vertical_spaces(count_newlines(snippet)); 109 process_last_snippet(self, "", snippet); 110 } else { 111 self.write_snippet(span, &process_last_snippet); 112 } 113 } 114 push_vertical_spaces(&mut self, mut newline_count: usize)115 fn push_vertical_spaces(&mut self, mut newline_count: usize) { 116 let offset = self.buffer.chars().rev().take_while(|c| *c == '\n').count(); 117 let newline_upper_bound = self.config.blank_lines_upper_bound() + 1; 118 let newline_lower_bound = self.config.blank_lines_lower_bound() + 1; 119 120 if newline_count + offset > newline_upper_bound { 121 if offset >= newline_upper_bound { 122 newline_count = 0; 123 } else { 124 newline_count = newline_upper_bound - offset; 125 } 126 } else if newline_count + offset < newline_lower_bound { 127 if offset >= newline_lower_bound { 128 newline_count = 0; 129 } else { 130 newline_count = newline_lower_bound - offset; 131 } 132 } 133 134 let blank_lines = "\n".repeat(newline_count); 135 self.push_str(&blank_lines); 136 } 137 write_snippet<F>(&mut self, span: Span, process_last_snippet: F) where F: Fn(&mut FmtVisitor<'_>, &str, &str),138 fn write_snippet<F>(&mut self, span: Span, process_last_snippet: F) 139 where 140 F: Fn(&mut FmtVisitor<'_>, &str, &str), 141 { 142 // Get a snippet from the file start to the span's hi without allocating. 143 // We need it to determine what precedes the current comment. If the comment 144 // follows code on the same line, we won't touch it. 145 let big_span_lo = self.snippet_provider.start_pos(); 146 let big_snippet = self.snippet_provider.entire_snippet(); 147 let big_diff = (span.lo() - big_span_lo).to_usize(); 148 149 let snippet = self.snippet(span); 150 151 debug!("write_snippet `{}`", snippet); 152 153 self.write_snippet_inner(big_snippet, snippet, big_diff, span, process_last_snippet); 154 } 155 write_snippet_inner<F>( &mut self, big_snippet: &str, old_snippet: &str, big_diff: usize, span: Span, process_last_snippet: F, ) where F: Fn(&mut FmtVisitor<'_>, &str, &str),156 fn write_snippet_inner<F>( 157 &mut self, 158 big_snippet: &str, 159 old_snippet: &str, 160 big_diff: usize, 161 span: Span, 162 process_last_snippet: F, 163 ) where 164 F: Fn(&mut FmtVisitor<'_>, &str, &str), 165 { 166 // Trim whitespace from the right hand side of each line. 167 // Annoyingly, the library functions for splitting by lines etc. are not 168 // quite right, so we must do it ourselves. 169 let line = self.parse_sess.line_of_byte_pos(span.lo()); 170 let file_name = &self.parse_sess.span_to_filename(span); 171 let mut status = SnippetStatus::new(line); 172 173 let snippet = &*transform_missing_snippet(self.config, old_snippet); 174 175 let slice_within_file_lines_range = 176 |file_lines: FileLines, cur_line, s| -> (usize, usize, bool) { 177 let (lf_count, crlf_count) = count_lf_crlf(s); 178 let newline_count = lf_count + crlf_count; 179 let within_file_lines_range = file_lines.contains_range( 180 file_name, 181 cur_line, 182 // if a newline character is at the end of the slice, then the number of 183 // newlines needs to be decreased by 1 so that the range checked against 184 // the file_lines is the visual range one would expect. 185 cur_line + newline_count - if s.ends_with('\n') { 1 } else { 0 }, 186 ); 187 (lf_count, crlf_count, within_file_lines_range) 188 }; 189 for (kind, offset, subslice) in CommentCodeSlices::new(snippet) { 190 debug!("{:?}: {:?}", kind, subslice); 191 192 let (lf_count, crlf_count, within_file_lines_range) = 193 slice_within_file_lines_range(self.config.file_lines(), status.cur_line, subslice); 194 let newline_count = lf_count + crlf_count; 195 if CodeCharKind::Comment == kind && within_file_lines_range { 196 // 1: comment. 197 self.process_comment( 198 &mut status, 199 snippet, 200 &big_snippet[..(offset + big_diff)], 201 offset, 202 subslice, 203 ); 204 } else if subslice.trim().is_empty() && newline_count > 0 && within_file_lines_range { 205 // 2: blank lines. 206 self.push_vertical_spaces(newline_count); 207 status.cur_line += newline_count; 208 status.line_start = offset + lf_count + crlf_count * 2; 209 } else { 210 // 3: code which we failed to format or which is not within file-lines range. 211 self.process_missing_code(&mut status, snippet, subslice, offset, file_name); 212 } 213 } 214 215 let last_snippet = &snippet[status.line_start..]; 216 let (_, _, within_file_lines_range) = 217 slice_within_file_lines_range(self.config.file_lines(), status.cur_line, last_snippet); 218 if within_file_lines_range { 219 process_last_snippet(self, last_snippet, snippet); 220 } else { 221 // just append what's left 222 self.push_str(last_snippet); 223 } 224 } 225 process_comment( &mut self, status: &mut SnippetStatus, snippet: &str, big_snippet: &str, offset: usize, subslice: &str, )226 fn process_comment( 227 &mut self, 228 status: &mut SnippetStatus, 229 snippet: &str, 230 big_snippet: &str, 231 offset: usize, 232 subslice: &str, 233 ) { 234 let last_char = big_snippet 235 .chars() 236 .rev() 237 .find(|rev_c| ![' ', '\t'].contains(rev_c)); 238 239 let fix_indent = last_char.map_or(true, |rev_c| ['{', '\n'].contains(&rev_c)); 240 let mut on_same_line = false; 241 242 let comment_indent = if fix_indent { 243 if let Some('{') = last_char { 244 self.push_str("\n"); 245 } 246 let indent_str = self.block_indent.to_string(self.config); 247 self.push_str(&indent_str); 248 self.block_indent 249 } else if self.config.version() == Version::Two && !snippet.starts_with('\n') { 250 // The comment appears on the same line as the previous formatted code. 251 // Assuming that comment is logically associated with that code, we want to keep it on 252 // the same level and avoid mixing it with possible other comment. 253 on_same_line = true; 254 self.push_str(" "); 255 self.block_indent 256 } else { 257 self.push_str(" "); 258 Indent::from_width(self.config, last_line_width(&self.buffer)) 259 }; 260 261 let comment_width = ::std::cmp::min( 262 self.config.comment_width(), 263 self.config.max_width() - self.block_indent.width(), 264 ); 265 let comment_shape = Shape::legacy(comment_width, comment_indent); 266 267 if on_same_line { 268 match subslice.find('\n') { 269 None => { 270 self.push_str(subslice); 271 } 272 Some(offset) if offset + 1 == subslice.len() => { 273 self.push_str(&subslice[..offset]); 274 } 275 Some(offset) => { 276 // keep first line as is: if it were too long and wrapped, it may get mixed 277 // with the other lines. 278 let first_line = &subslice[..offset]; 279 self.push_str(first_line); 280 self.push_str(&comment_indent.to_string_with_newline(self.config)); 281 282 let other_lines = &subslice[offset + 1..]; 283 let comment_str = 284 rewrite_comment(other_lines, false, comment_shape, self.config) 285 .unwrap_or_else(|| String::from(other_lines)); 286 self.push_str(&comment_str); 287 } 288 } 289 } else { 290 let comment_str = rewrite_comment(subslice, false, comment_shape, self.config) 291 .unwrap_or_else(|| String::from(subslice)); 292 self.push_str(&comment_str); 293 } 294 295 status.last_wspace = None; 296 status.line_start = offset + subslice.len(); 297 298 // Add a newline: 299 // - if there isn't one already 300 // - otherwise, only if the last line is a line comment 301 if status.line_start <= snippet.len() { 302 match snippet[status.line_start..] 303 .chars() 304 // skip trailing whitespaces 305 .find(|c| !(*c == ' ' || *c == '\t')) 306 { 307 Some('\n') | Some('\r') => { 308 if !is_last_comment_block(subslice) { 309 self.push_str("\n"); 310 } 311 } 312 _ => self.push_str("\n"), 313 } 314 } 315 316 status.cur_line += count_newlines(subslice); 317 } 318 process_missing_code( &mut self, status: &mut SnippetStatus, snippet: &str, subslice: &str, offset: usize, file_name: &FileName, )319 fn process_missing_code( 320 &mut self, 321 status: &mut SnippetStatus, 322 snippet: &str, 323 subslice: &str, 324 offset: usize, 325 file_name: &FileName, 326 ) { 327 for (mut i, c) in subslice.char_indices() { 328 i += offset; 329 330 if c == '\n' { 331 let skip_this_line = !self 332 .config 333 .file_lines() 334 .contains_line(file_name, status.cur_line); 335 if skip_this_line { 336 status.last_wspace = None; 337 } 338 339 if let Some(lw) = status.last_wspace { 340 self.push_str(&snippet[status.line_start..lw]); 341 self.push_str("\n"); 342 status.last_wspace = None; 343 } else { 344 self.push_str(&snippet[status.line_start..=i]); 345 } 346 347 status.cur_line += 1; 348 status.line_start = i + 1; 349 } else if c.is_whitespace() && status.last_wspace.is_none() { 350 status.last_wspace = Some(i); 351 } else { 352 status.last_wspace = None; 353 } 354 } 355 356 let remaining = snippet[status.line_start..subslice.len() + offset].trim(); 357 if !remaining.is_empty() { 358 self.push_str(&self.block_indent.to_string(self.config)); 359 self.push_str(remaining); 360 status.line_start = subslice.len() + offset; 361 } 362 } 363 } 364