perfectionist 0.0.0-rc.18

Additional linting rules for Rust projects
Documentation
//! Renders the canonical `use`-tree text for a group of leaves under a
//! chosen [`Style`]. Each function returns the text that goes between
//! `use ` and the trailing `;` for one statement; the caller prepends
//! the shared visibility / attributes and joins the statements.

use std::collections::BTreeMap;

use super::config::Style;
use super::model::{Leaf, LeafItem, self_has_splittable_sibling};

pub(super) fn render(style: Style, leaves: &[Leaf]) -> Vec<String> {
    match style {
        Style::Crate => render_crate(leaves, false),
        Style::Module => render_module(leaves),
        Style::Item => render_item(leaves),
    }
}

/// The alternative `crate` shape that folds a bare item sharing a
/// module's name into `self` (`{thing, thing::T}` → `thing::{self, T}`).
///
/// This is the form rustfmt's `imports_granularity = "Crate"` produces,
/// but it is *not* interchangeable with the sibling form: `use a::b;`
/// binds `b` in every namespace, while `a::b::{self}` re-imports only the
/// module `b`. When the two shapes differ the caller offers both as
/// `MaybeIncorrect` alternatives, because the rule can't tell from syntax
/// whether a value or macro is re-exported under the module's name (see
/// <https://github.com/KSXGitHub/perfectionist/issues/186>).
pub(super) fn render_crate_self(leaves: &[Leaf]) -> Vec<String> {
    render_crate(leaves, true)
}

/// The alternative `crate` shape that splits a module-only `self` sharing
/// its name with a sibling inside the module back into a bare item
/// (`thing::{self, T}` → `{thing, thing::T}`).
///
/// This is the inverse of [`render_crate_self`]: where the fold collapses
/// `{thing, thing::T}` into `thing::{self, T}`, the split lowers each
/// `self` that has a splittable sibling into the bare item one level up. A
/// `self` with no such sibling (`use thing::{self}`, or one paired only
/// with another `self` for the same module) is a genuine module-only
/// import with nothing to split against, so it is left untouched. Selected
/// by the `split` `self_merge` setting (see
/// <https://github.com/KSXGitHub/perfectionist/issues/206>).
pub(super) fn render_crate_split(leaves: &[Leaf]) -> Vec<String> {
    let lowered: Vec<Leaf> = leaves
        .iter()
        .enumerate()
        .map(|(index, leaf)| {
            if matches!(leaf.item, LeafItem::SelfMod) && self_has_splittable_sibling(leaves, index)
            {
                let mut module = leaf.module.clone();
                let name = module
                    .pop()
                    .expect("a `self` leaf always has a module path");
                Leaf {
                    module,
                    item: LeafItem::Named(name),
                    rename: leaf.rename.clone(),
                }
            } else {
                leaf.clone()
            }
        })
        .collect();
    render_crate(&lowered, false)
}

fn join(segments: &[String]) -> String {
    segments.join("::")
}

fn rename_suffix(rename: &Option<String>) -> String {
    match rename {
        Some(name) => format!(" as {name}"),
        None => String::new(),
    }
}

/// Order brace entries: `self` first, then names (case-insensitively),
/// then the glob `*` last. The full entry text is the final tiebreaker,
/// so entries that collide on the primary key (`self` vs `self as x`, or
/// `Foo` vs `foo`) still have a total, deterministic order rather than
/// relying on input order. Deduplicates exact repeats.
fn sort_entries(entries: &mut Vec<String>) {
    entries.sort_by(|left, right| entry_key(left).cmp(&entry_key(right)));
    entries.dedup();
}

fn entry_key(entry: &str) -> (u8, String, &str) {
    let group = if entry == "self" || entry.starts_with("self ") {
        0
    } else if entry == "*" {
        2
    } else {
        1
    };
    (group, entry.to_ascii_lowercase(), entry)
}

/// Wrap a module path's entries: a single non-`self` entry needs no
/// braces (`std::io::Read`, `std::io::*`); everything else does.
fn wrap(prefix: &str, entries: &[String]) -> String {
    if entries.len() == 1 && !needs_brace(&entries[0]) {
        format!("{prefix}::{}", entries[0])
    } else {
        format!("{prefix}::{{{}}}", entries.join(", "))
    }
}

fn needs_brace(entry: &str) -> bool {
    entry == "self" || entry.starts_with("self ")
}

