rust/src/lib.rs

703 lines
23 KiB
Rust
Raw Normal View History

2019-02-09 00:55:17 -06:00
#![deny(rust_2018_idioms)]
2017-11-06 06:44:59 -06:00
#[macro_use]
extern crate derive_new;
#[cfg(test)]
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
2015-09-03 22:38:12 -05:00
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::io::{self, Write};
2018-07-22 21:52:02 -05:00
use std::mem;
use std::panic;
use std::path::PathBuf;
2016-03-01 16:27:19 -06:00
use std::rc::Rc;
2018-04-24 03:10:09 -05:00
use failure::Fail;
2019-03-16 22:25:59 -05:00
use syntax::{ast, parse::DirectoryOwnership};
2019-02-04 04:30:43 -06:00
use crate::comment::LineClasses;
use crate::formatting::{FormatErrorMap, FormattingError, ReportedErrors, SourceFile};
use crate::issues::Issue;
use crate::shape::Indent;
use crate::utils::indent_next_line;
2019-02-04 04:30:43 -06:00
pub use crate::config::{
load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName, NewlineStyle,
2019-02-09 07:23:54 -06:00
Range, Verbosity,
};
pub use crate::rustfmt_diff::{ModifiedChunk, ModifiedLines};
2015-06-08 13:23:24 -05:00
#[macro_use]
mod utils;
2018-02-17 10:06:54 -06:00
2018-02-23 07:30:05 -06:00
mod attr;
mod chains;
pub(crate) mod checkstyle;
2017-11-12 20:26:33 -06:00
mod closures;
mod comment;
pub(crate) mod config;
2015-04-21 04:01:19 -05:00
mod expr;
pub(crate) mod formatting;
2015-04-21 04:01:19 -05:00
mod imports;
mod issues;
mod items;
mod lists;
2015-09-14 15:53:30 -05:00
mod macros;
mod matches;
mod missed_spans;
pub(crate) mod modules;
mod overflow;
2018-06-27 19:02:05 -05:00
mod pairs;
2015-10-17 08:56:53 -05:00
mod patterns;
2018-02-17 10:06:54 -06:00
mod reorder;
mod rewrite;
pub(crate) mod rustfmt_diff;
mod shape;
2018-08-23 16:14:19 -05:00
pub(crate) mod source_file;
pub(crate) mod source_map;
mod spanned;
mod string;
#[cfg(test)]
mod test;
mod types;
mod vertical;
pub(crate) mod visitor;
2018-05-20 22:54:56 -05:00
/// The various errors that can occur during formatting. Note that not all of
/// these can currently be propagated to clients.
2018-05-18 23:11:30 -05:00
#[derive(Fail, Debug)]
pub enum ErrorKind {
2018-05-20 22:54:56 -05:00
/// Line has exceeded character limit (found, maximum).
2018-04-24 03:10:09 -05:00
#[fail(
2018-05-06 02:17:09 -05:00
display = "line formatted, but exceeded maximum width \
(maximum: {} (see `max_width` option), found: {})",
_1, _0
2018-04-24 03:10:09 -05:00
)]
LineOverflow(usize, usize),
2018-05-20 22:54:56 -05:00
/// Line ends in whitespace.
2018-04-24 03:10:09 -05:00
#[fail(display = "left behind trailing whitespace")]
TrailingWhitespace,
2018-05-20 22:54:56 -05:00
/// TODO or FIXME item without an issue number.
2018-04-24 03:10:09 -05:00
#[fail(display = "found {}", _0)]
BadIssue(Issue),
2018-05-20 22:54:56 -05:00
/// License check has failed.
2018-04-24 03:10:09 -05:00
#[fail(display = "license check failed")]
Attempt at checking for license (#209) I'm not quite sure how best to handle loading the license template from a path -- I mean obviously I know *how* to do it, but I'm not sure where to fit it in the codebase :) So this first attempt puts the license template directly into the config file. These are my misgivings about the license template config option as a path to a file (I'd love feedback if some of these are wrong or can be easily circumvented!): 1. I thought the obvious choice for the type of `license_template` in `create_config!` should be `PathBuf`, but `PathBuf` doesn't implement `FromStr` (yet? see https://github.com/rust-lang/rust/issues/44431), so it would have to be wrapped in a tuple struct, and I went down that road for a little while but then it seemed like too much ceremony for too little gain. 2. So a plain `String` then (which, mind you, also means the same `doc_hint()`, i.e. `<string>`, not `<path>` or something like that). The fact that it's a valid path will be checked once we try to read the file. 3. But where in the code should the license template be read? The obvious choice for me would be somewhere in `Config::from_toml()`, but since `Config` is defined via the `create_config!` macro, that would mean tight coupling between the macro invocation (which defines the configuration option `license_template`) and its definition (which would rely on the existence of that option to run the template loading code). 4. `license_template` could also be made a special option which is hardwired into the macro. This gets rid of the tight coupling, but special-casing one of the config options would make the code harder to navigate. 5. Instead, the macro could maybe be rewritten to allow for config options that load additional resources from files when the config is being parsed, but that's beyond my skill level I'm afraid (and probably overengineering the problem if it's only ever going to be used for this one option). 6. Finally, the file can be loaded at some later point in time, e.g. in `format_lines()`, right before `check_license()` is called. But to face a potential *IO* error at so late a stage, when the source files have already been parsed... I don't know, it doesn't feel right. BTW I don't like that I'm actually parsing the license template as late as inside `check_license()` either, but for much the same reasons, I don't know where else to put it. If the `Config` were hand-rolled instead of a macro, I'd just define a custom `license_template` option and load and parse the template in the `Config`'s init. But the way things are, I'm a bit at a loss. However, if someone more familiar with the project would kindly provide a few hints as to how the path approach can be done in a way that is as clean as possible in the context of the codebase, I'll be more than happy to implement it! :)
2018-02-16 16:21:57 -06:00
LicenseCheck,
2018-05-20 22:54:56 -05:00
/// Used deprecated skip attribute.
#[fail(display = "`rustfmt_skip` is deprecated; use `rustfmt::skip`")]
DeprecatedAttr,
2018-05-20 22:54:56 -05:00
/// Used a rustfmt:: attribute other than skip.
#[fail(display = "invalid attribute")]
BadAttr,
2018-05-20 22:54:56 -05:00
/// An io error during reading or writing.
2018-05-18 23:11:30 -05:00
#[fail(display = "io error: {}", _0)]
IoError(io::Error),
2018-10-18 17:44:14 -05:00
/// Parse error occurred when parsing the input.
#[fail(display = "parse error")]
ParseError,
2018-05-20 22:54:56 -05:00
/// The user mandated a version and the current version of Rustfmt does not
/// satisfy that requirement.
#[fail(display = "version mismatch")]
VersionMismatch,
/// If we had formatted the given node, then we would have lost a comment.
#[fail(display = "not formatted because a comment would be lost")]
LostComment,
}
impl ErrorKind {
fn is_comment(&self) -> bool {
match self {
ErrorKind::LostComment => true,
_ => false,
}
}
2018-05-18 23:11:30 -05:00
}
impl From<io::Error> for ErrorKind {
fn from(e: io::Error) -> ErrorKind {
ErrorKind::IoError(e)
}
}
/// Result of formatting a snippet of code along with ranges of lines that didn't get formatted,
/// i.e., that got returned as they were originally.
#[derive(Debug)]
struct FormattedSnippet {
snippet: String,
non_formatted_ranges: Vec<(usize, usize)>,
}
impl FormattedSnippet {
/// In case the snippet needed to be wrapped in a function, this shifts down the ranges of
/// non-formatted code.
fn unwrap_code_block(&mut self) {
self.non_formatted_ranges
.iter_mut()
.for_each(|(low, high)| {
*low -= 1;
*high -= 1;
});
}
2019-02-18 20:56:42 -06:00
/// Returns `true` if the line n did not get formatted.
fn is_line_non_formatted(&self, n: usize) -> bool {
self.non_formatted_ranges
.iter()
.any(|(low, high)| *low <= n && n <= *high)
}
}
2018-05-20 22:54:56 -05:00
/// Reports on any issues that occurred during a run of Rustfmt.
///
/// Can be reported to the user via its `Display` implementation of `print_fancy`.
#[derive(Clone)]
pub struct FormatReport {
// Maps stringified file paths to their associated formatting errors.
internal: Rc<RefCell<(FormatErrorMap, ReportedErrors)>>,
non_formatted_ranges: Vec<(usize, usize)>,
}
impl FormatReport {
fn new() -> FormatReport {
2017-07-03 04:54:26 -05:00
FormatReport {
internal: Rc::new(RefCell::new((HashMap::new(), ReportedErrors::default()))),
non_formatted_ranges: Vec::new(),
2017-07-03 04:54:26 -05:00
}
}
fn add_non_formatted_ranges(&mut self, mut ranges: Vec<(usize, usize)>) {
self.non_formatted_ranges.append(&mut ranges);
}
fn append(&self, f: FileName, mut v: Vec<FormattingError>) {
self.track_errors(&v);
self.internal
.borrow_mut()
.0
.entry(f)
.and_modify(|fe| fe.append(&mut v))
.or_insert(v);
}
fn track_errors(&self, new_errors: &[FormattingError]) {
let errs = &mut self.internal.borrow_mut().1;
2018-07-24 04:41:49 -05:00
if !new_errors.is_empty() {
errs.has_formatting_errors = true;
}
if errs.has_operational_errors && errs.has_check_errors {
return;
}
for err in new_errors {
match err.kind {
ErrorKind::LineOverflow(..) | ErrorKind::TrailingWhitespace => {
errs.has_operational_errors = true;
}
ErrorKind::BadIssue(_)
| ErrorKind::LicenseCheck
| ErrorKind::DeprecatedAttr
| ErrorKind::BadAttr
| ErrorKind::VersionMismatch => {
errs.has_check_errors = true;
}
2018-05-18 23:11:30 -05:00
_ => {}
}
}
}
2018-07-24 04:41:49 -05:00
fn add_diff(&mut self) {
self.internal.borrow_mut().1.has_diff = true;
}
fn add_macro_format_failure(&mut self) {
self.internal.borrow_mut().1.has_macro_format_failure = true;
}
fn add_parsing_error(&mut self) {
self.internal.borrow_mut().1.has_parsing_errors = true;
}
fn warning_count(&self) -> usize {
self.internal
.borrow()
.0
.iter()
.map(|(_, errors)| errors.len())
2018-01-25 23:53:28 -06:00
.sum()
}
2018-05-20 22:54:56 -05:00
/// Whether any warnings or errors are present in the report.
pub fn has_warnings(&self) -> bool {
2018-07-24 04:41:49 -05:00
self.internal.borrow().1.has_formatting_errors
}
2018-05-20 22:54:56 -05:00
/// Print the report to a terminal using colours and potentially other
/// fancy output.
pub fn fancy_print(
&self,
2019-02-09 01:14:30 -06:00
mut t: Box<dyn term::Terminal<Output = io::Stderr>>,
) -> Result<(), term::Error> {
for (file, errors) in &self.internal.borrow().0 {
for error in errors {
let prefix_space_len = error.line.to_string().len();
2018-04-01 02:20:46 -05:00
let prefix_spaces = " ".repeat(1 + prefix_space_len);
// First line: the overview of error
t.fg(term::color::RED)?;
t.attr(term::Attr::Bold)?;
write!(t, "{} ", error.msg_prefix())?;
t.reset()?;
t.attr(term::Attr::Bold)?;
2018-04-24 17:21:23 -05:00
writeln!(t, "{}", error.kind)?;
// Second line: file info
write!(t, "{}--> ", &prefix_spaces[1..])?;
t.reset()?;
2018-04-24 17:21:23 -05:00
writeln!(t, "{}:{}", file, error.line)?;
// Third to fifth lines: show the line which triggered error, if available.
if !error.line_buffer.is_empty() {
let (space_len, target_len) = error.format_len();
t.attr(term::Attr::Bold)?;
write!(t, "{}|\n{} | ", prefix_spaces, error.line)?;
t.reset()?;
2018-04-24 17:21:23 -05:00
writeln!(t, "{}", error.line_buffer)?;
t.attr(term::Attr::Bold)?;
write!(t, "{}| ", prefix_spaces)?;
t.fg(term::color::RED)?;
writeln!(t, "{}", FormatReport::target_str(space_len, target_len))?;
t.reset()?;
}
// The last line: show note if available.
let msg_suffix = error.msg_suffix();
if !msg_suffix.is_empty() {
t.attr(term::Attr::Bold)?;
write!(t, "{}= note: ", prefix_spaces)?;
t.reset()?;
2018-04-24 17:21:23 -05:00
writeln!(t, "{}", error.msg_suffix())?;
} else {
2018-04-24 17:21:23 -05:00
writeln!(t)?;
}
t.reset()?;
}
}
if !self.internal.borrow().0.is_empty() {
t.attr(term::Attr::Bold)?;
write!(t, "warning: ")?;
t.reset()?;
write!(
t,
"rustfmt may have failed to format. See previous {} errors.\n\n",
self.warning_count(),
)?;
}
Ok(())
}
fn target_str(space_len: usize, target_len: usize) -> String {
let empty_line = " ".repeat(space_len);
let overflowed = "^".repeat(target_len);
empty_line + &overflowed
}
}
impl fmt::Display for FormatReport {
// Prints all the formatting errors.
2019-02-09 01:14:30 -06:00
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
for (file, errors) in &self.internal.borrow().0 {
for error in errors {
let prefix_space_len = error.line.to_string().len();
2018-04-01 02:20:46 -05:00
let prefix_spaces = " ".repeat(1 + prefix_space_len);
let error_line_buffer = if error.line_buffer.is_empty() {
String::from(" ")
} else {
let (space_len, target_len) = error.format_len();
format!(
"{}|\n{} | {}\n{}| {}",
prefix_spaces,
error.line,
error.line_buffer,
prefix_spaces,
FormatReport::target_str(space_len, target_len)
)
};
let error_info = format!("{} {}", error.msg_prefix(), error.kind);
let file_info = format!("{}--> {}:{}", &prefix_spaces[1..], file, error.line);
let msg_suffix = error.msg_suffix();
let note = if msg_suffix.is_empty() {
String::new()
} else {
format!("{}note= ", prefix_spaces)
};
2018-04-24 17:21:23 -05:00
writeln!(
fmt,
2018-04-24 17:21:23 -05:00
"{}\n{}\n{}\n{}{}",
error_info,
file_info,
error_line_buffer,
note,
error.msg_suffix()
)?;
}
}
if !self.internal.borrow().0.is_empty() {
2018-04-24 17:21:23 -05:00
writeln!(
fmt,
2018-04-24 17:21:23 -05:00
"warning: rustfmt may have failed to format. See previous {} errors.",
self.warning_count(),
)?;
}
Ok(())
}
}
/// Format the given snippet. The snippet is expected to be *complete* code.
/// When we cannot parse the given snippet, this function returns `None`.
fn format_snippet(snippet: &str, config: &Config) -> Option<FormattedSnippet> {
let mut config = config.clone();
panic::catch_unwind(|| {
let mut out: Vec<u8> = Vec::with_capacity(snippet.len() * 2);
config.set().emit_mode(config::EmitMode::Stdout);
config.set().verbose(Verbosity::Quiet);
config.set().hide_parse_errors(true);
let (formatting_error, result) = {
let input = Input::Text(snippet.into());
let mut session = Session::new(config, Some(&mut out));
let result = session.format(input);
(
session.errors.has_macro_format_failure
|| session.out.as_ref().unwrap().is_empty() && !snippet.is_empty()
|| result.is_err(),
result,
)
};
if formatting_error {
None
} else {
String::from_utf8(out).ok().map(|snippet| FormattedSnippet {
snippet,
non_formatted_ranges: result.unwrap().non_formatted_ranges,
})
2018-07-22 21:52:02 -05:00
}
})
// Discard panics encountered while formatting the snippet
// The ? operator is needed to remove the extra Option
.ok()?
}
/// Format the given code block. Mainly targeted for code block in comment.
2019-02-18 20:56:42 -06:00
/// The code block may be incomplete (i.e., parser may be unable to parse it).
/// To avoid panic in parser, we wrap the code block with a dummy function.
2019-02-18 20:56:42 -06:00
/// The returned code block does **not** end with newline.
fn format_code_block(code_snippet: &str, config: &Config) -> Option<FormattedSnippet> {
const FN_MAIN_PREFIX: &str = "fn main() {\n";
fn enclose_in_main_block(s: &str, config: &Config) -> String {
let indent = Indent::from_width(config, config.tab_spaces());
let mut result = String::with_capacity(s.len() * 2);
result.push_str(FN_MAIN_PREFIX);
let mut need_indent = true;
for (kind, line) in LineClasses::new(s) {
if need_indent {
result.push_str(&indent.to_string(config));
}
result.push_str(&line);
result.push('\n');
need_indent = indent_next_line(kind, &line, config);
}
result.push('}');
result
}
// Wrap the given code block with `fn main()` if it does not have one.
let snippet = enclose_in_main_block(code_snippet, config);
let mut result = String::with_capacity(snippet.len());
let mut is_first = true;
2018-06-13 07:54:06 -05:00
// While formatting the code, ignore the config's newline style setting and always use "\n"
2019-02-18 20:56:42 -06:00
// instead of "\r\n" for the newline characters. This is ok because the output here is
2018-06-13 07:54:06 -05:00
// not directly outputted by rustfmt command, but used by the comment formatter's input.
2018-10-18 17:44:14 -05:00
// We have output-file-wide "\n" ==> "\r\n" conversion process after here if it's necessary.
2018-06-13 07:54:06 -05:00
let mut config_with_unix_newline = config.clone();
config_with_unix_newline
.set()
.newline_style(NewlineStyle::Unix);
let mut formatted = format_snippet(&snippet, &config_with_unix_newline)?;
// Remove wrapping main block
formatted.unwrap_code_block();
2018-06-13 07:54:06 -05:00
// Trim "fn main() {" on the first line and "}" on the last line,
// then unindent the whole code block.
let block_len = formatted
.snippet
.rfind('}')
.unwrap_or(formatted.snippet.len());
let mut is_indented = true;
for (kind, ref line) in LineClasses::new(&formatted.snippet[FN_MAIN_PREFIX.len()..block_len]) {
if !is_first {
result.push('\n');
} else {
is_first = false;
}
let trimmed_line = if !is_indented {
line
} else if line.len() > config.max_width() {
// If there are lines that are larger than max width, we cannot tell
// whether we have succeeded but have some comments or strings that
// are too long, or we have failed to format code block. We will be
// conservative and just return `None` in this case.
return None;
} else if line.len() > config.tab_spaces() {
// Make sure that the line has leading whitespaces.
let indent_str = Indent::from_width(config, config.tab_spaces()).to_string(config);
if line.starts_with(indent_str.as_ref()) {
let offset = if config.hard_tabs() {
1
} else {
config.tab_spaces()
};
&line[offset..]
} else {
line
}
} else {
line
};
result.push_str(trimmed_line);
is_indented = indent_next_line(kind, line, config);
}
Some(FormattedSnippet {
snippet: result,
non_formatted_ranges: formatted.non_formatted_ranges,
})
}
2018-07-22 21:52:02 -05:00
/// A session is a run of rustfmt across a single or multiple inputs.
2019-02-09 01:14:30 -06:00
pub struct Session<'b, T: Write> {
2018-07-22 21:52:02 -05:00
pub config: Config,
pub out: Option<&'b mut T>,
2018-07-24 04:41:49 -05:00
pub(crate) errors: ReportedErrors,
source_file: SourceFile,
}
2018-07-22 21:52:02 -05:00
impl<'b, T: Write + 'b> Session<'b, T> {
pub fn new(config: Config, out: Option<&'b mut T>) -> Session<'b, T> {
if config.emit_mode() == EmitMode::Checkstyle {
println!("{}", checkstyle::header());
}
Session {
config,
out,
2018-07-24 04:41:49 -05:00
errors: ReportedErrors::default(),
source_file: SourceFile::new(),
2018-07-22 21:52:02 -05:00
}
}
/// The main entry point for Rustfmt. Formats the given input according to the
/// given config. `out` is only necessary if required by the configuration.
pub fn format(&mut self, input: Input) -> Result<FormatReport, ErrorKind> {
self.format_input_inner(input)
}
2018-07-22 21:52:02 -05:00
pub fn override_config<F, U>(&mut self, mut config: Config, f: F) -> U
where
F: FnOnce(&mut Session<'b, T>) -> U,
{
mem::swap(&mut config, &mut self.config);
let result = f(self);
mem::swap(&mut config, &mut self.config);
result
}
2018-07-24 04:41:49 -05:00
pub fn add_operational_error(&mut self) {
self.errors.has_operational_errors = true;
}
pub fn has_operational_errors(&self) -> bool {
self.errors.has_operational_errors
}
pub fn has_parsing_errors(&self) -> bool {
self.errors.has_parsing_errors
}
pub fn has_formatting_errors(&self) -> bool {
self.errors.has_formatting_errors
}
pub fn has_check_errors(&self) -> bool {
self.errors.has_check_errors
}
pub fn has_diff(&self) -> bool {
self.errors.has_diff
}
pub fn has_no_errors(&self) -> bool {
!(self.has_operational_errors()
|| self.has_parsing_errors()
|| self.has_formatting_errors()
|| self.has_check_errors()
|| self.has_diff())
|| self.errors.has_macro_format_failure
}
2018-07-22 21:52:02 -05:00
}
impl<'b, T: Write + 'b> Drop for Session<'b, T> {
fn drop(&mut self) {
if self.config.emit_mode() == EmitMode::Checkstyle {
println!("{}", checkstyle::footer());
}
}
}
#[derive(Debug)]
pub enum Input {
File(PathBuf),
Text(String),
}
impl Input {
fn is_text(&self) -> bool {
match *self {
Input::File(_) => false,
Input::Text(_) => true,
}
}
fn file_name(&self) -> FileName {
match *self {
Input::File(ref file) => FileName::Real(file.clone()),
Input::Text(..) => FileName::Stdin,
}
}
2019-03-16 22:25:59 -05:00
fn to_directory_ownership(&self) -> Option<DirectoryOwnership> {
match self {
Input::File(ref file) => {
// If there exists a directory with the same name as an input,
// then the input should be parsed as a sub module.
let file_stem = file.file_stem()?;
if file.parent()?.to_path_buf().join(file_stem).is_dir() {
Some(DirectoryOwnership::Owned {
relative: file_stem.to_str().map(ast::Ident::from_str),
})
} else {
None
}
}
_ => None,
}
}
}
#[cfg(test)]
mod unit_tests {
use super::*;
#[test]
fn test_no_panic_on_format_snippet_and_format_code_block() {
// `format_snippet()` and `format_code_block()` should not panic
// even when we cannot parse the given snippet.
let snippet = "let";
assert!(format_snippet(snippet, &Config::default()).is_none());
assert!(format_code_block(snippet, &Config::default()).is_none());
}
fn test_format_inner<F>(formatter: F, input: &str, expected: &str) -> bool
where
F: Fn(&str, &Config) -> Option<FormattedSnippet>,
{
let output = formatter(input, &Config::default());
output.is_some() && output.unwrap().snippet == expected
}
#[test]
fn test_format_snippet() {
let snippet = "fn main() { println!(\"hello, world\"); }";
#[cfg(not(windows))]
let expected = "fn main() {\n \
println!(\"hello, world\");\n\
}\n";
#[cfg(windows)]
let expected = "fn main() {\r\n \
println!(\"hello, world\");\r\n\
}\r\n";
assert!(test_format_inner(format_snippet, snippet, expected));
}
#[test]
fn test_format_code_block_fail() {
#[rustfmt::skip]
let code_block = "this_line_is_100_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(x, y, z);";
assert!(format_code_block(code_block, &Config::default()).is_none());
}
#[test]
fn test_format_code_block() {
// simple code block
let code_block = "let x=3;";
let expected = "let x = 3;";
assert!(test_format_inner(format_code_block, code_block, expected));
// more complex code block, taken from chains.rs.
let code_block =
"let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) {
(
chain_indent(context, shape.add_offset(parent_rewrite.len())),
context.config.indent_style() == IndentStyle::Visual || is_small_parent,
)
} else if is_block_expr(context, &parent, &parent_rewrite) {
match context.config.indent_style() {
// Try to put the first child on the same line with parent's last line
IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true),
// The parent is a block, so align the rest of the chain with the closing
// brace.
IndentStyle::Visual => (parent_shape, false),
}
} else {
(
chain_indent(context, shape.add_offset(parent_rewrite.len())),
false,
)
};
";
let expected =
"let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) {
(
chain_indent(context, shape.add_offset(parent_rewrite.len())),
context.config.indent_style() == IndentStyle::Visual || is_small_parent,
)
} else if is_block_expr(context, &parent, &parent_rewrite) {
match context.config.indent_style() {
// Try to put the first child on the same line with parent's last line
IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true),
// The parent is a block, so align the rest of the chain with the closing
// brace.
IndentStyle::Visual => (parent_shape, false),
}
} else {
(
chain_indent(context, shape.add_offset(parent_rewrite.len())),
false,
)
};";
assert!(test_format_inner(format_code_block, code_block, expected));
}
}