Skip to content

Commit

Permalink
fix(items): Take into account multi-line outer macro attributes
Browse files Browse the repository at this point in the history
We properly handle multi-line attributes in front of an enum variant.

There are several types of attributes [1], but the only attributes that
can occur in this context are outer attributes like
`#[repr(transparent)]`, `/// Example` or `/** Example */`.

This commit deals with macro attributes like `#[repr(transparent)]`.
We implement our own trivial macro attribute parser to exclude them from
the variant definition, as we could not find a easy way of re-using
existing parsing code (with the `syn` crate or `rustc_parse`).

We will deal with outer documentation blocks in the next commit.

[1] https://docs.rs/syn/2.0.75/syn/struct.Attribute.html
  • Loading branch information
malikolivier committed Aug 21, 2024
1 parent 5601462 commit 277cb90
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 11 deletions.
67 changes: 56 additions & 11 deletions src/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use std::borrow::Cow;
use std::cmp::{max, min, Ordering};

use itertools::Itertools;
use regex::Regex;
use rustc_ast::visit;
use rustc_ast::{ast, ptr};
Expand Down Expand Up @@ -653,22 +654,66 @@ impl<'a> FmtVisitor<'a> {
return variant_str.contains('\n');
}

let mut first_line_is_read = false;
for line in variant_str.split('\n') {
if first_line_is_read {
return false;
// First exclude all doc comments
let mut lines = variant_str
.split('\n')
.filter(|line| !line.trim().starts_with("///"));

let mut variant_str = lines.join("\n");
// Skip macro attributes in variant_str
// We skip one macro attribute per loop iteration
loop {
let mut macro_attribute_found = false;
let mut macro_attribute_start_i = 0;
let mut bracket_count = 0;
let mut chars = variant_str.chars().enumerate();
while let Some((i, c)) = chars.next() {
match c {
'#' => {
if let Some((_, '[')) = chars.next() {
macro_attribute_start_i = i;
bracket_count += 1;
}
}
'[' => bracket_count += 1,
']' => {
bracket_count -= 1;
if bracket_count == 0 {
// Macro attribute was found and ends at the i-th position
// We remove it from variant_str
let mut s =
variant_str[..macro_attribute_start_i].trim().to_owned();
s.push_str(variant_str[(i + 1)..].trim());
variant_str = s;
macro_attribute_found = true;
break;
}
}
'\'' => {
// Handle char in attribute
chars.next();
chars.next();
}
'"' => {
// Handle quoted strings within attribute
while let Some((_, c)) = chars.next() {
if c == '\\' {
chars.next(); // Skip escaped character
} else if c == '"' {
break; // end of string
}
}
}
_ => {}
}
}

// skip rustdoc comments and macro attributes
let line = line.trim_start();
if line.starts_with("///") || line.starts_with("#") {
continue;
} else {
first_line_is_read = true;
if !macro_attribute_found {
break;
}
}

true
variant_str.contains('\n')
};
let has_multiline_variant = items.iter().any(is_multi_line_variant);
let has_single_line_variant = items.iter().any(|item| !is_multi_line_variant(item));
Expand Down
44 changes: 44 additions & 0 deletions tests/target/attribute-in-enum/vertical-macro-multi-line.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// rustfmt-style_edition: 2024
enum A {
B {
a: usize,
b: usize,
c: usize,
d: usize,
},

#[multiline_macro_attribute(
very_very_long_option1,
very_very_long_option2,
very_very_long_option3
)]
C {
a: usize,
},

#[attr_with_expression1(x = ']')]
D1 {
a: usize,
},

#[attr_with_expression2(x = vec![])]
D2 {
a: usize,
},

#[attr_with_expression3(x = "]")]
D3 {
a: usize,
},

#[attr_with_expression4(x = "\"]")]
D4 {
a: usize,
},

#[attr1]
#[attr2]
D5 {
a: usize,
},
}

0 comments on commit 277cb90

Please sign in to comment.