]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 | 1 | #![feature(rustc_private)] |
f20569fa XL |
2 | #![deny(rust_2018_idioms)] |
3 | #![warn(unreachable_pub)] | |
136023e0 | 4 | #![recursion_limit = "256"] |
94222f64 | 5 | #![allow(clippy::match_like_matches_macro)] |
5099ac24 | 6 | #![allow(unreachable_pub)] |
f20569fa XL |
7 | |
8 | #[macro_use] | |
9 | extern crate derive_new; | |
10 | #[cfg(test)] | |
11 | #[macro_use] | |
12 | extern crate lazy_static; | |
13 | #[macro_use] | |
14 | extern crate log; | |
15 | ||
cdc7bbd5 XL |
16 | // N.B. these crates are loaded from the sysroot, so they need extern crate. |
17 | extern crate rustc_ast; | |
18 | extern crate rustc_ast_pretty; | |
a2a8927a | 19 | extern crate rustc_builtin_macros; |
cdc7bbd5 XL |
20 | extern crate rustc_data_structures; |
21 | extern crate rustc_errors; | |
22 | extern crate rustc_expand; | |
23 | extern crate rustc_parse; | |
24 | extern crate rustc_session; | |
25 | extern crate rustc_span; | |
26 | ||
6522a427 EL |
27 | // Necessary to pull in object code as the rest of the rustc crates are shipped only as rmeta |
28 | // files. | |
29 | #[allow(unused_extern_crates)] | |
30 | extern crate rustc_driver; | |
31 | ||
f20569fa XL |
32 | use std::cell::RefCell; |
33 | use std::collections::HashMap; | |
34 | use std::fmt; | |
35 | use std::io::{self, Write}; | |
36 | use std::mem; | |
37 | use std::panic; | |
38 | use std::path::PathBuf; | |
39 | use std::rc::Rc; | |
40 | ||
f20569fa | 41 | use rustc_ast::ast; |
94222f64 | 42 | use rustc_span::symbol; |
f20569fa XL |
43 | use thiserror::Error; |
44 | ||
45 | use crate::comment::LineClasses; | |
46 | use crate::emitter::Emitter; | |
47 | use crate::formatting::{FormatErrorMap, FormattingError, ReportedErrors, SourceFile}; | |
f20569fa | 48 | use crate::modules::ModuleResolutionError; |
a2a8927a | 49 | use crate::parse::parser::DirectoryOwnership; |
f20569fa | 50 | use crate::shape::Indent; |
f20569fa XL |
51 | use crate::utils::indent_next_line; |
52 | ||
53 | pub use crate::config::{ | |
54 | load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName, NewlineStyle, | |
55 | Range, Verbosity, | |
56 | }; | |
57 | ||
58 | pub use crate::format_report_formatter::{FormatReportFormatter, FormatReportFormatterBuilder}; | |
59 | ||
60 | pub use crate::rustfmt_diff::{ModifiedChunk, ModifiedLines}; | |
61 | ||
62 | #[macro_use] | |
63 | mod utils; | |
64 | ||
65 | mod attr; | |
66 | mod chains; | |
67 | mod closures; | |
68 | mod comment; | |
69 | pub(crate) mod config; | |
70 | mod coverage; | |
71 | mod emitter; | |
72 | mod expr; | |
73 | mod format_report_formatter; | |
74 | pub(crate) mod formatting; | |
75 | mod ignore_path; | |
76 | mod imports; | |
f20569fa XL |
77 | mod items; |
78 | mod lists; | |
79 | mod macros; | |
80 | mod matches; | |
81 | mod missed_spans; | |
82 | pub(crate) mod modules; | |
83 | mod overflow; | |
84 | mod pairs; | |
a2a8927a | 85 | mod parse; |
f20569fa XL |
86 | mod patterns; |
87 | mod release_channel; | |
88 | mod reorder; | |
89 | mod rewrite; | |
90 | pub(crate) mod rustfmt_diff; | |
91 | mod shape; | |
92 | mod skip; | |
93 | pub(crate) mod source_file; | |
94 | pub(crate) mod source_map; | |
95 | mod spanned; | |
96 | mod stmt; | |
97 | mod string; | |
f20569fa XL |
98 | #[cfg(test)] |
99 | mod test; | |
100 | mod types; | |
101 | mod vertical; | |
102 | pub(crate) mod visitor; | |
103 | ||
104 | /// The various errors that can occur during formatting. Note that not all of | |
105 | /// these can currently be propagated to clients. | |
106 | #[derive(Error, Debug)] | |
107 | pub enum ErrorKind { | |
108 | /// Line has exceeded character limit (found, maximum). | |
109 | #[error( | |
110 | "line formatted, but exceeded maximum width \ | |
111 | (maximum: {1} (see `max_width` option), found: {0})" | |
112 | )] | |
113 | LineOverflow(usize, usize), | |
114 | /// Line ends in whitespace. | |
115 | #[error("left behind trailing whitespace")] | |
116 | TrailingWhitespace, | |
f20569fa XL |
117 | /// Used deprecated skip attribute. |
118 | #[error("`rustfmt_skip` is deprecated; use `rustfmt::skip`")] | |
119 | DeprecatedAttr, | |
120 | /// Used a rustfmt:: attribute other than skip or skip::macros. | |
121 | #[error("invalid attribute")] | |
122 | BadAttr, | |
123 | /// An io error during reading or writing. | |
124 | #[error("io error: {0}")] | |
125 | IoError(io::Error), | |
126 | /// Error during module resolution. | |
127 | #[error("{0}")] | |
128 | ModuleResolutionError(#[from] ModuleResolutionError), | |
129 | /// Parse error occurred when parsing the input. | |
130 | #[error("parse error")] | |
131 | ParseError, | |
132 | /// The user mandated a version and the current version of Rustfmt does not | |
133 | /// satisfy that requirement. | |
134 | #[error("version mismatch")] | |
135 | VersionMismatch, | |
136 | /// If we had formatted the given node, then we would have lost a comment. | |
137 | #[error("not formatted because a comment would be lost")] | |
138 | LostComment, | |
139 | /// Invalid glob pattern in `ignore` configuration option. | |
140 | #[error("Invalid glob pattern found in ignore list: {0}")] | |
141 | InvalidGlobPattern(ignore::Error), | |
142 | } | |
143 | ||
144 | impl ErrorKind { | |
145 | fn is_comment(&self) -> bool { | |
94222f64 | 146 | matches!(self, ErrorKind::LostComment) |
f20569fa XL |
147 | } |
148 | } | |
149 | ||
150 | impl From<io::Error> for ErrorKind { | |
151 | fn from(e: io::Error) -> ErrorKind { | |
152 | ErrorKind::IoError(e) | |
153 | } | |
154 | } | |
155 | ||
156 | /// Result of formatting a snippet of code along with ranges of lines that didn't get formatted, | |
157 | /// i.e., that got returned as they were originally. | |
158 | #[derive(Debug)] | |
159 | struct FormattedSnippet { | |
160 | snippet: String, | |
161 | non_formatted_ranges: Vec<(usize, usize)>, | |
162 | } | |
163 | ||
164 | impl FormattedSnippet { | |
165 | /// In case the snippet needed to be wrapped in a function, this shifts down the ranges of | |
166 | /// non-formatted code. | |
167 | fn unwrap_code_block(&mut self) { | |
168 | self.non_formatted_ranges | |
169 | .iter_mut() | |
170 | .for_each(|(low, high)| { | |
171 | *low -= 1; | |
172 | *high -= 1; | |
173 | }); | |
174 | } | |
175 | ||
176 | /// Returns `true` if the line n did not get formatted. | |
177 | fn is_line_non_formatted(&self, n: usize) -> bool { | |
178 | self.non_formatted_ranges | |
179 | .iter() | |
180 | .any(|(low, high)| *low <= n && n <= *high) | |
181 | } | |
182 | } | |
183 | ||
184 | /// Reports on any issues that occurred during a run of Rustfmt. | |
185 | /// | |
186 | /// Can be reported to the user using the `Display` impl on [`FormatReportFormatter`]. | |
187 | #[derive(Clone)] | |
188 | pub struct FormatReport { | |
189 | // Maps stringified file paths to their associated formatting errors. | |
190 | internal: Rc<RefCell<(FormatErrorMap, ReportedErrors)>>, | |
191 | non_formatted_ranges: Vec<(usize, usize)>, | |
192 | } | |
193 | ||
194 | impl FormatReport { | |
195 | fn new() -> FormatReport { | |
196 | FormatReport { | |
197 | internal: Rc::new(RefCell::new((HashMap::new(), ReportedErrors::default()))), | |
198 | non_formatted_ranges: Vec::new(), | |
199 | } | |
200 | } | |
201 | ||
202 | fn add_non_formatted_ranges(&mut self, mut ranges: Vec<(usize, usize)>) { | |
203 | self.non_formatted_ranges.append(&mut ranges); | |
204 | } | |
205 | ||
206 | fn append(&self, f: FileName, mut v: Vec<FormattingError>) { | |
207 | self.track_errors(&v); | |
208 | self.internal | |
209 | .borrow_mut() | |
210 | .0 | |
211 | .entry(f) | |
212 | .and_modify(|fe| fe.append(&mut v)) | |
213 | .or_insert(v); | |
214 | } | |
215 | ||
216 | fn track_errors(&self, new_errors: &[FormattingError]) { | |
217 | let errs = &mut self.internal.borrow_mut().1; | |
218 | if !new_errors.is_empty() { | |
219 | errs.has_formatting_errors = true; | |
220 | } | |
cdc7bbd5 XL |
221 | if errs.has_operational_errors && errs.has_check_errors && errs.has_unformatted_code_errors |
222 | { | |
f20569fa XL |
223 | return; |
224 | } | |
225 | for err in new_errors { | |
226 | match err.kind { | |
cdc7bbd5 XL |
227 | ErrorKind::LineOverflow(..) => { |
228 | errs.has_operational_errors = true; | |
229 | } | |
230 | ErrorKind::TrailingWhitespace => { | |
f20569fa | 231 | errs.has_operational_errors = true; |
cdc7bbd5 XL |
232 | errs.has_unformatted_code_errors = true; |
233 | } | |
234 | ErrorKind::LostComment => { | |
235 | errs.has_unformatted_code_errors = true; | |
f20569fa | 236 | } |
923072b8 | 237 | ErrorKind::DeprecatedAttr | ErrorKind::BadAttr | ErrorKind::VersionMismatch => { |
f20569fa XL |
238 | errs.has_check_errors = true; |
239 | } | |
240 | _ => {} | |
241 | } | |
242 | } | |
243 | } | |
244 | ||
245 | fn add_diff(&mut self) { | |
246 | self.internal.borrow_mut().1.has_diff = true; | |
247 | } | |
248 | ||
249 | fn add_macro_format_failure(&mut self) { | |
250 | self.internal.borrow_mut().1.has_macro_format_failure = true; | |
251 | } | |
252 | ||
253 | fn add_parsing_error(&mut self) { | |
254 | self.internal.borrow_mut().1.has_parsing_errors = true; | |
255 | } | |
256 | ||
257 | fn warning_count(&self) -> usize { | |
258 | self.internal | |
259 | .borrow() | |
260 | .0 | |
261 | .iter() | |
262 | .map(|(_, errors)| errors.len()) | |
263 | .sum() | |
264 | } | |
265 | ||
266 | /// Whether any warnings or errors are present in the report. | |
267 | pub fn has_warnings(&self) -> bool { | |
268 | self.internal.borrow().1.has_formatting_errors | |
269 | } | |
270 | ||
271 | /// Print the report to a terminal using colours and potentially other | |
272 | /// fancy output. | |
273 | #[deprecated(note = "Use FormatReportFormatter with colors enabled instead")] | |
274 | pub fn fancy_print( | |
275 | &self, | |
276 | mut t: Box<dyn term::Terminal<Output = io::Stderr>>, | |
277 | ) -> Result<(), term::Error> { | |
278 | writeln!( | |
279 | t, | |
280 | "{}", | |
3c0e092e | 281 | FormatReportFormatterBuilder::new(self) |
f20569fa XL |
282 | .enable_colors(true) |
283 | .build() | |
284 | )?; | |
285 | Ok(()) | |
286 | } | |
287 | } | |
288 | ||
289 | /// Deprecated - Use FormatReportFormatter instead | |
290 | // https://github.com/rust-lang/rust/issues/78625 | |
291 | // https://github.com/rust-lang/rust/issues/39935 | |
292 | impl fmt::Display for FormatReport { | |
293 | // Prints all the formatting errors. | |
294 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { | |
3c0e092e | 295 | write!(fmt, "{}", FormatReportFormatterBuilder::new(self).build())?; |
f20569fa XL |
296 | Ok(()) |
297 | } | |
298 | } | |
299 | ||
300 | /// Format the given snippet. The snippet is expected to be *complete* code. | |
301 | /// When we cannot parse the given snippet, this function returns `None`. | |
302 | fn format_snippet(snippet: &str, config: &Config, is_macro_def: bool) -> Option<FormattedSnippet> { | |
303 | let mut config = config.clone(); | |
304 | panic::catch_unwind(|| { | |
305 | let mut out: Vec<u8> = Vec::with_capacity(snippet.len() * 2); | |
306 | config.set().emit_mode(config::EmitMode::Stdout); | |
307 | config.set().verbose(Verbosity::Quiet); | |
308 | config.set().hide_parse_errors(true); | |
cdc7bbd5 XL |
309 | if is_macro_def { |
310 | config.set().error_on_unformatted(true); | |
311 | } | |
f20569fa XL |
312 | |
313 | let (formatting_error, result) = { | |
314 | let input = Input::Text(snippet.into()); | |
315 | let mut session = Session::new(config, Some(&mut out)); | |
316 | let result = session.format_input_inner(input, is_macro_def); | |
317 | ( | |
318 | session.errors.has_macro_format_failure | |
319 | || session.out.as_ref().unwrap().is_empty() && !snippet.is_empty() | |
cdc7bbd5 XL |
320 | || result.is_err() |
321 | || (is_macro_def && session.has_unformatted_code_errors()), | |
f20569fa XL |
322 | result, |
323 | ) | |
324 | }; | |
325 | if formatting_error { | |
326 | None | |
327 | } else { | |
328 | String::from_utf8(out).ok().map(|snippet| FormattedSnippet { | |
329 | snippet, | |
330 | non_formatted_ranges: result.unwrap().non_formatted_ranges, | |
331 | }) | |
332 | } | |
333 | }) | |
334 | // Discard panics encountered while formatting the snippet | |
335 | // The ? operator is needed to remove the extra Option | |
336 | .ok()? | |
337 | } | |
338 | ||
339 | /// Format the given code block. Mainly targeted for code block in comment. | |
340 | /// The code block may be incomplete (i.e., parser may be unable to parse it). | |
341 | /// To avoid panic in parser, we wrap the code block with a dummy function. | |
342 | /// The returned code block does **not** end with newline. | |
343 | fn format_code_block( | |
344 | code_snippet: &str, | |
345 | config: &Config, | |
346 | is_macro_def: bool, | |
347 | ) -> Option<FormattedSnippet> { | |
348 | const FN_MAIN_PREFIX: &str = "fn main() {\n"; | |
349 | ||
350 | fn enclose_in_main_block(s: &str, config: &Config) -> String { | |
351 | let indent = Indent::from_width(config, config.tab_spaces()); | |
352 | let mut result = String::with_capacity(s.len() * 2); | |
353 | result.push_str(FN_MAIN_PREFIX); | |
354 | let mut need_indent = true; | |
355 | for (kind, line) in LineClasses::new(s) { | |
356 | if need_indent { | |
357 | result.push_str(&indent.to_string(config)); | |
358 | } | |
359 | result.push_str(&line); | |
360 | result.push('\n'); | |
361 | need_indent = indent_next_line(kind, &line, config); | |
362 | } | |
363 | result.push('}'); | |
364 | result | |
365 | } | |
366 | ||
367 | // Wrap the given code block with `fn main()` if it does not have one. | |
368 | let snippet = enclose_in_main_block(code_snippet, config); | |
369 | let mut result = String::with_capacity(snippet.len()); | |
370 | let mut is_first = true; | |
371 | ||
372 | // While formatting the code, ignore the config's newline style setting and always use "\n" | |
373 | // instead of "\r\n" for the newline characters. This is ok because the output here is | |
374 | // not directly outputted by rustfmt command, but used by the comment formatter's input. | |
375 | // We have output-file-wide "\n" ==> "\r\n" conversion process after here if it's necessary. | |
376 | let mut config_with_unix_newline = config.clone(); | |
377 | config_with_unix_newline | |
378 | .set() | |
379 | .newline_style(NewlineStyle::Unix); | |
380 | let mut formatted = format_snippet(&snippet, &config_with_unix_newline, is_macro_def)?; | |
381 | // Remove wrapping main block | |
382 | formatted.unwrap_code_block(); | |
383 | ||
384 | // Trim "fn main() {" on the first line and "}" on the last line, | |
385 | // then unindent the whole code block. | |
386 | let block_len = formatted | |
387 | .snippet | |
388 | .rfind('}') | |
389 | .unwrap_or_else(|| formatted.snippet.len()); | |
390 | let mut is_indented = true; | |
391 | let indent_str = Indent::from_width(config, config.tab_spaces()).to_string(config); | |
392 | for (kind, ref line) in LineClasses::new(&formatted.snippet[FN_MAIN_PREFIX.len()..block_len]) { | |
393 | if !is_first { | |
394 | result.push('\n'); | |
395 | } else { | |
396 | is_first = false; | |
397 | } | |
398 | let trimmed_line = if !is_indented { | |
399 | line | |
400 | } else if line.len() > config.max_width() { | |
401 | // If there are lines that are larger than max width, we cannot tell | |
402 | // whether we have succeeded but have some comments or strings that | |
403 | // are too long, or we have failed to format code block. We will be | |
404 | // conservative and just return `None` in this case. | |
405 | return None; | |
406 | } else if line.len() > indent_str.len() { | |
407 | // Make sure that the line has leading whitespaces. | |
408 | if line.starts_with(indent_str.as_ref()) { | |
409 | let offset = if config.hard_tabs() { | |
410 | 1 | |
411 | } else { | |
412 | config.tab_spaces() | |
413 | }; | |
414 | &line[offset..] | |
415 | } else { | |
416 | line | |
417 | } | |
418 | } else { | |
419 | line | |
420 | }; | |
421 | result.push_str(trimmed_line); | |
422 | is_indented = indent_next_line(kind, line, config); | |
423 | } | |
424 | Some(FormattedSnippet { | |
425 | snippet: result, | |
426 | non_formatted_ranges: formatted.non_formatted_ranges, | |
427 | }) | |
428 | } | |
429 | ||
430 | /// A session is a run of rustfmt across a single or multiple inputs. | |
431 | pub struct Session<'b, T: Write> { | |
432 | pub config: Config, | |
433 | pub out: Option<&'b mut T>, | |
434 | pub(crate) errors: ReportedErrors, | |
435 | source_file: SourceFile, | |
436 | emitter: Box<dyn Emitter + 'b>, | |
437 | } | |
438 | ||
439 | impl<'b, T: Write + 'b> Session<'b, T> { | |
440 | pub fn new(config: Config, mut out: Option<&'b mut T>) -> Session<'b, T> { | |
441 | let emitter = create_emitter(&config); | |
442 | ||
443 | if let Some(ref mut out) = out { | |
444 | let _ = emitter.emit_header(out); | |
445 | } | |
446 | ||
447 | Session { | |
448 | config, | |
449 | out, | |
450 | emitter, | |
451 | errors: ReportedErrors::default(), | |
452 | source_file: SourceFile::new(), | |
453 | } | |
454 | } | |
455 | ||
456 | /// The main entry point for Rustfmt. Formats the given input according to the | |
457 | /// given config. `out` is only necessary if required by the configuration. | |
458 | pub fn format(&mut self, input: Input) -> Result<FormatReport, ErrorKind> { | |
459 | self.format_input_inner(input, false) | |
460 | } | |
461 | ||
462 | pub fn override_config<F, U>(&mut self, mut config: Config, f: F) -> U | |
463 | where | |
464 | F: FnOnce(&mut Session<'b, T>) -> U, | |
465 | { | |
466 | mem::swap(&mut config, &mut self.config); | |
467 | let result = f(self); | |
468 | mem::swap(&mut config, &mut self.config); | |
469 | result | |
470 | } | |
471 | ||
472 | pub fn add_operational_error(&mut self) { | |
473 | self.errors.has_operational_errors = true; | |
474 | } | |
475 | ||
476 | pub fn has_operational_errors(&self) -> bool { | |
477 | self.errors.has_operational_errors | |
478 | } | |
479 | ||
480 | pub fn has_parsing_errors(&self) -> bool { | |
481 | self.errors.has_parsing_errors | |
482 | } | |
483 | ||
484 | pub fn has_formatting_errors(&self) -> bool { | |
485 | self.errors.has_formatting_errors | |
486 | } | |
487 | ||
488 | pub fn has_check_errors(&self) -> bool { | |
489 | self.errors.has_check_errors | |
490 | } | |
491 | ||
492 | pub fn has_diff(&self) -> bool { | |
493 | self.errors.has_diff | |
494 | } | |
495 | ||
cdc7bbd5 XL |
496 | pub fn has_unformatted_code_errors(&self) -> bool { |
497 | self.errors.has_unformatted_code_errors | |
498 | } | |
499 | ||
f20569fa XL |
500 | pub fn has_no_errors(&self) -> bool { |
501 | !(self.has_operational_errors() | |
502 | || self.has_parsing_errors() | |
503 | || self.has_formatting_errors() | |
504 | || self.has_check_errors() | |
cdc7bbd5 XL |
505 | || self.has_diff() |
506 | || self.has_unformatted_code_errors() | |
507 | || self.errors.has_macro_format_failure) | |
f20569fa XL |
508 | } |
509 | } | |
510 | ||
511 | pub(crate) fn create_emitter<'a>(config: &Config) -> Box<dyn Emitter + 'a> { | |
512 | match config.emit_mode() { | |
513 | EmitMode::Files if config.make_backup() => { | |
514 | Box::new(emitter::FilesWithBackupEmitter::default()) | |
515 | } | |
516 | EmitMode::Files => Box::new(emitter::FilesEmitter::new( | |
517 | config.print_misformatted_file_names(), | |
518 | )), | |
519 | EmitMode::Stdout | EmitMode::Coverage => { | |
520 | Box::new(emitter::StdoutEmitter::new(config.verbose())) | |
521 | } | |
522 | EmitMode::Json => Box::new(emitter::JsonEmitter::default()), | |
523 | EmitMode::ModifiedLines => Box::new(emitter::ModifiedLinesEmitter::default()), | |
524 | EmitMode::Checkstyle => Box::new(emitter::CheckstyleEmitter::default()), | |
525 | EmitMode::Diff => Box::new(emitter::DiffEmitter::new(config.clone())), | |
526 | } | |
527 | } | |
528 | ||
529 | impl<'b, T: Write + 'b> Drop for Session<'b, T> { | |
530 | fn drop(&mut self) { | |
531 | if let Some(ref mut out) = self.out { | |
532 | let _ = self.emitter.emit_footer(out); | |
533 | } | |
534 | } | |
535 | } | |
536 | ||
537 | #[derive(Debug)] | |
538 | pub enum Input { | |
539 | File(PathBuf), | |
540 | Text(String), | |
541 | } | |
542 | ||
543 | impl Input { | |
544 | fn file_name(&self) -> FileName { | |
545 | match *self { | |
546 | Input::File(ref file) => FileName::Real(file.clone()), | |
547 | Input::Text(..) => FileName::Stdin, | |
548 | } | |
549 | } | |
550 | ||
551 | fn to_directory_ownership(&self) -> Option<DirectoryOwnership> { | |
552 | match self { | |
553 | Input::File(ref file) => { | |
554 | // If there exists a directory with the same name as an input, | |
555 | // then the input should be parsed as a sub module. | |
556 | let file_stem = file.file_stem()?; | |
557 | if file.parent()?.to_path_buf().join(file_stem).is_dir() { | |
558 | Some(DirectoryOwnership::Owned { | |
559 | relative: file_stem.to_str().map(symbol::Ident::from_str), | |
560 | }) | |
561 | } else { | |
562 | None | |
563 | } | |
564 | } | |
565 | _ => None, | |
566 | } | |
567 | } | |
568 | } | |
569 | ||
570 | #[cfg(test)] | |
571 | mod unit_tests { | |
572 | use super::*; | |
573 | ||
574 | #[test] | |
575 | fn test_no_panic_on_format_snippet_and_format_code_block() { | |
576 | // `format_snippet()` and `format_code_block()` should not panic | |
577 | // even when we cannot parse the given snippet. | |
578 | let snippet = "let"; | |
579 | assert!(format_snippet(snippet, &Config::default(), false).is_none()); | |
580 | assert!(format_code_block(snippet, &Config::default(), false).is_none()); | |
581 | } | |
582 | ||
583 | fn test_format_inner<F>(formatter: F, input: &str, expected: &str) -> bool | |
584 | where | |
585 | F: Fn(&str, &Config, bool) -> Option<FormattedSnippet>, | |
586 | { | |
587 | let output = formatter(input, &Config::default(), false); | |
588 | output.is_some() && output.unwrap().snippet == expected | |
589 | } | |
590 | ||
591 | #[test] | |
592 | fn test_format_snippet() { | |
593 | let snippet = "fn main() { println!(\"hello, world\"); }"; | |
594 | #[cfg(not(windows))] | |
595 | let expected = "fn main() {\n \ | |
596 | println!(\"hello, world\");\n\ | |
597 | }\n"; | |
598 | #[cfg(windows)] | |
599 | let expected = "fn main() {\r\n \ | |
600 | println!(\"hello, world\");\r\n\ | |
601 | }\r\n"; | |
602 | assert!(test_format_inner(format_snippet, snippet, expected)); | |
603 | } | |
604 | ||
605 | #[test] | |
606 | fn test_format_code_block_fail() { | |
607 | #[rustfmt::skip] | |
608 | let code_block = "this_line_is_100_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(x, y, z);"; | |
609 | assert!(format_code_block(code_block, &Config::default(), false).is_none()); | |
610 | } | |
611 | ||
612 | #[test] | |
613 | fn test_format_code_block() { | |
614 | // simple code block | |
615 | let code_block = "let x=3;"; | |
616 | let expected = "let x = 3;"; | |
617 | assert!(test_format_inner(format_code_block, code_block, expected)); | |
618 | ||
619 | // more complex code block, taken from chains.rs. | |
620 | let code_block = | |
621 | "let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) { | |
622 | ( | |
623 | chain_indent(context, shape.add_offset(parent_rewrite.len())), | |
624 | context.config.indent_style() == IndentStyle::Visual || is_small_parent, | |
625 | ) | |
626 | } else if is_block_expr(context, &parent, &parent_rewrite) { | |
627 | match context.config.indent_style() { | |
628 | // Try to put the first child on the same line with parent's last line | |
629 | IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true), | |
630 | // The parent is a block, so align the rest of the chain with the closing | |
631 | // brace. | |
632 | IndentStyle::Visual => (parent_shape, false), | |
633 | } | |
634 | } else { | |
635 | ( | |
636 | chain_indent(context, shape.add_offset(parent_rewrite.len())), | |
637 | false, | |
638 | ) | |
639 | }; | |
640 | "; | |
641 | let expected = | |
642 | "let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) { | |
643 | ( | |
644 | chain_indent(context, shape.add_offset(parent_rewrite.len())), | |
645 | context.config.indent_style() == IndentStyle::Visual || is_small_parent, | |
646 | ) | |
647 | } else if is_block_expr(context, &parent, &parent_rewrite) { | |
648 | match context.config.indent_style() { | |
649 | // Try to put the first child on the same line with parent's last line | |
650 | IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true), | |
651 | // The parent is a block, so align the rest of the chain with the closing | |
652 | // brace. | |
653 | IndentStyle::Visual => (parent_shape, false), | |
654 | } | |
655 | } else { | |
656 | ( | |
657 | chain_indent(context, shape.add_offset(parent_rewrite.len())), | |
658 | false, | |
659 | ) | |
660 | };"; | |
661 | assert!(test_format_inner(format_code_block, code_block, expected)); | |
662 | } | |
663 | } |