cargo-stern4rust 0.9.1

Cargo subcommand that fails the build when a Rust workspace breaks a house coding rule, such as AAA test structure or one struct per file
Documentation
// Copyright 2025 Umberto Gotti <umberto.gotti@umbertogotti.dev>
// Licensed under the MIT License
// SPDX-License-Identifier: MIT

use syn::FnArg;
use syn::ImplItem;
use syn::Item;
use syn::Signature;
use syn::Visibility;
use syn::parse_file;

use crate::finding::model::public_entry_point::PublicEntryPoint;
use crate::source_file::SourceFile;

// Everything a source file exposes that a test could call.
//
// Two shapes count: a free `pub fn`, and a `pub fn` in an inherent impl block.
//
// **Neither half of a trait counts**, and the two exclusions are the same
// reason. A method *implementing* a trait carries no visibility of its own and
// is reached through the trait rather than named, so requiring a test to call it
// by name asks for something the caller does not write. A method *declared* by a
// trait is not an implementation at all -- there is no behaviour behind it to
// test.
//
// Counting declarations while excusing implementations was incoherent: a trait
// method can only be called through an implementor, and the implementor was
// excused. What it produced was a fake per trait whose only purpose was to be
// asserted against -- a test of the compiler, not of the code.
pub struct PublicEntryPointFinder;

impl PublicEntryPointFinder {
    pub fn find(file: &SourceFile) -> Option<Vec<PublicEntryPoint>> {
        let syntax = parse_file(&file.contents()).ok()?;
        Some(
            syntax
                .items
                .iter()
                .flat_map(Self::of_item)
                .collect::<Vec<PublicEntryPoint>>(),
        )
    }

    fn of_item(item: &Item) -> Vec<PublicEntryPoint> {
        match item {
            Item::Fn(function) if Self::is_public(&function.vis) => {
                vec![Self::of_signature(&function.sig)]
            }
            Item::Impl(block) if block.trait_.is_none() => block
                .items
                .iter()
                .filter_map(|inner| match inner {
                    ImplItem::Fn(method) if Self::is_public(&method.vis) => {
                        Some(Self::of_signature(&method.sig))
                    }
                    _ => None,
                })
                .collect(),
            Item::Mod(module) => module
                .content
                .as_ref()
                .map(|(_, inner)| inner.iter().flat_map(Self::of_item).collect())
                .unwrap_or_default(),
            _ => Vec::new(),
        }
    }

    fn of_signature(signature: &Signature) -> PublicEntryPoint {
        let arity = signature
            .inputs
            .iter()
            .filter(|input| !matches!(input, FnArg::Receiver(_)))
            .count();
        PublicEntryPoint::new(&signature.ident.to_string(), arity)
    }

    fn is_public(visibility: &Visibility) -> bool {
        matches!(visibility, Visibility::Public(_))
    }
}