keep track of lines which formatting was disabled in order to prevent indentation which would cause code right-shifting

This commit is contained in:
Stéphane Campinas 2018-10-24 01:24:56 +02:00
parent 9c75a15f4c
commit 2f5d864c08
No known key found for this signature in database
GPG Key ID: 6D5620D908210133
7 changed files with 129 additions and 79 deletions

View File

@ -620,7 +620,7 @@ impl<'a> CommentRewrite<'a> {
let mut config = self.fmt.config.clone();
config.set().format_doc_comments(false);
match ::format_code_block(&self.code_block_buffer, &config) {
Some(ref s) => trim_custom_comment_prefix(s),
Some(ref s) => trim_custom_comment_prefix(&s.snippet),
None => trim_custom_comment_prefix(&self.code_block_buffer),
}
}

View File

@ -173,6 +173,8 @@ impl<'a, T: FormatHandler + 'a> FormatContext<'a, T> {
if visitor.macro_rewrite_failure {
self.report.add_macro_format_failure();
}
self.report
.add_non_formatted_ranges(visitor.skipped_range.clone());
self.handler
.handle_formatted_file(path, visitor.buffer.to_owned(), &mut self.report)

View File

@ -144,6 +144,34 @@ impl From<io::Error> for ErrorKind {
}
}
/// 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;
});
}
/// 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)
}
}
/// Reports on any issues that occurred during a run of Rustfmt.
///
/// Can be reported to the user via its `Display` implementation of `print_fancy`.
@ -151,15 +179,21 @@ impl From<io::Error> for ErrorKind {
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 {
FormatReport {
internal: Rc::new(RefCell::new((HashMap::new(), ReportedErrors::default()))),
non_formatted_ranges: Vec::new(),
}
}
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
@ -349,37 +383,44 @@ impl fmt::Display for FormatReport {
/// 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<String> {
fn format_snippet(snippet: &str, config: &Config) -> Option<FormattedSnippet> {
let mut config = config.clone();
let out = panic::catch_unwind(|| {
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 = {
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()
(
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 {
Some(out)
String::from_utf8(out).ok().map(|snippet| FormattedSnippet {
snippet,
non_formatted_ranges: result.unwrap().non_formatted_ranges,
})
}
})
.ok()??; // The first try operator handles the error from catch_unwind,
// whereas the second one handles None from the closure.
String::from_utf8(out).ok()
// 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.
/// 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.
/// The returned code block does *not* end with newline.
fn format_code_block(code_snippet: &str, config: &Config) -> Option<String> {
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 {
@ -412,13 +453,18 @@ fn format_code_block(code_snippet: &str, config: &Config) -> Option<String> {
config_with_unix_newline
.set()
.newline_style(NewlineStyle::Unix);
let formatted = format_snippet(&snippet, &config_with_unix_newline)?;
let mut formatted = format_snippet(&snippet, &config_with_unix_newline)?;
// Remove wrapping main block
formatted.unwrap_code_block();
// Trim "fn main() {" on the first line and "}" on the last line,
// then unindent the whole code block.
let block_len = formatted.rfind('}').unwrap_or(formatted.len());
let block_len = formatted
.snippet
.rfind('}')
.unwrap_or(formatted.snippet.len());
let mut is_indented = true;
for (kind, ref line) in LineClasses::new(&formatted[FN_MAIN_PREFIX.len()..block_len]) {
for (kind, ref line) in LineClasses::new(&formatted.snippet[FN_MAIN_PREFIX.len()..block_len]) {
if !is_first {
result.push('\n');
} else {
@ -451,7 +497,10 @@ fn format_code_block(code_snippet: &str, config: &Config) -> Option<String> {
result.push_str(trimmed_line);
is_indented = !kind.is_string() || line.ends_with('\\');
}
Some(result)
Some(FormattedSnippet {
snippet: result,
non_formatted_ranges: formatted.non_formatted_ranges,
})
}
/// A session is a run of rustfmt across a single or multiple inputs.
@ -571,10 +620,10 @@ mod unit_tests {
fn test_format_inner<F>(formatter: F, input: &str, expected: &str) -> bool
where
F: Fn(&str, &Config) -> Option<String>,
F: Fn(&str, &Config) -> Option<FormattedSnippet>,
{
let output = formatter(input, &Config::default());
output.is_some() && output.unwrap() == expected
output.is_some() && output.unwrap().snippet == expected
}
#[test]

View File

@ -1323,7 +1323,7 @@ impl MacroBranch {
config.set().max_width(new_width);
// First try to format as items, then as statements.
let new_body = match ::format_snippet(&body_str, &config) {
let new_body_snippet = match ::format_snippet(&body_str, &config) {
Some(new_body) => new_body,
None => {
let new_width = new_width + config.tab_spaces();
@ -1334,15 +1334,23 @@ impl MacroBranch {
}
}
};
let new_body = wrap_str(new_body, config.max_width(), shape)?;
let new_body = wrap_str(
new_body_snippet.snippet.to_string(),
config.max_width(),
shape,
)?;
// Indent the body since it is in a block.
let indent_str = body_indent.to_string(&config);
let mut new_body = LineClasses::new(new_body.trim_right())
.enumerate()
.fold(
(String::new(), true),
|(mut s, need_indent), (kind, ref l)| {
if !l.is_empty() && need_indent {
|(mut s, need_indent), (i, (kind, ref l))| {
if !l.is_empty()
&& need_indent
&& !new_body_snippet.is_line_non_formatted(i + 1)
{
s += &indent_str;
}
(s + l + "\n", !kind.is_string() || l.ends_with('\\'))

View File

@ -29,7 +29,7 @@ use source_map::{LineRangeUtils, SpanUtils};
use spanned::Spanned;
use utils::{
self, contains_skip, count_newlines, inner_attributes, mk_sp, ptr_vec_to_ref_vec,
rewrite_ident, trim_left_preserve_layout, DEPR_SKIP_ANNOTATION,
rewrite_ident, DEPR_SKIP_ANNOTATION,
};
use {ErrorKind, FormatReport, FormattingError};
@ -71,6 +71,8 @@ pub struct FmtVisitor<'a> {
pub is_if_else_block: bool,
pub snippet_provider: &'a SnippetProvider<'a>,
pub line_number: usize,
/// List of 1-based line ranges which were annotated with skip
/// Both bounds are inclusifs.
pub skipped_range: Vec<(usize, usize)>,
pub macro_rewrite_failure: bool,
pub(crate) report: FormatReport,
@ -109,8 +111,9 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
self.format_missing(stmt.span.hi());
}
ast::StmtKind::Local(..) | ast::StmtKind::Expr(..) | ast::StmtKind::Semi(..) => {
if contains_skip(get_attrs_from_stmt(stmt)) {
self.push_skipped_with_span(stmt.span());
let attrs = get_attrs_from_stmt(stmt);
if contains_skip(attrs) {
self.push_skipped_with_span(attrs, stmt.span());
} else {
let shape = self.shape();
let rewrite = self.with_context(|ctx| stmt.rewrite(&ctx, shape));
@ -120,7 +123,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
ast::StmtKind::Mac(ref mac) => {
let (ref mac, _macro_style, ref attrs) = **mac;
if self.visit_attrs(attrs, ast::AttrStyle::Outer) {
self.push_skipped_with_span(stmt.span());
self.push_skipped_with_span(attrs, stmt.span());
} else {
self.visit_mac(mac, None, MacroPosition::Statement);
}
@ -328,14 +331,14 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
// For use items, skip rewriting attributes. Just check for a skip attribute.
ast::ItemKind::Use(..) => {
if contains_skip(attrs) {
self.push_skipped_with_span(item.span());
self.push_skipped_with_span(attrs.as_slice(), item.span());
return;
}
}
// Module is inline, in this case we treat it like any other item.
_ if !is_mod_decl(item) => {
if self.visit_attrs(&item.attrs, ast::AttrStyle::Outer) {
self.push_skipped_with_span(item.span());
self.push_skipped_with_span(item.attrs.as_slice(), item.span());
return;
}
}
@ -354,7 +357,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
}
_ => {
if self.visit_attrs(&item.attrs, ast::AttrStyle::Outer) {
self.push_skipped_with_span(item.span());
self.push_skipped_with_span(item.attrs.as_slice(), item.span());
return;
}
}
@ -471,7 +474,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
skip_out_of_file_lines_range_visitor!(self, ti.span);
if self.visit_attrs(&ti.attrs, ast::AttrStyle::Outer) {
self.push_skipped_with_span(ti.span());
self.push_skipped_with_span(ti.attrs.as_slice(), ti.span());
return;
}
@ -515,7 +518,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
skip_out_of_file_lines_range_visitor!(self, ii.span);
if self.visit_attrs(&ii.attrs, ast::AttrStyle::Outer) {
self.push_skipped_with_span(ii.span());
self.push_skipped_with_span(ii.attrs.as_slice(), ii.span());
return;
}
@ -574,16 +577,9 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
}
#[allow(clippy::needless_pass_by_value)]
fn push_rewrite_inner(&mut self, span: Span, rewrite: Option<String>, is_skipped: bool) {
fn push_rewrite_inner(&mut self, span: Span, rewrite: Option<String>) {
if let Some(ref s) = rewrite {
self.push_str(s);
} else if is_skipped {
// in case the code block (e.g., inside a macro or a doc) is skipped a minimum of
// leading whitespaces is trimmed so that the code layout is kept but allows it to
// be indented as necessary
let snippet =
trim_left_preserve_layout(self.snippet(span), &self.block_indent, self.config);
self.push_str(&snippet);
} else {
let snippet = self.snippet(span);
self.push_str(snippet);
@ -593,13 +589,20 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
pub fn push_rewrite(&mut self, span: Span, rewrite: Option<String>) {
self.format_missing_with_indent(source!(self, span).lo());
self.push_rewrite_inner(span, rewrite, false);
self.push_rewrite_inner(span, rewrite);
}
pub fn push_skipped_with_span(&mut self, span: Span) {
self.format_missing_with_indent(source!(self, span).lo());
let lo = self.line_number + 1;
self.push_rewrite_inner(span, None, true);
pub fn push_skipped_with_span(&mut self, attrs: &[ast::Attribute], item_span: Span) {
self.format_missing_with_indent(source!(self, item_span).lo());
// do not take into account the lines with attributes as part of the skipped range
let attrs_end = attrs
.iter()
.map(|attr| self.source_map.lookup_char_pos(attr.span().hi()).line)
.max()
.unwrap_or(1);
// Add 1 to get the line past the last attribute
let lo = attrs_end + 1;
self.push_rewrite_inner(item_span, None);
let hi = self.line_number + 1;
self.skipped_range.push((lo, hi));
}

View File

@ -1,30 +0,0 @@
// rustfmt-wrap_comments: true
/// ```
/// pub unsafe fn _mm256_shufflehi_epi16(a: __m256i, imm8: i32) -> __m256i {
/// let imm8 = (imm8 & 0xFF) as u8;
/// let a = a.as_i16x16();
/// macro_rules! shuffle_done {
/// ($x01:expr, $x23:expr, $x45:expr, $x67:expr) => {
/// #[cfg_attr(rustfmt, rustfmt_skip)]
/// simd_shuffle16(a, a, [
/// 0, 1, 2, 3, 4+$x01, 4+$x23, 4+$x45, 4+$x67,
/// 8, 9, 10, 11, 12+$x01, 12+$x23, 12+$x45, 12+$x67
/// ]);
/// };
/// }
/// }
/// ```
pub unsafe fn _mm256_shufflehi_epi16(a: __m256i, imm8: i32) -> __m256i {
let imm8 = (imm8 & 0xFF) as u8;
let a = a.as_i16x16();
macro_rules! shuffle_done {
($x01:expr, $x23:expr, $x45:expr, $x67:expr) => {
#[cfg_attr(rustfmt, rustfmt_skip)]
simd_shuffle16(a, a, [
0, 1, 2, 3, 4+$x01, 4+$x23, 4+$x45, 4+$x67,
8, 9, 10, 11, 12+$x01, 12+$x23, 12+$x45, 12+$x67
]);
};
}
}

View File

@ -1,5 +1,8 @@
// rustfmt-wrap_comments: true
/// Although the indentation of the skipped method is off, it shouldn't be
/// changed.
///
/// ```
/// pub unsafe fn _mm256_shufflehi_epi16(a: __m256i, imm8: i32) -> __m256i {
/// let imm8 = (imm8 & 0xFF) as u8;
@ -7,10 +10,10 @@
/// macro_rules! shuffle_done {
/// ($x01:expr, $x23:expr, $x45:expr, $x67:expr) => {
/// #[cfg_attr(rustfmt, rustfmt_skip)]
/// simd_shuffle16(a, a, [
/// 0, 1, 2, 3, 4+$x01, 4+$x23, 4+$x45, 4+$x67,
/// 8, 9, 10, 11, 12+$x01, 12+$x23, 12+$x45, 12+$x67
/// ]);
/// simd_shuffle16(a, a, [
/// 0, 1, 2, 3, 4+$x01, 4+$x23, 4+$x45, 4+$x67,
/// 8, 9, 10, 11, 12+$x01, 12+$x23, 12+$x45, 12+$x67
/// ]);
/// };
/// }
/// }
@ -21,7 +24,22 @@ pub unsafe fn _mm256_shufflehi_epi16(a: __m256i, imm8: i32) -> __m256i {
macro_rules! shuffle_done {
($x01:expr, $x23:expr, $x45:expr, $x67:expr) => {
#[cfg_attr(rustfmt, rustfmt_skip)]
simd_shuffle16(a, a, [
simd_shuffle16(a, a, [
0, 1, 2, 3, 4+$x01, 4+$x23, 4+$x45, 4+$x67,
8, 9, 10, 11, 12+$x01, 12+$x23, 12+$x45, 12+$x67
]);
};
}
}
/// The skipped method shouldn't right-shift
pub unsafe fn _mm256_shufflehi_epi32(a: __m256i, imm8: i32) -> __m256i {
let imm8 = (imm8 & 0xFF) as u8;
let a = a.as_i16x16();
macro_rules! shuffle_done {
($x01:expr, $x23:expr, $x45:expr, $x67:expr) => {
#[cfg_attr(rustfmt, rustfmt_skip)]
simd_shuffle32(a, a, [
0, 1, 2, 3, 4+$x01, 4+$x23, 4+$x45, 4+$x67,
8, 9, 10, 11, 12+$x01, 12+$x23, 12+$x45, 12+$x67
]);