heddle_object_model/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 AmbiguousFileMove { candidate_paths: Vec<String> },
24 Unknown,
26}
27
28pub fn annotation_status_for_source(
30 annotation: &Annotation,
31 scope: &AnnotationScope,
32 source: &[u8],
33 file_path: &Path,
34) -> StalenessStatus {
35 annotation_status_for_source_with_symbol_resolver(
36 annotation,
37 scope,
38 source,
39 file_path,
40 resolve_current_symbol,
41 )
42}
43
44pub fn annotation_status_for_source_with_symbol_resolver(
46 annotation: &Annotation,
47 scope: &AnnotationScope,
48 source: &[u8],
49 file_path: &Path,
50 mut resolve_symbol: impl FnMut(&[u8], &Path, &str, Option<(u32, u32)>) -> Option<(u32, u32)>,
51) -> StalenessStatus {
52 let Some(revision) = annotation.current_revision() else {
53 return StalenessStatus::Unknown;
54 };
55 let expected_hash = match &revision.source_hash {
56 Some(h) => h,
57 None => return StalenessStatus::Unknown,
58 };
59
60 let scoped_bytes = match scope {
61 AnnotationScope::File => source.to_vec(),
62 AnnotationScope::Lines(start, end) => extract_line_range(source, *start, *end),
63 AnnotationScope::Symbol {
64 name,
65 resolved_lines,
66 } => match resolve_symbol(source, file_path, name, *resolved_lines) {
67 Some((start, end)) => extract_line_range(source, start, end),
68 None => {
69 return StalenessStatus::SymbolMissing {
70 symbol: name.clone(),
71 };
72 }
73 },
74 };
75
76 let current_hash = ContentHash::compute(&scoped_bytes);
77 if current_hash == *expected_hash {
78 StalenessStatus::Fresh
79 } else {
80 StalenessStatus::SourceChanged {
81 old_hash: *expected_hash,
82 new_hash: current_hash,
83 }
84 }
85}
86
87pub fn extract_line_range(source: &[u8], start: u32, end: u32) -> Vec<u8> {
92 let text = std::str::from_utf8(source).unwrap_or("");
93 let lines: Vec<&str> = text.lines().collect();
94 let start_idx = (start as usize).saturating_sub(1);
95 let end_idx = (end as usize).min(lines.len());
96 if start_idx >= end_idx {
97 return Vec::new();
98 }
99 lines[start_idx..end_idx].join("\n").into_bytes()
100}
101
102pub fn resolve_current_symbol(
107 _source: &[u8],
108 _file_path: &Path,
109 _symbol: &str,
110 stored: Option<(u32, u32)>,
111) -> Option<(u32, u32)> {
112 stored
113}