cargo-mend 0.16.1

Opinionated visibility auditing for Rust crates and workspaces
use serde::Deserialize;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DiagnosticCode {
    ForbiddenPubCrate,
    ForbiddenPubInCrate,
    ReviewPubMod,
    SuspiciousPub,
    UnusedPub,
    PreferModuleImport,
    InlinePathQualifiedType,
    ShortenLocalCrateImport,
    ReplaceDeepSuperImport,
    WildcardParentPubUse,
    InternalParentPubUseFacade,
    NarrowToPubCrate,
    FieldVisibilityWiderThanType,
    ImportsAtTop,
}

impl DiagnosticCode {
    pub const ALL: &[Self] = &[
        Self::ForbiddenPubCrate,
        Self::ForbiddenPubInCrate,
        Self::ReviewPubMod,
        Self::SuspiciousPub,
        Self::UnusedPub,
        Self::PreferModuleImport,
        Self::InlinePathQualifiedType,
        Self::ShortenLocalCrateImport,
        Self::ReplaceDeepSuperImport,
        Self::WildcardParentPubUse,
        Self::InternalParentPubUseFacade,
        Self::NarrowToPubCrate,
        Self::FieldVisibilityWiderThanType,
        Self::ImportsAtTop,
    ];

    pub const fn as_str(self) -> &'static str {
        match self {
            Self::ForbiddenPubCrate => "forbidden_pub_crate",
            Self::ForbiddenPubInCrate => "forbidden_pub_in_crate",
            Self::ReviewPubMod => "review_pub_mod",
            Self::SuspiciousPub => "suspicious_pub",
            Self::UnusedPub => "unused_pub",
            Self::PreferModuleImport => "prefer_module_import",
            Self::InlinePathQualifiedType => "inline_path_qualified_type",
            Self::ShortenLocalCrateImport => "shorten_local_crate_import",
            Self::ReplaceDeepSuperImport => "replace_deep_super_import",
            Self::WildcardParentPubUse => "wildcard_parent_pub_use",
            Self::InternalParentPubUseFacade => "internal_parent_pub_use_facade",
            Self::NarrowToPubCrate => "narrow_to_pub_crate",
            Self::FieldVisibilityWiderThanType => "field_visibility_wider_than_type",
            Self::ImportsAtTop => "imports_at_top",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum FixSupport {
    #[default]
    None,
    ShortenImport,
    PreferModuleImport,
    InlinePathQualifiedType,
    #[serde(rename = "fix_pub_use")]
    PubUse,
    NeedsManualPubUseCleanup,
    InternalParentFacade,
    UnusedPub,
    NarrowToPubCrate,
    #[serde(rename = "fix_field_visibility")]
    FieldVisibility,
    #[serde(rename = "fix_imports_at_top")]
    ImportsAtTop,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FixSummaryBucket {
    Standard,
    PubUse,
}

impl FixSupport {
    pub const fn note(self) -> Option<&'static str> {
        match self {
            Self::None | Self::NeedsManualPubUseCleanup | Self::InternalParentFacade => None,
            Self::ShortenImport
            | Self::PreferModuleImport
            | Self::InlinePathQualifiedType
            | Self::UnusedPub
            | Self::NarrowToPubCrate
            | Self::FieldVisibility
            | Self::ImportsAtTop => Some("this warning is auto-fixable with `cargo mend --fix`"),
            Self::PubUse => Some("this warning is auto-fixable with `cargo mend --fix-pub-use`"),
        }
    }

    pub const fn summary_bucket(self) -> Option<FixSummaryBucket> {
        match self {
            Self::None | Self::NeedsManualPubUseCleanup | Self::InternalParentFacade => None,
            Self::ShortenImport
            | Self::PreferModuleImport
            | Self::InlinePathQualifiedType
            | Self::UnusedPub
            | Self::NarrowToPubCrate
            | Self::FieldVisibility
            | Self::ImportsAtTop => Some(FixSummaryBucket::Standard),
            Self::PubUse => Some(FixSummaryBucket::PubUse),
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub struct DiagnosticSpec {
    pub headline:    &'static str,
    pub help_anchor: &'static str,
    pub fix_support: FixSupport,
}

pub const fn diagnostic_spec(code: DiagnosticCode) -> &'static DiagnosticSpec {
    const FORBIDDEN_PUB_CRATE: DiagnosticSpec = DiagnosticSpec {
        headline:    "use of `pub(crate)` is forbidden by policy",
        help_anchor: "forbidden-pub-crate",
        fix_support: FixSupport::None,
    };
    const FORBIDDEN_PUB_IN_CRATE: DiagnosticSpec = DiagnosticSpec {
        headline:    "use of `pub(in crate::...)` is forbidden by policy",
        help_anchor: "forbidden-pub-in-crate",
        fix_support: FixSupport::None,
    };
    const REVIEW_PUB_MOD: DiagnosticSpec = DiagnosticSpec {
        headline:    "`pub mod` requires explicit review or allowlisting",
        help_anchor: "review-pub-mod",
        fix_support: FixSupport::None,
    };
    const SUSPICIOUS_PUB: DiagnosticSpec = DiagnosticSpec {
        headline:    "`pub` is broader than this nested module boundary",
        help_anchor: "suspicious-pub",
        fix_support: FixSupport::None,
    };
    const UNUSED_PUB: DiagnosticSpec = DiagnosticSpec {
        headline:    "`pub` item is not used outside its defining module",
        help_anchor: "unused-pub",
        fix_support: FixSupport::UnusedPub,
    };
    const PREFER_MODULE_IMPORT: DiagnosticSpec = DiagnosticSpec {
        headline:    "function import should use module-qualified form",
        help_anchor: "prefer-module-import",
        fix_support: FixSupport::PreferModuleImport,
    };
    const INLINE_PATH_QUALIFIED_TYPE: DiagnosticSpec = DiagnosticSpec {
        headline:    "inline path-qualified type should use a `use` import",
        help_anchor: "inline-path-qualified-type",
        fix_support: FixSupport::InlinePathQualifiedType,
    };
    const SHORTEN_LOCAL_CRATE_IMPORT: DiagnosticSpec = DiagnosticSpec {
        headline:    "crate-relative import can be shortened to a local-relative import",
        help_anchor: "shorten-local-crate-import",
        fix_support: FixSupport::ShortenImport,
    };
    const REPLACE_DEEP_SUPER_IMPORT: DiagnosticSpec = DiagnosticSpec {
        headline:    "deep `super::` chain should use a `crate::` path",
        help_anchor: "replace-deep-super-import",
        fix_support: FixSupport::ShortenImport,
    };
    const WILDCARD_PARENT_PUB_USE: DiagnosticSpec = DiagnosticSpec {
        headline:    "parent module `pub use *` should be explicit",
        help_anchor: "wildcard-parent-pub-use",
        fix_support: FixSupport::None,
    };
    const INTERNAL_PARENT_PUB_USE_FACADE: DiagnosticSpec = DiagnosticSpec {
        headline:    "parent module `pub use` is acting as an internal facade",
        help_anchor: "internal-parent-pub-use-facade",
        fix_support: FixSupport::InternalParentFacade,
    };
    const NARROW_TO_PUB_CRATE: DiagnosticSpec = DiagnosticSpec {
        headline:    "`pub` exceeds the item's effective reach — use `pub(crate)`",
        help_anchor: "narrow-to-pub-crate",
        fix_support: FixSupport::NarrowToPubCrate,
    };
    const FIELD_VISIBILITY_WIDER_THAN_TYPE: DiagnosticSpec = DiagnosticSpec {
        headline:    "field visibility is wider than its containing type",
        help_anchor: "field-visibility-wider-than-type",
        fix_support: FixSupport::FieldVisibility,
    };
    const IMPORTS_AT_TOP: DiagnosticSpec = DiagnosticSpec {
        headline:    "`use` statement should live at the top of the file or inline module",
        help_anchor: "imports-at-top",
        fix_support: FixSupport::ImportsAtTop,
    };

    match code {
        DiagnosticCode::ForbiddenPubCrate => &FORBIDDEN_PUB_CRATE,
        DiagnosticCode::ForbiddenPubInCrate => &FORBIDDEN_PUB_IN_CRATE,
        DiagnosticCode::ReviewPubMod => &REVIEW_PUB_MOD,
        DiagnosticCode::SuspiciousPub => &SUSPICIOUS_PUB,
        DiagnosticCode::UnusedPub => &UNUSED_PUB,
        DiagnosticCode::PreferModuleImport => &PREFER_MODULE_IMPORT,
        DiagnosticCode::InlinePathQualifiedType => &INLINE_PATH_QUALIFIED_TYPE,
        DiagnosticCode::ShortenLocalCrateImport => &SHORTEN_LOCAL_CRATE_IMPORT,
        DiagnosticCode::ReplaceDeepSuperImport => &REPLACE_DEEP_SUPER_IMPORT,
        DiagnosticCode::WildcardParentPubUse => &WILDCARD_PARENT_PUB_USE,
        DiagnosticCode::InternalParentPubUseFacade => &INTERNAL_PARENT_PUB_USE_FACADE,
        DiagnosticCode::NarrowToPubCrate => &NARROW_TO_PUB_CRATE,
        DiagnosticCode::FieldVisibilityWiderThanType => &FIELD_VISIBILITY_WIDER_THAN_TYPE,
        DiagnosticCode::ImportsAtTop => &IMPORTS_AT_TOP,
    }
}