use_builder/
lib.rs

1//! A crate to build source code use sections by combining multiple (possibly duplicate) use section inputs
2//!
3//! ```rust
4//! use assert_unordered::assert_eq_unordered;
5//! use quote::quote;
6//! use use_builder::{UseBuilder, UseItems};
7//!
8//! // #1 - Build a two or more use trees and convert into `UseItems` (wrapped `Vec<ItemUse>`)
9//!
10//! let use1 = quote! {
11//!     use crate::Test;
12//!     use std::error::{Error as StdError};
13//!     use std::fmt::Debug;
14//! };
15//!
16//! let use2 = quote! {
17//!     use syn::ItemUse;
18//!     use std::fmt::Display;
19//!     use crate::*;
20//! };
21//!
22//! let items1: UseItems = syn::parse2(use1).unwrap();
23//! let items2: UseItems = syn::parse2(use2).unwrap();
24//!
25//! // #2 - Parse, process, and extract into sections
26//!
27//! let builder = UseBuilder::from_uses(vec![items1, items2]);
28//! let (std_use, ext_use, crate_use) = builder.into_items_sections().unwrap();
29//!
30//! // #3 - Validate our response matches expectation
31//!
32//! let std_expected = quote! {
33//!     use std::error::Error as StdError;
34//!     use std::fmt::{Debug, Display};
35//! };
36//! let std_expected = syn::parse2::<UseItems>(std_expected).unwrap().into_inner();
37//!
38//! let ext_expected = quote! {
39//!     use syn::ItemUse;
40//! };
41//! let ext_expected = syn::parse2::<UseItems>(ext_expected).unwrap().into_inner();
42//!
43//! let crate_expected = quote! {
44//!     use crate::*;
45//! };
46//! let crate_expected = syn::parse2::<UseItems>(crate_expected).unwrap().into_inner();
47//!
48//! assert_eq_unordered!(std_expected, std_use);
49//! assert_eq_unordered!(ext_expected, ext_use);
50//! assert_eq_unordered!(crate_expected, crate_use);
51//! ```
52
53#![warn(missing_docs)]
54
55// Trick to test README samples (from: https://github.com/rust-lang/cargo/issues/383#issuecomment-720873790)
56#[cfg(doctest)]
57mod test_readme {
58    macro_rules! external_doc_test {
59        ($x:expr) => {
60            #[doc = $x]
61            extern "C" {}
62        };
63    }
64
65    external_doc_test!(include_str!("../README.md"));
66}
67
68use quote::__private::TokenStream;
69use quote::{ToTokens, TokenStreamExt};
70
71use indexmap::IndexMap;
72use std::collections::HashSet;
73use std::error::Error as StdError;
74use std::{cmp, fmt, hash};
75
76const STD: [&str; 5] = ["std", "alloc", "core", "proc_macro", "test"];
77const CRATE: [&str; 3] = ["self", "super", "crate"];
78
79// *** UseItems ***
80
81/// An opaque type primarily used for parsing to get an inner `Vec<syn::ItemUse>` (however,
82/// [from_items](UseItems::from_items) can also be used for an existing [Vec] of items if parsing is
83/// not required). This type is the sole input into [UseBuilder].
84pub struct UseItems {
85    items: Vec<syn::ItemUse>,
86}
87
88impl UseItems {
89    /// Instead of using syn parsing, this can be used to wrap an existing [Vec] of use items
90    #[inline]
91    pub fn from_items(items: Vec<syn::ItemUse>) -> Self {
92        Self { items }
93    }
94
95    /// Consume this value and emit the inner [Vec] of [syn::ItemUse]
96    #[inline]
97    pub fn into_inner(self) -> Vec<syn::ItemUse> {
98        self.items
99    }
100}
101
102impl syn::parse::Parse for UseItems {
103    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
104        // Random guess on capacity
105        let mut items = Vec::with_capacity(5);
106
107        while !input.is_empty() {
108            items.push(input.parse()?);
109        }
110
111        Ok(Self { items })
112    }
113}
114
115impl ToTokens for UseItems {
116    fn to_tokens(&self, tokens: &mut TokenStream) {
117        tokens.append_all(&self.items);
118    }
119}
120
121impl IntoIterator for UseItems {
122    type Item = syn::ItemUse;
123    type IntoIter = std::vec::IntoIter<Self::Item>;
124
125    #[inline]
126    fn into_iter(self) -> Self::IntoIter {
127        self.items.into_iter()
128    }
129}
130
131// *** Use Entry ***
132
133#[derive(Clone, Debug, cmp::Eq, hash::Hash, cmp::PartialEq)]
134enum UseKey {
135    Name(syn::Ident),
136    Rename(syn::Ident, syn::Ident),
137    Glob,
138}
139
140// *** Use Data ***
141
142#[derive(Clone, Debug, cmp::Eq, hash::Hash, cmp::PartialEq)]
143struct UseData {
144    vis: syn::Visibility,
145    attrs: Vec<syn::Attribute>,
146    has_leading_colons: bool,
147}
148
149impl UseData {
150    #[inline]
151    fn new(vis: syn::Visibility, attrs: Vec<syn::Attribute>, has_leading_colons: bool) -> Self {
152        Self {
153            vis,
154            attrs,
155            has_leading_colons,
156        }
157    }
158}
159
160// *** UseValue ***
161
162#[derive(Clone, Default, Debug)]
163struct UseValue {
164    nodes: HashSet<UseData>,
165    paths: UseBuilder,
166}
167
168// *** Error ***
169
170/// The error type returned if issues occur during [UseBuilder] operations
171#[derive(fmt::Debug)]
172pub enum Error {
173    /// A glob was found as the first entry in a use path - this is illegal Rust
174    TopLevelGlob,
175    /// A group was found as the first entry in a use path - this is not supported
176    TopLevelGroup,
177    /// The same use was found but with differing dual colon prefix, attributes, or visibility
178    UseWithDiffAttr,
179}
180
181impl fmt::Display for Error {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        use Error::*;
184
185        match self {
186            TopLevelGlob => f.write_str("Top level glob is not allowed"),
187            TopLevelGroup => f.write_str("Top level group is not allowed"),
188            UseWithDiffAttr => f.write_str(
189                "Multiple copies of the same import with differing attributes are not allowed",
190            ),
191        }
192    }
193}
194
195impl StdError for Error {}
196
197// *** ItemUse Builder ***
198
199#[derive(Clone, Default)]
200struct ItemUseBuilder {
201    paths: Vec<syn::Ident>,
202}
203
204impl ItemUseBuilder {
205    #[inline]
206    fn add_path(&mut self, path: syn::Ident) {
207        self.paths.push(path);
208    }
209
210    fn into_item_use(mut self, names: Vec<UseKey>, data: UseData) -> syn::ItemUse {
211        let key_to_tree = |key| match key {
212            UseKey::Name(name) => syn::UseTree::Name(syn::UseName { ident: name }),
213            UseKey::Rename(name, rename) => syn::UseTree::Rename(syn::UseRename {
214                ident: name,
215                as_token: Default::default(),
216                rename,
217            }),
218            _ => unreachable!("Impossible glob"),
219        };
220
221        // #1 - Setup name tree
222
223        // Regardless of number of entries in names, if there is a glob, ignore the rest
224        let mut tree = if names.contains(&UseKey::Glob) {
225            syn::UseTree::Glob(syn::UseGlob {
226                star_token: Default::default(),
227            })
228        // If a single entry then it is either a name or rename
229        } else if names.len() == 1 {
230            // Panic safety: we verified there is exactly one item in the set
231            key_to_tree(names.into_iter().next().unwrap())
232        // Group
233        } else {
234            let items = names.into_iter().map(key_to_tree).collect();
235
236            syn::UseTree::Group(syn::UseGroup {
237                brace_token: Default::default(),
238                items,
239            })
240        };
241
242        // #2 - Build path (in reverse order)
243
244        while !self.paths.is_empty() {
245            let path = self.paths.remove(self.paths.len() - 1);
246
247            tree = syn::UseTree::Path(syn::UsePath {
248                ident: path,
249                colon2_token: Default::default(),
250                tree: Box::new(tree),
251            });
252        }
253
254        // #3 - Build ItemUse
255
256        let leading_colon = if data.has_leading_colons {
257            Some(syn::token::Colon2::default())
258        } else {
259            None
260        };
261
262        syn::ItemUse {
263            attrs: data.attrs,
264            vis: data.vis,
265            use_token: Default::default(),
266            leading_colon,
267            tree,
268            semi_token: Default::default(),
269        }
270    }
271}
272
273// *** UseMap ***
274
275/// Type that contains a partitioned list of uses by std, external, and crate level
276pub type StdExtCrateUse = (Vec<syn::ItemUse>, Vec<syn::ItemUse>, Vec<syn::ItemUse>);
277
278/// A type that builds vecs of [syn::ItemUse]. It takes a [Vec] of [UseItems] as input, ensures no
279/// conflicting duplicates, groups them, and then emits as [Vec] (or multiple [Vec]) of [syn::ItemUse]
280#[derive(Clone, Default, Debug)]
281pub struct UseBuilder {
282    map: IndexMap<UseKey, UseValue>,
283    entries: usize,
284}
285
286impl UseBuilder {
287    /// Create a new builder from a [Vec] of [UseItems]
288    pub fn from_uses(items: Vec<UseItems>) -> Self {
289        let mut root_map = Self {
290            map: IndexMap::new(),
291            entries: 0,
292        };
293
294        for inner_items in items {
295            for item in inner_items.items {
296                let data = UseData::new(item.vis, item.attrs, item.leading_colon.is_some());
297                root_map.parse_tree(item.tree, data);
298            }
299        }
300
301        root_map
302    }
303
304    fn add_node(&mut self, entry: UseKey, data: UseData) {
305        match self.map.entry(entry) {
306            indexmap::map::Entry::Occupied(mut e) => {
307                e.get_mut().nodes.insert(data);
308            }
309            indexmap::map::Entry::Vacant(e) => {
310                self.entries += 1;
311                let mut u = UseValue::default();
312                u.nodes.insert(data);
313                e.insert(u);
314            }
315        }
316    }
317
318    fn add_path(&mut self, entry: UseKey) -> &mut UseBuilder {
319        match self.map.entry(entry) {
320            indexmap::map::Entry::Occupied(e) => &mut e.into_mut().paths,
321            indexmap::map::Entry::Vacant(e) => {
322                let u = UseValue::default();
323                &mut e.insert(u).paths
324            }
325        }
326    }
327
328    fn parse_tree(&mut self, tree: syn::UseTree, data: UseData) {
329        use syn::UseTree::*;
330
331        match tree {
332            Path(syn::UsePath { ident, tree, .. }) => {
333                let map = self.add_path(UseKey::Name(ident));
334                // TODO: I hate cloning tree here, but Box::into_inner() is unstable - replace when stable
335                map.parse_tree(syn::UseTree::clone(&*tree), data);
336            }
337            Name(syn::UseName { ident }) => {
338                self.add_node(UseKey::Name(ident), data);
339            }
340            Rename(syn::UseRename { ident, rename, .. }) => {
341                self.add_node(UseKey::Rename(ident, rename), data);
342            }
343            Glob(syn::UseGlob { .. }) => {
344                self.add_node(UseKey::Glob, data);
345            }
346            Group(syn::UseGroup { items, .. }) => {
347                for item in items {
348                    self.parse_tree(item, data.clone());
349                }
350            }
351        }
352    }
353
354    fn next_map(
355        use_map: UseBuilder,
356        builder: ItemUseBuilder,
357        items: &mut Vec<syn::ItemUse>,
358    ) -> Result<(), Error> {
359        let mut map: IndexMap<UseData, Vec<UseKey>> = IndexMap::new();
360        let len = use_map.map.len();
361
362        // Node Strategy: try to combine as we loop over
363        for (key, value) in use_map.map {
364            // *** Path handling **
365
366            // Ignore anything but names for future paths (others are invalid as paths)
367            if let UseKey::Name(path) = key.clone() {
368                // Create a builder from the original
369                let mut builder = builder.clone();
370                builder.add_path(path);
371                if let err @ Err(_) = Self::next_map(value.paths, builder, items) {
372                    return err;
373                }
374            }
375
376            // *** Node handling ***
377
378            // Peek at nodes held by this key
379            if !value.nodes.is_empty() {
380                // We should really only have one entry - more than that means incompatible attrs
381                if value.nodes.len() > 1 {
382                    return Err(Error::UseWithDiffAttr);
383                }
384
385                // Insert into our map
386                // Panic safety: we confirmed above there is exactly one entry
387                match map.entry(value.nodes.into_iter().next().unwrap()) {
388                    indexmap::map::Entry::Occupied(mut e) => {
389                        e.get_mut().push(key);
390                    }
391                    indexmap::map::Entry::Vacant(e) => {
392                        let mut set = Vec::with_capacity(len);
393                        set.push(key);
394                        e.insert(set);
395                    }
396                }
397            }
398        }
399
400        // If we found any nodes, build them based on associated data
401        for (data, names) in map {
402            let item = builder.clone().into_item_use(names, data);
403            items.push(item);
404        }
405
406        Ok(())
407    }
408
409    /// Consume this builder an emit a [Vec] of [syn::ItemUse]
410    pub fn into_items(self) -> Result<Vec<syn::ItemUse>, Error> {
411        let mut items = Vec::with_capacity(self.entries);
412        let builder = ItemUseBuilder::default();
413        Self::next_map(self, builder, &mut items)?;
414        Ok(items)
415    }
416
417    /// Consume this builder and emit three vectors of [syn::ItemUse] partitioned by crate type:
418    /// std, external, and intra-crate uses
419    pub fn into_items_sections(self) -> Result<StdExtCrateUse, Error> {
420        let items = self.into_items()?;
421
422        // Will be too big - better too big than too small
423        let mut std_uses = Vec::with_capacity(items.len());
424        let mut extern_uses = Vec::with_capacity(items.len());
425        let mut crate_uses = Vec::with_capacity(items.len());
426
427        for item in items {
428            use syn::UseTree::*;
429
430            match &item.tree {
431                // Name and rename don't make much sense, but technically legal
432                Path(syn::UsePath { ident, .. })
433                | Name(syn::UseName { ident })
434                | Rename(syn::UseRename { ident, .. }) => {
435                    let name = &*ident.to_string();
436
437                    if STD.contains(&name) {
438                        std_uses.push(item);
439                    } else if CRATE.contains(&name) {
440                        crate_uses.push(item);
441                    } else {
442                        extern_uses.push(item);
443                    };
444                }
445                Glob(_) => return Err(Error::TopLevelGlob),
446                Group(_) => {}
447            }
448        }
449
450        Ok((std_uses, extern_uses, crate_uses))
451    }
452}
453
454#[cfg(test)]
455mod tests {
456    use assert_unordered::assert_eq_unordered;
457    use quote::quote;
458
459    use crate::{UseBuilder, UseItems};
460
461    fn make_builder() -> UseBuilder {
462        let use1 = quote! {
463            use crate::Test;
464            use std::error::Error as StdError;
465            use std::fmt::Debug;
466        };
467
468        let use2 = quote! {
469            use syn::ItemUse;
470            use std::fmt::Display;
471            use crate::*;
472        };
473
474        let items1: UseItems = syn::parse2(use1).unwrap();
475        let items2: UseItems = syn::parse2(use2).unwrap();
476
477        UseBuilder::from_uses(vec![items1, items2])
478    }
479
480    #[test]
481    fn items() {
482        let builder = make_builder();
483        //eprintln!("{:#?}", &builder);
484        let uses = builder.into_items().unwrap();
485        //println!("{uses:#?}");
486
487        let expected = quote! {
488            use crate::*;
489            use std::error::Error as StdError;
490            use std::fmt::{Debug, Display};
491            use syn::ItemUse;
492        };
493        let expected = syn::parse2::<UseItems>(expected).unwrap().into_inner();
494
495        assert_eq_unordered!(expected, uses);
496    }
497
498    #[test]
499    fn items_separated() {
500        let builder = make_builder();
501        let (std_use, ext_use, crate_use) = builder.into_items_sections().unwrap();
502
503        let std_expected = quote! {
504            use std::error::Error as StdError;
505            use std::fmt::{Debug, Display};
506        };
507        let std_expected = syn::parse2::<UseItems>(std_expected).unwrap().into_inner();
508
509        let ext_expected = quote! {
510            use syn::ItemUse;
511        };
512        let ext_expected = syn::parse2::<UseItems>(ext_expected).unwrap().into_inner();
513
514        let crate_expected = quote! {
515            use crate::*;
516        };
517        let crate_expected = syn::parse2::<UseItems>(crate_expected)
518            .unwrap()
519            .into_inner();
520
521        assert_eq_unordered!(std_expected, std_use);
522        assert_eq_unordered!(ext_expected, ext_use);
523        assert_eq_unordered!(crate_expected, crate_use);
524    }
525}