vsec 0.0.1

Detect secrets and in Rust codebases
Documentation
// src/models/context.rs

use std::path::PathBuf;

/// Tracks the analysis context as we traverse the AST
#[derive(Debug, Clone, Default)]
pub struct AnalysisContext {
    /// Current file being analyzed
    pub file_path: PathBuf,

    /// Stack of scopes we're inside
    pub scope_stack: Vec<Scope>,

    /// Whether we're in a test context
    pub in_test_context: bool,

    /// Whether we're in an example
    pub in_example: bool,

    /// Whether we're in a benchmark
    pub in_benchmark: bool,

    /// Current function name (if any)
    pub current_function: Option<String>,

    /// Current impl block (if any)
    pub current_impl: Option<ImplContext>,

    /// Visibility of current item
    pub visibility: Visibility,

    /// Whether current function contains auth-related patterns
    /// (e.g., ".get("authorization")", "unauthenticated", etc.)
    pub has_auth_indicators: bool,
}

impl AnalysisContext {
    pub fn new(file_path: PathBuf) -> Self {
        let path_context = PathContext::analyze(&file_path);

        Self {
            file_path,
            in_test_context: path_context.is_test,
            in_example: path_context.is_example,
            in_benchmark: path_context.is_benchmark,
            ..Default::default()
        }
    }

    pub fn push_scope(&mut self, scope: Scope) {
        self.scope_stack.push(scope);
    }

    pub fn pop_scope(&mut self) -> Option<Scope> {
        self.scope_stack.pop()
    }

    pub fn should_skip(&self) -> bool {
        self.in_test_context || self.in_example || self.in_benchmark
    }
}

/// Structured path context analysis (OS-independent)
#[derive(Debug, Clone, Default)]
pub struct PathContext {
    /// Normalized path components
    pub components: Vec<String>,
    pub is_test: bool,
    pub is_example: bool,
    pub is_benchmark: bool,
    pub is_generated: bool,
}

impl PathContext {
    pub fn analyze(path: &std::path::Path) -> Self {
        let components: Vec<String> = path
            .components()
            .filter_map(|c| c.as_os_str().to_str())
            .map(String::from)
            .collect();

        let is_test = components.iter().any(|c| {
            c == "tests"
                || c == "test"
                || c.ends_with("_test.rs")
                || c.starts_with("test_")
                || c.ends_with("_test")    // e2e_test, integration_test, etc.
                || c.ends_with("_tests")   // unit_tests, etc.
        });

        let is_example = components
            .iter()
            .any(|c| c == "examples" || c == "example");

        let is_benchmark = components.iter().any(|c| c == "benches" || c == "bench");

        let is_generated = components
            .iter()
            .any(|c| c == "generated" || c == "target" || c.ends_with(".generated.rs"));

        Self {
            components,
            is_test,
            is_example,
            is_benchmark,
            is_generated,
        }
    }
}

/// Different types of scopes
#[derive(Debug, Clone)]
pub enum Scope {
    /// Regular module: `mod foo { ... }`
    Module(String),

    /// Test module: `#[cfg(test)] mod tests { ... }`
    TestModule,

    /// Regular function
    Function(String),

    /// Test function: `#[test] fn test_foo() { ... }`
    TestFunction,

    /// Impl block
    Impl(ImplContext),

    /// Trait impl
    TraitImpl {
        trait_name: String,
        for_type: String,
    },

    /// Block expression
    Block,

    /// Closure
    Closure,

    /// Match arm
    MatchArm,

    /// If block
    IfBlock,

    /// Loop
    Loop,
}

/// Context for an impl block
#[derive(Debug, Clone)]
pub struct ImplContext {
    pub type_name: String,
    pub trait_name: Option<String>,
}

/// Visibility levels
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum Visibility {
    #[default]
    Private,
    PubCrate,
    PubSuper,
    PubIn(String),
    Public,
}

impl Visibility {
    /// 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,
                    other => Self::PubIn(other.to_string()),
                }
            }
            syn::Visibility::Inherited => Self::Private,
        }
    }
}