rulewright 0.1.1

Executable engineering standards for Rust workspaces
Documentation
#[cfg(test)]
use googletest::prelude::*;
use std::collections::HashSet;

use crate::{Example, Violation, languages::workspace::WorkspaceCtx, matches_ignore, violation};

#[rustfmt::skip]
const EXAMPLES: &[Example] = &[
    Example { label: "unused dependency", code: "fn main() {}", pass: false },
    Example { label: "path-referenced dependency", code: "fn encode(value: serde::Value) {}", pass: true },
    Example { label: "derive-only dependency", code: "#[derive(serde::Serialize)] struct Value;", pass: true },
];

crate::language_workspace_rule!(
    toml_cargo_unused_deps,
    "Flag workspace-member dependencies that are never referenced by any Rust compile target.",
    "Unused dependencies increase build time and supply-chain surface. Full-workspace runs are authoritative because references are aggregated across src, tests, benches, examples, and build.rs.",
    Medium,
    params {
        always_used: [String] = [],
    },
);

fn check_toml_cargo_unused_deps(ctx: &WorkspaceCtx<'_>) -> Vec<Violation> {
    let always_used: HashSet<String> = ctx
        .config
        .get_str_array("toml_cargo_unused_deps", &PARAMS[0])
        .into_iter()
        .map(|name| name.replace('-', "_"))
        .collect();
    let mut violations = Vec::new();

    for manifest in ctx.manifests {
        if matches_ignore(
            &manifest.rel,
            ctx.config.ignore_patterns("toml_cargo_unused_deps"),
        ) {
            continue;
        }

        let referenced: HashSet<&str> = ctx
            .files
            .iter()
            .filter(|file| belongs_to_manifest(&file.rel, &manifest.rel, ctx.manifests))
            .flat_map(|file| file.crate_roots.iter().map(String::as_str))
            .collect();

        for dependency in &manifest.dependencies {
            if always_used.contains(&dependency.root)
                || referenced.contains(dependency.root.as_str())
            {
                continue;
            }

            violations.push(violation(
                &manifest.rel,
                dependency.line,
                format!(
                    "dependency `{}` is never referenced by any Rust target in this workspace member",
                    dependency.name
                ),
            ));
        }
    }

    violations
}

fn belongs_to_manifest(
    source_rel: &str,
    manifest_rel: &str,
    manifests: &[crate::WorkspaceManifest],
) -> bool {
    let Some((member_dir, _)) = manifest_rel.rsplit_once('/') else {
        return manifests.iter().all(|other| {
            other.rel == manifest_rel
                || other
                    .rel
                    .rsplit_once('/')
                    .is_none_or(|(other_dir, _)| !source_rel.starts_with(&format!("{other_dir}/")))
        });
    };

    source_rel.starts_with(&format!("{member_dir}/"))
}

#[cfg(test)]
mod tests {
    use super::*;

    fn run(source: &str, dependency_name: &str) -> Vec<Violation> {
        crate::test_support::check_workspace_member(
            "crates/example",
            source,
            &[(dependency_name, dependency_name)],
            check_toml_cargo_unused_deps,
        )
    }

    #[gtest]
    fn examples() -> Result<()> {
        for example in EXAMPLES {
            let violations = run(example.code, "serde");

            verify_eq!(violations.is_empty(), example.pass)?;
        }

        Ok(())
    }

    #[gtest]
    fn renamed_dependency_uses_the_manifest_key_as_its_crate_root() -> Result<()> {
        let violations = run("fn decode(value: json::Value) {}", "json");

        verify_true!(violations.is_empty())?;

        Ok(())
    }

    #[gtest]
    fn qualified_path_inside_macro_tokens_counts_as_used() -> Result<()> {
        let violations = run(
            "macro_rules! encode { () => { serde::Serialize }; }",
            "serde",
        );

        verify_true!(violations.is_empty())?;

        Ok(())
    }

    #[gtest]
    fn extern_crate_counts_as_a_direct_reference() -> Result<()> {
        let violations = run(
            "#[cfg(feature = \"wasm\")] extern crate wasm_bindgen;",
            "wasm-bindgen",
        );

        verify_true!(violations.is_empty())
    }
}