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