objects/object/
staleness_core.rs1use std::path::Path;
5
6use super::{Annotation, AnnotationScope, ContentHash};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum StalenessStatus {
11 Fresh,
13 SourceChanged {
15 old_hash: ContentHash,
16 new_hash: ContentHash,
17 },
18 FileMissing,
20 SymbolMissing { symbol: String },
22 Unknown,
24}
25
26pub fn annotation_status_for_source(
28 annotation: &Annotation,
29 scope: &AnnotationScope,
30 source: &[u8],
31 file_path: &Path,
32) -> StalenessStatus {
33 annotation_status_for_source_with_symbol_resolver(
34 annotation,
35 scope,
36 source,
37 file_path,
38 resolve_current_symbol,
39 )
40}
41
42pub fn annotation_status_for_source_with_symbol_resolver(
44 annotation: &Annotation,
45 scope: &AnnotationScope,
46 source: &[u8],
47 file_path: &Path,
48 mut resolve_symbol: impl FnMut(&[u8], &Path, &str, Option<(u32, u32)>) -> Option<(u32, u32)>,
49) -> StalenessStatus {
50 let Some(revision) = annotation.current_revision() else {
51 return StalenessStatus::Unknown;
52 };
53 let expected_hash = match &revision.source_hash {
54 Some(h) => h,
55 None => return StalenessStatus::Unknown,
56 };
57
58 let scoped_bytes = match scope {
59 AnnotationScope::File => source.to_vec(),
60 AnnotationScope::Lines(start, end) => extract_line_range(source, *start, *end),
61 AnnotationScope::Symbol {
62 name,
63 resolved_lines,
64 } => match resolve_symbol(source, file_path, name, *resolved_lines) {
65 Some((start, end)) => extract_line_range(source, start, end),
66 None => {
67 return StalenessStatus::SymbolMissing {
68 symbol: name.clone(),
69 };
70 }
71 },
72 };
73
74 let current_hash = ContentHash::compute(&scoped_bytes);
75 if current_hash == *expected_hash {
76 StalenessStatus::Fresh
77 } else {
78 StalenessStatus::SourceChanged {
79 old_hash: *expected_hash,
80 new_hash: current_hash,
81 }
82 }
83}
84
85pub fn extract_line_range(source: &[u8], start: u32, end: u32) -> Vec<u8> {
90 let text = std::str::from_utf8(source).unwrap_or("");
91 let lines: Vec<&str> = text.lines().collect();
92 let start_idx = (start as usize).saturating_sub(1);
93 let end_idx = (end as usize).min(lines.len());
94 if start_idx >= end_idx {
95 return Vec::new();
96 }
97 lines[start_idx..end_idx].join("\n").into_bytes()
98}
99
100pub fn resolve_current_symbol(
105 _source: &[u8],
106 _file_path: &Path,
107 _symbol: &str,
108 stored: Option<(u32, u32)>,
109) -> Option<(u32, u32)> {
110 stored
111}