1use {
5 crate::{path::{SimplePath, SimplePathSegment}, token, util::Braced},
6 rustidy_ast_util::{
7 Identifier,
8 Punctuated,
9 PunctuatedTrailing,
10 delimited,
11 punct::{self, PunctuatedRest},
12 },
13 rustidy_format::{Format, FormatOutput, Formattable, WhitespaceConfig, WhitespaceFormat},
14 rustidy_parse::Parse,
15 rustidy_print::Print,
16 rustidy_util::Whitespace,
17 std::{borrow::Cow, cmp},
18};
19
20#[derive(PartialEq, Eq, Clone, Debug)]
22#[derive(serde::Serialize, serde::Deserialize)]
23#[derive(Parse, Formattable, Format, Print)]
24#[parse(name = "use declaration")]
25pub struct UseDeclaration {
26 pub use_: token::Use,
27 #[parse(fatal)]
28 #[format(prefix_ws = Whitespace::SINGLE)]
29 pub tree: UseTree,
30 #[format(prefix_ws = Whitespace::REMOVE)]
31 pub semi: token::Semi,
32}
33
34impl UseDeclaration {
35 pub fn merge(&mut self, other: Self) {
37 self.tree.merge(other.tree);
38 }
39}
40
41#[derive(PartialEq, Eq, Clone, Debug)]
43#[derive(strum::EnumIs)]
44#[derive(serde::Serialize, serde::Deserialize)]
45#[derive(Parse, Formattable, Format, Print)]
46pub enum UseTree {
47 Glob(UseTreeGlob),
48 Group(UseTreeGroup),
49 Simple(UseTreeSimple),
50}
51
52impl UseTree {
53 #[must_use]
55 pub fn into_group(self) -> UseTreeGroup {
56 match self {
57 Self::Group(tree) => tree,
58
59 _ => UseTreeGroup {
60 prefix: None,
61 tree: Braced::from_value(Some(PunctuatedTrailing::single(Box::new(self)))),
62 },
63 }
64 }
65
66 pub fn merge(&mut self, other: Self) {
68 match (self, other) {
69 (Self::Group(lhs), rhs) => lhs.push_back(rhs),
70 (lhs, Self::Group(mut rhs)) => replace_with::replace_with_or_abort(lhs, |lhs| {
71 rhs.push_front(lhs);
72 Self::Group(rhs)
73 }),
74
75 (lhs, rhs) => replace_with::replace_with_or_abort(lhs, |lhs| {
76 let mut values = PunctuatedTrailing::single(Box::new(lhs));
77 values.push_value(Box::new(rhs));
78 Self::Group(UseTreeGroup {
79 prefix: None,
80 tree: Braced::from_value(Some(values))
81 })
82 }),
83 }
84 }
85}
86
87#[derive(PartialEq, Eq, Clone, Debug)]
88#[derive(serde::Serialize, serde::Deserialize)]
89#[derive(Parse, Formattable, Format, Print)]
90pub struct UseTreeGlob {
91 pub prefix: Option<UseTreeGlobPrefix>,
92 #[format(prefix_ws(expr = Whitespace::REMOVE, if_ = self.prefix.is_some()))]
93 pub glob: token::Star,
94}
95
96#[derive(PartialEq, Eq, Clone, Debug)]
97#[derive(serde::Serialize, serde::Deserialize)]
98#[derive(Parse, Formattable, Format, Print)]
99pub struct UseTreeGlobPrefix {
100 pub path: Option<SimplePath>,
101 #[format(prefix_ws(expr = Whitespace::REMOVE, if_ = self.path.is_some()))]
102 pub sep: token::PathSep,
103}
104
105#[derive(PartialEq, Eq, Clone, Debug)]
106#[derive(serde::Serialize, serde::Deserialize)]
107#[derive(Parse, Formattable, Format, Print)]
108#[format(before_with = Self::flatten)]
109#[format(before_with = Self::sort)]
110pub struct UseTreeGroup {
111 pub prefix: Option<UseTreeGroupPrefix>,
112 #[format(prefix_ws(expr = Whitespace::REMOVE, if_ = self.prefix.is_some()))]
113 #[format(with = Self::format_tree)]
114 pub tree: Braced<Option<PunctuatedTrailing<Box<UseTree>, token::Comma>>>,
115}
116
117impl UseTreeGroup {
118 pub fn push_front(&mut self, tree: UseTree) {
120 match tree {
121 UseTree::Group(rhs) if self.prefix == rhs.prefix => match &mut self.tree.value {
122 Some(lhs) => if let Some(mut rhs) = rhs.tree.value {
123 replace_with::replace_with_or_abort(lhs, |lhs| {
124 rhs.extend_from_punctuated_trailing(lhs);
125 rhs
126 });
127 },
128 None => self.tree.value = rhs.tree.value,
129 },
130
131 UseTree::Group(rhs) => replace_with::replace_with_or_abort(self, |lhs| {
132 let mut values = PunctuatedTrailing::single(Box::new(UseTree::Group(rhs)));
133 values
134 .push_value(Box::new(UseTree::Group(lhs)));
135 Self {
136 prefix: None,
137 tree: Braced::from_value(Some(values))
138 }
139 }),
140
141 _ => match &mut self.tree.value {
142 Some(lhs) => lhs.push_front_value(Box::new(tree)),
143 None => self.tree.value = Some(PunctuatedTrailing::single(Box::new(tree))),
144 },
145 }
146 }
147
148 pub fn push_back(&mut self, tree: UseTree) {
150 match tree {
151 UseTree::Group(rhs) if self.prefix == rhs.prefix => match &mut self.tree.value {
152 Some(lhs) => if let Some(rhs) = rhs.tree.value {
153 lhs.extend_from_punctuated_trailing(rhs);
154 },
155 None => self.tree.value = rhs.tree.value,
156 },
157
158 UseTree::Group(rhs) => replace_with::replace_with_or_abort(self, |lhs| {
159 let mut values = PunctuatedTrailing::single(Box::new(UseTree::Group(lhs)));
160 values
161 .push_value(Box::new(UseTree::Group(rhs)));
162 Self {
163 prefix: None,
164 tree: Braced::from_value(Some(values))
165 }
166 }),
167
168 _ => match &mut self.tree.value {
169 Some(lhs) => lhs.push_value(Box::new(tree)),
170 None => self.tree.value = Some(PunctuatedTrailing::single(Box::new(tree))),
171 },
172 }
173 }
174
175 pub fn sort(&mut self, _ctx: &mut rustidy_format::Context) {
177 let Some(trees) = &mut self.tree.value else {
178 return
179 };
180
181 struct SimplePathSortOrder<'a>(&'a SimplePath);
183
184 impl PartialEq for SimplePathSortOrder<'_> {
185 fn eq(&self, other: &Self) -> bool {
186 self.cmp(other).is_eq()
187 }
188 }
189 impl Eq for SimplePathSortOrder<'_> {}
190 impl PartialOrd for SimplePathSortOrder<'_> {
191 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
192 Some(self.cmp(other))
193 }
194 }
195 impl Ord for SimplePathSortOrder<'_> {
196 fn cmp(&self, other: &Self) -> cmp::Ordering {
197 let prefix_cmp = self.0
198 .prefix
199 .is_some()
200 .cmp(&other.0.prefix.is_some());
201 if !prefix_cmp.is_eq() {
202 return prefix_cmp;
203 }
204
205 let mut lhs = self.0.segments.values();
206 let mut rhs = other.0.segments.values();
207 loop {
208 let (lhs, rhs) = match (lhs.next(), rhs.next()) {
209 (Some(lhs), Some(rhs)) => (lhs, rhs),
210 (Some(_), None) => return cmp::Ordering::Less,
211 (None, Some(_)) => return cmp::Ordering::Greater,
212 (None, None) => return cmp::Ordering::Equal,
213 };
214
215 #[derive(PartialEq, Eq, PartialOrd, Ord)]
216 pub enum Segment<'a> {
217 Crate,
218 Super,
219 SelfLower,
220 DollarCrate,
221 Ident(Cow<'a, str>),
222 }
223 fn segment(segment: &SimplePathSegment) -> Segment<'_> {
224 match segment {
225 SimplePathSegment::Super(_) => Segment::Super,
226 SimplePathSegment::SelfLower(_) => Segment::SelfLower,
227 SimplePathSegment::Crate(_) => Segment::Crate,
228 SimplePathSegment::DollarCrate(_) => Segment::DollarCrate,
229 SimplePathSegment::Ident(ident) => Segment::Ident(ident.as_str()),
230 }
231 }
232
233 let segment_cmp = segment(lhs).cmp(&segment(rhs));
234 if !segment_cmp.is_eq() {
235 return segment_cmp;
236 }
237 }
238 }
239 }
240
241 #[derive(PartialEq, Eq, PartialOrd, Ord)]
242 enum SortOrder<'a> {
243 GroupWithPrefixRoot,
244 Glob,
245 WithPath(SimplePathSortOrder<'a>),
246 GroupNoPrefix,
247 }
248
249 #[expect(clippy::borrowed_box, reason = "It's necessary for the closure")]
250 trees.sort_values_by_key(
251 for<'a> |tree: &'a Box<UseTree>, _: Option<&'a token::Comma>| -> SortOrder<'a> {
252 match &**tree {
253 UseTree::Glob(_) => SortOrder::Glob,
254 UseTree::Group(tree) => match &tree.prefix {
255 Some(prefix) => match &prefix.path {
256 Some(path) => SortOrder::WithPath(SimplePathSortOrder(path)),
257 None => SortOrder::GroupWithPrefixRoot,
258 },
259 None => SortOrder::GroupNoPrefix,
260 },
261 UseTree::Simple(tree) => SortOrder::WithPath(SimplePathSortOrder(&tree.path)),
262 }
263 }
264 );
265 }
266
267 pub fn flatten(&mut self, ctx: &mut rustidy_format::Context) {
269 replace_with::replace_with_or_abort(&mut self.tree.value, |trees| {
270 let mut trees = trees?;
271 let mut trees_first = Some(PunctuatedRest {
272 punct: token::Comma::new(),
273 value: trees.punctuated.first,
274 });
275 let mut sub_trees = vec![];
276 let mut new_trees: Vec<PunctuatedRest<_, token::Comma>> = vec![];
277 let mut trailing_comma = trees.trailing;
278
279 while let Some(PunctuatedRest { punct: mut comma, value: tree, }) = sub_trees
282 .pop()
283 .or_else(|| trees.punctuated.rest.pop())
284 .or_else(|| trees_first.take()) {
285 let mut latest_ws_join_prefix = |ws: Whitespace| match new_trees.last_mut() {
287 Some(PunctuatedRest { punct: last_comma, .. }) => last_comma.ws.join_prefix(ws),
288 None => match &mut trailing_comma {
289 Some(trailing_comma) => trailing_comma.ws.join_prefix(ws),
290 None => self.tree.suffix.ws.join_prefix(ws),
291 },
292 };
293
294 match *tree {
295 UseTree::Group(group) if group.prefix.is_none() => {
296 latest_ws_join_prefix(group.tree.suffix.ws);
297
298 match group.tree.value {
299 Some(trees) => {
300 comma
301 .ws
302 .join_prefix(group.tree.prefix.ws);
303 sub_trees.push(
304 PunctuatedRest { punct: comma, value: trees.punctuated.first, }
305 );
306 for rest in trees.punctuated.rest {
307 sub_trees.push(rest);
308 }
309 },
310 None => latest_ws_join_prefix(group.tree.prefix.ws),
311 }
312 },
313 _ => new_trees
314 .push(PunctuatedRest { punct: comma, value: tree, }),
315 }
316 }
317
318 new_trees.pop().map(
319 |PunctuatedRest { punct: first_comma, value: mut first }| {
320 first
321 .prefix_ws_join_prefix(ctx, first_comma.ws)
322 .expect("Use tree should have prefix whitespace");
323
324 new_trees.reverse();
325 PunctuatedTrailing {
326 punctuated: Punctuated { first, rest: new_trees },
327 trailing: trailing_comma,
328 }
329 },
330 )
331 });
332 }
333
334 fn format_tree_compact(
335 tree: &mut Braced<Option<PunctuatedTrailing<Box<UseTree>, token::Comma>>>,
336 ctx: &mut rustidy_format::Context,
337 prefix_ws: WhitespaceConfig,
338 ) -> FormatOutput {
339 if let Some(punct) = &mut tree.value {
340 punct.trailing = None;
341 }
342
343 tree.format(
344 ctx,
345 prefix_ws,
346 delimited::FmtRemoveWith(punct::FmtArgs {
347 value_prefix_ws: Whitespace::SINGLE,
348 punct_prefix_ws: Whitespace::REMOVE,
349 value_args: (),
350 punct_args: (),
351 }),
352 )
353 }
354
355 fn format_tree(
356 tree: &mut Braced<Option<PunctuatedTrailing<Box<UseTree>, token::Comma>>>,
357 ctx: &mut rustidy_format::Context,
358 prefix_ws: WhitespaceConfig,
359 _args: (),
360 ) -> FormatOutput {
361 let compact_output = Self::format_tree_compact(tree, ctx, prefix_ws);
362
363 match compact_output.len_without_prefix_ws() > ctx.config().max_use_tree_len {
367 true => {
368 if let Some(punct) = &mut tree.value && punct.trailing.is_none() {
369 punct.trailing = Some(token::Comma::new());
370 }
371
372 tree.format(
373 ctx,
374 prefix_ws,
375 delimited::fmt_indent_if_non_blank_with((), punct::FmtArgs {
376 value_prefix_ws: Whitespace::INDENT,
377 punct_prefix_ws: Whitespace::REMOVE,
378 value_args: (),
379 punct_args: (),
380 }, (),),
381 )
382 },
383 false => compact_output,
384 }
385 }
386}
387
388#[derive(PartialEq, Eq, Clone, Debug)]
389#[derive(serde::Serialize, serde::Deserialize)]
390#[derive(Parse, Formattable, Format, Print)]
391pub struct UseTreeGroupPrefix {
392 pub path: Option<SimplePath>,
393 #[format(prefix_ws(expr = Whitespace::REMOVE, if_ = self.path.is_some()))]
394 pub sep: token::PathSep,
395}
396
397#[derive(PartialEq, Eq, Clone, Debug)]
398#[derive(serde::Serialize, serde::Deserialize)]
399#[derive(Parse, Formattable, Format, Print)]
400pub struct UseTreeSimple {
401 pub path: SimplePath,
402 #[format(prefix_ws = Whitespace::SINGLE)]
403 pub as_: Option<UseTreeSimpleAs>,
404}
405
406#[derive(PartialEq, Eq, Clone, Debug)]
407#[derive(serde::Serialize, serde::Deserialize)]
408#[derive(Parse, Formattable, Format, Print)]
409pub struct UseTreeSimpleAs {
410 pub as_: token::As,
411 #[parse(fatal)]
412 #[format(prefix_ws = Whitespace::SINGLE)]
413 pub value: UseTreeSimpleAsValue,
414}
415
416#[derive(PartialEq, Eq, Clone, Debug)]
417#[derive(serde::Serialize, serde::Deserialize)]
418#[derive(Parse, Formattable, Format, Print)]
419pub enum UseTreeSimpleAsValue {
420 Ident(Identifier),
421 Underscore(token::Underscore),
422}