Merge imports with the same prefix into a single nested import

This commit is contained in:
Seiichi Uchida 2018-04-06 22:35:04 +09:00 committed by Seiichi Uchida
parent 5dd203eabe
commit 1954513ace
3 changed files with 306 additions and 18 deletions

View file

@ -12,7 +12,7 @@ use std::cmp::Ordering;
use config::lists::*; use config::lists::*;
use syntax::ast::{self, UseTreeKind}; use syntax::ast::{self, UseTreeKind};
use syntax::codemap::{BytePos, Span}; use syntax::codemap::{self, BytePos, Span, DUMMY_SP};
use codemap::SpanUtils; use codemap::SpanUtils;
use config::IndentStyle; use config::IndentStyle;
@ -24,6 +24,7 @@ use utils::mk_sp;
use visitor::FmtVisitor; use visitor::FmtVisitor;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt;
/// Returns a name imported by a `use` declaration. e.g. returns `Ordering` /// Returns a name imported by a `use` declaration. e.g. returns `Ordering`
/// for `std::cmp::Ordering` and `self` for `std::cmp::self`. /// for `std::cmp::Ordering` and `self` for `std::cmp::self`.
@ -89,7 +90,7 @@ impl<'a> FmtVisitor<'a> {
// sorting. // sorting.
// FIXME we do a lot of allocation to make our own representation. // FIXME we do a lot of allocation to make our own representation.
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub enum UseSegment { pub enum UseSegment {
Ident(String, Option<String>), Ident(String, Option<String>),
Slf(Option<String>), Slf(Option<String>),
@ -98,12 +99,12 @@ pub enum UseSegment {
List(Vec<UseTree>), List(Vec<UseTree>),
} }
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct UseTree { pub struct UseTree {
pub path: Vec<UseSegment>, pub path: Vec<UseSegment>,
pub span: Span, pub span: Span,
// Comment information within nested use tree. // Comment information within nested use tree.
list_item: Option<ListItem>, pub list_item: Option<ListItem>,
// Additional fields for top level use items. // Additional fields for top level use items.
// Should we have another struct for top-level use items rather than reusing this? // Should we have another struct for top-level use items rather than reusing this?
visibility: Option<ast::Visibility>, visibility: Option<ast::Visibility>,
@ -143,12 +144,84 @@ impl UseSegment {
} }
} }
pub fn merge_use_trees(use_trees: Vec<UseTree>) -> Vec<UseTree> {
let mut result = Vec::with_capacity(use_trees.len());
for use_tree in use_trees {
if use_tree.has_comment() || use_tree.attrs.is_some() {
result.push(use_tree);
continue;
}
for flattened in use_tree.flatten() {
merge_use_trees_inner(&mut result, flattened);
}
}
result
}
fn merge_use_trees_inner(trees: &mut Vec<UseTree>, use_tree: UseTree) {
for tree in trees.iter_mut() {
if tree.share_prefix(&use_tree) {
tree.merge(use_tree);
return;
}
}
trees.push(use_tree);
}
impl fmt::Debug for UseTree {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}
impl fmt::Debug for UseSegment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}
impl fmt::Display for UseSegment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
UseSegment::Glob => write!(f, "*"),
UseSegment::Ident(ref s, _) => write!(f, "{}", s),
UseSegment::Slf(..) => write!(f, "self"),
UseSegment::Super(..) => write!(f, "super"),
UseSegment::List(ref list) => {
write!(f, "{{")?;
for (i, item) in list.iter().enumerate() {
let is_last = i == list.len() - 1;
write!(f, "{}", item)?;
if !is_last {
write!(f, ", ")?;
}
}
write!(f, "}}")
}
}
}
}
impl fmt::Display for UseTree {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (i, segment) in self.path.iter().enumerate() {
let is_last = i == self.path.len() - 1;
write!(f, "{}", segment)?;
if !is_last {
write!(f, "::")?;
}
}
write!(f, "")
}
}
impl UseTree { impl UseTree {
// Rewrite use tree with `use ` and a trailing `;`. // Rewrite use tree with `use ` and a trailing `;`.
pub fn rewrite_top_level(&self, context: &RewriteContext, shape: Shape) -> Option<String> { pub fn rewrite_top_level(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
let mut result = String::with_capacity(256); let mut result = String::with_capacity(256);
if let Some(ref attrs) = self.attrs { if let Some(ref attrs) = self.attrs {
result.push_str(&attrs.rewrite(context, shape)?); result.push_str(&attrs.rewrite(context, shape).expect("rewrite attr"));
if !result.is_empty() { if !result.is_empty() {
result.push_str(&shape.indent.to_string_with_newline(context.config)); result.push_str(&shape.indent.to_string_with_newline(context.config));
} }
@ -168,6 +241,17 @@ impl UseTree {
Some(result) Some(result)
} }
// FIXME: Use correct span?
fn from_path(path: Vec<UseSegment>, span: Span) -> UseTree {
UseTree {
path,
span,
list_item: None,
visibility: None,
attrs: None,
}
}
pub fn from_ast_with_normalization( pub fn from_ast_with_normalization(
context: &RewriteContext, context: &RewriteContext,
item: &ast::Item, item: &ast::Item,
@ -360,6 +444,131 @@ impl UseTree {
self.path.push(last); self.path.push(last);
self self
} }
fn has_comment(&self) -> bool {
self.list_item.as_ref().map_or(false, |list_item| {
list_item.pre_comment.is_some() || list_item.post_comment.is_some()
})
}
fn same_visibility(&self, other: &UseTree) -> bool {
match (&self.visibility, &other.visibility) {
(
Some(codemap::Spanned {
node: ast::VisibilityKind::Inherited,
..
}),
None,
)
| (
None,
Some(codemap::Spanned {
node: ast::VisibilityKind::Inherited,
..
}),
)
| (None, None) => true,
(
Some(codemap::Spanned { node: lnode, .. }),
Some(codemap::Spanned { node: rnode, .. }),
) => lnode == rnode,
_ => false,
}
}
fn share_prefix(&self, other: &UseTree) -> bool {
if self.path.is_empty() || other.path.is_empty() || self.attrs.is_some()
|| !self.same_visibility(other)
{
false
} else {
self.path[0] == other.path[0]
}
}
fn flatten(self) -> Vec<UseTree> {
if self.path.is_empty() {
return vec![self];
}
match self.path.clone().last().unwrap() {
UseSegment::List(list) => {
let prefix = &self.path[..self.path.len() - 1];
let mut result = vec![];
for nested_use_tree in list.into_iter() {
for mut flattend in nested_use_tree.clone().flatten().iter_mut() {
let mut new_path = prefix.to_vec();
new_path.append(&mut flattend.path);
result.push(UseTree {
path: new_path,
span: self.span,
list_item: None,
visibility: self.visibility.clone(),
attrs: None,
});
}
}
result
}
_ => vec![self],
}
}
fn merge(&mut self, other: UseTree) {
let mut new_path = vec![];
let mut len = 0;
for (i, (mut a, b)) in self.path
.clone()
.iter_mut()
.zip(other.path.clone().into_iter())
.enumerate()
{
if *a == b {
len = i + 1;
new_path.push(b);
continue;
} else {
len = i;
break;
}
}
if let Some(merged) = merge_rest(&self.path, &other.path, len) {
new_path.push(merged);
self.span = self.span.to(other.span);
}
self.path = new_path;
}
}
fn merge_rest(a: &[UseSegment], b: &[UseSegment], len: usize) -> Option<UseSegment> {
let a_rest = &a[len..];
let b_rest = &b[len..];
if a_rest.is_empty() && b_rest.is_empty() {
return None;
}
if a_rest.is_empty() {
return Some(UseSegment::List(vec![
UseTree::from_path(vec![UseSegment::Slf(None)], DUMMY_SP),
UseTree::from_path(b_rest.to_vec(), DUMMY_SP),
]));
}
if b_rest.is_empty() {
return Some(UseSegment::List(vec![
UseTree::from_path(vec![UseSegment::Slf(None)], DUMMY_SP),
UseTree::from_path(a_rest.to_vec(), DUMMY_SP),
]));
}
if let UseSegment::List(mut list) = a_rest[0].clone() {
merge_use_trees_inner(&mut list, UseTree::from_path(b_rest.to_vec(), DUMMY_SP));
list.sort();
return Some(UseSegment::List(list.clone()));
}
let mut list = vec![
UseTree::from_path(a_rest.to_vec(), DUMMY_SP),
UseTree::from_path(b_rest.to_vec(), DUMMY_SP),
];
list.sort();
Some(UseSegment::List(list))
} }
impl PartialOrd for UseSegment { impl PartialOrd for UseSegment {
@ -461,9 +670,12 @@ fn rewrite_nested_use_tree(
IndentStyle::Visual => shape.visual_indent(0), IndentStyle::Visual => shape.visual_indent(0),
}; };
for use_tree in use_tree_list { for use_tree in use_tree_list {
let mut list_item = use_tree.list_item.clone()?; if let Some(mut list_item) = use_tree.list_item.clone() {
list_item.item = use_tree.rewrite(context, nested_shape); list_item.item = use_tree.rewrite(context, nested_shape);
list_items.push(list_item); list_items.push(list_item);
} else {
list_items.push(ListItem::from_str(use_tree.rewrite(context, nested_shape)?));
}
} }
let (tactic, remaining_width) = if use_tree_list.iter().any(|use_segment| { let (tactic, remaining_width) = if use_tree_list.iter().any(|use_segment| {
use_segment use_segment
@ -683,6 +895,60 @@ mod test {
parser.parse_in_list() parser.parse_in_list()
} }
macro parse_use_trees($($s:expr),* $(,)*) {
vec![
$(parse_use_tree($s),)*
]
}
#[test]
fn test_use_tree_merge() {
macro test_merge([$($input:expr),* $(,)*], [$($output:expr),* $(,)*]) {
assert_eq!(
merge_use_trees(parse_use_trees!($($input,)*)),
parse_use_trees!($($output,)*),
);
}
test_merge!(["a::b::{c, d}", "a::b::{e, f}"], ["a::b::{c, d, e, f}"]);
test_merge!(["a::b::c", "a::b"], ["a::b::{self, c}"]);
test_merge!(["a::b", "a::b"], ["a::b"]);
test_merge!(["a", "a::b", "a::b::c"], ["a::{self, b::{self, c}}"]);
test_merge!(
["a::{b::{self, c}, d::e}", "a::d::f"],
["a::{b::{self, c}, d::{e, f}}"]
);
test_merge!(
["a::d::f", "a::{b::{self, c}, d::e}"],
["a::{b::{self, c}, d::{e, f}}"]
);
test_merge!(
["a::{c, d, b}", "a::{d, e, b, a, f}", "a::{f, g, c}"],
["a::{a, b, c, d, e, f, g}"]
);
}
#[test]
fn test_use_tree_flatten() {
assert_eq!(
parse_use_tree("a::b::{c, d, e, f}").flatten(),
parse_use_trees!("a::b::c", "a::b::d", "a::b::e", "a::b::f",)
);
assert_eq!(
parse_use_tree("a::b::{c::{d, e, f}, g, h::{i, j, k}}").flatten(),
parse_use_trees![
"a::b::c::d",
"a::b::c::e",
"a::b::c::f",
"a::b::g",
"a::b::h::i",
"a::b::h::j",
"a::b::h::k",
]
);
}
#[test] #[test]
fn test_use_tree_normalize() { fn test_use_tree_normalize() {
assert_eq!( assert_eq!(

View file

@ -80,6 +80,16 @@ pub struct ListItem {
} }
impl ListItem { impl ListItem {
pub fn empty() -> ListItem {
ListItem {
pre_comment: None,
pre_comment_style: ListItemCommentStyle::None,
item: None,
post_comment: None,
new_lines: false,
}
}
pub fn inner_as_ref(&self) -> &str { pub fn inner_as_ref(&self) -> &str {
self.item.as_ref().map_or("", |s| s) self.item.as_ref().map_or("", |s| s)
} }

View file

@ -22,7 +22,7 @@ use syntax::{ast, attr, codemap::Span};
use attr::filter_inline_attrs; use attr::filter_inline_attrs;
use codemap::LineRangeUtils; use codemap::LineRangeUtils;
use comment::combine_strs_with_missing_comments; use comment::combine_strs_with_missing_comments;
use imports::UseTree; use imports::{merge_use_trees, UseTree};
use items::{is_mod_decl, rewrite_extern_crate, rewrite_mod}; use items::{is_mod_decl, rewrite_extern_crate, rewrite_mod};
use lists::{itemize_list, write_list, ListFormatting, ListItem}; use lists::{itemize_list, write_list, ListFormatting, ListItem};
use rewrite::{Rewrite, RewriteContext}; use rewrite::{Rewrite, RewriteContext};
@ -117,29 +117,41 @@ fn rewrite_reorderable_items(
match reorderable_items[0].node { match reorderable_items[0].node {
// FIXME: Remove duplicated code. // FIXME: Remove duplicated code.
ast::ItemKind::Use(..) => { ast::ItemKind::Use(..) => {
let normalized_items: Vec<_> = reorderable_items let mut normalized_items: Vec<_> = reorderable_items
.iter() .iter()
.filter_map(|item| UseTree::from_ast_with_normalization(context, item)) .filter_map(|item| UseTree::from_ast_with_normalization(context, item))
.collect(); .collect();
let cloned = normalized_items.clone();
// 4 = "use ", 1 = ";" // Add comments before merging.
let nested_shape = shape.offset_left(4)?.sub_width(1)?;
let list_items = itemize_list( let list_items = itemize_list(
context.snippet_provider, context.snippet_provider,
normalized_items.iter(), cloned.iter(),
"", "",
";", ";",
|item| item.span.lo(), |item| item.span.lo(),
|item| item.span.hi(), |item| item.span.hi(),
|item| item.rewrite_top_level(context, nested_shape), |_item| Some("".to_owned()),
span.lo(), span.lo(),
span.hi(), span.hi(),
false, false,
); );
for (item, list_item) in normalized_items.iter_mut().zip(list_items) {
item.list_item = Some(list_item.clone());
}
if context.config.merge_imports() {
normalized_items = merge_use_trees(normalized_items);
}
normalized_items.sort();
let mut item_pair_vec: Vec<_> = list_items.zip(&normalized_items).collect(); // 4 = "use ", 1 = ";"
item_pair_vec.sort_by(|a, b| a.1.cmp(b.1)); let nested_shape = shape.offset_left(4)?.sub_width(1)?;
let item_vec: Vec<_> = item_pair_vec.into_iter().map(|pair| pair.0).collect(); let item_vec: Vec<_> = normalized_items
.into_iter()
.map(|use_tree| ListItem {
item: use_tree.rewrite_top_level(context, nested_shape),
..use_tree.list_item.unwrap_or_else(|| ListItem::empty())
})
.collect();
wrap_reorderable_items(context, &item_vec, nested_shape) wrap_reorderable_items(context, &item_vec, nested_shape)
} }