vsec 0.0.1

Detect secrets and in Rust codebases
Documentation
// src/registry/definition.rs

use std::path::PathBuf;

/// A constant definition found during the indexing phase
#[derive(Debug, Clone)]
pub struct ConstantDefinition {
    /// The name of the constant
    pub name: String,

    /// The string value of the constant
    pub value: String,

    /// File where this was defined
    pub file: PathBuf,

    /// Module path within the file (e.g., "inner::nested")
    /// Empty string for top-level definitions
    pub module_path: String,

    /// Line number of definition
    pub line: u32,

    /// Visibility (pub, pub(crate), etc.)
    pub visibility: ConstantVisibility,

    /// Preliminary score (from name analysis alone)
    pub preliminary_score: i32,
}

impl ConstantDefinition {
    /// Create a new constant definition
    pub fn new(name: String, value: String, file: PathBuf, line: u32) -> Self {
        Self {
            name,
            value,
            file,
            module_path: String::new(),
            line,
            visibility: ConstantVisibility::Private,
            preliminary_score: 0,
        }
    }

    /// Get the fully qualified key for this definition (file::module::name)
    pub fn qualified_key(&self) -> String {
        if self.module_path.is_empty() {
            format!("{}::{}", self.file.display(), self.name)
        } else {
            format!("{}::{}::{}", self.file.display(), self.module_path, self.name)
        }
    }

    /// Set the visibility
    pub fn with_visibility(mut self, visibility: ConstantVisibility) -> Self {
        self.visibility = visibility;
        self
    }

    /// Set the preliminary score
    pub fn with_score(mut self, score: i32) -> Self {
        self.preliminary_score = score;
        self
    }

    /// Set the module path
    pub fn with_module_path(mut self, module_path: String) -> Self {
        self.module_path = module_path;
        self
    }

    /// Check if this constant is publicly accessible
    pub fn is_public(&self) -> bool {
        matches!(
            self.visibility,
            ConstantVisibility::Public | ConstantVisibility::PubCrate
        )
    }
}

/// Visibility levels for constants
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConstantVisibility {
    /// `pub` - visible everywhere
    Public,
    /// `pub(crate)` - visible in crate
    PubCrate,
    /// `pub(super)` - visible in parent
    PubSuper,
    /// Private - only visible in file
    Private,
}

impl ConstantVisibility {
    /// Parse from syn Visibility
    pub fn from_syn(vis: &syn::Visibility) -> Self {
        match vis {
            syn::Visibility::Public(_) => Self::Public,
            syn::Visibility::Restricted(r) => {
                let path = r
                    .path
                    .segments
                    .first()
                    .map(|s| s.ident.to_string())
                    .unwrap_or_default();
                match path.as_str() {
                    "crate" => Self::PubCrate,
                    "super" => Self::PubSuper,
                    _ => Self::PubCrate, // pub(in path)
                }
            }
            syn::Visibility::Inherited => Self::Private,
        }
    }
}

impl Default for ConstantVisibility {
    fn default() -> Self {
        Self::Private
    }
}

/// Statistics about the registry contents
#[derive(Debug, Clone, Default)]
pub struct RegistryStats {
    /// Total number of constants indexed
    pub total: usize,

    /// Number of public constants
    pub public: usize,

    /// Number of suspicious constants (preliminary score > 30)
    pub suspicious: usize,
}

impl RegistryStats {
    /// Get the percentage of public constants
    pub fn public_percentage(&self) -> f64 {
        if self.total == 0 {
            0.0
        } else {
            (self.public as f64 / self.total as f64) * 100.0
        }
    }

    /// Get the percentage of suspicious constants
    pub fn suspicious_percentage(&self) -> f64 {
        if self.total == 0 {
            0.0
        } else {
            (self.suspicious as f64 / self.total as f64) * 100.0
        }
    }
}

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

    #[test]
    fn test_constant_definition() {
        let def = ConstantDefinition::new(
            "API_KEY".into(),
            "secret".into(),
            PathBuf::from("src/lib.rs"),
            10,
        )
        .with_visibility(ConstantVisibility::Public)
        .with_score(50);

        assert_eq!(def.name, "API_KEY");
        assert!(def.is_public());
        assert_eq!(def.preliminary_score, 50);
    }

    #[test]
    fn test_visibility_from_syn() {
        use syn::parse_quote;

        let vis: syn::Visibility = parse_quote!(pub);
        assert_eq!(ConstantVisibility::from_syn(&vis), ConstantVisibility::Public);

        let vis: syn::Visibility = parse_quote!(pub(crate));
        assert_eq!(
            ConstantVisibility::from_syn(&vis),
            ConstantVisibility::PubCrate
        );
    }
}