ryo-symbol 0.1.0

Symbol system for Rust codebase - unique identifiers and file path management
Documentation
//! File span (location information within a file)

use crate::file_path::WorkspaceFilePath;
use crate::path::SymbolPath;
use serde::Serialize;

/// Position information within a file
///
/// Represents a byte range within a source file.
///
/// NOTE: Deserialize is NOT derived because WorkspaceFilePath requires
/// context (workspace_root) during deserialization.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
pub struct FileSpan {
    /// The file containing this span
    pub file: WorkspaceFilePath,
    /// Start byte offset (inclusive)
    pub start: u32,
    /// End byte offset (exclusive)
    pub end: u32,
}

impl FileSpan {
    /// Create a new FileSpan
    pub fn new(file: WorkspaceFilePath, start: u32, end: u32) -> Self {
        Self { file, start, end }
    }

    /// Get the length of the span in bytes
    pub fn len(&self) -> u32 {
        self.end.saturating_sub(self.start)
    }

    /// Check if the span is empty
    pub fn is_empty(&self) -> bool {
        self.start >= self.end
    }

    /// Check if this span contains a byte offset
    pub fn contains(&self, offset: u32) -> bool {
        offset >= self.start && offset < self.end
    }

    /// Check if this span overlaps with another
    pub fn overlaps(&self, other: &FileSpan) -> bool {
        self.file == other.file && self.start < other.end && other.start < self.end
    }
}

impl std::fmt::Display for FileSpan {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}:{}-{}", self.file, self.start, self.end)
    }
}

/// Visibility of a symbol
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Default)]
pub enum Visibility {
    /// pub
    Public,
    /// pub(crate)
    Crate,
    /// pub(super)
    Super,
    /// pub(in path) - visible within specified path
    Restricted(Box<SymbolPath>),
    /// private (default)
    #[default]
    Private,
}

impl Visibility {
    /// Check if this visibility is public.
    #[inline]
    pub fn is_public(&self) -> bool {
        matches!(self, Visibility::Public)
    }

    /// Check if this visibility is at least crate-visible.
    #[inline]
    pub fn is_crate_visible(&self) -> bool {
        matches!(self, Visibility::Public | Visibility::Crate)
    }

    /// Check if this is private.
    #[inline]
    pub fn is_private(&self) -> bool {
        matches!(self, Visibility::Private)
    }
}

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

    #[test]
    fn test_file_span() {
        let file = WorkspaceFilePath::new_for_test("src/lib.rs", "/workspace", "test_crate");
        let span = FileSpan::new(file, 10, 20);

        assert_eq!(span.len(), 10);
        assert!(!span.is_empty());
        assert!(span.contains(10));
        assert!(span.contains(19));
        assert!(!span.contains(20));
    }

    #[test]
    fn test_span_overlap() {
        let file = WorkspaceFilePath::new_for_test("src/lib.rs", "/workspace", "test_crate");
        let span1 = FileSpan::new(file.clone(), 10, 20);
        let span2 = FileSpan::new(file.clone(), 15, 25);
        let span3 = FileSpan::new(file.clone(), 20, 30);

        assert!(span1.overlaps(&span2));
        assert!(!span1.overlaps(&span3)); // Adjacent but not overlapping
    }
}