Skip to content

Custom import reordering #5632

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
### Added

- Users can now set `skip_macro_invocations` in `rustfmt.toml` [#5816](https://github.com/rust-lang/rustfmt/issues/5816)
- Supported custom wildcarded groups for [group_imports](https://rust-lang.github.io/rustfmt/?version=v1.4.38&search=#group_imports).

### Misc

Expand Down
30 changes: 28 additions & 2 deletions Configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -2229,7 +2229,7 @@ Controls the strategy for how consecutive imports are grouped together.
Controls the strategy for grouping sets of consecutive imports. Imports may contain newlines between imports and still be grouped together as a single set, but other statements between imports will result in different grouping sets.

- **Default value**: `Preserve`
- **Possible values**: `Preserve`, `StdExternalCrate`, `One`
- **Possible values**: `Preserve`, `StdExternalCrate`, `One`, custom wildcard groups
- **Stable**: No (tracking issue: [#5083](https://github.com/rust-lang/rustfmt/issues/5083))

Each set of imports (one or more `use` statements, optionally separated by newlines) will be formatted independently. Other statements such as `mod ...` or `extern crate ...` will cause imports to not be grouped together.
Expand Down Expand Up @@ -2294,6 +2294,32 @@ use std::sync::Arc;
use uuid::Uuid;
```

#### `[ ["$std::*", "proc_macro::*"], ["*"], ["my_crate::*", "crate::*::xyz"], ["$crate::*"] ]`:

Discards existing import groups, and create groups as specified by the wildcarded list.
Handy aliases are supported:

- `$std` prefix is an alias for standard library (i.e `std`, `core`, `alloc`);
- `$crate` prefix is an alias for crate-local modules (i.e `self`, `crate`, `super`).
- `*` is a special fallback group (i.e used if no other group matches), could only be specified once.

With the provided config the following order would be set:

```rust
use proc_macro::Span;
use std::rc::Rc;

use rand;

use crate::abc::xyz;
use my_crate::a::B;
use my_crate::A;

use self::X;
use super::Y;
use crate::Z;
```

## `reorder_modules`

Reorder `mod` declarations alphabetically in group.
Expand Down Expand Up @@ -2342,7 +2368,7 @@ specific version of rustfmt is used in your CI, use this option.

The width threshold for an array element to be considered "short".

The layout of an array is dependent on the length of each of its elements.
The layout of an array is dependent on the length of each of its elements.
If the length of every element in an array is below this threshold (all elements are "short") then the array can be formatted in the mixed/compressed style, but if any one element has a length that exceeds this threshold then the array elements will have to be formatted vertically.

- **Default value**: `10`
Expand Down
46 changes: 46 additions & 0 deletions config_proc_macro/src/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use syn::{
parenthesized,
parse::{Error as ParserError, Parse, ParseStream},
Ident, Token,
};

#[derive(Debug, Default)]
pub struct Args {
pub skip_derive: Option<Vec<String>>,
}

impl Args {
pub fn skip_derives(&self) -> impl Iterator<Item = &str> + '_ {
self.skip_derive
.as_ref()
.into_iter()
.flatten()
.map(|s| s.as_ref())
}
}

impl Parse for Args {
fn parse(input: ParseStream<'_>) -> Result<Self, ParserError> {
let mut args = Self::default();

while !input.is_empty() {
let ident: Ident = input.parse()?;
match ident.to_string().as_str() {
"skip_derive" => {
let content;
parenthesized!(content in input);
args.skip_derive = Some(
content
.parse_terminated(Ident::parse, Token![,])?
.into_iter()
.map(|i| i.to_string())
.collect(),
);
}
a => panic!("unknown attribute: {}", a),
}
}

Ok(args)
}
}
5 changes: 0 additions & 5 deletions config_proc_macro/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,6 @@ pub fn config_value(attr: &syn::Attribute) -> Option<String> {
get_name_value_str_lit(attr, "value")
}

/// Returns `true` if the given attribute is a `value` attribute.
pub fn is_config_value(attr: &syn::Attribute) -> bool {
is_attr_name_value(attr, "value")
}

/// Returns `true` if the given attribute is an `unstable` attribute.
pub fn is_unstable_variant(attr: &syn::Attribute) -> bool {
is_attr_path(attr, "unstable_variant")
Expand Down
7 changes: 4 additions & 3 deletions config_proc_macro/src/config_type.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use proc_macro2::TokenStream;

use crate::args::Args;
use crate::item_enum::define_config_type_on_enum;
use crate::item_struct::define_config_type_on_struct;

/// Defines `config_type` on enum or struct.
// FIXME: Implement this on struct.
pub fn define_config_type(input: &syn::Item) -> TokenStream {
pub fn define_config_type(args: &Args, input: &syn::Item) -> TokenStream {
match input {
syn::Item::Struct(st) => define_config_type_on_struct(st),
syn::Item::Enum(en) => define_config_type_on_enum(en),
syn::Item::Struct(st) => define_config_type_on_struct(args, st),
syn::Item::Enum(en) => define_config_type_on_enum(args, en),
_ => panic!("Expected enum or struct"),
}
.unwrap()
Expand Down
174 changes: 80 additions & 94 deletions config_proc_macro/src/item_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;

use crate::args::Args;
use crate::attrs::*;
use crate::utils::*;

type Variants = syn::punctuated::Punctuated<syn::Variant, syn::Token![,]>;

/// Defines and implements `config_type` enum.
pub fn define_config_type_on_enum(em: &syn::ItemEnum) -> syn::Result<TokenStream> {
pub fn define_config_type_on_enum(args: &Args, em: &syn::ItemEnum) -> syn::Result<TokenStream> {
let syn::ItemEnum {
vis,
enum_token,
Expand All @@ -21,23 +22,56 @@ pub fn define_config_type_on_enum(em: &syn::ItemEnum) -> syn::Result<TokenStream
let mod_name_str = format!("__define_config_type_on_enum_{}", ident);
let mod_name = syn::Ident::new(&mod_name_str, ident.span());
let variants = fold_quote(variants.iter().map(process_variant), |meta| quote!(#meta,));
let mut has_serde = false;
let derives = [
"std::fmt::Debug",
"std::clone::Clone",
"std::marker::Copy",
"std::cmp::Eq",
"std::cmp::PartialEq",
"serde::Serialize",
"serde::Deserialize",
]
.iter()
.filter(|d| args.skip_derives().all(|s| !d.ends_with(s)))
.inspect(|d| has_serde |= d.contains("serde::"))
.map(|d| syn::parse_str(d).unwrap())
.collect::<Vec<syn::Path>>();
let derives = derives
.is_empty()
.then(|| quote!())
.unwrap_or_else(|| quote! { #[derive( #( #derives ),* )] });
let serde_attr = has_serde
.then(|| quote!(#[serde(rename_all = "PascalCase")]))
.unwrap_or_default();

let impl_doc_hint = impl_doc_hint(&em.ident, &em.variants);
let impl_from_str = impl_from_str(&em.ident, &em.variants);
let impl_display = impl_display(&em.ident, &em.variants);
let impl_serde = impl_serde(&em.ident, &em.variants);
let impl_deserialize = impl_deserialize(&em.ident, &em.variants);
let impl_from_str = if args
.skip_derives()
.all(|s| !"std::str::FromStr".ends_with(s))
{
impl_from_str(&em.ident, &em.variants)
} else {
Default::default()
};
let impl_display = if args
.skip_derives()
.all(|s| !"std::str::Display".ends_with(s))
{
impl_display(&em.ident, &em.variants)
} else {
Default::default()
};

Ok(quote! {
#[allow(non_snake_case)]
mod #mod_name {
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#derives
#serde_attr
pub #enum_token #ident #generics { #variants }
#impl_display
#impl_doc_hint
#impl_from_str
#impl_serde
#impl_deserialize
}
#vis use #mod_name::#ident;
})
Expand All @@ -48,8 +82,14 @@ fn process_variant(variant: &syn::Variant) -> TokenStream {
let metas = variant
.attrs
.iter()
.filter(|attr| !is_doc_hint(attr) && !is_config_value(attr) && !is_unstable_variant(attr));
let attrs = fold_quote(metas, |meta| quote!(#meta));
.filter(|attr| !is_doc_hint(attr) && !is_unstable_variant(attr));
let attrs = fold_quote(metas, |meta| {
if let Some(rename) = config_value(meta) {
quote!(#[serde(rename = #rename)])
} else {
quote!(#meta)
}
});
let syn::Variant { ident, fields, .. } = variant;
quote!(#attrs #ident #fields)
}
Expand Down Expand Up @@ -97,13 +137,36 @@ fn impl_doc_hint(ident: &syn::Ident, variants: &Variants) -> TokenStream {
}

fn impl_display(ident: &syn::Ident, variants: &Variants) -> TokenStream {
let vs = variants
.iter()
.filter(|v| is_unit(v))
.map(|v| (config_value_of_variant(v), &v.ident));
let match_patterns = fold_quote(vs, |(s, v)| {
quote! {
#ident::#v => write!(f, "{}", #s),
let match_patterns = fold_quote(variants, |v| {
let variant = &v.ident;
match &v.fields {
syn::Fields::Unit => {
let s = config_value_of_variant(v);
quote! {
#ident::#variant => write!(f, "{}", #s),
}
}
syn::Fields::Named(x) if x.named.len() == 1 => {
let x = &x.named[0];
quote! {
#ident::#variant { #x } => write!(f, "{}", #x),
}
}
syn::Fields::Named(x) => unimplemented!(
"cannot implement display for variant with {} named fileds",
x.named.len()
),
syn::Fields::Unnamed(x) if x.unnamed.len() == 1 => {
quote! {
#ident::#variant(x) => write!(f, "{}", x),
}
}
syn::Fields::Unnamed(x) => {
unimplemented!(
"cannot implement display for variant with {} named fileds",
x.unnamed.len()
)
}
}
});
quote! {
Expand Down Expand Up @@ -163,80 +226,3 @@ fn config_value_of_variant(variant: &syn::Variant) -> String {
fn unstable_of_variant(variant: &syn::Variant) -> bool {
any_unstable_variant(&variant.attrs)
}

fn impl_serde(ident: &syn::Ident, variants: &Variants) -> TokenStream {
let arms = fold_quote(variants.iter(), |v| {
let v_ident = &v.ident;
let pattern = match v.fields {
syn::Fields::Named(..) => quote!(#ident::v_ident{..}),
syn::Fields::Unnamed(..) => quote!(#ident::#v_ident(..)),
syn::Fields::Unit => quote!(#ident::#v_ident),
};
let option_value = config_value_of_variant(v);
quote! {
#pattern => serializer.serialize_str(&#option_value),
}
});

quote! {
impl ::serde::ser::Serialize for #ident {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ::serde::ser::Serializer,
{
use serde::ser::Error;
match self {
#arms
_ => Err(S::Error::custom(format!("Cannot serialize {:?}", self))),
}
}
}
}
}

// Currently only unit variants are supported.
fn impl_deserialize(ident: &syn::Ident, variants: &Variants) -> TokenStream {
let supported_vs = variants.iter().filter(|v| is_unit(v));
let if_patterns = fold_quote(supported_vs, |v| {
let config_value = config_value_of_variant(v);
let variant_ident = &v.ident;
quote! {
if #config_value.eq_ignore_ascii_case(s) {
return Ok(#ident::#variant_ident);
}
}
});

let supported_vs = variants.iter().filter(|v| is_unit(v));
let allowed = fold_quote(supported_vs.map(config_value_of_variant), |s| quote!(#s,));

quote! {
impl<'de> serde::de::Deserialize<'de> for #ident {
fn deserialize<D>(d: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Error, Visitor};
use std::marker::PhantomData;
use std::fmt;
struct StringOnly<T>(PhantomData<T>);
impl<'de, T> Visitor<'de> for StringOnly<T>
where T: serde::Deserializer<'de> {
type Value = String;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("string")
}
fn visit_str<E>(self, value: &str) -> Result<String, E> {
Ok(String::from(value))
}
}
let s = &d.deserialize_string(StringOnly::<D>(PhantomData))?;

#if_patterns

static ALLOWED: &'static[&str] = &[#allowed];
Err(D::Error::unknown_variant(&s, ALLOWED))
}
}
}
}
7 changes: 6 additions & 1 deletion config_proc_macro/src/item_struct.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use proc_macro2::TokenStream;

pub fn define_config_type_on_struct(_st: &syn::ItemStruct) -> syn::Result<TokenStream> {
use crate::args::Args;

pub fn define_config_type_on_struct(
_args: &Args,
_st: &syn::ItemStruct,
) -> syn::Result<TokenStream> {
unimplemented!()
}
6 changes: 4 additions & 2 deletions config_proc_macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#![recursion_limit = "256"]

mod args;
mod attrs;
mod config_type;
mod item_enum;
Expand All @@ -14,9 +15,10 @@ use proc_macro::TokenStream;
use syn::parse_macro_input;

#[proc_macro_attribute]
pub fn config_type(_args: TokenStream, input: TokenStream) -> TokenStream {
pub fn config_type(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as args::Args);
let input = parse_macro_input!(input as syn::Item);
let output = config_type::define_config_type(&input);
let output = config_type::define_config_type(&args, &input);

#[cfg(feature = "debug-with-rustfmt")]
{
Expand Down
Loading