cargo-mend 0.5.1

Opinionated visibility auditing for Rust crates and workspaces
use std::collections::BTreeSet;

use super::cli::FixCli;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum FixKind {
    ShortenImport,
    PreferModuleImport,
    InlinePathQualifiedType,
    NarrowToPubCrate,
    FixPubUse,
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct FixSelection {
    kinds: BTreeSet<FixKind>,
}

impl FixSelection {
    pub(crate) fn from_cli(fix_cli: &FixCli) -> Self {
        let mut kinds = BTreeSet::new();
        if fix_cli.fix() || fix_cli.fix_all() {
            kinds.insert(FixKind::ShortenImport);
            kinds.insert(FixKind::PreferModuleImport);
            kinds.insert(FixKind::InlinePathQualifiedType);
            kinds.insert(FixKind::NarrowToPubCrate);
        }
        if fix_cli.fix_pub_use() || fix_cli.fix_all() {
            kinds.insert(FixKind::FixPubUse);
        }
        Self { kinds }
    }

    fn all_fix_kinds() -> Self {
        let mut kinds = BTreeSet::new();
        kinds.insert(FixKind::ShortenImport);
        kinds.insert(FixKind::PreferModuleImport);
        kinds.insert(FixKind::InlinePathQualifiedType);
        kinds.insert(FixKind::NarrowToPubCrate);
        kinds.insert(FixKind::FixPubUse);
        Self { kinds }
    }

    pub(crate) fn contains(&self, kind: FixKind) -> bool { self.kinds.contains(&kind) }

    pub(crate) fn is_empty(&self) -> bool { self.kinds.is_empty() }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OperationIntent {
    ReadOnly,
    DryRun,
    Apply,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct OperationMode {
    pub intent: OperationIntent,
    pub fixes:  FixSelection,
}

impl OperationMode {
    pub(crate) fn from_cli(fix_cli: &FixCli) -> Self {
        let fixes = FixSelection::from_cli(fix_cli);
        if fix_cli.dry_run() {
            let effective_fixes = if fixes.is_empty() {
                FixSelection::all_fix_kinds()
            } else {
                fixes
            };
            return Self {
                intent: OperationIntent::DryRun,
                fixes:  effective_fixes,
            };
        }

        if fixes.is_empty() {
            Self {
                intent: OperationIntent::ReadOnly,
                fixes,
            }
        } else {
            Self {
                intent: OperationIntent::Apply,
                fixes,
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::FixKind;
    use super::OperationIntent;
    use super::OperationMode;
    use crate::cli::AutoFixCli;
    use crate::cli::FixCli;
    use crate::cli::FixExecutionCli;

    #[test]
    fn operation_mode_is_read_only_without_fix_flags() {
        let cli = FixCli {
            auto_fix:  AutoFixCli::default(),
            execution: FixExecutionCli::default(),
        };
        let mode = OperationMode::from_cli(&cli);
        assert_eq!(mode.intent, OperationIntent::ReadOnly);
    }

    #[test]
    fn operation_mode_dry_run_alone_implies_all_fix_kinds() {
        let cli = FixCli {
            auto_fix:  AutoFixCli::default(),
            execution: FixExecutionCli {
                dry_run: true,
                ..Default::default()
            },
        };
        let mode = OperationMode::from_cli(&cli);
        assert_eq!(mode.intent, OperationIntent::DryRun);
        assert!(mode.fixes.contains(FixKind::ShortenImport));
        assert!(mode.fixes.contains(FixKind::PreferModuleImport));
        assert!(mode.fixes.contains(FixKind::InlinePathQualifiedType));
        assert!(mode.fixes.contains(FixKind::NarrowToPubCrate));
        assert!(mode.fixes.contains(FixKind::FixPubUse));
    }

    #[test]
    fn operation_mode_allows_previewing_multiple_fix_kinds() {
        let cli = FixCli {
            auto_fix:  AutoFixCli {
                fix: true,
                fix_pub_use: true,
                ..Default::default()
            },
            execution: FixExecutionCli {
                dry_run: true,
                ..Default::default()
            },
        };
        let mode = OperationMode::from_cli(&cli);
        assert_eq!(mode.intent, OperationIntent::DryRun);
        assert!(mode.fixes.contains(FixKind::ShortenImport));
        assert!(mode.fixes.contains(FixKind::NarrowToPubCrate));
        assert!(mode.fixes.contains(FixKind::FixPubUse));
    }

    #[test]
    fn operation_mode_allows_applying_multiple_fix_kinds() {
        let cli = FixCli {
            auto_fix:  AutoFixCli {
                fix: true,
                fix_pub_use: true,
                ..Default::default()
            },
            execution: FixExecutionCli::default(),
        };
        let mode = OperationMode::from_cli(&cli);
        assert_eq!(mode.intent, OperationIntent::Apply);
        assert!(mode.fixes.contains(FixKind::ShortenImport));
        assert!(mode.fixes.contains(FixKind::NarrowToPubCrate));
        assert!(mode.fixes.contains(FixKind::FixPubUse));
    }
}