/// `use serde;` / `use serde as alias;` — the standalone form every
/// style keeps as-is.
fn crate_item_body(leaf: &Leaf) -> String {
    let LeafItem::Named(name) = &leaf.item else {
        unreachable!("crate item is always a named leaf")
    };
    format!("{name}{}", rename_suffix(&leaf.rename))
}

fn item_entry(leaf: &Leaf) -> String {
    match &leaf.item {
        LeafItem::Named(name) => format!("{name}{}", rename_suffix(&leaf.rename)),
        LeafItem::Glob => "*".to_owned(),
        LeafItem::SelfMod => format!("self{}", rename_suffix(&leaf.rename)),
    }
}

fn render_item(leaves: &[Leaf]) -> Vec<String> {
    let mut out: Vec<String> = leaves
        .iter()
        .map(|leaf| {
            if leaf.is_crate_item() {
                crate_item_body(leaf)
            } else {
                let entry = item_entry(leaf);
                wrap(&join(&leaf.module), std::slice::from_ref(&entry))
            }
        })
        .collect();
    out.sort();
    out.dedup();
    out
}

fn render_module(leaves: &[Leaf]) -> Vec<String> {
    let mut groups: BTreeMap<&[String], Vec<&Leaf>> = BTreeMap::new();
    for leaf in leaves {
        groups.entry(leaf.module.as_slice()).or_default().push(leaf);
    }
    let mut out = Vec::new();
    for (module, members) in groups {
        if module.is_empty() {
            for leaf in members {
                out.push(crate_item_body(leaf));
            }
            continue;
        }
        let mut entries: Vec<String> = members.iter().map(|leaf| item_entry(leaf)).collect();
        sort_entries(&mut entries);
        out.push(wrap(&join(module), &entries));
    }
    out.sort();
    out.dedup();
    out
}

#[derive(Default)]
struct Node {
    items: Vec<Leaf>,
    children: BTreeMap<String, Node>,
}

fn insert(node: &mut Node, module: &[String], leaf: &Leaf) {
    match module.split_first() {
        None => node.items.push(leaf.clone()),
        Some((head, rest)) => insert(node.children.entry(head.clone()).or_default(), rest, leaf),
    }
}

fn node_entries(node: &Node, synthesize_self: bool) -> Vec<String> {
    let mut entries: Vec<String> = Vec::new();
    for item in &node.items {
        // A named item whose name matches a child module — `use a::b;`
        // next to `use a::b::C;` — can be read as the module imported as
        // itself. Only the `synthesize_self` shape folds it into that
        // child's brace as `self` (below); the default sibling shape
        // keeps it standing alone. The two are NOT interchangeable:
        // `use a::b;` binds `b` in *every* namespace, so folding it to
        // `a::b::{self}` drops any value or macro re-exported under the
        // module's name. An explicit `self` written in the source
        // (`LeafItem::SelfMod`) is a genuine module-only import and
        // renders inside the child brace under both shapes. See
        // <https://github.com/KSXGitHub/perfectionist/issues/186>.
        if synthesize_self
            && let LeafItem::Named(name) = &item.item
            && node.children.contains_key(name)
        {
            continue;
        }
        entries.push(item_entry(item));
    }
    for (segment, child) in &node.children {
        let mut sub = node_entries(child, synthesize_self);
        if synthesize_self {
            for item in &node.items {
                if let LeafItem::Named(name) = &item.item
                    && name == segment
                {
                    sub.push(format!("self{}", rename_suffix(&item.rename)));
                }
            }
            sort_entries(&mut sub);
        }
        entries.push(wrap(segment, &sub));
    }
    sort_entries(&mut entries);
    entries
}

fn render_crate(leaves: &[Leaf], synthesize_self: bool) -> Vec<String> {
    // Everything goes into one trie keyed by path segment; a bare
    // `use foo;` lands as a named item at the root and renders as `foo`,
    // while `use foo::Bar;` descends into the `foo` child. Under
    // `synthesize_self` the two fold together as `foo::{self, Bar}`;
    // otherwise they sit side by side as `{foo, foo::Bar}`. Each
    // top-level entry is one statement.
    let mut root = Node::default();
    for leaf in leaves {
        insert(&mut root, &leaf.module, leaf);
    }
    let mut out = node_entries(&root, synthesize_self);
    out.sort();
    out.dedup();
    out
}