cargo-mend 0.20.0

Opinionated visibility auditing for Rust crates and workspaces
use anyhow::Result;

use super::annotation::VisibilityAnnotation;
use super::annotation::VisibilitySyntax;
use super::policy;
use super::policy::SignatureExposure;
use super::use_sites::ParentFacadeAnalysis;
use crate::compiler::persistence::FindingsSink;

mod classify;
mod finding_params;
mod record;
mod visibility_context;
mod visit;

pub(super) use classify::CrateKind;
pub(super) use classify::ModuleLocation;
pub(super) use classify::ParentVisibility;
pub(super) use finding_params::AllowanceReason;
pub(super) use finding_params::FindingParams;
pub(super) use finding_params::SuspiciousPubAssessment;
pub(super) use finding_params::SuspiciousPubInput;
pub(super) use visibility_context::ItemCategory;
pub(super) use visibility_context::ItemInfo;
pub(super) use visibility_context::VisibilityContext;
pub(super) use visibility_context::collect_and_store_findings;

#[derive(Clone, Copy)]
pub(super) enum ExposureConsumers {
    AllVisibilityFindings,
    ForbiddenAnnotation,
}

pub(super) fn signature_exposure_for_annotation(
    ctx: &VisibilityContext<'_, '_>,
    item: &ItemInfo<'_>,
    annotation: &VisibilityAnnotation<'_>,
    consumers: ExposureConsumers,
) -> Result<SignatureExposure> {
    if !annotation_consumes_signature_exposure(annotation.syntax(), consumers) {
        return Ok(SignatureExposure::Contained);
    }
    policy::signature_exposure_reach(ctx, item.def_id, item.file_path, item.name)
}

const fn annotation_consumes_signature_exposure(
    syntax: VisibilitySyntax,
    consumers: ExposureConsumers,
) -> bool {
    let forbidden_annotation = matches!(
        syntax,
        VisibilitySyntax::Crate
            | VisibilitySyntax::InCrate
            | VisibilitySyntax::InParent
            | VisibilitySyntax::InCurrent
            | VisibilitySyntax::InPath(_)
    );
    forbidden_annotation
        || matches!(
            (syntax, consumers),
            (
                VisibilitySyntax::Public,
                ExposureConsumers::AllVisibilityFindings
            )
        )
}

pub(super) fn record_forbidden_visibility_annotation(
    ctx: &VisibilityContext<'_, '_>,
    item: &ItemInfo<'_>,
    annotation: &VisibilityAnnotation<'_>,
    parent_facade_analysis: Option<&ParentFacadeAnalysis<'_>>,
    sink: &mut FindingsSink,
) -> Result<bool> {
    let finding_context = classify::visibility_finding_context(ctx, item);
    let signature_exposure = signature_exposure_for_annotation(
        ctx,
        item,
        annotation,
        ExposureConsumers::ForbiddenAnnotation,
    )?;
    record::record_forbidden_visibility_annotation(
        ctx,
        item,
        annotation,
        &finding_context,
        parent_facade_analysis,
        signature_exposure,
        sink,
    )
}

#[cfg(test)]
mod tests {
    use super::ExposureConsumers;
    use super::VisibilitySyntax;
    use super::annotation_consumes_signature_exposure;
    use crate::compiler::visibility::annotation::PathSpelling;

    #[test]
    fn signature_exposure_analysis_is_lazy_by_annotation_consumer() {
        for consumers in [
            ExposureConsumers::AllVisibilityFindings,
            ExposureConsumers::ForbiddenAnnotation,
        ] {
            assert!(!annotation_consumes_signature_exposure(
                VisibilitySyntax::Private,
                consumers,
            ));
        }
        assert!(annotation_consumes_signature_exposure(
            VisibilitySyntax::Public,
            ExposureConsumers::AllVisibilityFindings,
        ));
        assert!(!annotation_consumes_signature_exposure(
            VisibilitySyntax::Public,
            ExposureConsumers::ForbiddenAnnotation,
        ));
        for syntax in [
            VisibilitySyntax::Crate,
            VisibilitySyntax::InCrate,
            VisibilitySyntax::InParent,
            VisibilitySyntax::InCurrent,
            VisibilitySyntax::InPath(PathSpelling::CrateRooted),
            VisibilitySyntax::InPath(PathSpelling::Relative),
        ] {
            assert!(annotation_consumes_signature_exposure(
                syntax,
                ExposureConsumers::ForbiddenAnnotation,
            ));
        }
    }
}