add "one" import granularity

This commit is contained in:
davidsemakula 2024-01-14 20:06:16 +03:00
parent 9d9b34354d
commit 57d4b5bb0f
4 changed files with 243 additions and 60 deletions

View File

@ -9,7 +9,7 @@ use syntax::{
algo, algo,
ast::{ ast::{
self, edit_in_place::Removable, make, AstNode, HasAttrs, HasModuleItem, HasVisibility, self, edit_in_place::Removable, make, AstNode, HasAttrs, HasModuleItem, HasVisibility,
PathSegmentKind, UseTree, PathSegmentKind,
}, },
ted, Direction, NodeOrToken, SyntaxKind, SyntaxNode, ted, Direction, NodeOrToken, SyntaxKind, SyntaxNode,
}; };
@ -26,7 +26,8 @@ pub use hir::PrefixKind;
/// How imports should be grouped into use statements. /// How imports should be grouped into use statements.
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ImportGranularity { pub enum ImportGranularity {
/// Do not change the granularity of any imports and preserve the original structure written by the developer. /// Do not change the granularity of any imports and preserve the original structure written
/// by the developer.
Preserve, Preserve,
/// Merge imports from the same crate into a single use statement. /// Merge imports from the same crate into a single use statement.
Crate, Crate,
@ -34,6 +35,9 @@ pub enum ImportGranularity {
Module, Module,
/// Flatten imports so that each has its own use statement. /// Flatten imports so that each has its own use statement.
Item, Item,
/// Merge all imports into a single use statement as long as they have the same visibility
/// and attributes.
One,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -167,7 +171,7 @@ pub fn insert_use_as_alias(scope: &ImportScope, path: ast::Path, cfg: &InsertUse
.tree() .tree()
.syntax() .syntax()
.descendants() .descendants()
.find_map(UseTree::cast) .find_map(ast::UseTree::cast)
.expect("Failed to make ast node `Rename`"); .expect("Failed to make ast node `Rename`");
let alias = node.rename(); let alias = node.rename();
@ -184,6 +188,7 @@ fn insert_use_with_alias_option(
let mut mb = match cfg.granularity { let mut mb = match cfg.granularity {
ImportGranularity::Crate => Some(MergeBehavior::Crate), ImportGranularity::Crate => Some(MergeBehavior::Crate),
ImportGranularity::Module => Some(MergeBehavior::Module), ImportGranularity::Module => Some(MergeBehavior::Module),
ImportGranularity::One => Some(MergeBehavior::One),
ImportGranularity::Item | ImportGranularity::Preserve => None, ImportGranularity::Item | ImportGranularity::Preserve => None,
}; };
if !cfg.enforce_granularity { if !cfg.enforce_granularity {
@ -195,11 +200,16 @@ fn insert_use_with_alias_option(
ImportGranularityGuess::ModuleOrItem => mb.and(Some(MergeBehavior::Module)), ImportGranularityGuess::ModuleOrItem => mb.and(Some(MergeBehavior::Module)),
ImportGranularityGuess::Crate => Some(MergeBehavior::Crate), ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
ImportGranularityGuess::CrateOrModule => mb.or(Some(MergeBehavior::Crate)), ImportGranularityGuess::CrateOrModule => mb.or(Some(MergeBehavior::Crate)),
ImportGranularityGuess::One => Some(MergeBehavior::One),
}; };
} }
let use_item = let mut use_tree = make::use_tree(path.clone(), None, alias, false);
make::use_(None, make::use_tree(path.clone(), None, alias, false)).clone_for_update(); if mb == Some(MergeBehavior::One) && use_tree.path().is_some() {
use_tree = use_tree.clone_for_update();
use_tree.wrap_in_tree_list();
}
let use_item = make::use_(None, use_tree).clone_for_update();
// merge into existing imports if possible // merge into existing imports if possible
if let Some(mb) = mb { if let Some(mb) = mb {
@ -216,7 +226,7 @@ fn insert_use_with_alias_option(
// either we weren't allowed to merge or there is no import that fits the merge conditions // either we weren't allowed to merge or there is no import that fits the merge conditions
// so look for the place we have to insert to // so look for the place we have to insert to
insert_use_(scope, &path, cfg.group, use_item); insert_use_(scope, use_item, cfg.group);
} }
pub fn ast_to_remove_for_path_in_use_stmt(path: &ast::Path) -> Option<Box<dyn Removable>> { pub fn ast_to_remove_for_path_in_use_stmt(path: &ast::Path) -> Option<Box<dyn Removable>> {
@ -248,15 +258,18 @@ enum ImportGroup {
ThisCrate, ThisCrate,
ThisModule, ThisModule,
SuperModule, SuperModule,
One,
} }
impl ImportGroup { impl ImportGroup {
fn new(path: &ast::Path) -> ImportGroup { fn new(use_tree: &ast::UseTree) -> ImportGroup {
let default = ImportGroup::ExternCrate; if use_tree.path().is_none() && use_tree.use_tree_list().is_some() {
return ImportGroup::One;
}
let first_segment = match path.first_segment() { let Some(first_segment) = use_tree.path().as_ref().and_then(ast::Path::first_segment)
Some(it) => it, else {
None => return default, return ImportGroup::ExternCrate;
}; };
let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw); let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
@ -284,6 +297,7 @@ enum ImportGranularityGuess {
ModuleOrItem, ModuleOrItem,
Crate, Crate,
CrateOrModule, CrateOrModule,
One,
} }
fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess { fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
@ -303,12 +317,24 @@ fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
} }
.filter_map(use_stmt); .filter_map(use_stmt);
let mut res = ImportGranularityGuess::Unknown; let mut res = ImportGranularityGuess::Unknown;
let (mut prev, mut prev_vis, mut prev_attrs) = match use_stmts.next() { let Some((mut prev, mut prev_vis, mut prev_attrs)) = use_stmts.next() else { return res };
Some(it) => it,
None => return res, let is_tree_one_style =
}; |use_tree: &ast::UseTree| use_tree.path().is_none() && use_tree.use_tree_list().is_some();
let mut seen_one_style_groups = Vec::new();
loop { loop {
if let Some(use_tree_list) = prev.use_tree_list() { if is_tree_one_style(&prev) {
if res != ImportGranularityGuess::One {
if res != ImportGranularityGuess::Unknown {
// This scope has a mix of one-style and other style imports.
break ImportGranularityGuess::Unknown;
}
res = ImportGranularityGuess::One;
seen_one_style_groups.push((prev_vis.clone(), prev_attrs.clone()));
}
} else if let Some(use_tree_list) = prev.use_tree_list() {
if use_tree_list.use_trees().any(|tree| tree.use_tree_list().is_some()) { if use_tree_list.use_trees().any(|tree| tree.use_tree_list().is_some()) {
// Nested tree lists can only occur in crate style, or with no proper style being enforced in the file. // Nested tree lists can only occur in crate style, or with no proper style being enforced in the file.
break ImportGranularityGuess::Crate; break ImportGranularityGuess::Crate;
@ -318,11 +344,22 @@ fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
} }
} }
let (curr, curr_vis, curr_attrs) = match use_stmts.next() { let Some((curr, curr_vis, curr_attrs)) = use_stmts.next() else { break res };
Some(it) => it, if is_tree_one_style(&curr) {
None => break res, if res != ImportGranularityGuess::One
}; || seen_one_style_groups.iter().any(|(prev_vis, prev_attrs)| {
if eq_visibility(prev_vis, curr_vis.clone()) && eq_attrs(prev_attrs, curr_attrs.clone()) { eq_visibility(prev_vis.clone(), curr_vis.clone())
&& eq_attrs(prev_attrs.clone(), curr_attrs.clone())
})
{
// This scope has either a mix of one-style and other style imports or
// multiple one-style imports with the same visibility and attributes.
break ImportGranularityGuess::Unknown;
}
seen_one_style_groups.push((curr_vis.clone(), curr_attrs.clone()));
} else if eq_visibility(prev_vis, curr_vis.clone())
&& eq_attrs(prev_attrs, curr_attrs.clone())
{
if let Some((prev_path, curr_path)) = prev.path().zip(curr.path()) { if let Some((prev_path, curr_path)) = prev.path().zip(curr.path()) {
if let Some((prev_prefix, _)) = common_prefix(&prev_path, &curr_path) { if let Some((prev_prefix, _)) = common_prefix(&prev_path, &curr_path) {
if prev.use_tree_list().is_none() && curr.use_tree_list().is_none() { if prev.use_tree_list().is_none() && curr.use_tree_list().is_none() {
@ -350,39 +387,33 @@ fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
} }
} }
fn insert_use_( fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) {
scope: &ImportScope,
insert_path: &ast::Path,
group_imports: bool,
use_item: ast::Use,
) {
let scope_syntax = scope.as_syntax_node(); let scope_syntax = scope.as_syntax_node();
let insert_use_tree = let insert_use_tree =
use_item.use_tree().expect("`use_item` should have a use tree for `insert_path`"); use_item.use_tree().expect("`use_item` should have a use tree for `insert_path`");
let group = ImportGroup::new(insert_path); let group = ImportGroup::new(&insert_use_tree);
let path_node_iter = scope_syntax let path_node_iter = scope_syntax
.children() .children()
.filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node))) .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
.flat_map(|(use_, node)| { .flat_map(|(use_, node)| {
let tree = use_.use_tree()?; let tree = use_.use_tree()?;
let path = tree.path()?; Some((tree, node))
Some((path, tree, node))
}); });
if group_imports { if group_imports {
// Iterator that discards anything thats not in the required grouping // Iterator that discards anything that's not in the required grouping
// This implementation allows the user to rearrange their import groups as this only takes the first group that fits // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
let group_iter = path_node_iter let group_iter = path_node_iter
.clone() .clone()
.skip_while(|(path, ..)| ImportGroup::new(path) != group) .skip_while(|(use_tree, ..)| ImportGroup::new(use_tree) != group)
.take_while(|(path, ..)| ImportGroup::new(path) == group); .take_while(|(use_tree, ..)| ImportGroup::new(use_tree) == group);
// track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
let mut last = None; let mut last = None;
// find the element that would come directly after our new import // find the element that would come directly after our new import
let post_insert: Option<(_, _, SyntaxNode)> = group_iter let post_insert: Option<(_, SyntaxNode)> = group_iter
.inspect(|(.., node)| last = Some(node.clone())) .inspect(|(.., node)| last = Some(node.clone()))
.find(|(_, use_tree, _)| use_tree_cmp(&insert_use_tree, use_tree) != Ordering::Greater); .find(|(use_tree, _)| use_tree_cmp(&insert_use_tree, use_tree) != Ordering::Greater);
if let Some((.., node)) = post_insert { if let Some((.., node)) = post_insert {
cov_mark::hit!(insert_group); cov_mark::hit!(insert_group);
@ -401,7 +432,7 @@ fn insert_use_(
// find the group that comes after where we want to insert // find the group that comes after where we want to insert
let post_group = path_node_iter let post_group = path_node_iter
.inspect(|(.., node)| last = Some(node.clone())) .inspect(|(.., node)| last = Some(node.clone()))
.find(|(p, ..)| ImportGroup::new(p) > group); .find(|(use_tree, ..)| ImportGroup::new(use_tree) > group);
if let Some((.., node)) = post_group { if let Some((.., node)) = post_group {
cov_mark::hit!(insert_group_new_group); cov_mark::hit!(insert_group_new_group);
ted::insert(ted::Position::before(&node), use_item.syntax()); ted::insert(ted::Position::before(&node), use_item.syntax());
@ -419,7 +450,7 @@ fn insert_use_(
} }
} else { } else {
// There exists a group, so append to the end of it // There exists a group, so append to the end of it
if let Some((_, _, node)) = path_node_iter.last() { if let Some((_, node)) = path_node_iter.last() {
cov_mark::hit!(insert_no_grouping_last); cov_mark::hit!(insert_no_grouping_last);
ted::insert(ted::Position::after(node), use_item.syntax()); ted::insert(ted::Position::after(node), use_item.syntax());
return; return;

View File

@ -619,7 +619,9 @@ fn main() {}"#,
#[test] #[test]
fn merge_groups() { fn merge_groups() {
check_module("std::io", r"use std::fmt;", r"use std::{fmt, io};") check_module("std::io", r"use std::fmt;", r"use std::{fmt, io};");
check_one("std::io", r"use {std::fmt};", r"use {std::{fmt, io}};");
check_one("std::io", r"use std::fmt;", r"use {std::{fmt, io}};");
} }
#[test] #[test]
@ -629,12 +631,18 @@ fn merge_groups_last() {
r"use std::fmt::{Result, Display};", r"use std::fmt::{Result, Display};",
r"use std::fmt::{Result, Display}; r"use std::fmt::{Result, Display};
use std::io;", use std::io;",
) );
check_one(
"std::io",
r"use {std::fmt::{Result, Display}};",
r"use {std::{fmt::{Result, Display}, io}};",
);
} }
#[test] #[test]
fn merge_last_into_self() { fn merge_last_into_self() {
check_module("foo::bar::baz", r"use foo::bar;", r"use foo::bar::{self, baz};"); check_module("foo::bar::baz", r"use foo::bar;", r"use foo::bar::{self, baz};");
check_one("foo::bar::baz", r"use {foo::bar};", r"use {foo::bar::{self, baz}};");
} }
#[test] #[test]
@ -643,7 +651,12 @@ fn merge_groups_full() {
"std::io", "std::io",
r"use std::fmt::{Result, Display};", r"use std::fmt::{Result, Display};",
r"use std::{fmt::{Result, Display}, io};", r"use std::{fmt::{Result, Display}, io};",
) );
check_one(
"std::io",
r"use {std::fmt::{Result, Display}};",
r"use {std::{fmt::{Result, Display}, io}};",
);
} }
#[test] #[test]
@ -658,6 +671,11 @@ fn merge_groups_long_full() {
r"use std::foo::bar::Qux;", r"use std::foo::bar::Qux;",
r"use std::foo::bar::{r#Baz, Qux};", r"use std::foo::bar::{r#Baz, Qux};",
); );
check_one(
"std::foo::bar::Baz",
r"use {std::foo::bar::Qux};",
r"use {std::foo::bar::{Baz, Qux}};",
);
} }
#[test] #[test]
@ -681,6 +699,11 @@ fn merge_groups_long_full_list() {
r"use std::foo::bar::{Qux, Quux};", r"use std::foo::bar::{Qux, Quux};",
r"use std::foo::bar::{r#Baz, Quux, Qux};", r"use std::foo::bar::{r#Baz, Quux, Qux};",
); );
check_one(
"std::foo::bar::Baz",
r"use {std::foo::bar::{Qux, Quux}};",
r"use {std::foo::bar::{Baz, Quux, Qux}};",
);
} }
#[test] #[test]
@ -704,6 +727,11 @@ fn merge_groups_long_full_nested() {
r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
r"use std::foo::bar::{quux::{Fez, Fizz}, r#Baz, Qux};", r"use std::foo::bar::{quux::{Fez, Fizz}, r#Baz, Qux};",
); );
check_one(
"std::foo::bar::Baz",
r"use {std::foo::bar::{Qux, quux::{Fez, Fizz}}};",
r"use {std::foo::bar::{quux::{Fez, Fizz}, Baz, Qux}};",
);
} }
#[test] #[test]
@ -722,7 +750,12 @@ fn merge_groups_full_nested_deep() {
"std::foo::bar::quux::Baz", "std::foo::bar::quux::Baz",
r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};", r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};",
) );
check_one(
"std::foo::bar::quux::Baz",
r"use {std::foo::bar::{Qux, quux::{Fez, Fizz}}};",
r"use {std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}}};",
);
} }
#[test] #[test]
@ -741,6 +774,11 @@ fn merge_groups_last_nested_long() {
r"use std::{foo::bar::Qux};", r"use std::{foo::bar::Qux};",
r"use std::{foo::bar::{Baz, Qux}};", r"use std::{foo::bar::{Baz, Qux}};",
); );
check_one(
"std::foo::bar::Baz",
r"use {std::{foo::bar::Qux}};",
r"use {std::{foo::bar::{Baz, Qux}}};",
);
} }
#[test] #[test]
@ -750,7 +788,13 @@ fn merge_groups_skip_pub() {
r"pub use std::fmt::{Result, Display};", r"pub use std::fmt::{Result, Display};",
r"pub use std::fmt::{Result, Display}; r"pub use std::fmt::{Result, Display};
use std::io;", use std::io;",
) );
check_one(
"std::io",
r"pub use {std::fmt::{Result, Display}};",
r"pub use {std::fmt::{Result, Display}};
use {std::io};",
);
} }
#[test] #[test]
@ -760,7 +804,13 @@ fn merge_groups_skip_pub_crate() {
r"pub(crate) use std::fmt::{Result, Display};", r"pub(crate) use std::fmt::{Result, Display};",
r"pub(crate) use std::fmt::{Result, Display}; r"pub(crate) use std::fmt::{Result, Display};
use std::io;", use std::io;",
) );
check_one(
"std::io",
r"pub(crate) use {std::fmt::{Result, Display}};",
r"pub(crate) use {std::fmt::{Result, Display}};
use {std::io};",
);
} }
#[test] #[test]
@ -774,7 +824,17 @@ fn merge_groups_skip_attributed() {
#[cfg(feature = "gated")] use std::fmt::{Result, Display}; #[cfg(feature = "gated")] use std::fmt::{Result, Display};
use std::io; use std::io;
"#, "#,
) );
check_one(
"std::io",
r#"
#[cfg(feature = "gated")] use {std::fmt::{Result, Display}};
"#,
r#"
#[cfg(feature = "gated")] use {std::fmt::{Result, Display}};
use {std::io};
"#,
);
} }
#[test] #[test]
@ -936,6 +996,7 @@ fn guess_single() {
check_guess(r"use foo::{baz::{qux, quux}, bar};", ImportGranularityGuess::Crate); check_guess(r"use foo::{baz::{qux, quux}, bar};", ImportGranularityGuess::Crate);
check_guess(r"use foo::bar;", ImportGranularityGuess::Unknown); check_guess(r"use foo::bar;", ImportGranularityGuess::Unknown);
check_guess(r"use foo::bar::{baz, qux};", ImportGranularityGuess::CrateOrModule); check_guess(r"use foo::bar::{baz, qux};", ImportGranularityGuess::CrateOrModule);
check_guess(r"use {foo::bar};", ImportGranularityGuess::One);
} }
#[test] #[test]
@ -1027,6 +1088,19 @@ use foo::{baz::{qux, quux}, bar};
); );
} }
#[test]
fn guess_one() {
check_guess(
r"
use {
frob::bar::baz,
foo::{baz::{qux, quux}, bar}
};
",
ImportGranularityGuess::One,
);
}
#[test] #[test]
fn guess_skips_differing_vis() { fn guess_skips_differing_vis() {
check_guess( check_guess(
@ -1038,6 +1112,28 @@ pub use foo::bar::qux;
); );
} }
#[test]
fn guess_one_differing_vis() {
check_guess(
r"
use {foo::bar::baz};
pub use {foo::bar::qux};
",
ImportGranularityGuess::One,
);
}
#[test]
fn guess_skips_multiple_one_style_same_vis() {
check_guess(
r"
use {foo::bar::baz};
use {foo::bar::qux};
",
ImportGranularityGuess::Unknown,
);
}
#[test] #[test]
fn guess_skips_differing_attrs() { fn guess_skips_differing_attrs() {
check_guess( check_guess(
@ -1050,6 +1146,31 @@ pub use foo::bar::qux;
); );
} }
#[test]
fn guess_one_differing_attrs() {
check_guess(
r"
pub use {foo::bar::baz};
#[doc(hidden)]
pub use {foo::bar::qux};
",
ImportGranularityGuess::One,
);
}
#[test]
fn guess_skips_multiple_one_style_same_attrs() {
check_guess(
r"
#[doc(hidden)]
use {foo::bar::baz};
#[doc(hidden)]
use {foo::bar::qux};
",
ImportGranularityGuess::Unknown,
);
}
#[test] #[test]
fn guess_grouping_matters() { fn guess_grouping_matters() {
check_guess( check_guess(
@ -1167,6 +1288,10 @@ fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Item) check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Item)
} }
fn check_one(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::One)
}
fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) { fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) {
let use0 = ast::SourceFile::parse(ra_fixture0) let use0 = ast::SourceFile::parse(ra_fixture0)
.tree() .tree()

View File

@ -21,12 +21,15 @@ pub enum MergeBehavior {
Crate, Crate,
/// Merge imports from the same module into a single use statement. /// Merge imports from the same module into a single use statement.
Module, Module,
/// Merge all imports into a single use statement as long as they have the same visibility
/// and attributes.
One,
} }
impl MergeBehavior { impl MergeBehavior {
fn is_tree_allowed(&self, tree: &ast::UseTree) -> bool { fn is_tree_allowed(&self, tree: &ast::UseTree) -> bool {
match self { match self {
MergeBehavior::Crate => true, MergeBehavior::Crate | MergeBehavior::One => true,
// only simple single segment paths are allowed // only simple single segment paths are allowed
MergeBehavior::Module => { MergeBehavior::Module => {
tree.use_tree_list().is_none() && tree.path().map(path_len) <= Some(1) tree.use_tree_list().is_none() && tree.path().map(path_len) <= Some(1)
@ -72,6 +75,10 @@ pub fn try_merge_trees(
} }
fn try_merge_trees_mut(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior) -> Option<()> { fn try_merge_trees_mut(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior) -> Option<()> {
if merge == MergeBehavior::One {
lhs.wrap_in_tree_list();
rhs.wrap_in_tree_list();
} else {
let lhs_path = lhs.path()?; let lhs_path = lhs.path()?;
let rhs_path = rhs.path()?; let rhs_path = rhs.path()?;
@ -88,6 +95,7 @@ fn try_merge_trees_mut(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehav
// we can safely return here, in this case `recursive_merge` doesn't do anything // we can safely return here, in this case `recursive_merge` doesn't do anything
return Some(()); return Some(());
} }
}
recursive_merge(lhs, rhs, merge) recursive_merge(lhs, rhs, merge)
} }

View File

@ -1,6 +1,6 @@
//! Structural editing for ast. //! Structural editing for ast.
use std::iter::{empty, successors}; use std::iter::{empty, once, successors};
use parser::{SyntaxKind, T}; use parser::{SyntaxKind, T};
@ -530,6 +530,25 @@ impl ast::UseTree {
Some(()) Some(())
} }
} }
/// Wraps the use tree in use tree list with no top level path (if it isn't already).
///
/// # Examples
///
/// `foo::bar` -> `{foo::bar}`
///
/// `{foo::bar}` -> `{foo::bar}`
pub fn wrap_in_tree_list(&self) {
if self.path().is_none() {
return;
}
let subtree = self.clone_subtree().clone_for_update();
ted::remove_all_iter(self.syntax().children_with_tokens());
ted::append_child(
self.syntax(),
make::use_tree_list(once(subtree)).clone_for_update().syntax(),
);
}
} }
impl ast::UseTreeList { impl ast::UseTreeList {