_diffctx/utility/
importance.rs1use std::path::Path;
8use std::sync::Arc;
9
10use rustc_hash::{FxHashMap, FxHashSet};
11
12use crate::config::importance::{
13 DEFAULT_IMPORTANCE, GENERATED_CAP, GENERATED_DIRS, PERIPHERAL_CAP, PERIPHERAL_DIRS,
14 PERIPHERAL_STEMS, PERIPHERAL_SUFFIXES,
15};
16use crate::types::Fragment;
17
18fn path_components_lower(path: &Path) -> FxHashSet<String> {
19 path.components()
20 .filter_map(|c| c.as_os_str().to_str())
21 .map(|s| s.to_lowercase())
22 .collect()
23}
24
25fn is_peripheral(path: &Path) -> bool {
26 let parts = path_components_lower(path);
27 if PERIPHERAL_DIRS.iter().any(|d| parts.contains(*d)) {
28 return true;
29 }
30 let stem = path
31 .file_stem()
32 .and_then(|s| s.to_str())
33 .unwrap_or("")
34 .to_lowercase();
35 if PERIPHERAL_STEMS.iter().any(|p| stem.starts_with(p)) {
36 return true;
37 }
38 if PERIPHERAL_SUFFIXES.iter().any(|s| stem.ends_with(s)) {
39 return true;
40 }
41 false
42}
43
44fn is_generated(path: &Path) -> bool {
45 let parts = path_components_lower(path);
46 GENERATED_DIRS.iter().any(|d| parts.contains(*d))
47}
48
49pub fn compute_file_importance(fragments: &[Fragment]) -> FxHashMap<Arc<str>, f64> {
55 let mut seen: FxHashSet<Arc<str>> = FxHashSet::default();
56 let mut out: FxHashMap<Arc<str>, f64> = FxHashMap::default();
57 for f in fragments {
58 let path_str = f.path();
59 let key: Arc<str> = Arc::from(path_str);
60 if !seen.insert(key.clone()) {
61 continue;
62 }
63 let path = Path::new(path_str);
64 let imp = if is_generated(path) {
65 GENERATED_CAP
66 } else if is_peripheral(path) {
67 PERIPHERAL_CAP
68 } else {
69 DEFAULT_IMPORTANCE
70 };
71 out.insert(key, imp);
72 }
73 out
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 fn classify(path_str: &str) -> f64 {
81 let path = Path::new(path_str);
82 if is_generated(path) {
83 GENERATED_CAP
84 } else if is_peripheral(path) {
85 PERIPHERAL_CAP
86 } else {
87 DEFAULT_IMPORTANCE
88 }
89 }
90
91 #[test]
92 fn default_for_production_paths() {
93 assert_eq!(classify("src/handler.ts"), DEFAULT_IMPORTANCE);
94 assert_eq!(classify("lib/auth/login.rs"), DEFAULT_IMPORTANCE);
95 assert_eq!(classify("server/main.go"), DEFAULT_IMPORTANCE);
96 }
97
98 #[test]
99 fn peripheral_for_examples_and_demo() {
100 assert_eq!(classify("examples/parsing.ts"), PERIPHERAL_CAP);
101 assert_eq!(classify("demo/usage.py"), PERIPHERAL_CAP);
102 assert_eq!(classify("vendor/lib/foo.go"), PERIPHERAL_CAP);
103 assert_eq!(classify("fixtures/sample.json"), PERIPHERAL_CAP);
104 assert_eq!(classify("docs/guide.md"), PERIPHERAL_CAP);
105 }
106
107 #[test]
108 fn peripheral_for_stem_patterns() {
109 assert_eq!(classify("tests/example_usage.py"), PERIPHERAL_CAP);
110 assert_eq!(classify("src/module_demo.rs"), PERIPHERAL_CAP);
111 assert_eq!(classify("scripts/sample_run.sh"), PERIPHERAL_CAP);
112 }
113
114 #[test]
115 fn generated_takes_precedence() {
116 assert_eq!(classify("generated/protobuf.rs"), GENERATED_CAP);
117 assert_eq!(classify("src/__generated__/api.ts"), GENERATED_CAP);
118 assert_eq!(classify("examples/__generated__/foo.py"), GENERATED_CAP);
120 }
121
122 #[test]
123 fn case_insensitive_directory_matching() {
124 assert_eq!(classify("Examples/foo.py"), PERIPHERAL_CAP);
125 assert_eq!(classify("VENDOR/lib.go"), PERIPHERAL_CAP);
126 assert_eq!(classify("Generated/api.ts"), GENERATED_CAP);
127 }
128
129 #[test]
130 fn ordering_is_stable_under_priors() {
131 assert!(DEFAULT_IMPORTANCE / PERIPHERAL_CAP >= 6.0);
133 assert!(PERIPHERAL_CAP / GENERATED_CAP >= 1.4);
135 assert!(GENERATED_CAP < 0.25);
137 assert!(PERIPHERAL_CAP < 0.25);
138 assert!(GENERATED_CAP > 0.0);
140 assert!(PERIPHERAL_CAP > 0.0);
141 }
142}