backhopper_core/compat/
behaviour_callback_resolve.rs1use std::collections::BTreeSet;
14
15use crate::compat::added_lines::{AddedLinesSubject, file_line};
16use crate::compat::qualified_call_resolve::{ModuleProvenance, TreeReader, classify_module};
17use crate::compat::source_attributes::{
18 extract_behaviours, extract_callbacks, extract_optional_callbacks,
19};
20use crate::model::names::{ModuleName, RelativePath};
21use crate::model::verdict::Reason;
22
23pub fn analyse_behaviour_callbacks(
31 subjects: &[AddedLinesSubject<'_>],
32 covered_modules: &BTreeSet<ModuleName>,
33 resolve_module_path: &dyn Fn(&ModuleName) -> Option<RelativePath>,
34 read_target: TreeReader<'_>,
35 read_source: Option<TreeReader<'_>>,
36) -> Vec<Reason> {
37 let Some(read_source) = read_source else {
38 return Vec::new();
39 };
40 let mut reasons = Vec::new();
41 for subject in subjects {
42 let mut seen = BTreeSet::new();
43 for declared in extract_behaviours(subject.added_text) {
44 if !seen.insert(declared.behaviour.clone()) {
45 continue;
46 }
47 let ModuleProvenance::FirstParty { path } =
48 classify_module(&declared.behaviour, covered_modules, resolve_module_path)
49 else {
50 continue;
51 };
52 let (Some(target_text), Some(source_text)) = (read_target(&path), read_source(&path))
53 else {
54 continue;
55 };
56 let target_callbacks = extract_callbacks(&target_text);
57 if target_callbacks.is_empty() {
58 continue;
59 }
60 let source_callbacks = extract_callbacks(&source_text);
61 let optional = extract_optional_callbacks(&target_text);
62 let line = file_line(subject.line_map, declared.line);
63 for (key, target_signature) in &target_callbacks {
64 match source_callbacks.get(key) {
65 None if !optional.contains(key) => {
66 reasons.push(Reason::BehaviourCallbackAddedOnTarget {
67 source_path: subject.source_path.clone(),
68 behaviour: declared.behaviour.clone(),
69 callback: key.0.clone(),
70 arity: key.1,
71 line,
72 });
73 }
74 Some(source_signature) if source_signature != target_signature => {
75 reasons.push(Reason::BehaviourCallbackDriftOnTarget {
76 source_path: subject.source_path.clone(),
77 behaviour: declared.behaviour.clone(),
78 callback: key.0.clone(),
79 arity: key.1,
80 source_signature: source_signature.clone(),
81 target_signature: target_signature.clone(),
82 line,
83 });
84 }
85 _ => {}
86 }
87 }
88 }
89 }
90 reasons
91}