Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

src/vector/spans.rs

//! **Item spans — the extent of every item, nested.**
//!
//! The span MODEL: where an item starts and ends (1-based inclusive, docs and
//! attributes included), its nesting depth, and whether it is a leaf. Consumed by
//! the sub-clone detector (to label a match with its enclosing item and to run
//! R6) and by the chunker; the warehouse's missing `end_line` is
//! [`ItemSpan::end_line`].
//!
//! The `syn` walker that computed spans (`item_spans` / `leaf_spans`) was removed
//! with Mímir v2 (PLAN-MIMIR-V2 §7.3b); the rustc-driver dump supplies them
//! (lane 3). Until then every consumer is handed no spans and says so.

/// One item's extent, at any nesting depth.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ItemSpan {
    /// `fn`, `struct`, `enum`, `impl`, `mod`, `trait`, `method`, `const`, … —
    /// the same vocabulary `symbol_facts.item_kind` uses.
    pub kind: &'static str,
    /// The item's own name (`""` for an item that has none, e.g. a bare `use`).
    pub name: String,
    /// Dotted container path from the file root: `"Ledger"` for a method of
    /// `impl Ledger`, `"inner::Ledger"` inside `mod inner`. Empty at file level.
    pub path: String,
    /// First source line, 1-based inclusive (includes docs + attributes).
    pub start_line: usize,
    /// Last source line, 1-based inclusive. **This is the `end_line`
    /// `symbol_facts` has never had.**
    pub end_line: usize,
    /// Nesting depth: 0 for a top-level item, 1 for a method of a top-level
    /// `impl`, and so on.
    pub depth: usize,
    /// No further items were descended into — this is a body, not a container.
    pub is_leaf: bool,
}

impl ItemSpan {
    /// Source lines covered (1-based inclusive range height).
    pub fn lines(&self) -> usize {
        self.end_line.saturating_sub(self.start_line) + 1
    }
    /// `path::name` when nested, plain `name` at file level — a reading label.
    pub fn qualified(&self) -> String {
        if self.path.is_empty() {
            self.name.clone()
        } else {
            format!("{}::{}", self.path, self.name)
        }
    }
}

/// The innermost item containing `line`, if any — how a sub-item match gets a
/// human-readable home ("inside `Ledger::reconcile`") without that name ever
/// being an identity input.
pub fn enclosing<'a>(spans: &'a [ItemSpan], line: usize) -> Option<&'a ItemSpan> {
    spans
        .iter()
        .filter(|s| s.start_line <= line && line <= s.end_line)
        .max_by_key(|s| (s.depth, std::cmp::Reverse(s.lines())))
}

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

    fn span(kind: &'static str, name: &str, path: &str, s: usize, e: usize, depth: usize) -> ItemSpan {
        ItemSpan { kind, name: name.into(), path: path.into(), start_line: s, end_line: e, depth, is_leaf: depth > 0 }
    }

    #[test]
    fn enclosing_picks_the_innermost_item() {
        let spans = vec![
            span("impl", "Ledger", "", 3, 12, 0),
            span("method", "credit", "Ledger", 4, 6, 1),
            span("method", "debit", "Ledger", 8, 11, 1),
        ];
        assert_eq!(enclosing(&spans, 5).map(|s| s.qualified()), Some("Ledger::credit".into()));
        assert_eq!(enclosing(&spans, 7).map(|s| s.name.clone()), Some("Ledger".into()));
        assert!(enclosing(&spans, 20).is_none());
        assert_eq!(spans[2].lines(), 4);
    }
}